Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Shelly 2.5 #1827

Merged
merged 19 commits into from
Aug 26, 2019
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions code/espurna/config/arduino.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -213,3 +214,4 @@
//#define V9261F_SUPPORT 1
//#define VEML6075_SUPPORT 1
//#define VL53L1X_SUPPORT 1
//#define ADE7953_SUPPORT 1
55 changes: 46 additions & 9 deletions code/espurna/config/hardware.h
Original file line number Diff line number Diff line change
Expand Up @@ -3102,8 +3102,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"
Expand All @@ -3122,14 +3122,8 @@
#define RELAY1_PIN 15
#define RELAY1_TYPE RELAY_TYPE_NORMAL

// Light
#define LED1_PIN 0
mcspr marked this conversation as resolved.
Show resolved Hide resolved
#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
Expand All @@ -3146,7 +3140,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

// -----------------------------------------------------------------------------

Expand Down
3 changes: 3 additions & 0 deletions code/espurna/config/progmem.h
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,9 @@ PROGMEM const char espurna_sensors[] =
#if EZOPH_SUPPORT
"EZOPH "
#endif
#if ADE7953_SUPPORT
"ADE7953 "
#endif
"";


Expand Down
17 changes: 17 additions & 0 deletions code/espurna/config/sensors.h
Original file line number Diff line number Diff line change
Expand Up @@ -1011,6 +1011,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
//--------------------------------------------------------------------------------
Expand Down Expand Up @@ -1161,4 +1174,8 @@
#include "../sensors/VL53L1XSensor.h"
#endif

#if ADE7953_SUPPORT
#include "../sensors/ADE7953Sensor.h"
#endif

#endif // SENSOR_SUPPORT
1 change: 1 addition & 0 deletions code/espurna/config/types.h
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,7 @@
#define SENSOR_BMP180_ID 34
#define SENSOR_MAX6675_ID 35
#define SENSOR_LDR_ID 36
#define SENSOR_ADE7953_ID 37

//--------------------------------------------------------------------------------
// Magnitudes
Expand Down
8 changes: 8 additions & 0 deletions code/espurna/sensor.ino
Original file line number Diff line number Diff line change
Expand Up @@ -880,6 +880,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) {
Expand Down
215 changes: 215 additions & 0 deletions code/espurna/sensors/ADE7953Sensor.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
// -----------------------------------------------------------------------------
// 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 {

public:
// ---------------------------------------------------------------------
// Public
// ---------------------------------------------------------------------
ADE7953Sensor(): I2CSensor() {
_sensor_id = SENSOR_ADE7953_ID;
_readings.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 = Ade7953Read(0x31C); // Both relays
current_rms1 = Ade7953Read(0x31B); // Relay 1
if (current_rms1 < 2000) { // No load threshold (20mA)
current_rms1 = 0;
active_power1 = 0;
} else {
active_power1 = (int32_t)Ade7953Read(0x313) * -1; // Relay 1
active_power1 = (active_power1 > 0) ? active_power1 : 0;
}
current_rms2 = Ade7953Read(0x31A); // Relay 2
if (current_rms2 < 2000) { // No load threshold (20mA)
current_rms2 = 0;
active_power2 = 0;
} else {
active_power2 = (int32_t)Ade7953Read(0x312); // Relay 2
active_power2 = (active_power2 > 0) ? active_power2 : 0;
}
_voltage = (float) voltage_rms / ADE7953_UREF;

writeFloat(
ADE7953_ALL_RELAYS,
(float)(current_rms1 + current_rms2) / (ADE7953_IREF * 10),
(float)(active_power1 + active_power2) / (ADE7953_PREF / 10)
);
writeFloat(
ADE7953_RELAY_1,
(float) current_rms1 / (ADE7953_IREF * 10),
(float) active_power1 / (ADE7953_PREF / 10)
);
writeFloat(
ADE7953_RELAY_2,
(float)current_rms2 / (ADE7953_IREF * 10),
(float)active_power2 / (ADE7953_PREF / 10)
);
}

void writeFloat(unsigned int relay, float current, float power) {
mcspr marked this conversation as resolved.
Show resolved Hide resolved
auto& reading_ref = _readings.at(relay);
reading_ref.current = current;
reading_ref.power = power;
}

// 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 _readings[relay].energy;
if (index == 1) return _readings[relay].current;
if (index == 2) return _readings[relay].power;
return 0;
}

protected:
void _init() {
delay(100); // Need 100mS to init ADE7953
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

_ready = true;
}

