// 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()) { enabled = usermod[FPSTR(_enabled)].as(); } } } 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";