Browse Source

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
master
Antonio López Marín 5 years ago
committed by Max Prokhorov
parent
commit
93c1dd6bf9
9 changed files with 399 additions and 11 deletions
  1. +2
    -0
      code/espurna/config/arduino.h
  2. +46
    -5
      code/espurna/config/hardware.h
  3. +3
    -0
      code/espurna/config/progmem.h
  4. +17
    -0
      code/espurna/config/sensors.h
  5. +1
    -0
      code/espurna/config/types.h
  6. +70
    -5
      code/espurna/sensor.ino
  7. +249
    -0
      code/espurna/sensors/ADE7953Sensor.h
  8. +1
    -1
      code/espurna/sensors/AnalogSensor.h
  9. +10
    -0
      code/platformio.ini

+ 2
- 0
code/espurna/config/arduino.h View File

@ -12,6 +12,7 @@
//#define ALLTERCO_SHELLY1 //#define ALLTERCO_SHELLY1
//#define ALLTERCO_SHELLY2 //#define ALLTERCO_SHELLY2
//#define ALLTERCO_SHELLY1PM //#define ALLTERCO_SHELLY1PM
//#define ALLTERCO_SHELLY25
//#define ARILUX_AL_LC01 //#define ARILUX_AL_LC01
//#define ARILUX_AL_LC02 //#define ARILUX_AL_LC02
//#define ARILUX_AL_LC02_V14 //#define ARILUX_AL_LC02_V14
@ -215,3 +216,4 @@
//#define V9261F_SUPPORT 1 //#define V9261F_SUPPORT 1
//#define VEML6075_SUPPORT 1 //#define VEML6075_SUPPORT 1
//#define VL53L1X_SUPPORT 1 //#define VL53L1X_SUPPORT 1
//#define ADE7953_SUPPORT 1

+ 46
- 5
code/espurna/config/hardware.h View File

@ -3147,8 +3147,8 @@
#define RELAY1_TYPE RELAY_TYPE_NORMAL #define RELAY1_TYPE RELAY_TYPE_NORMAL
#define RELAY2_PIN 5 #define RELAY2_PIN 5
#define RELAY2_TYPE RELAY_TYPE_NORMAL #define RELAY2_TYPE RELAY_TYPE_NORMAL
#elif defined(ALLTERCO_SHELLY1PM)
#elif defined(ALLTERCO_SHELLY1PM)
// Info // Info
#define MANUFACTURER "ALLTERCO" #define MANUFACTURER "ALLTERCO"
#define DEVICE "SHELLY1PM" #define DEVICE "SHELLY1PM"
@ -3172,9 +3172,7 @@
#define LED1_PIN_INVERSE 1 #define LED1_PIN_INVERSE 1
// HJL01 / BL0937 // HJL01 / BL0937
#ifndef HLW8012_SUPPORT
#define HLW8012_SUPPORT 1 #define HLW8012_SUPPORT 1
#endif
#define HLW8012_SEL_PIN 12 #define HLW8012_SEL_PIN 12
#define HLW8012_CF1_PIN 13 #define HLW8012_CF1_PIN 13
#define HLW8012_CF_PIN 5 #define HLW8012_CF_PIN 5
@ -3191,7 +3189,50 @@
#define NTC_BETA 3350 #define NTC_BETA 3350
#define NTC_R_UP 10000 #define NTC_R_UP 10000
#define NTC_R_DOWN 0 #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
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------


+ 3
- 0
code/espurna/config/progmem.h View File

@ -273,6 +273,9 @@ PROGMEM const char espurna_sensors[] =
#if EZOPH_SUPPORT #if EZOPH_SUPPORT
"EZOPH " "EZOPH "
#endif #endif
#if ADE7953_SUPPORT
"ADE7953 "
#endif
""; "";


+ 17
- 0
code/espurna/config/sensors.h View File

