kopia lustrzana https://github.com/Aircoookie/WLED
Update Usermod to support INA226
rodzic
be77287bd1
commit
be0d1cacd9
Plik binarny nie jest wyświetlany.
Przed Szerokość: | Wysokość: | Rozmiar: 21 KiB |
|
@ -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);
|
|
@ -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;
|
|
@ -1,5 +1,8 @@
|
|||
{
|
||||
"name": "ina2xx_v2",
|
||||
"build": {
|
||||
"libArchive": false
|
||||
},
|
||||
"dependencies": {
|
||||
"wollewald/INA219_WE":"~1.3.8",
|
||||
"wollewald/INA226_WE":"~1.2.12"
|
||||
|
|
Ładowanie…
Reference in New Issue