Using a Sonoff S31 with Blynk

testing

This post will describe how the Itead Sonofff S31 can control a household AC electric device with a cellphone loaded with the Blynk application. Additionally, it can monitor energy usage by keeping track of real-time power, current and voltage of the connected appliances.

s31

A Blynk timer widget allows automatic on/off control via a time schedule. The provided source code also includes Amazon Alexa voice command compatibility.

screenshot_2019-01-21-08-08-56

The Sonoff S31 incorporates an CSE7766 single-phase, multi-function measuring chip, which provides measurements of electric current, voltage and power through the esp8266 UART. The Cse7766 operates at a baud rate of 4800bps (± 2%), with 8-bit data, 1 even parity check and 1 stop bit. The datasheet contains all the details and can be downloaded here.

powermeter

For disassembly of the Sonoff S31 and reflash instructions, see the blog here.


/*************************************************************************
 * Title: Simple ESP-8266 Amazon Echo/sonoff wifi relay control
 * File: sonoff_echo_blynk_Utility.ino
 * Author: James Eli
 * Date: 5/1/2017
 * 
 * Sonoff S31 ESP8266 "utility".
 *   Uses Blynk App V0 (switch), V1 (indicator LED), V2 (timer), V3 (voltage), 
 *   V4 (current), V5 (power), V6 (energy)
 *
 * This program controls a Sonoff wifi relay module communicating either 
 * through an amazon echo or the Blynk android application to the Sonoff 
 * onboard esp-8266 module. Amazon Echo responds to ECHO_KEY_WORD.
 *************************************************************************/
#include <ESP8266WiFi.h>
#include <DNSServer.h>
#include <WiFiManager.h> 
#include <ESP8266WebServer.h>
#include <BlynkSimpleEsp8266.h>
#include <WiFiUdp.h>
#include <ArduinoOTA.h>
#include <SimpleTimer.h>
#include "WemoSwitch.h"
#include "WemoManager.h"
#include "CallbackFunction.h"

bool isFirstConnect = true;       // Flag for re-sync on connection.
int relayState = LOW;             // Blynk app pushbutton status.

// Blynk app authentication code.
char auth[] = "...";

boolean SwitchReset = true;       // Flag indicating that the hardware button has been released

// esp8266 pins.
#define ESP8266_GPIO13  13        // Sonof green LED (LOW == ON).
#define ESP8266_GPIO0   0         // Sonoff pushbutton (LOW == pressed).
#define ESP8266_GPIO12  12        // Sonoff relay (HIGH == ON).
const int RELAY = ESP8266_GPIO12; // Relay switching pin. Relay is pin 12 on the SonOff
const int LED = ESP8266_GPIO13;   // On/off indicator LED. Onboard LED is 13 on Sonoff
const int SWITCH = ESP8266_GPIO0; // Pushbutton.

// CSE7766 data.
double power = 0;
double voltage = 0;
double current = 0;
double energy = 0;
double ratioV = 1.0;
double ratioC = 1.0;
double ratioP = 1.0;
// Serial data input buffer.
unsigned char serialBuffer[24];
// Serial error flags.
int error;
// Energy reset counter.
int energyResetCounter;
#define MAX_ENREGY_RESET_COUNT 12

// CSE7766 error codes.
#define SENSOR_ERROR_OK             0       // No error.
#define SENSOR_ERROR_OUT_OF_RANGE   1       // Result out of sensor range.
#define SENSOR_ERROR_WARM_UP        2       // Sensor is warming-up.
#define SENSOR_ERROR_TIMEOUT        3       // Response from sensor timed out.
#define SENSOR_ERROR_UNKNOWN_ID     4       // Sensor did not report a known ID.
#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.
#define CSE7766_V1R                 1.0     // 1mR current resistor.
#define CSE7766_V2R                 1.0     // 1M voltage resistor.

// Amazon echo response term & Wifi access point name.
#define ECHO_KEY_WORD "utility"

WemoManager wemoManager;
WemoSwitch *device = NULL;

SimpleTimer timer;