@ -1255,6 +1255,19 @@
#define I2C_CLEAR_BUS 0 // Clear I2C bus on boot #define I2C_CLEAR_BUS 0 // Clear I2C bus on boot
#define I2C_PERFORM_SCAN 1 // Perform a bus scan 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 // Class loading
//-------------------------------------------------------------------------------- //--------------------------------------------------------------------------------
@ -1405,4 +1418,8 @@
#include "../sensors/VL53L1XSensor.h" #include "../sensors/VL53L1XSensor.h"
#endif #endif
#if ADE7953_SUPPORT
#include "../sensors/ADE7953Sensor.h"
#endif
#endif // SENSOR_SUPPORT #endif // SENSOR_SUPPORT

+ 1
- 0
code/espurna/config/types.h View File

@ -314,6 +314,7 @@
#define SENSOR_BMP180_ID 34 #define SENSOR_BMP180_ID 34
#define SENSOR_MAX6675_ID 35 #define SENSOR_MAX6675_ID 35
#define SENSOR_LDR_ID 36 #define SENSOR_LDR_ID 36
#define SENSOR_ADE7953_ID 37
//-------------------------------------------------------------------------------- //--------------------------------------------------------------------------------
// Magnitudes // Magnitudes


+ 70
- 5
code/espurna/sensor.ino View File

@ -462,18 +462,37 @@ void _sensorResetTS() {
#endif #endif
} }
double _sensorEnergyTotal() {
double _sensorEnergyTotal(unsigned int index = -1) {
double value = 0; double value = 0;
if (rtcmemStatus()) { if (rtcmemStatus()) {
value = Rtcmem->energy; 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; 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) { void _sensorEnergyTotal(double value) {
static unsigned long save_count = 0; static unsigned long save_count = 0;
@ -1077,6 +1096,14 @@ void _sensorLoad() {
_sensors.push_back(sensor); _sensors.push_back(sensor);
} }
#endif #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) { void _sensorCallback(unsigned char i, unsigned char type, double value) {
@ -1216,6 +1243,19 @@ void _sensorInit() {
#endif // HLW8012_SUPPORT #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 CSE7766_SUPPORT
if (_sensors[i]->getID() == SENSOR_CSE7766_ID) { if (_sensors[i]->getID() == SENSOR_CSE7766_ID) {
@ -1452,6 +1492,22 @@ void _sensorConfigure() {
#endif // PZEM004T_SUPPORT #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 // Update filter sizes
@ -1786,8 +1842,17 @@ void sensorLoop() {
// Persist total energy value // 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) } // if (report_count == 0)


+ 249
- 0
code/espurna/sensors/ADE7953Sensor.h View File

@ -0,0 +1,249 @@
// -----------------------------------------------------------------------------
// ADE7853 Sensor over I2C
// Copyright (C) 2017-2019 by Xose Pérez <xose dot perez at gmail dot com>
// Implemented by Antonio López <tonilopezmr at gmail dot com>
// -----------------------------------------------------------------------------
#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 <Wire.h>
// -----------------------------------------------------------------------------
// 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<reading_t> _readings;
float _voltage = 0;
std::vector<double> _energy_offsets;
};
#endif // SENSOR_SUPPORT && ADE7953_SUPPORT

+ 1
- 1
code/espurna/sensors/AnalogSensor.h View File

@ -68,7 +68,7 @@ class AnalogSensor : public BaseSensor {
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
// Initialization method, must be idempotent // Initialization method, must be idempotent
void begin() {
void begin() {
_ready = true; _ready = true;
} }


+ 10
- 0
code/platformio.ini View File

@ -1420,6 +1420,16 @@ build_flags = ${common.build_flags_2m1m} -DALLTERCO_SHELLY1PM
upload_port = ${common.ota_upload_port} upload_port = ${common.ota_upload_port}
upload_flags = ${common.ota_upload_flags} 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] [env:xiaomi-smart-desk-lamp]
board = ${common.board_1m} board = ${common.board_1m}
build_flags = ${common.build_flags_1m0m} -DXIAOMI_SMART_DESK_LAMP build_flags = ${common.build_flags_1m0m} -DXIAOMI_SMART_DESK_LAMP


Loading…
Cancel
Save