Update Usermod to support INA226

pull/4237/head
KrX3D 2025-06-08 20:45:52 +02:00 zatwierdzone przez GitHub
rodzic be77287bd1
commit be0d1cacd9
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: B5690EEEBB952194
4 zmienionych plików z 1122 dodań i 957 usunięć

Plik binarny nie jest wyświetlany.

Przed

Szerokość:  |  Wysokość:  |  Rozmiar: 21 KiB

Wyświetl plik

@ -1,139 +1,12 @@
// Configurable settings for the INA2xx Usermod
#include "ina2xx_v2.h"
// logging macro:
#define _logUsermodInaSensor(fmt, ...) \
DEBUG_PRINTF("[INA2xx_Sensor] " fmt "\n", ##__VA_ARGS__)
const char UsermodINA2xx::_name[] PROGMEM = "INA2xx";
// Enabled setting
#ifndef INA219_ENABLED
#define INA219_ENABLED false // Default disabled value
#endif
#ifndef INA219_I2C_ADDRESS
#define INA219_I2C_ADDRESS 0x40 // Default I2C address
#endif
#ifndef INA219_CHECK_INTERVAL
#define INA219_CHECK_INTERVAL 5 // Default 5 seconds (will be converted to ms)
#endif
#ifndef INA219_CONVERSION_TIME
#define INA219_CONVERSION_TIME BIT_MODE_12 // Conversion Time, Default 12-bit resolution
#endif
#ifndef INA219_DECIMAL_FACTOR
#define INA219_DECIMAL_FACTOR 3 // Decimal factor for current/power readings. Default 3 decimal places
#endif
#ifndef INA219_SHUNT_RESISTOR
#define INA219_SHUNT_RESISTOR 0.1 // Shunt Resistor value. Default 0.1 ohms
#endif
#ifndef INA219_CORRECTION_FACTOR
#define INA219_CORRECTION_FACTOR 1.0 // Default correction factor. Default 1.0
#endif
#ifndef INA219_P_GAIN
#define INA219_P_GAIN PG_320 // PG_40, PG_80, PG_160, PG_320
#endif
#ifndef INA219_BUSRANGE
#define INA219_BUSRANGE BRNG_32 // BRNG_16, BRNG_32
#endif
#ifndef INA219_SHUNT_VOLT_OFFSET
#define INA219_SHUNT_VOLT_OFFSET 0.0 // mV offset at zero current
#endif
#ifndef INA219_MQTT_PUBLISH
#define INA219_MQTT_PUBLISH false // Default: do not publish to MQTT
#endif
#ifndef INA219_MQTT_PUBLISH_ALWAYS
#define INA219_MQTT_PUBLISH_ALWAYS false // Default: only publish on change
#endif
#ifndef INA219_HA_DISCOVERY
#define INA219_HA_DISCOVERY false // Default: Home Assistant discovery disabled
#endif
#include "wled.h"
#include <INA219_WE.h>
#define UPDATE_CONFIG(obj, key, var, fmt) \
do { \
auto _tmp = var; \
if ( getJsonValue((obj)[(key)], _tmp) ) { \
if (_tmp != var) { \
_logUsermodInaSensor("%s updated to: " fmt, key, _tmp);\
var = _tmp; \
} \
} else { \
configComplete = false; \
} \
} while(0)
class UsermodINA2xx : public Usermod {
private:
static const char _name[]; // Name of the usermod
bool initDone = false; // Flag for successful initialization
unsigned long lastCheck = 0; // Timestamp for the last check
bool alreadyLoggedDisabled = false;
// Define the variables using the pre-defined or default values
bool enabled = INA219_ENABLED;
uint8_t _i2cAddress = INA219_I2C_ADDRESS;
uint16_t _checkInterval = INA219_CHECK_INTERVAL; // seconds
uint32_t checkInterval = static_cast<uint32_t>(_checkInterval) * 1000UL; // ms
INA219_ADC_MODE conversionTime = static_cast<INA219_ADC_MODE>(INA219_CONVERSION_TIME);
uint8_t _decimalFactor = INA219_DECIMAL_FACTOR;
float shuntResistor = INA219_SHUNT_RESISTOR;
float correctionFactor = INA219_CORRECTION_FACTOR;
INA219_PGAIN pGain = static_cast<INA219_PGAIN>(INA219_P_GAIN);
INA219_BUS_RANGE busRange = static_cast<INA219_BUS_RANGE>(INA219_BUSRANGE);
float shuntVoltOffset_mV = INA219_SHUNT_VOLT_OFFSET;
bool mqttPublish = INA219_MQTT_PUBLISH;
bool mqttPublishSent = !INA219_MQTT_PUBLISH;
bool mqttPublishAlways = INA219_MQTT_PUBLISH_ALWAYS;
bool haDiscovery = INA219_HA_DISCOVERY;
bool haDiscoverySent = !INA219_HA_DISCOVERY;
// Variables to store sensor readings
float busVoltage = 0;
float current = 0;
float current_mA = 0;
float power = 0;
float power_mW = 0;
float shuntVoltage = 0;
float loadVoltage = 0;
bool overflow = false;
// Last sent variables
float last_sent_shuntVoltage = 0;
float last_sent_busVoltage = 0;
float last_sent_loadVoltage = 0;
float last_sent_current = 0;
float last_sent_current_mA = 0;
float last_sent_power = 0;
float last_sent_power_mW = 0;
bool last_sent_overflow = false;
float dailyEnergy_kWh = 0.0; // Daily energy in kWh
float monthlyEnergy_kWh = 0.0; // Monthly energy in kWh
float totalEnergy_kWh = 0.0; // Total energy in kWh
unsigned long lastPublishTime = 0; // Track the last publish time
// Variables to store last reset timestamps
unsigned long dailyResetTime = 0;
unsigned long monthlyResetTime = 0;
bool mqttStateRestored = false;
INA219_WE *_ina2xx = nullptr; // INA2xx sensor object
UsermodINA2xx ina2xx_v2;
REGISTER_USERMOD(ina2xx_v2);
// Function to truncate decimals based on the configured decimal factor
float roundDecimals(float val) {
float UsermodINA2xx::roundDecimals(float val) {
_logUsermodInaSensor("Truncating value %.6f with factor %d", val, _decimalFactor);
if (_decimalFactor == 0) {
return roundf(val);
@ -146,7 +19,7 @@ private:
return roundf(val * factor) / factor;
}
bool hasSignificantChange(float oldValue, float newValue, float threshold = 0.01f) {
bool UsermodINA2xx::hasSignificantChange(float oldValue, float newValue, float threshold) {
bool changed = fabsf(oldValue - newValue) > threshold;
if (changed) {
_logUsermodInaSensor("Significant change detected: old=%.6f, new=%.6f, diff=%.6f", oldValue, newValue, fabsf(oldValue - newValue));
@ -154,7 +27,7 @@ private:
return changed;
}
bool hasValueChanged() {
bool UsermodINA2xx::hasValueChanged() {
bool changed = hasSignificantChange(last_sent_shuntVoltage, shuntVoltage) ||
hasSignificantChange(last_sent_busVoltage, busVoltage) ||
hasSignificantChange(last_sent_loadVoltage, loadVoltage) ||
@ -171,7 +44,7 @@ private:
}
// Update INA2xx settings and reinitialize sensor if necessary
bool updateINA2xxSettings() {
bool UsermodINA2xx::updateINA2xxSettings() {
_logUsermodInaSensor("Updating INA2xx sensor settings");
// Validate I2C pins; if invalid, disable usermod and log message
@ -193,7 +66,7 @@ private:
if (!enabled) return true;
_logUsermodInaSensor("Creating new INA2xx instance with address 0x%02X", _i2cAddress);
_ina2xx = new INA219_WE(_i2cAddress);
_ina2xx = new INA_SENSOR_CLASS(_i2cAddress);
if (!_ina2xx) {
_logUsermodInaSensor("Failed to allocate memory for INA2xx sensor!");
@ -210,15 +83,16 @@ private:
return false;
}
_logUsermodInaSensor("Setting correction factor to %.4f", correctionFactor);
_ina2xx->setCorrectionFactor(correctionFactor);
#if INA_SENSOR_TYPE == 219
_logUsermodInaSensor("Setting shunt resistor to %.4f Ohms", shuntResistor);
_ina2xx->setShuntSizeInOhms(shuntResistor);
_logUsermodInaSensor("Setting ADC mode to %d", conversionTime);
_ina2xx->setADCMode(conversionTime);
_logUsermodInaSensor("Setting correction factor to %.4f", correctionFactor);
_ina2xx->setCorrectionFactor(correctionFactor);
_logUsermodInaSensor("Setting PGA gain to %d", pGain);
_ina2xx->setPGain(pGain);
@ -228,13 +102,24 @@ private:
_logUsermodInaSensor("Setting shunt voltage offset to %.3f mV", shuntVoltOffset_mV);
_ina2xx->setShuntVoltOffset_mV(shuntVoltOffset_mV);
_logUsermodInaSensor("INA2xx sensor configured successfully");
#elif INA_SENSOR_TYPE == 226
_ina2xx->setMeasureMode(CONTINUOUS);
_ina2xx->setAverage(average);
_ina2xx->setConversionTime(conversionTime);
_ina2xx->setResistorRange(shuntResistor,currentRange); // choose resistor 100 mOhm (default )and gain range up to 10 A (1.3A default)
_ina2xx->readAndClearFlags();
_ina2xx->waitUntilConversionCompleted(); //if you comment this line the first data might be zero
#endif
_logUsermodInaSensor("INA2xx sensor configured successfully");
return true;
}
// Sanitize the mqttClientID by replacing invalid characters.
String sanitizeMqttClientID(const String &clientID) {
String UsermodINA2xx::sanitizeMqttClientID(const String &clientID) {
String sanitizedID;
_logUsermodInaSensor("Sanitizing MQTT client ID: %s", clientID.c_str());
@ -279,7 +164,7 @@ private:
/**
** Function to update energy calculations based on power and duration
**/
void updateEnergy(float power, unsigned long durationMs) {
void UsermodINA2xx::updateEnergy(float power, unsigned long durationMs) {
float durationHours = durationMs / 3600000.0; // Milliseconds to hours
_logUsermodInaSensor("Updating energy - Power: %.3f W, Duration: %lu ms (%.6f hours)", power, durationMs, durationHours);
@ -334,7 +219,7 @@ private:
/**
** Function to publish INA2xx sensor data to MQTT
**/
void publishMqtt(float shuntVoltage, float busVoltage, float loadVoltage,
void UsermodINA2xx::publishMqtt(float shuntVoltage, float busVoltage, float loadVoltage,
float current, float current_mA, float power,
float power_mW, bool overflow) {
if (!WLED_MQTT_CONNECTED) {
@ -392,7 +277,7 @@ private:
/**
** Function to create Home Assistant sensor configuration
**/
void mqttCreateHassSensor(const String &name, const String &topic,
void UsermodINA2xx::mqttCreateHassSensor(const String &name, const String &topic,
const String &deviceClass, const String &unitOfMeasurement,
const String &jsonKey, const String &SensorType) {
String sanitizedName = name;
@ -413,8 +298,6 @@ private:
uid += "_" + sanitizedName;
doc[F("uniq_id")] = uid;
_logUsermodInaSensor("Sensor unique ID: %s", uid.c_str());
// Template to extract specific value from JSON
doc[F("val_tpl")] = String("{{ value_json.") + jsonKey + String(" }}");
if (unitOfMeasurement != "")
doc[F("unit_of_meas")] = unitOfMeasurement;
@ -462,7 +345,7 @@ private:
_logUsermodInaSensor("Home Assistant sensor %s created", sanitizedName.c_str());
}
void mqttRemoveHassSensor(const String &name, const String &SensorType) {
void UsermodINA2xx::mqttRemoveHassSensor(const String &name, const String &SensorType) {
String sanitizedName = name;
sanitizedName.replace(' ', '-');
_logUsermodInaSensor("Removing HA sensor: %s", sanitizedName.c_str());
@ -480,11 +363,75 @@ private:
mqtt->publish(sensorTopic, 0, true, "");
_logUsermodInaSensor("Published empty message to remove sensor: %s", sensorTopic);
}
/**
** Function to publish sensor data to MQTT
**/
bool UsermodINA2xx::onMqttMessage(char* topic, char* payload) {
if (!WLED_MQTT_CONNECTED || !enabled) return false;
// Check if the message is for the correct topic
if (strstr(topic, "/sensor/ina2xx") != nullptr) {
_logUsermodInaSensor("MQTT message received on INA2xx topic");
StaticJsonDocument<512> jsonDoc;
// Parse the JSON payload
DeserializationError error = deserializeJson(jsonDoc, payload);
if (error) {
_logUsermodInaSensor("JSON Parse Error: %s", error.c_str());
return false;
}
// Update the energy values
if (!mqttStateRestored) {
// Only merge in retained MQTT values once!
if (jsonDoc.containsKey("daily_energy_kWh")) {
float restored = jsonDoc["daily_energy_kWh"];
if (!isnan(restored)) {
dailyEnergy_kWh += restored;
_logUsermodInaSensor("Merged daily energy from MQTT: +%.6f kWh => %.6f kWh", restored, dailyEnergy_kWh);
}
}
if (jsonDoc.containsKey("monthly_energy_kWh")) {
float restored = jsonDoc["monthly_energy_kWh"];
if (!isnan(restored)) {
monthlyEnergy_kWh += restored;
_logUsermodInaSensor("Merged monthly energy from MQTT: +%.6f kWh => %.6f kWh", restored, monthlyEnergy_kWh);
}
}
if (jsonDoc.containsKey("total_energy_kWh")) {
float restored = jsonDoc["total_energy_kWh"];
if (!isnan(restored)) {
totalEnergy_kWh += restored;
_logUsermodInaSensor("Merged total energy from MQTT: +%.6f kWh => %.6f kWh", restored, totalEnergy_kWh);
}
}
mqttStateRestored = true; // Only do this once!
}
return true;
}
return false;
}
/**
** Subscribe to MQTT topic for controlling the usermod
**/
void UsermodINA2xx::onMqttConnect(bool sessionPresent) {
if (!enabled) return;
if (WLED_MQTT_CONNECTED) {
char subuf[64];
if (mqttDeviceTopic[0] != 0) {
strcpy(subuf, mqttDeviceTopic);
strcat_P(subuf, PSTR("/sensor/ina2xx"));
mqtt->subscribe(subuf, 0);
_logUsermodInaSensor("Subscribed to MQTT topic: %s", subuf);
}
}
}
#endif
public:
// Destructor to clean up INA2xx object
~UsermodINA2xx() {
UsermodINA2xx::~UsermodINA2xx() {
if (_ina2xx) {
_logUsermodInaSensor("Cleaning up INA2xx sensor object");
delete _ina2xx;
@ -493,7 +440,7 @@ public:
}
// Setup function called once on boot or restart
void setup() override {
void UsermodINA2xx::setup() {
_logUsermodInaSensor("Setting up INA2xx sensor usermod");
initDone = updateINA2xxSettings(); // Configure INA2xx settings
if (initDone) {
@ -503,8 +450,41 @@ public:
}
}
#if INA_SENSOR_TYPE == 226
void UsermodINA2xx::checkForI2cErrors(){
byte errorCode = _ina2xx->getI2cErrorCode();
if(errorCode){
Serial.print("I2C error: ");
Serial.println(errorCode);
_logUsermodInaSensor("I2C error: %s", errorCode);
switch(errorCode){
case 1:
_logUsermodInaSensor("Data too long to fit in transmit buffer");
break;
case 2:
_logUsermodInaSensor("Received NACK on transmit of address");
break;
case 3:
_logUsermodInaSensor("Received NACK on transmit of data");
break;
case 4:
_logUsermodInaSensor("Other error");
break;
case 5:
_logUsermodInaSensor("Timeout");
break;
default:
_logUsermodInaSensor("Can't identify the error");
}
if(errorCode){
while(1){}
}
}
}
#endif
// Loop function called continuously
void loop() override {
void UsermodINA2xx::loop() {
// Check if the usermod is enabled and the check interval has elapsed
if (!enabled || !initDone || !_ina2xx || millis() - lastCheck < checkInterval) {
return;
@ -526,7 +506,13 @@ public:
power = roundDecimals(rawPower_mW / 1000.0); // Convert from mW to W
loadVoltage = roundDecimals(busVoltage + (shuntVoltage / 1000));
#if INA_SENSOR_TYPE == 219
overflow = _ina2xx->getOverflow() != 0;
#elif INA_SENSOR_TYPE == 226
overflow = _ina2xx->overflow;
checkForI2cErrors();
#endif
_logUsermodInaSensor("Sensor readings - Shunt: %.3f mV, Bus: %.3f V, Load: %.3f V", shuntVoltage, busVoltage, loadVoltage);
_logUsermodInaSensor("Sensor readings - Current: %.3f A (%.3f mA), Power: %.3f W (%.3f mW)", current, current_mA, power, power_mW);
@ -608,11 +594,11 @@ public:
mqttRemoveHassSensor(F("Voltage"), F("sensor"));
mqttRemoveHassSensor(F("Power"), F("sensor"));
mqttRemoveHassSensor(F("Shunt Voltage"), F("sensor"));
mqttRemoveHassSensor(F("Shunt Resistor"), F("sensor"));
mqttRemoveHassSensor(F("Overflow"), F("sensor"));
mqttRemoveHassSensor(F("Daily Energy"), F("sensor"));
mqttRemoveHassSensor(F("Monthly Energy"), F("sensor"));
mqttRemoveHassSensor(F("Total Energy"), F("sensor"));
mqttRemoveHassSensor(F("Shunt Resistor"), F("sensor"));
mqttRemoveHassSensor(F("Overflow"), F("sensor"));
haDiscoverySent = false; // Mark as sent to avoid repeating
_logUsermodInaSensor("Home Assistant discovery removal complete");
@ -621,77 +607,10 @@ public:
#endif
}
#ifndef WLED_DISABLE_MQTT
/**
** Function to publish sensor data to MQTT
**/
bool onMqttMessage(char* topic, char* payload) override {
if (!WLED_MQTT_CONNECTED || !enabled) return false;
// Check if the message is for the correct topic
if (strstr(topic, "/sensor/ina2xx") != nullptr) {
_logUsermodInaSensor("MQTT message received on INA2xx topic");
StaticJsonDocument<512> jsonDoc;
// Parse the JSON payload
DeserializationError error = deserializeJson(jsonDoc, payload);
if (error) {
_logUsermodInaSensor("JSON Parse Error: %s", error.c_str());
return false;
}
// Update the energy values
if (!mqttStateRestored) {
// Only merge in retained MQTT values once!
if (jsonDoc.containsKey("daily_energy_kWh")) {
float restored = jsonDoc["daily_energy_kWh"];
if (!isnan(restored)) {
dailyEnergy_kWh += restored;
_logUsermodInaSensor("Merged daily energy from MQTT: +%.6f kWh => %.6f kWh", restored, dailyEnergy_kWh);
}
}
if (jsonDoc.containsKey("monthly_energy_kWh")) {
float restored = jsonDoc["monthly_energy_kWh"];
if (!isnan(restored)) {
monthlyEnergy_kWh += restored;
_logUsermodInaSensor("Merged monthly energy from MQTT: +%.6f kWh => %.6f kWh", restored, monthlyEnergy_kWh);
}
}
if (jsonDoc.containsKey("total_energy_kWh")) {
float restored = jsonDoc["total_energy_kWh"];
if (!isnan(restored)) {
totalEnergy_kWh += restored;
_logUsermodInaSensor("Merged total energy from MQTT: +%.6f kWh => %.6f kWh", restored, totalEnergy_kWh);
}
}
mqttStateRestored = true; // Only do this once!
}
return true;
}
return false;
}
/**
** Subscribe to MQTT topic for controlling the usermod
**/
void onMqttConnect(bool sessionPresent) override {
if (!enabled) return;
if (WLED_MQTT_CONNECTED) {
char subuf[64];
if (mqttDeviceTopic[0] != 0) {
strcpy(subuf, mqttDeviceTopic);
strcat_P(subuf, PSTR("/sensor/ina2xx"));
mqtt->subscribe(subuf, 0);
_logUsermodInaSensor("Subscribed to MQTT topic: %s", subuf);
}
}
}
#endif
/**
** Add energy consumption data to a JSON object for reporting
**/
void addToJsonInfo(JsonObject &root) {
void UsermodINA2xx::addToJsonInfo(JsonObject &root) {
JsonObject user = root[F("u")];
if (user.isNull()) {
user = root.createNestedObject(F("u"));
@ -731,7 +650,7 @@ public:
/**
** Add the current state of energy consumption to a JSON object
**/
void addToJsonState(JsonObject& root) override {
void UsermodINA2xx::addToJsonState(JsonObject& root) {
if (!enabled) return;
if (!initDone) {
_logUsermodInaSensor("Not adding to JSON state - initialization not complete");
@ -764,7 +683,7 @@ public:
/**
** Read energy consumption data from a JSON object
**/
void readFromJsonState(JsonObject& root) override {
void UsermodINA2xx::readFromJsonState(JsonObject& root) {
if (!enabled) return;
if (!initDone) { // Prevent crashes on boot if initialization is not done
_logUsermodInaSensor("Not reading from JSON state - initialization not complete");
@ -829,7 +748,7 @@ public:
/**
** Append configuration options to the Usermod menu.
**/
void addToConfig(JsonObject& root) override {
void UsermodINA2xx::addToConfig(JsonObject& root) {
JsonObject top = root.createNestedObject(F("INA2xx"));
top["Enabled"] = enabled;
top["i2c_address"] = static_cast<uint8_t>(_i2cAddress);
@ -838,9 +757,14 @@ public:
top["decimals"] = _decimalFactor;
top["shunt_resistor"] = shuntResistor;
top["correction_factor"] = correctionFactor;
#if INA_SENSOR_TYPE == 219
top["pga_gain"] = pGain;
top["bus_range"] = busRange;
top["shunt_offset"] = shuntVoltOffset_mV;
#elif INA_SENSOR_TYPE == 226
top["average"] = average;
top["currentRange"] = currentRange;
#endif
#ifndef WLED_DISABLE_MQTT
// Store MQTT settings if MQTT is not disabled
@ -853,7 +777,7 @@ public:
/**
** Append configuration UI data for the usermod menu.
**/
void appendConfigData() override {
void UsermodINA2xx::appendConfigData() {
// Append the dropdown for I2C address selection
oappend("dd=addDropdown('INA2xx','i2c_address');");
oappend("addOption(dd,'0x40 - Default',0x40, true);");
@ -861,12 +785,19 @@ public:
oappend("addOption(dd,'0x44 - A1 soldered',0x44);");
oappend("addOption(dd,'0x45 - A0 and A1 soldered',0x45);");
// Append the dropdown for decimal precision (0 to 3)
oappend("df=addDropdown('INA2xx','decimals');");
for (int i = 0; i <= 3; i++) {
oappend(String("addOption(df,'" + String(i) + "'," + String(i) + (i == 2 ? ", true);" : ");")).c_str());
}
#if INA_SENSOR_TYPE == 219
// Append the dropdown for ADC mode (resolution + samples)
oappend("ct=addDropdown('INA2xx','conversion_time');");
oappend("addOption(ct,'9-Bit (84 µs)',0);");
oappend("addOption(ct,'10-Bit (148 µs)',1);");
oappend("addOption(ct,'11-Bit (276 µs)',2);");
oappend("addOption(ct,'12-Bit (532 µs)',3, true);");
oappend("addOption(ct,'12-Bit (532 µs) (default)',3, true);");
oappend("addOption(ct,'2 samples (1.06 ms)',9);");
oappend("addOption(ct,'4 samples (2.13 ms)',10);");
oappend("addOption(ct,'8 samples (4.26 ms)',11);");
@ -874,28 +805,66 @@ public:
oappend("addOption(ct,'32 samples (17.02 ms)',13);");
oappend("addOption(ct,'64 samples (34.05 ms)',14);");
oappend("addOption(ct,'128 samples (68.10 ms)',15);");
#elif INA_SENSOR_TYPE == 226
oappend("ct=addDropdown('INA2xx','conversion_time');");
oappend("addOption(ct,'140 µs',0);");
oappend("addOption(ct,'204 µs',1);");
oappend("addOption(ct,'332 µs',2);");
oappend("addOption(ct,'588 µs',3);");
oappend("addOption(ct,'1.1 ms (default)',4, true);");
oappend("addOption(ct,'2.116 ms',5);");
oappend("addOption(ct,'4.156 ms',6);");
oappend("addOption(ct,'8.244 ms',7);");
#endif
// Append the dropdown for decimal precision (0 to 3)
oappend("df=addDropdown('INA2xx','decimals');");
for (int i = 0; i <= 3; i++) {
oappend(String("addOption(df,'" + String(i) + "'," + String(i) + (i == 2 ? ", true);" : ");")).c_str());
//INA226
oappend("dda=addDropdown('INA2xx','average');");
oappend("addOption(dda,'1 (default)',0, true);");
oappend("addOption(dda,'4',512);");
oappend("addOption(dda,'16',1024);");
oappend("addOption(dda,'64',1536);");
oappend("addOption(dda,'128',2048);");
oappend("addOption(dda,'256',2560);");
oappend("addOption(dda,'512',3072);");
oappend("addOption(dda,'1024',3584);");
oappend("df = addDropdown('INA2xx','currentRange');");
for (int i = 1; i <= 100; i++) {
float amp = i * 0.1f; // 0.1, 0.2, …, 10.0
String strVal = String(amp, 1); // “0.1”, “0.2”, …, “1.3”, …, “10.0”
// Make “1.3” the default
bool selected = (fabs(amp - 1.3f) < 0.001f);
// Build the label: e.g. “1.3A (default)” or “3.7A”
String label = strVal + "A";
if (selected) label += " (default)";
// addOption(df,'3.7A',3.7);
String line = String("addOption(df,'")
+ label
+ "',"
+ strVal
+ (selected ? ", true);" : ");");
oappend(line.c_str());
}
//INA219
oappend("pg=addDropdown('INA2xx','pga_gain');");
oappend("addOption(pg,'40mV',0);");
oappend("addOption(pg,'80mV',2048);");
oappend("addOption(pg,'160mV',4096);");
oappend("addOption(pg,'320mV',6144, true);");
oappend("addOption(pg,'320mV (default)',6144, true);");
oappend("br=addDropdown('INA2xx','bus_range');");
oappend("addOption(br,'16V',0);");
oappend("addOption(br,'32V',8192, true);");
oappend("addOption(br,'32V (default)',8192, true);");
}
/**
** Read settings from the Usermod menu configuration
**/
bool readFromConfig(JsonObject& root) override {
bool UsermodINA2xx::readFromConfig(JsonObject& root) {
JsonObject top = root[FPSTR(_name)];
bool configComplete = !top.isNull();
@ -907,9 +876,14 @@ public:
UPDATE_CONFIG(top, "decimals", _decimalFactor, "%u");
UPDATE_CONFIG(top, "shunt_resistor", shuntResistor, "%.6f Ohms");
UPDATE_CONFIG(top, "correction_factor", correctionFactor, "%.3f");
#if INA_SENSOR_TYPE == 219
UPDATE_CONFIG(top, "pga_gain", pGain, "%d");
UPDATE_CONFIG(top, "bus_range", busRange, "%d");
UPDATE_CONFIG(top, "shunt_offset", shuntVoltOffset_mV,"%.3f mV");
#elif INA_SENSOR_TYPE == 226
UPDATE_CONFIG(top, "average", average, "%d");
UPDATE_CONFIG(top, "currentRange", currentRange, "%d");
#endif
#ifndef WLED_DISABLE_MQTT
UPDATE_CONFIG(top, "mqtt_publish", mqttPublish, "%u");
@ -929,7 +903,7 @@ public:
checkInterval = newInterval;
}
} else {
_logUsermodInaSensor("Invalid check_interval value %u; using default %u seconds", tempInterval, INA219_CHECK_INTERVAL);
_logUsermodInaSensor("Invalid check_interval value %u; using default %u seconds", tempInterval, INA2XX_CHECK_INTERVAL);
checkInterval = static_cast<uint32_t>(_checkInterval) * 1000UL;
}
} else {
@ -949,12 +923,6 @@ public:
/**
** Get the unique identifier for this usermod.
**/
uint16_t getId() override {
uint16_t UsermodINA2xx::getId() {
return USERMOD_ID_INA2XX;
}
};
const char UsermodINA2xx::_name[] PROGMEM = "INA2xx";
static UsermodINA2xx ina2xx_v2;
REGISTER_USERMOD(ina2xx_v2);

Wyświetl plik

@ -0,0 +1,194 @@
// Configurable settings for the INA2xx Usermod
#pragma once
#include "wled.h"
// Choose sensor type: 219 or 226
#ifndef INA_SENSOR_TYPE
#define INA_SENSOR_TYPE 219
#endif
#if INA_SENSOR_TYPE == 219
#include <INA219_WE.h>
using INA_SENSOR_CLASS = INA219_WE;
#elif INA_SENSOR_TYPE == 226
#include <INA226_WE.h>
using INA_SENSOR_CLASS = INA226_WE;
#else
#error "INA_SENSOR_TYPE must be 219 or 226"
#endif
// logging macro:
#define _logUsermodInaSensor(fmt, ...) \
DEBUG_PRINTF("[INA2xx_Sensor] " fmt "\n", ##__VA_ARGS__)
#ifndef INA2XX_ENABLED
#define INA2XX_ENABLED false // Default disabled value
#endif
#ifndef INA2XX_I2C_ADDRESS
#define INA2XX_I2C_ADDRESS 0x40 // Default I2C address
#endif
#ifndef INA2XX_CHECK_INTERVAL
#define INA2XX_CHECK_INTERVAL 5 // Default 5 seconds (will be converted to ms)
#endif
#ifndef INA2XX_CORRECTION_FACTOR
#define INA2XX_CORRECTION_FACTOR 1.0 // Default correction factor. Default 1.0
#endif
#ifndef INA2XX_CONVERSION_TIME
#if INA_SENSOR_TYPE == 219
#define INA2XX_CONVERSION_TIME BIT_MODE_12 // Conversion Time, Default 12-bit resolution
#elif INA_SENSOR_TYPE == 226
#define INA2XX_CONVERSION_TIME CONV_TIME_1100 // Conversion Time
#endif
#endif
#ifndef INA2XX_SHUNT_RESISTOR
#define INA2XX_SHUNT_RESISTOR 0.1 // Shunt Resistor value. Default 0.1 ohms
#endif
#ifndef INA2XX_DECIMAL_FACTOR
#define INA2XX_DECIMAL_FACTOR 3 // Decimal factor for current/power readings. Default 3 decimal places
#endif
#if INA_SENSOR_TYPE == 219
#ifndef INA2XX_BUSRANGE
#define INA2XX_BUSRANGE BRNG_32 // BRNG_16, BRNG_32
#endif
#ifndef INA2XX_P_GAIN
#define INA2XX_P_GAIN PG_320 // PG_40, PG_80, PG_160, PG_320
#endif
#ifndef INA2XX_SHUNT_VOLT_OFFSET
#define INA2XX_SHUNT_VOLT_OFFSET 0.0 // mV offset at zero current
#endif
#elif INA_SENSOR_TYPE == 226
#ifndef INA2XX_AVERAGES
#define INA2XX_AVERAGES AVERAGE_1
#endif
#ifndef INA2XX_RANGE
#define INA2XX_RANGE 1.3 //Current Range, Max 10.0 (10A)
#endif
#endif
#ifndef INA2XX_MQTT_PUBLISH
#define INA2XX_MQTT_PUBLISH false // Default: do not publish to MQTT
#endif
#ifndef INA2XX_MQTT_PUBLISH_ALWAYS
#define INA2XX_MQTT_PUBLISH_ALWAYS false // Default: only publish on change
#endif
#ifndef INA2XX_HA_DISCOVERY
#define INA2XX_HA_DISCOVERY false // Default: Home Assistant discovery disabled
#endif
#define UPDATE_CONFIG(obj, key, var, fmt) \
do { \
auto _tmp = var; \
if ( getJsonValue((obj)[(key)], _tmp) ) { \
if (_tmp != var) { \
_logUsermodInaSensor("%s updated to: " fmt, key, _tmp);\
var = _tmp; \
} \
} else { \
configComplete = false; \
} \
} while(0)
class UsermodINA2xx : public Usermod {
private:
static const char _name[];
bool initDone = false; // Flag for successful initialization
unsigned long lastCheck = 0; // Timestamp for the last check
bool alreadyLoggedDisabled = false;
// Define the variables using the pre-defined or default values
bool enabled = INA2XX_ENABLED;
uint8_t _i2cAddress = INA2XX_I2C_ADDRESS;
uint16_t _checkInterval = INA2XX_CHECK_INTERVAL; // seconds
uint32_t checkInterval = static_cast<uint32_t>(_checkInterval) * 1000UL; // ms
uint8_t _decimalFactor = INA2XX_DECIMAL_FACTOR;
float shuntResistor = INA2XX_SHUNT_RESISTOR;
float correctionFactor = INA2XX_CORRECTION_FACTOR;
#if INA_SENSOR_TYPE == 219
INA219_PGAIN pGain = static_cast<INA219_PGAIN>(INA2XX_P_GAIN);
INA219_BUS_RANGE busRange = static_cast<INA219_BUS_RANGE>(INA2XX_BUSRANGE);
float shuntVoltOffset_mV = INA2XX_SHUNT_VOLT_OFFSET;
INA219_ADC_MODE conversionTime = static_cast<INA219_ADC_MODE>(INA2XX_CONVERSION_TIME);
#elif INA_SENSOR_TYPE == 226
INA226_AVERAGES average = static_cast<INA226_AVERAGES>(INA2XX_AVERAGES);
INA226_CONV_TIME conversionTime = static_cast<INA226_CONV_TIME>(INA2XX_CONVERSION_TIME);
float currentRange = INA2XX_RANGE;
#endif
bool mqttPublish = INA2XX_MQTT_PUBLISH;
bool mqttPublishSent = !INA2XX_MQTT_PUBLISH;
bool mqttPublishAlways = INA2XX_MQTT_PUBLISH_ALWAYS;
bool haDiscovery = INA2XX_HA_DISCOVERY;
bool haDiscoverySent = !INA2XX_HA_DISCOVERY;
// Variables to store sensor readings
float busVoltage = 0.0;
float current = 0.0;
float current_mA = 0.0;
float power = 0.0;
float power_mW = 0.0;
float shuntVoltage = 0.0;
float loadVoltage = 0.0;
bool overflow = false;
// Last sent variables
float last_sent_shuntVoltage = 0;
float last_sent_busVoltage = 0;
float last_sent_loadVoltage = 0;
float last_sent_current = 0;
float last_sent_current_mA = 0;
float last_sent_power = 0;
float last_sent_power_mW = 0;
bool last_sent_overflow = false;
float dailyEnergy_kWh = 0.0; // Daily energy in kWh
float monthlyEnergy_kWh = 0.0; // Monthly energy in kWh
float totalEnergy_kWh = 0.0; // Total energy in kWh
unsigned long lastPublishTime = 0; // Track the last publish time
// Variables to store last reset timestamps
unsigned long dailyResetTime = 0;
unsigned long monthlyResetTime = 0;
bool mqttStateRestored = false;
INA_SENSOR_CLASS *_ina2xx = nullptr; // INA2xx sensor object
float roundDecimals(float val);
bool hasSignificantChange(float oldValue, float newValue, float threshold = 0.01f);
bool hasValueChanged();
void checkForI2cErrors();
bool updateINA2xxSettings();
String sanitizeMqttClientID(const String &clientID);
void updateEnergy(float power, unsigned long durationMs);
#ifndef WLED_DISABLE_MQTT
void publishMqtt(float shuntVoltage, float busVoltage, float loadVoltage,
float current, float current_mA, float power,
float power_mW, bool overflow);
void mqttCreateHassSensor(const String &name, const String &topic,
const String &deviceClass, const String &unitOfMeasurement,
const String &jsonKey, const String &SensorType);
void mqttRemoveHassSensor(const String &name, const String &SensorType);
#endif
public:
~UsermodINA2xx();
void setup() override;
void loop() override;
#ifndef WLED_DISABLE_MQTT
bool onMqttMessage(char* topic, char* payload) override;
void onMqttConnect(bool sessionPresent) override;
#endif
void addToJsonInfo(JsonObject &root) override;
void addToJsonState(JsonObject &root) override;
void readFromJsonState(JsonObject &root) override;
void addToConfig(JsonObject& root) override;
void appendConfigData() override;
bool readFromConfig(JsonObject& root) override;
uint16_t getId() override;
};
extern UsermodINA2xx ina2xx_v2;

Wyświetl plik

@ -1,5 +1,8 @@
{
"name": "ina2xx_v2",
"build": {
"libArchive": false
},
"dependencies": {
"wollewald/INA219_WE":"~1.3.8",
"wollewald/INA226_WE":"~1.2.12"