Add support for Shelly 3EM

Add support for Shelly 3EM (#13515)
This commit is contained in:
Theo Arends 2022-03-11 16:27:49 +01:00
parent ae484e28ba
commit bf01806e1d
4 changed files with 146 additions and 126 deletions

View File

@ -13,6 +13,7 @@ All notable changes to this project will be documented in this file.
- Full DS3231 integration and synchronisation when using UBX (=GPS), NTP or manual time
- LVGL Splash screen and ``SetOption135 1`` to disable splash screen
- Command ``RfTimeout 100..60000`` to disable duplicate RfReceive. Default 1000 (#15061)
- Support for Shelly 3EM (#13515)
### Changed
- Extent number of pulsetimers from 8 to 32 (#8266)

View File

@ -112,6 +112,7 @@ The latter links can be used for OTA upgrades too like ``OtaUrl http://ota.tasmo
- NeoPool commands ``NPpHMin``, ``NPpHMax``, ``NPpH``, ``NPRedox``, ``NPHydrolysis``, ``NPIonization``, ``NPChlorine`` and ``NPControl`` [#15015](https://github.com/arendst/Tasmota/issues/15015)
- NeoPool system voltages display
- TasmotaSerial implement ``end()``
- Support for Shelly 3EM [#13515](https://github.com/arendst/Tasmota/issues/13515)
- Full DS3231 integration and synchronisation when using UBX (=GPS), NTP or manual time
- ESP32 Berry always enable rules
- ESP32 Berry bootloop protection

View File

@ -761,7 +761,7 @@ bool RuleSetProcess(uint8_t rule_set, String &event_saved)
AddLog(LOG_LEVEL_DEBUG, PSTR("RUL-RP2: Event |%s|, Rule |%s|, Command(s) |%s|"), event.c_str(), event_trigger.c_str(), commands.c_str());
#endif
if (RulesRuleMatch(rule_set, event, event_trigger, stop_all_rules)) {
if (!event_trigger.startsWith(F("FILE#")) && RulesRuleMatch(rule_set, event, event_trigger, stop_all_rules)) {
if (Rules.no_execute) return true;
if (plen == plen2) { stop_all_rules = true; } // If BREAK was used on a triggered rule, Stop execution of this rule set
commands.trim();
@ -837,6 +837,36 @@ bool RuleSetProcess(uint8_t rule_set, String &event_saved)
/*******************************************************************************************/
String RuleLoadFile(const char* fname) {
/* Read a string from rule space data between 'ON FILE#<fname> DO ' and ' ENDON' like:
rule3 on file#calib.dat do {"rms":{"current_a":3166385,"voltage_a":-767262},"freq":0} endon
NOTE: String may not contain word 'ENDON'!!
*/
String filename = F("ON FILE#");
filename += fname;
filename += F(" DO ");
// filename.toUpperCase();
for (uint32_t i = 0; i < MAX_RULE_SETS; i++) {
if (!GetRuleLen(i)) { continue; }
String rules = GetRule(i);
rules.toUpperCase();
int start = rules.indexOf(filename);
if (start == -1) { continue; }
start += filename.length();
int end = rules.indexOf(F(" ENDON"), start);
if (end == -1) { continue; }
rules = GetRule(i);
return rules.substring(start, end); // {"rms":{"current_a":3166385,"voltage_a":-767262},"freq":0}
}
// AddLog(LOG_LEVEL_DEBUG, PSTR("RUL: File '%s' not found or empty"), fname);
return "";
}
/*******************************************************************************************/
bool RulesProcessEvent(const char *json_event)
{
#ifdef USE_BERRY

View File

@ -17,6 +17,16 @@
* Based on datasheet from https://www.analog.com/en/products/ade7880.html
*
* I2C Address: 0x38
*********************************************************************************************
* - ATTENTION Before installing Tasmota retrieve the calibration data from the Shelly 3EM:
* - Download file calib.dat from your configured shelly 3EM http://<shelly3em_ip_address>/calib.dat
* - Search your Shelly 3EM firmware dump for calib.dat
* - Edit the file to become a single line. Notice the removal of unneeded data to make the string as short as possible:
* {"rms":{"current_a":3166385,"current_b":3125691,"current_c":3131983,"current_s":1756557,"voltage_a":-767262,"voltage_b":-763439,"voltage_c":-749854},"angles":{"angle0":180,"angle1":176,"angle2":176},"powers":{"totactive": {"a":-1345820,"b":-1347328,"c":-1351979}},"freq":0}
* - Install Tasmota and use the above template.
* - In addition to possible rules add a rule containing the calib.dat string from above like:
* rule3 on file#calib.dat do {"rms":{"current_a":3166385,"current_b":3125691,"current_c":3131983,"current_s":1756557,"voltage_a":-767262,"voltage_b":-763439,"voltage_c":-749854},"angles":{"angle0":180,"angle1":176,"angle2":176},"powers":{"totactive": {"a":-1345820,"b":-1347328,"c":-1351979}},"freq":0} endon
* - Restart Tasmota and obeserve that the results seem calibrated as Tasmota now uses the information from calib.dat
\*********************************************************************************************/
#define XNRG_23 23
@ -29,51 +39,7 @@
//#define ADE7880_DEBUG
//#define ADE7880_PROFILING
/*
Derive these parameters from the original Shelly 3EM 4M firmware dump. Look for JSON file called calib.data
{
"state": 0,
"rms": {
"current_a": 3166385,
"current_b": 3125691,
"current_c": 3131983,
"current_n": -1474892307,
"current_s": 1756557,
"voltage_a": -767262,
"voltage_b": -763439,
"voltage_c": -749854
},
"angles": {
"angle0": 180,
"angle1": 176,
"angle2": 176
},
"powers": {
"totactive": {
"a": -1345820,
"b": -1347328,
"c": -1351979
},
"apparent": {
"a": 214966,
"b": 214961,
"c": 214949
}
},
"energies": {
"totactive": {
"a": 8752,
"b": 8751,
"c": 8751
},
"apparent": {
"a": 40496,
"b": 40513,
"c": 40524
}
}
}
*/
// Default calibration parameters can be overridden by a rule as documented above.
#define ADE7880_FREQ_INIT 0 // Connected to networks with fundamental frequencies between 55 Hz and 66 Hz (1). Default 45 Hz to 55 Hz (0).
#define ADE7880_AIGAIN_INIT 3166385 // rms, current_a
#define ADE7880_BIGAIN_INIT 3125691 // rms, current_b
@ -391,32 +357,32 @@ int32_t Ade7880ReadVerify(uint16_t reg) {
/*********************************************************************************************/
bool Ade7880Init(void) {
// Init sequence about 100mS after reset - See page 40 (takes 68ms)
// Init sequence about 6mS after reset - See page 40 (takes 68ms)
#ifdef ADE7880_PROFILING
uint32_t start = millis();
#endif // ADE7880_PROFILING
uint32_t status1 = Ade7880ReadVerify(ADE7880_STATUS1); // 0x01A08000
uint32_t status1 = Ade7880ReadVerify(ADE7880_STATUS1); // 0xE503 - 0x01A08000
if (bitRead(status1, 15)) { // RSTDONE
// Power on or Reset
Ade7880WriteVerify(ADE7880_CONFIG2, 0x02); // ADE7880_I2C_LOCK
Ade7880WriteVerify(ADE7880_STATUS1, 0x3FFE8930); // Acknowledge RSTDONE - Reset IRQ1 line
status1 = Ade7880ReadVerify(ADE7880_STATUS1); // 0x01A00007
Ade7880WriteVerify(ADE7880_CONFIG2, 0x02); // 0xEC01 - ADE7880_I2C_LOCK
Ade7880WriteVerify(ADE7880_STATUS1, 0x3FFE8930); // 0xE503 - Acknowledge RSTDONE - Reset IRQ1 line
status1 = Ade7880ReadVerify(ADE7880_STATUS1); // 0xE503 - 0x01A00007
} else {
return false;
}
uint8_t version = Ade7880ReadVerify(ADE7880_Version); // 0x01
Ade7880WriteVerify(ADE7880_Gain, 0x0000); // Gain register set to 1 for current, and voltage
uint8_t version = Ade7880ReadVerify(ADE7880_Version); // 0xE707 - 0x01
Ade7880WriteVerify(ADE7880_Gain, 0x0000); // 0xE60F - Gain register set to 1 for current, and voltage
if (Ade7880.calib_frequency) {
Ade7880WriteVerify(ADE7880_COMPMODE, 0x41FF); // Connected to networks with fundamental frequencies between 55 Hz and 66 Hz. Default is 45 Hz and 55 Hz.
Ade7880WriteVerify(ADE7880_COMPMODE, 0x41FF); // 0xE60E - Connected to networks with fundamental frequencies between 55 Hz and 66 Hz. Default is 45 Hz and 55 Hz.
}
for (uint32_t phase = 0; phase < 3; phase++) {
Ade7880WriteVerify(ADE7880_AVGAIN + (phase * 2), Ade7880.calib_voltage[phase]);
Ade7880WriteVerify(ADE7880_AIGAIN + (phase * 2), Ade7880.calib_current[phase]);
Ade7880WriteVerify(ADE7880_APGAIN + (phase * 2), Ade7880.calib_acpower[phase]);
Ade7880WriteVerify(ADE7880_APHCAL + phase, Ade7880.calib_angle[phase]);
Ade7880WriteVerify(ADE7880_AVGAIN + (phase * 2), Ade7880.calib_voltage[phase]); // 0x4381
Ade7880WriteVerify(ADE7880_AIGAIN + (phase * 2), Ade7880.calib_current[phase]); // 0x4380
Ade7880WriteVerify(ADE7880_APGAIN + (phase * 2), Ade7880.calib_acpower[phase]); // 0x4389
Ade7880WriteVerify(ADE7880_APHCAL + phase, Ade7880.calib_angle[phase]); // 0xE614
}
Ade7880WriteVerify(ADE7880_NIGAIN, Ade7880.calib_current[3]);
Ade7880WriteVerify(ADE7880_NIGAIN, Ade7880.calib_current[3]); // 0x4386
bool error = false;
for (uint32_t phase = 0; phase < 3; phase++) {
if (Ade7880ReadVerify(ADE7880_AVGAIN + (phase * 2)) != (Ade7880.calib_voltage[phase] & 0x0FFFFFFF)) { error = true; }
@ -429,27 +395,27 @@ bool Ade7880Init(void) {
AddLog(LOG_LEVEL_DEBUG, PSTR("A78: Error initializing parameters"));
return false;
}
if (!Ade7880WriteVerify(ADE7880_LCYCMODE, 0x09)) { // Line cycle accumulation mode
if (!Ade7880WriteVerify(ADE7880_LCYCMODE, 0x09)) { // 0xE702 - Line cycle accumulation mode
// - Watt-hour accumulation registers (AWATTHR, BWATTHR, CWATTHR, AFWATTHR, BFWATTHR, and CFWATTHR) are placed into line cycle accumulation mode.
// - Phase A is selected for zero-crossings counts in the line cycle accumulation mode.
AddLog(LOG_LEVEL_DEBUG, PSTR("A78: Error setting LCYCMODE register"));
return false;
}
if (!Ade7880WriteVerify(ADE7880_LINECYC, 0x0064)) { // = 100
if (!Ade7880WriteVerify(ADE7880_LINECYC, 0x0064)) { // 0xE60C - = 100
AddLog(LOG_LEVEL_DEBUG, PSTR("A78: Error setting LINECYC register"));
return false;
}
Ade7880WriteVerify(ADE7880_MASK0, 0x00000020); // IRQ0 at end of an integration over an integer number of half line cycles set in the LINECYC register.
if (!Ade7880VerifyWrite(ADE7880_MASK0)) {
Ade7880WriteVerify(ADE7880_MASK0, 0x00000020); // 0xE50A - IRQ0 at end of an integration over an integer number of half line cycles set in the LINECYC register.
if (!Ade7880VerifyWrite(ADE7880_MASK0)) { // 0xE50A
AddLog(LOG_LEVEL_DEBUG, PSTR("A78: Error setting MASK0 register"));
return false;
}
Ade7880Write(ADE7880_MASK0, 0x00000020);
Ade7880Write(ADE7880_MASK0, 0x00000020);
Ade7880Write(ADE7880_MASK0, 0x00000020);
Ade7880Write(ADE7880_DSPWP_SEL, 0xAD); // Select DSP write protection
Ade7880Write(ADE7880_DSPWP_SET, 0x80); // Write protect DSP area
Ade7880WriteVerify(ADE7880_Run, 0x0201); // Start DSP
Ade7880Write(ADE7880_MASK0, 0x00000020); // 0xE50A
Ade7880Write(ADE7880_MASK0, 0x00000020); // 0xE50A
Ade7880Write(ADE7880_MASK0, 0x00000020); // 0xE50A
Ade7880Write(ADE7880_DSPWP_SEL, 0xAD); // 0xE7FE - Select DSP write protection
Ade7880Write(ADE7880_DSPWP_SET, 0x80); // 0xE7E3 - Write protect DSP area
Ade7880WriteVerify(ADE7880_Run, 0x0201); // 0xE228 - Start DSP
#ifdef ADE7880_PROFILING
AddLog(LOG_LEVEL_DEBUG, PSTR("A78: Init done in %d ms"), millis() - start);
@ -498,12 +464,12 @@ void Ade7880Cycle(void) {
uint32_t start = millis();
#endif // ADE7880_PROFILING
uint32_t status0 = Ade7880ReadVerify(ADE7880_STATUS0); // 0x000FEFE0
uint32_t status0 = Ade7880ReadVerify(ADE7880_STATUS0); // 0xE502 - 0x000FEFE0
if (!bitRead(status0, 5)) { // LENERGY
return;
} else {
Ade7880WriteVerify(ADE7880_STATUS0, 0x00000020); // Acknowledge LENERGY - Reset IRQ0 line
status0 = Ade7880ReadVerify(ADE7880_STATUS0); // 0x000FEFC0
Ade7880WriteVerify(ADE7880_STATUS0, 0x00000020); // 0xE502 - Acknowledge LENERGY - Reset IRQ0 line
status0 = Ade7880ReadVerify(ADE7880_STATUS0); // 0xE502 - 0x000FEFC0
}
if (Ade7880.cycle_count) { // Allow calibration stabilization
Ade7880.cycle_count--;
@ -511,12 +477,12 @@ void Ade7880Cycle(void) {
}
for (uint32_t phase = 0; phase < 3; phase++) {
Energy.data_valid[phase] = 0;
Energy.voltage[phase] = (float)Ade7880ReadVerify(ADE7880_AVRMS + (phase * 2)) / 10000; // 0x0024CC94 = 241.1668 V
Energy.current[phase] = (float)Ade7880ReadVerify(ADE7880_AIRMS + (phase * 2)) / 1000000; // 0x00002D6D = 0.011629 A
Energy.active_power[phase] = (float)Ade7880ReadVerify(ADE7880_AWATT + phase) / 100; // 0xFFFFF524 = -27.79 W (wrong calibration)
Energy.apparent_power[phase] = (float)Ade7880ReadVerify(ADE7880_AVA + phase) / 100; // 0xFFFFF50D
Energy.frequency[phase] = 256000.0f / Ade7880ReadVerify(ADE7880_APERIOD + phase); // Page 34 and based on ADE7880_FREQ_INIT
Ade7880.active_energy[phase] = Ade7880ReadVerify(ADE7880_AWATTHR + phase); // 0xFFFFFF8F = -1.12 Whr ??
Energy.voltage[phase] = (float)Ade7880ReadVerify(ADE7880_AVRMS + (phase * 2)) / 10000; // 0x43C1 - 0x0024CC94 = 241.1668 V
Energy.current[phase] = (float)Ade7880ReadVerify(ADE7880_AIRMS + (phase * 2)) / 1000000; // 0x43C0 - 0x00002D6D = 0.011629 A
Energy.active_power[phase] = (float)Ade7880ReadVerify(ADE7880_AWATT + phase) / 100; // 0xE513 - 0xFFFFF524 = -27.79 W (wrong calibration)
Energy.apparent_power[phase] = (float)Ade7880ReadVerify(ADE7880_AVA + phase) / 100; // 0xE519 - 0xFFFFF50D
Energy.frequency[phase] = 256000.0f / Ade7880ReadVerify(ADE7880_APERIOD + phase); // 0xE905 - Page 34 and based on ADE7880_FREQ_INIT
Ade7880.active_energy[phase] = Ade7880ReadVerify(ADE7880_AWATTHR + phase); // 0xE400 - 0xFFFFFF8F = -0.112 kWhr ??
}
#ifdef ADE7880_PROFILING
@ -549,55 +515,17 @@ void Ade7880EnergyEverySecond(void) {
}
}
void Ade7880Defaults(void) {
Ade7880.calib_frequency = ADE7880_FREQ_INIT;
Ade7880.calib_current[0] = ADE7880_AIGAIN_INIT;
Ade7880.calib_current[1] = ADE7880_BIGAIN_INIT;
Ade7880.calib_current[2] = ADE7880_CIGAIN_INIT;
Ade7880.calib_current[3] = ADE7880_NIGAIN_INIT;
Ade7880.calib_voltage[0] = ADE7880_AVGAIN_INIT;
Ade7880.calib_voltage[1] = ADE7880_BVGAIN_INIT;
Ade7880.calib_voltage[2] = ADE7880_CVGAIN_INIT;
Ade7880.calib_acpower[0] = ADE7880_APGAIN_INIT;
Ade7880.calib_acpower[1] = ADE7880_BPGAIN_INIT;
Ade7880.calib_acpower[2] = ADE7880_CPGAIN_INIT;
Ade7880.calib_angle[0] = ADE7880_APHCAL_INIT;
Ade7880.calib_angle[1] = ADE7880_BPHCAL_INIT;
Ade7880.calib_angle[2] = ADE7880_CPHCAL_INIT;
}
void Ade7880DrvInit(void) {
if (PinUsed(GPIO_ADE7880_IRQ) && PinUsed(GPIO_ADE7880_IRQ, 1)) {
if (I2cSetDevice(ADE7880_ADDR)) {
I2cSetActiveFound(ADE7880_ADDR, "ADE7880");
pinMode(Pin(GPIO_ADE7880_IRQ), INPUT);
attachInterrupt(Pin(GPIO_ADE7880_IRQ), Ade7880Isr0, FALLING);
Ade7880.irq0_state = 0;
pinMode(Pin(GPIO_ADE7880_IRQ, 1), INPUT);
Ade7880Defaults();
if (Ade7880SetCalibrate()) {
Energy.phase_count = 3; // Three phases
// Settings->flag5.energy_phase = 1; // SetOption129 - (Energy) Show phase information
// Energy.use_overtemp = true; // Use global temperature for overtemp detection
TasmotaGlobal.energy_driver = XNRG_23;
}
}
}
}
bool Ade7880Command(void) {
bool serviced = false;
if (CMND_ENERGYCONFIG == Energy.command_code) {
if (XdrvMailbox.data_len) {
bool Ade7880SetDefaults(const char* json) {
// {"rms":{"current_a":3166385,"current_b":3125691,"current_c":3131983,"current_s":1756557,"voltage_a":-767262,"voltage_b":-763439,"voltage_c":-749854},"angles":{"angle0":180,"angle1":176,"angle2":176},"powers":{"totactive": {"a":-1345820,"b":-1347328,"c":-1351979}},"freq":0}
JsonParser parser(XdrvMailbox.data);
uint32_t len = strlen(json) +1;
if (len < 7) { return false; } // Too short
char json_buffer[len];
memcpy(json_buffer, json, len); // Keep original safe
JsonParser parser(json_buffer);
JsonParserObject root = parser.getRootObject();
if (root) {
Ade7880Defaults();
if (!root) { return false; }
// All parameters are optional allowing for partial changes
JsonParserToken val = root[PSTR("freq")];
if (val) { Ade7880.calib_frequency = val.getUInt(); }
@ -639,7 +567,67 @@ bool Ade7880Command(void) {
if (val) { Ade7880.calib_acpower[2] = val.getInt(); }
}
}
return true;
}
void Ade7880Defaults(void) {
Ade7880.calib_frequency = ADE7880_FREQ_INIT;
Ade7880.calib_current[0] = ADE7880_AIGAIN_INIT;
Ade7880.calib_current[1] = ADE7880_BIGAIN_INIT;
Ade7880.calib_current[2] = ADE7880_CIGAIN_INIT;
Ade7880.calib_current[3] = ADE7880_NIGAIN_INIT;
Ade7880.calib_voltage[0] = ADE7880_AVGAIN_INIT;
Ade7880.calib_voltage[1] = ADE7880_BVGAIN_INIT;
Ade7880.calib_voltage[2] = ADE7880_CVGAIN_INIT;
Ade7880.calib_acpower[0] = ADE7880_APGAIN_INIT;
Ade7880.calib_acpower[1] = ADE7880_BPGAIN_INIT;
Ade7880.calib_acpower[2] = ADE7880_CPGAIN_INIT;
Ade7880.calib_angle[0] = ADE7880_APHCAL_INIT;
Ade7880.calib_angle[1] = ADE7880_BPHCAL_INIT;
Ade7880.calib_angle[2] = ADE7880_CPHCAL_INIT;
#ifdef USE_RULES
// rule3 on file#calib.dat do {"rms":{"current_a":3166385,"current_b":3125691,"current_c":3131983,"current_s":1756557,"voltage_a":-767262,"voltage_b":-763439,"voltage_c":-749854},"angles":{"angle0":180,"angle1":176,"angle2":176},"powers":{"totactive": {"a":-1345820,"b":-1347328,"c":-1351979}},"freq":0} endon
String calib = RuleLoadFile("CALIB.DAT");
if (calib.length()) {
// AddLog(LOG_LEVEL_DEBUG, PSTR("A78: File '%s'"), calib.c_str());
Ade7880SetDefaults(calib.c_str());
}
#endif // USE_RULES
}
void Ade7880DrvInit(void) {
if (PinUsed(GPIO_ADE7880_IRQ) && PinUsed(GPIO_ADE7880_IRQ, 1)) {
if (I2cSetDevice(ADE7880_ADDR)) {
I2cSetActiveFound(ADE7880_ADDR, "ADE7880");
pinMode(Pin(GPIO_ADE7880_IRQ), INPUT);
attachInterrupt(Pin(GPIO_ADE7880_IRQ), Ade7880Isr0, FALLING);
Ade7880.irq0_state = 0;
pinMode(Pin(GPIO_ADE7880_IRQ, 1), INPUT);
Ade7880Defaults();
if (Ade7880SetCalibrate()) {
Energy.phase_count = 3; // Three phases
// Settings->flag5.energy_phase = 1; // SetOption129 - (Energy) Show phase information
// Energy.use_overtemp = true; // Use global temperature for overtemp detection
TasmotaGlobal.energy_driver = XNRG_23;
}
}
}
}
bool Ade7880Command(void) {
bool serviced = false;
if (CMND_ENERGYCONFIG == Energy.command_code) {
// EnergyConfig {"rms":{"current_a":3166385,"current_b":3125691,"current_c":3131983,"current_s":1756557,"voltage_a":-767262,"voltage_b":-763439,"voltage_c":-749854},"angles":{"angle0":180,"angle1":176,"angle2":176},"powers":{"totactive": {"a":-1345820,"b":-1347328,"c":-1351979}},"freq":0}
// EnergyConfig {"rms":{"voltage_c":-549854}}
// EnergyCOnfig {"freq":0}
if (XdrvMailbox.data_len) {
Ade7880Defaults(); // Load defaults
if (Ade7880SetDefaults(XdrvMailbox.data)) {
bool status = Ade7880SetCalibrate();
AddLog(LOG_LEVEL_DEBUG, PSTR("A78: Re-init status %d"), status);
}