diff --git a/tasmota/include/i18n.h b/tasmota/include/i18n.h index 8dc78ba2f..0d1238d06 100644 --- a/tasmota/include/i18n.h +++ b/tasmota/include/i18n.h @@ -499,6 +499,10 @@ #define D_JSON_ENERGYMONITOR "EnergyMonitor" #define D_JSON_MAXENERGYREACHED "MaxEnergyReached" +// xsns_100_ina3221.ino +#define D_JSON_CHARGE "Charge" + + // Commands xdrv_04_light.ino #define D_SO_CHANNELREMAP "ChannelRemap" // SO37 #define D_SO_MULTIPWM "MultiPWM" // SO68 diff --git a/tasmota/language/de_DE.h b/tasmota/language/de_DE.h index abe17123a..d17b8ecb0 100644 --- a/tasmota/language/de_DE.h +++ b/tasmota/language/de_DE.h @@ -536,6 +536,11 @@ #define D_ENERGY_YESTERDAY "Energie gestern" #define D_ENERGY_TOTAL "Energie gesamt" +// xsns_100_ina3221.ino +#define D_CHARGE "Ladung" +#define D_ENERGY "Energie" +#define D_UNIT_CHARGE "Ah" + // xdrv_27_shutter.ino #define D_OPEN "Öffnen" #define D_CLOSE "Schliessen" diff --git a/tasmota/language/en_GB.h b/tasmota/language/en_GB.h index 9ae001954..177478b31 100644 --- a/tasmota/language/en_GB.h +++ b/tasmota/language/en_GB.h @@ -535,6 +535,11 @@ #define D_ENERGY_TODAY "Energy Today" #define D_ENERGY_YESTERDAY "Energy Yesterday" #define D_ENERGY_TOTAL "Energy Total" + +// xsns_100_ina3221.ino +#define D_UNIT_CHARGE "Ah" +#define D_CHARGE "Charge" +#define D_ENERGY "Energy" // xdrv_27_shutter.ino #define D_OPEN "Open" diff --git a/tasmota/my_user_config.h b/tasmota/my_user_config.h index 34e16e4d3..7dfc66918 100644 --- a/tasmota/my_user_config.h +++ b/tasmota/my_user_config.h @@ -734,6 +734,8 @@ https://rya.nc/tasmota-fingerprint.html" // #define INA3221_ADDRESS1 // allow to change the 1st address to search for INA3221 to 0x41..0x43 // #define INA3221_MAX_COUNT // change the number of devices to search for (default 4). // // Both settings together allow to limit searching for INA3221 to only a subset of addresses +// #define INA3221_CALC_CHARGE_AH // calculate charge in Ah +// #define INA3221_CALC_ENERGY_WH // calculate energy in Wh // #define USE_PMSA003I // [I2cDriver78] Enable PMSA003I Air Quality Sensor (I2C address 0x12) (+1k8 code) // #define USE_GDK101 // [I2cDriver79] Enable GDK101 sensor (I2C addresses 0x18 - 0x1B) (+1k2 code) // #define GDK101_SHOW_FW_VERSION diff --git a/tasmota/tasmota_xsns_sensor/xsns_100_ina3221.ino b/tasmota/tasmota_xsns_sensor/xsns_100_ina3221.ino index b2ec980e8..6c67243b5 100644 --- a/tasmota/tasmota_xsns_sensor/xsns_100_ina3221.ino +++ b/tasmota/tasmota_xsns_sensor/xsns_100_ina3221.ino @@ -1,7 +1,7 @@ /* xsns_100_ina3221.ino - INA3221 3-channels Current Sensor support for Tasmota - Copyright (C) 2021 Barbudor and Theo Arends + Copyright (C) 2021 Barbudor and Theo Arends + fb-pilot 2024-3-7 Based on Barbudor's CircuitPython_INA3221 This program is free software: you can redistribute it and/or modify @@ -30,16 +30,32 @@ * IMPORTANT INFORMATION * By default the driver is enabled to support up to 4 INA3221 from hte above addresse * If you want to enable less addresses in order to use other I2C chip on those addresses you - * can define in your user_config_override.h the following: - * #define INA3221_MAX_COUNT the max number of INA3221 to support - * #define INA3221_ADDRESS1 the I2C address of the 1st INA3221 + * can define in your user_config_override.h the following: + * #define INA3221_MAX_COUNT the max number of INA3221 to support + * #define INA3221_ADDRESS1 the I2C address of the 1st INA3221 * For example to support only 2 INA3221 at addresses 0x41 and 0x42 you can use: - * #define INA3221_MAX_COUNT 2 - * #define INA3221_ADDRESS1 0x41 - * That would leave 0x40 and 0x42 for other devices + * #define INA3221_MAX_COUNT 2 + * #define INA3221_ADDRESS1 0x41 + * That would leave 0x40 and 0x43 for other devices + * By defining INA3221_CALC_CHARGE_AH INA3221_CALC_ENERGY_WH the driver adds a estimation of + * energies in Ah and Wh to the output, To reset the energie calculation disable tge + * according INA3221 chanel by setting the shunt to 0.0 and enable it again + * For example : + * sensor100 1,0.0,0.1,0.2 and sensor100 1,0.1,0.1,0.2 will reset channel 1 * Nevertheless, hte driver tries to identifiy if the chip as an address is a IN3221 \*********************************************************************************************/ +// setup of INA3221 config + +#ifndef INA3221_CONFIG_INIT +#define INA3221_CONFIG_INIT INA3221_AVERAGING_16_SAMPLES |\ + INA3221_VBUS_CONV_TIME_1MS |\ + INA3221_SHUNT_CONV_TIME_1MS +// that results in a complete conversions sequence in 6,6 ms and a slope time of 105,6 ms +#endif + +// end of setup of INA3221 config + #define XSNS_100 100 #define XI2C_72 72 // See I2CDEVICES.md @@ -61,6 +77,8 @@ #error "**** INA3221 bad combination for ADDRESS1 and MAX_COUNT ****" #endif + + #define INA3221_NB_CHAN (3) // Config register - ch : 0..2 @@ -71,13 +89,13 @@ #define INA3221_ENABLE_CH(ch) (0x4000>>(ch)) // default: set #define INA3221_AVERAGING_MASK (0x0E00) -#define INA3221_AVERAGING_NONE (0x0000) // 1 sample, default +#define INA3221_AVERAGING_NONE (0x0000) #define INA3221_AVERAGING_4_SAMPLES (0x0200) #define INA3221_AVERAGING_16_SAMPLES (0x0400) #define INA3221_AVERAGING_64_SAMPLES (0x0600) #define INA3221_AVERAGING_128_SAMPLES (0x0800) #define INA3221_AVERAGING_256_SAMPLES (0x0A00) -#define INA3221_AVERAGING_512_SAMPLES (0x0C00) +#define INA3221_AVERAGING_512_SAMPLES (0x0C00) // 1 sample #define INA3221_AVERAGING_1024_SAMPLES (0x0E00) #define INA3221_VBUS_CONV_TIME_MASK (0x01C0) @@ -85,7 +103,7 @@ #define INA3221_VBUS_CONV_TIME_204US (0x0040) #define INA3221_VBUS_CONV_TIME_332US (0x0080) #define INA3221_VBUS_CONV_TIME_588US (0x00C0) -#define INA3221_VBUS_CONV_TIME_1MS (0x0100) // 1.1ms, default +#define INA3221_VBUS_CONV_TIME_1MS (0x0100) // 1.1ms #define INA3221_VBUS_CONV_TIME_2MS (0x0140) // 2.116ms #define INA3221_VBUS_CONV_TIME_4MS (0x0180) // 4.156ms #define INA3221_VBUS_CONV_TIME_8MS (0x01C0) // 8.244ms @@ -95,7 +113,7 @@ #define INA3221_SHUNT_CONV_TIME_204US (0x0008) #define INA3221_SHUNT_CONV_TIME_332US (0x0010) #define INA3221_SHUNT_CONV_TIME_588US (0x0018) -#define INA3221_SHUNT_CONV_TIME_1MS (0x0020) // 1.1ms, default +#define INA3221_SHUNT_CONV_TIME_1MS (0x0020) // 1.1ms #define INA3221_SHUNT_CONV_TIME_2MS (0x0028) // 2.116ms #define INA3221_SHUNT_CONV_TIME_4MS (0x0030) // 4.156ms #define INA3221_SHUNT_CONV_TIME_8MS (0x0038) // 8.244ms @@ -108,7 +126,7 @@ #define INA3221_MODE_POWER_DOWN2 (0x0004) #define INA3221_MODE_SHUNT_VOLTAGE_CONTINUOUS (0x0005) #define INA3221_MODE_BUS_VOLTAGE_CONTINUOUS (0x0006) -#define INA3221_MODE_SHUNT_AND_BUS_CONTINOUS (0x0007) +#define INA3221_MODE_SHUNT_AND_BUS_CONTINOUS (0x0007) // default // Other registers - ch = 0..2 #define INA3221_REG_SHUNT_VOLTAGE_CH(ch) (0x01+((ch)<<1)) @@ -145,6 +163,8 @@ #define INA3221C_SHUNT_ADC_LSB (0.00004) // VShunt ADC LSB is 40µV #define INA3221_DEFAULT_SHUNT_RESISTOR (0.1) +#define INA3221_ENERGY_FACTOR (1.0/(3600.0*1000.0)) // reading values all xx ms + #ifdef DEBUG_TASMOTA_SENSOR // temporary strings for floating point in debug messages char _ina3221_dbg1[FLOATSZ]; @@ -154,10 +174,21 @@ char _ina3221_dbg2[FLOATSZ]; const char INA3221_SENSORCMND_START[] PROGMEM = "{\"" D_CMND_SENSOR "%d\":{\"idx\":%d,\"addr\":\"0x%02X\",\"rshunt\":["; const char INA3221_SENSORCMND_END[] PROGMEM = "]}}"; +#if defined(INA3221_CALC_CHARGE_AH) || defined(INA3221_CALC_ENERGY_WH) +uint32_t INA3221_last_millis; +uint32_t INA3221_delta_ms; +#endif + struct INA3221_Channel_Data { float voltage; float current; float shunt; + #ifdef INA3221_CALC_CHARGE_AH + float charge_ah; + #endif + #ifdef INA3221_CALC_ENERGY_WH + float energy_wh; + #endif }; struct INA3221_Data { @@ -188,10 +219,8 @@ bool Ina3221SetConfig(uint8_t addr) // write default configuration uint16_t config = INA3221_ENABLE_MASK | - INA3221_AVERAGING_16_SAMPLES | - INA3221_VBUS_CONV_TIME_1MS | - INA3221_SHUNT_CONV_TIME_1MS | - INA3221_MODE_SHUNT_AND_BUS_CONTINOUS; + INA3221_CONFIG_INIT | + INA3221_MODE_BUS_VOLTAGE_CONTINUOUS; DEBUG_SENSOR_LOG(PSTR(D_INA3221 ":SetConfig: addr:0x%02X, config=0x%04X"), addr, config); // Set Config register if (!I2cWrite16(addr, INA3221_REG_CONFIG, config)) @@ -218,10 +247,17 @@ bool Ina3221PowerDown(uint8_t device) void Ina3221SetShunt(uint8_t device, uint8_t channel, float shunt) { Ina3221Data[device].chan[channel].shunt = shunt; - if (shunt > 0.0) + if (shunt > 0.0){ Ina3221Data[device].enabled_chan |= (1<current = INA3221C_SHUNT_ADC_LSB * (float)(shunt_voltage >> 3) / pChannel->shunt; + #ifdef INA3221_CALC_CHARGE_AH + pChannel->charge_ah += (pChannel->current * (float)INA3221_delta_ms * INA3221_ENERGY_FACTOR); + #endif + #ifdef INA3221_CALC_ENERGY_WH + pChannel->energy_wh += (pChannel->current * pChannel->voltage * (float)INA3221_delta_ms * INA3221_ENERGY_FACTOR); + #endif } else { pChannel->current = INFINITY; + #ifdef INA3221_CALC_CHARGE_AH + pChannel->charge_ah = INFINITY; + #endif + #ifdef INA3221_CALC_ENERGY_WH + pChannel->energy_wh = INFINITY; + #endif } #ifdef DEBUG_TASMOTA_SENSOR @@ -318,6 +366,9 @@ void Ina3221Detect(void) free(Ina3221Data); Ina3221Data = nullptr; } + #if defined(INA3221_CALC_CHARGE_AH) || defined(INA3221_CALC_ENERGY_WH) + INA3221_last_millis = millis(); + #endif } void Ina3221Every250ms(void) @@ -327,18 +378,41 @@ void Ina3221Every250ms(void) for (int chan = 0 ; enabled_chan ; chan++, enabled_chan>>=1) { if (0x01 & enabled_chan) Ina3221Read(_ina3221_current_device, chan); - } + } - if (++_ina3221_current_device >= INA3221_MAX_COUNT) + if (++_ina3221_current_device >= INA3221_MAX_COUNT){ _ina3221_current_device = 0; + #if defined(INA3221_CALC_CHARGE_AH) || defined(INA3221_CALC_ENERGY_WH) + INA3221_delta_ms = millis()-INA3221_last_millis; + INA3221_last_millis = millis(); + #endif + } } #ifdef USE_WEBSERVER +// {s} = , {m} = , {e} = +#define INA3221_AL "" const char HTTP_SNS_INA3221_HEADER[] PROGMEM = - "{s}" D_INA3221 "     " D_VOLTAGE "  " D_CURRENT "  " D_POWERUSAGE " {e}"; + // "{s}" D_INA3221 "     " D_VOLTAGE "  " D_CURRENT "  " D_POWERUSAGE + "{s}" D_INA3221 "    " INA3221_AL D_VOLTAGE "  " INA3221_AL D_CURRENT "  " INA3221_AL D_POWERUSAGE + #ifdef INA3221_CALC_CHARGE_AH + " " INA3221_AL D_CHARGE + #endif + #ifdef INA3221_CALC_ENERGY_WH + " " INA3221_AL D_ENERGY + #endif + "{e}"; -const char HTTP_SNS_INA3221_DATA[] PROGMEM = - "{s}%s  %s " D_UNIT_VOLT "  %s " D_UNIT_AMPERE "  %s " D_UNIT_WATT " {e}"; + const char HTTP_SNS_INA3221_DATA[] PROGMEM = + // "{s}%s  %s " D_UNIT_VOLT "  %s " D_UNIT_AMPERE "  %s " D_UNIT_WATT + "{s}%s " INA3221_AL " %s " D_UNIT_VOLT "  " INA3221_AL " %s " D_UNIT_AMPERE "  " INA3221_AL " %s " D_UNIT_WATT + #ifdef INA3221_CALC_CHARGE_AH + " " INA3221_AL " %s " D_UNIT_CHARGE + #endif + #ifdef INA3221_CALC_ENERGY_WH + " " INA3221_AL " %s " D_UNIT_WATTHOUR + #endif + "{e}"; #endif // USE_WEBSERVER void Ina3221Show(bool json) @@ -348,6 +422,12 @@ void Ina3221Show(bool json) char voltage[3*FLOATSZ+3]; char current[3*FLOATSZ+3]; char power[3*FLOATSZ+3]; + #ifdef INA3221_CALC_CHARGE_AH + char charge_ah[3*FLOATSZ+3]; + #endif + #ifdef INA3221_CALC_ENERGY_WH + char energy_wh[3*FLOATSZ+3]; + #endif if (json) { // data @@ -355,34 +435,75 @@ void Ina3221Show(bool json) uint8_t enabled_chan = Ina3221Data[device].enabled_chan; if (!enabled_chan) continue; - if (Ina3221count > 1) + if (Ina3221count > 1){ snprintf_P(name, sizeof(name), PSTR("%s%c%d"), INA3221_TYPE, IndexSeparator(), device +1); - else + } + else{ snprintf_P(name, sizeof(name), PSTR("%s"), INA3221_TYPE); - voltage[0] = current[0] = power[0] = '\0'; - + voltage[0] = current[0] = power[0] = + #ifdef INA3221_CALC_CHARGE_AH + charge_ah[0] = + #endif + #ifdef INA3221_CALC_ENERGY_WH + energy_wh[0] = + #endif + '\0'; + } for (int chan=0 ; enabled_chan ; chan++, enabled_chan>>=1) { if (0x01 & enabled_chan) { dtostrfd(Ina3221Data[device].chan[chan].voltage, Settings->flag2.voltage_resolution, temp); strncat(voltage, temp, sizeof(voltage)); dtostrfd(Ina3221Data[device].chan[chan].current, Settings->flag2.current_resolution, temp); - strncat(current, temp, sizeof(voltage)); + strncat(current, temp, sizeof(current)); dtostrfd(Ina3221Data[device].chan[chan].voltage * Ina3221Data[device].chan[chan].current, Settings->flag2.wattage_resolution, temp); - strncat(power, temp, sizeof(voltage)); - } //if enabled - else { + strncat(power, temp, sizeof(power)); + #ifdef INA3221_CALC_CHARGE_AH + dtostrfd(Ina3221Data[device].chan[chan].charge_ah, Settings->flag2.energy_resolution, temp); + strncat(charge_ah, temp, sizeof(charge_ah)); + #endif + #ifdef INA3221_CALC_ENERGY_WH + dtostrfd(Ina3221Data[device].chan[chan].energy_wh, Settings->flag2.energy_resolution, temp); + strncat(energy_wh, temp, sizeof(energy_wh)); + #endif + } //if enabled + else { strncat(voltage, "null", sizeof(voltage)); - strncat(current, "null", sizeof(voltage)); - strncat(power, "null", sizeof(voltage)); + strncat(current, "null", sizeof(current)); + strncat(power, "null", sizeof(power)); + #ifdef INA3221_CALC_CHARGE_AH + strncat(charge_ah, "null", sizeof(charge_ah)); + #endif + #ifdef INA3221_CALC_ENERGY_WH + strncat(energy_wh, "null", sizeof(energy_wh)); + #endif } if (0xFE & enabled_chan) { strncat(voltage, ",", sizeof(voltage)); - strncat(current, ",", sizeof(voltage)); - strncat(power, ",", sizeof(voltage)); + strncat(current, ",", sizeof(current)); + strncat(power, ",", sizeof(power)); + #ifdef INA3221_CALC_CHARGE_AH + strncat(charge_ah, ",", sizeof(charge_ah)); + #endif + #ifdef INA3221_CALC_ENERGY_WH + strncat(energy_wh, ",", sizeof(energy_wh)); + #endif } - } // for channel - ResponseAppend_P(PSTR(",\"%s\":{\"Id\":\"0x%02x\",\"" D_JSON_VOLTAGE "\":[%s],\"" D_JSON_CURRENT "\":[%s],\"" D_JSON_POWERUSAGE "\":[%s]}"), - name, Ina3221Data[device].i2caddr, voltage, current, power); + } // for channel + ResponseAppend_P(PSTR(",\"%s\":{\"Id\":\"0x%02x\",\"" D_JSON_VOLTAGE "\":[%s],\"" D_JSON_CURRENT "\":[%s],\"" D_JSON_POWERUSAGE "\":[%s]" + #ifdef INA3221_CALC_CHARGE_AH + ",\"" D_JSON_CHARGE "\":[%s]" + #endif + #ifdef INA3221_CALC_ENERGY_WH + ",\"" D_JSON_ENERGY "\":[%s]" + #endif + "}"),name, Ina3221Data[device].i2caddr, voltage, current, power + #ifdef INA3221_CALC_CHARGE_AH + , charge_ah + #endif + #ifdef INA3221_CALC_ENERGY_WH + , energy_wh + #endif + ); #ifdef USE_DOMOTICZ if (0 == TasmotaGlobal.tele_period) { DomoticzSensor(DZ_VOLTAGE, voltage); @@ -390,7 +511,7 @@ void Ina3221Show(bool json) } #endif // USE_DOMOTICZ } // for device - } // if json + } // if json #ifdef USE_WEBSERVER else { // header @@ -400,14 +521,28 @@ void Ina3221Show(bool json) uint8_t enabled_chan = Ina3221Data[device].enabled_chan; for (int chan=0 ; enabled_chan ; chan++, enabled_chan>>=1) { if (0x01 & enabled_chan) { - if (Ina3221count > 1) + if (Ina3221count > 1){ snprintf_P(name, sizeof(name), PSTR("%s%c%d:%d"), INA3221_TYPE, IndexSeparator(), device +1, chan); - else + }else{ snprintf_P(name, sizeof(name), PSTR("%s:%d"), INA3221_TYPE, chan); - dtostrfd(Ina3221Data[device].chan[chan].voltage, Settings->flag2.voltage_resolution, voltage); - dtostrfd(Ina3221Data[device].chan[chan].current, Settings->flag2.current_resolution, current); - dtostrfd(Ina3221Data[device].chan[chan].voltage * Ina3221Data[device].chan[chan].current, Settings->flag2.wattage_resolution, power); - WSContentSend_PD(HTTP_SNS_INA3221_DATA, name, voltage, current, power); + dtostrfd(Ina3221Data[device].chan[chan].voltage, Settings->flag2.voltage_resolution, voltage); + dtostrfd(Ina3221Data[device].chan[chan].current, Settings->flag2.current_resolution, current); + dtostrfd(Ina3221Data[device].chan[chan].voltage * Ina3221Data[device].chan[chan].current, Settings->flag2.wattage_resolution, power); + #ifdef INA3221_CALC_CHARGE_AH + dtostrfd(Ina3221Data[device].chan[chan].charge_ah, Settings->flag2.energy_resolution, charge_ah); + #endif + #ifdef INA3221_CALC_ENERGY_WH + dtostrfd(Ina3221Data[device].chan[chan].energy_wh, Settings->flag2.energy_resolution, energy_wh); + #endif + WSContentSend_PD(HTTP_SNS_INA3221_DATA, name, voltage, current, power + #ifdef INA3221_CALC_CHARGE_AH + , charge_ah + #endif + #ifdef INA3221_CALC_ENERGY_WH + , energy_wh + #endif + ); + } } // if active } // for channel } // for device