TasmotaSerial *SspmSerial;
typedef struct {
float voltage[SSPM_MAX_MODULES][4]; // 123.12 V
float current[SSPM_MAX_MODULES][4]; // 123.12 A
float active_power[SSPM_MAX_MODULES][4]; // 123.12 W
float apparent_power[SSPM_MAX_MODULES][4]; // 123.12 VA
float reactive_power[SSPM_MAX_MODULES][4]; // 123.12 VAr
float power_factor[SSPM_MAX_MODULES][4]; // 0.12
float total[SSPM_MAX_MODULES][4]; // 12345 kWh total energy
uint32_t timeout;
power_t old_power;
uint16_t serial_in_byte_counter;
uint16_t expected_bytes;
uint8_t module[SSPM_MAX_MODULES][SSPM_MODULE_NAME_SIZE];
uint8_t allow_updates;
uint8_t get_energy_relay;
uint8_t rotate;
uint8_t module_max;
uint8_t module_selected;
uint8_t no_send_key;
uint8_t counter;
uint8_t command_sequence;
uint8_t loop_step;
uint8_t mstate;
uint8_t last_button;
bool discovery_triggered;
} TSspm;
uint8_t *SspmBuffer = nullptr;
TSspm *Sspm = nullptr;
void SSPMSetLock(uint32_t seconds) {
Sspm->timeout = seconds * 10; // Decremented every 100mSec
Sspm->allow_updates = 0; // Disable requests from 100mSec loop
}
uint16_t SSPMCalculateCRC(uint8_t *frame, uint32_t num) {
// CRC-16/ARC (polynomial 0x8005 reflected as 0xA001)
uint16_t crc = 0;
for (uint32_t i = 2; i < num; i++) {
crc ^= frame[i];
for (uint32_t i = 0; i < 8; i++) {
crc = (crc & 1) ? (crc >> 1) ^ 0xA001 : crc >> 1;
}
}
return crc ^ 0;
}
void SSPMTime(uint8_t *frame) {
/*
0 1 2 3 4 5 6
YY YY MM DD HH MM SS
07 e5 0b 06 0c 39 01
*/
TIME_T time;
BreakTime(Rtc.utc_time, time);
uint16_t year = time.year + 1970;
frame[0] = year >> 8;
frame[1] = year;
frame[2] = time.month;
frame[3] = time.day_of_month;
frame[4] = time.hour;
frame[5] = time.minute;
frame[6] = time.second;
}
void SSPMSend(uint32_t size) {
uint16_t crc = SSPMCalculateCRC(SspmBuffer, size -2);
SspmBuffer[size -2] = (uint8_t)(crc >> 8);
SspmBuffer[size -1] = (uint8_t)crc;
AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("SPM: ESP %*_H"), size, SspmBuffer);
SspmSerial->write(SspmBuffer, size);
}
void SSPMInitSend(void) {
/*
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
AA 55 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Marker |Module id |Ac|Cm|Size |
*/
memset(SspmBuffer, 0, 19);
SspmBuffer[0] = 0xAA;
SspmBuffer[1] = 0x55;
SspmBuffer[2] = 0x01;
}
void SSPMSendCmnd(uint32_t command) {
/*
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|
*/
SSPMInitSend();
SspmBuffer[16] = command;
if (0 == command) {
Sspm->command_sequence = 0;
} else {
Sspm->command_sequence++;
}
SspmBuffer[19] = Sspm->command_sequence;
SSPMSend(22);
}
void SSPMSendInitScan(void) {
/*
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 ff ff ff ff ff ff ff ff ff ff ff ff 00 10 00 00 02 cd f0
Marker |Module id |Ac|Cm|Size |Ix|Chksm|
Acknowledge:
AA 55 01 ff ff ff ff ff ff ff ff ff ff ff ff 80 10 00 01 00 02 e5 03
|Ac|Cm|Size |Rt|Ix|Chksm|
*/
SSPMSetLock(30); // Disable requests from 100mSec loop
memset(SspmBuffer, 0xFF, 15);
SspmBuffer[0] = 0xAA;
SspmBuffer[1] = 0x55;
SspmBuffer[2] = 0x01;
SspmBuffer[15] = 0;
SspmBuffer[16] = SSPM_FUNC_INIT_SCAN; // 0x10
SspmBuffer[17] = 0;
SspmBuffer[18] = 0;
Sspm->command_sequence++;
SspmBuffer[19] = Sspm->command_sequence;
SSPMSend(22);
AddLog(LOG_LEVEL_DEBUG, PSTR("SPM: Start relay scan..."));
}
void SSPMSendOPS(uint32_t relay_num) {
/*
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|
| | | 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
Values are XX XX - number
XX - decimals
Acknowledge:
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
*/
SspmBuffer[16] = SSPM_FUNC_SET_OPS; // 0x03
}
void SSPMSendScheme(uint32_t relay) {
/*
Time scheme
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 45 46 47 48 49 50 51
One time
AA 55 01 8b 34 32 37 39 37 34 13 4b 35 36 37 00 0a 00 1e 01 01 01 07 e5 0b 0e 0b 38 08 00 6b 01 00 ea 60 20 23 1b 04 fd 7a 83 05 63 ee dd a9 b9 3a 7e 14 95
AA 55 01 8b 34 32 37 39 37 34 13 4b 35 36 37 00 0a 00 1e 01 01 01 07 e5 0b 0e 0c 04 35 00 55 01 02 46 76 0e 0c 20 e1 22 7c 67 ab 9c 66 73 6d bd e8 7f 50 d4
Marker |Module id |Ac|Cm|Size |No| |Mo| YYYY|MM|DD|HH|MM |St|Re|Scheme id |
No - Number of schemes defined
Mo - Scheme type (1 = temporarly, 2 = scheduled)
Re - Relay 0 to 3
St - State (0 = off, 1 = On)
Scheduled On
AA 55 01 8b 34 32 37 39 37 34 13 4b 35 36 37 00 0a 00 18 01 01 02 15 0c 0c 01 03 99 65 93 dc f8 d0 b0 29 a8 66 ba 8f 41 66 29 24 80 5b 48
AA 55 01 8b 34 32 37 39 37 34 13 4b 35 36 37 00 0a 00 18 01 01 02 15 0c 0c 01 03 99 65 93 dc f8 d0 b0 29 a8 66 ba 8f 41 66 29 24 82 9a c9
AA 55 01 8b 34 32 37 39 37 34 13 4b 35 36 37 00 0a 00 18 01 01 02 53 0c 0c 01 03 99 65 93 dc f8 d0 b0 29 a8 66 ba 8f 41 66 29 24 83 44 aa
AA 55 01 8b 34 32 37 39 37 34 13 4b 35 36 37 00 0a 00 18 01 01 02 53 0d 0b 00 02 99 65 93 dc f8 d0 b0 29 a8 66 ba 8f 41 66 29 24 84 e0 22
AA 55 01 8b 34 32 37 39 37 34 13 4b 35 36 37 00 0a 00 18 01 01 02 0e 0d 3b 01 03 84 fb ea 35 ca 16 51 b5 b8 10 a1 1c d0 1a 3f 7a 86 e3 fa
AA 55 01 8b 34 32 37 39 37 34 13 4b 35 36 37 00 0a 00 2f 02 01 02 0e 0d 3b 01 03 84 fb ea 35 ca 16 51 b5 b8 10 a1 1c d0 1a 3f 7a
01 02 53 0d 0b 00 02 99 65 93 dc f8 d0 b0 29 a8 66 ba 8f 41 66 29 24 87 e8 02
AA 55 01 8b 34 32 37 39 37 34 13 4b 35 36 37 00 0a 00 2f 02 01 02 53 0d 0b 00 02 99 65 93 dc f8 d0 b0 29 a8 66 ba 8f 41 66 29 24
01 02 0e 0d 3b 01 03 84 fb ea 35 ca 16 51 b5 b8 10 a1 1c d0 1a 3f 7a 89 6e e6
AA 55 01 8b 34 32 37 39 37 34 13 4b 35 36 37 00 0a 00 4c 03 01 01 07 e5 0b 0e 0e 0e 26 00 e7 01 00 e6 b2 48 8e ef be ce 78 3e 5d a8 3a c0 c5 6f 5e = One time
01 02 53 0d 0b 00 02 99 65 93 dc f8 d0 b0 29 a8 66 ba 8f 41 66 29 24 = 14:11 OFF CH3 SuMoThSa
01 02 0e 0d 3b 01 03 84 fb ea 35 ca 16 51 b5 b8 10 a1 1c d0 1a 3f 7a 8a 2f f8 = 14:59 ON CH4 MoTuWe
Marker |Module id |Ac|Cm|Size |No| |Mo|Dy|HH|MM|St|Re|Scheme id |
Dy - Bitmask days xxxxxxx1 sunday
xxxxxx1x monday
xxxxx1xx tuesday
xxxx1xxx wednesday
xxx1xxxx thursday
xx1xxxxx friday
x1xxxxxx saturday
Scheduled Off
AA 55 01 8b 34 32 37 39 37 34 13 4b 35 36 37 00 0a 00 01 00 81 26 9f
Schedule 2 off
AA 55 01 8b 34 32 37 39 37 34 13 4b 35 36 37 00 0a 00 18 01 01 02 53 0d 0b 00 02 99 65 93 dc f8 d0 b0 29 a8 66 ba 8f 41 66 29 24 88 e5 22
Marker |Module id |Ac|Cm|Size |
*/
SspmBuffer[16] = SSPM_FUNC_SET_SCHEME; // 0x0A
}
void SSPMSendSetTime(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
AA 55 01 00 00 00 00 00 00 00 00 00 00 00 00 00 0c 00 0b 07 e5 0b 06 0c 39 01 00 00 02 00 04 8a 37
Marker |Module id |Ac|Cm|Size |YY YY MM DD HH MM SS|Ln|St|Tr| |Ix|Chksm|
*/
SSPMInitSend();
SspmBuffer[16] = SSPM_FUNC_SET_TIME;
SspmBuffer[18] = 0x0B;
SSPMTime(SspmBuffer + 19);
SspmBuffer[26] = 0x00;
SspmBuffer[27] = 0x00;
SspmBuffer[28] = 0x02;
SspmBuffer[29] = 0x00;
Sspm->command_sequence++;
SspmBuffer[30] = Sspm->command_sequence;
SSPMSend(33);
}
void SSPMSendSetRelay(uint32_t relay, uint32_t state) {
/*
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 00 08 00 01 44 08 c0 34
Marker |Module id |Ac|Cm|Size |Pl|Ix|Chksm|
*/
uint8_t channel = 1 << (relay & 0x03); // Channel relays are bit masked
if (state) {
channel |= (channel << 4);
}
uint8_t module = relay >> 2;
SSPMInitSend();
memcpy(SspmBuffer +3, Sspm->module[module], SSPM_MODULE_NAME_SIZE);
SspmBuffer[16] = SSPM_FUNC_SET_RELAY;
SspmBuffer[18] = 0x01;
SspmBuffer[19] = channel;
Sspm->command_sequence++;
SspmBuffer[20] = Sspm->command_sequence;
SSPMSend(23);
}
void SSPMSendGetModuleState(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
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|
*/
SSPMInitSend();
memcpy(SspmBuffer +3, Sspm->module[module], SSPM_MODULE_NAME_SIZE);
SspmBuffer[16] = SSPM_FUNC_GET_MODULE_STATE;
SspmBuffer[18] = 0x01;
SspmBuffer[19] = 0x0F; // State of all four relays
Sspm->command_sequence++;
SspmBuffer[20] = Sspm->command_sequence;
SSPMSend(23);
}
void SSPMSendGetOps(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 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|
*/
SSPMInitSend();
memcpy(SspmBuffer +3, Sspm->module[module], SSPM_MODULE_NAME_SIZE);
SspmBuffer[16] = SSPM_FUNC_GET_OPS;
Sspm->command_sequence++;
SspmBuffer[19] = Sspm->command_sequence;
SSPMSend(22);
}
void SSPMSendGetScheme(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 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|
*/
SSPMInitSend();
memcpy(SspmBuffer +3, Sspm->module[module], SSPM_MODULE_NAME_SIZE);
SspmBuffer[16] = SSPM_FUNC_GET_SCHEME;
Sspm->command_sequence++;
SspmBuffer[19] = Sspm->command_sequence;
SSPMSend(22);
}
void SSPMSendGetEnergy(uint32_t relay) {
/*
relay_num = 1..8
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 18 00 10 6b 7e 32 37 39 37 34 13 4b 35 36 37 01 01 00 3c 2a db d1
*/
uint8_t module = relay >> 2;
uint8_t channel = 1 << (relay & 0x03); // Channel relays are bit masked
SSPMInitSend();
SspmBuffer[16] = SSPM_FUNC_GET_ENERGY;
SspmBuffer[18] = 0x10;
memcpy(SspmBuffer +19, Sspm->module[module], SSPM_MODULE_NAME_SIZE);
SspmBuffer[31] = 0x01;
SspmBuffer[32] = channel;
SspmBuffer[33] = 0;
SspmBuffer[34] = 0x3C;
Sspm->command_sequence++;
SspmBuffer[35] = Sspm->command_sequence;
SSPMSend(38);
}
void SSPMSendGetEnergyTotal(uint32_t relay) {
/*
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 01 14 e6 93
*/
uint8_t module = relay >> 2;
uint8_t channel = relay & 0x03; // Channel relays are NOT bit masked this time
SSPMInitSend();
SspmBuffer[16] = SSPM_FUNC_GET_ENERGY_TOTAL;
SspmBuffer[18] = 0x0D;
memcpy(SspmBuffer +19, Sspm->module[module], SSPM_MODULE_NAME_SIZE);
SspmBuffer[31] = channel;
Sspm->command_sequence++;
SspmBuffer[32] = Sspm->command_sequence;
SSPMSend(35);
}
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
*/
uint8_t module = relay >> 2;
SSPMInitSend();
SspmBuffer[16] = SSPM_FUNC_GET_LOG;
SspmBuffer[18] = 0x10;
memcpy(SspmBuffer +19, Sspm->module[module], SSPM_MODULE_NAME_SIZE);
SspmBuffer[31] = 0;
SspmBuffer[32] = 0;
SspmBuffer[33] = 0;
SspmBuffer[34] = entries; // Number of logs
Sspm->command_sequence++;
SspmBuffer[35] = Sspm->command_sequence;
SSPMSend(38);
}
void SSPMSendAck(uint32_t command_sequence) {
/*
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 0f 00 01 00 01 3d e6
Marker |Module id |Ac|Cm|Size |Pl|Ix|Chksm|
*/
SspmBuffer[15] = 0x80;
SspmBuffer[17] = 0x00;
SspmBuffer[18] = 0x01;
SspmBuffer[19] = 0x00;
SspmBuffer[20] = command_sequence;
SSPMSend(23);
}
void SSPMHandleReceivedData(void) {
uint8_t command = SspmBuffer[16];
bool ack = (0x80 == SspmBuffer[15]);
uint8_t command_sequence = SspmBuffer[19 + Sspm->expected_bytes];
// AddLog(LOG_LEVEL_DEBUG, PSTR("SPM: Rcvd ack %d, cmnd %d, seq %d, size %d"),
// ack, command, command_sequence, Sspm->expected_bytes);
if (ack) {
// Responses from ARM (Acked)
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|
*/
if ((1 == Sspm->expected_bytes) && (0 == SspmBuffer[19])) {
Sspm->mstate++; // Cycle to
}
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|
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
00 11 30 00 00 00 0A 00 F0 00 00 00 0A 00 14 00 00
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|
| | | 4400W| 0.1W| 240V| 0.1V| 20A| |
*/
if (0x02 == Sspm->expected_bytes) {
}
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:
/* 0x09
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
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 == Sspm->expected_bytes) {
// SspmBuffer[20] & 0x0F // Relays operational
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])) {
current_state <<= (i * 4);
mask <<= (i * 4);
TasmotaGlobal.power &= (POWER_MASK ^ mask);
TasmotaGlobal.power |= current_state;
break;
}
}
Sspm->old_power = TasmotaGlobal.power;
TasmotaGlobal.devices_present += 4;
}
SSPMSendGetOps(Sspm->module_selected -1);
break;
case SSPM_FUNC_GET_SCHEME:
/* 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
|?? ??|
*/
if (0x02 == Sspm->expected_bytes) {
}
Sspm->module_selected++;
if (Sspm->module_selected < Sspm->module_max) {
SSPMSendGetScheme(Sspm->module_selected);
} else {
AddLog(LOG_LEVEL_DEBUG, PSTR("SPM: Relay scan done"));
Sspm->mstate = SPM_SCAN_COMPLETE;
}
break;
case SSPM_FUNC_SET_TIME:
/* 0x0C
AA 55 01 00 00 00 00 00 00 00 00 00 00 00 00 80 0c 00 01 00 04 3e 62
*/
TasmotaGlobal.devices_present = 0;
SSPMSendGetModuleState(Sspm->module_selected -1);
break;
case SSPM_FUNC_INIT_SCAN:
/* 0x10
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 ff ff ff ff ff ff ff ff ff ff ff ff 80 10 00 01 00 02 e5 03
*/
break;
case SSPM_FUNC_UNITS:
/* 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
|?? ?? ?? ??|
*/
Sspm->mstate = SPM_START_SCAN;
break;
case SSPM_FUNC_GET_ENERGY_TOTAL:
/* 0x16
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 16 01 7e 00 8b 34 32 37 39 37 34 13 4b 35 36 37
03 <- L4
07 e5 0b 0d <- End date (Today) 2021 nov 13
07 e5 05 11 <- Start date 2021 may 17
00 05 <- 5kWh (13/11 Today)
00 00 <- 0 (12/11 Yesterday)
00 04 <- 4kWh (11/11 etc)
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 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 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 00 00 00 00 00 00
42 67 46
*/
{
uint32_t total_energy = 0;
uint32_t entries = (Sspm->expected_bytes - 22) / 2;
for (uint32_t i = 0; i < entries; i++) {
uint32_t today_energy = (SspmBuffer[41 + (i*2)] << 8) + SspmBuffer[42 + (i*2)];
if (today_energy != 28702) { // Unknown why sometimes 0x701E (=28702kWh) pops up
total_energy += 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->total[module][channel] = total_energy; // xkWh
break;
}
}
Sspm->allow_updates = 1;
}
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)
*/
break;
}
} else {
// Initiated by ARM
switch(command) {
case SSPM_FUNC_ENERGY_RESULT:
/* 0x06
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 45 46 47 48 49
AA 55 01 00 00 00 00 00 00 00 00 00 00 00 00 00 06 00 1c 6b 7e 32 37 39 37 34 13 4b 35 36 37 01 00 00 00 e3 5b 00 00 00 00 00 00 00 00 00 6b 1f 95 1e
AA 55 01 00 00 00 00 00 00 00 00 00 00 00 00 00 06 00 1C 8B 34 32 37 39 37 34 13 4B 35 36 37 01 00 0B 00 E4 37 00 19 0E 00 00 02 00 19 09 4B 28 1D 71
AA 55 01 00 00 00 00 00 00 00 00 00 00 00 00 00 06 00 1C 8B 34 32 37 39 37 34 13 4B 35 36 37 08 00 0A 00 E3 61 00 18 2E 00 00 00 00 18 33 4B 27 D3 0D
AA 55 01 00 00 00 00 00 00 00 00 00 00 00 00 00 06 00 1C 8B 34 32 37 39 37 34 13 4B 35 36 37 08 02 04 00 DC 14 01 C1 3D 00 10 19 01 C2 29 4B 37 6B 26
AA 55 01 00 00 00 00 00 00 00 00 00 00 00 00 00 06 00 1c 8b 34 32 37 39 37 34 13 4b 35 36 37 08 00 44 00 e1 35 00 9a 3e 00 01 45 00 9a 38 00 08 8b ae
AA 55 01 00 00 00 00 00 00 00 00 00 00 00 00 00 06 00 1c 8b 34 32 37 39 37 34 13 4b 35 36 37 08 00 4a 00 e1 22 00 61 4d 00 2c 38 00 a8 28 20 26 21 70
|Ch|Curre|Voltage |ActivePo|Reactive|Apparent|??|
Values are XX XX - number
XX - decimals
*/
{
uint32_t channel = 0;
for (channel = 0; channel < 4; channel++) {
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
break;
}
}
SSPMSendAck(command_sequence);
Sspm->allow_updates = 1;
}
break;
case SSPM_FUNC_KEY_PRESS:
/* 0x07
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|
*/
if (!Sspm->no_send_key) {
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])) {
relay <<= (i * 4);
relay_state <<= (i * 4);
break;
}
}
for (uint32_t i = 1; i <= TasmotaGlobal.devices_present; i++) {
if (relay &1) {
ExecuteCommandPower(i, relay_state &1, SRC_BUTTON);
}
relay >>= 1;
relay_state >>= 1;
}
Sspm->old_power = TasmotaGlobal.power;
}
SSPMSendAck(command_sequence);
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
*/
SSPMSendAck(command_sequence);
break;
case SSPM_FUNC_SCAN_RESULT:
/* 0x13
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 45 46 47 48 49 50 51 52 53 54 55 56 57
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| |Max I|Min I|Max U |Min U |Max P |Min P |Ix|Chksm|
| 20A| 0.1A| 240V| 0.1V| 4400W| 0.1W|
*/
if ((0x24 == Sspm->expected_bytes) && (Sspm->module_max < SSPM_MAX_MODULES)) {
memcpy(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++;
}
SSPMSendAck(command_sequence);
break;
case SSPM_FUNC_SCAN_DONE:
/* 0x19
AA 55 01 00 00 00 00 00 00 00 00 00 00 00 00 00 19 00 00 03 a1 16
*/
SSPMSendAck(command_sequence);
Sspm->module_selected = Sspm->module_max;
SSPMSendSetTime();
break;
}
}
}
void SSPMSerialInput(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
AA 55 01 00 00 00 00 00 00 00 00 00 00 00 00 00 19 00 00 03 a1 16
Marker |Module id |Ac|Cm|Size |Ix|Chksm|
AA 55 01 00 00 00 00 00 00 00 00 00 00 00 00 80 00 00 01 00 00 fc 73
Marker |Module id |Ac|Cm|Size |Pl|Ix|Chksm|
AA 55 01 6b 7e 32 37 39 37 34 13 4b 35 36 37 80 09 00 06 00 0f 01 01 01 01 05 f9 9d
Marker |Module id |Ac|Cm|Size |Payload |Ix|Chksm|
00 Request
80 Response (Ack)
*/
while (SspmSerial->available()) {
yield();
uint8_t serial_in_byte = SspmSerial->read();
if ((0x55 == serial_in_byte) && (0xAA == SspmBuffer[Sspm->serial_in_byte_counter -1])) {
Sspm->expected_bytes = 0;
SspmBuffer[0] = 0xAA;
Sspm->serial_in_byte_counter = 1;
}
if (Sspm->serial_in_byte_counter < SSPM_SERIAL_BUFFER_SIZE -1) {
SspmBuffer[Sspm->serial_in_byte_counter++] = serial_in_byte;
if (19 == Sspm->serial_in_byte_counter) {
Sspm->expected_bytes = (SspmBuffer[Sspm->serial_in_byte_counter -2] << 8) + SspmBuffer[Sspm->serial_in_byte_counter -1];
}
if (Sspm->serial_in_byte_counter == (22 + Sspm->expected_bytes)) {
AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("SPM: ARM %*_H"), Sspm->serial_in_byte_counter, SspmBuffer);
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);
if (crc_rcvd == crc_calc) {
SSPMHandleReceivedData();
} else {
AddLog(LOG_LEVEL_DEBUG, PSTR("SPM: CRC error"));
}
Sspm->serial_in_byte_counter = 0;
Sspm->expected_bytes = 0;
}
} else {
AddLog(LOG_LEVEL_DEBUG, PSTR("SPM: Serial input buffer overflow"));
Sspm->serial_in_byte_counter = 0;
Sspm->expected_bytes = 0;
}
}
}
void SSPMInit(void) {
if (!ValidTemplate(PSTR("Sonoff SPM (POC1)")) &&
!ValidTemplate(PSTR("Sonoff SPM (POC2)"))) { return; }
if (!PinUsed(GPIO_RXD) || !PinUsed(GPIO_TXD)) { return; }
Sspm = (TSspm*)calloc(sizeof(TSspm), 1);
if (!Sspm) { return; }
SspmBuffer = (uint8_t*)malloc(SSPM_SERIAL_BUFFER_SIZE);
if (!SspmBuffer) {
free(Sspm);
return;
}
SspmSerial = new TasmotaSerial(Pin(GPIO_RXD), Pin(GPIO_TXD), 1, 0, SSPM_SERIAL_BUFFER_SIZE);
if (!SspmSerial->begin(115200)) {
free(SspmBuffer);
free(Sspm);
return;
}
pinMode(SSPM_GPIO_ARM_RESET, OUTPUT);
digitalWrite(SSPM_GPIO_ARM_RESET, 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
Settings->flag2.wattage_resolution = 1; // SPM has 2 decimals but this keeps the gui clean
Settings->flag2.energy_resolution = 0; // SPM has no decimals on total energy
}
#if CONFIG_IDF_TARGET_ESP32
#ifdef USE_ETHERNET
Settings->eth_address = 0; // EthAddress
Settings->eth_type = ETH_PHY_LAN8720; // EthType
Settings->eth_clk_mode = ETH_CLOCK_GPIO17_OUT; // EthClockMode
#endif
#endif
Sspm->old_power = TasmotaGlobal.power;
Sspm->mstate = SPM_WAIT; // Start init sequence
}
void SSPMEvery100ms(void) {
if (Sspm->no_send_key) { Sspm->no_send_key--; }
if (Sspm->timeout) {
Sspm->timeout--;
if (!Sspm->timeout) {
Sspm->allow_updates = 1;
}
}
// Fix race condition if the ARM doesn't respond
if ((Sspm->mstate > SPM_NONE) && (Sspm->mstate < SPM_SEND_FUNC_UNITS)) {
Sspm->counter++;
if (Sspm->counter > 20) {
Sspm->mstate = SPM_NONE;
}
}
switch (Sspm->mstate) {
case SPM_NONE:
return;
case SPM_WAIT:
// 100ms wait
Sspm->mstate = SPM_RESET;
break;
case SPM_RESET:
// Reset ARM
digitalWrite(SSPM_GPIO_ARM_RESET, 0);
delay(18);
digitalWrite(SSPM_GPIO_ARM_RESET, 1);
delay(18);
Sspm->mstate = SPM_POLL_ARM;
case SPM_POLL_ARM:
// Wait for first acknowledge from ARM after reset
SSPMSendCmnd(SSPM_FUNC_FIND);
break;
case SPM_POLL_ARM_2:
// Wait for second acknowledge from ARM after reset
SSPMSendCmnd(SSPM_FUNC_FIND);
break;
case SPM_SEND_FUNC_UNITS:
// Get number of units
SSPMSendCmnd(SSPM_FUNC_UNITS);
break;
case SPM_START_SCAN:
// Start scan module sequence
Sspm->module_max = 0;
SSPMSendInitScan();
Sspm->mstate = SPM_WAIT_FOR_SCAN;
break;
case SPM_WAIT_FOR_SCAN:
// Wait for scan sequence to complete
break;
case SPM_SCAN_COMPLETE:
// Scan sequence finished
TasmotaGlobal.discovery_counter = 1; // Force TasDiscovery()
Sspm->get_energy_relay = 1;
Sspm->allow_updates = 1; // Enable requests from 100mSec loop
Sspm->mstate = SPM_GET_ENERGY_TOTALS;
break;
case SPM_GET_ENERGY_TOTALS:
// Retrieve Energy total status from up to 128 relays
if (Sspm->allow_updates && (Sspm->get_energy_relay > 0)) {
SSPMSetLock(4);
SSPMSendGetEnergyTotal(Sspm->get_energy_relay -1);
Sspm->get_energy_relay++;
if (Sspm->get_energy_relay > TasmotaGlobal.devices_present) {
Sspm->get_energy_relay = 1;
Sspm->mstate = SPM_UPDATE_CHANNELS;
}
}
break;
case SPM_UPDATE_CHANNELS:
// Retrieve Energy status from up to 128 powered on relays
if (Sspm->allow_updates && (Sspm->get_energy_relay > 0)) {
power_t powered_on = TasmotaGlobal.power >> (Sspm->get_energy_relay -1);
if (powered_on &1) {
SSPMSetLock(4);
SSPMSendGetEnergy(Sspm->get_energy_relay -1);
} else {
uint32_t relay_set = (Sspm->get_energy_relay -1) >> 2;
uint32_t relay_num = (Sspm->get_energy_relay -1) &3;
if (Sspm->voltage[relay_set][relay_num]) {
Sspm->voltage[relay_set][relay_num] = 0;
Sspm->current[relay_set][relay_num] = 0;
Sspm->active_power[relay_set][relay_num] = 0;
Sspm->apparent_power[relay_set][relay_num] = 0;
Sspm->reactive_power[relay_set][relay_num] = 0;
Sspm->power_factor[relay_set][relay_num] = 0;
}
}
Sspm->get_energy_relay++;
if (Sspm->get_energy_relay > TasmotaGlobal.devices_present) {
Sspm->get_energy_relay = 1;
}
Sspm->loop_step++; // Rolls over after 256 so allows for scanning at least all relays twice
if (!Sspm->loop_step) {
Sspm->get_energy_relay = 1;
Sspm->mstate = SPM_UPDATE_TOTALS;
}
}
break;
case SPM_UPDATE_TOTALS:
// Retrieve Energy totals from up to 128 powered on relays
if (Sspm->allow_updates && (Sspm->get_energy_relay > 0)) {
power_t powered_on = TasmotaGlobal.power >> (Sspm->get_energy_relay -1);
// Get energy total only once in any 256 requests to safe comms
if (powered_on &1) {
SSPMSetLock(4);
SSPMSendGetEnergyTotal(Sspm->get_energy_relay -1);
}
Sspm->get_energy_relay++;
if (Sspm->get_energy_relay > TasmotaGlobal.devices_present) {
Sspm->get_energy_relay = 1;
Sspm->mstate = SPM_UPDATE_CHANNELS;
}
}
break;
}
}
bool SSPMSetDevicePower(void) {
power_t new_power = XdrvMailbox.index;
if (new_power != Sspm->old_power) {
for (uint32_t i = 0; i < TasmotaGlobal.devices_present; i++) {
uint8_t new_state = (new_power >> i) &1;
if (new_state != ((Sspm->old_power >> i) &1)) {
SSPMSendSetRelay(i, new_state);
Sspm->no_send_key = 10; // Disable buttons for 10 * 0.1 second
}
}
Sspm->old_power = new_power;
}
return true;
}
bool SSPMButton(void) {
bool result = false;
uint32_t button = XdrvMailbox.payload;
if ((PRESSED == button) && (NOT_PRESSED == Sspm->last_button)) { // Button pressed
Sspm->mstate = SPM_START_SCAN;
result = true; // Disable further button processing
}
Sspm->last_button = button;
return result;
}
const uint16_t SSPM_SIZE = 128;
const char kSSPMEnergyPhases[] PROGMEM = "%*_f%*_f | %*_f | %*_f | |[%*_f,%*_f,%*_f,%*_f]";
char* SSPMEnergyFormat(char* result, float* input, uint32_t resolution, bool json) {
char layout[100];
GetTextIndexed(layout, sizeof(layout), json, kSSPMEnergyPhases);
ext_snprintf_P(result, SSPM_SIZE, layout, resolution, &input[0], resolution, &input[1], resolution, &input[2], resolution, &input[3]);
return result;
}
const char HTTP_SSPM_VOLTAGE[] PROGMEM =
"{s}" D_VOLTAGE " | %s" D_UNIT_VOLT "{e}";
const char HTTP_SSPM_CURRENT[] PROGMEM =
"{s}" D_CURRENT " | %s" D_UNIT_AMPERE "{e}";
const char HTTP_SSPM_POWER[] PROGMEM =
"{s}" D_POWERUSAGE_ACTIVE " | %s" D_UNIT_WATT "{e}";
const char HTTP_SSPM_ENERGY[] PROGMEM =
"{s}" D_POWERUSAGE_APPARENT " | %s" D_UNIT_VA "{e}"
"{s}" D_POWERUSAGE_REACTIVE " | %s" D_UNIT_VAR "{e}"
"{s}" D_ENERGY_TOTAL " | %s" D_UNIT_KILOWATTHOUR "{e}"; // {s} = | , {m} = | , {e} = |
void SSPMEnergyShow(bool json) {
if (!TasmotaGlobal.devices_present) { return; } // Not ready yet
if (json) {
ResponseAppend_P(PSTR(",\"SPM\":{\"" D_JSON_ENERGY "\":["));
for (uint32_t i = 0; i < TasmotaGlobal.devices_present; i++) {
ResponseAppend_P(PSTR("%s%*_f"), (i>0)?",":"", -1, &Sspm->total[i >>2][i &3]);
}
ResponseAppend_P(PSTR("],\"" D_JSON_ACTIVE_POWERUSAGE "\":["));
for (uint32_t i = 0; i < TasmotaGlobal.devices_present; i++) {
ResponseAppend_P(PSTR("%s%*_f"), (i>0)?",":"", Settings->flag2.wattage_resolution, &Sspm->active_power[i >>2][i &3]);
}
ResponseAppend_P(PSTR("],\"" D_JSON_APPARENT_POWERUSAGE "\":["));
for (uint32_t i = 0; i < TasmotaGlobal.devices_present; i++) {
ResponseAppend_P(PSTR("%s%*_f"), (i>0)?",":"", Settings->flag2.wattage_resolution, &Sspm->apparent_power[i >>2][i &3]);
}
ResponseAppend_P(PSTR("],\"" D_JSON_REACTIVE_POWERUSAGE "\":["));
for (uint32_t i = 0; i < TasmotaGlobal.devices_present; i++) {
ResponseAppend_P(PSTR("%s%*_f"), (i>0)?",":"", Settings->flag2.wattage_resolution, &Sspm->reactive_power[i >>2][i &3]);
}
ResponseAppend_P(PSTR("],\"" D_JSON_VOLTAGE "\":["));
for (uint32_t i = 0; i < TasmotaGlobal.devices_present; i++) {
ResponseAppend_P(PSTR("%s%*_f"), (i>0)?",":"", Settings->flag2.voltage_resolution, &Sspm->voltage[i >>2][i &3]);
}
ResponseAppend_P(PSTR("],\"" D_JSON_CURRENT "\":["));
for (uint32_t i = 0; i < TasmotaGlobal.devices_present; i++) {
ResponseAppend_P(PSTR("%s%*_f"), (i>0)?",":"", Settings->flag2.current_resolution, &Sspm->current[i >>2][i &3]);
}
ResponseAppend_P(PSTR("]}"));
} else {
Sspm->rotate++;
if (Sspm->rotate >= TasmotaGlobal.devices_present) {
Sspm->rotate = 0;
}
uint32_t module = Sspm->rotate >> 2;
uint32_t relay_base = module * 4;
WSContentSend_P(PSTR("{t}{s}")); // First column is empty ({t} = , {s} = )
for (uint32_t i = 0; i < 4; i++) {
WSContentSend_P(PSTR(" | L%d"), relay_base +i);
}
WSContentSend_P(PSTR(" | {e}")); // Last column is units ({e} = |
)
char value_chr[SSPM_SIZE];
WSContentSend_PD(HTTP_SSPM_VOLTAGE, SSPMEnergyFormat(value_chr, Sspm->voltage[module], Settings->flag2.voltage_resolution, json));
WSContentSend_PD(HTTP_SSPM_CURRENT, SSPMEnergyFormat(value_chr, Sspm->current[module], Settings->flag2.current_resolution, json));
WSContentSend_PD(HTTP_SSPM_POWER, SSPMEnergyFormat(value_chr, Sspm->active_power[module], Settings->flag2.wattage_resolution, json));
char valu2_chr[SSPM_SIZE];
char valu3_chr[SSPM_SIZE];
WSContentSend_PD(HTTP_SSPM_ENERGY, SSPMEnergyFormat(value_chr, Sspm->apparent_power[module], Settings->flag2.wattage_resolution, json),
SSPMEnergyFormat(valu2_chr, Sspm->reactive_power[module], Settings->flag2.wattage_resolution, json),
SSPMEnergyFormat(valu3_chr, Sspm->total[module], Settings->flag2.energy_resolution, json));
WSContentSend_P(PSTR("
{t}")); // {t} = - Define for next FUNC_WEB_SENSOR
}
}
/*********************************************************************************************\
* Commands
\*********************************************************************************************/
const char kSSPMCommands[] PROGMEM = "SSPM|" // Prefix
"Log|Energy|History|Scan" ;
void (* const SSPMCommand[])(void) PROGMEM = {
&CmndSSPMLog, &CmndSSPMEnergy, &CmndSSPMEnergyHistory, &CmndSSPMScan };
void CmndSSPMLog(void) {
if ((XdrvMailbox.index < 1) || (XdrvMailbox.index > TasmotaGlobal.devices_present)) { XdrvMailbox.index = 1; }
XdrvMailbox.payload &= 0x1F; // Max 32 entries
SSPMSendGetLog(XdrvMailbox.index -1, XdrvMailbox.payload +1);
ResponseCmndDone();
}
void CmndSSPMEnergy(void) {
if ((XdrvMailbox.index < 1) || (XdrvMailbox.index > TasmotaGlobal.devices_present)) { XdrvMailbox.index = 1; }
SSPMSendGetEnergy(XdrvMailbox.index -1);
ResponseCmndDone();
}
void CmndSSPMEnergyHistory(void) {
if ((XdrvMailbox.index < 1) || (XdrvMailbox.index > TasmotaGlobal.devices_present)) { XdrvMailbox.index = 1; }
SSPMSendGetEnergyTotal(XdrvMailbox.index -1);
ResponseCmndDone();
}
void CmndSSPMScan(void) {
Sspm->mstate = SPM_START_SCAN;
ResponseCmndChar(PSTR(D_JSON_STARTED));
}
/*********************************************************************************************\
* Interface
\*********************************************************************************************/
bool Xdrv86(uint8_t function) {
bool result = false;
if (FUNC_INIT == function) {
SSPMInit();
}
else if (Sspm) {
switch (function) {
case FUNC_LOOP:
if (SspmSerial) { SSPMSerialInput(); }
break;
case FUNC_EVERY_100_MSECOND:
SSPMEvery100ms();
break;
case FUNC_SET_DEVICE_POWER:
result = SSPMSetDevicePower();
break;
case FUNC_JSON_APPEND:
SSPMEnergyShow(true);
break;
#ifdef USE_WEBSERVER
case FUNC_WEB_SENSOR:
SSPMEnergyShow(false);
break;
#endif // USE_WEBSERVER
case FUNC_COMMAND:
result = DecodeCommand(kSSPMCommands, SSPMCommand);
break;
case FUNC_BUTTON_PRESSED:
result = SSPMButton();
break;
}
}
return result;
}
#endif // USE_SONOFF_SPM
#endif // ESP32