void setup() 
{
  // WiFiManager intialization.
  WiFiManager wifi;               

  // Initialize pins.
  pinMode( RELAY, OUTPUT );
  pinMode( LED, OUTPUT );
  pinMode( SWITCH, INPUT_PULLUP );
  delay( 10 );
  // Switch relay off, LED on.
  digitalWrite( RELAY, LOW );
  digitalWrite( LED, LOW ); 

  // Create AP, if necessary
  wifi.autoConnect( ECHO_KEY_WORD ); 
  // wemoManager used for alexa interface.
  wemoManager.begin();

  // Set WIFI module to STA mode
  WiFi.mode( WIFI_STA );
  
  // Format: Alexa invocation name, local port no, on callback, off callback
  device = new WemoSwitch( ECHO_KEY_WORD, 80, RelayOn, RelayOff );
  wemoManager.addDevice( *device );

  // Initialize Blynk.
  Blynk.config( auth );

  // Set ota details.
  ArduinoOTA.setHostname( "utility" );
  ArduinoOTA.begin();

  // Setup cse7766 serial.
  Serial.flush();
  Serial.begin( 4800 );

  // Start a timer for checking button presses @ 100ms intervals.
  timer.setInterval( 100, ButtonCheck );

  // Start a timer for checking cse7766 power monitor @ 1000ms intervals.
  timer.setInterval( 1000, ReadCse7766 );
  
  // Switch LED off to signal initialization complete.
  digitalWrite( LED, HIGH );
}

// Main program loop.
void loop() 
{
  wemoManager.serverLoop();
  ArduinoOTA.handle();
  Blynk.run();
  timer.run();
}

// Toggle the relay on
void RelayOn() 
{
  digitalWrite( RELAY, HIGH );
  relayState = true;
  Blynk.virtualWrite( V0, HIGH ); // Sync the Blynk button widget state
  Blynk.virtualWrite( V1, relayState*255 );
}

// Toggle the relay off
void RelayOff() 
{
  digitalWrite( RELAY, LOW );
  relayState = false;
  Blynk.virtualWrite( V0, LOW ); // Sync the Blynk button widget state
  Blynk.virtualWrite( V1, relayState*255 );
}

// Handle switch changes originating on the Blynk app
BLYNK_WRITE( V0 ) 
{
  int SwitchStatus = param.asInt();
  
  if ( SwitchStatus )
    RelayOn();
  else 
    RelayOff();
}

// Handle timer changes originating from the Blynk app.
BLYNK_WRITE( V2 ) 
{
  int SwitchStatus = param.asInt();
  
  if ( SwitchStatus )
    RelayOn();
  else 
    RelayOff();
}

// This function runs every time Blynk connection is established.
BLYNK_CONNECTED() 
{
  if ( isFirstConnect ) 
  {
    Blynk.syncAll();
    isFirstConnect = false;
  }
}

// Handle hardware switch activation.
void ButtonCheck() 
{
  // look for new button press
  boolean SwitchState = ( digitalRead( SWITCH ) );
  
  // toggle the switch if there's a new button press
  if ( !SwitchState && SwitchReset == true ) 
  {
    if ( relayState )
      RelayOff();
    else
      RelayOn();
  
    // Flag that indicates the physical button hasn't been released
    SwitchReset = false;
    delay( 50 );            // De-bounce interlude.
  } 
  else if ( SwitchState ) 
  {
    // reset flag the physical button release
    SwitchReset = true;
  }
}

// Relay toggle helper function.
void ToggleRelay() 
{
  relayState = !relayState;
  
  if ( relayState ) 
    RelayOn();
  else 
    RelayOff();
}

// CSE7766 checksum.
bool CheckSum() 
{
  unsigned char checksum = 0;
  
  for (unsigned char i = 2; i < 23; i  ) 
    checksum  = serialBuffer[i];

  return checksum == serialBuffer[23];
}

