Add Sonoff SPM module mapping

Add Sonoff SPM command ``SspmMap 2,1,..`` to map scanned module to physical module (#14281)
This commit is contained in:
Theo Arends 2022-01-06 18:01:35 +01:00
parent 479b378707
commit b98e82ae3d
3 changed files with 196 additions and 37 deletions

View File

@ -6,6 +6,7 @@ All notable changes to this project will be documented in this file.
## [2022.01.1]
### Added
- Experimental ADE7953 (Shelly EM) reset on restart (#14261)
- ESP32 Sonoff SPM command ``SspmMap 2,1,..`` to map scanned module to physical module (#14281)
### Changed
- PubSubClient library from v2.8.12 to v2.8.13

View File

@ -103,6 +103,7 @@ The latter links can be used for OTA upgrades too like ``OtaUrl http://ota.tasmo
## Changelog v2022.01.1
### Added
- Command ``SSerialConfig <serialconfig>`` to change Serial Bridge configuration
- ESP32 Sonoff SPM command ``SspmMap 2,1,..`` to map scanned module to physical module [#14281](https://github.com/arendst/Tasmota/issues/14281)
- PWM Dimmer two button support [#13993](https://github.com/arendst/Tasmota/issues/13993)
- Device Group Send full status item [#14045](https://github.com/arendst/Tasmota/issues/14045)
- Support for MAX7219 Dot Matrix displays [#14091](https://github.com/arendst/Tasmota/issues/14091)

View File

@ -49,7 +49,9 @@
* 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:
* SspmMap 2,3,1,.. - Map scanned module number to physical module number using positional numbering
* SspmDisplay 0|1 - Select alternative GUI rotating display either all or powered on only
* SspmIAmHere<relay> - Blink ERROR in SPM-4Relay where relay resides
* SspmScan - Rescan ARM modbus taking around 20 seconds
@ -140,6 +142,8 @@
/*********************************************************************************************/
const uint32_t SSPM_VERSION = 0x01010101; // Latest driver version (See settings deltas below)
enum SspmMachineStates { SPM_NONE, // Do nothing
SPM_WAIT, // Wait 100ms
SPM_RESET, // Toggle ARM reset pin
@ -156,6 +160,13 @@ enum SspmMachineStates { SPM_NONE, // Do nothing
#include <TasmotaSerial.h>
TasmotaSerial *SspmSerial;
// Global structure containing driver saved variables
struct {
uint32_t crc32; // To detect file changes
uint32_t version; // To detect driver function changes
uint16_t module_map[32]; // Max possible SPM relay modules
} SSPMSettings;
typedef struct {
float voltage[SSPM_MAX_MODULES][4]; // 123.12 V
float current[SSPM_MAX_MODULES][4]; // 123.12 A
@ -192,8 +203,124 @@ typedef struct {
uint8_t *SspmBuffer = nullptr;
TSspm *Sspm = nullptr;
/*********************************************************************************************\
* Driver Settings load and save using filesystem
\*********************************************************************************************/
uint32_t SSPMSettingsCrc32(void) {
// Use Tasmota CRC calculation function
return GetCfgCrc32((uint8_t*)&SSPMSettings +4, sizeof(SSPMSettings) -4); // Skip crc32
}
void SSPMSettingsDefault(void) {
// Init default values in case file is not found
AddLog(LOG_LEVEL_INFO, PSTR("CFG: SPM " D_USE_DEFAULTS));
memset(&SSPMSettings, 0x00, sizeof(SSPMSettings));
SSPMSettings.version = SSPM_VERSION;
// Init any other parameter in struct SSPMSettings
}
void SSPMSettingsDelta(void) {
// Fix possible setting deltas
if (SSPMSettings.version != SSPM_VERSION) { // Fix version dependent changes
if (Settings->version < 0x01010100) {
AddLog(LOG_LEVEL_INFO, PSTR("CFG: SPM update oldest version restore"));
}
if (Settings->version < 0x01010101) {
AddLog(LOG_LEVEL_INFO, PSTR("CFG: SPM update old version restore"));
}
// Set current version and save settings
SSPMSettings.version = SSPM_VERSION;
SSPMSettingsSave();
}
}
void SSPMSettingsLoad(void) {
// Init default values in case file is not found
SSPMSettingsDefault();
// Try to load file /.drvset086
char filename[20];
// Use for drivers:
snprintf_P(filename, sizeof(filename), PSTR(TASM_FILE_DRIVER), XDRV_86);
#ifdef USE_UFILESYS
if (TfsLoadFile(filename, (uint8_t*)&SSPMSettings, sizeof(SSPMSettings))) {
// Fix possible setting deltas
SSPMSettingsDelta();
AddLog(LOG_LEVEL_INFO, PSTR("CFG: SPM loaded from file"));
} else {
// File system not ready: No flash space reserved for file system
AddLog(LOG_LEVEL_INFO, PSTR("CFG: SPM ERROR File system not ready or file not found"));
}
#else
AddLog(LOG_LEVEL_INFO, PSTR("CFG: SPM ERROR File system not enabled"));
#endif // USE_UFILESYS
SSPMSettings.crc32 = SSPMSettingsCrc32();
}
void SSPMSettingsSave(void) {
// Called from FUNC_SAVE_SETTINGS every SaveData second and at restart
if (SSPMSettingsCrc32() != SSPMSettings.crc32) {
// Try to save file /.drvset086
SSPMSettings.crc32 = SSPMSettingsCrc32();
char filename[20];
// Use for drivers:
snprintf_P(filename, sizeof(filename), PSTR(TASM_FILE_DRIVER), XDRV_86);
#ifdef USE_UFILESYS
if (TfsSaveFile(filename, (const uint8_t*)&SSPMSettings, sizeof(SSPMSettings))) {
AddLog(LOG_LEVEL_INFO, PSTR("CFG: SPM saved to file"));
} else {
// File system not ready: No flash space reserved for file system
AddLog(LOG_LEVEL_INFO, PSTR("CFG: SPM ERROR File system not ready or unable to save file"));
}
#else
AddLog(LOG_LEVEL_INFO, PSTR("CFG: SPM ERROR File system not enabled"));
#endif // USE_UFILESYS
}
}
/*********************************************************************************************/
uint32_t SSMPGetModuleId(uint32_t module) {
// Return short module id
uint32_t module_id = 0;
if (module < Sspm->module_max) {
module_id = Sspm->module[module][0] << 8 | Sspm->module[module][1];
}
return module_id;
}
uint32_t SSPMGetMappedModuleId(uint32_t module) {
// Return mapped module number
for (uint32_t module_nr = 0; module_nr < Sspm->module_max; module_nr++) {
if (SSPMSettings.module_map[module] == SSMPGetModuleId(module_nr)) {
return module_nr; // 0, 1, ..
}
}
return module;
}
uint32_t SSPMGetModuleNumberFromMap(uint32_t id) {
// Return module number based on first two bytes of module id
for (uint32_t module_nr = 0; module_nr < SSPM_MAX_MODULES; module_nr++) {
if (id == SSPMSettings.module_map[module_nr]) {
return module_nr; // 0, 1, ...
}
}
return 0;
}
void SSPMSetLock(uint32_t seconds) {
Sspm->timeout = seconds * 10; // Decremented every 100mSec
Sspm->allow_updates = 0; // Disable requests from 100mSec loop
@ -211,6 +338,8 @@ uint16_t SSPMCalculateCRC(uint8_t *frame, uint32_t num) {
return crc ^ 0;
}
/*********************************************************************************************/
void SSPMSend(uint32_t size) {
uint16_t crc = SSPMCalculateCRC(SspmBuffer, size -2);
SspmBuffer[size -2] = (uint8_t)(crc >> 8);
@ -303,7 +432,7 @@ void SSPMSendGetOps(uint32_t module) {
Marker |Module id |Ac|Cm|Size |Ix|Chksm|
*/
SSPMInitSend();
memcpy(SspmBuffer +3, Sspm->module[module], SSPM_MODULE_NAME_SIZE);
memcpy(SspmBuffer +3, Sspm->module[SSPMGetMappedModuleId(module)], SSPM_MODULE_NAME_SIZE);
SspmBuffer[16] = SSPM_FUNC_GET_OPS; // 0x04
Sspm->command_sequence++;
SspmBuffer[19] = Sspm->command_sequence;
@ -323,7 +452,7 @@ void SSPMSendSetRelay(uint32_t relay, uint32_t state) {
}
uint8_t module = relay >> 2;
SSPMInitSend();
memcpy(SspmBuffer +3, Sspm->module[module], SSPM_MODULE_NAME_SIZE);
memcpy(SspmBuffer +3, Sspm->module[SSPMGetMappedModuleId(module)], SSPM_MODULE_NAME_SIZE);
SspmBuffer[16] = SSPM_FUNC_SET_RELAY; // 0x08
SspmBuffer[18] = 0x01;
SspmBuffer[19] = channel;
@ -340,7 +469,7 @@ void SSPMSendGetModuleState(uint32_t module) {
Marker |Module id |Ac|Cm|Size |Pl|Ix|Chksm|
*/
SSPMInitSend();
memcpy(SspmBuffer +3, Sspm->module[module], SSPM_MODULE_NAME_SIZE);
memcpy(SspmBuffer +3, Sspm->module[SSPMGetMappedModuleId(module)], SSPM_MODULE_NAME_SIZE);
SspmBuffer[16] = SSPM_FUNC_GET_MODULE_STATE; // 0x09
SspmBuffer[18] = 0x01;
SspmBuffer[19] = 0x0F; // State of all four relays
@ -407,7 +536,7 @@ void SSPMSendGetScheme(uint32_t module) {
Marker |Module id |Ac|Cm|Size |Ix|Chksm|
*/
SSPMInitSend();
memcpy(SspmBuffer +3, Sspm->module[module], SSPM_MODULE_NAME_SIZE);
memcpy(SspmBuffer +3, Sspm->module[SSPMGetMappedModuleId(module)], SSPM_MODULE_NAME_SIZE);
SspmBuffer[16] = SSPM_FUNC_GET_SCHEME; // 0x0B
Sspm->command_sequence++;
SspmBuffer[19] = Sspm->command_sequence;
@ -459,7 +588,7 @@ void SSPMSendIAmHere(uint32_t relay) {
*/
uint8_t module = relay >> 2;
SSPMInitSend();
memcpy(SspmBuffer +3, Sspm->module[module], SSPM_MODULE_NAME_SIZE);
memcpy(SspmBuffer +3, Sspm->module[SSPMGetMappedModuleId(module)], SSPM_MODULE_NAME_SIZE);
SspmBuffer[16] = SSPM_FUNC_IAMHERE; // 0x0D
Sspm->command_sequence++;
SspmBuffer[19] = Sspm->command_sequence;
@ -507,7 +636,7 @@ void SSPMSendGetEnergyTotal(uint32_t relay) {
SSPMInitSend();
SspmBuffer[16] = SSPM_FUNC_GET_ENERGY_TOTAL; // 0x16
SspmBuffer[18] = 0x0D;
memcpy(SspmBuffer +19, Sspm->module[module], SSPM_MODULE_NAME_SIZE);
memcpy(SspmBuffer +19, Sspm->module[SSPMGetMappedModuleId(module)], SSPM_MODULE_NAME_SIZE);
SspmBuffer[31] = channel;
Sspm->command_sequence++;
SspmBuffer[32] = Sspm->command_sequence;
@ -527,7 +656,7 @@ void SSPMSendGetEnergy(uint32_t relay) {
SSPMInitSend();
SspmBuffer[16] = SSPM_FUNC_GET_ENERGY; // 0x18
SspmBuffer[18] = 0x10;
memcpy(SspmBuffer +19, Sspm->module[module], SSPM_MODULE_NAME_SIZE);
memcpy(SspmBuffer +19, Sspm->module[SSPMGetMappedModuleId(module)], SSPM_MODULE_NAME_SIZE);
SspmBuffer[31] = 0x01;
SspmBuffer[32] = channel;
SspmBuffer[33] = 0;
@ -551,7 +680,7 @@ void SSPMSendGetLog(uint32_t relay, uint32_t entries) {
SSPMInitSend();
SspmBuffer[16] = SSPM_FUNC_GET_LOG; // 0x1A
SspmBuffer[18] = 0x10;
memcpy(SspmBuffer +19, Sspm->module[module], SSPM_MODULE_NAME_SIZE);
memcpy(SspmBuffer +19, Sspm->module[SSPMGetMappedModuleId(module)], SSPM_MODULE_NAME_SIZE);
SspmBuffer[31] = startlog >> 8; // MSB start log
SspmBuffer[32] = startlog; // LSB start log
SspmBuffer[33] = entries >> 8; // MSB end log
@ -619,7 +748,8 @@ void SSPMHandleReceivedData(void) {
power_t current_state = SspmBuffer[20] >> 4; // Relays state
power_t mask = 0x0000000F;
for (uint32_t i = 0; i < Sspm->module_max; i++) {
if ((SspmBuffer[3] == Sspm->module[i][0]) && (SspmBuffer[4] == Sspm->module[i][1])) {
uint32_t module = SSPMGetMappedModuleId(i);
if ((SspmBuffer[3] == Sspm->module[module][0]) && (SspmBuffer[4] == Sspm->module[module][1])) {
current_state <<= (i * 4);
mask <<= (i * 4);
TasmotaGlobal.power &= (POWER_MASK ^ mask);
@ -703,14 +833,10 @@ void SSPMHandleReceivedData(void) {
energy_total += today_energy;
}
uint32_t channel = SspmBuffer[32];
for (uint32_t module = 0; module < Sspm->module_max; module++) {
if ((SspmBuffer[20] == Sspm->module[module][0]) && (SspmBuffer[21] == Sspm->module[module][1])) {
Sspm->energy_today[module][channel] = energy_today;
Sspm->energy_yesterday[module][channel] = energy_yesterday;
Sspm->energy_total[module][channel] = energy_total; // x.xxkWh
break;
}
}
uint32_t module = SSPMGetModuleNumberFromMap(SspmBuffer[20] << 8 | SspmBuffer[21]);
Sspm->energy_today[module][channel] = energy_today;
Sspm->energy_yesterday[module][channel] = energy_yesterday;
Sspm->energy_total[module][channel] = energy_total; // x.xxkWh
Sspm->allow_updates = 1;
}
break;
@ -787,19 +913,15 @@ void SSPMHandleReceivedData(void) {
if (SspmBuffer[31] & 1) { break; }
SspmBuffer[31] >>= 1;
}
for (uint32_t module = 0; module < Sspm->module_max; module++) {
if ((SspmBuffer[19] == Sspm->module[module][0]) && (SspmBuffer[20] == Sspm->module[module][1])) {
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
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;
break;
}
}
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
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;
SSPMSendAck(command_sequence);
Sspm->allow_updates = 1;
}
@ -814,7 +936,8 @@ void SSPMHandleReceivedData(void) {
power_t relay = SspmBuffer[31] & 0x0F; // Relays active
power_t relay_state = SspmBuffer[31] >> 4; // Relays state
for (uint32_t i = 0; i < Sspm->module_max; i++) {
if ((SspmBuffer[19] == Sspm->module[i][0]) && (SspmBuffer[20] == Sspm->module[i][1])) {
uint32_t module = SSPMGetMappedModuleId(i);
if ((SspmBuffer[19] == Sspm->module[module][0]) && (SspmBuffer[20] == Sspm->module[module][1])) {
relay <<= (i * 4);
relay_state <<= (i * 4);
break;
@ -847,12 +970,15 @@ void SSPMHandleReceivedData(void) {
Ty = Type of sub-device. 130: Four-channel sub-device
*/
if ((0x24 == Sspm->expected_bytes) && (Sspm->module_max < SSPM_MAX_MODULES)) {
memmove(Sspm->module[1], Sspm->module[0], (SSPM_MAX_MODULES -1) * SSPM_MODULE_NAME_SIZE);
memcpy(Sspm->module[0], SspmBuffer + 19, SSPM_MODULE_NAME_SIZE);
Sspm->module_max++;
memcpy(Sspm->module[Sspm->module_max], SspmBuffer + 19, SSPM_MODULE_NAME_SIZE);
if (0 == SSPMSettings.module_map[Sspm->module_max]) {
SSPMSettings.module_map[Sspm->module_max] = SspmBuffer[19] << 8 | SspmBuffer[20];
}
AddLog(LOG_LEVEL_DEBUG, PSTR("SPM: Module %d type %d version %d.%d.%d found with id %12_H"),
Sspm->module_max, SspmBuffer[35], SspmBuffer[36], SspmBuffer[37], SspmBuffer[38], Sspm->module[0]);
Sspm->module_max +1, SspmBuffer[35], SspmBuffer[36], SspmBuffer[37], SspmBuffer[38], Sspm->module[Sspm->module_max]);
Sspm->module_max++;
}
SSPMSendAck(command_sequence);
break;
@ -938,6 +1064,8 @@ void SSPMInit(void) {
return;
}
SSPMSettingsLoad();
pinMode(SSPM_GPIO_ARM_RESET, OUTPUT);
digitalWrite(SSPM_GPIO_ARM_RESET, 1);
@ -1250,10 +1378,10 @@ void SSPMEnergyShow(bool json) {
\*********************************************************************************************/
const char kSSPMCommands[] PROGMEM = "SSPM|" // Prefix
"Log|Energy|History|Scan|IamHere|Display|Reset" ;
"Log|Energy|History|Scan|IamHere|Display|Reset|Map" ;
void (* const SSPMCommand[])(void) PROGMEM = {
&CmndSSPMLog, &CmndSSPMEnergy, &CmndSSPMEnergyHistory, &CmndSSPMScan, &CmndSSPMIamHere, &CmndSSPMDisplay, &CmndSSPMReset };
&CmndSSPMLog, &CmndSSPMEnergy, &CmndSSPMEnergyHistory, &CmndSSPMScan, &CmndSSPMIamHere, &CmndSSPMDisplay, &CmndSSPMReset, &CmndSSPMMap };
void CmndSSPMLog(void) {
// Report 29 log entries
@ -1277,12 +1405,14 @@ void CmndSSPMEnergyHistory(void) {
void CmndSSPMScan(void) {
// Start relay module scan taking up to 20 seconds
// SspmScan
Sspm->mstate = SPM_START_SCAN;
ResponseCmndChar(PSTR(D_JSON_STARTED));
}
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();
@ -1290,6 +1420,7 @@ void CmndSSPMIamHere(void) {
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;
}
@ -1298,6 +1429,7 @@ void CmndSSPMDisplay(void) {
void CmndSSPMReset(void) {
// Reset ARM and restart
// Reset 1
if (1 == XdrvMailbox.payload) {
Sspm->mstate = SPM_NONE;
SSPMSendCmnd(SSPM_FUNC_RESET);
@ -1308,6 +1440,28 @@ void CmndSSPMReset(void) {
}
}
void CmndSSPMMap(void) {
// Map scanned module number to physical module number using positional numbering
// SspmMap 1,3,4,2
// TODO: Might need input checks on count and valid different numbers
if (Sspm->module_max) { // Valid after initial scan
char *p;
uint32_t i = 0;
for (char* str = strtok_r(XdrvMailbox.data, ",", &p); str && i < Sspm->module_max; str = strtok_r(nullptr, ",", &p)) {
uint32_t module = atoi(str);
if ((module > 0) && (module <= Sspm->module_max)) { // Only valid modules 1 to x
SSPMSettings.module_map[i] = SSMPGetModuleId(module -1);
}
i++;
}
Response_P(PSTR("{\"%s\":["), XdrvMailbox.command);
for (uint32_t i = 0; i < Sspm->module_max; i++) {
ResponseAppend_P(PSTR("%s%d"), (i)?",":"", SSPMGetModuleNumberFromMap(SSMPGetModuleId(i)) +1);
}
ResponseAppend_P(PSTR("]}"));
}
}
/*********************************************************************************************\
* Interface
\*********************************************************************************************/
@ -1326,6 +1480,9 @@ bool Xdrv86(uint8_t function) {
case FUNC_EVERY_100_MSECOND:
SSPMEvery100ms();
break;
case FUNC_SAVE_SETTINGS:
SSPMSettingsSave();
break;
case FUNC_SET_DEVICE_POWER:
result = SSPMSetDevicePower();
break;