diff --git a/CHANGELOG.md b/CHANGELOG.md index b93f19817..77f5a41ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,9 +13,11 @@ All notable changes to this project will be documented in this file. - Rule variable %color% (#14572) - Command ``SspmDisplay 1`` to display Sonoff SPM energy data in GUI for relays powered on only - Command ``SspmEnergyTotal`` to (p)reset Sonoff SPM total energy without today's energy +- Command ``SspmEnergyYesterday`` to (p)reset Sonoff SPM energy yesterday - Command ``SspmHistory`` to retrieve Sonoff SPM daily energy up to last six month (as defined by ARM firmware) - Command ``SspmIAmHere`` to (faintly) blink Sonoff SPM-4Relay module error light of requested relay - Command ``SspmLog [x]`` to retrieve Sonoff SPM relay power state change and cause logging +- Command ``SspmOverload `` to set Sonoff SPM overload criteria for any relay - Command ``SspmScan`` to rescan Sonoff SPM modbus - Support for MQ analog sensor for air quality by Francesco Adriani (#14581) - Command ``SetOption134 1`` to disable PWM auto-phasing for lights by default (new behavior) (#14590) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 2135624e8..0d3d0c028 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -108,10 +108,12 @@ The latter links can be used for OTA upgrades too like ``OtaUrl http://ota.tasmo - Command ``SSerialConfig `` to change Serial Bridge configuration - Command ``SspmDisplay 1`` to display Sonoff SPM energy data in GUI for relays powered on only - Command ``SspmEnergyTotal`` to (p)reset Sonoff SPM total energy without today's energy +- Command ``SspmEnergyYesterday`` to (p)reset Sonoff SPM energy yesterday - Command ``SspmHistory`` to retrieve Sonoff SPM daily energy up to last six month (as defined by ARM firmware) - Command ``SspmIAmHere`` to (faintly) blink Sonoff SPM-4Relay module error light of requested relay - Command ``SspmLog [x]`` to retrieve Sonoff SPM relay power state change and cause logging - Command ``SspmMap 2,1,..`` to map Sonoff SPM scanned module to physical module [#14281](https://github.com/arendst/Tasmota/issues/14281) +- Command ``SspmOverload `` to set Sonoff SPM overload criteria for any relay - Command ``SspmScan`` to rescan Sonoff SPM modbus - Command ``WebTime ,`` to show part of date and/or time in web gui based on "2017-03-07T11:08:02-07:00" - Commands for ESP32 ethernet configuration ``EthIpAddress``, ``EthGateway``, ``EthSubnetmask``, ``EthDnsServer1`` and ``EthDnsServer2`` [#14385](https://github.com/arendst/Tasmota/issues/14385) @@ -133,6 +135,8 @@ The latter links can be used for OTA upgrades too like ``OtaUrl http://ota.tasmo - ESP32 support for TuyaMcu - ESP32 Berry features - ESP32 support for USE_PWM_DIMMER as GPIO ``Option E1`` +- ESP32 increase PWM channels to 16 +- ESP32 initial support for ESP32S3 with support for 38 configurable GPIOs ### Breaking Changed diff --git a/tasmota/xdrv_86_esp32_sonoff_spm.ino b/tasmota/xdrv_86_esp32_sonoff_spm.ino index 225db6ccc..a29ae8237 100644 --- a/tasmota/xdrv_86_esp32_sonoff_spm.ino +++ b/tasmota/xdrv_86_esp32_sonoff_spm.ino @@ -24,12 +24,7 @@ /*********************************************************************************************\ * Sonoff Stackable Power Manager * - * {"NAME":"Sonoff SPM","GPIO":[0,0,0,0,3200,5536,0,0,0,0,0,0,3232,0,5600,0,0,0,0,5568,0,0,0,0,0,0,0,0,544,0,0,32,0,0,0,0],"FLAG":0,"BASE":1} - * - * Initial POC template: - * {"NAME":"Sonoff SPM (POC1)","GPIO":[1,1,1,1,3200,1,1,1,1,1,1,1,3232,1,1,1,0,1,1,1,0,1,1,1,0,0,0,0,544,1,1,32,1,0,0,1],"FLAG":0,"BASE":1} - * Add ethernet support: - * {"NAME":"Sonoff SPM (POC2)","GPIO":[0,0,0,0,3200,5536,0,0,0,0,0,0,3232,0,5600,0,0,0,0,5568,0,0,0,0,0,0,0,0,544,0,0,32,0,0,0,0],"FLAG":0,"BASE":1} + * {"NAME":"Sonoff SPM","GPIO":[0,0,0,0,3200,5536,0,0,672,704,736,0,3232,0,5600,0,0,0,0,5568,0,0,0,0,0,0,0,0,544,0,0,32,0,0,0,0],"FLAG":0,"BASE":1} * * Things to know: * Bulk of the action is handled by ARM processors present in every unit communicating over modbus RS-485. @@ -39,32 +34,41 @@ * Power on sequence for two SPM-4Relay modules is 00-00-15-10-(0F)-(13)-(13)-(19)-0C-09-04-09-04-0B-0B * Up to six months of daily energy are stored onthe SD-Card. Previous data is lost. * Tasmota support is based on Sonoff SPM v1.0.0 ARM firmware. + * Energy history cannot be guaranteed using either SD-Card or internal flash. As a solution Tasmota stores the total energy and yesterday energy just after midnight. * - * Tasmota POC1: - * Up to 7 SPM-4Relay units supporting up to 28 relays. - * Gui rotating energy display for 4 relays at a time. - * Button on SPM-Main initiates re-scan of SPM-4Relay units. - * Blue led equals Tasmota WiFi status. - * - * Tasmota POC2: - * Ethernet support. - * Gui optimized for energy display. - * Yellow led lights if no ARM connection can be made. - * Yellow led blinks 2 seconds if an ARM-ESP comms CRC error is detected. - * Persistence for module mapping - * Supported commands: - * SspmDisplay 0|1 - Select alternative GUI rotating display either all or powered on only - * SspmHistory - Retrieve daily energy of last six month (as defined by ARM firmware) - * SspmIAmHere - Blink ERROR in SPM-4Relay where relay resides - * SspmLog [x] - Retrieve relay power state change and cause logging - * SspmMap 2,3,1,.. - Map scanned module number to physical module number using positional numbering - * SspmScan - Rescan ARM modbus taking around 20 seconds - * SspmReset 1 - Reset ARM and restart ESP + * Tasmota features: + * - Up to 7 SPM-4Relay units supporting a total of 28 relays. + * - Button on SPM-Main initiates re-scan of SPM-4Relay units. + * - SPI master to ARM (ARM firmware upload from ESP using EasyFlash not supported). + * - Ethernet support. + * - Gui rotating energy display for 4 relays at a time. + * - Gui optimized for energy display. + * - Blue led equals Tasmota WiFi status. + * - Yellow led lights if no ARM connection can be made. + * - Yellow led blinks 2 seconds if an ARM-ESP comms CRC error is detected. + * - Persistence for module mapping, total energy and energy yesterday + * - Supported commands: + * SspmDisplay 0|1 - Select alternative GUI rotating display either all (0) or powered on only (1) + * SspmDump 0|1 - Select shortenend (0) or full (1) serial receive buffer dumps + * SspmEnergyTotal - (p)reset total energy without today's energy + * SspmEnergyYesterday - (p)reset energy yesterday + * SspmHistory - Retrieve daily energy of last six month (as defined by ARM firmware) + * SspmIAmHere - Blink ERROR in SPM-4Relay where relay resides + * SspmLog [x] - Retrieve relay power state change and cause logging + * SspmMap 2,3,1,.. - Map scanned module number to physical module number using positional numbering + * SspmOverload 0 - Set default overload detection parameters as read from module during initial scan + * SspmOverload ,,,, + * SspmOverload 0,0.10,4400.00,0.10,240.00,20.00 - Set default values using comma separated options + * SspmOverload 0 0.10 4400.00 0.10 240.00 20.00 - Set default values using space separated options + * SspmOverload 10,12.3 - Enable overload detection after 10 seconds for MinPower + * SspmOverload 10,0,22.2 - Enable overload detection after 10 seconds for MaxPower + * SspmOverload 10,0,0,0,235.2 - Enable overload detection after 10 seconds for MaxVoltage + * SspmScan - Rescan ARM modbus taking around 20 seconds + * SspmReset 1 - Reset ARM and restart ESP * * Todo: * Gui for Overload Protection entry (is handled by ARM processor). * Gui for Scheduling entry (is handled by ARM processor). - * SPI master to ARM (ARM firmware upload from ESP using EasyFlash). * * Nice to have: * Support for all 32 SPM-4Relay units equals 128 relays (restricted due to internal Tasmota register use) @@ -116,9 +120,9 @@ #define XDRV_86 86 #define SSPM_MAX_MODULES 7 // Currently supports up to 7 SPM-4RELAY units for a total of 28 relays restricted by power_t size -#define SSPM_SERIAL_BUFFER_SIZE 512 // Needs to accomodate Energy total history for six months (408 bytes) +#define SSPM_SERIAL_BUFFER_SIZE 512 // Needs to accomodate Energy total history for 180 days (408 bytes) -// Send +// From ESP to ARM #define SSPM_FUNC_FIND 0 // 0x00 #define SSPM_FUNC_SET_OPS 3 // 0x03 - Overload Protection #define SSPM_FUNC_GET_OPS 4 // 0x04 @@ -136,13 +140,23 @@ #define SSPM_FUNC_ENERGY_PERIOD 27 // 0x1B #define SSPM_FUNC_RESET 28 // 0x1C - Remove device from eWelink and factory reset -// Receive +// From ARM to ESP #define SSPM_FUNC_ENERGY_RESULT 6 // 0x06 #define SSPM_FUNC_KEY_PRESS 7 // 0x07 #define SSPM_FUNC_SCAN_START 15 // 0x0F #define SSPM_FUNC_SCAN_RESULT 19 // 0x13 #define SSPM_FUNC_SCAN_DONE 25 // 0x19 +// Unknown +#define SSPM_FUNC_01 +#define SSPM_FUNC_02 +#define SSPM_FUNC_05 +#define SSPM_FUNC_14 +#define SSPM_FUNC_17 +#define SSPM_FUNC_18 +#define SSPM_FUNC_20 +#define SSPM_FUNC_23 + #define SSPM_GPIO_ARM_RESET 15 #define SSPM_GPIO_LED_ERROR 33 @@ -152,13 +166,15 @@ #define SSPM_TOTAL_MODULES 32 // Max number of SPM-4RELAY units for a total of 128 relays -const uint32_t SSPM_VERSION = 0x01010103; // Latest driver version (See settings deltas below) +const uint32_t SSPM_VERSION = 0x0104; // Latest driver version (See settings deltas below) enum SspmMachineStates { SPM_NONE, // Do nothing SPM_WAIT, // Wait 100ms SPM_RESET, // Toggle ARM reset pin SPM_POLL_ARM, // Wait for first acknowledge from ARM after reset + SPM_POLL_ARM_SPI, // Wait for first acknowledge from ARM SPI after reset SPM_POLL_ARM_2, // Wait for second acknowledge from ARM after reset + SPM_POLL_ARM_3, // Wait for second acknowledge from ARM after reset SPM_SEND_FUNC_UNITS, // Get number of units SPM_START_SCAN, // Start module scan sequence SPM_WAIT_FOR_SCAN, // Wait for scan sequence to complete @@ -168,16 +184,40 @@ enum SspmMachineStates { SPM_NONE, // Do nothing }; const char kSSPMTriggers[] PROGMEM = "Tasmota|Device|Overload|Overtemp"; +const char kSSPMOverload[] PROGMEM = "Tbd1|Voltage|Current|Power|Tbd2|Tbd3|Tbd4"; #include TasmotaSerial *SspmSerial; -// Global structure containing driver saved variables +typedef union { + uint16_t data; + struct { + uint16_t dump : 1; // bit 0 (2022.01.3) - SSPMDump - Short receive dump (0) or full receive dump (1) + uint16_t display : 1; // bit 1 (2022.01.3) - SSPMDisplay - GUI display all relays (0) or only powered on relays (1) + uint16_t spare02 : 1; // bit 2 + uint16_t spare03 : 1; // bit 3 + uint16_t spare04 : 1; // bit 4 + uint16_t spare05 : 1; // bit 5 + uint16_t spare06 : 1; // bit 6 + uint16_t spare07 : 1; // bit 7 + uint16_t spare08 : 1; // bit 8 (This bit is default 1 due to legacy use) + uint16_t spare09 : 1; // bit 9 + uint16_t spare10 : 1; // bit 10 + uint16_t spare11 : 1; // bit 11 + uint16_t spare12 : 1; // bit 12 + uint16_t spare13 : 1; // bit 13 + uint16_t spare14 : 1; // bit 14 + uint16_t spare15 : 1; // bit 15 + }; +} SSPMSOBitfield; + typedef struct { uint32_t crc32; // To detect file changes - uint32_t version; // To detect driver function changes + uint16_t version; // To detect driver function changes + SSPMSOBitfield flag; uint16_t module_map[SSPM_TOTAL_MODULES]; // Max possible SPM relay modules float energy_total[SSPM_TOTAL_MODULES][4]; // Total energy in kWh - float allows up to 262143.99 kWh + float energy_yesterday[SSPM_TOTAL_MODULES][4]; // Energy yesterday in kWh - float allows up to 262143.99 kWh } tSspmSettings; typedef struct { @@ -189,9 +229,21 @@ typedef struct { float reactive_power[SSPM_MAX_MODULES][4]; // 123.12 VAr float power_factor[SSPM_MAX_MODULES][4]; // 0.12 float energy_today[SSPM_MAX_MODULES][4]; // 12345 kWh - float energy_yesterday[SSPM_MAX_MODULES][4]; // 12345 kWh float energy_total[SSPM_MAX_MODULES][4]; // 12345 kWh total energy since last 6 month!!! + float min_power; + float max_power; + float min_voltage; + float max_voltage; + float min_current; + float max_current; + + float overload_min_power; + float overload_max_power; + float overload_min_voltage; + float overload_max_voltage; + float overload_max_current; + uint32_t timeout; power_t old_power; uint16_t last_totals; @@ -211,7 +263,9 @@ typedef struct { uint8_t mstate; uint8_t last_button; uint8_t error_led_blinks; - uint8_t yesterday; + uint8_t overload_relay; + uint8_t overload_delay; + uint8_t overload_enable; uint8_t history_relay; uint8_t log_relay; bool map_change; @@ -242,10 +296,9 @@ void SSPMSettingsDefault(void) { void SSPMSettingsDelta(void) { // Fix possible setting deltas if (Sspm->Settings.version != SSPM_VERSION) { // Fix version dependent changes - -// if (Settings->version < 0x01010101) { - -// } + if (Sspm->Settings.version < 0x0104) { + Sspm->Settings.flag.display = Settings->sbflag1.sspm_display; + } // Set current version and save settings Sspm->Settings.version = SSPM_VERSION; @@ -306,6 +359,8 @@ void SSPMSettingsSave(void) { uint32_t SSMPGetModuleId(uint32_t module) { // Return short module id + // input number + // output two_byte value uint32_t module_id = 0; if (module < Sspm->module_max) { module_id = Sspm->module[module][0] << 8 | Sspm->module[module][1]; @@ -315,6 +370,8 @@ uint32_t SSMPGetModuleId(uint32_t module) { int SSPMGetMappedModuleIdIfFound(uint32_t module) { // Return mapped module number + // input number + // output number for (uint32_t module_nr = 0; module_nr < Sspm->module_max; module_nr++) { if (Sspm->Settings.module_map[module] == SSMPGetModuleId(module_nr)) { return module_nr; // 0, 1, .. @@ -325,6 +382,8 @@ int SSPMGetMappedModuleIdIfFound(uint32_t module) { uint32_t SSPMGetMappedModuleId(uint32_t module) { // Return mapped module number + // input number + // output number int module_nr = SSPMGetMappedModuleIdIfFound(module); if (-1 == module_nr) { module_nr = module; // input module number if not found used as fallback @@ -334,6 +393,8 @@ uint32_t SSPMGetMappedModuleId(uint32_t module) { int SSPMGetModuleNumberFromMapIfFound(uint32_t id) { // Return module number based on first two bytes of module id + // input two-byte value + // output number for (uint32_t module_nr = 0; module_nr < SSPM_MAX_MODULES; module_nr++) { if (id == Sspm->Settings.module_map[module_nr]) { return module_nr; // 0, 1, ... @@ -344,6 +405,8 @@ int SSPMGetModuleNumberFromMapIfFound(uint32_t id) { uint32_t SSPMGetModuleNumberFromMap(uint32_t id) { // Return module number based on first two bytes of module id + // input two-byte value + // output number int module_nr = SSPMGetModuleNumberFromMapIfFound(id); if (-1 == module_nr) { module_nr = 0; // 0 if not found used as fallback @@ -351,6 +414,8 @@ uint32_t SSPMGetModuleNumberFromMap(uint32_t id) { return (uint32_t)module_nr; // 0, 1, ... } +/*********************************************************************************************/ + void SSPMSetLock(uint32_t seconds) { Sspm->timeout = seconds * 10; // Decremented every 100mSec Sspm->allow_updates = 0; // Disable requests from 100mSec loop @@ -368,6 +433,20 @@ uint16_t SSPMCalculateCRC(uint8_t *frame, uint32_t num) { return crc ^ 0; } +float SSPMGetValue(uint8_t *buffer) { + // return floast from three bytes in buffer + float value = (buffer[0] << 8) + buffer[1] + (float)buffer[2] / 100; + return value; +} + +void SSPMSetValue(uint8_t *buffer, float value) { + // Store float in three bytes + uint32_t integer = value; + buffer[0] = integer >> 8; + buffer[1] = integer; + buffer[2] = (value * 100) - (integer * 100); // Fraction +} + /*********************************************************************************************/ void SSPMSend(uint32_t size) { @@ -427,9 +506,11 @@ void SSPMSendCmnd(uint32_t command) { /*********************************************************************************************/ -void SSPMSendOPS(uint32_t relay_num) { +void SSPMSendOPS(uint32_t relay) { /* Overload Protection + + 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 39 AA 55 01 6b 7e 32 37 39 37 34 13 4b 35 36 37 00 03 00 12 04 00 11 30 00 00 00 0a 00 f0 00 00 00 0a 00 14 00 00 fb a6 f8 = Default settings Marker |Module id |Ac|Cm|Size |Ch|Ra|Max P |Min P |Max U |Min U |Max I |De|Ix|Chksm| @@ -444,15 +525,36 @@ void SSPMSendOPS(uint32_t relay_num) { Values are XX XX - number XX - decimals + aa 55 01 8b 34 32 37 39 37 34 13 4b 35 36 37 00 03 00 12 02 10 00 14 00 00 00 0a 00 f0 00 00 00 0a 00 14 00 14 46 7b 80 - L2, 20 seconds, MaxPower 20W + Acknowledge: + aa 55 01 8b 34 32 37 39 37 34 13 4b 35 36 37 80 03 00 01 00 46 75 1d AA 55 01 6b 7e 32 37 39 37 34 13 4b 35 36 37 80 03 00 01 00 14 08 bc |Ac|Cm|Size |Rt|Ix|Chksm| Ac - Acknowledge or error number Rt - Return code */ + uint8_t module = relay >> 2; + if (module >= Sspm->module_max) { return; } + + uint8_t channel = 1 << (relay & 0x03); // Channel relays are bit masked + SSPMInitSend(); + memcpy(SspmBuffer +3, Sspm->module[SSPMGetMappedModuleId(module)], SSPM_MODULE_NAME_SIZE); SspmBuffer[16] = SSPM_FUNC_SET_OPS; // 0x03 + SspmBuffer[18] = 0x12; + SspmBuffer[19] = channel; + SspmBuffer[20] = Sspm->overload_enable; + SSPMSetValue(&SspmBuffer[21], Sspm->overload_max_power); + SSPMSetValue(&SspmBuffer[24], Sspm->overload_min_power); + SSPMSetValue(&SspmBuffer[27], Sspm->overload_max_voltage); + SSPMSetValue(&SspmBuffer[30], Sspm->overload_min_voltage); + SSPMSetValue(&SspmBuffer[33], Sspm->overload_max_current); + SspmBuffer[36] = Sspm->overload_delay; + Sspm->command_sequence++; + SspmBuffer[37] = Sspm->command_sequence; + SSPMSend(40); } void SSPMSendGetOps(uint32_t module) { @@ -461,6 +563,8 @@ void SSPMSendGetOps(uint32_t module) { aa 55 01 6b 7e 32 37 39 37 34 13 4b 35 36 37 00 04 00 00 08 c0 0a 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; // 0x04 @@ -481,6 +585,9 @@ void SSPMSendSetRelay(uint32_t relay, uint32_t state) { channel |= (channel << 4); } uint8_t module = relay >> 2; + + if (module >= Sspm->module_max) { return; } + SSPMInitSend(); memcpy(SspmBuffer +3, Sspm->module[SSPMGetMappedModuleId(module)], SSPM_MODULE_NAME_SIZE); SspmBuffer[16] = SSPM_FUNC_SET_RELAY; // 0x08 @@ -498,6 +605,8 @@ void SSPMSendGetModuleState(uint32_t module) { AA 55 01 8b 34 32 37 39 37 34 13 4b 35 36 37 00 09 00 01 0f 05 b5 de Marker |Module id |Ac|Cm|Size |Pl|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_MODULE_STATE; // 0x09 @@ -565,6 +674,8 @@ void SSPMSendGetScheme(uint32_t module) { AA 55 01 6b 7e 32 37 39 37 34 13 4b 35 36 37 00 0b 00 00 09 14 c8 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_SCHEME; // 0x0B @@ -598,6 +709,7 @@ void SSPMSendSetTime(void) { SspmBuffer[26] = 0; SspmBuffer[27] = 0; SspmBuffer[28] = Rtc.time_timezone / 60; +// SspmBuffer[28] = (Rtc.time_timezone / 60) +1; // possibly itead bug SspmBuffer[29] = abs(Rtc.time_timezone % 60); Sspm->command_sequence++; SspmBuffer[30] = Sspm->command_sequence; @@ -617,6 +729,9 @@ void SSPMSendIAmHere(uint32_t relay) { Rs = Return state */ uint8_t module = relay >> 2; + + if (module >= Sspm->module_max) { return; } + SSPMInitSend(); memcpy(SspmBuffer +3, Sspm->module[SSPMGetMappedModuleId(module)], SSPM_MODULE_NAME_SIZE); SspmBuffer[16] = SSPM_FUNC_IAMHERE; // 0x0D @@ -662,6 +777,9 @@ void SSPMSendGetEnergyTotal(uint32_t relay) { Marker | | |Cm|Size |Module id |Ch|Ix|Chksm| */ uint8_t module = relay >> 2; + + if (module >= Sspm->module_max) { return; } + uint8_t channel = relay & 0x03; // Channel relays are NOT bit masked this time SSPMInitSend(); SspmBuffer[16] = SSPM_FUNC_GET_ENERGY_TOTAL; // 0x16 @@ -682,6 +800,9 @@ void SSPMSendGetEnergy(uint32_t relay) { Marker | | |Cm|Size |Module id | |Ch| |Ix|Chksm| */ uint8_t module = relay >> 2; + + if (module >= Sspm->module_max) { return; } + uint8_t channel = 1 << (relay & 0x03); // Channel relays are bit masked SSPMInitSend(); SspmBuffer[16] = SSPM_FUNC_GET_ENERGY; // 0x18 @@ -700,12 +821,16 @@ void SSPMSendGetEnergy(uint32_t relay) { void SSPMSendGetLog(uint32_t relay, uint32_t entries) { /* 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 - AA 55 01 00 00 00 00 00 00 00 00 00 00 00 00 00 1a 00 10 6b 7e 32 37 39 37 34 13 4b 35 36 37 00 00 00 1d 09 8c cd + AA 55 01 00 00 00 00 00 00 00 00 00 00 00 00 00 1a 00 10 6b 7e 32 37 39 37 34 13 4b 35 36 37 00 00 00 1d 09 8c cd -- Logs 1 to 29 + aa 55 01 00 00 00 00 00 00 00 00 00 00 00 00 00 1a 00 10 8b 34 32 37 39 37 34 13 4b 35 36 37 00 1d 00 3a 14 b8 ee -- Logs 30 to 58 Marker | | |Cm|Size |Module id |Start|End |Ix|Chksm| Start = newest log start number (Latest is 0) End = older log end number (End - Start >= 29 (0x1d)) */ uint8_t module = relay >> 2; + + if (module >= Sspm->module_max) { return; } + uint32_t startlog = (entries >= 29) ? entries -29 : 0; SSPMInitSend(); SspmBuffer[16] = SSPM_FUNC_GET_LOG; // 0x1A @@ -721,6 +846,39 @@ void SSPMSendGetLog(uint32_t relay, uint32_t entries) { SSPMSend(38); } +void SSPMSendGetEnergyPeriod(uint32_t relay) { + /* + Start + 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 39 40 41 42 43 44 + aa 55 01 00 00 00 00 00 00 00 00 00 00 00 00 00 1b 00 17 8b 34 32 37 39 37 34 13 4b 35 36 37 03 00 07 e6 01 1c 0d 08 33 00 81 1a 1c 41 - L4 Start 2022-01-28T14:08:51 + Marker | | |Cm|Size |Module id |Ch|St|Year |Mo|Da|HH|MM|SS|Milli|Ix|Chksm| + |L4|--| 2022| 1|28|13|08|51| 129| + |Start + + Immediate request for refresh + 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 + aa 55 01 00 00 00 00 00 00 00 00 00 00 00 00 00 1b 00 0e 8b 34 32 37 39 37 34 13 4b 35 36 37 03 02 1b 5a 30 - L4 Refresh + Marker | | |Cm|Size |Module id |Ch|--|Ix|Chksm| + |L4|Refresh + + Following requests + 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 + aa 55 01 00 00 00 00 00 00 00 00 00 00 00 00 00 1b 00 0e 8b 34 32 37 39 37 34 13 4b 35 36 37 03 02 1c 98 71 - L4 Refresh + Marker | | |Cm|Size |Module id |Ch|--|Ix|Chksm| + |L4|Refresh + + Stop + 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 39 40 41 42 43 44 + aa 55 01 00 00 00 00 00 00 00 00 00 00 00 00 00 1b 00 17 8b 34 32 37 39 37 34 13 4b 35 36 37 03 01 07 e6 01 1c 0d 28 1f 03 23 21 04 04 - L4 Stop 2022-01-28T14:40:32 + Marker | | |Cm|Size |Module id |Ch|St|Year |Mo|Da|HH|MM|SS|Milli|Ix|Chksm| + |L4|--| 2022| 1|28|13|40|31| 803| + |Stop + */ + + SspmBuffer[16] = SSPM_FUNC_ENERGY_PERIOD; // 0x1B + +} + /*********************************************************************************************/ void SSPMHandleReceivedData(void) { @@ -732,6 +890,13 @@ void SSPMHandleReceivedData(void) { bool ack = (0x80 == SspmBuffer[15]); // Ac uint32_t command = SspmBuffer[16]; // Cm uint32_t expected_bytes = (SspmBuffer[17] << 8) + SspmBuffer[18]; // Size + // 0 - OK + // 1 - + // 2 - Timeout + // 3 - Log empty + // 4 - + // 5 - Out of command sync + uint32_t status = SspmBuffer[19]; // Status id expected_bytes is 1 uint32_t command_sequence = SspmBuffer[19 + expected_bytes]; // Ix // AddLog(LOG_LEVEL_DEBUG, PSTR("SPM: Rcvd ack %d, cmnd %d, seq %d, size %d"), @@ -739,38 +904,106 @@ void SSPMHandleReceivedData(void) { if (ack) { // Responses from ARM (Acked) + if (status > 0) { + AddLog(LOG_LEVEL_DEBUG, PSTR("SPM: Command %d result %d"), command, status); + } switch(command) { case SSPM_FUNC_FIND: /* 0x00 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 00 00 00 00 00 00 00 00 00 00 00 00 80 00 00 01 00 00 fc 73 - |Er| |St| + AA 55 01 00 00 00 00 00 00 00 00 00 00 00 00 80 00 00 00 00 22 50 -- SPI response + |Er|Cm|Size |St|Ix|Chksm| */ - if ((1 == expected_bytes) && (0 == SspmBuffer[19])) { + if (!status) { Sspm->mstate++; // Cycle to } break; + case SSPM_FUNC_SET_OPS: + /* 0x03 + 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 6b 7e 32 37 39 37 34 13 4b 35 36 37 80 03 00 01 00 fb 84 fd + Marker |Module id |Ac|Cm|Size |St|Ix|Chksm| + */ + if (!status && (Sspm->overload_relay < 255)) { + SSPMSendGetOps(Sspm->overload_relay >> 2); + } + break; case SSPM_FUNC_GET_OPS: /* 0x04 - Overload Protection 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 04 00 02 00 00 06 98 06 - Marker |Module id |Ac|Cm|Size | |Ix|Chksm| + Marker |Module id |Ac|Cm|Size |St| |Ix|Chksm| + 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 AA 55 01 6B 7E 32 37 39 37 34 13 4B 35 36 37 80 04 00 35 00 07 00 11 30 00 00 00 0A 00 F0 00 00 00 0A 00 14 00 00 + 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 00 11 30 00 00 00 0A 00 F0 00 00 00 0A 00 14 00 00 + 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 00 11 30 00 00 00 0A 00 F0 00 00 00 0A 00 14 00 00 07 8A 86 - Marker |Module id |Ac|Cm|Size | |Ch|Ra|Max P |Min P |Max U |Min U |Max I |De|Ix|Chksm| + Marker |Module id |Ac|Cm|Size |St|Ch|Ra|Max P |Min P |Max U |Min U |Max I |De|Ix|Chksm| | | | 4400W| 0.1W| 240V| 0.1V| 20A| | + Ch - Bitmask channel 01 = 1, 02 = 2, 04 = 3, 08 = 4 + Ra - Bitmask enabled features xxxxxxx1 Enable Max current + Ra - Bitmask enabled features xxxxxx1x Enable Min voltage + Ra - Bitmask enabled features xxxxx1xx Enable Max voltage + Ra - Bitmask enabled features xxxx1xxx Enable Min power + Ra - Bitmask enabled features xxx1xxxx Enable Max power + De - 0 to 255 seconds Overload detection delay */ - if (0x02 == expected_bytes) { - - } - - Sspm->module_selected--; - if (Sspm->module_selected > 0) { - SSPMSendGetModuleState(Sspm->module_selected -1); + if (!status && (Sspm->overload_relay < 255)) { + Response_P(PSTR("{\"SSPMOverload%d\":"), Sspm->overload_relay +1); + if (expected_bytes < 19) { + ResponseAppend_P(PSTR("\"None\"}")); + } else { + uint32_t module = SSPMGetModuleNumberFromMap(SspmBuffer[3] << 8 | SspmBuffer[4]); + uint32_t channels = SspmBuffer[20]; + uint32_t offset = 21; + for (uint32_t i = 0; i < 4; i++) { + if (channels & 1) { + uint32_t relay = (module * 4) +i; + if (Sspm->overload_relay == relay) { + Sspm->overload_enable = SspmBuffer[offset]; + uint32_t enabled = Sspm->overload_enable; + char bitmask[] = "00000"; + for (uint32_t j = 0; j < 5; j++) { + if (enabled & 1) { + if (0 == j) { bitmask[4] = '1'; } // MaxCurrent + if (1 == j) { bitmask[2] = '1'; } // MinVoltage + if (2 == j) { bitmask[3] = '1'; } // MaxVoltage + if (3 == j) { bitmask[0] = '1'; } // MinPower + if (4 == j) { bitmask[1] = '1'; } // MaxPower + } + enabled >>= 1; + } + Sspm->overload_max_power = SSPMGetValue(&SspmBuffer[offset +1]); // x.xxVA + Sspm->overload_min_power = SSPMGetValue(&SspmBuffer[offset +4]); // x.xxVA + Sspm->overload_max_voltage = SSPMGetValue(&SspmBuffer[offset +7]); // x.xxV + Sspm->overload_min_voltage = SSPMGetValue(&SspmBuffer[offset +10]); // x.xxV + Sspm->overload_max_current = SSPMGetValue(&SspmBuffer[offset +13]); // x.xxA + Sspm->overload_delay = SspmBuffer[offset +16]; + ResponseAppend_P(PSTR("{\"Delay\":%d,\"Set\":%s,\"MinPower\":%2_f,\"MaxPower\":%2_f,\"MinVoltage\":%2_f,\"MaxVoltage\":%2_f,\"MaxCurrent\":%2_f}}"), + Sspm->overload_delay, bitmask, + &Sspm->overload_min_power, &Sspm->overload_max_power, &Sspm->overload_min_voltage, &Sspm->overload_max_voltage, &Sspm->overload_max_current); + break; + } + offset += 17; + } + channels >>= 1; + } + if (!channels) { + ResponseAppend_P(PSTR("\"None\"}")); + } + } + MqttPublishPrefixTopicRulesProcess_P(RESULT_OR_STAT, PSTR("SSPMOverload")); + Sspm->overload_relay = 255; } else { - SSPMSendGetScheme(Sspm->module_selected); + Sspm->module_selected--; + if (Sspm->module_selected > 0) { + SSPMSendGetModuleState(Sspm->module_selected -1); + } else { + SSPMSendGetScheme(Sspm->module_selected); + } } break; case SSPM_FUNC_GET_MODULE_STATE: @@ -779,7 +1012,7 @@ void SSPMHandleReceivedData(void) { AA 55 01 8b 34 32 37 39 37 34 13 4b 35 36 37 80 09 00 06 00 0f 01 01 01 01 05 fe 35 |OS|4RelayMasks| */ - if (0x06 == expected_bytes) { + if (!status && (0x06 == expected_bytes)) { // SspmBuffer[20] & 0x0F // Relays operational power_t current_state = SspmBuffer[20] >> 4; // Relays state power_t mask = 0x0000000F; @@ -802,7 +1035,7 @@ void SSPMHandleReceivedData(void) { /* 0x0B 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 6b 7e 32 37 39 37 34 13 4b 35 36 37 80 0b 00 02 00 00 09 bb c7 - |?? ??| + |St|??| */ if (0x02 == expected_bytes) { @@ -833,11 +1066,10 @@ void SSPMHandleReceivedData(void) { /* 0x15 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 AA 55 01 00 00 00 00 00 00 00 00 00 00 00 00 80 15 00 04 00 01 00 00 01 81 b1 - |Ty|FwVersio| - | 0| 1.0.0| + |St|FwVersio| + | | 1.0.0| */ - AddLog(LOG_LEVEL_INFO, PSTR("SPM: Main type %d version %d.%d.%d found"), - SspmBuffer[19], SspmBuffer[20], SspmBuffer[21], SspmBuffer[22]); + AddLog(LOG_LEVEL_INFO, PSTR("SPM: Main version %d.%d.%d found"), SspmBuffer[20], SspmBuffer[21], SspmBuffer[22]); Sspm->mstate = SPM_START_SCAN; break; @@ -858,10 +1090,13 @@ void SSPMHandleReceivedData(void) { 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 42 67 46 + + 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 + AA 55 01 00 00 00 00 00 00 00 00 00 00 00 00 00 16 00 0D 6B 7E 32 37 39 37 34 13 4B 35 36 37 00 B4 0E 92 - L1 Response after midnight (out of sequence) + AA 55 01 00 00 00 00 00 00 00 00 00 00 00 00 00 16 00 0D 8B 34 32 37 39 37 34 13 4B 35 36 37 00 B5 24 54 - L5 Response after midnight (out of sequence) + AA 55 01 00 00 00 00 00 00 00 00 00 00 00 00 00 16 00 0D 8B 34 32 37 39 37 34 13 4B 35 36 37 01 B6 B5 15 - L6 Response after midnight (out of sequence) */ - if (expected_bytes < 24) { - AddLog(LOG_LEVEL_DEBUG, PSTR("SPM: History error%d"), SspmBuffer[19]); - } else { + if (expected_bytes > 24) { uint32_t entries = (expected_bytes - 22) / 2; // Find last valid (= non-zero) entry in 6 month fifo buffer uint16_t energy = 0; @@ -872,11 +1107,6 @@ void SSPMHandleReceivedData(void) { uint32_t channel = SspmBuffer[32]; uint32_t module = SSPMGetModuleNumberFromMap(SspmBuffer[20] << 8 | SspmBuffer[21]); -/* - if (!module && !channel) { - AddLog(LOG_LEVEL_DEBUG, PSTR("DBG: SSPMHistory1 '%50_H'"), SspmBuffer); - } -*/ if (Sspm->history_relay < 255) { uint32_t history_module = Sspm->history_relay >> 2; uint32_t history_channel = Sspm->history_relay & 0x03; // Channel relays are NOT bit masked this time @@ -887,7 +1117,8 @@ void SSPMHandleReceivedData(void) { } } - float last_energy_today = Sspm->energy_yesterday[module][channel]; + float last_energy_today = Sspm->energy_today[module][channel]; + float energy_yesterday = 0; float energy_total = 0; for (uint32_t i = 0; i <= entries; i++) { float energy = SspmBuffer[41 + (i*2)] + (float)SspmBuffer[42 + (i*2)] / 100; // x.xxkWh @@ -900,27 +1131,21 @@ void SSPMHandleReceivedData(void) { if (0 == i) { Sspm->energy_today[module][channel] = energy; } else { - if (1 == i) { Sspm->energy_yesterday[module][channel] = energy; } + if (1 == i) { energy_yesterday = energy; } energy_total += energy; } } if (0 == Sspm->Settings.energy_total[module][channel]) { - Sspm->Settings.energy_total[module][channel] = energy_total; // Initial setting + Sspm->Settings.energy_yesterday[module][channel] = energy_yesterday; // Inital setting + Sspm->Settings.energy_total[module][channel] = energy_total; // Initial setting } - // If received daily energy is below previous daily energy then update total energy + // If received daily energy is below last daily energy then update total energy // This happens around midnight in normal situations if (Sspm->energy_today[module][channel] < last_energy_today) { - Sspm->Settings.energy_total[module][channel] += last_energy_today; // Daily incremental save + Sspm->Settings.energy_yesterday[module][channel] = last_energy_today; // Daily save + Sspm->Settings.energy_total[module][channel] += last_energy_today; // Daily incremental save } -/* - if (255 == Sspm->yesterday) { Sspm->yesterday = SspmBuffer[36]; } // Initial setting - // If the day has changed then update total energy - if (Sspm->yesterday != SspmBuffer[36]) { // Next day - Sspm->Settings.energy_total[module][channel] += last_energy_today; // Daily incremental save - } - Sspm->yesterday = SspmBuffer[36]; -*/ Sspm->energy_total[module][channel] = Sspm->Settings.energy_total[module][channel] + Sspm->energy_today[module][channel]; if (Sspm->history_relay < 255) { @@ -932,8 +1157,16 @@ void SSPMHandleReceivedData(void) { Sspm->allow_updates = 1; } break; + case SSPM_FUNC_GET_ENERGY: + /* 0x18 + 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 00 00 00 00 00 00 00 00 00 00 00 00 80 18 00 01 00 15 31 92 + AA 55 01 00 00 00 00 00 00 00 00 00 00 00 00 80 18 00 01 05 85 0D 91 -- Error after midnight + Marker |Module id |Ac|Cm|Size |St|Ix|Chksm| + */ + break; case SSPM_FUNC_GET_LOG: - /* 0x1A + /* 0x1A - Module logging 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 AA 55 01 00 00 00 00 00 00 00 00 00 00 00 00 80 1a 01 3a 00 6b 7e 32 37 39 37 34 13 4b 35 36 37 1e Number of log entries (1e = 30) @@ -956,18 +1189,20 @@ void SSPMHandleReceivedData(void) { 07 e5 0b 06 0d 30 2d 03 00 01 09 89 fe Error: - AA 55 01 00 00 00 00 00 00 00 00 00 00 00 00 80 1A 00 01 03 E5 45 EB + AA 55 01 00 00 00 00 00 00 00 00 00 00 00 00 80 1A 00 01 03 E5 45 EB -- Log empty | | */ - if (Sspm->log_relay < 255) { + if ((!status || (3 == status)) && (Sspm->log_relay < 255)) { uint32_t module = Sspm->log_relay >> 2; uint32_t channel = Sspm->log_relay & 0x03; // Channel relays are NOT bit masked this time Response_P(PSTR("{\"SSPMLog%d\":"), Sspm->log_relay +1); - if (expected_bytes < 15) { - ResponseAppend_P(PSTR("\"Error%d\"}"), SspmBuffer[19]); - } else if (module != SSPMGetModuleNumberFromMap(SspmBuffer[20] << 8 | SspmBuffer[21])) { + if (module != SSPMGetModuleNumberFromMap(SspmBuffer[20] << 8 | SspmBuffer[21])) { ResponseAppend_P(PSTR("\"Wrong module\"}")); - } else { + } + else if (3 == status) { + ResponseAppend_P(PSTR("\"Empty\"}")); // Module log empty + } + else { uint32_t entries = SspmBuffer[32]; uint32_t offset = 33; bool more = false; @@ -987,21 +1222,28 @@ void SSPMHandleReceivedData(void) { if (more) { ResponseAppend_P(PSTR("]}")); }else { - ResponseAppend_P(PSTR("\"None\"}")); + ResponseAppend_P(PSTR("\"None\"}")); // Module log contains no logging for requested relay } } MqttPublishPrefixTopicRulesProcess_P(RESULT_OR_STAT, PSTR("SSPMLog")); - Sspm->log_relay = 255; // Disable display energy history } + Sspm->log_relay = 255; // Disable display energy history break; case SSPM_FUNC_ENERGY_PERIOD: /* 0x1B Response after start energy period - AA 55 01 00 00 00 00 00 00 00 00 00 00 00 00 80 1b 00 0e [00] 8b 34 32 37 39 37 34 13 4b 35 36 37 [03] f7 b1 bc L4 - Response after refresh or stop energy period - AA 55 01 00 00 00 00 00 00 00 00 00 00 00 00 80 1b 00 11 [00] 8b 34 32 37 39 37 34 13 4b 35 36 37 [03] [00 00 00] f8 94 15 L4, kWh start period (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 + aa 55 01 00 00 00 00 00 00 00 00 00 00 00 00 80 1b 00 0e 00 8b 34 32 37 39 37 34 13 4b 35 36 37 03 1a fc 7c - L4 + Marker |Module id |Ac|Cm|Size |St|Module Id |Ch|Ix|Chksm| + |OK| |L4| + Response after first auto-refresh AND following refreshes AND Stop + 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 00 00 00 00 00 00 00 00 00 00 00 00 80 1b 00 11 00 8b 34 32 37 39 37 34 13 4b 35 36 37 03 00 00 00 1b 1d 54 + aa 55 01 00 00 00 00 00 00 00 00 00 00 00 00 80 1b 00 11 00 8b 34 32 37 39 37 34 13 4b 35 36 37 03 00 00 01 20 5e 14 + Marker |Module id |Ac|Cm|Size |St|Module Id |Ch|Energy |Ix|Chksm| + |OK| |L4| 0.01kWh| + */ break; case SSPM_FUNC_RESET: /* 0x1C @@ -1035,10 +1277,10 @@ void SSPMHandleReceivedData(void) { } uint32_t module = SSPMGetModuleNumberFromMap(SspmBuffer[19] << 8 | SspmBuffer[20]); Sspm->current[module][channel] = SspmBuffer[32] + (float)SspmBuffer[33] / 100; // x.xxA - Sspm->voltage[module][channel] = (SspmBuffer[34] << 8) + SspmBuffer[35] + (float)SspmBuffer[36] / 100; // x.xxV - Sspm->active_power[module][channel] = (SspmBuffer[37] << 8) + SspmBuffer[38] + (float)SspmBuffer[39] / 100; // x.xxW - Sspm->reactive_power[module][channel] = (SspmBuffer[40] << 8) + SspmBuffer[41] + (float)SspmBuffer[42] / 100; // x.xxVAr - Sspm->apparent_power[module][channel] = (SspmBuffer[43] << 8) + SspmBuffer[44] + (float)SspmBuffer[45] / 100; // x.xxVA + Sspm->voltage[module][channel] = SSPMGetValue(&SspmBuffer[34]); // x.xxV + Sspm->active_power[module][channel] = SSPMGetValue(&SspmBuffer[37]); // x.xxW + Sspm->reactive_power[module][channel] = SSPMGetValue(&SspmBuffer[40]); // x.xxVAr + Sspm->apparent_power[module][channel] = SSPMGetValue(&SspmBuffer[43]); // x.xxVA float power_factor = (Sspm->active_power[module][channel] && Sspm->apparent_power[module][channel]) ? Sspm->active_power[module][channel] / Sspm->apparent_power[module][channel] : 0; if (power_factor > 1) { power_factor = 1; } Sspm->power_factor[module][channel] = power_factor; @@ -1051,6 +1293,9 @@ 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 24 25 26 27 28 29 30 31 32 33 34 AA 55 01 00 00 00 00 00 00 00 00 00 00 00 00 00 07 00 0d 6b 7e 32 37 39 37 34 13 4b 35 36 37 11 04 bf c3 |AS| + AA 55 01 00 00 00 00 00 00 00 00 00 00 00 00 00 07 00 0D 6B 7E 32 37 39 37 34 13 4B 35 36 37 01 22 A5 4F - L1 Overload triggered power off + + aa 55 01 00 00 00 00 00 00 00 00 00 00 00 00 00 07 00 0d 8b 34 32 37 39 37 34 13 4b 35 36 37 02 2d bb 08 - L2 Overload triggered power off */ if (!Sspm->no_send_key) { power_t relay = SspmBuffer[31] & 0x0F; // Relays active @@ -1076,8 +1321,59 @@ void SSPMHandleReceivedData(void) { break; case SSPM_FUNC_SCAN_START: /* 0x0F - AA 55 01 00 00 00 00 00 00 00 00 00 00 00 00 00 0f 00 01 02 01 9d f8 + 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 00 00 00 00 00 00 00 00 00 00 00 00 00 0f 00 01 02 01 9d f8 - Response after normal scan start + AA 55 01 00 00 00 00 00 00 00 00 00 00 00 00 00 0F 00 01 02 01 9D F8 - Response after midnight - notice reset of sequence number + Marker | |Ac|Cm|Size |St|Ix|Chksm| + + 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 39 40 41 + AA 55 01 00 00 00 00 00 00 00 00 00 00 00 00 00 0F 00 14 00 6B 7E 32 37 39 37 34 13 4B 35 36 37 00 00 00 01 00 00 00 23 9D EF - Response after L1 max_power overload powered off + AA 55 01 00 00 00 00 00 00 00 00 00 00 00 00 00 0F 00 14 00 8B 34 32 37 39 37 34 13 4B 35 36 37 00 00 02 00 00 00 00 98 AC 8B - Response after L6 max_current overload powered off + AA 55 01 00 00 00 00 00 00 00 00 00 00 00 00 00 0F 00 14 00 8B 34 32 37 39 37 34 13 4B 35 36 37 00 02 00 00 00 00 00 94 8B A9 - Response after L6 max_voltage overload powered off + AA 55 01 00 00 00 00 00 00 00 00 00 00 00 00 00 0F 00 14 00 8B 34 32 37 39 37 34 13 4B 35 36 37 00 20 00 00 00 00 00 81 46 6A - Response after L6 min_voltage overload powered off + aa 55 01 00 00 00 00 00 00 00 00 00 00 00 00 00 0f 00 14 00 8b 34 32 37 39 37 34 13 4b 35 36 37 00 00 00 00 00 00 00 2f 38 ca - After power on and overload was disabled + aa 55 01 00 00 00 00 00 00 00 00 00 00 00 00 00 0f 00 14 00 8b 34 32 37 39 37 34 13 4b 35 36 37 80 00 00 00 00 00 00 04 47 82 - At 02:50:24 + aa 55 01 00 00 00 00 00 00 00 00 00 00 00 00 00 0f 00 14 00 8b 34 32 37 39 37 34 13 4b 35 36 37 00 00 00 00 00 00 00 05 e7 4b - At 02:50:30 + aa 55 01 00 00 00 00 00 00 00 00 00 00 00 00 00 0f 00 14 00 8b 34 32 37 39 37 34 13 4b 35 36 37 20 00 00 00 00 00 00 06 fe 09 - At 08:40:52 + aa 55 01 00 00 00 00 00 00 00 00 00 00 00 00 00 0f 00 14 00 8b 34 32 37 39 37 34 13 4b 35 36 37 00 00 00 00 00 00 00 07 26 ca - At 08:40:58 + Marker | |Ac|Cm|Size |St|Module id | |Vo|Cu|Po| | | |Ix|Chksm| + 32..38 - Bitmask channel 01 = 1, 02 = 2, 04 = 3, 08 = 4 (Max border) + 32..38 - Bitmask channel 10 = 1, 20 = 2, 40 = 3, 80 = 4 (Min border) + Cu - Current + Vo - Voltage + Po - Power + Ot - Overtemp */ + if (status > 0) { + AddLog(LOG_LEVEL_DEBUG, PSTR("SPM: Command %d result %d"), command, status); + } + else if (0x14 == expected_bytes) { // Overload/Overtemp triggered + uint32_t any_bit_set = 0; + for (uint32_t i = 0; i < 7; i++) { + any_bit_set += SspmBuffer[32 +i]; // Overload triggered channel bits + } + if (any_bit_set) { // Signals all is OK again if NOT set + uint32_t module = SSPMGetModuleNumberFromMap(SspmBuffer[20] << 8 | SspmBuffer[21]); + bool more = false; + char border[2][4] = { "Max","Min" }; + char stemp[10]; // "Tbd1|Voltage|Current|Power|Tbd2|Tbd3|Tbd4" + Response_P(PSTR("{\"SSPMOverload\":")); + for (uint32_t i = 0; i < 7; i++) { + uint32_t channel = SspmBuffer[32 +i]; + for (uint32_t j = 0; j < 8; j++) { + if (channel &1) { + uint32_t relay = (module << 2) +(j & 3); + uint32_t idx = (j >> 2) & 1; + ResponseAppend_P(PSTR("%s{\"L%d\":\"%s%s\"}"), (more)?",":"[", relay +1, border[idx], GetTextIndexed(stemp, sizeof(stemp), i, kSSPMOverload)); + more = true; + } + channel >>= 1; + } + } + ResponseAppend_P(PSTR("]}")); + MqttPublishPrefixTopicRulesProcess_P(RESULT_OR_STAT, PSTR("SSPMOverload")); + } + } SSPMSendAck(command_sequence); break; case SSPM_FUNC_SCAN_RESULT: @@ -1086,11 +1382,19 @@ void SSPMHandleReceivedData(void) { AA 55 01 00 00 00 00 00 00 00 00 00 00 00 00 00 13 00 24 6b 7e 32 37 39 37 34 13 4b 35 36 37 04 00 00 00 82 01 00 00 14 00 00 0a 00 f0 00 00 00 0a 11 30 00 00 00 0a 02 8f cd AA 55 01 00 00 00 00 00 00 00 00 00 00 00 00 00 13 00 24 8b 34 32 37 39 37 34 13 4b 35 36 37 04 00 00 00 82 01 00 00 14 00 00 0a 00 f0 00 00 00 0a 11 30 00 00 00 0a 02 a0 6f Marker | |Ac|Cm|Size |Module id |Ch| |Ty|FwVersio|Max I|Min I|Max U |Min U |Max P |Min P |Ix|Chksm| - |130| 1.0.0| 20A| 0.1A| 240V| 0.1V| 4400W| 0.1W| + |130| 1.0.0|20.0A|0.10A| 240.00V| 0.10V|4400.00W| 0.10W| Ty = Type of sub-device. 130: Four-channel sub-device */ if ((0x24 == expected_bytes) && (Sspm->module_max < SSPM_MAX_MODULES)) { memcpy(Sspm->module[Sspm->module_max], SspmBuffer + 19, SSPM_MODULE_NAME_SIZE); + if (0 == Sspm->max_power) { + Sspm->max_current = SspmBuffer[39] + (float)SspmBuffer[40] / 100; // x.xxA + Sspm->min_current = SspmBuffer[41] + (float)SspmBuffer[42] / 100; // x.xxA + Sspm->max_power = SSPMGetValue(&SspmBuffer[49]); // x.xxVA + Sspm->min_power = SSPMGetValue(&SspmBuffer[52]); // x.xxVA + Sspm->max_voltage = SSPMGetValue(&SspmBuffer[43]); // x.xxV + Sspm->min_voltage = SSPMGetValue(&SspmBuffer[46]); // x.xxV + } uint32_t module_id = SspmBuffer[19] << 8 | SspmBuffer[20]; if (0 == Sspm->Settings.module_map[Sspm->module_max]) { Sspm->Settings.module_map[Sspm->module_max] = module_id; @@ -1102,25 +1406,6 @@ void SSPMHandleReceivedData(void) { Sspm->module_max +1, mapped, SspmBuffer[35], SspmBuffer[36], SspmBuffer[37], SspmBuffer[38], Sspm->module[Sspm->module_max]); Sspm->module_max++; -/* - // Debugging module removal/addition/change (exception 3) - if ((1 == Sspm->module_max) && (Settings->flag3.user_esp8285_enable)) { // SO51 - SspmBuffer[19] = 0x4B; - SspmBuffer[20] = 0x62; - memcpy(Sspm->module[Sspm->module_max], SspmBuffer + 19, SSPM_MODULE_NAME_SIZE); - module_id = SspmBuffer[19] << 8 | SspmBuffer[20]; - if (0 == Sspm->Settings.module_map[Sspm->module_max]) { - Sspm->Settings.module_map[Sspm->module_max] = module_id; - } - mapped = SSPMGetModuleNumberFromMapIfFound(module_id); - if (-1 == mapped) { Sspm->map_change = true; } - mapped++; - 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++; - } -*/ } SSPMSendAck(command_sequence); break; @@ -1130,40 +1415,52 @@ void SSPMHandleReceivedData(void) { */ SSPMSendAck(command_sequence); - // Warn for mapping change when removal or addition of 4Relay modules - for (uint32_t module = Sspm->module_max; module < SSPM_MAX_MODULES; module++) { - // Clear obsolete mapping slots - Sspm->Settings.module_map[module] = 0; - } - for (uint32_t module = 0; module < Sspm->module_max; module++) { - // Remove not scanned module (probably physical removed) from mapping - if (-1 == SSPMGetMappedModuleIdIfFound(module)) { - // Clear mapping slot + if (Sspm->module_max) { + // Warn for mapping change when removal or addition of 4Relay modules + for (uint32_t module = Sspm->module_max; module < SSPM_MAX_MODULES; module++) { + // Clear obsolete mapping slots Sspm->Settings.module_map[module] = 0; - Sspm->map_change = true; } - } - for (uint32_t module = 0; module < Sspm->module_max; module++) { - // Add scanned module to mapping - uint32_t module_id = SSMPGetModuleId(module); - if (-1 == SSPMGetModuleNumberFromMapIfFound(module_id)) { - // Scanned module not in mapping list (probably due to physical install) - for (uint32_t i = 0; i < Sspm->module_max; i++) { - // Find empty slot in mapping and insert - if (0 == Sspm->Settings.module_map[i]) { - Sspm->Settings.module_map[i] = module_id; - Sspm->map_change = true; - break; + for (uint32_t module = 0; module < Sspm->module_max; module++) { + // Remove not scanned module (probably physical removed) from mapping + if (-1 == SSPMGetMappedModuleIdIfFound(module)) { + // Clear mapping slot + Sspm->Settings.module_map[module] = 0; + Sspm->map_change = true; + } + } + for (uint32_t module = 0; module < Sspm->module_max; module++) { + // Add scanned module to mapping + uint32_t module_id = SSMPGetModuleId(module); + if (-1 == SSPMGetModuleNumberFromMapIfFound(module_id)) { + // Scanned module not in mapping list (probably due to physical install) + for (uint32_t i = 0; i < Sspm->module_max; i++) { + // Find empty slot in mapping and insert + if (0 == Sspm->Settings.module_map[i]) { + Sspm->Settings.module_map[i] = module_id; + Sspm->map_change = true; + break; + } } } } - } - if (Sspm->map_change) { - Sspm->map_change = false; - AddLog(LOG_LEVEL_INFO, PSTR("SPM: WARNING 4Relay mapping possibly changed")); + if (Sspm->map_change) { + Sspm->map_change = false; + AddLog(LOG_LEVEL_INFO, PSTR("SPM: WARNING 4Relay mapping possibly changed")); + } + + Sspm->module_selected = Sspm->module_max; + } else { + for (uint32_t module = 0; module < SSPM_MAX_MODULES; module++) { + // Clear mapping slots + Sspm->Settings.module_map[module] = 0; + memset(Sspm->module[module], 0, SSPM_MODULE_NAME_SIZE); + } + AddLog(LOG_LEVEL_DEBUG, PSTR("SPM: Relay scan done - none found")); + + Sspm->mstate = SPM_NONE; } - Sspm->module_selected = Sspm->module_max; SSPMSendSetTime(); break; } @@ -1186,19 +1483,28 @@ void SSPMSerialInput(void) { yield(); uint8_t serial_in_byte = SspmSerial->read(); - if ((0x55 == serial_in_byte) && (0xAA == SspmBuffer[Sspm->serial_in_byte_counter -1])) { + if ((0x01 == serial_in_byte) && (0x55 == SspmBuffer[Sspm->serial_in_byte_counter -1]) && (0xAA == SspmBuffer[Sspm->serial_in_byte_counter -2])) { + + if (Sspm->serial_in_byte_counter > 2) { + AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("SPM: ARM out of sync %*_H"), Sspm->serial_in_byte_counter, SspmBuffer); + } + + // Start of message Sspm->expected_bytes = 0; SspmBuffer[0] = 0xAA; - Sspm->serial_in_byte_counter = 1; + SspmBuffer[1] = 0x55; + Sspm->serial_in_byte_counter = 2; } if (Sspm->serial_in_byte_counter < SSPM_SERIAL_BUFFER_SIZE -1) { SspmBuffer[Sspm->serial_in_byte_counter++] = serial_in_byte; - if ((0xAA == SspmBuffer[0]) && (0x55 == SspmBuffer[1]) && (19 == Sspm->serial_in_byte_counter)) { + if ((19 == Sspm->serial_in_byte_counter) && (0xAA == SspmBuffer[0]) && (0x55 == SspmBuffer[1]) && (0x01 == SspmBuffer[2])) { + // Message size known Sspm->expected_bytes = 22 + (SspmBuffer[17] << 8) + SspmBuffer[18]; } if (Sspm->serial_in_byte_counter == Sspm->expected_bytes) { - - AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("SPM: ARM %*_H"), Sspm->serial_in_byte_counter, SspmBuffer); + // Complete message received + bool more = (!Sspm->Settings.flag.dump && (Sspm->serial_in_byte_counter > 58)); // Skip long dumps as they overwrite log buffer + AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("SPM: ARM %*_H%s"), (more) ? 58 : Sspm->serial_in_byte_counter, SspmBuffer, (more) ? "..." : ""); uint16_t crc_rcvd = (SspmBuffer[Sspm->serial_in_byte_counter -2] << 8) + SspmBuffer[Sspm->serial_in_byte_counter -1]; uint16_t crc_calc = SSPMCalculateCRC(SspmBuffer, Sspm->serial_in_byte_counter -2); @@ -1221,6 +1527,108 @@ void SSPMSerialInput(void) { /*********************************************************************************************/ +bool SSPMSendSPI(uint32_t size) { + uint16_t crc = SSPMCalculateCRC(SspmBuffer, size -2); + SspmBuffer[size -2] = (uint8_t)(crc >> 8); + SspmBuffer[size -1] = (uint8_t)crc; + + SPI.beginTransaction(SPISettings(10000000, MSBFIRST, SPI_MODE0)); // Set up SPI at 10MHz, MSB first, Capture at rising edge + + // 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 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 FC 51 + // Marker |Module id |Ac|Cm|Size |Ix|Chksm| + SPI.writeBytes(SspmBuffer, size); // Send data + + delayMicroseconds(600); // Wait for receipt delay deduced from initial SPM SPI comms + + // 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 00 00 00 00 00 00 00 00 00 00 00 00 80 00 00 00 00 22 50 + // Marker |Module id |Ac|Cm|Size |Ix|Chksm| + bool crc_ok = false; + + uint32_t expected_bytes = 20; // Location of expected bytes (Size) - This works fine + SPI.transferBytes(nullptr, SspmBuffer, expected_bytes); // First receive data up to known location of expected bytes + if ((0xAA == SspmBuffer[0]) && (0x55 == SspmBuffer[1]) && (0x01 == SspmBuffer[2])) { + // Message size known + expected_bytes = 2 + (SspmBuffer[17] << 8) + SspmBuffer[18]; + if (expected_bytes < SSPM_SERIAL_BUFFER_SIZE - 20) { + SPI.transferBytes(nullptr, SspmBuffer +20, expected_bytes); // Then receive the expected bytes + // Complete message received + expected_bytes += 20; + +/* + uint32_t expected_bytes = 19; // Location of expected bytes (Size) - This fails probably because not on 4-byte boundary (See __spiTransferBytes) + SPI.transferBytes(nullptr, SspmBuffer, expected_bytes); // First receive data up to known location of expected bytes + if ((0xAA == SspmBuffer[0]) && (0x55 == SspmBuffer[1]) && (0x01 == SspmBuffer[2])) { + // Message size known + expected_bytes = 3 + (SspmBuffer[17] << 8) + SspmBuffer[18]; + if (expected_bytes < SSPM_SERIAL_BUFFER_SIZE - 19) { + SPI.transferBytes(nullptr, SspmBuffer +19, expected_bytes); // Then receive the expected bytes + // Complete message received + expected_bytes += 19; +*/ +/* + SPI.transferBytes(nullptr, SspmBuffer, 3); // First receive marker data - This fails probably because not on 4-byte boundary (See __spiTransferBytes) + if ((0xAA == SspmBuffer[0]) && (0x55 == SspmBuffer[1]) && (0x01 == SspmBuffer[2])) { + SPI.transferBytes(nullptr, SspmBuffer +3, 16); // Then receive data up to known location of expected bytes (Size at index 17 and 18) + // Message size known + uint32_t expected_bytes = 3 + (SspmBuffer[17] << 8) + SspmBuffer[18]; + if (expected_bytes < SSPM_SERIAL_BUFFER_SIZE - 19) { + SPI.transferBytes(nullptr, SspmBuffer +19, expected_bytes); // Then receive the expected bytes + // Complete message received + expected_bytes += 19; +*/ +/* + SPI.transferBytes(nullptr, SspmBuffer, 4); // First receive marker data - This fails too + if ((0xAA == SspmBuffer[0]) && (0x55 == SspmBuffer[1]) && (0x01 == SspmBuffer[2])) { + SPI.transferBytes(nullptr, SspmBuffer +4, 16); // Then receive data up to known location of expected bytes (Size at index 17 and 18) + // Message size known + uint32_t expected_bytes = 2 + (SspmBuffer[17] << 8) + SspmBuffer[18]; + if (expected_bytes < SSPM_SERIAL_BUFFER_SIZE - 20) { + SPI.transferBytes(nullptr, SspmBuffer +20, expected_bytes); // Then receive the expected bytes + // Complete message received + expected_bytes += 20; +*/ + bool more = (!Sspm->Settings.flag.dump && (expected_bytes > 58)); // Skip long dumps as they overwrite log buffer + AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("SPM: SPI %*_H%s"), (more) ? 58 : expected_bytes, SspmBuffer, (more) ? "..." : ""); + + uint16_t crc_rcvd = (SspmBuffer[expected_bytes -2] << 8) + SspmBuffer[expected_bytes -1]; + uint16_t crc_calc = SSPMCalculateCRC(SspmBuffer, expected_bytes -2); + crc_ok = (crc_rcvd == crc_calc); + if (crc_ok) { +// SSPMHandleReceivedData(); + } else { + Sspm->error_led_blinks = 20; + AddLog(LOG_LEVEL_DEBUG, PSTR("SPM: SPI CRC error")); + } + } else { + AddLog(LOG_LEVEL_DEBUG, PSTR("SPM: SPI buffer overflow")); + } + } + SPI.endTransaction(); // Stop SPI transaction + + return crc_ok; +} + +bool SSPMSendSPIFind(void) { + // Send + // 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 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 FC 51 every 600uSecs + // Wait for + // AA 55 01 00 00 00 00 00 00 00 00 00 00 00 00 80 00 00 01 00 00 FC 73 from ARM + // or + // AA 55 01 00 00 00 00 00 00 00 00 00 00 00 00 80 00 00 00 00 22 50 from ARM (No flash) + for (uint32_t i = 0; i < 40; i++) { + SSPMInitSend(); + SspmBuffer[19] = SSPM_FUNC_FIND; + if (SSPMSendSPI(22)) { return true; } + } + + return false; +} + +/*********************************************************************************************/ + void SSPMInit(void) { if (!ValidTemplate(PSTR("Sonoff SPM")) || !PinUsed(GPIO_RXD) || !PinUsed(GPIO_TXD)) { return; } @@ -1246,6 +1654,12 @@ void SSPMInit(void) { pinMode(SSPM_GPIO_LED_ERROR, OUTPUT); digitalWrite(SSPM_GPIO_LED_ERROR, 0); + if (TasmotaGlobal.spi_enabled) { + SPI.begin(Pin(GPIO_SPI_CLK), Pin(GPIO_SPI_MISO), Pin(GPIO_SPI_MOSI), -1); + } else { + SPI.begin(14, 12, 13, -1); + } + if (0 == Settings->flag2.voltage_resolution) { Settings->flag2.voltage_resolution = 1; // SPM has 2 decimals but this keeps the gui clean Settings->flag2.current_resolution = 2; // SPM has 2 decimals @@ -1261,7 +1675,7 @@ void SSPMInit(void) { #endif #endif - Sspm->yesterday = 255; // Not initialized + Sspm->overload_relay = 255; // Disable display overload settings Sspm->history_relay = 255; // Disable display energy history Sspm->log_relay = 255; // Disable display logging Sspm->old_power = TasmotaGlobal.power; @@ -1317,9 +1731,16 @@ void SSPMEvery100ms(void) { // Wait for first acknowledge from ARM after reset SSPMSendCmnd(SSPM_FUNC_FIND); break; + case SPM_POLL_ARM_SPI: + SSPMSendSPIFind(); + Sspm->mstate = SPM_POLL_ARM_2; + break; case SPM_POLL_ARM_2: - // Wait for second acknowledge from ARM after reset SSPMSendCmnd(SSPM_FUNC_FIND); + Sspm->mstate = SPM_POLL_ARM_3; + break; + case SPM_POLL_ARM_3: + // Wait for second acknowledge from ARM after reset break; case SPM_SEND_FUNC_UNITS: // Get number of units @@ -1327,6 +1748,10 @@ void SSPMEvery100ms(void) { break; case SPM_START_SCAN: // Start scan module sequence + Sspm->error_led_blinks = 0; // Reset error light + Sspm->overload_relay = 255; // Disable display overload settings + Sspm->history_relay = 255; // Disable display energy history + Sspm->log_relay = 255; // Disable display logging Sspm->module_max = 0; SSPMSendInitScan(); Sspm->mstate = SPM_WAIT_FOR_SCAN; @@ -1362,36 +1787,54 @@ void SSPMEvery100ms(void) { case SPM_UPDATE_CHANNELS: // Retrieve Energy status from up to 128 powered on relays (takes 128 * 2s!!) if (Sspm->allow_updates) { - Sspm->get_energy_relay++; - if (Sspm->get_energy_relay >= TasmotaGlobal.devices_present) { - Sspm->get_energy_relay = 0; - if (Sspm->last_totals > 1200) { // Get totals every 2 minutes (takes 128 * 0.2s) - Sspm->last_totals = 0; - Sspm->get_totals = 1; - } else { - Sspm->get_totals = 0; - } - } - power_t powered_on = TasmotaGlobal.power >> Sspm->get_energy_relay; - if (powered_on &1) { - SSPMSetLock(4); - if (Sspm->get_totals) { - SSPMSendGetEnergyTotal(Sspm->get_energy_relay); - } else { - SSPMSendGetEnergy(Sspm->get_energy_relay); - } + + // Test if this solves midnight timeout + + int32_t time = (RtcTime.hour *3600) + (RtcTime.minute *60) + RtcTime.second; + if ((time > 86370) || (time < 30)) { // Between 00:00:29 and 23:59:31 stall updates to satisfy ARM firmware + Sspm->get_energy_relay = TasmotaGlobal.devices_present; + Sspm->last_totals = 0; } else { - uint32_t module = Sspm->get_energy_relay >> 2; - uint32_t channel = Sspm->get_energy_relay &3; - if (Sspm->voltage[module][channel]) { - Sspm->voltage[module][channel] = 0; - Sspm->current[module][channel] = 0; - Sspm->active_power[module][channel] = 0; - Sspm->apparent_power[module][channel] = 0; - Sspm->reactive_power[module][channel] = 0; - Sspm->power_factor[module][channel] = 0; +/* + int32_t time = (RtcTime.hour *60) + RtcTime.minute; + if ((time > 1433) || (time < 1)) { // Between 23:54 and 01:00 stall updates to satisfy ARM firmware (flash writes at 23:55) + Sspm->get_energy_relay = TasmotaGlobal.devices_present; + Sspm->last_totals = 0; + } else { +*/ + Sspm->get_energy_relay++; + if (Sspm->get_energy_relay >= TasmotaGlobal.devices_present) { + Sspm->get_energy_relay = 0; + if (Sspm->last_totals > 1200) { // Get totals every 2 minutes (takes 128 * 0.2s) + Sspm->last_totals = 0; + Sspm->get_totals = 1; + } else { + Sspm->get_totals = 0; + } } + power_t powered_on = TasmotaGlobal.power >> Sspm->get_energy_relay; + if (powered_on &1) { + SSPMSetLock(4); + if (Sspm->get_totals) { + SSPMSendGetEnergyTotal(Sspm->get_energy_relay); + } else { + SSPMSendGetEnergy(Sspm->get_energy_relay); + } + } else { + uint32_t module = Sspm->get_energy_relay >> 2; + uint32_t channel = Sspm->get_energy_relay &3; + if (Sspm->voltage[module][channel]) { + Sspm->voltage[module][channel] = 0; + Sspm->current[module][channel] = 0; + Sspm->active_power[module][channel] = 0; + Sspm->apparent_power[module][channel] = 0; + Sspm->reactive_power[module][channel] = 0; + Sspm->power_factor[module][channel] = 0; + } + } + } + } break; @@ -1467,7 +1910,7 @@ void SSPMEnergyShow(bool json) { #ifdef SSPM_JSON_ENERGY_YESTERDAY ResponseAppend_P(PSTR("],\"" D_JSON_YESTERDAY "\":[")); for (uint32_t i = 0; i < TasmotaGlobal.devices_present; i++) { - ResponseAppend_P(PSTR("%s%*_f"), (i>0)?",":"", Settings->flag2.energy_resolution, &Sspm->energy_yesterday[i >>2][i &3]); + ResponseAppend_P(PSTR("%s%*_f"), (i>0)?",":"", Settings->flag2.energy_resolution, &Sspm->Settings.energy_yesterday[i >>2][i &3]); } #endif #ifdef SSPM_JSON_ENERGY_TODAY @@ -1508,8 +1951,8 @@ void SSPMEnergyShow(bool json) { uint32_t index = 0; power_t power = TasmotaGlobal.power; for (uint32_t i = 0; i < TasmotaGlobal.devices_present; i++) { - if ((0 == Settings->sbflag1.sspm_display) || - ((1 == Settings->sbflag1.sspm_display) && (power >> i) &1)) { + if ((0 == Sspm->Settings.flag.display) || + ((1 == Sspm->Settings.flag.display) && (power >> i) &1)) { relay[index] = i +1; indirect[index] = i; index++; @@ -1543,7 +1986,7 @@ void SSPMEnergyShow(bool json) { SSPMEnergyFormat(valu2_chr, Sspm->reactive_power[0], Settings->flag2.wattage_resolution, indirect, offset, count), SSPMEnergyFormat(valu3_chr, Sspm->power_factor[0], 2, indirect, offset, count)); WSContentSend_PD(HTTP_SSPM_ENERGY, SSPMEnergyFormat(value_chr, Sspm->energy_today[0], Settings->flag2.energy_resolution, indirect, offset, count), - SSPMEnergyFormat(valu2_chr, Sspm->energy_yesterday[0], Settings->flag2.energy_resolution, indirect, offset, count), + SSPMEnergyFormat(valu2_chr, Sspm->Settings.energy_yesterday[0], Settings->flag2.energy_resolution, indirect, offset, count), SSPMEnergyFormat(valu3_chr, Sspm->energy_total[0], Settings->flag2.energy_resolution, indirect, offset, count)); WSContentSend_P(PSTR("{t}")); // {t} = - Define for next FUNC_WEB_SENSOR } @@ -1555,50 +1998,176 @@ void SSPMEnergyShow(bool json) { \*********************************************************************************************/ const char kSSPMCommands[] PROGMEM = "SSPM|" // Prefix - "Log|Energy|History|Scan|IamHere|Display|Reset|Map|" D_CMND_ENERGYTOTAL; + "Display|Dump|" // SetOptions + "Log|Energy|History|Scan|IamHere|" + "Reset|Map|Overload|" + D_CMND_ENERGYTOTAL "|" D_CMND_ENERGYYESTERDAY; void (* const SSPMCommand[])(void) PROGMEM = { + &CmndSSPMDisplay, &CmndSSPMDump, &CmndSSPMLog, &CmndSSPMEnergy, &CmndSSPMHistory, &CmndSSPMScan, &CmndSSPMIamHere, - &CmndSSPMDisplay, &CmndSSPMReset, &CmndSSPMMap, &CmndSpmEnergyTotal }; + &CmndSSPMReset, &CmndSSPMMap, &CmndSSPMOverload, + &CmndSpmEnergyTotal, &CmndSpmEnergyYesterday }; + +void CmndSSPMDisplay(void) { + // Select either all relays or only powered on relays + // SspmDisplay 0 or SspmDisplay 1 + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 1)) { + Sspm->Settings.flag.display = XdrvMailbox.payload; + } + ResponseCmndNumber(Sspm->Settings.flag.display); +} + +void CmndSSPMDump(void) { + // Select either short or full serial dump controlling logging buffer space + // SspmDump 0 or SspmDump 1 + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 1)) { + Sspm->Settings.flag.dump = XdrvMailbox.payload; + } + ResponseCmndNumber(Sspm->Settings.flag.dump); +} void CmndSpmEnergyTotal(void) { // Reset Energy Total // SspmEnergyTotal 0 - Set total energy from midnight with sum of last month history // SspmEnergyTotal 4.23 - Set total energy from midnight (without today's energy) - uint32_t relay = XdrvMailbox.index -1; - uint32_t module = relay >> 2; - uint32_t channel = relay & 0x03; - if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= TasmotaGlobal.devices_present) && XdrvMailbox.data_len) { - Sspm->Settings.energy_total[module][channel] = CharToFloat(XdrvMailbox.data); - Sspm->energy_total[module][channel] = Sspm->Settings.energy_total[module][channel] + Sspm->energy_today[module][channel]; + if (Sspm->module_max) { + uint32_t relay = XdrvMailbox.index -1; + uint32_t module = relay >> 2; + uint32_t channel = relay & 0x03; + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= TasmotaGlobal.devices_present) && XdrvMailbox.data_len) { + Sspm->Settings.energy_total[module][channel] = CharToFloat(XdrvMailbox.data); + Sspm->energy_total[module][channel] = Sspm->Settings.energy_total[module][channel] + Sspm->energy_today[module][channel]; + } + ResponseCmndFloat(Sspm->energy_total[module][channel], Settings->flag2.energy_resolution); + } +} + +void CmndSpmEnergyYesterday(void) { + // Reset Energy Yesterday + // SspmEnergyTotal 0 - Set total energy from midnight with sum of last month history + // SspmEnergyTotal 4.23 - Set total energy from midnight (without today's energy) + if (Sspm->module_max) { + uint32_t relay = XdrvMailbox.index -1; + uint32_t module = relay >> 2; + uint32_t channel = relay & 0x03; + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= TasmotaGlobal.devices_present) && XdrvMailbox.data_len) { + Sspm->Settings.energy_yesterday[module][channel] = CharToFloat(XdrvMailbox.data); + } + ResponseCmndFloat(Sspm->Settings.energy_yesterday[module][channel], Settings->flag2.energy_resolution); + } +} + +void CmndSSPMOverload(void) { + // Get / Set overload + // SspmOverload 0 - Reset overload detection parameters + // SspmOverload {"Delay":0,"Set":00000,"MinPower":0.10,"MaxPower":4400.00,"MinVoltage":0.10,"MaxVoltage":240.00,"MaxCurrent":20.00} + // SspmOverload ,,,, + // SspmOverload 10,0.10,4400.00,0.10,240.00,20.00 + // SspmOverload 10 0.10 4400.00 0.10 240.00 20.00 + // SspmOverload 10,12.3 - Enable overload detection after 10 seconds for MinPower + // SspmOverload 10,0,22.2 - Enable overload detection after 10 seconds for MaxPower + // SspmOverload 10,0,0,0,235.2 - Enable overload detection after 10 seconds for MaxVoltage + if (Sspm->module_max) { + if ((XdrvMailbox.index < 1) || (XdrvMailbox.index > TasmotaGlobal.devices_present)) { XdrvMailbox.index = 1; } + Sspm->overload_relay = XdrvMailbox.index -1; + bool set_overload = false; + if (XdrvMailbox.data_len) { + // Init defaults + Sspm->overload_delay = 0; // No delay + Sspm->overload_enable = 0x00; // Disable overload detection + Sspm->overload_max_power = Sspm->max_power; // x.xxVA + Sspm->overload_min_power = Sspm->min_power; // x.xxVA + Sspm->overload_max_voltage = Sspm->max_voltage; // x.xxV + Sspm->overload_min_voltage = Sspm->min_voltage; // x.xxV + Sspm->overload_max_current = Sspm->max_current; // x.xxA + if ((1 == XdrvMailbox.data_len) && (0 == XdrvMailbox.payload)) { // Set defaults + // SspmOverload 0 - Reset overload detection parameters + set_overload = true; + } + else if ('{' == XdrvMailbox.data[0]) { // Process as JSON + // SspmOverload {"Delay":0,"Set":00000,"MinPower":0.10,"MaxPower":4400.00,"MinVoltage":0.10,"MaxVoltage":240.00,"MaxCurrent":20.00} + + // set_overload = true; + } + else if (strchr(XdrvMailbox.data, ',') != nullptr) { + // SspmOverload 10,0.10,4400.00,0.10,240.00,20.00 + // SspmOverload 10 0.10 4400.00 0.10 240.00 20.00 + char *data; + uint32_t i = 0; + for (char *str = strtok_r(XdrvMailbox.data, ", ", &data); str && i < 6; str = strtok_r(nullptr, ", ", &data)) { + float value = CharToFloat(str); + if (value > 0) { // 0 = default and no overload detection + switch (i++) { + case 0: + Sspm->overload_delay = (uint8_t)value; // Overload detection reaction time in seconds + break; + case 1: + Sspm->overload_enable = 0x08; // Enable min power overload detection + Sspm->overload_min_power = value; + break; + case 2: + Sspm->overload_enable = 0x10; // Enable max power overload detection + Sspm->overload_max_power = value; + break; + case 3: + Sspm->overload_enable = 0x02; // Enable min voltage overload detection + Sspm->overload_min_voltage = value; + break; + case 4: + Sspm->overload_enable = 0x04; // Enable max voltage overload detection + Sspm->overload_max_voltage = value; + break; + case 5: + Sspm->overload_enable = 0x01; // Enable max current overload detection + Sspm->overload_max_current = value; + break; + } + } + set_overload = true; + } + } + if (set_overload) { + SSPMSendOPS(Sspm->overload_relay); + ResponseClear(); + } + } else { + SSPMSendGetOps(Sspm->overload_relay >> 2); + ResponseClear(); + } } - ResponseCmndFloat(Sspm->energy_total[module][channel], Settings->flag2.energy_resolution); } void CmndSSPMLog(void) { // SspmLog - Report from up to 29 latest log entries // SspmLog 10 - Report from up to 10 latest log entries // SspmLog 100 - Report from up to 29 log entries - if ((XdrvMailbox.index < 1) || (XdrvMailbox.index > TasmotaGlobal.devices_present)) { XdrvMailbox.index = 1; } - if ((XdrvMailbox.payload < 1) || (XdrvMailbox.payload > 65000)) { XdrvMailbox.payload = 28; } - Sspm->log_relay = XdrvMailbox.index -1; - SSPMSendGetLog(Sspm->log_relay, XdrvMailbox.payload +1); - ResponseClear(); + if (Sspm->module_max) { + if ((XdrvMailbox.index < 1) || (XdrvMailbox.index > TasmotaGlobal.devices_present)) { XdrvMailbox.index = 1; } + if ((XdrvMailbox.payload < 1) || (XdrvMailbox.payload > 65000)) { XdrvMailbox.payload = 28; } + Sspm->log_relay = XdrvMailbox.index -1; + SSPMSendGetLog(Sspm->log_relay, XdrvMailbox.payload +1); + ResponseClear(); + } } void CmndSSPMEnergy(void) { - if ((XdrvMailbox.index < 1) || (XdrvMailbox.index > TasmotaGlobal.devices_present)) { XdrvMailbox.index = 1; } - SSPMSendGetEnergy(XdrvMailbox.index -1); - ResponseCmndDone(); + if (Sspm->module_max) { + if ((XdrvMailbox.index < 1) || (XdrvMailbox.index > TasmotaGlobal.devices_present)) { XdrvMailbox.index = 1; } + SSPMSendGetEnergy(XdrvMailbox.index -1); + ResponseCmndDone(); + } } void CmndSSPMHistory(void) { // Retreive daily history of one relay up to six month // SspmHistory - if ((XdrvMailbox.index < 1) || (XdrvMailbox.index > TasmotaGlobal.devices_present)) { XdrvMailbox.index = 1; } - Sspm->history_relay = XdrvMailbox.index -1; - SSPMSendGetEnergyTotal(Sspm->history_relay); - ResponseClear(); + if (Sspm->module_max) { + if ((XdrvMailbox.index < 1) || (XdrvMailbox.index > TasmotaGlobal.devices_present)) { XdrvMailbox.index = 1; } + Sspm->history_relay = XdrvMailbox.index -1; + SSPMSendGetEnergyTotal(Sspm->history_relay); + ResponseClear(); + } } void CmndSSPMScan(void) { @@ -1611,30 +2180,26 @@ void CmndSSPMScan(void) { void CmndSSPMIamHere(void) { // Blink module ERROR led containing relay // SspmIamHere 6 - if ((XdrvMailbox.payload < 1) || (XdrvMailbox.payload > TasmotaGlobal.devices_present)) { XdrvMailbox.payload = 1; } - SSPMSendIAmHere(XdrvMailbox.payload -1); - ResponseCmndDone(); -} - -void CmndSSPMDisplay(void) { - // Select either all relays or only powered on relays - // SspmDisplay 0 or SspmDisplay 1 - if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 1)) { - Settings->sbflag1.sspm_display = XdrvMailbox.payload; + if (Sspm->module_max) { + if ((XdrvMailbox.payload < 1) || (XdrvMailbox.payload > TasmotaGlobal.devices_present)) { XdrvMailbox.payload = 1; } + SSPMSendIAmHere(XdrvMailbox.payload -1); + ResponseCmndDone(); } - ResponseCmndNumber(Settings->sbflag1.sspm_display); } void CmndSSPMReset(void) { // Reset ARM and restart // Reset 1 - if (1 == XdrvMailbox.payload) { - Sspm->mstate = SPM_NONE; - SSPMSendCmnd(SSPM_FUNC_RESET); - TasmotaGlobal.restart_flag = 3; - ResponseCmndChar(PSTR(D_JSON_RESET_AND_RESTARTING)); - } else { - ResponseCmndChar(PSTR(D_JSON_ONE_TO_RESET)); + switch (XdrvMailbox.payload) { + case 1: + TasmotaGlobal.restart_flag = 2; + case 2: + Sspm->mstate = SPM_NONE; + SSPMSendCmnd(SSPM_FUNC_RESET); + ResponseCmndChar(PSTR(D_JSON_RESET_AND_RESTARTING)); + break; + default: + ResponseCmndChar(PSTR(D_JSON_ONE_TO_RESET)); } }