-
-
Notifications
You must be signed in to change notification settings - Fork 4.1k
Update and improve INA226 usermod #5411
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 6 commits
9fc745c
d2346d8
f571916
c06af7f
afc5e18
85c6da8
7bf8617
34466cc
97c6171
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,14 +1,30 @@ | ||
| #include "wled.h" | ||
| #include <INA226_WE.h> | ||
|
|
||
| #ifndef INA226_ADDRESS | ||
| #define INA226_ADDRESS 0x40 // Default I2C address for INA226 | ||
| #endif | ||
|
|
||
| #define DEFAULT_CHECKINTERVAL 60000 | ||
| #define DEFAULT_CHECKINTERVAL 1000 | ||
| #define DEFAULT_INASAMPLES 128 | ||
| #define DEFAULT_INASAMPLESENUM AVERAGE_128 | ||
| #define DEFAULT_INACONVERSIONTIME 1100 | ||
| #define DEFAULT_INACONVERSIONTIMEENUM CONV_TIME_1100 | ||
|
|
||
| // Compile-time defaults for shunt resistor (micro-ohms), current range (mA), and current offset (mA) | ||
| // These can be overridden via -D flags in platformio.ini / platformio_override.ini | ||
| #ifndef INA226_SHUNT_MICRO_OHMS | ||
| #define INA226_SHUNT_MICRO_OHMS 1000000 // 1 Ohm = 1,000,000 μΩ | ||
| #endif | ||
|
|
||
| #ifndef INA226_DEFAULT_CURRENT_RANGE | ||
| #define INA226_DEFAULT_CURRENT_RANGE 1000 // 1000 mA = 1 A | ||
| #endif | ||
|
|
||
| #ifndef INA226_CURRENT_OFFSET_MA | ||
| #define INA226_CURRENT_OFFSET_MA 0 // No offset by default | ||
| #endif | ||
|
|
||
| // 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 | ||
|
|
@@ -81,10 +97,11 @@ class UsermodINA226 : public Usermod | |
| uint16_t _settingInaSamples : 11; // Number of samples for averaging, max 1024 | ||
|
|
||
| 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 | ||
| uint16_t _checkIntervalMs; // 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, ..) | ||
| uint32_t _shuntResistorUOhm; // Shunt resistor value in micro-ohms (μΩ) | ||
| uint16_t _currentRangeMa; // Expected maximum current in milliamps | ||
| int16_t _currentOffsetMa; // Current offset in milliamps, subtracted from readings | ||
|
|
||
| uint8_t _lastStatus = 0; | ||
| float _lastCurrent = 0; | ||
|
|
@@ -118,9 +135,11 @@ class UsermodINA226 : public Usermod | |
| _ina226 = new INA226_WE(_i2cAddress); | ||
| if (!_ina226->init()) | ||
| { | ||
| DEBUG_PRINTLN(F("INA226 initialization failed!")); | ||
| DEBUG_PRINTLN(F("INA226: init failed!")); | ||
| return; | ||
| } | ||
| DEBUG_PRINTF_P(PSTR("INA226: addr=0x%02X shunt=%luμΩ range=%umA offset=%dmA\n"), | ||
| _i2cAddress, _shuntResistorUOhm, _currentRangeMa, _currentOffsetMa); | ||
| _ina226->setCorrectionFactor(1.0); | ||
|
|
||
| uint16_t tmpShort = _settingInaSamples; | ||
|
|
@@ -129,7 +148,7 @@ class UsermodINA226 : public Usermod | |
| tmpShort = _settingInaConversionTimeUs << 2; | ||
| _ina226->setConversionTime(getConversionTimeEnum(tmpShort)); | ||
|
|
||
| if (_checkInterval >= 20000) | ||
| if (_checkIntervalMs >= 20000) | ||
| { | ||
| _isTriggeredOperationMode = true; | ||
| _ina226->setMeasureMode(TRIGGERED); | ||
|
|
@@ -140,7 +159,11 @@ class UsermodINA226 : public Usermod | |
| _ina226->setMeasureMode(CONTINUOUS); | ||
| } | ||
|
|
||
| _ina226->setResistorRange(static_cast<float>(_shuntResistor) / 1000.0, static_cast<float>(_currentRange) / 1000.0); | ||
| _ina226->setResistorRange(static_cast<float>(_shuntResistorUOhm) / 1000000.0f, static_cast<float>(_currentRangeMa) / 1000.0f); | ||
|
|
||
| DEBUG_PRINTF_P(PSTR("INA226: mode=%s interval=%ums samples=%u convTime=%uμs\n"), | ||
| _isTriggeredOperationMode ? "triggered" : "continuous", | ||
| _checkIntervalMs, _settingInaSamples, _settingInaConversionTimeUs << 2); | ||
|
Comment on lines
+164
to
+166
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use
Proposed fix- DEBUG_PRINTF_P(PSTR("INA226: mode=%s interval=%ums samples=%u convTime=%uμs\n"),
+ DEBUG_PRINTF_P(PSTR("INA226: mode=%s interval=%lums samples=%u convTime=%uμs\n"),
_isTriggeredOperationMode ? "triggered" : "continuous",
_checkIntervalMs, _settingInaSamples, _settingInaConversionTimeUs << 2);🤖 Prompt for AI Agents |
||
| } | ||
|
|
||
| void fetchAndPushValues() | ||
|
|
@@ -150,17 +173,17 @@ class UsermodINA226 : public Usermod | |
| if (_lastStatus != 0) | ||
| return; | ||
|
|
||
| float current = truncateDecimals(_ina226->getCurrent_mA() / 1000.0); | ||
| float current = truncateDecimals((_ina226->getCurrent_mA() - _currentOffsetMa) / 1000.0f); | ||
| float voltage = truncateDecimals(_ina226->getBusVoltage_V()); | ||
| float power = truncateDecimals(_ina226->getBusPower() / 1000.0); | ||
| float shuntVoltage = truncateDecimals(_ina226->getShuntVoltage_V()); | ||
| float shuntVoltage = truncateDecimals(_ina226->getShuntVoltage_mV()); | ||
| 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("shunt_voltage_drop"), _lastShuntVoltageSent, shuntVoltage, 0.01f); | ||
| mqttPublishIfChanged(F("overflow"), _lastOverflowSent, overflow); | ||
| #endif | ||
|
|
||
|
|
@@ -169,6 +192,9 @@ class UsermodINA226 : public Usermod | |
| _lastPower = power; | ||
| _lastShuntVoltage = shuntVoltage; | ||
| _lastOverflow = overflow; | ||
|
|
||
| DEBUG_PRINTF_P(PSTR("INA226: %.3fA %.2fV %.2fW shunt=%.2fmV%s\n"), | ||
| current, voltage, power, shuntVoltage, overflow ? " OVF" : ""); | ||
| } | ||
|
|
||
| void handleTriggeredMode(unsigned long currentTime) | ||
|
|
@@ -188,7 +214,7 @@ class UsermodINA226 : public Usermod | |
| } | ||
| else | ||
| { | ||
| if (currentTime - _lastLoopCheck >= _checkInterval) | ||
| if (currentTime - _lastLoopCheck >= _checkIntervalMs) | ||
| { | ||
| // Start a measurement and use isBusy() later to determine when it is done | ||
| _ina226->startSingleMeasurementNoWait(); | ||
|
|
@@ -201,7 +227,7 @@ class UsermodINA226 : public Usermod | |
|
|
||
| void handleContinuousMode(unsigned long currentTime) | ||
| { | ||
| if (currentTime - _lastLoopCheck >= _checkInterval) | ||
| if (currentTime - _lastLoopCheck >= _checkIntervalMs) | ||
| { | ||
| _lastLoopCheck = currentTime; | ||
| fetchAndPushValues(); | ||
|
|
@@ -224,8 +250,8 @@ class UsermodINA226 : public Usermod | |
| 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/shunt_voltage_drop", mqttDeviceTopic); | ||
| mqttCreateHassSensor(F("Shunt Voltage Drop"), topic, F("voltage"), F("mV")); | ||
|
|
||
| snprintf_P(topic, 127, "%s/overflow", mqttDeviceTopic); | ||
| mqttCreateHassBinarySensor(F("Overflow"), topic); | ||
|
|
@@ -315,14 +341,23 @@ class UsermodINA226 : public Usermod | |
| UsermodINA226() | ||
| { | ||
| // Default values | ||
| _settingEnabled = true; | ||
| _settingInaSamples = DEFAULT_INASAMPLES; | ||
| _settingInaConversionTimeUs = DEFAULT_INACONVERSIONTIME; | ||
|
|
||
| _i2cAddress = INA226_ADDRESS; | ||
| _checkInterval = DEFAULT_CHECKINTERVAL; | ||
| _checkIntervalMs = DEFAULT_CHECKINTERVAL; | ||
| _decimalFactor = 100; | ||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| _shuntResistor = 1000; | ||
| _currentRange = 1000; | ||
| _shuntResistorUOhm = INA226_SHUNT_MICRO_OHMS; | ||
| _currentRangeMa = INA226_DEFAULT_CURRENT_RANGE; | ||
| _currentOffsetMa = INA226_CURRENT_OFFSET_MA; | ||
|
|
||
| _mqttPublish = false; | ||
| _mqttPublishAlways = false; | ||
| _mqttHomeAssistant = false; | ||
| _initDone = false; | ||
| _isTriggeredOperationMode = false; | ||
| _measurementTriggered = false; | ||
| } | ||
|
|
||
| void setup() | ||
|
|
@@ -399,7 +434,7 @@ class UsermodINA226 : public Usermod | |
| 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 jsonShuntVoltage = user.createNestedArray(F("Shunt Voltage Drop")); | ||
| JsonArray jsonOverflow = user.createNestedArray(F("Overflow")); | ||
|
|
||
| if (_lastLoopCheck == 0) | ||
|
|
@@ -432,7 +467,7 @@ class UsermodINA226 : public Usermod | |
| jsonPower.add(F("W")); | ||
|
|
||
| jsonShuntVoltage.add(_lastShuntVoltage); | ||
| jsonShuntVoltage.add(F("V")); | ||
| jsonShuntVoltage.add(F("mV")); | ||
|
|
||
| jsonOverflow.add(_lastOverflow ? F("true") : F("false")); | ||
| } | ||
|
|
@@ -442,12 +477,13 @@ class UsermodINA226 : public Usermod | |
| JsonObject top = root.createNestedObject(FPSTR(_name)); | ||
| top[F("Enabled")] = _settingEnabled; | ||
| top[F("I2CAddress")] = static_cast<uint8_t>(_i2cAddress); | ||
| top[F("CheckInterval")] = _checkInterval / 1000; | ||
| top[F("CheckInterval")] = _checkIntervalMs / 1000; | ||
| top[F("INASamples")] = _settingInaSamples; | ||
| top[F("INAConversionTime")] = _settingInaConversionTimeUs << 2; | ||
| top[F("Decimals")] = log10f(_decimalFactor); | ||
| top[F("ShuntResistor")] = _shuntResistor; | ||
| top[F("CurrentRange")] = _currentRange; | ||
| top[F("ShuntResistor")] = static_cast<float>(_shuntResistorUOhm) / 1000.0f; | ||
| top[F("CurrentRange")] = _currentRangeMa; | ||
| top[F("CurrentOffset")] = _currentOffsetMa; | ||
| #ifndef WLED_DISABLE_MQTT | ||
| top[F("MqttPublish")] = _mqttPublish; | ||
| top[F("MqttPublishAlways")] = _mqttPublishAlways; | ||
|
|
@@ -457,6 +493,17 @@ class UsermodINA226 : public Usermod | |
| DEBUG_PRINTLN(F("INA226 config saved.")); | ||
| } | ||
|
|
||
| void appendConfigData() override | ||
| { | ||
| oappend(F("addInfo('INA226:CheckInterval',1,'seconds');")); | ||
| oappend(F("addInfo('INA226:INASamples',1,'samples (1-1024)');")); | ||
| oappend(F("addInfo('INA226:INAConversionTime',1,'µs');")); | ||
| oappend(F("addInfo('INA226:Decimals',1,'(0-5)');")); | ||
| oappend(F("addInfo('INA226:ShuntResistor',1,'mΩ');")); | ||
| oappend(F("addInfo('INA226:CurrentRange',1,'mA');")); | ||
| oappend(F("addInfo('INA226:CurrentOffset',1,'mA');")); | ||
| } | ||
|
|
||
| bool readFromConfig(JsonObject &root) override | ||
| { | ||
| JsonObject top = root[FPSTR(_name)]; | ||
|
|
@@ -472,12 +519,12 @@ class UsermodINA226 : public Usermod | |
| configComplete = false; | ||
|
|
||
| configComplete &= getJsonValue(top[F("I2CAddress")], _i2cAddress); | ||
| if (getJsonValue(top[F("CheckInterval")], _checkInterval)) | ||
| if (getJsonValue(top[F("CheckInterval")], _checkIntervalMs)) | ||
| { | ||
| if (1 <= _checkInterval && _checkInterval <= 600) | ||
| _checkInterval *= 1000; | ||
| if (1 <= _checkIntervalMs && _checkIntervalMs <= 600) | ||
| _checkIntervalMs *= 1000; | ||
| else | ||
| _checkInterval = DEFAULT_CHECKINTERVAL; | ||
| _checkIntervalMs = DEFAULT_CHECKINTERVAL; | ||
| } | ||
| else | ||
| configComplete = false; | ||
|
|
@@ -511,8 +558,15 @@ class UsermodINA226 : public Usermod | |
| else | ||
| configComplete = false; | ||
|
|
||
| configComplete &= getJsonValue(top[F("ShuntResistor")], _shuntResistor); | ||
| configComplete &= getJsonValue(top[F("CurrentRange")], _currentRange); | ||
| float shuntMilliOhms; | ||
| if (getJsonValue(top[F("ShuntResistor")], shuntMilliOhms)) | ||
| _shuntResistorUOhm = static_cast<uint32_t>(shuntMilliOhms * 1000.0f + 0.5f); | ||
| else | ||
| configComplete = false; | ||
|
|
||
| configComplete &= getJsonValue(top[F("CurrentRange")], _currentRangeMa); | ||
| if (!getJsonValue(top[F("CurrentOffset")], _currentOffsetMa)) | ||
| _currentOffsetMa = INA226_CURRENT_OFFSET_MA; // Use compile-time default if missing from config | ||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| #ifndef WLED_DISABLE_MQTT | ||
| if (getJsonValue(top[F("MqttPublish")], tmpBool)) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,6 +18,7 @@ The following settings can be configured in the Usermod Menu: | |
| - **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). | ||
| - **CurrentOffset**: Current offset in milliamps, subtracted from raw readings. Useful for compensating a consistent bias in the sensor. Default is 0. | ||
| - **MqttPublish**: Enable or disable MQTT publishing. | ||
| - **MqttPublishAlways**: Publish always, regardless if there is a change. | ||
| - **MqttHomeAssistantDiscovery**: Enable Home Assistant discovery. | ||
|
|
@@ -63,4 +64,30 @@ extends = env:esp32dev | |
| custom_usermods = ${env:esp32dev.custom_usermods} INA226 | ||
| build_flags = ${env:esp32dev.build_flags} | ||
| ; -D USERMOD_INA226_DEBUG ; -- add a debug status to the info modal | ||
| ``` | ||
| ``` | ||
|
|
||
| ### Compile-time Defaults | ||
|
|
||
| Several parameters can be overridden at compile time via `-D` build flags. This is useful for setting board-specific defaults so the device works correctly on first boot without manual configuration. | ||
|
|
||
| | Build Flag | Default | Unit | Description | | ||
| |---|---|---|---| | ||
| | `INA226_ADDRESS` | `0x40` | — | I2C address of the INA226 | | ||
| | `INA226_SHUNT_MICRO_OHMS` | `1000000` | μΩ | Shunt resistor value (1 000 000 μΩ = 1 Ω) | | ||
| | `INA226_DEFAULT_CURRENT_RANGE` | `1000` | mA | Expected maximum current (1000 mA = 1 A) | | ||
| | `INA226_CURRENT_OFFSET_MA` | `0` | mA | Current offset subtracted from readings | | ||
|
|
||
| Example for a board with a 4.8 mΩ shunt, 10 A range, and 53 mA offset: | ||
|
|
||
| ```ini | ||
| [env:my_board] | ||
| extends = env:esp32dev | ||
| custom_usermods = ${env:esp32dev.custom_usermods} INA226 | ||
| build_flags = ${env:esp32dev.build_flags} | ||
| -D USERMOD_INA226 | ||
| -D INA226_SHUNT_MICRO_OHMS=4800 | ||
| -D INA226_DEFAULT_CURRENT_RANGE=10000 | ||
| -D INA226_CURRENT_OFFSET_MA=53 | ||
| ``` | ||
|
|
||
| All compile-time defaults can still be changed at runtime through the Usermod settings page. | ||
|
Comment on lines
+69
to
+93
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Document the MQTT/topic migration in this section. This PR intentionally renames 🤖 Prompt for AI Agents
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think this belongs in the
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
A lightweight middle ground could be:
That way users who land on the README without reading the release notes are still warned. But if the project's convention is to keep only release notes for such notices, that's perfectly reasonable too — ultimately it's your call as the maintainer. 🧠 Learnings used |
||
Uh oh!
There was an error while loading. Please reload this page.