Merge branch 'development' into pre-release-12.2

This commit is contained in:
Theo Arends 2022-10-16 15:06:42 +02:00
commit 34f441ce7d
17 changed files with 1262 additions and 563 deletions

View File

@ -9,6 +9,8 @@ All notable changes to this project will be documented in this file.
## [12.1.1.6] 20221017 ## [12.1.1.6] 20221017
### Added ### Added
- Command ``WcClock 10..200`` set webcam clock in MHz. Default is 20 - Command ``WcClock 10..200`` set webcam clock in MHz. Default is 20
- ESP32 Automatically resize FS to max flash size at initial boot (#16838)
- Command ``SspmPowerOnState<relay> 0|1|2`` to set Sonoff SPM 4Relay module v1.2.0 power on state overruling tasmota global power on state. 0 = Off, 1 = On, 2 = Saved state (#13447)
## [12.1.1.5] 20221013 ## [12.1.1.5] 20221013
### Added ### Added

View File

@ -116,6 +116,7 @@ The latter links can be used for OTA upgrades too like ``OtaUrl http://ota.tasmo
- Command ``UrlFetch <url>`` to download a file to filesystem - Command ``UrlFetch <url>`` to download a file to filesystem
- Command ``DspSpeed 2..127`` to control message rotation speed on display of POWR3xxD and THR3xxD - Command ``DspSpeed 2..127`` to control message rotation speed on display of POWR3xxD and THR3xxD
- Command ``DspLine<1|2> <index>,<unit>,<index>,<unit>,...`` to select message(s) on display of POWR3xxD and THR3xxD - Command ``DspLine<1|2> <index>,<unit>,<index>,<unit>,...`` to select message(s) on display of POWR3xxD and THR3xxD
- Command ``SspmPowerOnState<relay> 0|1|2`` to set Sonoff SPM 4Relay module v1.2.0 power on state overruling tasmota global power on state. 0 = Off, 1 = On, 2 = Saved state [#13447](https://github.com/arendst/Tasmota/issues/13447)
- Command ``Sunrise 0..3`` to select sunrise dawn angle between Normal, Civil, Nautical or Astronomical [#16795](https://github.com/arendst/Tasmota/issues/16795) - Command ``Sunrise 0..3`` to select sunrise dawn angle between Normal, Civil, Nautical or Astronomical [#16795](https://github.com/arendst/Tasmota/issues/16795)
- Command ``WcClock 10..200`` set webcam clock in MHz. Default is 20 - Command ``WcClock 10..200`` set webcam clock in MHz. Default is 20
- Support for Shelly Plus 2PM - Support for Shelly Plus 2PM
@ -133,6 +134,7 @@ The latter links can be used for OTA upgrades too like ``OtaUrl http://ota.tasmo
- Support for Ethernet in ESP32 safeboot firmware [#16388](https://github.com/arendst/Tasmota/issues/16388) - Support for Ethernet in ESP32 safeboot firmware [#16388](https://github.com/arendst/Tasmota/issues/16388)
- ESP32-S3 support for internal temperature sensor - ESP32-S3 support for internal temperature sensor
- ESP32-S2 and ESP32-S3 touch button support - ESP32-S2 and ESP32-S3 touch button support
- ESP32 Automatically resize FS to max flash size at initial boot [#16838](https://github.com/arendst/Tasmota/issues/16838)
- Berry has persistent MQTT subscriptions: auto-subscribe at (re)connection - Berry has persistent MQTT subscriptions: auto-subscribe at (re)connection
- Berry automated solidification of code - Berry automated solidification of code
- LVGL/HASPmota add tiny "pixel perfect" fonts for small screens [#16758](https://github.com/arendst/Tasmota/issues/16758) - LVGL/HASPmota add tiny "pixel perfect" fonts for small screens [#16758](https://github.com/arendst/Tasmota/issues/16758)

View File

@ -37,14 +37,23 @@ int WiFiClass32::getPhyMode() {
int phy_mode = 0; // " BGNL" int phy_mode = 0; // " BGNL"
uint8_t protocol_bitmap; uint8_t protocol_bitmap;
if (esp_wifi_get_protocol(WIFI_IF_STA, &protocol_bitmap) == ESP_OK) { if (esp_wifi_get_protocol(WIFI_IF_STA, &protocol_bitmap) == ESP_OK) {
if (protocol_bitmap & 1) { phy_mode = 1; } // 11b if (protocol_bitmap & 1) { phy_mode = WIFI_PHY_MODE_11B; } // 1 = 11b
if (protocol_bitmap & 2) { phy_mode = 2; } // 11g if (protocol_bitmap & 2) { phy_mode = WIFI_PHY_MODE_11G; } // 2 = 11bg
if (protocol_bitmap & 4) { phy_mode = 3; } // 11n if (protocol_bitmap & 4) { phy_mode = WIFI_PHY_MODE_11N; } // 3 = 11bgn
if (protocol_bitmap & 8) { phy_mode = 4; } // Low rate if (protocol_bitmap & 8) { phy_mode = 4; } // Low rate
} }
return phy_mode; return phy_mode;
} }
bool WiFiClass32::setPhyMode(WiFiPhyMode_t mode) {
uint8_t protocol_bitmap = WIFI_PROTOCOL_11B; // 1
switch (mode) {
case 3: protocol_bitmap |= WIFI_PROTOCOL_11N; // 4
case 2: protocol_bitmap |= WIFI_PROTOCOL_11G; // 2
}
return (ESP_OK == esp_wifi_set_protocol(WIFI_IF_STA, protocol_bitmap));
}
void WiFiClass32::wps_disable() { void WiFiClass32::wps_disable() {
} }

View File

@ -32,6 +32,11 @@
#define WIFI_LIGHT_SLEEP 1 #define WIFI_LIGHT_SLEEP 1
#define WIFI_MODEM_SLEEP 2 #define WIFI_MODEM_SLEEP 2
typedef enum WiFiPhyMode
{
WIFI_PHY_MODE_11B = 1, WIFI_PHY_MODE_11G = 2, WIFI_PHY_MODE_11N = 3
} WiFiPhyMode_t;
class WiFiClass32 : public WiFiClass class WiFiClass32 : public WiFiClass
{ {
public: public:
@ -41,6 +46,7 @@ public:
} }
static void setSleepMode(int iSleepMode); static void setSleepMode(int iSleepMode);
static int getPhyMode(); static int getPhyMode();
static bool setPhyMode(WiFiPhyMode_t mode);
static void wps_disable(); static void wps_disable();
static void setOutputPower(int n); static void setOutputPower(int n);

View File

@ -506,6 +506,105 @@ class Partition
self.otadata.save() self.otadata.save()
end end
# Internal: returns which flash sector contains the partition definition
# Returns 0 or 1, or `nil` if something went wrong
# Note: partition flash sector vary from ESP32 to ESP32C3/S3
static def get_flash_definition_sector()
import flash
for i:0..1
var offset = i * 0x1000
if flash.read(offset, 1) == bytes('E9') return offset end
end
end
# Internal: returns the maximum flash size possible
# Returns max flash size ok kB
def get_max_flash_size_k()
var flash_size_k = tasmota.memory()['flash']
var flash_size_real_k = tasmota.memory().find("flash_real", flash_size_k)
if (flash_size_k != flash_size_real_k) && self.get_flash_definition_sector() != nil
flash_size_k = flash_size_real_k # try to expand the flash size definition
end
return flash_size_k
end
# Internal: returns the unallocated flash size (in kB) beyond the file-system
# this indicates that the file-system can be extended (although erased at the same time)
def get_unallocated_k()
var last_slot = self.slots[-1]
if last_slot.is_spiffs()
# verify that last slot is filesystem
var flash_size_k = self.get_max_flash_size_k()
var partition_end_k = (last_slot.start + last_slot.sz) / 1024 # last kb used for fs
if partition_end_k < flash_size_k
return flash_size_k - partition_end_k
end
end
return 0
end
#- ---------------------------------------------------------------------- -#
#- Resize flash definition if needed
#- ---------------------------------------------------------------------- -#
def resize_max_flash_size_k()
var flash_size_k = tasmota.memory()['flash']
var flash_size_real_k = tasmota.memory().find("flash_real", flash_size_k)
var flash_definition_sector = self.get_flash_definition_sector()
if (flash_size_k != flash_size_real_k) && flash_definition_sector != nil
import flash
import string
flash_size_k = flash_size_real_k # try to expand the flash size definition
var flash_def = flash.read(flash_definition_sector, 4)
var size_before = flash_def[3]
var flash_size_code
var flash_size_real_m = flash_size_real_k / 1024 # size in MB
if flash_size_real_m == 1 flash_size_code = 0x00
elif flash_size_real_m == 2 flash_size_code = 0x10
elif flash_size_real_m == 4 flash_size_code = 0x20
elif flash_size_real_m == 8 flash_size_code = 0x30
elif flash_size_real_m == 16 flash_size_code = 0x40
end
if flash_size_code != nil
# apply the update
var old_def = flash_def[3]
flash_def[3] = (flash_def[3] & 0x0F) | flash_size_code
flash.write(flash_definition_sector, flash_def)
tasmota.log(string.format("UPL: changing flash definition from 0x02X to 0x%02X", old_def, flash_def[3]), 3)
else
raise "internal_error", "wrong flash size "+str(flash_size_real_m)
end
end
end
# Called at first boot
# Try to expand FS to max of flash size
def resize_fs_to_max()
import string
try
var unallocated = self.get_unallocated_k()
if unallocated <= 0 return nil end
tasmota.log(string.format("BRY: Trying to expand FS by %i kB", unallocated), 2)
self.resize_max_flash_size_k() # resize if needed
# since unallocated succeeded, we know the last slot is FS
var fs_slot = self.slots[-1]
fs_slot.sz += unallocated * 1024
self.save()
self.invalidate_spiffs() # erase SPIFFS or data is corrupt
# restart
tasmota.global.restart_flag = 2
tasmota.log("BRY: Successfully resized FS, restarting", 2)
except .. as e, m
tasmota.log(string.format("BRY: Exception> '%s' - %s", e, m), 2)
end
end
#- invalidate SPIFFS partition to force format at next boot -# #- invalidate SPIFFS partition to force format at next boot -#
#- we simply erase the first byte of the first 2 blocks in the SPIFFS partition -# #- we simply erase the first byte of the first 2 blocks in the SPIFFS partition -#
def invalidate_spiffs() def invalidate_spiffs()

View File

@ -204,19 +204,30 @@
#undef USE_I2C #undef USE_I2C
#undef USE_HOME_ASSISTANT #undef USE_HOME_ASSISTANT
#define USE_TASMOTA_DISCOVERY // Enable Tasmota Discovery support (+2k code) #define USE_TASMOTA_DISCOVERY // Enable Tasmota Discovery support (+2k code)
#undef USE_COUNTER #undef USE_DOMOTICZ
#undef USE_SERIAL_BRIDGE
#undef ROTARY_V1
#undef USE_IR_REMOTE #undef USE_IR_REMOTE
#undef USE_ADC
#undef USE_AC_ZERO_CROSS_DIMMER #undef USE_AC_ZERO_CROSS_DIMMER
#undef USE_PWM_DIMMER #undef USE_PWM_DIMMER
#undef USE_PWM_DIMMER_REMOTE
#undef USE_TUYA_MCU #undef USE_TUYA_MCU
#undef USE_EMULATION_HUE #undef USE_EMULATION_HUE
#undef USE_EMULATION_WEMO #undef USE_EMULATION_WEMO
#undef USE_BUZZER
#undef USE_ARILUX_RF #undef USE_ARILUX_RF
#undef USE_DS18x20 #undef USE_DS18x20
#undef USE_BMP
#undef USE_DHT
#undef USE_BH1750
#undef USE_WS2812 #undef USE_WS2812
#undef USE_ENERGY_SENSOR #undef USE_ENERGY_SENSOR
#undef USE_SHUTTER
#undef USE_DEVICE_GROUPS
//#undef USE_BERRY // Disable Berry scripting language //#undef USE_BERRY // Disable Berry scripting language
#undef USE_MI_ESP32 // (ESP32 only) Disable support for ESP32 as a BLE-bridge (+9k2 mem, +292k flash) #undef USE_MI_ESP32 // (ESP32 only) Disable support for ESP32 as a BLE-bridge (+9k2 mem, +292k flash)
#undef USE_BLE_ESP32
#endif // FIRMWARE_WEBCAM #endif // FIRMWARE_WEBCAM
/*********************************************************************************************\ /*********************************************************************************************\

View File

@ -598,6 +598,7 @@
#define USE_VEML6070_SHOW_RAW // VEML6070, shows the raw value of UV-A #define USE_VEML6070_SHOW_RAW // VEML6070, shows the raw value of UV-A
// #define USE_ADS1115 // [I2cDriver13] Enable ADS1115 16 bit A/D converter (I2C address 0x48, 0x49, 0x4A or 0x4B) based on Adafruit ADS1x15 library (no library needed) (+0k7 code) // #define USE_ADS1115 // [I2cDriver13] Enable ADS1115 16 bit A/D converter (I2C address 0x48, 0x49, 0x4A or 0x4B) based on Adafruit ADS1x15 library (no library needed) (+0k7 code)
// #define USE_INA219 // [I2cDriver14] Enable INA219 (I2C address 0x40, 0x41 0x44 or 0x45) Low voltage and current sensor (+1k code) // #define USE_INA219 // [I2cDriver14] Enable INA219 (I2C address 0x40, 0x41 0x44 or 0x45) Low voltage and current sensor (+1k code)
// #define INA219_SHUNT_RESISTOR (0.100) // 0.1 Ohm default shunt resistor, can be overriden in user_config_override or using Sensor13
// #define USE_INA226 // [I2cDriver35] Enable INA226 (I2C address 0x40, 0x41 0x44 or 0x45) Low voltage and current sensor (+2k3 code) // #define USE_INA226 // [I2cDriver35] Enable INA226 (I2C address 0x40, 0x41 0x44 or 0x45) Low voltage and current sensor (+2k3 code)
// #define USE_SHT3X // [I2cDriver15] Enable SHT3x (I2C address 0x44 or 0x45) or SHTC3 (I2C address 0x70) sensor (+0k7 code) // #define USE_SHT3X // [I2cDriver15] Enable SHT3x (I2C address 0x44 or 0x45) or SHTC3 (I2C address 0x70) sensor (+0k7 code)
// #define USE_TSL2561 // [I2cDriver16] Enable TSL2561 sensor (I2C address 0x29, 0x39 or 0x49) using library Joba_Tsl2561 (+2k3 code) // #define USE_TSL2561 // [I2cDriver16] Enable TSL2561 sensor (I2C address 0x29, 0x39 or 0x49) using library Joba_Tsl2561 (+2k3 code)

View File

@ -114,6 +114,7 @@ struct WIFI {
uint8_t wifi_test_counter = 0; uint8_t wifi_test_counter = 0;
uint16_t save_data_counter = 0; uint16_t save_data_counter = 0;
uint8_t old_wificonfig = MAX_WIFI_OPTION; // means "nothing yet saved here" uint8_t old_wificonfig = MAX_WIFI_OPTION; // means "nothing yet saved here"
uint8_t phy_mode = 0;
bool wifi_test_AP_TIMEOUT = false; bool wifi_test_AP_TIMEOUT = false;
bool wifi_Test_Restart = false; bool wifi_Test_Restart = false;
bool wifi_Test_Save_SSID2 = false; bool wifi_Test_Save_SSID2 = false;

View File

@ -2478,10 +2478,14 @@ void CmndWifi(void)
WifiEnable(); WifiEnable();
#endif #endif
} }
#ifdef ESP8266
} else if ((XdrvMailbox.payload >= 2) && (XdrvMailbox.payload <= 4)) { } else if ((XdrvMailbox.payload >= 2) && (XdrvMailbox.payload <= 4)) {
WiFi.setPhyMode(WiFiPhyMode_t(XdrvMailbox.payload - 1)); // 1-B/2-BG/3-BGN // Wifi 2 = B
// Wifi 3 = BG
// Wifi 4 = BGN
#ifdef ESP32
Wifi.phy_mode = XdrvMailbox.payload - 1;
#endif #endif
WiFi.setPhyMode(WiFiPhyMode_t(XdrvMailbox.payload - 1)); // 1-B/2-BG/3-BGN
} }
Response_P(PSTR("{\"" D_JSON_WIFI "\":\"%s\",\"" D_JSON_WIFI_MODE "\":\"11%c\"}"), GetStateText(Settings->flag4.network_wifi), pgm_read_byte(&kWifiPhyMode[WiFi.getPhyMode() & 0x3]) ); Response_P(PSTR("{\"" D_JSON_WIFI "\":\"%s\",\"" D_JSON_WIFI_MODE "\":\"11%c\"}"), GetStateText(Settings->flag4.network_wifi), pgm_read_byte(&kWifiPhyMode[WiFi.getPhyMode() & 0x3]) );
} }

View File

@ -217,6 +217,11 @@ void WifiBegin(uint8_t flag, uint8_t channel)
WiFiSetSleepMode(); WiFiSetSleepMode();
// if (WiFi.getPhyMode() != WIFI_PHY_MODE_11N) { WiFi.setPhyMode(WIFI_PHY_MODE_11N); } // B/G/N // if (WiFi.getPhyMode() != WIFI_PHY_MODE_11N) { WiFi.setPhyMode(WIFI_PHY_MODE_11N); } // B/G/N
// if (WiFi.getPhyMode() != WIFI_PHY_MODE_11G) { WiFi.setPhyMode(WIFI_PHY_MODE_11G); } // B/G // if (WiFi.getPhyMode() != WIFI_PHY_MODE_11G) { WiFi.setPhyMode(WIFI_PHY_MODE_11G); } // B/G
#ifdef ESP32
if (Wifi.phy_mode) {
WiFi.setPhyMode(WiFiPhyMode_t(Wifi.phy_mode)); // 1-B/2-BG/3-BGN
}
#endif
if (!WiFi.getAutoConnect()) { WiFi.setAutoConnect(true); } if (!WiFi.getAutoConnect()) { WiFi.setAutoConnect(true); }
// WiFi.setAutoReconnect(true); // WiFi.setAutoReconnect(true);
switch (flag) { switch (flag) {

View File

@ -4769,7 +4769,7 @@ extern char *SML_GetSVal(uint32_t index);
goto strexit; goto strexit;
} }
if (!strncmp(vname, "topic", 5)) { if (!strncmp(vname, "topic", 5)) {
if (sp) strlcpy(sp, SettingsText(SET_MQTT_TOPIC), glob_script_mem.max_ssize); if (sp) strlcpy(sp, TasmotaGlobal.mqtt_topic, glob_script_mem.max_ssize);
goto strexit; goto strexit;
} }
#ifdef USE_SCRIPT_TIMER #ifdef USE_SCRIPT_TIMER

View File

@ -33,19 +33,21 @@ extern "C" {
extern const be_ctypes_structure_t be_tasmota_global_struct = { extern const be_ctypes_structure_t be_tasmota_global_struct = {
sizeof(TasmotaGlobal), /* size in bytes */ sizeof(TasmotaGlobal), /* size in bytes */
3, /* number of elements */ 4, /* number of elements */
nullptr, nullptr,
(const be_ctypes_structure_item_t[3]) { (const be_ctypes_structure_item_t[4]) {
{ "devices_present", offsetof(TasmotaGlobal_t, devices_present), 0, 0, ctypes_u8, 0 }, { "devices_present", offsetof(TasmotaGlobal_t, devices_present), 0, 0, ctypes_u8, 0 },
{ "fast_loop_enabled", offsetof(TasmotaGlobal_t, berry_fast_loop_enabled), 0, 0, ctypes_u8, 0 }, { "fast_loop_enabled", offsetof(TasmotaGlobal_t, berry_fast_loop_enabled), 0, 0, ctypes_u8, 0 },
{ "restart_flag", offsetof(TasmotaGlobal_t, restart_flag), 0, 0, ctypes_u8, 0 },
{ "sleep", offsetof(TasmotaGlobal_t, sleep), 0, 0, ctypes_u8, 0 }, { "sleep", offsetof(TasmotaGlobal_t, sleep), 0, 0, ctypes_u8, 0 },
}}; }};
extern const be_ctypes_structure_t be_tasmota_settings_struct = { extern const be_ctypes_structure_t be_tasmota_settings_struct = {
sizeof(TSettings), /* size in bytes */ sizeof(TSettings), /* size in bytes */
1, /* number of elements */ 2, /* number of elements */
nullptr, nullptr,
(const be_ctypes_structure_item_t[1]) { (const be_ctypes_structure_item_t[2]) {
{ "bootcount", offsetof(TSettings, bootcount), 0, 0, ctypes_u16, 0 },
{ "sleep", offsetof(TSettings, sleep), 0, 0, ctypes_u8, 0 }, { "sleep", offsetof(TSettings, sleep), 0, 0, ctypes_u8, 0 },
}}; }};

View File

@ -43,6 +43,14 @@ const char berry_prog[] =
"def log(m,l) tasmota.log(m,l) end " "def log(m,l) tasmota.log(m,l) end "
"def load(f) return tasmota.load(f) end " "def load(f) return tasmota.load(f) end "
// try to resize FS to max at first boot
// "tasmota.log('>>> bootcount=' + str(tasmota.settings.bootcount), 2) "
"if tasmota.settings.bootcount == 0 "
"import partition_core "
"var p = partition_core.Partition() "
"p.resize_fs_to_max() "
"end "
#ifdef USE_AUTOCONF #ifdef USE_AUTOCONF
// autoconf // autoconf
"import autoconf " "import autoconf "

View File

@ -146,7 +146,10 @@
#define SSPM_FUNC_RESET 28 // 0x1C - Remove device from eWelink and factory reset #define SSPM_FUNC_RESET 28 // 0x1C - Remove device from eWelink and factory reset
#define SSPM_FUNC_UPLOAD_DATA 31 // 0x1F - SPI Upload incremental data blocks of max 512 bytes to ARM #define SSPM_FUNC_UPLOAD_DATA 31 // 0x1F - SPI Upload incremental data blocks of max 512 bytes to ARM
#define SSPM_FUNC_UPLOAD_DONE 33 // 0x21 - SPI Finish upload #define SSPM_FUNC_UPLOAD_DONE 33 // 0x21 - SPI Finish upload
#define SSPM_FUNC_GET_NEW1 37 // 0x25 #define SSPM_FUNC_34 34 // 0x22 - v1.2.0
#define SSPM_FUNC_GET_OPS_DEFAULTS 35 // 0x23 - v1.2.0 - Get Overload protection defaults
#define SSPM_FUNC_SET_POS 36 // 0x24 - v1.2.0 - Save power on relay state
#define SSPM_FUNC_GET_POS 37 // 0x25 - v1.2.0 - Read power on relay state
// From ARM to ESP // From ARM to ESP
#define SSPM_FUNC_ENERGY_RESULT 6 // 0x06 #define SSPM_FUNC_ENERGY_RESULT 6 // 0x06
@ -166,17 +169,14 @@
#define SSPM_FUNC_23 23 // 0x17 #define SSPM_FUNC_23 23 // 0x17
#define SSPM_FUNC_29 29 // 0x1D #define SSPM_FUNC_29 29 // 0x1D
#define SSPM_FUNC_32 32 // 0x20 #define SSPM_FUNC_32 32 // 0x20
#define SSPM_FUNC_34 34 // 0x22
#define SSPM_FUNC_35 35 // 0x23
#define SSPM_FUNC_36 36 // 0x24
#define SSPM_GPIO_ARM_RESET 15 #define SSPM_GPIO_ARM_RESET 15
#define SSPM_GPIO_LED_ERROR 33 #define SSPM_GPIO_LED_ERROR 33
#define SSPM_MODULE_NAME_SIZE 12 #define SSPM_MODULE_NAME_SIZE 12
#define SSPM_MAIN_V1_0_0 0x00010000 #define SSPM_VERSION_1_0_0 0x00010000
#define SSPM_MAIN_V1_2_0 0x00010200 #define SSPM_VERSION_1_2_0 0x00010200
/*********************************************************************************************/ /*********************************************************************************************/
@ -264,6 +264,7 @@ typedef struct {
uint32_t timeout; uint32_t timeout;
uint32_t main_version; uint32_t main_version;
uint32_t relay_version;
power_t old_power; power_t old_power;
power_t power_on_state; power_t power_on_state;
uint16_t last_totals; uint16_t last_totals;
@ -271,11 +272,12 @@ typedef struct {
uint16_t expected_bytes; uint16_t expected_bytes;
uint8_t module[SSPM_MAX_MODULES][SSPM_MODULE_NAME_SIZE]; uint8_t module[SSPM_MAX_MODULES][SSPM_MODULE_NAME_SIZE];
uint8_t history_day[SSPM_MAX_MODULES][4]; uint8_t history_day[SSPM_MAX_MODULES][4];
uint8_t poweron_state[SSPM_MAX_MODULES][4];
#ifdef SSPM_SIMULATE #ifdef SSPM_SIMULATE
uint8_t simulate; uint8_t simulate;
uint8_t simulated_module; uint8_t simulated_module;
#endif #endif // SSPM_SIMULATE
uint8_t allow_updates; uint8_t allow_updates;
uint8_t get_energy_relay; uint8_t get_energy_relay;
int8_t get_energy_relay_focus; int8_t get_energy_relay_focus;
@ -406,7 +408,7 @@ uint32_t SSPMGetMappedModuleId(uint32_t module) {
module_nr = 0; // Emulate modules by 0 module_nr = 0; // Emulate modules by 0
} }
} }
#endif #endif // SSPM_SIMULATE
return (uint32_t)module_nr; // 0, 1, ... return (uint32_t)module_nr; // 0, 1, ...
} }
@ -912,7 +914,68 @@ void SSPMSendGetEnergyPeriod(uint32_t relay) {
} }
void SSPMSendGetNew1(uint32_t module) { void SSPMSendFunc34(uint32_t module) {
/*
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
aa 55 01 8b 34 32 37 39 37 34 13 4b 35 36 37 00 22 00 00 f2 6a 7f
Marker |Module id |Ac|Cm|Size |Ix|Chksm|
*/
if (module >= Sspm->module_max) { return; }
SSPMInitSend();
memcpy(SspmBuffer +3, Sspm->module[SSPMGetMappedModuleId(module)], SSPM_MODULE_NAME_SIZE);
SspmBuffer[16] = SSPM_FUNC_34; // 0x22
Sspm->command_sequence++;
SspmBuffer[19] = Sspm->command_sequence;
SSPMSend(22);
}
void SSPMSendGetOPSDefaults(uint32_t module) {
/*
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
aa 55 01 8b 34 32 37 39 37 34 13 4b 35 36 37 00 23 00 00 f4 94 fe
Marker |Module id |Ac|Cm|Size |Ix|Chksm|
*/
if (module >= Sspm->module_max) { return; }
SSPMInitSend();
memcpy(SspmBuffer +3, Sspm->module[SSPMGetMappedModuleId(module)], SSPM_MODULE_NAME_SIZE);
SspmBuffer[16] = SSPM_FUNC_GET_OPS_DEFAULTS; // 0x23
Sspm->command_sequence++;
SspmBuffer[19] = Sspm->command_sequence;
SSPMSend(22);
}
void SSPMSendSetPowerOnState(uint32_t module) {
/*
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
aa 55 01 8b 34 32 37 39 37 34 13 4b 35 36 37 00 24 00 05 0f 00 01 02 01 00 f3 c2
Marker |Module id |Ac|Cm|Size |??|P1|P2|P3|P4|Ix|Chksm|
P1 - Relay1 power on state (0 = On, 1 = Off, 2 = Laststate)
P2 - Relay2 power on state (0 = On, 1 = Off, 2 = Laststate)
P3 - Relay3 power on state (0 = On, 1 = Off, 2 = Laststate)
P4 - Relay4 power on state (0 = On, 1 = Off, 2 = Laststate)
*/
if (module >= Sspm->module_max) { return; }
SSPMInitSend();
memcpy(SspmBuffer +3, Sspm->module[SSPMGetMappedModuleId(module)], SSPM_MODULE_NAME_SIZE);
SspmBuffer[16] = SSPM_FUNC_SET_POS; // 0x24
SspmBuffer[18] = 0x05;
SspmBuffer[19] = 0x0F;
SspmBuffer[20] = Sspm->poweron_state[module][0];
SspmBuffer[21] = Sspm->poweron_state[module][1];
SspmBuffer[22] = Sspm->poweron_state[module][2];
SspmBuffer[23] = Sspm->poweron_state[module][3];
Sspm->command_sequence++;
SspmBuffer[24] = Sspm->command_sequence;
SSPMSend(27);
}
void SSPMSendGetPowerOnState(uint32_t module) {
/* /*
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
aa 55 01 6b 7e 32 37 39 37 34 13 4b 35 36 37 00 25 00 00 08 c0 0a aa 55 01 6b 7e 32 37 39 37 34 13 4b 35 36 37 00 25 00 00 08 c0 0a
@ -922,7 +985,7 @@ void SSPMSendGetNew1(uint32_t module) {
SSPMInitSend(); SSPMInitSend();
memcpy(SspmBuffer +3, Sspm->module[SSPMGetMappedModuleId(module)], SSPM_MODULE_NAME_SIZE); memcpy(SspmBuffer +3, Sspm->module[SSPMGetMappedModuleId(module)], SSPM_MODULE_NAME_SIZE);
SspmBuffer[16] = SSPM_FUNC_GET_NEW1; // 0x25 SspmBuffer[16] = SSPM_FUNC_GET_POS; // 0x25
Sspm->command_sequence++; Sspm->command_sequence++;
SspmBuffer[19] = Sspm->command_sequence; SspmBuffer[19] = Sspm->command_sequence;
@ -955,6 +1018,11 @@ void SSPMAddModule(void) {
} }
Sspm->map_change = true; Sspm->map_change = true;
} }
uint32_t relay_version = SspmBuffer[36] << 16 | SspmBuffer[37] << 8 | SspmBuffer[38]; // 0x00010000 or 0x00010200
if (relay_version < Sspm->relay_version) {
Sspm->relay_version = relay_version; // Lowest version will be supported
}
mapped++; mapped++;
AddLog(LOG_LEVEL_INFO, PSTR("SPM: 4Relay %d (mapped to %d) type %d version %d.%d.%d found with id %12_H"), AddLog(LOG_LEVEL_INFO, PSTR("SPM: 4Relay %d (mapped to %d) type %d version %d.%d.%d found with id %12_H"),
Sspm->module_max +1, mapped, SspmBuffer[35], SspmBuffer[36], SspmBuffer[37], SspmBuffer[38], Sspm->module[Sspm->module_max]); Sspm->module_max +1, mapped, SspmBuffer[35], SspmBuffer[36], SspmBuffer[37], SspmBuffer[38], Sspm->module[Sspm->module_max]);
@ -969,6 +1037,16 @@ void SSPMAddModule(void) {
/*********************************************************************************************/ /*********************************************************************************************/
void SSPMLogResult(uint32_t command, uint32_t status) {
if (1 == status) {
AddLog(LOG_LEVEL_DEBUG, PSTR("SPM: Command %d not supported"), command);
} else if (2 == status) {
AddLog(LOG_LEVEL_DEBUG, PSTR("SPM: Command %d timeout"), command);
} else {
AddLog(LOG_LEVEL_DEBUG, PSTR("SPM: Command %d result %d"), command, status);
}
}
void SSPMHandleReceivedData(void) { void SSPMHandleReceivedData(void) {
/* /*
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
@ -979,7 +1057,7 @@ void SSPMHandleReceivedData(void) {
uint32_t command = SspmBuffer[16]; // Cm uint32_t command = SspmBuffer[16]; // Cm
uint32_t expected_bytes = (SspmBuffer[17] << 8) + SspmBuffer[18]; // Size uint32_t expected_bytes = (SspmBuffer[17] << 8) + SspmBuffer[18]; // Size
// 0 - OK // 0 - OK
// 1 - // 1 - Not supported
// 2 - Timeout // 2 - Timeout
// 3 - Log empty // 3 - Log empty
// 4 - // 4 -
@ -993,7 +1071,7 @@ void SSPMHandleReceivedData(void) {
if (ack) { if (ack) {
// Responses from ARM (Acked) // Responses from ARM (Acked)
if (status > 0) { if (status > 0) {
AddLog(LOG_LEVEL_DEBUG, PSTR("SPM: Command %d result %d"), command, status); SSPMLogResult(command, status);
} }
switch(command) { switch(command) {
case SSPM_FUNC_FIND: case SSPM_FUNC_FIND:
@ -1086,8 +1164,8 @@ void SSPMHandleReceivedData(void) {
MqttPublishPrefixTopicRulesProcess_P(RESULT_OR_STAT, PSTR("SSPMOverload")); MqttPublishPrefixTopicRulesProcess_P(RESULT_OR_STAT, PSTR("SSPMOverload"));
Sspm->overload_relay = 255; Sspm->overload_relay = 255;
} else { } else {
if (Sspm->main_version > SSPM_MAIN_V1_0_0) { if (Sspm->main_version > SSPM_VERSION_1_0_0) {
SSPMSendGetNew1(Sspm->module_selected -1); SSPMSendGetPowerOnState(Sspm->module_selected -1);
} else { } else {
Sspm->module_selected--; Sspm->module_selected--;
if (Sspm->module_selected > 0) { if (Sspm->module_selected > 0) {
@ -1109,7 +1187,7 @@ void SSPMHandleReceivedData(void) {
uint32_t module = SSPMGetModuleNumberFromMap(SspmBuffer[3] << 8 | SspmBuffer[4]); uint32_t module = SSPMGetModuleNumberFromMap(SspmBuffer[3] << 8 | SspmBuffer[4]);
#ifdef SSPM_SIMULATE #ifdef SSPM_SIMULATE
if (Sspm->Settings.simulate_count) { module = Sspm->simulated_module; } if (Sspm->Settings.simulate_count) { module = Sspm->simulated_module; }
#endif #endif // SSPM_SIMULATE
power_t current_state = (SspmBuffer[20] >> 4) << (module * 4); // Relays state power_t current_state = (SspmBuffer[20] >> 4) << (module * 4); // Relays state
power_t mask = 0x0000000F << (module * 4); power_t mask = 0x0000000F << (module * 4);
TasmotaGlobal.power &= (POWER_MASK ^ mask); TasmotaGlobal.power &= (POWER_MASK ^ mask);
@ -1206,7 +1284,7 @@ void SSPMHandleReceivedData(void) {
uint32_t module = SSPMGetModuleNumberFromMap(SspmBuffer[20] << 8 | SspmBuffer[21]); uint32_t module = SSPMGetModuleNumberFromMap(SspmBuffer[20] << 8 | SspmBuffer[21]);
#ifdef SSPM_SIMULATE #ifdef SSPM_SIMULATE
if (Sspm->Settings.simulate_count) { module = Sspm->simulated_module; } if (Sspm->Settings.simulate_count) { module = Sspm->simulated_module; }
#endif #endif // SSPM_SIMULATE
if (Sspm->history_relay < 255) { if (Sspm->history_relay < 255) {
uint32_t history_module = Sspm->history_relay >> 2; uint32_t history_module = Sspm->history_relay >> 2;
uint32_t history_channel = Sspm->history_relay & 0x03; // Channel relays are NOT bit masked this time uint32_t history_channel = Sspm->history_relay & 0x03; // Channel relays are NOT bit masked this time
@ -1359,16 +1437,51 @@ void SSPMHandleReceivedData(void) {
case SSPM_FUNC_RESET: case SSPM_FUNC_RESET:
/* 0x1C /* 0x1C
AA 55 01 00 00 00 00 00 00 00 00 00 00 00 00 80 1c 00 01 00 0b f9 e3 AA 55 01 00 00 00 00 00 00 00 00 00 00 00 00 80 1c 00 01 00 0b f9 e3
Marker |Module id |Ac|Cm|Size |St|Ix|Chksm|
*/ */
// TasmotaGlobal.restart_flag = 2; // TasmotaGlobal.restart_flag = 2;
break; break;
case SSPM_FUNC_GET_NEW1: case SSPM_FUNC_34:
/* 0x25 v1.2.0 /* 0x22 v1.2.0
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 21 22 23 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
AA 55 01 8b 34 32 37 39 37 34 13 4b 35 36 37 80 25 00 01 01 06 98 06 aa 55 01 8b 34 32 37 39 37 34 13 4b 35 36 37 80 22 00 02 00 00 f2 19 00
Marker |Module id |Ac|Cm|Size |St| |Ix|Chksm|
*/
break;
case SSPM_FUNC_GET_OPS_DEFAULTS:
/* 0x23 v1.2.0
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
aa 55 01 8b 34 32 37 39 37 34 13 4b 35 36 37 80 23 00 11 00 14 00 00 0a 01 08 00 00 5a 00 12 c0 00 00 00 0a f4 7f 4d
Marker |Module id |Ac|Cm|Size |St|Max I|Min I|Max U |Min U |Max P |Min P |Ix|Chksm|
|OK|20.0A|0.10A| 240.00V| 0.10V|4400.00W| 0.10W|
*/
break;
case SSPM_FUNC_SET_POS:
/* 0x24 v1.2.0
aa 55 01 8b 34 32 37 39 37 34 13 4b 35 36 37 80 24 00 01 00 00 80 a8
Marker |Module id |Ac|Cm|Size |St|Ix|Chksm| Marker |Module id |Ac|Cm|Size |St|Ix|Chksm|
*/ */
break;
case SSPM_FUNC_GET_POS:
/* 0x25 v1.2.0 - Get Power On State
Response v1.0.0
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
AA 55 01 8b 34 32 37 39 37 34 13 4b 35 36 37 80 25 00 01 01 06 98 06
Marker |Module id |Ac|Cm|Size |St|Ix|Chksm|
Response v1.2.0
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
aa 55 01 8b 34 32 37 39 37 34 13 4b 35 36 37 80 25 00 05 00 00 01 02 01 07 b6 89
Marker |Module id |Ac|Cm|Size |St|P1|P2|P3|P4|Ix|Chksm|
P1 - Relay1 power on state (0 = On, 1 = Off, 2 = Laststate)
P2 - Relay2 power on state (0 = On, 1 = Off, 2 = Laststate)
P3 - Relay3 power on state (0 = On, 1 = Off, 2 = Laststate)
P4 - Relay4 power on state (0 = On, 1 = Off, 2 = Laststate)
*/
Sspm->module_selected--; Sspm->module_selected--;
for (uint32_t i = 0; i < 4; i++) {
Sspm->poweron_state[Sspm->module_selected][i] = (!status && (expected_bytes >= 0x05)) ? SspmBuffer[20 +i] : 1;
}
if (Sspm->module_selected > 0) { if (Sspm->module_selected > 0) {
SSPMSendGetModuleState(Sspm->module_selected -1); SSPMSendGetModuleState(Sspm->module_selected -1);
} else { } else {
@ -1402,7 +1515,7 @@ void SSPMHandleReceivedData(void) {
uint32_t module = SSPMGetModuleNumberFromMap(SspmBuffer[19] << 8 | SspmBuffer[20]); uint32_t module = SSPMGetModuleNumberFromMap(SspmBuffer[19] << 8 | SspmBuffer[20]);
#ifdef SSPM_SIMULATE #ifdef SSPM_SIMULATE
if (Sspm->Settings.simulate_count) { module = Sspm->simulated_module; } if (Sspm->Settings.simulate_count) { module = Sspm->simulated_module; }
#endif #endif // SSPM_SIMULATE
Sspm->current[module][channel] = SspmBuffer[32] + (float)SspmBuffer[33] / 100; // x.xxA Sspm->current[module][channel] = SspmBuffer[32] + (float)SspmBuffer[33] / 100; // x.xxA
Sspm->voltage[module][channel] = SSPMGetValue(&SspmBuffer[34]); // x.xxV Sspm->voltage[module][channel] = SSPMGetValue(&SspmBuffer[34]); // x.xxV
Sspm->active_power[module][channel] = SSPMGetValue(&SspmBuffer[37]); // x.xxW Sspm->active_power[module][channel] = SSPMGetValue(&SspmBuffer[37]); // x.xxW
@ -1428,7 +1541,7 @@ void SSPMHandleReceivedData(void) {
uint32_t module = SSPMGetModuleNumberFromMap(SspmBuffer[19] << 8 | SspmBuffer[20]); uint32_t module = SSPMGetModuleNumberFromMap(SspmBuffer[19] << 8 | SspmBuffer[20]);
#ifdef SSPM_SIMULATE #ifdef SSPM_SIMULATE
// if (Sspm->Settings.simulate_count) { module = Sspm->simulated_module; } // Won't work as this is initiated from device // if (Sspm->Settings.simulate_count) { module = Sspm->simulated_module; } // Won't work as this is initiated from device
#endif #endif // SSPM_SIMULATE
power_t relay = (SspmBuffer[31] & 0x0F) << (module * 4); // Relays active power_t relay = (SspmBuffer[31] & 0x0F) << (module * 4); // Relays active
power_t relay_state = (SspmBuffer[31] >> 4) << (module * 4); // Relays state power_t relay_state = (SspmBuffer[31] >> 4) << (module * 4); // Relays state
for (uint32_t i = 1; i <= TasmotaGlobal.devices_present; i++) { for (uint32_t i = 1; i <= TasmotaGlobal.devices_present; i++) {
@ -1470,7 +1583,7 @@ void SSPMHandleReceivedData(void) {
Ot - Overtemp Ot - Overtemp
*/ */
if (status > 0) { if (status > 0) {
AddLog(LOG_LEVEL_DEBUG, PSTR("SPM: Command %d result %d"), command, status); SSPMLogResult(command, status);
} }
else if (0x14 == expected_bytes) { // Overload/Overtemp triggered else if (0x14 == expected_bytes) { // Overload/Overtemp triggered
uint32_t any_bit_set = 0; uint32_t any_bit_set = 0;
@ -1524,7 +1637,7 @@ void SSPMHandleReceivedData(void) {
SspmBuffer[19] = current_idh; SspmBuffer[19] = current_idh;
SspmBuffer[20] = current_idl; SspmBuffer[20] = current_idl;
} }
#endif #endif // SSPM_SIMULATE
} }
SSPMSendAck(command_sequence); SSPMSendAck(command_sequence);
break; break;
@ -1818,6 +1931,7 @@ void SSPMInit(void) {
#endif #endif
#endif #endif
Sspm->relay_version = 0xFFFFFFFF; // Find lowest supported relay version
Sspm->overload_relay = 255; // Disable display overload settings Sspm->overload_relay = 255; // Disable display overload settings
Sspm->history_relay = 255; // Disable display energy history Sspm->history_relay = 255; // Disable display energy history
Sspm->log_relay = 255; // Disable display logging Sspm->log_relay = 255; // Disable display logging
@ -1916,15 +2030,18 @@ void SSPMEvery100ms(void) {
// Scan sequence finished // Scan sequence finished
#ifdef SSPM_SIMULATE #ifdef SSPM_SIMULATE
if (!Sspm->Settings.simulate_count) { if (!Sspm->Settings.simulate_count) {
#endif #endif // SSPM_SIMULATE
if (Sspm->relay_version < SSPM_VERSION_1_2_0) {
// Set relay power on state based on Tasmota global setting
if (Sspm->power_on_state) { if (Sspm->power_on_state) {
TasmotaGlobal.power = Sspm->power_on_state; TasmotaGlobal.power = Sspm->power_on_state;
Sspm->power_on_state = 0; // Reset power on state solving re-scan Sspm->power_on_state = 0; // Reset power on state solving re-scan
SetPowerOnState(); // Set power on state now that all relays have been detected SetPowerOnState(); // Set power on state now that all relays have been detected
} }
}
#ifdef SSPM_SIMULATE #ifdef SSPM_SIMULATE
} }
#endif #endif // SSPM_SIMULATE
TasmotaGlobal.discovery_counter = 1; // Force TasDiscovery() TasmotaGlobal.discovery_counter = 1; // Force TasDiscovery()
Sspm->allow_updates = 1; // Enable requests from 100mSec loop Sspm->allow_updates = 1; // Enable requests from 100mSec loop
Sspm->get_energy_relay = 0; Sspm->get_energy_relay = 0;
@ -2201,19 +2318,19 @@ const char kSSPMCommands[] PROGMEM = "SSPM|" // Prefix
"Display|Dump|" // SetOptions "Display|Dump|" // SetOptions
#ifdef SSPM_SIMULATE #ifdef SSPM_SIMULATE
"Simulate|" "Simulate|"
#endif #endif // SSPM_SIMULATE
"Log|Energy|History|Scan|IamHere|" "Log|Energy|History|Scan|IamHere|"
"Reset|Map|Overload|" "Reset|Map|Overload|"
D_CMND_ENERGYTOTAL "|" D_CMND_ENERGYYESTERDAY "|Send"; D_CMND_ENERGYTOTAL "|" D_CMND_ENERGYYESTERDAY "|Send|" D_CMND_POWERONSTATE;
void (* const SSPMCommand[])(void) PROGMEM = { void (* const SSPMCommand[])(void) PROGMEM = {
&CmndSSPMDisplay, &CmndSSPMDump, &CmndSSPMDisplay, &CmndSSPMDump,
#ifdef SSPM_SIMULATE #ifdef SSPM_SIMULATE
&CmndSSPMSimulate, &CmndSSPMSimulate,
#endif #endif // SSPM_SIMULATE
&CmndSSPMLog, &CmndSSPMEnergy, &CmndSSPMHistory, &CmndSSPMScan, &CmndSSPMIamHere, &CmndSSPMLog, &CmndSSPMEnergy, &CmndSSPMHistory, &CmndSSPMScan, &CmndSSPMIamHere,
&CmndSSPMReset, &CmndSSPMMap, &CmndSSPMOverload, &CmndSSPMReset, &CmndSSPMMap, &CmndSSPMOverload,
&CmndSpmEnergyTotal, &CmndSpmEnergyYesterday, &CmndSSPMSend }; &CmndSpmEnergyTotal, &CmndSpmEnergyYesterday, &CmndSSPMSend, &CmndSSPMPowerOnState };
void CmndSSPMDisplay(void) { void CmndSSPMDisplay(void) {
// Select either all relays, only powered on relays or user selected relay module // Select either all relays, only powered on relays or user selected relay module
@ -2244,7 +2361,7 @@ void CmndSSPMSimulate(void) {
} }
ResponseCmndNumber(Sspm->Settings.simulate_count); ResponseCmndNumber(Sspm->Settings.simulate_count);
} }
#endif #endif // SSPM_SIMULATE
void CmndSpmEnergyTotal(void) { void CmndSpmEnergyTotal(void) {
// Reset Energy Total // Reset Energy Total
@ -2471,6 +2588,31 @@ void CmndSSPMSend(void) {
} }
} }
void CmndSSPMPowerOnState(void) {
// SspmPowerOnState2 0|1|2 - Set relay2 power on state (0 = Off, 1 = On, 2 = Saved)
uint32_t max_index = Sspm->module_max *4;
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= max_index)) {
uint32_t module = (XdrvMailbox.index -1) >>2;
uint32_t relay = (XdrvMailbox.index -1) &3;
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 2)) {
if (XdrvMailbox.payload < 2) { XdrvMailbox.payload = !XdrvMailbox.payload; } // Swap Tasmota power off (0) with Sonoff (1)
Sspm->poweron_state[module][relay] = XdrvMailbox.payload;
SSPMSendSetPowerOnState(module);
}
Response_P(PSTR("{\"%s\":["), XdrvMailbox.command);
bool more = false;
for (uint32_t module = 0; module < Sspm->module_max; module++) {
for (uint32_t relay = 0; relay < 4; relay++) {
uint32_t poweron_state = Sspm->poweron_state[module][relay];
if (poweron_state < 2) { poweron_state = !poweron_state; } // Swap Sonoff power off (1) with Tasmota (0)
ResponseAppend_P(PSTR("%s%d"), (more)?",":"", poweron_state);
more = true;
}
}
ResponseAppend_P(PSTR("]}"));
}
}
/*********************************************************************************************\ /*********************************************************************************************\
* Interface * Interface
\*********************************************************************************************/ \*********************************************************************************************/

View File

@ -29,7 +29,7 @@
#define XLGT_08 8 #define XLGT_08 8
// Layout: Bits B[7:8]=10 (address selection identification bits), B[5:6] sleep mode if set to 00, B[0:4] Address selection // Layout: Bits B[7:8]=10 (address selection identification bits), B[5:6] sleep mode if set to 00, B[0:4] Address selection
#define BP5758D_ADDR_SLEEP 0x86 //10 00 0110: Sleep mode bits set (OUT1 gray-scale level setup selected, ignored by chip) #define BP5758D_ADDR_SLEEP 0x80 //10 00 xxxx: Set to sleep mode
#define BP5758D_ADDR_SETUP 0x90 //10 01 0000: OUT1-5 enable/disable setup - used during init #define BP5758D_ADDR_SETUP 0x90 //10 01 0000: OUT1-5 enable/disable setup - used during init
#define BP5758D_ADDR_OUT1_CR 0x91 //10 01 0001: OUT1 current range #define BP5758D_ADDR_OUT1_CR 0x91 //10 01 0001: OUT1 current range
#define BP5758D_ADDR_OUT2_CR 0x92 //10 01 0010: OUT2 current range #define BP5758D_ADDR_OUT2_CR 0x92 //10 01 0010: OUT2 current range
@ -44,6 +44,7 @@
// Output enabled (OUT1-5, represented by lower 5 bits) // Output enabled (OUT1-5, represented by lower 5 bits)
#define BP5758D_ENABLE_OUTPUTS_ALL 0x1F #define BP5758D_ENABLE_OUTPUTS_ALL 0x1F
#define BP5758D_DISABLE_OUTPUTS_ALL 0x00
// Current values: Bit 6 to 0 represent 30mA, 32mA, 16mA, 8mA, 4mA, 2mA, 1mA respectively // Current values: Bit 6 to 0 represent 30mA, 32mA, 16mA, 8mA, 4mA, 2mA, 1mA respectively
#define BP5758D_10MA 0x0A // 0 0001010 #define BP5758D_10MA 0x0A // 0 0001010
@ -106,15 +107,26 @@ void Bp5758dStop(void) {
/********************************************************************************************/ /********************************************************************************************/
bool Bp5758dSetChannels(void) { bool Bp5758dSetChannels(void) {
static bool bIsSleeping = false; //Save sleep state of Lamp
uint16_t *cur_col_10 = (uint16_t*)XdrvMailbox.command; uint16_t *cur_col_10 = (uint16_t*)XdrvMailbox.command;
// If we receive 0 for all channels, we'll assume that the lightbulb is off, and activate BP5758d's sleep mode. // If we receive 0 for all channels, we'll assume that the lightbulb is off, and activate BP5758d's sleep mode.
if (cur_col_10[0]==0 && cur_col_10[1]==0 && cur_col_10[2]==0 && cur_col_10[3]==0 && cur_col_10[4]==0) { if (cur_col_10[0]==0 && cur_col_10[1]==0 && cur_col_10[2]==0 && cur_col_10[3]==0 && cur_col_10[4]==0) {
Bp5758dStart(BP5758D_ADDR_SETUP);
Bp5758dWrite(BP5758D_DISABLE_OUTPUTS_ALL);
Bp5758dStart(BP5758D_ADDR_SLEEP); Bp5758dStart(BP5758D_ADDR_SLEEP);
Bp5758dStop(); Bp5758dStop();
bIsSleeping = true;
return true; return true;
} }
if (bIsSleeping) {
bIsSleeping = false; //No need to run it every time a val gets changed
Bp5758dStart(BP5758D_ADDR_SETUP); //Sleep mode gets disabled too since bits 5:6 get set to 01
Bp5758dWrite(BP5758D_ENABLE_OUTPUTS_ALL); //Set all outputs to ON
Bp5758dStop();
}
// Even though we could address changing channels only, in practice we observed that the lightbulb always sets all channels. // Even though we could address changing channels only, in practice we observed that the lightbulb always sets all channels.
Bp5758dStart(BP5758D_ADDR_OUT1_GL); Bp5758dStart(BP5758D_ADDR_OUT1_GL);
// Brigtness values are transmitted as two bytes. The light-bulb accepts a 10-bit integer (0-1023) as an input value. // Brigtness values are transmitted as two bytes. The light-bulb accepts a 10-bit integer (0-1023) as an input value.

View File

@ -31,7 +31,22 @@
#define XSNS_13 13 #define XSNS_13 13
#define XI2C_14 14 // See I2CDEVICES.md #define XI2C_14 14 // See I2CDEVICES.md
#ifndef INA219_MAX_COUNT
#define INA219_MAX_COUNT 4 #define INA219_MAX_COUNT 4
#endif
#if (INA219_MAX_COUNT > 4)
#error "**** INA219_MAX_COUNT can't be greater than 4 ****"
#endif
#ifndef INA219_FIRST_ADDRESS
#define INA219_FIRST_ADDRESS (0)
#endif
#if ((INA219_FIRST_ADDRESS + INA219_MAX_COUNT) > 4)
#error "**** INA219 bad combination for FIRST_ADDRESS and MAX_COUNT ****"
#endif
#ifndef INA219_SHUNT_RESISTOR
#define INA219_SHUNT_RESISTOR (0.100) // 0.1 Ohm default on most INA219 modules
#endif
#define INA219_ADDRESS1 (0x40) // 1000000 (A0+A1=GND) #define INA219_ADDRESS1 (0x40) // 1000000 (A0+A1=GND)
#define INA219_ADDRESS2 (0x41) // 1000000 (A0=Vcc, A1=GND) #define INA219_ADDRESS2 (0x41) // 1000000 (A0=Vcc, A1=GND)
@ -101,34 +116,36 @@
#define ISL28022_REG_INTRSTATUS (0x08) #define ISL28022_REG_INTRSTATUS (0x08)
#define ISL28022_REG_AUXCTRL (0x09) #define ISL28022_REG_AUXCTRL (0x09)
#define INA219_DEFAULT_SHUNT_RESISTOR_MILLIOHMS (100.0) // 0.1 Ohm #define INA219_BUS_ADC_LSB (0.004) // VBus ADC LSB=4mV=0.004V
#define INA219_SHUNT_ADC_LSB_MV (0.01) // VShunt ADC LSB=10µV=0.01mV
#ifdef DEBUG_TASMOTA_SENSOR #ifdef DEBUG_TASMOTA_SENSOR
// temporary strings for floating point in debug messages // temporary strings for floating point in debug messages
char __ina219_dbg1[10]; char __ina219_dbg1[FLOATSZ];
char __ina219_dbg2[10]; char __ina219_dbg2[FLOATSZ];
#endif #endif
#define INA219_ACTIVE 1 #define INA219_MODEL 1
#define ISL28022_ACTIVE 2 #define ISL28022_MODEL 2
struct INA219_Channel_Data {
float voltage;
float current;
uint8_t active;
uint8_t valid;
};
struct INA219_Data { struct INA219_Data {
struct INA219_Channel_Data chan[INA219_MAX_COUNT]; float voltage;
float current;
// The following multiplier is used to convert shunt voltage (in mV) to current (in A) // The following multiplier is used to convert shunt voltage (in mV) to current (in A)
// Current_A = ShuntVoltage_mV / ShuntResistor_milliOhms = ShuntVoltage_mV * ina219_current_multiplier // Current_A = ShuntVoltage_mV / ShuntResistor_milliOhms = ShuntVoltage_mV * ina219_current_multiplier
// ina219_current_multiplier = 1 / ShuntResistor_milliOhms // ina219_current_multiplier = 1 / ShuntResistor_milliOhms
float current_multiplier; float current_multiplier;
uint8_t count; uint8_t model;
uint8_t addr;
}; };
struct INA219_Data *Ina219Data = nullptr; struct INA219_Data *Ina219Data = nullptr;
uint8_t Ina219Count = 0;
const char INA219_SENSORCMND_START[] PROGMEM = "{\"" D_CMND_SENSOR "%d\":{\"mode\":%d,\"rshunt\":[";
const char INA219_SENSORCMND_END[] PROGMEM = "]}}";
const char *INA219_TYPE[] = { "INA219", "ISL28022" }; const char *INA219_TYPE[] = { "INA219", "ISL28022" };
const uint8_t INA219_ADDRESSES[] = { INA219_ADDRESS1, INA219_ADDRESS2, INA219_ADDRESS3, INA219_ADDRESS4 }; const uint8_t INA219_ADDRESSES[] = { INA219_ADDRESS1, INA219_ADDRESS2, INA219_ADDRESS3, INA219_ADDRESS4 };
@ -148,58 +165,60 @@ const uint8_t INA219_ADDRESSES[] = { INA219_ADDRESS1, INA219_ADDRESS2, INA219_AD
* Note that some shunt values can be represented by 2 different encoded values such as * Note that some shunt values can be represented by 2 different encoded values such as
* 11 or 100 both present 10 milliOhms * 11 or 100 both present 10 milliOhms
* Because it is difficult to make a range check on such encoded value, none is performed * Because it is difficult to make a range check on such encoded value, none is performed
* \*********************************************************************************************/
void Ina219SetShuntMode(uint8_t index, uint8_t mode, float shunt)
{
if (mode < 10) {
// All legacy modes: shunt is INA219_SHUNT_RESISTOR unless provided by `Sensor13 <n> <shunt>`
// Shunt value provided this way is NOT stored in flash and requires an "on system#boot" rule
} else {
// Modes >= 10 allow to provide shunt values that is stored in flash but limited in possible
// values due to the encoding mode used to store the value in a single uint8_t
int mult = mode % 10;
int shunt_milliOhms = mode / 10;
shunt = shunt_milliOhms / 1000.0;
for ( ; mult > 0 ; mult-- )
shunt *= 10.0;
}
Ina219Data[index].current_multiplier = 0.001 / shunt;
#ifdef DEBUG_TASMOTA_SENSOR
dtostrfd(shunt,6,__ina219_dbg1);
dtostrfd(Ina219Data[index].current_multiplier,5,__ina219_dbg2);
DEBUG_SENSOR_LOG("Ina219SetShuntMode[%d]: mode=%d, shunt=%s, cur_mul=%s", index, mode, __ina219_dbg1, __ina219_dbg2);
#endif
}
float Ina219GetShunt(uint8_t index)
{
return 0.001 / Ina219Data[index].current_multiplier;
}
/*********************************************************************************************\
* Return 0 if configuration failed * Return 0 if configuration failed
* Return 1 if chip identified as INA219 * Return 1 if chip identified as INA219
* Return 2 if chip identified as ISL28022 * Return 2 if chip identified as ISL28022
\*********************************************************************************************/ \*********************************************************************************************/
uint8_t Ina219SetCalibration(uint8_t mode, uint16_t addr) uint8_t Ina219Init(uint16_t addr)
{ {
uint16_t config = 0; uint16_t config = 0;
DEBUG_SENSOR_LOG("Ina219SetCalibration: mode=%d",mode);
if (mode < 5)
{
// All legacy modes 0..2 are handled the same and consider default 0.1 shunt resistor
Ina219Data->current_multiplier = 1.0 / INA219_DEFAULT_SHUNT_RESISTOR_MILLIOHMS;
#ifdef DEBUG_TASMOTA_SENSOR
dtostrfd(Ina219Data->current_multiplier,5,__ina219_dbg1);
DEBUG_SENSOR_LOG("Ina219SetCalibration: cur_mul=%s",__ina219_dbg1);
#endif
}
else if (mode >= 10)
{
int mult = mode % 10;
int shunt_milliOhms = mode / 10;
for ( ; mult > 0 ; mult-- )
shunt_milliOhms *= 10;
Ina219Data->current_multiplier = 1.0 / shunt_milliOhms;
#ifdef DEBUG_TASMOTA_SENSOR
dtostrfd(Ina219Data->current_multiplier,5,__ina219_dbg1);
DEBUG_SENSOR_LOG("Ina219SetCalibration: shunt=%dmO => cur_mul=%s",shunt_milliOhms,__ina219_dbg1);
#endif
}
config = ISL28022_CONFIG_BVOLTAGERANGE_60V // If INA219 0..32V, If ISL28022 0..60V config = ISL28022_CONFIG_BVOLTAGERANGE_60V // If INA219 0..32V, If ISL28022 0..60V
| INA219_CONFIG_GAIN_8_320MV // Use max scale | INA219_CONFIG_GAIN_8_320MV // Use max scale
| INA219_CONFIG_BADCRES_12BIT_16S_8510US // use averaging to improve accuracy | INA219_CONFIG_BADCRES_12BIT_16S_8510US // use averaging to improve accuracy
| INA219_CONFIG_SADCRES_12BIT_16S_8510US // use averaging to improve accuracy | INA219_CONFIG_SADCRES_12BIT_16S_8510US // use averaging to improve accuracy
| INA219_CONFIG_MODE_SANDBVOLT_CONTINUOUS; | INA219_CONFIG_MODE_SANDBVOLT_CONTINUOUS;
#ifdef DEBUG_TASMOTA_SENSOR DEBUG_SENSOR_LOG(PSTR("Ina219Init: Config=0x%04X (%d)"), config, config);
AddLog(LOG_LEVEL_DEBUG, PSTR("Ina219SetCalibration: Config=0x%04X (%d)"), config, config);
#endif
// Set Config register to take into account the settings above // Set Config register to take into account the settings above
if (!I2cWrite16(addr, INA219_REG_CONFIG, config)) if (!I2cWrite16(addr, INA219_REG_CONFIG, config))
return 0; return 0;
uint16_t intr_reg = 0x0FFFF; uint16_t intr_reg = 0x0FFFF;
bool status = I2cValidRead16(&intr_reg, addr, ISL28022_REG_INTRSTATUS); bool status = I2cValidRead16(&intr_reg, addr, ISL28022_REG_INTRSTATUS);
#ifdef DEBUG_TASMOTA_SENSOR DEBUG_SENSOR_LOG(PSTR("Ina219Init: IntrReg=0x%04X (%d)"), intr_reg, status);
AddLog(LOG_LEVEL_DEBUG, PSTR("Ina219: IntrReg=0x%04X (%d)"), intr_reg, status);
#endif
if (status && 0 == intr_reg) if (status && 0 == intr_reg)
return ISL28022_ACTIVE; // ISL28022 return ISL28022_MODEL; // ISL28022
return INA219_ACTIVE; // INA219 return INA219_MODEL; // INA219
} }
float Ina219GetShuntVoltage_mV(uint16_t addr) float Ina219GetShuntVoltage_mV(uint16_t addr)
@ -208,48 +227,39 @@ float Ina219GetShuntVoltage_mV(uint16_t addr)
int16_t shunt_voltage = I2cReadS16(addr, INA219_REG_SHUNTVOLTAGE); int16_t shunt_voltage = I2cReadS16(addr, INA219_REG_SHUNTVOLTAGE);
DEBUG_SENSOR_LOG("Ina219GetShuntVoltage_mV: ShReg = 0x%04X (%d)",shunt_voltage, shunt_voltage); DEBUG_SENSOR_LOG("Ina219GetShuntVoltage_mV: ShReg = 0x%04X (%d)",shunt_voltage, shunt_voltage);
// convert to shunt voltage in mV (so +-327mV) (LSB=10µV=0.01mV) // convert to shunt voltage in mV (so +-327mV) (LSB=10µV=0.01mV)
return (float)shunt_voltage * 0.01; return (float)shunt_voltage * INA219_SHUNT_ADC_LSB_MV;
} }
float Ina219GetBusVoltage_V(uint16_t addr, uint8_t model) float Ina219GetBusVoltage_V(uint16_t addr, uint8_t model)
{ {
uint16_t bus_voltage = I2cRead16(addr, INA219_REG_BUSVOLTAGE); uint16_t bus_voltage = I2cRead16(addr, INA219_REG_BUSVOLTAGE);
if (ISL28022_ACTIVE == model) { bus_voltage >>= (ISL28022_MODEL == model) ? 2 : 3;
// ISL2802 LSB is bit 2
bus_voltage >>= 2;
DEBUG_SENSOR_LOG("Isl28022GetBusVoltage_V: BusReg = 0x%04X (%d)",bus_voltage, bus_voltage);
}
else {
// INA219 LSB is bit 3
bus_voltage >>= 3;
DEBUG_SENSOR_LOG("Ina219GetBusVoltage_V: BusReg = 0x%04X (%d)",bus_voltage, bus_voltage); DEBUG_SENSOR_LOG("Ina219GetBusVoltage_V: BusReg = 0x%04X (%d)",bus_voltage, bus_voltage);
}
// and multiply by LSB raw bus voltage to return bus voltage in volts (LSB=4mV=0.004V) // and multiply by LSB raw bus voltage to return bus voltage in volts (LSB=4mV=0.004V)
return (float)bus_voltage * 0.004; return (float)bus_voltage * INA219_BUS_ADC_LSB;
} }
bool Ina219Read(void) bool Ina219Read(void)
{ {
for (int i=0; i<INA219_MAX_COUNT; i++) { for (int i=0 ; i < Ina219Count; i++) {
if (!Ina219Data->chan[i].active) { continue; } uint16_t addr = Ina219Data[i].addr;
uint16_t addr = INA219_ADDRESSES[i]; if (!addr) { continue; }
float bus_voltage_V = Ina219GetBusVoltage_V(addr, Ina219Data->chan[i].active); float bus_voltage_V = Ina219GetBusVoltage_V(addr, Ina219Data[i].model);
float shunt_voltage_mV = Ina219GetShuntVoltage_mV(addr); float shunt_voltage_mV = Ina219GetShuntVoltage_mV(addr);
#ifdef DEBUG_TASMOTA_SENSOR #ifdef DEBUG_TASMOTA_SENSOR
dtostrfd(bus_voltage_V,5,__ina219_dbg1); dtostrfd(bus_voltage_V,5,__ina219_dbg1);
dtostrfd(shunt_voltage_mV,5,__ina219_dbg2); dtostrfd(shunt_voltage_mV,5,__ina219_dbg2);
DEBUG_SENSOR_LOG("Ina219Read: bV=%sV, sV=%smV",__ina219_dbg1,__ina219_dbg2); DEBUG_SENSOR_LOG("Ina219Read[%d]: bV=%sV, sV=%smV", i, __ina219_dbg1, __ina219_dbg2);
#endif #endif
// we return the power-supply-side voltage (as bus_voltage register provides the load-side voltage) // we return the power-supply-side voltage (as bus_voltage register provides the load-side voltage)
Ina219Data->chan[i].voltage = bus_voltage_V + (shunt_voltage_mV / 1000); Ina219Data[i].voltage = bus_voltage_V + (shunt_voltage_mV / 1000);
// current is simply calculted from shunt voltage using pre-calculated multiplier // current is simply calculted from shunt voltage using pre-calculated multiplier
Ina219Data->chan[i].current = shunt_voltage_mV * Ina219Data->current_multiplier; Ina219Data[i].current = shunt_voltage_mV * Ina219Data[i].current_multiplier;
#ifdef DEBUG_TASMOTA_SENSOR #ifdef DEBUG_TASMOTA_SENSOR
dtostrfd(Ina219Data->chan[i].voltage,5,__ina219_dbg1); dtostrfd(Ina219Data[i].voltage,5,__ina219_dbg1);
dtostrfd(Ina219Data->chan[i].current,5,__ina219_dbg2); dtostrfd(Ina219Data[i].current,5,__ina219_dbg2);
DEBUG_SENSOR_LOG("Ina219Read: V=%sV, I=%smA",__ina219_dbg1,__ina219_dbg2); DEBUG_SENSOR_LOG("Ina219Read[%d]: V=%sV, I=%smA", i, __ina219_dbg1,__ina219_dbg2);
#endif #endif
Ina219Data->chan[i].valid = SENSOR_MAX_MISS;
} }
return true; return true;
} }
@ -260,11 +270,24 @@ bool Ina219Read(void)
bool Ina219CommandSensor(void) bool Ina219CommandSensor(void)
{ {
char argument[XdrvMailbox.data_len];
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 255)) { if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 255)) {
Settings->ina219_mode = XdrvMailbox.payload; Settings->ina219_mode = XdrvMailbox.payload;
TasmotaGlobal.restart_flag = 2; for (int i=0; i < Ina219Count; i++) {
float shunt = INA219_SHUNT_RESISTOR;
if (ArgC() > (i +1)) {
shunt = CharToFloat(ArgV(argument, 2 +i));
} }
Response_P(S_JSON_SENSOR_INDEX_NVALUE, XSNS_13, Settings->ina219_mode); Ina219SetShuntMode(i, Settings->ina219_mode, shunt);
}
}
Response_P(INA219_SENSORCMND_START, XSNS_13, Settings->ina219_mode);
for (int i = 0 ; i < Ina219Count ; i++ ) {
dtostrfd(Ina219GetShunt(i),5,argument);
ResponseAppend_P(PSTR("%s%c"), argument, ((i < (Ina219Count-1))?',':'\0'));
}
ResponseAppend_P(INA219_SENSORCMND_END);
return true; return true;
} }
@ -274,20 +297,22 @@ bool Ina219CommandSensor(void)
void Ina219Detect(void) void Ina219Detect(void)
{ {
for (uint32_t i = 0; i < INA219_MAX_COUNT; i++) { for (uint32_t i = 0; i < INA219_MAX_COUNT; i++) {
uint16_t addr = INA219_ADDRESSES[i]; uint16_t addr = INA219_ADDRESSES[INA219_FIRST_ADDRESS +i];
if (!I2cSetDevice(addr)) { continue; } if (!I2cSetDevice(addr)) { continue; }
if (!Ina219Data) { if (!Ina219Data) {
Ina219Data = (struct INA219_Data*)calloc(1,sizeof(struct INA219_Data)); Ina219Data = (struct INA219_Data*)calloc(INA219_MAX_COUNT,sizeof(struct INA219_Data));
if (!Ina219Data) { if (!Ina219Data) {
AddLog(LOG_LEVEL_ERROR,PSTR("INA219: Mem Error")); AddLog(LOG_LEVEL_ERROR,PSTR("INA219: Mem Error"));
return; return;
} }
} }
int model = Ina219SetCalibration(Settings->ina219_mode, addr); int model = Ina219Init(addr);
if (model) { if (model) {
I2cSetActiveFound(addr, INA219_TYPE[model-1]); I2cSetActiveFound(addr, INA219_TYPE[model-1]);
Ina219Data->chan[i].active = model; Ina219SetShuntMode(Ina219Count, Settings->ina219_mode, INA219_SHUNT_RESISTOR);
Ina219Data->count++; Ina219Data[Ina219Count].model = model;
Ina219Data[Ina219Count].addr = addr;
Ina219Count++;
} }
} }
} }
@ -307,28 +332,21 @@ const char HTTP_SNS_INA219_DATA[] PROGMEM =
void Ina219Show(bool json) void Ina219Show(bool json)
{ {
int num_found=0; for (int i = 0; i < Ina219Count; i++) {
for (int i=0; i<INA219_MAX_COUNT; i++) if (!Ina219Data[i].model)
if (Ina219Data->chan[i].active && Ina219Data->chan[i].valid)
num_found++;
int sensor_num = 0;
for (int i=0; i<INA219_MAX_COUNT; i++) {
if (!Ina219Data->chan[i].active && !Ina219Data->chan[i].valid)
continue; continue;
sensor_num++;
char voltage[16]; char voltage[16];
dtostrfd(Ina219Data->chan[i].voltage, Settings->flag2.voltage_resolution, voltage); dtostrfd(Ina219Data[i].voltage, Settings->flag2.voltage_resolution, voltage);
char current[16]; char current[16];
dtostrfd(Ina219Data->chan[i].current, Settings->flag2.current_resolution, current); dtostrfd(Ina219Data[i].current, Settings->flag2.current_resolution, current);
char power[16]; char power[16];
dtostrfd(Ina219Data->chan[i].voltage * Ina219Data->chan[i].current, Settings->flag2.wattage_resolution, power); dtostrfd(Ina219Data[i].voltage * Ina219Data[i].current, Settings->flag2.wattage_resolution, power);
char name[16]; char name[16];
if (num_found>1) if (Ina219Count>1)
snprintf_P(name, sizeof(name), PSTR("%s%c%d"), INA219_TYPE[Ina219Data->chan[i].active-1], IndexSeparator(), sensor_num); snprintf_P(name, sizeof(name), PSTR("%s%c%d"), INA219_TYPE[Ina219Data[i].model-1], IndexSeparator(), i +1);
else else
snprintf_P(name, sizeof(name), PSTR("%s"), INA219_TYPE[Ina219Data->chan[i].active-1]); snprintf_P(name, sizeof(name), PSTR("%s"), INA219_TYPE[Ina219Data[i].model-1]);
if (json) { if (json) {
ResponseAppend_P(PSTR(",\"%s\":{\"Id\":%02x,\"" D_JSON_VOLTAGE "\":%s,\"" D_JSON_CURRENT "\":%s,\"" D_JSON_POWERUSAGE "\":%s}"), ResponseAppend_P(PSTR(",\"%s\":{\"Id\":%02x,\"" D_JSON_VOLTAGE "\":%s,\"" D_JSON_CURRENT "\":%s,\"" D_JSON_POWERUSAGE "\":%s}"),