int Ade7953RegSize(uint16_t reg) {
Copy link
Collaborator

@mcspr mcspr Jul 22, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might as well be static, since we don't use any member access. And we already are inside a class, Ade7953 prefix is redundant.

I was somewhat confused by the sizing function, is this mentioned somewhere in the datasheet?

Kind-of related
If it is really how it works, I tried making it more compact (idk about what is more readable though...)

const int reg_size_new(const uint16_t reg) {

    const uint8_t mask = ((reg >> 8) & 0b1111); // edit: this was a redundant & 0b11111111, and is this even needed?

    if (!mask || (mask & 0b1100)) {
        return 1;
    } else if (mask & 0b0011) {
        return mask + 1;
    }

    return 0;

}

You can try this with ~/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-gdb (you'll need start building something with espressif8266@2.2.2 platform first, it will move the old one to a separate dir)

(gdb) disassemble reg_size
Dump of assembler code for function reg_size(unsigned short):
   0x40201018 <+0>:     extui   a2, a2, 8, 4
   0x4020101b <+3>:     movi.n  a3, 8
   0x4020101d <+5>:     bltu    a3, a2, 0x40201069 <reg_size(unsigned short)+81>
   0x40201020 <+8>:     l32r    a3, 0x40201014
   0x40201023 <+11>:    addx4   a2, a2, a3
   0x40201026 <+14>:    l32i.n  a2, a2, 0
   0x40201028 <+16>:    jx      a2
   0x4020102b <+19>:    and     a6, a3, a0
   0x4020102e <+22>:    excw
   0x40201031 <+25>:    excw
   0x40201034 <+28>:    or      a1, a0, a5
   0x40201037 <+31>:    and     a5, a5, a4
   0x4020103a <+34>:    excw
   0x4020103d <+37>:    excw
   0x40201040 <+40>:    s32i.n  a6, a0, 4
   0x40201042 <+42>:    excw
   0x40201045 <+45>:    excw
   0x40201048 <+48>:    excw
   0x4020104b <+51>:    and     a6, a3, a4
   0x4020104e <+54>:    excw
   0x40201051 <+57>:    s8i     a0, a6, 0
   0x40201054 <+60>:    andbc   b0, b12, b0
   0x40201057 <+63>:    addi.n  a2, a2, 1
   0x40201059 <+65>:    j       0x4020105e <reg_size(unsigned short)+70>
   0x4020105c <+68>:    movi.n  a2, 0
   0x4020105e <+70>:    addi.n  a2, a2, 1
   0x40201060 <+72>:    j       0x40201065 <reg_size(unsigned short)+77>
   0x40201063 <+75>:    movi.n  a2, 0
   0x40201065 <+77>:    addi.n  a2, a2, 1
   0x40201067 <+79>:    ret.n
   0x40201069 <+81>:    movi.n  a2, 0
   0x4020106b <+83>:    ret.n
(gdb) disassemble reg_size_new
Dump of assembler code for function reg_size_new(unsigned short):
   0x40202e9c <+0>:     extui   a3, a2, 8, 8
   0x40202e9f <+3>:     extui   a5, a3, 0, 4 // edit: this is the "& 0b1111" aka bitfield extraction
   0x40202ea2 <+6>:     movi.n  a2, 1
   0x40202ea4 <+8>:     beqz.n  a5, 0x40202eb8 <reg_size_new(unsigned short)+28>
   0x40202ea6 <+10>:    movi.n  a4, 12
   0x40202ea8 <+12>:    and     a4, a3, a4
   0x40202eab <+15>:    bnez.n  a4, 0x40202eb8 <reg_size_new(unsigned short)+28>
   0x40202ead <+17>:    add.n   a5, a5, a2
   0x40202eaf <+19>:    extui   a3, a3, 0, 2
   0x40202eb2 <+22>:    or      a2, a4, a4
   0x40202eb5 <+25>:    movnez  a2, a5, a3
   0x40202eb8 <+28>:    ret.n

At least what it seems like, that the code size goes down 77 bytes.
edit: ...it might also might not matter at all. i will try with full build later. current tests are with __attribute__((noinline)) an a small test project

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I need to learn more about moving bytes, I'm not really sure what reg_size does, but it seems to be a very important function.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And just to be clear about this, what happens is:

>>> '{:#06b}'.format(0x31C >> 8)
'0b0011'
>>> '{:#06b}'.format(0x21C >> 8)
'0b0010'

Then, when checking last 2 bits

>>> (0b0011 & 0b0011) + 1
4
>>> (0b0010 & 0b0011) + 1
3

So, the 0x3... prefix contains 4 bytes => 32 bits and 0x2... contains 3 bytes => 24 bits

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) {
mcspr marked this conversation as resolved.
Show resolved Hide resolved
int size = Ade7953RegSize(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
}
}

uint32_t Ade7953Read(uint16_t reg) {
mcspr marked this conversation as resolved.
Show resolved Hide resolved
uint32_t response = 0;

int size = Ade7953RegSize(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;
}

typedef struct {
mcspr marked this conversation as resolved.
Show resolved Hide resolved
float current = 0.0;
float power = 0.0;
float energy = 0.0;
} reading_t;

std::vector<reading_t> _readings;
float _voltage = 0;
};

#endif // SENSOR_SUPPORT && ADE7953_SUPPORT
10 changes: 10 additions & 0 deletions code/platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -1394,6 +1394,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
Expand Down