Skip to content

Commit

Permalink
Preliminary support for CSE7766 (Sonoff S31 & Sonoff POW R2)
Browse files Browse the repository at this point in the history
  • Loading branch information
xoseperez committed Apr 3, 2018
1 parent 2b445ec commit 0e6f2d1
Show file tree
Hide file tree
Showing 10 changed files with 2,384 additions and 2,025 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ ESPurna ("spark" in Catalan) is a custom firmware for ESP8285/ESP8266 based smar
It uses the Arduino Core for ESP8266 framework and a number of 3rd party libraries.

[![version](https://img.shields.io/badge/version-1.12.5b-brightgreen.svg)](CHANGELOG.md)
![branch](https://img.shields.io/badge/branch-dev-orange.svg)
[![travis](https://travis-ci.org/xoseperez/espurna.svg?branch=dev)](https://travis-ci.org/xoseperez/espurna)
[![codacy](https://img.shields.io/codacy/grade/c9496e25cf07434cba786b462cb15f49/dev.svg)](https://www.codacy.com/app/xoseperez/espurna/dashboard)
![branch](https://img.shields.io/badge/branch-sensors-orange.svg)
[![travis](https://travis-ci.org/xoseperez/espurna.svg?branch=sensors)](https://travis-ci.org/xoseperez/espurna)
[![codacy](https://img.shields.io/codacy/grade/c9496e25cf07434cba786b462cb15f49/sensors.svg)](https://www.codacy.com/app/xoseperez/espurna/dashboard)
[![license](https://img.shields.io/github/license/xoseperez/espurna.svg)](LICENSE)
<br />
[![donate](https://img.shields.io/badge/donate-PayPal-blue.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=xose%2eperez%40gmail%2ecom&lc=US&no_note=0&currency_code=EUR&bn=PP%2dDonationsBF%3abtn_donate_LG%2egif%3aNonHostedGuest)
Expand Down
1 change: 1 addition & 0 deletions code/espurna/config/arduino.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
//#define ITEAD_SONOFF_T1_1CH
//#define ITEAD_SONOFF_T1_2CH
//#define ITEAD_SONOFF_T1_3CH
//#define ITEAD_SONOFF_S31
//#define YJZK_SWITCH_2CH
//#define ELECTRODRAGON_WIFI_IOT
//#define WORKCHOICE_ECOPLUG
Expand Down
27 changes: 27 additions & 0 deletions code/espurna/config/hardware.h
Original file line number Diff line number Diff line change
Expand Up @@ -768,6 +768,33 @@
#define LED1_PIN 13
#define LED1_PIN_INVERSE 1

#elif defined(ITEAD_SONOFF_S31)

// Info
#define MANUFACTURER "ITEAD"
#define DEVICE "SONOFF_S31"

// Buttons
#define BUTTON1_PIN 0
#define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH
#define BUTTON1_RELAY 1

// Relays
#define RELAY1_PIN 12
#define RELAY1_TYPE RELAY_TYPE_NORMAL

// LEDs
#define LED1_PIN 13
#define LED1_PIN_INVERSE 1

// Disable UART noise
#define TERMINAL_SUPPORT 0
#define DEBUG_SERIAL_SUPPORT 0

// CSE7766
#define CSE7766_SUPPORT 1
#define CSE7766_PIN 1

// -----------------------------------------------------------------------------
// YJZK
// -----------------------------------------------------------------------------
Expand Down
33 changes: 32 additions & 1 deletion code/espurna/config/sensors.h
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@
#define SENSOR_PZEM004T_ID 0x18
#define SENSOR_AM2320_ID 0x19
#define SENSOR_GUVAS12SD_ID 0x20
#define SENSOR_CSE7766_ID 0x21

//--------------------------------------------------------------------------------
// Magnitudes
Expand Down Expand Up @@ -236,6 +237,30 @@
#define DHT_TYPE DHT_CHIP_DHT22
#endif

//------------------------------------------------------------------------------
// CSE7766 based power sensor
// Enable support by passing CSE7766_SUPPORT=1 build flag
//------------------------------------------------------------------------------

#ifndef CSE7766_SUPPORT
#define CSE7766_SUPPORT 1
#endif

#ifndef CSE7766_PIN
#define CSE7766_PIN 1 // TX pin from the CSE7766
#endif

#ifndef CSE7766_PIN_INVERSE
#define CSE7766_PIN_INVERSE 0 // Signal is inverted
#endif

#define CSE7766_SYNC_INTERVAL 300 // Safe time between transmissions (ms)
#define CSE7766_BAUDRATE 4800 // UART baudrate

#define CSE7766_V1R 1.0 // 1mR current resistor
#define CSE7766_V2R 1.0 // 1M voltage resistor


//------------------------------------------------------------------------------
// Digital sensor
// Enable support by passing DIGITAL_SUPPORT=1 build flag
Expand Down Expand Up @@ -556,7 +581,8 @@
|| EMON_ADC121_SUPPORT || EMON_ADS1X15_SUPPORT \
|| EMON_ANALOG_SUPPORT || EVENTS_SUPPORT || HLW8012_SUPPORT \
|| MHZ19_SUPPORT || PMSX003_SUPPORT || SHT3X_I2C_SUPPORT \
|| SI7021_SUPPORT || V9261F_SUPPORT || AM2320_SUPPORT || GUVAS12SD_SUPPORT
|| SI7021_SUPPORT || V9261F_SUPPORT || AM2320_SUPPORT \
|| GUVAS12SD_SUPPORT || CSE7766_SUPPORT
#define SENSOR_SUPPORT 1
#else
#define SENSOR_SUPPORT 0
Expand Down Expand Up @@ -687,6 +713,11 @@ PROGMEM const char* const magnitude_units[] = {
#include "../sensors/BMX280Sensor.h"
#endif

#if CSE7766_SUPPORT
#include <SoftwareSerial.h>
#include "../sensors/CSE7766Sensor.h"
#endif

#if DALLAS_SUPPORT
#include <OneWire.h>
#include "../sensors/DallasSensor.h"
Expand Down
Binary file modified code/espurna/data/index.html.gz
Binary file not shown.
2 changes: 2 additions & 0 deletions code/espurna/sensors/BaseSensor.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
#define SENSOR_ERROR_CRC 5 // Sensor data corrupted
#define SENSOR_ERROR_I2C 6 // Wrong or locked I2C address
#define SENSOR_ERROR_GPIO_USED 7 // The GPIO is already in use
#define SENSOR_ERROR_CALIBRATION 8 // Calibration error or Not calibrated
#define SENSOR_ERROR_OTHER 99 // Any other error

typedef std::function<void(unsigned char, const char *)> TSensorCallback;

Expand Down
273 changes: 273 additions & 0 deletions code/espurna/sensors/CSE7766Sensor.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,273 @@
// -----------------------------------------------------------------------------
// CSE7766 based power monitor
// Copyright (C) 2018 by Xose Pérez <xose dot perez at gmail dot com>
// http://www.chipsea.com/UploadFiles/2017/08/11144342F01B5662.pdf
// -----------------------------------------------------------------------------

#if SENSOR_SUPPORT && CSE7766_SUPPORT

#pragma once

#include "Arduino.h"
#include "BaseSensor.h"

#include <SoftwareSerial.h>

class CSE7766Sensor : public BaseSensor {

public:

// ---------------------------------------------------------------------
// Public
// ---------------------------------------------------------------------

CSE7766Sensor(): BaseSensor(), _data() {
_count = 4;
_sensor_id = SENSOR_CSE7766_ID;
}

~CSE7766Sensor() {
if (_serial) delete _serial;
}

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

void setRX(unsigned char pin_rx) {
if (_pin_rx == pin_rx) return;
_pin_rx = pin_rx;
_dirty = true;
}

void setInverted(bool inverted) {
if (_inverted == inverted) return;
_inverted = inverted;
_dirty = true;
}

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

unsigned char getRX() {
return _pin_rx;
}

bool getInverted() {
return _inverted;
}

// ---------------------------------------------------------------------
// Sensor API
// ---------------------------------------------------------------------

// Initialization method, must be idempotent
void begin() {

if (!_dirty) return;

if (_serial) delete _serial;

_serial = new SoftwareSerial(_pin_rx, SW_SERIAL_UNUSED_PIN, _inverted, 32);
_serial->enableIntTx(false);
_serial->begin(CSE7766_BAUDRATE);

_ready = true;
_dirty = false;

}

// Descriptive name of the sensor
String description() {
char buffer[28];
snprintf(buffer, sizeof(buffer), "CSE7766 @ SwSerial(%u,NULL)", _pin_rx);
return String(buffer);
}

// Descriptive name of the slot # index
String slot(unsigned char index) {
return description();
};

// Address of the sensor (it could be the GPIO or I2C address)
String address(unsigned char index) {
return String(_pin_rx);
}

// Loop-like method, call it in your main loop
void tick() {
_read();
}

// Type for slot # index
unsigned char type(unsigned char index) {
if (index == 0) return MAGNITUDE_CURRENT;
if (index == 1) return MAGNITUDE_VOLTAGE;
if (index == 2) return MAGNITUDE_POWER_ACTIVE;
if (index == 3) return MAGNITUDE_ENERGY;
return MAGNITUDE_NONE;
}

// Current value for slot # index
double value(unsigned char index) {
if (index == 0) return _current;
if (index == 1) return _voltage;
if (index == 2) return _active;
if (index == 3) return _energy;
return 0;
}

protected:

// ---------------------------------------------------------------------
// Protected
// ---------------------------------------------------------------------

/**
* "
* Checksum is the sum of all data
* except for packet header and packet tail lowering by 8bit (...)
* "
* @return bool
*/
bool _checksum() {
unsigned char checksum = 0;
for (unsigned char i = 2; i < 23; i++) {
checksum += _data[i];
}
return checksum == _data[23];
}

void _process() {

// Checksum
if (!_checksum()) {
_error = SENSOR_ERROR_CRC;
#if SENSOR_DEBUG
DEBUG_MSG_P(PSTR("[SENSOR] CSE7766: Checksum error"));
#endif
return;
}

// Calibration
if (0xAA == _data[0]) {
_error = SENSOR_ERROR_CALIBRATION;
#if SENSOR_DEBUG
DEBUG_MSG_P(PSTR("[SENSOR] CSE7766: Chip not calibrated"));
#endif
return;
}

if ((_data[0] & 0xFC) > 0xF0) {
_error = SENSOR_ERROR_OTHER;
#if SENSOR_DEBUG
if (0xF1 == _data[0] & 0xF1) DEBUG_MSG_P(PSTR("[SENSOR] CSE7766: Abnormal coefficient storage area"));
if (0xF2 == _data[0] & 0xF2) DEBUG_MSG_P(PSTR("[SENSOR] CSE7766: Power cycle exceeded range"));
if (0xF4 == _data[0] & 0xF4) DEBUG_MSG_P(PSTR("[SENSOR] CSE7766: Current cycle exceeded range"));
if (0xF8 == _data[0] & 0xF8) DEBUG_MSG_P(PSTR("[SENSOR] CSE7766: Voltage cycle exceeded range"));
#endif
return;
}

// Calibration coefficients
if (0 == _coefV) {
_coefV = (_data[2] << 16 | _data[3] << 8 | _data[4]) / 100;
_coefV *= 100;
_coefC = (_data[8] << 16 | _data[9] << 8 | _data[10]);
_coefP = (_data[14] << 16 | _data[15] << 8 | _data[16]) / 1000;
_coefP *= 1000;
}

// Adj: this looks like a sampling report
uint8_t adj = _data[20];

// Calculate voltage
_voltage = 0;
if ((adj & 0x40) == 0x40) {
unsigned long voltage_cycle = _data[5] << 16 | _data[6] << 8 | _data[7];
_voltage = _coefV / voltage_cycle / CSE7766_V2R;
}

// Calculate power
_active = 0;
if ((adj & 0x10) == 0x10) {
if ((_data[0] & 0xF2) != 0xF2) {
unsigned long power_cycle = _data[17] << 16 | _data[18] << 8 | _data[19];
_active = _coefP / power_cycle / CSE7766_V1R / CSE7766_V2R;
}
}

// Calculate current
_current = 0;
if ((adj & 0x20) == 0x20) {
if (_active > 0) {
unsigned long current_cycle = _data[11] << 16 | _data[12] << 8 | _data[13];
_current = _coefC / current_cycle / CSE7766_V1R;
}
}

// Calculate energy
/*
static unsigned long cf_pulses_last = 0;
unsigned long cf_pulses = _data[21] << 8 | _data[22];
unsigned long frequency = cf_pulses - cf_pulses_last;
cf_pulses_last = cf_pulses;
_energy += (100000 * frequency * _coefP);
*/

}

void _read() {

_error = SENSOR_ERROR_OK;

static unsigned char index = 0;
static unsigned long last = millis();

while (_serial->available()) {

// A 24 bytes message takes ~55ms to go through at 4800 bps
// Reset counter if more than 1000ms have passed since last byte.
if (millis() - last > CSE7766_SYNC_INTERVAL) index = 0;
last = millis();

uint8_t byte = _serial->read();

// second byte in packet must be 0x5A
if ((1 == index) && (0xA5 != byte)) {
index = 0;
} else {
_data[index++] = byte;
if (index > 23) {
_serial->flush();
break;
}
}

}

// Process packet
if (24 == index) {
_process();
index = 0;
}

}

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

unsigned int _pin_rx = CSE7766_PIN;
bool _inverted = CSE7766_PIN_INVERSE;
SoftwareSerial * _serial = NULL;

double _active = 0;
double _voltage = 0;
double _current = 0;
double _energy = 0;

unsigned long _coefV = 0;
unsigned long _coefC = 0;
unsigned long _coefP = 0;

unsigned char _data[24];

};

#endif // SENSOR_SUPPORT && CSE7766_SUPPORT
Loading

0 comments on commit 0e6f2d1

Please sign in to comment.