// Process a cse7766 data packet.
void ProcessCse7766Packet() 
{
  // Confirm packet checksum.
  if ( !CheckSum() ) 
  {
    error = SENSOR_ERROR_CRC;
    return;
  }

  // Check for calibration error.
  if ( serialBuffer[0] == 0xAA ) 
  {
    error = SENSOR_ERROR_CALIBRATION;
    return;
  }
  if ( (serialBuffer[0] & 0xFC) == 0xFC ) 
  {
    error = SENSOR_ERROR_OTHER;
    return;
  }

  // Retrieve calibration coefficients.
  unsigned long coefV = (serialBuffer[2] << 16 | serialBuffer[3] << 8 | serialBuffer[4] );
  unsigned long coefC = (serialBuffer[8] << 16 | serialBuffer[9] << 8 | serialBuffer[10]);
  unsigned long coefP = (serialBuffer[14] << 16 | serialBuffer[15] << 8 | serialBuffer[16]);
  uint8_t adj = serialBuffer[20];

  // Calculate voltage.
  voltage = 0;
  if ( (adj & 0x40) == 0x40 ) 
  {
    unsigned long voltageCycle = serialBuffer[5] << 16 | serialBuffer[6] << 8 | serialBuffer[7];
    voltage = ratioV*coefV/voltageCycle/CSE7766_V2R;
  }

  // Calculate power.
  power = 0;
  if ( (adj & 0x10) == 0x10 ) 
  {
    if ( (serialBuffer[0] & 0xF2) != 0xF2 ) 
    {
      unsigned long powerCycle = serialBuffer[17] << 16 | serialBuffer[18] << 8 | serialBuffer[19];
      power = ratioP*coefP/powerCycle/CSE7766_V1R/CSE7766_V2R;
    }
  }

  // Calculate current.
  current = 0;
  if ( (adj & 0x20) == 0x20 ) 
  {
    if ( power > 0 ) 
    {
      unsigned long currentCycle = serialBuffer[11] << 16 | serialBuffer[12] << 8 | serialBuffer[13];
      current = ratioC*coefC/currentCycle/CSE7766_V1R;
    }
  }

  // Calculate energy.
  unsigned int difference;
  static unsigned int cfPulsesLast = 0;
  unsigned int cfPulses = serialBuffer[21] << 8 | serialBuffer[22];
  
  if (0 == cfPulsesLast) 
    cfPulsesLast = cfPulses;
  
  if (cfPulses < cfPulsesLast) 
    difference = cfPulses   (0xFFFF - cfPulsesLast)   1;
  else
    difference = cfPulses - cfPulsesLast;
  
  energy  = difference*(float)coefP/1000000.0;
  cfPulsesLast = cfPulses;

  // Energy reset.
  if ( power == 0 )
    energyResetCounter  ;
  else
    energyResetCounter = 0;
  if ( energyResetCounter >= MAX_ENREGY_RESET_COUNT )
  {
    energy = 0.0;
    energyResetCounter = 0;
  }
   
  // Push data to Blynk app.
  Blynk.virtualWrite( V3, voltage ); // Voltage (Volts).
  Blynk.virtualWrite( V4, current ); // Current (Amps).
  Blynk.virtualWrite( V5, power );   // Power (Watts).
  Blynk.virtualWrite( V6, energy );  // Energy (kWh).
}

// Read serial cse7766 power monitor data packet.
void ReadCse7766() 
{
  // Assume a non-specific error.
  error = SENSOR_ERROR_OTHER; 
  static unsigned char index = 0;

  while ( Serial.available() > 0 )
  {
    // A 24 byte message takes ~55ms to go through at 4800 bps.
    index = 0;

    uint8_t input = Serial.read();

    // first byte must be 0x55 or 0xF?.
    if ( index == 0 ) 
    { 
      if ( (input != 0x55) && (input < 0xF0) ) 
        continue;
    }
    // second byte must be 0x5A.
    else if ( index == 1 ) 
    {
      if ( input != 0x5A ) 
      {
        index = 0;
        continue;
      }
    }
    
    serialBuffer[index  ] = input;
    
    if ( index > 23 ) 
    {
      Serial.flush();
      break;
    }
  }

  // Process packet.
  if ( index == 24 ) 
  {
    error = SENSOR_ERROR_OK;
    ProcessCse7766Packet();
    index = 0;
  }

  // Report error state (LED) of cse7766.
  if ( error == SENSOR_ERROR_OK )
    Blynk.virtualWrite( V18, 0 );
  else
    Blynk.virtualWrite( V18, 255 );
}
Advertisements

About Jim Eli

µC experimenter
This entry was posted in arduino, iot and tagged , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s