From 3d34802ab22ab83ce6ee36f2b19c6594d52d300a Mon Sep 17 00:00:00 2001 From: Michael Bisbjerg Date: Wed, 15 May 2024 23:27:56 +0200 Subject: [PATCH 1/6] Initial ina226 --- usermods/INA226_v2/README.md | 36 ++ usermods/INA226_v2/platformio_override.ini | 9 + usermods/INA226_v2/usermod_ina226.h | 416 +++++++++++++++++++++ wled00/const.h | 1 + wled00/usermods_list.cpp | 8 + 5 files changed, 470 insertions(+) create mode 100644 usermods/INA226_v2/README.md create mode 100644 usermods/INA226_v2/platformio_override.ini create mode 100644 usermods/INA226_v2/usermod_ina226.h diff --git a/usermods/INA226_v2/README.md b/usermods/INA226_v2/README.md new file mode 100644 index 000000000..6d17eda70 --- /dev/null +++ b/usermods/INA226_v2/README.md @@ -0,0 +1,36 @@ +# Usermod AHT10 +This Usermod is designed to read a `AHT10`, `AHT15` or `AHT20` sensor and output the following: +- Temperature +- Humidity + +Configuration is performed via the Usermod menu. The following settings can be configured in the Usermod Menu: +- I2CAddress: The i2c address in decimal. Set it to either 56 (0x38, the default) or 57 (0x39). +- SensorType, one of: + - 0 - AHT10 + - 1 - AHT15 + - 2 - AHT20 +- CheckInterval: Number of seconds between readings +- Decimals: Number of decimals to put in the output + +Dependencies, These must be added under `lib_deps` in your `platform.ini` (or `platform_override.ini`). +- Libraries + - `enjoyneering/AHT10@~1.1.0` (by [enjoyneering](https://registry.platformio.org/libraries/enjoyneering/AHT10)) + - `Wire` + +## Author +[@LordMike](https://github.com/LordMike) + +# Compiling + +To enable, compile with `USERMOD_AHT10` defined (e.g. in `platformio_override.ini`) +```ini +[env:ina226_example] +extends = env:esp32dev +build_flags = + ${common.build_flags} ${esp32.build_flags} + -D USERMOD_INA226 + ; -D USERMOD_INA226_DEBUG ; -- add a debug status to the info modal +lib_deps = + ${esp32.lib_deps} + wollewald/INA226_WE@~1.2.9 +``` diff --git a/usermods/INA226_v2/platformio_override.ini b/usermods/INA226_v2/platformio_override.ini new file mode 100644 index 000000000..885b2dd1e --- /dev/null +++ b/usermods/INA226_v2/platformio_override.ini @@ -0,0 +1,9 @@ +[env:ina226_example] +extends = env:esp32dev +build_flags = + ${common.build_flags} ${esp32.build_flags} + -D USERMOD_INA226 + ; -D USERMOD_INA226_DEBUG ; -- add a debug status to the info modal +lib_deps = + ${esp32.lib_deps} + wollewald/INA226_WE@~1.2.9 \ No newline at end of file diff --git a/usermods/INA226_v2/usermod_ina226.h b/usermods/INA226_v2/usermod_ina226.h new file mode 100644 index 000000000..0ce286114 --- /dev/null +++ b/usermods/INA226_v2/usermod_ina226.h @@ -0,0 +1,416 @@ +#pragma once + +#include "wled.h" +#include + +#define INA226_ADDRESS 0x40 // Default I2C address for INA226 + +class UsermodINA226 : public Usermod +{ +private: + static const char _name[]; + + unsigned long _lastLoopCheck = 0; + + bool _settingEnabled : 1; // Enable the usermod + bool _mqttPublish : 1; // Publish MQTT values + bool _mqttPublishAlways : 1; // Publish always, regardless if there is a change + bool _mqttHomeAssistant : 1; // Enable Home Assistant docs + bool _initDone : 1; // Initialization is done + + uint8_t _i2cAddress = INA226_ADDRESS; + uint16_t _checkInterval = 60000; // milliseconds, user settings is in seconds + float _decimalFactor = 100; // a power of 10 factor. 1 would be no change, 10 is one decimal, 100 is two etc. User sees a power of 10 (0, 1, 2, ..) + uint16_t _shuntResistor = 1000; // Shunt resistor value in milliohms + uint16_t _currentRange = 1000; // Expected maximum current in milliamps + + uint8_t _lastStatus = 0; + float _lastCurrent = 0; + float _lastVoltage = 0; + float _lastPower = 0; + float _lastShuntVoltage = 0; + bool _lastOverflow = false; + +#ifndef WLED_MQTT_DISABLE + float _lastCurrentSent = 0; + float _lastVoltageSent = 0; + float _lastPowerSent = 0; + float _lastShuntVoltageSent = 0; + bool _lastOverflowSent = false; +#endif + + INA226_WE *_ina226 = nullptr; + + float truncateDecimals(float val) + { + return roundf(val * _decimalFactor) / _decimalFactor; + } + + void setOptimalSettings() + { + INA226_AVERAGES avg; + INA226_CONV_TIME conversionTime; + + // Identify the combination of samples and conversion times that will provide us with a measurement within our specified check interval. + // The two values will define how stable a measurement is (number of samples) and how much time can be used to calculate on it + // (conversion time). The calculation is: + // `Samples * ConversionTime * 2` + // + // This table shows all possible combinations and the time it'll take. + // | Conversion Time (μs) | 1 Sample | 4 Samples | 16 Samples | 64 Samples | 128 Samples | 256 Samples | 512 Samples | 1024 Samples | + // |----------------------|----------|-----------|------------|------------|-------------|-------------|-------------|--------------| + // | 140 | 0.28 ms | 1.12 ms | 4.48 ms | 17.92 ms | 35.84 ms | 71.68 ms | 143.36 ms | 286.72 ms | + // | 204 | 0.408 ms | 1.632 ms | 6.528 ms | 26.112 ms | 52.224 ms | 104.448 ms | 208.896 ms | 417.792 ms | + // | 332 | 0.664 ms | 2.656 ms | 10.624 ms | 42.496 ms | 84.992 ms | 169.984 ms | 339.968 ms | 679.936 ms | + // | 588 | 1.176 ms | 4.704 ms | 18.816 ms | 75.264 ms | 150.528 ms | 301.056 ms | 602.112 ms | 1204.224 ms | + // | 1100 | 2.2 ms | 8.8 ms | 35.2 ms | 140.8 ms | 281.6 ms | 563.2 ms | 1126.4 ms | 2252.8 ms | + // | 2116 | 4.232 ms | 16.928 ms | 67.712 ms | 270.848 ms | 541.696 ms | 1083.392 ms | 2166.784 ms | 4333.568 ms | + // | 4156 | 8.312 ms | 33.248 ms | 132.992 ms | 531.968 ms | 1063.936 ms | 2127.872 ms | 4255.744 ms | 8511.488 ms | + // | 8244 | 16.488 ms| 65.952 ms | 263.808 ms | 1055.232 ms| 2110.464 ms | 4220.928 ms | 8441.856 ms | 16883.712 ms | + + // The below determines which number of average samples to use, because this number is likely most important, and then finds the max conversion time. + if (_checkInterval >= 5000) + { + avg = AVERAGE_1024; + if (_checkInterval > 17000) + conversionTime = CONV_TIME_8244; + else + conversionTime = CONV_TIME_4156; + } + else if (_checkInterval >= 2000) + { + avg = AVERAGE_512; + if (_checkInterval > 3000) + conversionTime = CONV_TIME_2116; + else + conversionTime = CONV_TIME_1100; + } + else + { + // Always 1 second or more + + avg = AVERAGE_256; + if (_checkInterval >= 3000) + conversionTime = CONV_TIME_4156; + else if (_checkInterval >= 2000) + conversionTime = CONV_TIME_2116; + else + conversionTime = CONV_TIME_1100; + } + + _ina226->setAverage(avg); + _ina226->setConversionTime(conversionTime); + } + + void initializeINA226() + { + if (_ina226 != nullptr) + { + delete _ina226; + } + + _ina226 = new INA226_WE(_i2cAddress); + if (!_ina226->init()) + { + DEBUG_PRINTLN(F("INA226 initialization failed!")); + return; + } + _ina226->setCorrectionFactor(1.0); + setOptimalSettings(); + _ina226->setMeasureMode(CONTINUOUS); + _ina226->setResistorRange(static_cast(_shuntResistor) / 1000.0, static_cast(_currentRange) / 1000.0); + } + + ~UsermodINA226() + { + delete _ina226; + _ina226 = nullptr; + } + +#ifndef WLED_DISABLE_MQTT + void mqttInitialize() + { + if (!WLED_MQTT_CONNECTED || !_mqttPublish || !_mqttHomeAssistant) + return; + + char topic[128]; + snprintf_P(topic, 127, "%s/current", mqttDeviceTopic); + mqttCreateHassSensor(F("Current"), topic, F("current"), F("A")); + + snprintf_P(topic, 127, "%s/voltage", mqttDeviceTopic); + mqttCreateHassSensor(F("Voltage"), topic, F("voltage"), F("V")); + + snprintf_P(topic, 127, "%s/power", mqttDeviceTopic); + mqttCreateHassSensor(F("Power"), topic, F("power"), F("W")); + + snprintf_P(topic, 127, "%s/shunt_voltage", mqttDeviceTopic); + mqttCreateHassSensor(F("Shunt Voltage"), topic, F("voltage"), F("V")); + + snprintf_P(topic, 127, "%s/overflow", mqttDeviceTopic); + mqttCreateHassBinarySensor(F("Overflow"), topic); + } + + void mqttPublishIfChanged(const __FlashStringHelper *topic, float &lastState, float state, float minChange) + { + if (WLED_MQTT_CONNECTED && _mqttPublish && (_mqttPublishAlways || fabsf(lastState - state) > minChange)) + { + char subuf[128]; + snprintf_P(subuf, 127, PSTR("%s/%s"), mqttDeviceTopic, (const char *)topic); + mqtt->publish(subuf, 0, false, String(state).c_str()); + + lastState = state; + } + } + + void mqttPublishIfChanged(const __FlashStringHelper *topic, bool &lastState, bool state) + { + if (WLED_MQTT_CONNECTED && _mqttPublish && (_mqttPublishAlways || lastState != state)) + { + char subuf[128]; + snprintf_P(subuf, 127, PSTR("%s/%s"), mqttDeviceTopic, (const char *)topic); + mqtt->publish(subuf, 0, false, state ? "true" : "false"); + + lastState = state; + } + } + + void mqttCreateHassSensor(const String &name, const String &topic, const String &deviceClass, const String &unitOfMeasurement) + { + String t = String(F("homeassistant/sensor/")) + mqttClientID + "/" + name + F("/config"); + + StaticJsonDocument<600> doc; + + doc[F("name")] = 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")); + device[F("name")] = serverDescription; + device[F("identifiers")] = "wled-sensor-" + String(mqttClientID); + device[F("manufacturer")] = F(WLED_BRAND); + device[F("model")] = F(WLED_PRODUCT_NAME); + 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 mqttCreateHassBinarySensor(const String &name, const String &topic) + { + String t = String(F("homeassistant/binary_sensor/")) + mqttClientID + "/" + name + F("/config"); + + StaticJsonDocument<600> doc; + + doc[F("name")] = name; + doc[F("state_topic")] = topic; + doc[F("unique_id")] = String(mqttClientID) + name; + + JsonObject device = doc.createNestedObject(F("device")); + device[F("name")] = serverDescription; + device[F("identifiers")] = "wled-sensor-" + String(mqttClientID); + device[F("manufacturer")] = F(WLED_BRAND); + device[F("model")] = F(WLED_PRODUCT_NAME); + 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()); + } +#endif + +public: + void setup() + { + initializeINA226(); + } + + void loop() + { + if (!_settingEnabled || strip.isUpdating()) + return; + + unsigned long currentTime = millis(); + + if (currentTime - _lastLoopCheck < _checkInterval) + return; + _lastLoopCheck = currentTime; + + _lastStatus = _ina226->getI2cErrorCode(); + + if (_lastStatus == 0) + { + float current = truncateDecimals(_ina226->getCurrent_mA() / 1000.0); + float voltage = truncateDecimals(_ina226->getBusVoltage_V()); + float power = truncateDecimals(_ina226->getBusPower() / 1000.0); // Correct power value to W + float shuntVoltage = truncateDecimals(_ina226->getShuntVoltage_V()); + bool overflow = _ina226->overflow; + +#ifndef WLED_DISABLE_MQTT + mqttPublishIfChanged(F("current"), _lastCurrentSent, current, 0.01f); + mqttPublishIfChanged(F("voltage"), _lastVoltageSent, voltage, 0.01f); + mqttPublishIfChanged(F("power"), _lastPowerSent, power, 0.1f); + mqttPublishIfChanged(F("shunt_voltage"), _lastShuntVoltageSent, shuntVoltage, 0.01f); + mqttPublishIfChanged(F("overflow"), _lastOverflowSent, overflow); +#endif + + _lastCurrent = current; + _lastVoltage = voltage; + _lastPower = power; + _lastShuntVoltage = shuntVoltage; + _lastOverflow = overflow; + } + } + +#ifndef WLED_DISABLE_MQTT + void onMqttConnect(bool sessionPresent) + { + mqttInitialize(); + } +#endif + + uint16_t getId() + { + return USERMOD_ID_INA226; + } + + void addToJsonInfo(JsonObject &root) override + { + JsonObject user = root["u"]; + if (user.isNull()) + user = root.createNestedObject("u"); + + JsonArray jsonCurrent = user.createNestedArray(F("Current")); + JsonArray jsonVoltage = user.createNestedArray(F("Voltage")); + JsonArray jsonPower = user.createNestedArray(F("Power")); + JsonArray jsonShuntVoltage = user.createNestedArray(F("Shunt Voltage")); + JsonArray jsonOverflow = user.createNestedArray(F("Overflow")); + + if (_lastLoopCheck == 0) + { + jsonCurrent.add(F("Not read yet")); + jsonVoltage.add(F("Not read yet")); + jsonPower.add(F("Not read yet")); + jsonShuntVoltage.add(F("Not read yet")); + jsonOverflow.add(F("Not read yet")); + return; + } + + if (_lastStatus != 0) + { + jsonCurrent.add(F("An error occurred")); + jsonVoltage.add(F("An error occurred")); + jsonPower.add(F("An error occurred")); + jsonShuntVoltage.add(F("An error occurred")); + jsonOverflow.add(F("An error occurred")); + return; + } + + jsonCurrent.add(_lastCurrent); + jsonCurrent.add(F("A")); + + jsonVoltage.add(_lastVoltage); + jsonVoltage.add(F("V")); + + jsonPower.add(_lastPower); + jsonPower.add(F("W")); + + jsonShuntVoltage.add(_lastShuntVoltage); + jsonShuntVoltage.add(F("V")); + + jsonOverflow.add(_lastOverflow ? F("true") : F("false")); + } + + void addToConfig(JsonObject &root) + { + JsonObject top = root.createNestedObject(FPSTR(_name)); + top[F("Enabled")] = _settingEnabled; + top[F("I2CAddress")] = static_cast(_i2cAddress); + top[F("CheckInterval")] = _checkInterval / 1000; + top[F("Decimals")] = log10f(_decimalFactor); + top[F("ShuntResistor")] = _shuntResistor; + top[F("CurrentRange")] = _currentRange; +#ifndef WLED_DISABLE_MQTT + top[F("MqttPublish")] = _mqttPublish; + top[F("MqttPublishAlways")] = _mqttPublishAlways; + top[F("MqttHomeAssistantDiscovery")] = _mqttHomeAssistant; +#endif + + DEBUG_PRINTLN(F("INA226 config saved.")); + } + + bool readFromConfig(JsonObject &root) override + { + JsonObject top = root[FPSTR(_name)]; + + bool configComplete = !top.isNull(); + if (!configComplete) + return false; + + bool tmpBool = false; + configComplete &= getJsonValue(top[F("Enabled")], tmpBool); + if (configComplete) + _settingEnabled = tmpBool; + + configComplete &= getJsonValue(top[F("I2CAddress")], _i2cAddress); + configComplete &= getJsonValue(top[F("CheckInterval")], _checkInterval); + if (configComplete) + { + if (1 <= _checkInterval && _checkInterval <= 600) + _checkInterval *= 1000; + else + _checkInterval = 60000; + } + + configComplete &= getJsonValue(top[F("Decimals")], _decimalFactor); + if (configComplete) + { + if (0 <= _decimalFactor && _decimalFactor <= 5) + _decimalFactor = pow10f(_decimalFactor); + else + _decimalFactor = 100; + } + + configComplete &= getJsonValue(top[F("ShuntResistor")], _shuntResistor); + configComplete &= getJsonValue(top[F("CurrentRange")], _currentRange); + +#ifndef WLED_DISABLE_MQTT + configComplete &= getJsonValue(top[F("MqttPublish")], tmpBool); + if (configComplete) + _mqttPublish = tmpBool; + + configComplete &= getJsonValue(top[F("MqttPublishAlways")], tmpBool); + if (configComplete) + _mqttPublishAlways = tmpBool; + + configComplete &= getJsonValue(top[F("MqttHomeAssistantDiscovery")], tmpBool); + if (configComplete) + _mqttHomeAssistant = tmpBool; +#endif + + if (_initDone) + { + initializeINA226(); + +#ifndef WLED_DISABLE_MQTT + mqttInitialize(); +#endif + } + + _initDone = true; + return configComplete; + } +}; + +const char UsermodINA226::_name[] PROGMEM = "INA226"; diff --git a/wled00/const.h b/wled00/const.h index a44e67598..2f48d32a4 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -178,6 +178,7 @@ #define USERMOD_ID_HTTP_PULL_LIGHT_CONTROL 46 //usermod "usermod_v2_HttpPullLightControl.h" #define USERMOD_ID_TETRISAI 47 //Usermod "usermod_v2_tetris.h" #define USERMOD_ID_MAX17048 48 //Usermod "usermod_max17048.h" +#define USERMOD_ID_INA226 50 //Usermod "usermod_ina226.h" //Access point behavior #define AP_BEHAVIOR_BOOT_NO_CONN 0 //Open AP when no connection after boot diff --git a/wled00/usermods_list.cpp b/wled00/usermods_list.cpp index 3f34d18fe..3dcbc9f0a 100644 --- a/wled00/usermods_list.cpp +++ b/wled00/usermods_list.cpp @@ -217,6 +217,10 @@ #include "../usermods/TetrisAI_v2/usermod_v2_tetrisai.h" #endif +#ifdef USERMOD_INA226 + #include "../usermods/INA226_v2/usermod_ina226.h" +#endif + void registerUsermods() { /* @@ -421,4 +425,8 @@ void registerUsermods() #ifdef USERMOD_TETRISAI usermods.add(new TetrisAIUsermod()); #endif + + #ifdef USERMOD_INA226 + usermods.add(new UsermodINA226()); + #endif } From d24cf14009ad632b89d4b119e0c093f7010acab8 Mon Sep 17 00:00:00 2001 From: Michael Bisbjerg Date: Thu, 16 May 2024 00:06:41 +0200 Subject: [PATCH 2/6] Triggered & continuous modes --- usermods/INA226_v2/usermod_ina226.h | 182 +++++++++++++++++++++++----- 1 file changed, 150 insertions(+), 32 deletions(-) diff --git a/usermods/INA226_v2/usermod_ina226.h b/usermods/INA226_v2/usermod_ina226.h index 0ce286114..752a68c3a 100644 --- a/usermods/INA226_v2/usermod_ina226.h +++ b/usermods/INA226_v2/usermod_ina226.h @@ -11,12 +11,15 @@ private: static const char _name[]; unsigned long _lastLoopCheck = 0; + unsigned long _lastCheckTime = 0; - bool _settingEnabled : 1; // Enable the usermod - bool _mqttPublish : 1; // Publish MQTT values - bool _mqttPublishAlways : 1; // Publish always, regardless if there is a change - bool _mqttHomeAssistant : 1; // Enable Home Assistant docs - bool _initDone : 1; // Initialization is done + bool _settingEnabled : 1; // Enable the usermod + bool _mqttPublish : 1; // Publish MQTT values + bool _mqttPublishAlways : 1; // Publish always, regardless if there is a change + bool _mqttHomeAssistant : 1; // Enable Home Assistant docs + bool _initDone : 1; // Initialization is done + bool _isTriggeredOperationMode : 1; // false = continuous, true = triggered + bool _measurementTriggered : 1; // if triggered mode, then true indicates we're waiting for measurements uint8_t _i2cAddress = INA226_ADDRESS; uint16_t _checkInterval = 60000; // milliseconds, user settings is in seconds @@ -32,6 +35,9 @@ private: bool _lastOverflow = false; #ifndef WLED_MQTT_DISABLE + uint16_t _debugAverages; + uint16_t _debugConversionTime; + float _lastCurrentSent = 0; float _lastVoltageSent = 0; float _lastPowerSent = 0; @@ -50,6 +56,8 @@ private: { INA226_AVERAGES avg; INA226_CONV_TIME conversionTime; + uint16_t debugAveragesValue = 0; + uint16_t debugConversionTimeValue = 0; // Identify the combination of samples and conversion times that will provide us with a measurement within our specified check interval. // The two values will define how stable a measurement is (number of samples) and how much time can be used to calculate on it @@ -72,34 +80,63 @@ private: if (_checkInterval >= 5000) { avg = AVERAGE_1024; + debugAveragesValue = 1024; if (_checkInterval > 17000) + { conversionTime = CONV_TIME_8244; + debugConversionTimeValue = 8244; + } else + { conversionTime = CONV_TIME_4156; + debugConversionTimeValue = 4156; + } } else if (_checkInterval >= 2000) { avg = AVERAGE_512; + debugAveragesValue = 512; if (_checkInterval > 3000) + { conversionTime = CONV_TIME_2116; + debugConversionTimeValue = 2116; + } else + { conversionTime = CONV_TIME_1100; + debugConversionTimeValue = 1100; + } } else { // Always 1 second or more avg = AVERAGE_256; + debugAveragesValue = 256; if (_checkInterval >= 3000) + { conversionTime = CONV_TIME_4156; + debugConversionTimeValue = 4156; + } else if (_checkInterval >= 2000) + { conversionTime = CONV_TIME_2116; + debugConversionTimeValue = 2116; + } else + { conversionTime = CONV_TIME_1100; + debugConversionTimeValue = 1100; + } } _ina226->setAverage(avg); _ina226->setConversionTime(conversionTime); + +#ifndef WLED_MQTT_DISABLE + _debugAverages = debugAveragesValue; + _debugConversionTime = debugConversionTimeValue; +#endif } void initializeINA226() @@ -117,10 +154,86 @@ private: } _ina226->setCorrectionFactor(1.0); setOptimalSettings(); - _ina226->setMeasureMode(CONTINUOUS); + + if (_checkInterval >= 20000) + { + // If we're only checking every 20s, we can use the triggered mode. This mode powers down the INA226 between measurements and saves energy this way. + _isTriggeredOperationMode = true; + _ina226->setMeasureMode(TRIGGERED); + } + else + { + // Continuous mode is simpler and will just keep values fresh in the chip. + _isTriggeredOperationMode = false; + _ina226->setMeasureMode(CONTINUOUS); + } + _ina226->setResistorRange(static_cast(_shuntResistor) / 1000.0, static_cast(_currentRange) / 1000.0); } + void fetchAndPushValues() + { + _lastStatus = _ina226->getI2cErrorCode(); + + if (_lastStatus != 0) + return; + + float current = truncateDecimals(_ina226->getCurrent_mA() / 1000.0); + float voltage = truncateDecimals(_ina226->getBusVoltage_V()); + float power = truncateDecimals(_ina226->getBusPower() / 1000.0); + float shuntVoltage = truncateDecimals(_ina226->getShuntVoltage_V()); + bool overflow = _ina226->overflow; + +#ifndef WLED_DISABLE_MQTT + mqttPublishIfChanged(F("current"), _lastCurrentSent, current, 0.01f); + mqttPublishIfChanged(F("voltage"), _lastVoltageSent, voltage, 0.01f); + mqttPublishIfChanged(F("power"), _lastPowerSent, power, 0.1f); + mqttPublishIfChanged(F("shunt_voltage"), _lastShuntVoltageSent, shuntVoltage, 0.01f); + mqttPublishIfChanged(F("overflow"), _lastOverflowSent, overflow); +#endif + + _lastCurrent = current; + _lastVoltage = voltage; + _lastPower = power; + _lastShuntVoltage = shuntVoltage; + _lastOverflow = overflow; + } + + void handleTriggeredMode(unsigned long currentTime) + { + if (_measurementTriggered) + { + if (currentTime - _lastCheckTime >= 400) + { + _lastCheckTime = currentTime; + if (_ina226->isBusy()) + return; + + fetchAndPushValues(); + _measurementTriggered = false; + } + } + else + { + if (currentTime - _lastLoopCheck >= _checkInterval) + { + _ina226->startSingleMeasurement(); + _lastLoopCheck = currentTime; + _measurementTriggered = true; + _lastCheckTime = currentTime; + } + } + } + + void handleContinuousMode(unsigned long currentTime) + { + if (currentTime - _lastLoopCheck >= _checkInterval) + { + _lastLoopCheck = currentTime; + fetchAndPushValues(); + } + } + ~UsermodINA226() { delete _ina226; @@ -243,33 +356,13 @@ public: unsigned long currentTime = millis(); - if (currentTime - _lastLoopCheck < _checkInterval) - return; - _lastLoopCheck = currentTime; - - _lastStatus = _ina226->getI2cErrorCode(); - - if (_lastStatus == 0) + if (_isTriggeredOperationMode) { - float current = truncateDecimals(_ina226->getCurrent_mA() / 1000.0); - float voltage = truncateDecimals(_ina226->getBusVoltage_V()); - float power = truncateDecimals(_ina226->getBusPower() / 1000.0); // Correct power value to W - float shuntVoltage = truncateDecimals(_ina226->getShuntVoltage_V()); - bool overflow = _ina226->overflow; - -#ifndef WLED_DISABLE_MQTT - mqttPublishIfChanged(F("current"), _lastCurrentSent, current, 0.01f); - mqttPublishIfChanged(F("voltage"), _lastVoltageSent, voltage, 0.01f); - mqttPublishIfChanged(F("power"), _lastPowerSent, power, 0.1f); - mqttPublishIfChanged(F("shunt_voltage"), _lastShuntVoltageSent, shuntVoltage, 0.01f); - mqttPublishIfChanged(F("overflow"), _lastOverflowSent, overflow); -#endif - - _lastCurrent = current; - _lastVoltage = voltage; - _lastPower = power; - _lastShuntVoltage = shuntVoltage; - _lastOverflow = overflow; + handleTriggeredMode(currentTime); + } + else + { + handleContinuousMode(currentTime); } } @@ -291,6 +384,31 @@ public: if (user.isNull()) user = root.createNestedObject("u"); +#ifdef USERMOD_INA226_DEBUG + JsonArray temp = user.createNestedArray(F("INA226 last loop")); + temp.add(_lastLoopCheck); + + temp = user.createNestedArray(F("INA226 last status")); + temp.add(_lastStatus); + + temp = user.createNestedArray(F("INA226 average samples")); + temp.add(_debugAverages); + temp.add(F("samples")); + + temp = user.createNestedArray(F("INA226 conversion time")); + temp.add(_debugConversionTime); + temp.add(F("us")); + + temp = user.createNestedArray(F("INA226 mode")); + temp.add(_isTriggeredOperationMode ? F("triggered") : F("continuous")); + + if (_isTriggeredOperationMode) + { + temp = user.createNestedArray(F("INA226 triggered")); + temp.add(_measurementTriggered ? F("waiting for measurement") : F("")); + } +#endif + JsonArray jsonCurrent = user.createNestedArray(F("Current")); JsonArray jsonVoltage = user.createNestedArray(F("Voltage")); JsonArray jsonPower = user.createNestedArray(F("Power")); From dcb5049f97a291b5d50b259df5832b1dea71e27a Mon Sep 17 00:00:00 2001 From: Michael Bisbjerg Date: Fri, 17 May 2024 10:47:33 +0200 Subject: [PATCH 3/6] Move to individual settings for samples and conversion time --- usermods/INA226_v2/README.md | 75 ++++++-- usermods/INA226_v2/usermod_ina226.h | 263 +++++++++++++++------------- 2 files changed, 200 insertions(+), 138 deletions(-) diff --git a/usermods/INA226_v2/README.md b/usermods/INA226_v2/README.md index 6d17eda70..b1e691618 100644 --- a/usermods/INA226_v2/README.md +++ b/usermods/INA226_v2/README.md @@ -1,28 +1,69 @@ -# Usermod AHT10 -This Usermod is designed to read a `AHT10`, `AHT15` or `AHT20` sensor and output the following: -- Temperature -- Humidity +# Usermod INA226 -Configuration is performed via the Usermod menu. The following settings can be configured in the Usermod Menu: -- I2CAddress: The i2c address in decimal. Set it to either 56 (0x38, the default) or 57 (0x39). -- SensorType, one of: - - 0 - AHT10 - - 1 - AHT15 - - 2 - AHT20 -- CheckInterval: Number of seconds between readings -- Decimals: Number of decimals to put in the output +This Usermod is designed to read values from an INA226 sensor and output the following: +- Current +- Voltage +- Power +- Shunt Voltage +- Overflow status + +## Configuration + +The following settings can be configured in the Usermod Menu: +- **Enabled**: Enable or disable the usermod. +- **I2CAddress**: The I2C address in decimal. Default is 64 (0x40). +- **CheckInterval**: Number of seconds between readings. This should be higher than the time it takes to make a reading, determined by the two next options. +- **INASamples**: The number of samples to configure the INA226 to use for a measurement. Higher counts provide more accuracy. See the 'Understanding Samples and Conversion Times' section for more details. +- **INAConversionTime**: The time to use on converting and preparing readings on the INA226. Higher times provide more precision. See the 'Understanding Samples and Conversion Times' section for more details. +- **Decimals**: Number of decimals in the output. +- **ShuntResistor**: Shunt resistor value in milliohms. An R100 shunt resistor should be written as "100", while R010 should be "10". +- **CurrentRange**: Expected maximum current in milliamps (e.g., 5 A = 5000 mA). +- **MqttPublish**: Enable or disable MQTT publishing. +- **MqttPublishAlways**: Publish always, regardless if there is a change. +- **MqttHomeAssistantDiscovery**: Enable Home Assistant discovery. + +## Dependencies + +These must be added under `lib_deps` in your `platform.ini` (or `platform_override.ini`). -Dependencies, These must be added under `lib_deps` in your `platform.ini` (or `platform_override.ini`). - Libraries - - `enjoyneering/AHT10@~1.1.0` (by [enjoyneering](https://registry.platformio.org/libraries/enjoyneering/AHT10)) + - `wollewald/INA226_WE@~1.2.9` (by [wollewald](https://registry.platformio.org/libraries/wollewald/INA226_WE)) - `Wire` +## Understanding Samples and Conversion Times + +The INA226 uses a programmable ADC with configurable conversion times and averaging to optimize the measurement accuracy and speed. The conversion time and number of samples are determined based on the `INASamples` and `INAConversionTime` settings. The following table outlines the possible combinations: + +| Conversion Time (μs) | 1 Sample | 4 Samples | 16 Samples | 64 Samples | 128 Samples | 256 Samples | 512 Samples | 1024 Samples | +|----------------------|----------|-----------|------------|------------|-------------|-------------|-------------|--------------| +| 140 | 0.28 ms | 1.12 ms | 4.48 ms | 17.92 ms | 35.84 ms | 71.68 ms | 143.36 ms | 286.72 ms | +| 204 | 0.408 ms | 1.632 ms | 6.528 ms | 26.112 ms | 52.224 ms | 104.448 ms | 208.896 ms | 417.792 ms | +| 332 | 0.664 ms | 2.656 ms | 10.624 ms | 42.496 ms | 84.992 ms | 169.984 ms | 339.968 ms | 679.936 ms | +| 588 | 1.176 ms | 4.704 ms | 18.816 ms | 75.264 ms | 150.528 ms | 301.056 ms | 602.112 ms | 1204.224 ms | +| 1100 | 2.2 ms | 8.8 ms | 35.2 ms | 140.8 ms | 281.6 ms | 563.2 ms | 1126.4 ms | 2252.8 ms | +| 2116 | 4.232 ms | 16.928 ms | 67.712 ms | 270.848 ms | 541.696 ms | 1083.392 ms | 2166.784 ms | 4333.568 ms | +| 4156 | 8.312 ms | 33.248 ms | 132.992 ms | 531.968 ms | 1063.936 ms | 2127.872 ms | 4255.744 ms | 8511.488 ms | +| 8244 | 16.488 ms| 65.952 ms | 263.808 ms | 1055.232 ms| 2110.464 ms | 4220.928 ms | 8441.856 ms | 16883.712 ms | + +It is important to pick a combination that provides the needed balance between accuracy and precision while ensuring new readings within the `CheckInterval` setting. When `USERMOD_INA226_DEBUG` is defined, the info pane contains the expected time to make a reading, which can be seen in the table above. + +As an example, if you want a new reading every 5 seconds (`CheckInterval`), a valid combination is `256 samples` and `4156 μs` which would provide new values every 2.1 seconds. + +The picked values also slightly affect power usage. If the `CheckInterval` is set to more than 20 seconds, the INA226 is configured in `triggered` reading mode, where it only uses power as long as it's working. Then the conversion time and average samples counts determine how long the chip stays turned on every `CheckInterval` time. + +### Calculating Current and Power + +The INA226 calculates current by measuring the differential voltage across a shunt resistor and using the calibration register value to convert this measurement into current. Power is calculated by multiplying the current by the bus voltage. + +For detailed programming information and register configurations, refer to the [INA226 datasheet](https://www.ti.com/product/INA226). + ## Author [@LordMike](https://github.com/LordMike) -# Compiling +## Compiling + +To enable, compile with `USERMOD_INA226` defined (e.g. in `platformio_override.ini`). -To enable, compile with `USERMOD_AHT10` defined (e.g. in `platformio_override.ini`) ```ini [env:ina226_example] extends = env:esp32dev @@ -33,4 +74,4 @@ build_flags = lib_deps = ${esp32.lib_deps} wollewald/INA226_WE@~1.2.9 -``` +``` \ No newline at end of file diff --git a/usermods/INA226_v2/usermod_ina226.h b/usermods/INA226_v2/usermod_ina226.h index 752a68c3a..1493efb1d 100644 --- a/usermods/INA226_v2/usermod_ina226.h +++ b/usermods/INA226_v2/usermod_ina226.h @@ -5,6 +5,65 @@ #define INA226_ADDRESS 0x40 // Default I2C address for INA226 +#define DEFAULT_CHECKINTERVAL 60000 +#define DEFAULT_INASAMPLES 128 +#define DEFAULT_INASAMPLESENUM AVERAGE_128 +#define DEFAULT_INACONVERSIONTIME 1100 +#define DEFAULT_INACONVERSIONTIMEENUM CONV_TIME_1100 + +// A packed version of all INA settings enums and their human friendly counterparts packed into a 32 bit structure +// Some values are shifted and need to be preprocessed before usage +struct InaSettingLookup +{ + uint16_t avgSamples : 11; // Max 1024, which could be in 10 bits if we shifted by 1; if we somehow handle the edge case with "1" + uint8_t avgEnum : 4; // Shift by 8 to get the INA226_AVERAGES value, accepts 0x00 to 0x0F, we need 0x00 to 0x0E + uint16_t convTimeUs : 14; // We could save 2 bits by shifting this, but we won't save anything at present. + INA226_CONV_TIME convTimeEnum : 3; // Only the lowest 3 bits are defined in the conversion time enumerations +}; + +const InaSettingLookup _inaSettingsLookup[] = { + {1024, AVERAGE_1024 >> 8, 8244, CONV_TIME_8244}, + {512, AVERAGE_512 >> 8, 4156, CONV_TIME_4156}, + {256, AVERAGE_256 >> 8, 2116, CONV_TIME_2116}, + {128, AVERAGE_128 >> 8, 1100, CONV_TIME_1100}, + {64, AVERAGE_64 >> 8, 588, CONV_TIME_588}, + {16, AVERAGE_16 >> 8, 332, CONV_TIME_332}, + {4, AVERAGE_4 >> 8, 204, CONV_TIME_204}, + {1, AVERAGE_1 >> 8, 140, CONV_TIME_140}}; + +// Note: Will update the provided arg to be the correct value +INA226_AVERAGES getAverageEnum(uint16_t &samples) +{ + for (const auto &setting : _inaSettingsLookup) + { + // If a user supplies 2000 samples, we serve up the highest possible value + if (samples >= setting.avgSamples) + { + samples = setting.avgSamples; + return static_cast(setting.avgEnum << 8); + } + } + // Default value if not found + samples = DEFAULT_INASAMPLES; + return DEFAULT_INASAMPLESENUM; +} + +INA226_CONV_TIME getConversionTimeEnum(uint16_t &timeUs) +{ + for (const auto &setting : _inaSettingsLookup) + { + // If a user supplies 9000 us, we serve up the highest possible value + if (timeUs >= setting.convTimeUs) + { + timeUs = setting.convTimeUs; + return setting.convTimeEnum; + } + } + // Default value if not found + timeUs = DEFAULT_INACONVERSIONTIME; + return DEFAULT_INACONVERSIONTIMEENUM; +} + class UsermodINA226 : public Usermod { private: @@ -13,19 +72,21 @@ private: unsigned long _lastLoopCheck = 0; unsigned long _lastCheckTime = 0; - bool _settingEnabled : 1; // Enable the usermod - bool _mqttPublish : 1; // Publish MQTT values - bool _mqttPublishAlways : 1; // Publish always, regardless if there is a change - bool _mqttHomeAssistant : 1; // Enable Home Assistant docs - bool _initDone : 1; // Initialization is done - bool _isTriggeredOperationMode : 1; // false = continuous, true = triggered - bool _measurementTriggered : 1; // if triggered mode, then true indicates we're waiting for measurements + bool _settingEnabled : 1; // Enable the usermod + bool _mqttPublish : 1; // Publish MQTT values + bool _mqttPublishAlways : 1; // Publish always, regardless if there is a change + bool _mqttHomeAssistant : 1; // Enable Home Assistant docs + bool _initDone : 1; // Initialization is done + bool _isTriggeredOperationMode : 1; // false = continuous, true = triggered + bool _measurementTriggered : 1; // if triggered mode, then true indicates we're waiting for measurements + uint16_t _settingInaConversionTimeUs : 12; // Conversion time, shift by 2 + uint16_t _settingInaSamples : 11; // Number of samples for averaging, max 1024 - uint8_t _i2cAddress = INA226_ADDRESS; - uint16_t _checkInterval = 60000; // milliseconds, user settings is in seconds - float _decimalFactor = 100; // a power of 10 factor. 1 would be no change, 10 is one decimal, 100 is two etc. User sees a power of 10 (0, 1, 2, ..) - uint16_t _shuntResistor = 1000; // Shunt resistor value in milliohms - uint16_t _currentRange = 1000; // Expected maximum current in milliamps + uint8_t _i2cAddress; + uint16_t _checkInterval; // milliseconds, user settings is in seconds + float _decimalFactor; // a power of 10 factor. 1 would be no change, 10 is one decimal, 100 is two etc. User sees a power of 10 (0, 1, 2, ..) + uint16_t _shuntResistor; // Shunt resistor value in milliohms + uint16_t _currentRange; // Expected maximum current in milliamps uint8_t _lastStatus = 0; float _lastCurrent = 0; @@ -35,9 +96,6 @@ private: bool _lastOverflow = false; #ifndef WLED_MQTT_DISABLE - uint16_t _debugAverages; - uint16_t _debugConversionTime; - float _lastCurrentSent = 0; float _lastVoltageSent = 0; float _lastPowerSent = 0; @@ -52,93 +110,6 @@ private: return roundf(val * _decimalFactor) / _decimalFactor; } - void setOptimalSettings() - { - INA226_AVERAGES avg; - INA226_CONV_TIME conversionTime; - uint16_t debugAveragesValue = 0; - uint16_t debugConversionTimeValue = 0; - - // Identify the combination of samples and conversion times that will provide us with a measurement within our specified check interval. - // The two values will define how stable a measurement is (number of samples) and how much time can be used to calculate on it - // (conversion time). The calculation is: - // `Samples * ConversionTime * 2` - // - // This table shows all possible combinations and the time it'll take. - // | Conversion Time (μs) | 1 Sample | 4 Samples | 16 Samples | 64 Samples | 128 Samples | 256 Samples | 512 Samples | 1024 Samples | - // |----------------------|----------|-----------|------------|------------|-------------|-------------|-------------|--------------| - // | 140 | 0.28 ms | 1.12 ms | 4.48 ms | 17.92 ms | 35.84 ms | 71.68 ms | 143.36 ms | 286.72 ms | - // | 204 | 0.408 ms | 1.632 ms | 6.528 ms | 26.112 ms | 52.224 ms | 104.448 ms | 208.896 ms | 417.792 ms | - // | 332 | 0.664 ms | 2.656 ms | 10.624 ms | 42.496 ms | 84.992 ms | 169.984 ms | 339.968 ms | 679.936 ms | - // | 588 | 1.176 ms | 4.704 ms | 18.816 ms | 75.264 ms | 150.528 ms | 301.056 ms | 602.112 ms | 1204.224 ms | - // | 1100 | 2.2 ms | 8.8 ms | 35.2 ms | 140.8 ms | 281.6 ms | 563.2 ms | 1126.4 ms | 2252.8 ms | - // | 2116 | 4.232 ms | 16.928 ms | 67.712 ms | 270.848 ms | 541.696 ms | 1083.392 ms | 2166.784 ms | 4333.568 ms | - // | 4156 | 8.312 ms | 33.248 ms | 132.992 ms | 531.968 ms | 1063.936 ms | 2127.872 ms | 4255.744 ms | 8511.488 ms | - // | 8244 | 16.488 ms| 65.952 ms | 263.808 ms | 1055.232 ms| 2110.464 ms | 4220.928 ms | 8441.856 ms | 16883.712 ms | - - // The below determines which number of average samples to use, because this number is likely most important, and then finds the max conversion time. - if (_checkInterval >= 5000) - { - avg = AVERAGE_1024; - debugAveragesValue = 1024; - if (_checkInterval > 17000) - { - conversionTime = CONV_TIME_8244; - debugConversionTimeValue = 8244; - } - else - { - conversionTime = CONV_TIME_4156; - debugConversionTimeValue = 4156; - } - } - else if (_checkInterval >= 2000) - { - avg = AVERAGE_512; - debugAveragesValue = 512; - if (_checkInterval > 3000) - { - conversionTime = CONV_TIME_2116; - debugConversionTimeValue = 2116; - } - else - { - conversionTime = CONV_TIME_1100; - debugConversionTimeValue = 1100; - } - } - else - { - // Always 1 second or more - - avg = AVERAGE_256; - debugAveragesValue = 256; - if (_checkInterval >= 3000) - { - conversionTime = CONV_TIME_4156; - debugConversionTimeValue = 4156; - } - else if (_checkInterval >= 2000) - { - conversionTime = CONV_TIME_2116; - debugConversionTimeValue = 2116; - } - else - { - conversionTime = CONV_TIME_1100; - debugConversionTimeValue = 1100; - } - } - - _ina226->setAverage(avg); - _ina226->setConversionTime(conversionTime); - -#ifndef WLED_MQTT_DISABLE - _debugAverages = debugAveragesValue; - _debugConversionTime = debugConversionTimeValue; -#endif - } - void initializeINA226() { if (_ina226 != nullptr) @@ -153,17 +124,20 @@ private: return; } _ina226->setCorrectionFactor(1.0); - setOptimalSettings(); + + uint16_t tmpShort = _settingInaSamples; + _ina226->setAverage(getAverageEnum(tmpShort)); + + tmpShort = _settingInaConversionTimeUs << 2; + _ina226->setConversionTime(getConversionTimeEnum(tmpShort)); if (_checkInterval >= 20000) { - // If we're only checking every 20s, we can use the triggered mode. This mode powers down the INA226 between measurements and saves energy this way. _isTriggeredOperationMode = true; _ina226->setMeasureMode(TRIGGERED); } else { - // Continuous mode is simpler and will just keep values fresh in the chip. _isTriggeredOperationMode = false; _ina226->setMeasureMode(CONTINUOUS); } @@ -203,6 +177,7 @@ private: { if (_measurementTriggered) { + // Test if we have a measurement every 400ms if (currentTime - _lastCheckTime >= 400) { _lastCheckTime = currentTime; @@ -344,6 +319,19 @@ private: #endif public: + UsermodINA226() + { + // Default values + _settingInaSamples = DEFAULT_INASAMPLES; + _settingInaConversionTimeUs = DEFAULT_INACONVERSIONTIME; + + _i2cAddress = INA226_ADDRESS; + _checkInterval = DEFAULT_CHECKINTERVAL; + _decimalFactor = 100; + _shuntResistor = 1000; + _currentRange = 1000; + } + void setup() { initializeINA226(); @@ -392,13 +380,19 @@ public: temp.add(_lastStatus); temp = user.createNestedArray(F("INA226 average samples")); - temp.add(_debugAverages); + temp.add(_settingInaSamples); temp.add(F("samples")); temp = user.createNestedArray(F("INA226 conversion time")); - temp.add(_debugConversionTime); + temp.add(_settingInaConversionTimeUs << 2); temp.add(F("us")); + // INA226 uses (2 * conversion time * samples) time to take a reading. + temp = user.createNestedArray(F("INA226 expected sample time")); + uint32_t sampleTimeNeededUs = (static_cast(_settingInaConversionTimeUs) << 2) * _settingInaSamples * 2; + temp.add(truncateDecimals(sampleTimeNeededUs / 1000.0)); + temp.add(F("ms")); + temp = user.createNestedArray(F("INA226 mode")); temp.add(_isTriggeredOperationMode ? F("triggered") : F("continuous")); @@ -456,6 +450,8 @@ public: top[F("Enabled")] = _settingEnabled; top[F("I2CAddress")] = static_cast(_i2cAddress); top[F("CheckInterval")] = _checkInterval / 1000; + top[F("INASamples")] = _settingInaSamples; + top[F("INAConversionTime")] = _settingInaConversionTimeUs << 2; top[F("Decimals")] = log10f(_decimalFactor); top[F("ShuntResistor")] = _shuntResistor; top[F("CurrentRange")] = _currentRange; @@ -476,45 +472,70 @@ public: if (!configComplete) return false; - bool tmpBool = false; - configComplete &= getJsonValue(top[F("Enabled")], tmpBool); - if (configComplete) + bool tmpBool; + if (getJsonValue(top[F("Enabled")], tmpBool)) _settingEnabled = tmpBool; + else + configComplete = false; configComplete &= getJsonValue(top[F("I2CAddress")], _i2cAddress); - configComplete &= getJsonValue(top[F("CheckInterval")], _checkInterval); - if (configComplete) + if (getJsonValue(top[F("CheckInterval")], _checkInterval)) { if (1 <= _checkInterval && _checkInterval <= 600) _checkInterval *= 1000; else - _checkInterval = 60000; + _checkInterval = DEFAULT_CHECKINTERVAL; } + else + configComplete = false; - configComplete &= getJsonValue(top[F("Decimals")], _decimalFactor); - if (configComplete) + uint16_t tmpShort; + if (getJsonValue(top[F("INASamples")], tmpShort)) + { + // The method below will fix the provided value to a valid one + getAverageEnum(tmpShort); + _settingInaSamples = tmpShort; + } + else + configComplete = false; + + if (getJsonValue(top[F("INAConversionTime")], tmpShort)) + { + // The method below will fix the provided value to a valid one + getConversionTimeEnum(tmpShort); + _settingInaConversionTimeUs = tmpShort >> 2; + } + else + configComplete = false; + + if (getJsonValue(top[F("Decimals")], _decimalFactor)) { if (0 <= _decimalFactor && _decimalFactor <= 5) _decimalFactor = pow10f(_decimalFactor); else _decimalFactor = 100; } + else + configComplete = false; configComplete &= getJsonValue(top[F("ShuntResistor")], _shuntResistor); configComplete &= getJsonValue(top[F("CurrentRange")], _currentRange); #ifndef WLED_DISABLE_MQTT - configComplete &= getJsonValue(top[F("MqttPublish")], tmpBool); - if (configComplete) + if (getJsonValue(top[F("MqttPublish")], tmpBool)) _mqttPublish = tmpBool; + else + configComplete = false; - configComplete &= getJsonValue(top[F("MqttPublishAlways")], tmpBool); - if (configComplete) + if (getJsonValue(top[F("MqttPublishAlways")], tmpBool)) _mqttPublishAlways = tmpBool; + else + configComplete = false; - configComplete &= getJsonValue(top[F("MqttHomeAssistantDiscovery")], tmpBool); - if (configComplete) + if (getJsonValue(top[F("MqttHomeAssistantDiscovery")], tmpBool)) _mqttHomeAssistant = tmpBool; + else + configComplete = false; #endif if (_initDone) From 075c1644077d8d85f3395223fafe57d883381864 Mon Sep 17 00:00:00 2001 From: Michael Bisbjerg Date: Fri, 17 May 2024 15:03:56 +0200 Subject: [PATCH 4/6] Fix bug in triggered measurements --- usermods/INA226_v2/usermod_ina226.h | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/usermods/INA226_v2/usermod_ina226.h b/usermods/INA226_v2/usermod_ina226.h index 1493efb1d..52bc3d83f 100644 --- a/usermods/INA226_v2/usermod_ina226.h +++ b/usermods/INA226_v2/usermod_ina226.h @@ -52,7 +52,7 @@ INA226_CONV_TIME getConversionTimeEnum(uint16_t &timeUs) { for (const auto &setting : _inaSettingsLookup) { - // If a user supplies 9000 us, we serve up the highest possible value + // If a user supplies 9000 μs, we serve up the highest possible value if (timeUs >= setting.convTimeUs) { timeUs = setting.convTimeUs; @@ -70,7 +70,7 @@ private: static const char _name[]; unsigned long _lastLoopCheck = 0; - unsigned long _lastCheckTime = 0; + unsigned long _lastTriggerTime = 0; bool _settingEnabled : 1; // Enable the usermod bool _mqttPublish : 1; // Publish MQTT values @@ -178,9 +178,9 @@ private: if (_measurementTriggered) { // Test if we have a measurement every 400ms - if (currentTime - _lastCheckTime >= 400) + if (currentTime - _lastTriggerTime >= 400) { - _lastCheckTime = currentTime; + _lastTriggerTime = currentTime; if (_ina226->isBusy()) return; @@ -192,10 +192,11 @@ private: { if (currentTime - _lastLoopCheck >= _checkInterval) { - _ina226->startSingleMeasurement(); + // Start a measurement and use isBusy() later to determine when it is done + _ina226->startSingleMeasurementNoWait(); _lastLoopCheck = currentTime; + _lastTriggerTime = currentTime; _measurementTriggered = true; - _lastCheckTime = currentTime; } } } @@ -385,7 +386,7 @@ public: temp = user.createNestedArray(F("INA226 conversion time")); temp.add(_settingInaConversionTimeUs << 2); - temp.add(F("us")); + temp.add(F("μs")); // INA226 uses (2 * conversion time * samples) time to take a reading. temp = user.createNestedArray(F("INA226 expected sample time")); From 0df726cdabac94a4512aea7d8d44ddec99255b95 Mon Sep 17 00:00:00 2001 From: Michael Bisbjerg Date: Sat, 18 May 2024 15:52:01 +0200 Subject: [PATCH 5/6] Add note to platformio override sample --- platformio_override.sample.ini | 3 +++ 1 file changed, 3 insertions(+) diff --git a/platformio_override.sample.ini b/platformio_override.sample.ini index 05bd1983e..c64c63189 100644 --- a/platformio_override.sample.ini +++ b/platformio_override.sample.ini @@ -94,6 +94,9 @@ build_flags = ${common.build_flags} ${esp8266.build_flags} ; -D USERMOD_AUTO_SAVE ; -D AUTOSAVE_AFTER_SEC=90 ; +; Use INA226 usermod +; -D USERMOD_INA226 +; ; Use 4 Line Display usermod with SPI display ; -D USERMOD_FOUR_LINE_DISPLAY ; -D USE_ALT_DISPlAY # mandatory From 4a7ef07089e7d6280e1ac686c190293cc38bbf98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Kristan?= Date: Mon, 20 May 2024 12:24:26 +0200 Subject: [PATCH 6/6] Fix for #3991 --- wled00/json.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/json.cpp b/wled00/json.cpp index 711ff8f0c..1f9b0fae6 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -598,7 +598,7 @@ void serializeState(JsonObject root, bool forPreset, bool includeBri, bool segme nl["dur"] = nightlightDelayMins; nl["mode"] = nightlightMode; nl[F("tbri")] = nightlightTargetBri; - nl[F("rem")] = nightlightActive ? (nightlightDelayMs - (millis() - nightlightStartTime)) / 1000 : -1; // seconds remaining + nl[F("rem")] = nightlightActive ? (int)(nightlightDelayMs - (millis() - nightlightStartTime)) / 1000 : -1; // seconds remaining JsonObject udpn = root.createNestedObject("udpn"); udpn[F("send")] = sendNotificationsRT;