From 6adb513cd637d4f83fc744ba13c3724a1bc5427d Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Sun, 14 Apr 2019 18:20:24 +0200 Subject: [PATCH] Add Shelly 2.5 Energy Monitoring (#5592) Add Shelly 2.5 Energy Monitoring (#5592) --- sonoff/_changelog.ino | 1 + sonoff/my_user_config.h | 1 + sonoff/xnrg_07_ade7953.ino | 244 +++++++++++++++++++++++++++++++++++++ 3 files changed, 246 insertions(+) create mode 100644 sonoff/xnrg_07_ade7953.ino diff --git a/sonoff/_changelog.ino b/sonoff/_changelog.ino index fa32f4b00..87a65954f 100644 --- a/sonoff/_changelog.ino +++ b/sonoff/_changelog.ino @@ -1,6 +1,7 @@ /* 6.5.0.8 20190413 * Fix use of SerialDelimiter value 128 (#5634) * Fix lost syslog connection regression from 6.5.0.4 + * Add Shelly 2.5 Energy Monitoring (#5592) * * 6.5.0.7 20190410 * Add command LedMask to assign which relay has access to power LED (#5602, #5612) diff --git a/sonoff/my_user_config.h b/sonoff/my_user_config.h index c076574e5..2d89e6e95 100644 --- a/sonoff/my_user_config.h +++ b/sonoff/my_user_config.h @@ -342,6 +342,7 @@ // #define USE_MGC3130 // Enable MGC3130 Electric Field Effect Sensor (I2C address 0x42) (+2k7 code, 0k3 mem) // #define USE_MAX44009 // Enable MAX44009 Ambient Light sensor (I2C addresses 0x4A and 0x4B) (+0k8 code) // #define USE_SCD30 // Enable Sensiron SCd30 CO2 sensor (I2C address 0x61) (+3k3 code) + #define USE_ADE7953 // Enable ADE7953 Energy monitor as used on Shelly 2.5 (I2C address 0x38) (+1k5) // #define USE_DISPLAY // Add I2C Display Support (+2k code) #define USE_DISPLAY_MODES1TO5 // Enable display mode 1 to 5 in addition to mode 0 diff --git a/sonoff/xnrg_07_ade7953.ino b/sonoff/xnrg_07_ade7953.ino new file mode 100644 index 000000000..e66b15f32 --- /dev/null +++ b/sonoff/xnrg_07_ade7953.ino @@ -0,0 +1,244 @@ +/* + xnrg_07_ade7953.ino - ADE7953 energy sensor support for Sonoff-Tasmota + + Copyright (C) 2019 Theo Arends + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifdef USE_I2C +#ifdef USE_ENERGY_SENSOR +#ifdef USE_ADE7953 +/*********************************************************************************************\ + * ADE7953 - Energy (Shelly 2.5) + * + * Based on datasheet from https://www.analog.com/en/products/ade7953.html + * + * I2C Address: 0x38 +\*********************************************************************************************/ + +#define XNRG_07 7 + +#define ADE7953_PREF 1540 +#define ADE7953_UREF 26000 +#define ADE7953_IREF 10000 + +#define ADE7953_ADDR 0x38 + +uint32_t ade7953_active_power = 0; +uint32_t ade7953_active_power1 = 0; +uint32_t ade7953_active_power2 = 0; +uint32_t ade7953_current_rms = 0; +uint32_t ade7953_current_rms1 = 0; +uint32_t ade7953_current_rms2 = 0; +uint32_t ade7953_voltage_rms = 0; +uint8_t ade7953_init = 0; + +int Ade7953RegSize(uint16_t reg) +{ + int size = 0; + switch ((reg >> 8) & 0x0F) { + case 0x03: + size++; + case 0x02: + size++; + case 0x01: + size++; + case 0x00: + case 0x07: + case 0x08: + size++; + } + return size; +} + +void Ade7953Write(uint16_t reg, uint32_t val) +{ + int size = Ade7953RegSize(reg); + if (size) { + Wire.beginTransmission(ADE7953_ADDR); + Wire.write((reg >> 8) & 0xFF); + Wire.write(reg & 0xFF); + while (size--) { + Wire.write((val >> (8 * size)) & 0xFF); // Write data, MSB first + } + Wire.endTransmission(); + delayMicroseconds(5); // Bus-free time minimum 4.7us + } +} + +uint32_t Ade7953Read(uint16_t reg) +{ + uint32_t response = 0; + + int size = Ade7953RegSize(reg); + if (size) { + Wire.beginTransmission(ADE7953_ADDR); + Wire.write((reg >> 8) & 0xFF); + Wire.write(reg & 0xFF); + Wire.endTransmission(0); + Wire.requestFrom(ADE7953_ADDR, size); + if (size <= Wire.available()) { + for (int i = 0; i < size; i++) { + response = response << 8 | Wire.read(); // receive DATA (MSB first) + } + } + } + return response; +} + +void Ade7953Init(void) +{ + Ade7953Write(0x102, 0x0004); // Locking the communication interface (Clear bit COMM_LOCK), Enable HPF + Ade7953Write(0x0FE, 0x00AD); // Unlock register 0x120 + Ade7953Write(0x120, 0x0030); // Configure optimum setting +} + +void Ade7953GetData(void) +{ + ade7953_voltage_rms = Ade7953Read(0x31C); // Both relays + ade7953_current_rms1 = Ade7953Read(0x31B); // Relay 1 + if (ade7953_current_rms1 < 2000) { // No load threshold (20mA) + ade7953_current_rms1 = 0; + ade7953_active_power1 = 0; + } else { + ade7953_active_power1 = (int32_t)Ade7953Read(0x313) * -1; // Relay 1 + } + ade7953_current_rms2 = Ade7953Read(0x31A); // Relay 2 + if (ade7953_current_rms2 < 2000) { // No load threshold (20mA) + ade7953_current_rms2 = 0; + ade7953_active_power2 = 0; + } else { + ade7953_active_power2 = (int32_t)Ade7953Read(0x312); // Relay 2 + } + // First phase only supports accumulated Current and Power + ade7953_current_rms = ade7953_current_rms1 + ade7953_current_rms2; + ade7953_active_power = ade7953_active_power1 + ade7953_active_power2; + + if (energy_power_on) { // Powered on + energy_voltage = (float)ade7953_voltage_rms / Settings.energy_voltage_calibration; + energy_active_power = (float)ade7953_active_power / (Settings.energy_power_calibration / 10); + if (0 == energy_active_power) { + energy_current = 0; + } else { + energy_current = (float)ade7953_current_rms / (Settings.energy_current_calibration * 10); + } + } else { // Powered off + energy_voltage = 0; + energy_active_power = 0; + energy_current = 0; + } +} + +void Ade7953EverySecond() +{ + if (ade7953_active_power) { + energy_kWhtoday_delta += ((ade7953_active_power * (100000 / (Settings.energy_power_calibration / 10))) / 3600); + EnergyUpdateToday(); + } + if (ade7953_init) { + if (1 == ade7953_init) { + Ade7953Init(); + } + ade7953_init--; + } + else { + Ade7953GetData(); + } +} + +void Ade7953DrvInit(void) +{ + if (!energy_flg) { + if (i2c_flg && (pin[GPIO_ADE7953_IRQ] < 99)) { // Irq on GPIO16 is not supported... + delay(100); // Need 100mS to init ADE7953 + if (I2cDevice(ADE7953_ADDR)) { + if (HLW_PREF_PULSE == Settings.energy_power_calibration) { + Settings.energy_power_calibration = ADE7953_PREF; + Settings.energy_voltage_calibration = ADE7953_UREF; + Settings.energy_current_calibration = ADE7953_IREF; + } + AddLog_P2(LOG_LEVEL_DEBUG, S_LOG_I2C_FOUND_AT, "ADE7953", ADE7953_ADDR); + ade7953_init = 2; + energy_flg = XNRG_07; + } + } + } +} + +bool Ade7953Command(void) +{ + bool serviced = true; + + double value = CharToDouble(XdrvMailbox.data); + + if (CMND_POWERCAL == energy_command_code) { + if (1 == XdrvMailbox.payload) { XdrvMailbox.payload = ADE7953_PREF; } + // Service in xdrv_03_energy.ino + } + else if (CMND_VOLTAGECAL == energy_command_code) { + if (1 == XdrvMailbox.payload) { XdrvMailbox.payload = ADE7953_UREF; } + // Service in xdrv_03_energy.ino + } + else if (CMND_CURRENTCAL == energy_command_code) { + if (1 == XdrvMailbox.payload) { XdrvMailbox.payload = ADE7953_IREF; } + // Service in xdrv_03_energy.ino + } + else if (CMND_POWERSET == energy_command_code) { + if (XdrvMailbox.data_len && ade7953_active_power) { + Settings.energy_power_calibration = (uint32_t)((double)ade7953_active_power / (value / 10)); // W + } + } + else if (CMND_VOLTAGESET == energy_command_code) { + if (XdrvMailbox.data_len && ade7953_voltage_rms) { + Settings.energy_voltage_calibration = (uint32_t)((double)ade7953_voltage_rms / value); // V + } + } + else if (CMND_CURRENTSET == energy_command_code) { + if (XdrvMailbox.data_len && ade7953_current_rms) { + Settings.energy_current_calibration = (uint32_t)((double)ade7953_current_rms / (value * 10)); // A + } + } + else serviced = false; // Unknown command + + return serviced; +} + +/*********************************************************************************************\ + * Interface +\*********************************************************************************************/ + +int Xnrg07(uint8_t function) +{ + int result = 0; + + if (FUNC_PRE_INIT == function) { + Ade7953DrvInit(); + } + else if (XNRG_07 == energy_flg) { + switch (function) { + case FUNC_EVERY_SECOND: + Ade7953EverySecond(); + break; + case FUNC_COMMAND: + result = Ade7953Command(); + break; + } + } + return result; +} + +#endif // USE_ADE7953 +#endif // USE_ENERGY_SENSOR +#endif // USE_I2C