From 93c1dd6bf9ffba381d85148e78ee96cd65c07a57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20L=C3=B3pez=20Mar=C3=ADn?= Date: Mon, 26 Aug 2019 23:25:19 +0200 Subject: [PATCH] Support Shelly 2.5 (#1827) * Add Shelly 2.5 * Add ADE7953 sensor support * Fix compilation * Use only one instance * WIP: Not working, I got StoreProhibited exception * Fix exception * Fix merge issues * Apply request review changes * declare struct before using it * uninterruptible delay * try optimized reg_size * fix types * Remove gpio 0 input in analog sensor * Add energy to ADE7953, it is not finished yet * Add new method to save energy with index * Finish energy for ADE7953 --- code/espurna/config/arduino.h | 2 + code/espurna/config/hardware.h | 51 +++++- code/espurna/config/progmem.h | 3 + code/espurna/config/sensors.h | 17 ++ code/espurna/config/types.h | 1 + code/espurna/sensor.ino | 75 +++++++- code/espurna/sensors/ADE7953Sensor.h | 249 +++++++++++++++++++++++++++ code/espurna/sensors/AnalogSensor.h | 2 +- code/platformio.ini | 10 ++ 9 files changed, 399 insertions(+), 11 deletions(-) create mode 100644 code/espurna/sensors/ADE7953Sensor.h diff --git a/code/espurna/config/arduino.h b/code/espurna/config/arduino.h index 6a8cf4e6..7dfc2f3d 100644 --- a/code/espurna/config/arduino.h +++ b/code/espurna/config/arduino.h @@ -12,6 +12,7 @@ //#define ALLTERCO_SHELLY1 //#define ALLTERCO_SHELLY2 //#define ALLTERCO_SHELLY1PM +//#define ALLTERCO_SHELLY25 //#define ARILUX_AL_LC01 //#define ARILUX_AL_LC02 //#define ARILUX_AL_LC02_V14 @@ -215,3 +216,4 @@ //#define V9261F_SUPPORT 1 //#define VEML6075_SUPPORT 1 //#define VL53L1X_SUPPORT 1 +//#define ADE7953_SUPPORT 1 diff --git a/code/espurna/config/hardware.h b/code/espurna/config/hardware.h index 471ce2ab..48880231 100644 --- a/code/espurna/config/hardware.h +++ b/code/espurna/config/hardware.h @@ -3147,8 +3147,8 @@ #define RELAY1_TYPE RELAY_TYPE_NORMAL #define RELAY2_PIN 5 #define RELAY2_TYPE RELAY_TYPE_NORMAL - -#elif defined(ALLTERCO_SHELLY1PM) + + #elif defined(ALLTERCO_SHELLY1PM) // Info #define MANUFACTURER "ALLTERCO" #define DEVICE "SHELLY1PM" @@ -3172,9 +3172,7 @@ #define LED1_PIN_INVERSE 1 // HJL01 / BL0937 - #ifndef HLW8012_SUPPORT #define HLW8012_SUPPORT 1 - #endif #define HLW8012_SEL_PIN 12 #define HLW8012_CF1_PIN 13 #define HLW8012_CF_PIN 5 @@ -3191,7 +3189,50 @@ #define NTC_BETA 3350 #define NTC_R_UP 10000 #define NTC_R_DOWN 0 - #define NTC_R0 10000 + #define NTC_R0 8000 + + #elif defined(ALLTERCO_SHELLY25) + // Info + #define MANUFACTURER "ALLTERCO" + #define DEVICE "SHELLY25" + + // Buttons + #define BUTTON1_PIN 13 + #define BUTTON1_MODE BUTTON_SWITCH + #define BUTTON1_RELAY 1 + + #define BUTTON2_PIN 5 + #define BUTTON2_MODE BUTTON_SWITCH + #define BUTTON2_RELAY 2 + + #define BUTTON3_PIN 2 + #define BUTTON3_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH + #define BUTTON3_LNGCLICK BUTTON_MODE_RESET + #define BUTTON3_LNGLNGCLICK BUTTON_MODE_FACTORY + + // Relays + #define RELAY1_PIN 4 + #define RELAY1_TYPE RELAY_TYPE_NORMAL + + #define RELAY2_PIN 15 + #define RELAY2_TYPE RELAY_TYPE_NORMAL + + // Light + #define LED1_PIN 0 + #define LED1_PIN_INVERSE 1 + + //Temperature + #define NTC_SUPPORT 1 + #define SENSOR_SUPPORT 1 + #define NTC_BETA 3350 + #define NTC_R_UP 10000 + #define NTC_R_DOWN 0 + #define NTC_R0 8000 + + //Current + #define ADE7953_SUPPORT 1 + #define I2C_SDA_PIN 12 + #define I2C_SCL_PIN 14 // ----------------------------------------------------------------------------- diff --git a/code/espurna/config/progmem.h b/code/espurna/config/progmem.h index c304b7b4..1b8aaa35 100644 --- a/code/espurna/config/progmem.h +++ b/code/espurna/config/progmem.h @@ -273,6 +273,9 @@ PROGMEM const char espurna_sensors[] = #if EZOPH_SUPPORT "EZOPH " #endif + #if ADE7953_SUPPORT + "ADE7953 " + #endif ""; diff --git a/code/espurna/config/sensors.h b/code/espurna/config/sensors.h index 79a636a9..d5796357 100644 --- a/code/espurna/config/sensors.h +++ b/code/espurna/config/sensors.h @@ -1255,6 +1255,19 @@ #define I2C_CLEAR_BUS 0 // Clear I2C bus on boot #define I2C_PERFORM_SCAN 1 // Perform a bus scan on boot +// ----------------------------------------------------------------------------- +// ADE7953 Shelly Sensor +// Enable support by passing ADE7953_SUPPORT=1 build flag +// ----------------------------------------------------------------------------- + +#ifndef ADE7953_SUPPORT +#define ADE7953_SUPPORT 0 +#endif + +#ifndef ADE7953_ADDRESS +#define ADE7953_ADDRESS 0x38 +#endif + //-------------------------------------------------------------------------------- // Class loading //-------------------------------------------------------------------------------- @@ -1405,4 +1418,8 @@ #include "../sensors/VL53L1XSensor.h" #endif +#if ADE7953_SUPPORT + #include "../sensors/ADE7953Sensor.h" +#endif + #endif // SENSOR_SUPPORT diff --git a/code/espurna/config/types.h b/code/espurna/config/types.h index adc20dd1..d389b6ae 100644 --- a/code/espurna/config/types.h +++ b/code/espurna/config/types.h @@ -314,6 +314,7 @@ #define SENSOR_BMP180_ID 34 #define SENSOR_MAX6675_ID 35 #define SENSOR_LDR_ID 36 +#define SENSOR_ADE7953_ID 37 //-------------------------------------------------------------------------------- // Magnitudes diff --git a/code/espurna/sensor.ino b/code/espurna/sensor.ino index 24999bde..ec6d4ae4 100644 --- a/code/espurna/sensor.ino +++ b/code/espurna/sensor.ino @@ -462,18 +462,37 @@ void _sensorResetTS() { #endif } -double _sensorEnergyTotal() { +double _sensorEnergyTotal(unsigned int index = -1) { double value = 0; if (rtcmemStatus()) { value = Rtcmem->energy; - } else { - value = (_sensor_save_every > 0) ? getSetting("eneTotal", 0).toInt() : 0; + } else { + if(index != -1) { + value = (_sensor_save_every > 0) ? getSetting("eneTotal", index, 0).toInt() : 0; + } else { + value = (_sensor_save_every > 0) ? getSetting("eneTotal", 0).toInt() : 0; + } } return value; } +void _sensorEnergyTotal(unsigned int index, double value) { + static unsigned long save_count = 0; + + // Save to EEPROM every '_sensor_save_every' readings + if (_sensor_save_every > 0) { + save_count = (save_count + 1) % _sensor_save_every; + if (0 == save_count) { + setSetting("eneTotal", index, value); + saveSettings(); + } + } + + // Always save to RTCMEM + Rtcmem->energy = value; +} void _sensorEnergyTotal(double value) { static unsigned long save_count = 0; @@ -1077,6 +1096,14 @@ void _sensorLoad() { _sensors.push_back(sensor); } #endif + + #if ADE7953_SUPPORT + { + ADE7953Sensor * sensor = new ADE7953Sensor(); + sensor->setAddress(ADE7953_ADDRESS); + _sensors.push_back(sensor); + } + #endif } void _sensorCallback(unsigned char i, unsigned char type, double value) { @@ -1216,6 +1243,19 @@ void _sensorInit() { #endif // HLW8012_SUPPORT + #if ADE7953_SUPPORT + + if (_sensors[i]->getID() == SENSOR_ADE7953_ID) { + ADE7953Sensor * sensor = (ADE7953Sensor *) _sensors[i]; + unsigned int dev_count = sensor->getTotalDevices(); + for(unsigned int dev = 0; dev < dev_count; dev++) { + double value = _sensorEnergyTotal(dev); + if (value > 0) sensor->resetEnergy(dev, value); + } + } + + #endif // ADE7953_SUPPORT + #if CSE7766_SUPPORT if (_sensors[i]->getID() == SENSOR_CSE7766_ID) { @@ -1452,6 +1492,22 @@ void _sensorConfigure() { #endif // PZEM004T_SUPPORT + #if ADE7953_SUPPORT + + if (_sensors[i]->getID() == SENSOR_ADE7953_ID) { + ADE7953Sensor * sensor = (ADE7953Sensor *) _sensors[i]; + if (getSetting("pwrResetE", 0).toInt() == 1) { + unsigned char dev_count = sensor->getTotalDevices(); + for(unsigned char dev = 0; dev < dev_count; dev++) { + sensor->resetEnergy(dev); + delSetting("eneTotal", dev); + } + _sensorResetTS(); + } + } + + #endif // ADE7953_SUPPORT + } // Update filter sizes @@ -1786,8 +1842,17 @@ void sensorLoop() { // Persist total energy value - if (MAGNITUDE_ENERGY == magnitude.type) { - _sensorEnergyTotal(value_raw); + if (MAGNITUDE_ENERGY == magnitude.type) { + if (magnitude.sensor->getID() != SENSOR_ADE7953_ID) { + _sensorEnergyTotal(value_raw); + } else { + #if ADE7953_SUPPORT + ADE7953Sensor * sensor = (ADE7953Sensor *) magnitude.sensor; + unsigned int dev_count = sensor->getTotalDevices(); + unsigned int dev = (magnitude.local / dev_count) - 1; + _sensorEnergyTotal(dev, value_raw); + #endif // ADE7953_SUPPORT + } } } // if (report_count == 0) diff --git a/code/espurna/sensors/ADE7953Sensor.h b/code/espurna/sensors/ADE7953Sensor.h new file mode 100644 index 00000000..5b920880 --- /dev/null +++ b/code/espurna/sensors/ADE7953Sensor.h @@ -0,0 +1,249 @@ +// ----------------------------------------------------------------------------- +// ADE7853 Sensor over I2C +// Copyright (C) 2017-2019 by Xose Pérez +// Implemented by Antonio López +// ----------------------------------------------------------------------------- + +#if SENSOR_SUPPORT && ADE7953_SUPPORT + +#pragma once + +#undef I2C_SUPPORT +#define I2C_SUPPORT 1 // Explicitly request I2C support. + +#include "Arduino.h" +#include "I2CSensor.h" +#include + +// ----------------------------------------------------------------------------- +// ADE7953 - Energy (Shelly 2.5) +// +// Based on datasheet from https://www.analog.com/en/products/ade7953.html +// Based on Tasmota code https://github.com/arendst/Sonoff-Tasmota/blob/development/sonoff/xnrg_07_ade7953.ino +// +// I2C Address: 0x38 +// ----------------------------------------------------------------------------- + +#define ADE7953_PREF 1540 +#define ADE7953_UREF 26000 +#define ADE7953_IREF 10000 + +#define ADE7953_ALL_RELAYS 0 +#define ADE7953_RELAY_1 1 +#define ADE7953_RELAY_2 2 + +#define ADE7953_VOLTAGE 1 +#define ADE7953_TOTAL_DEVICES 3 + +class ADE7953Sensor : public I2CSensor { + + protected: + + struct reading_t { + float current = 0.0; + float power = 0.0; + float energy = 0.0; + }; + + public: + // --------------------------------------------------------------------- + // Public + // --------------------------------------------------------------------- + ADE7953Sensor(): I2CSensor() { + _sensor_id = SENSOR_ADE7953_ID; + _readings.resize(ADE7953_TOTAL_DEVICES); + _energy_offsets.resize(ADE7953_TOTAL_DEVICES); + _count = _readings.size() * ADE7953_TOTAL_DEVICES + ADE7953_VOLTAGE; //10 + } + + // --------------------------------------------------------------------- + // Sensors API + // --------------------------------------------------------------------- + + // Initialization method, must be idempotent + void begin() { + if (!_dirty) return; + _init(); + _dirty = !_ready; + } + + // Descriptive name of the sensor + String description() { + char buffer[25]; + snprintf(buffer, sizeof(buffer), "ADE7953 @ I2C (0x%02X)", _address); + return String(buffer); + } + + // Descriptive name of the slot # index + String slot(unsigned char index) { + return description(); + }; + + // Type for slot # index + unsigned char type(unsigned char index) { + if (index == 0) return MAGNITUDE_VOLTAGE; + index = index % ADE7953_TOTAL_DEVICES; + if (index == 0) return MAGNITUDE_ENERGY; + if (index == 1) return MAGNITUDE_CURRENT; + if (index == 2) return MAGNITUDE_POWER_ACTIVE; + return MAGNITUDE_NONE; + } + + // Pre-read hook (usually to populate registers with up-to-date data) + void pre() { + uint32_t active_power1 = 0; + uint32_t active_power2 = 0; + uint32_t current_rms = 0; + uint32_t current_rms1 = 0; + uint32_t current_rms2 = 0; + uint32_t voltage_rms = 0; + + voltage_rms = read(_address, 0x31C); // Both relays + current_rms1 = read(_address, 0x31B); // Relay 1 + if (current_rms1 < 2000) { // No load threshold (20mA) + current_rms1 = 0; + active_power1 = 0; + } else { + active_power1 = (int32_t)read(_address, 0x313) * -1; // Relay 1 + active_power1 = (active_power1 > 0) ? active_power1 : 0; + } + current_rms2 = read(_address, 0x31A); // Relay 2 + if (current_rms2 < 2000) { // No load threshold (20mA) + current_rms2 = 0; + active_power2 = 0; + } else { + active_power2 = (int32_t)read(_address, 0x312); // Relay 2 + active_power2 = (active_power2 > 0) ? active_power2 : 0; + } + _voltage = (float) voltage_rms / ADE7953_UREF; + + storeReading( + ADE7953_ALL_RELAYS, + (float)(current_rms1 + current_rms2) / (ADE7953_IREF * 10), + (float)(active_power1 + active_power2) / (ADE7953_PREF / 10) + ); + storeReading( + ADE7953_RELAY_1, + (float) current_rms1 / (ADE7953_IREF * 10), + (float) active_power1 / (ADE7953_PREF / 10) + ); + storeReading( + ADE7953_RELAY_2, + (float)current_rms2 / (ADE7953_IREF * 10), + (float)active_power2 / (ADE7953_PREF / 10) + ); + } + + inline void storeReading(unsigned int relay, float current, float power) { + auto& reading_ref = _readings.at(relay); + reading_ref.current = current; + reading_ref.power = power; + static unsigned long last = 0; + if (last > 0) { + reading_ref.energy += (power * (millis() - last) / 1000); + } + last = millis(); + } + + // Current value for slot # index + double value(unsigned char index) { + if (index == 0) return _voltage; + int relay = (index - 1) / ADE7953_TOTAL_DEVICES; + index = index % ADE7953_TOTAL_DEVICES; + if (index == 0) return _energy_offsets[relay] + _readings[relay].energy; + if (index == 1) return _readings[relay].current; + if (index == 2) return _readings[relay].power; + return 0; + } + + unsigned int getTotalDevices() { + return ADE7953_TOTAL_DEVICES; + } + + void resetEnergy(int relay, double value = 0) { + _energy_offsets[relay] = value; + } + + protected: + void _init() { + nice_delay(100); // Need 100mS to init ADE7953 + write(_address, 0x102, 0x0004); // Locking the communication interface (Clear bit COMM_LOCK), Enable HPF + write(_address, 0x0FE, 0x00AD); // Unlock register 0x120 + write(_address, 0x120, 0x0030); // Configure optimum setting + + _ready = true; + } + + #if 0 + static int reg_size(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; + } + #else + // Optimized version of the function above, -80 bytes of code + // Use the known property of register addresses to calculate their size + static const int reg_size(const uint16_t reg) { + + const uint8_t mask = ((reg >> 8) & 0b1111); + + if (!mask || (mask & 0b1100)) { + return 1; + } else if (mask & 0b0011) { + return mask + 1; + } + + return 0; + + } + #endif + + void write(unsigned char address, uint16_t reg, uint32_t val) { + int size = reg_size(reg); + if (size) { + Wire.beginTransmission(address); + 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 + } + } + + static uint32_t read(int address, uint16_t reg) { + uint32_t response = 0; + const int size = reg_size(reg); + if (size) { + Wire.beginTransmission(address); + Wire.write((reg >> 8) & 0xFF); + Wire.write(reg & 0xFF); + Wire.endTransmission(0); + Wire.requestFrom(address, size); + if (size <= Wire.available()) { + for (int i = 0; i < size; i++) { + response = response << 8 | Wire.read(); // receive DATA (MSB first) + } + } + } + return response; + } + + std::vector _readings; + float _voltage = 0; + std::vector _energy_offsets; +}; + +#endif // SENSOR_SUPPORT && ADE7953_SUPPORT diff --git a/code/espurna/sensors/AnalogSensor.h b/code/espurna/sensors/AnalogSensor.h index d54f30cd..c8f12be8 100644 --- a/code/espurna/sensors/AnalogSensor.h +++ b/code/espurna/sensors/AnalogSensor.h @@ -68,7 +68,7 @@ class AnalogSensor : public BaseSensor { // --------------------------------------------------------------------- // Initialization method, must be idempotent - void begin() { + void begin() { _ready = true; } diff --git a/code/platformio.ini b/code/platformio.ini index 85b643d6..672455f7 100644 --- a/code/platformio.ini +++ b/code/platformio.ini @@ -1420,6 +1420,16 @@ build_flags = ${common.build_flags_2m1m} -DALLTERCO_SHELLY1PM upload_port = ${common.ota_upload_port} upload_flags = ${common.ota_upload_flags} +[env:allterco-shelly25] +board = ${common.board_2m} +build_flags = ${common.build_flags_2m1m} -DALLTERCO_SHELLY25 + +[env:allterco-shelly25-ota] +board = ${common.board_2m} +build_flags = ${common.build_flags_2m1m} -DALLTERCO_SHELLY25 +upload_port = ${common.ota_upload_port} +upload_flags = ${common.ota_upload_flags} + [env:xiaomi-smart-desk-lamp] board = ${common.board_1m} build_flags = ${common.build_flags_1m0m} -DXIAOMI_SMART_DESK_LAMP