forked from MoonModules/WLED-MM
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added files to usermod directory and changes to platform.ini to suppo…
…rt Adafruit MAX17048 module. (MoonModules#2) Co-authored-by: Azots <78281612+Azots@users.noreply.github.com>
- Loading branch information
Showing
6 changed files
with
359 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
# Adafruit MAX17048 Usermod (LiPo & LiIon Battery Monitor & Fuel Gauge) | ||
This usermod reads information from an Adafruit MAX17048 and outputs the following: | ||
- Battery Voltage | ||
- Battery Level Percentage | ||
|
||
|
||
## Dependencies | ||
Libraries: | ||
- `Adafruit_BusIO@~1.14.5` (by [adafruit](https://github.com/adafruit/Adafruit_BusIO)) | ||
- `Adafruit_MAX1704X@~1.0.2` (by [adafruit](https://github.com/adafruit/Adafruit_MAX1704X)) | ||
|
||
These must be added under `lib_deps` in your `platform.ini` (or `platform_override.ini`). | ||
Data is published over MQTT - make sure you've enabled the MQTT sync interface. | ||
|
||
## Compilation | ||
|
||
To enable, compile with `USERMOD_MAX17048` define in the build_flags (e.g. in `platformio.ini` or `platformio_override.ini`) such as in the example below: | ||
```ini | ||
[env:usermod_max17048_d1_mini] | ||
extends = env:d1_mini | ||
build_flags = | ||
${common.build_flags_esp8266} | ||
-D USERMOD_MAX17048 | ||
lib_deps = | ||
${esp8266.lib_deps} | ||
https://github.com/adafruit/Adafruit_BusIO @ 1.14.5 | ||
https://github.com/adafruit/Adafruit_MAX1704X @ 1.0.2 | ||
``` | ||
|
||
### Configuration Options | ||
The following settings can be set at compile-time but are configurable on the usermod menu (except First Monitor time): | ||
- USERMOD_MAX17048_MIN_MONITOR_INTERVAL (the min number of milliseconds between checks, defaults to 10,000 ms) | ||
- USERMOD_MAX17048_MAX_MONITOR_INTERVAL (the max number of milliseconds between checks, defaults to 10,000 ms) | ||
- USERMOD_MAX17048_FIRST_MONITOR_AT | ||
|
||
|
||
Additionally, the Usermod Menu allows you to: | ||
- Enable or Disable the usermod | ||
- Enable or Disable Home Assistant Discovery (turn on/off to sent MQTT Discovery entries for Home Assistant) | ||
- Configure SCL/SDA GPIO Pins | ||
|
||
## API | ||
The following method is available to interact with the usermod from other code modules: | ||
- `getBatteryVoltageV` read the last battery voltage (in Volt) obtained from the sensor | ||
- `getBatteryPercent` reads the last battery percentage obtained from the sensor | ||
|
||
## MQTT | ||
MQTT topics are as follows (`<deviceTopic>` is set in MQTT section of Sync Setup menu): | ||
Measurement type | MQTT topic | ||
--- | --- | ||
Battery Voltage | `<deviceTopic>/batteryVoltage` | ||
Battery Percent | `<deviceTopic>/batteryPercent` | ||
|
||
## Authors | ||
Carlos Cruz [@ccruz09](https://github.com/ccruz09) | ||
|
||
|
||
## Revision History | ||
Jan 2024 | ||
- Added Home Assistant Discovery | ||
- Implemented PinManager to register pins | ||
- Added API call for other modules to read battery voltage and percentage | ||
- Added info-screen outputs | ||
- Updated `readme.md` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,281 @@ | ||
// force the compiler to show a warning to confirm that this file is included | ||
#warning **** Included USERMOD_MAX17048 V2.0 **** | ||
|
||
#pragma once | ||
|
||
#include "wled.h" | ||
#include "Adafruit_MAX1704X.h" | ||
|
||
|
||
// the max interval to check battery level, 10 seconds | ||
#ifndef USERMOD_MAX17048_MAX_MONITOR_INTERVAL | ||
#define USERMOD_MAX17048_MAX_MONITOR_INTERVAL 10000 | ||
#endif | ||
|
||
// the min interval to check battery level, 500 ms | ||
#ifndef USERMOD_MAX17048_MIN_MONITOR_INTERVAL | ||
#define USERMOD_MAX17048_MIN_MONITOR_INTERVAL 500 | ||
#endif | ||
|
||
// how many seconds after boot to perform the first check, 10 seconds | ||
#ifndef USERMOD_MAX17048_FIRST_MONITOR_AT | ||
#define USERMOD_MAX17048_FIRST_MONITOR_AT 10000 | ||
#endif | ||
|
||
/* | ||
* Usermod to display Battery Life using Adafruit's MAX17048 LiPoly/ LiIon Fuel Gauge and Battery Monitor. | ||
*/ | ||
class Usermod_MAX17048 : public Usermod { | ||
|
||
private: | ||
|
||
bool enabled = true; | ||
|
||
unsigned long maxReadingInterval = USERMOD_MAX17048_MAX_MONITOR_INTERVAL; | ||
unsigned long minReadingInterval = USERMOD_MAX17048_MIN_MONITOR_INTERVAL; | ||
unsigned long lastCheck = UINT32_MAX - (USERMOD_MAX17048_MAX_MONITOR_INTERVAL - USERMOD_MAX17048_FIRST_MONITOR_AT); | ||
unsigned long lastSend = UINT32_MAX - (USERMOD_MAX17048_MAX_MONITOR_INTERVAL - USERMOD_MAX17048_FIRST_MONITOR_AT); | ||
|
||
|
||
uint8_t VoltageDecimals = 3; // Number of decimal places in published voltage values | ||
uint8_t PercentDecimals = 1; // Number of decimal places in published percent values | ||
|
||
// string that are used multiple time (this will save some flash memory) | ||
static const char _name[]; | ||
static const char _enabled[]; | ||
static const char _maxReadInterval[]; | ||
static const char _minReadInterval[]; | ||
static const char _HomeAssistantDiscovery[]; | ||
|
||
bool monitorFound = false; | ||
bool firstReadComplete = false; | ||
bool initDone = false; | ||
|
||
Adafruit_MAX17048 maxLipo; | ||
float lastBattVoltage = -10; | ||
float lastBattPercent = -1; | ||
|
||
// MQTT and Home Assistant Variables | ||
bool HomeAssistantDiscovery = false; // Publish Home Assistant Device Information | ||
bool mqttInitialized = false; | ||
|
||
void _mqttInitialize() | ||
{ | ||
char mqttBatteryVoltageTopic[128]; | ||
char mqttBatteryPercentTopic[128]; | ||
|
||
snprintf_P(mqttBatteryVoltageTopic, 127, PSTR("%s/batteryVoltage"), mqttDeviceTopic); | ||
snprintf_P(mqttBatteryPercentTopic, 127, PSTR("%s/batteryPercent"), mqttDeviceTopic); | ||
|
||
if (HomeAssistantDiscovery) { | ||
_createMqttSensor(F("BatteryVoltage"), mqttBatteryVoltageTopic, "voltage", F("V")); | ||
_createMqttSensor(F("BatteryPercent"), mqttBatteryPercentTopic, "battery", F("%")); | ||
} | ||
} | ||
|
||
void _createMqttSensor(const String &name, const String &topic, const String &deviceClass, const String &unitOfMeasurement) | ||
{ | ||
String t = String(F("homeassistant/sensor/")) + mqttClientID + F("/") + name + F("/config"); | ||
|
||
StaticJsonDocument<600> doc; | ||
|
||
doc[F("name")] = String(serverDescription) + " " + name; | ||
doc[F("state_topic")] = topic; | ||
doc[F("unique_id")] = String(mqttClientID) + name; | ||
if (unitOfMeasurement != "") | ||
doc[F("unit_of_measurement")] = unitOfMeasurement; | ||
if (deviceClass != "") | ||
doc[F("device_class")] = deviceClass; | ||
doc[F("expire_after")] = 1800; | ||
|
||
JsonObject device = doc.createNestedObject(F("device")); // attach the sensor to the same device | ||
device[F("name")] = serverDescription; | ||
device[F("identifiers")] = "wled-sensor-" + String(mqttClientID); | ||
device[F("manufacturer")] = F("WLED"); | ||
device[F("model")] = F("FOSS"); | ||
device[F("sw_version")] = versionString; | ||
|
||
String temp; | ||
serializeJson(doc, temp); | ||
DEBUG_PRINTLN(t); | ||
DEBUG_PRINTLN(temp); | ||
|
||
mqtt->publish(t.c_str(), 0, true, temp.c_str()); | ||
} | ||
|
||
void publishMqtt(const char *topic, const char* state) { | ||
#ifndef WLED_DISABLE_MQTT | ||
//Check if MQTT Connected, otherwise it will crash the 8266 | ||
if (WLED_MQTT_CONNECTED){ | ||
char subuf[128]; | ||
snprintf_P(subuf, 127, PSTR("%s/%s"), mqttDeviceTopic, topic); | ||
mqtt->publish(subuf, 0, false, state); | ||
} | ||
#endif | ||
} | ||
|
||
public: | ||
|
||
inline void enable(bool enable) { enabled = enable; } | ||
|
||
inline bool isEnabled() { return enabled; } | ||
|
||
void setup() { | ||
// do your set-up here | ||
if (i2c_scl<0 || i2c_sda<0) { enabled = false; return; } | ||
monitorFound = maxLipo.begin(); | ||
initDone = true; | ||
} | ||
|
||
void loop() { | ||
// if usermod is disabled or called during strip updating just exit | ||
// NOTE: on very long strips strip.isUpdating() may always return true so update accordingly | ||
if (!enabled || strip.isUpdating()) return; | ||
|
||
unsigned long now = millis(); | ||
|
||
if (now - lastCheck < minReadingInterval) { return; } | ||
|
||
bool shouldUpdate = now - lastSend > maxReadingInterval; | ||
|
||
float battVoltage = maxLipo.cellVoltage(); | ||
float battPercent = maxLipo.cellPercent(); | ||
lastCheck = millis(); | ||
firstReadComplete = true; | ||
|
||
if (shouldUpdate) | ||
{ | ||
lastBattVoltage = roundf(battVoltage * powf(10, VoltageDecimals)) / powf(10, VoltageDecimals); | ||
lastBattPercent = roundf(battPercent * powf(10, PercentDecimals)) / powf(10, PercentDecimals); | ||
lastSend = millis(); | ||
|
||
publishMqtt("batteryVoltage", String(lastBattVoltage, VoltageDecimals).c_str()); | ||
publishMqtt("batteryPercent", String(lastBattPercent, PercentDecimals).c_str()); | ||
DEBUG_PRINTLN(F("Battery Voltage: ") + String(lastBattVoltage, VoltageDecimals) + F("V")); | ||
DEBUG_PRINTLN(F("Battery Percent: ") + String(lastBattPercent, PercentDecimals) + F("%")); | ||
} | ||
} | ||
|
||
void onMqttConnect(bool sessionPresent) | ||
{ | ||
if (WLED_MQTT_CONNECTED && !mqttInitialized) | ||
{ | ||
_mqttInitialize(); | ||
mqttInitialized = true; | ||
} | ||
} | ||
|
||
inline float getBatteryVoltageV() { | ||
return (float) lastBattVoltage; | ||
} | ||
|
||
inline float getBatteryPercent() { | ||
return (float) lastBattPercent; | ||
} | ||
|
||
void addToJsonInfo(JsonObject& root) | ||
{ | ||
// if "u" object does not exist yet wee need to create it | ||
JsonObject user = root["u"]; | ||
if (user.isNull()) user = root.createNestedObject("u"); | ||
|
||
|
||
JsonArray battery_json = user.createNestedArray(F("Battery Monitor")); | ||
if (!enabled) { | ||
battery_json.add(F("Disabled")); | ||
} | ||
else if(!monitorFound) { | ||
battery_json.add(F("MAX17048 Not Found")); | ||
} | ||
else if (!firstReadComplete) { | ||
// if we haven't read the sensor yet, let the user know | ||
// that we are still waiting for the first measurement | ||
battery_json.add((USERMOD_MAX17048_FIRST_MONITOR_AT - millis()) / 1000); | ||
battery_json.add(F(" sec until read")); | ||
} else { | ||
battery_json.add(F("Enabled")); | ||
JsonArray voltage_json = user.createNestedArray(F("Battery Voltage")); | ||
voltage_json.add(lastBattVoltage); | ||
voltage_json.add(F("V")); | ||
JsonArray percent_json = user.createNestedArray(F("Battery Percent")); | ||
percent_json.add(lastBattPercent); | ||
percent_json.add(F("%")); | ||
} | ||
} | ||
|
||
void addToJsonState(JsonObject& root) | ||
{ | ||
JsonObject usermod = root[FPSTR(_name)]; | ||
if (usermod.isNull()) | ||
{ | ||
usermod = root.createNestedObject(FPSTR(_name)); | ||
} | ||
usermod[FPSTR(_enabled)] = enabled; | ||
} | ||
|
||
void readFromJsonState(JsonObject& root) | ||
{ | ||
JsonObject usermod = root[FPSTR(_name)]; | ||
if (!usermod.isNull()) | ||
{ | ||
if (usermod[FPSTR(_enabled)].is<bool>()) | ||
{ | ||
enabled = usermod[FPSTR(_enabled)].as<bool>(); | ||
} | ||
} | ||
} | ||
|
||
void addToConfig(JsonObject& root) | ||
{ | ||
JsonObject top = root.createNestedObject(FPSTR(_name)); | ||
top[FPSTR(_enabled)] = enabled; | ||
top[FPSTR(_maxReadInterval)] = maxReadingInterval; | ||
top[FPSTR(_minReadInterval)] = minReadingInterval; | ||
top[FPSTR(_HomeAssistantDiscovery)] = HomeAssistantDiscovery; | ||
DEBUG_PRINT(F(_name)); | ||
DEBUG_PRINTLN(F(" config saved.")); | ||
} | ||
|
||
bool readFromConfig(JsonObject& root) | ||
{ | ||
JsonObject top = root[FPSTR(_name)]; | ||
|
||
if (top.isNull()) { | ||
DEBUG_PRINT(F(_name)); | ||
DEBUG_PRINTLN(F(": No config found. (Using defaults.)")); | ||
return false; | ||
} | ||
|
||
bool configComplete = !top.isNull(); | ||
|
||
configComplete &= getJsonValue(top[FPSTR(_enabled)], enabled); | ||
configComplete &= getJsonValue(top[FPSTR(_maxReadInterval)], maxReadingInterval, USERMOD_MAX17048_MAX_MONITOR_INTERVAL); | ||
configComplete &= getJsonValue(top[FPSTR(_minReadInterval)], minReadingInterval, USERMOD_MAX17048_MIN_MONITOR_INTERVAL); | ||
configComplete &= getJsonValue(top[FPSTR(_HomeAssistantDiscovery)], HomeAssistantDiscovery, false); | ||
|
||
DEBUG_PRINT(FPSTR(_name)); | ||
if (!initDone) { | ||
// first run: reading from cfg.json | ||
DEBUG_PRINTLN(F(" config loaded.")); | ||
} else { | ||
DEBUG_PRINTLN(F(" config (re)loaded.")); | ||
// changing parameters from settings page | ||
} | ||
|
||
return configComplete; | ||
} | ||
|
||
uint16_t getId() | ||
{ | ||
return USERMOD_ID_MAX17048; | ||
} | ||
|
||
}; | ||
|
||
|
||
// add more strings here to reduce flash memory usage | ||
const char Usermod_MAX17048::_name[] PROGMEM = "Adafruit MAX17048 Battery Monitor"; | ||
const char Usermod_MAX17048::_enabled[] PROGMEM = "enabled"; | ||
const char Usermod_MAX17048::_maxReadInterval[] PROGMEM = "max-read-interval-ms"; | ||
const char Usermod_MAX17048::_minReadInterval[] PROGMEM = "min-read-interval-ms"; | ||
const char Usermod_MAX17048::_HomeAssistantDiscovery[] PROGMEM = "HomeAssistantDiscovery"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters