Blynk-ing an ANAVI Light Controller

The ANAVI Light Controller is an open source hardware WiFi development board. As the name suggests it controls 12V RGB LED strip lights and can retrieve data from various I2C sensor modules for temperature, humidity, light and gestures. In this application, I am not using any of these sensors, however, in the future I do intend to experiment with an APDS-9960, primarily for gesture detection.

I want to incorporate LED strip lighting into my existing home automation project. This project is primarily controlled via a Blynk application on my android phone. In this demonstration project, the Blynk application requires 4 slider widgets- one each for the R, G, B colors and an additional one for controlling overall brightness of the LEDs. An additional button widget can be added to provide instant on/off capability and a timer widget could be used for scheduled operation. I further use the functionality of the button to incorporate Alexa voice commands. Use of the timer widget option is not discussed here.

At the heart of the ANAVI Light Controller is an ESP8266-12E. The ESP8266 programming pins are exposed, so the ANAVI board can easily be re-flashed. GPIO0 of ESP8266 is connected to the button called SW1 allowing effortless entry into the programming mode. Furthermore, there is a small red indication LED marked as D1 which could be used for various purposes. In my application, I use this red LED to signal completion of device setup. In order to flash new firmware on ANAVI Light Controller you need a USB to UART serial debug cable and a 12V power supply. Full instruction for re-flashing can be found here.

My replacement firmware code is below. For this project I utilized the PlatformIO IDE with Visual Studio Code, however the Arduino IDE development environment could also be used. In my application, the Alexa voice commands simply allow turning all of the LEDS on and off and is not used for color selection. Also, please note my choice of Blynk virtual pin numbers is purely arbitrary. Good luck.

/*************************************************************************
 * Title: Simple ESP-8266 Wifi RGB LED Strip Light Controller
 * File: main.cpp (esp8266_anavi_rgb_led_controller.ino)
 * Author: James Eli
 * Date: 4/19/2019
 *
 * Program controls an RGB LED strip light via ANAVI light controller device. 
 *
 *************************************************************************
 * 4/19/2019: Migrated to platformio. JME
 *************************************************************************/
#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <DNSServer.h>
#include <WiFiManager.h> 
#include <ESP8266WebServer.h>
#include <BlynkSimpleEsp8266.h>
#include <WiFiUdp.h>
#include <SimpleTimer.h>
#include "WemoSwitch.h"
#include "WemoManager.h"
#include "CallbackFunction.h"
#include <ArduinoOTA.h>

// Blynk App authentication token, wifi ssid and password.
char auth[] = "...";
char ssid[] = "...";
char pass[] = "...";

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

// Maximum brightness.
#define MAX_BRIGHTNESS 255

// Esp8266 pins.
const int ESP8266_RED_PIN   = 12;
const int ESP8266_GREEN_PIN = 13;
const int ESP8266_BLUE_PIN  = 14;
const int ALARM_PIN = 16;
// Blynk virtual pin vs. anavi hw pin assignments.
// Red   = Blynk virtual pin V30 = anavi pin 12
// Green = Blynk virtual pin V31 = anavi pin 13
// Blue  = Blynk virtual pin V32 = anavi pin 14
// V29 = BUTTON
// RGB slider values (0-255).
int red, green, blue;
// Brightness slider (0-255).
int brightness;
// Button status.
int button;
// Connect resync flag.
bool isFirstConnect = true;

WemoManager wemoManager;
WemoSwitch *device = NULL;

void otaStarted() { Serial.println("OTA update started!"); }
void otaFinished() { Serial.println("OTA update finished!"); }
void otaProgress(unsigned int progress, unsigned int total) { Serial.printf("OTA progress: %u%%\r", (progress / (total / 100))); }
void otaError(ota_error_t error) {
    Serial.printf("Error [%u]: ", error);
    switch (error) {
        case OTA_AUTH_ERROR:
            Serial.println("auth failed!");
            break;
        case OTA_BEGIN_ERROR:
            Serial.println("begin failed!");
            break;
        case OTA_CONNECT_ERROR:
            Serial.println("connect failed!");
            break;
        case OTA_RECEIVE_ERROR:
            Serial.println("receive failed!");
            break;
        case OTA_END_ERROR:
            Serial.println("end failed!");
            break;
    }
}

// Turn all LED lines off.
void LEDsOff() 
{
  analogWrite(ESP8266_RED_PIN, 0);
  analogWrite(ESP8266_GREEN_PIN, 0);
  analogWrite(ESP8266_BLUE_PIN, 0);
}

// Turn LEDS on.
void LEDsOn() 
{
  analogWrite(ESP8266_RED_PIN, (red*brightness)/MAX_BRIGHTNESS);
  analogWrite(ESP8266_GREEN_PIN, (green*brightness)/MAX_BRIGHTNESS);
  analogWrite(ESP8266_BLUE_PIN, (blue*brightness)/MAX_BRIGHTNESS);
}

void setup() 
{
  // LED.
  pinMode(ALARM_PIN, OUTPUT);
  digitalWrite(ALARM_PIN, HIGH);

  // WiFiManager intialization.
  WiFiManager wifi;               

  // Init pins.
  pinMode( ESP8266_RED_PIN, OUTPUT );
  pinMode( ESP8266_GREEN_PIN, OUTPUT );
  pinMode( ESP8266_BLUE_PIN, OUTPUT );

  // Set ota details.
  ArduinoOTA.onStart(otaStarted);
  ArduinoOTA.onEnd(otaFinished);
  ArduinoOTA.onProgress(otaProgress);
  ArduinoOTA.onError(otaError);
  ArduinoOTA.setHostname("cabinets");
  ArduinoOTA.begin();

  // 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, LEDsOn, LEDsOff );
  wemoManager.addDevice( *device );

  // Report success.
  digitalWrite(ALARM_PIN, LOW);

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

void loop() 
{
  wemoManager.serverLoop();
  ArduinoOTA.handle();
  Blynk.run();
}

void syncPins()
{
  LEDsOn();

  if (red || green || blue || brightness)
  { 
    Blynk.virtualWrite(V29, 1);
    button = 1;
  }
  else 
  {
    Blynk.virtualWrite(V29, 0);
    button = 0;
  }
}

// Read value from r, g, b and brightness sliders.
BLYNK_WRITE(V30) { red = param.asInt();  syncPins(); }
BLYNK_WRITE(V31) { green = param.asInt(); syncPins(); }
BLYNK_WRITE(V32) { blue = param.asInt(); syncPins(); }
BLYNK_WRITE(V33) { brightness = param.asInt(); syncPins(); }

// Button.
BLYNK_WRITE(V29)
{
    button = param.asInt();
    
    if (button == 0) 
      LEDsOff();
    else if (red || green || blue || brightness)
      LEDsOn();
    else 
    {
      Blynk.virtualWrite(V29, 0);
      button = 0;
    }  
}

BLYNK_CONNECTED()
{
  if (isFirstConnect)
  {
    Blynk.syncAll();
    isFirstConnect = false;
  }
}
Advertisements
Posted in arduino, iot | Tagged , , , | Leave a comment

Controlling an Itead Sonoff B1 Color LED Bulb Using Blynk

The Itead Sonoff B1 color LED bulb is a 6W (2A maximum), 600lm output, RGB full color, dimmable bulb with an integrated ESP8285 WIFI chip. As purchased it can be controlled via the proprietary EWeLink application. However, I wanted to incorporate the bulb into my existing home automation project which is primarily controlled via a Blynk application on my android phone.

In order to fully control the bulb, the Blynk application will require at least 5 slider widgets (one each for the RGB, cool and white colors). An additional button widget can be added to provide instant on/off capability and a timer widget could be used for scheduled operation. Use of the timer widget option is not discussed here.

A quick search of the internet revealed directions on how to disassemble, connect to, and re-flash the ESP8285 chip. This process required soldering 4 wires to test points on the bulb’s PCB. The wires are then used to connect a USB-to-Serial type programmer. After reprogramming is completed, the wires are detached. For simplicity, I utilized the Arduino IDE development environment with the ESP8266 add-on for all of the programming needs.

To disassemble the bulb, I used a small plastic auto trim tool to assist in the removal of the translucent cover. My cover was lightly tacked in place through the use of 2 very small dabs of an extremely weak glue. I simply applied light pressure working my small plastic wedge around the perimeter of the cover, which easily separated. The cover simply snapped back in place.

My replacement firmware code is below. Please note my choice of Blynk virtual pin numbers is purely arbitrary. Unlike other advice found on the Internet, I used the Arduino IDE’s Generic ESP8285 board, 1M (64 SPIFFS) flash size and DOUT upload settings during programming. Good luck.

/*************************************************************************
 * Title: Simple ESP-8266 Wifi Sonoff B1 LED Light Controller
 * File: sonoff_echo_blynk_sonoff_b1.ino
 * Author: James Eli
 * Date: 2/7/2019
 *
 * This program controls a Sonoff B1 RGB LED light via the blynk app. 
 * 
 * Notes:
 *  (1) To place an ESP8266 into program mode, GPIO0 must be LOW during power up. 
 *  (2) See: https://github.com/arendst/Sonoff-Tasmota/wiki/Sonoff-B1-and-B1-R2
 *  and: https://tinkerman.cat/sonoff-b1-lights-and-shades/
 *        
 * Upload Settings:
 *   Board: Generic Generic ESP8285
 *   Flash size: 1M (64 SPIFFS)
 *   Flash Mode: DOUT
*************************************************************************
 * Change Log:
 *   1/14/2018: Initial release. JME
 *************************************************************************/
#include <ESP8266WiFi.h>
#include <BlynkSimpleEsp8266.h>

// Blynk App authentication token, wifi ssid and password.
char auth[] = "..."; 
char ssid[] = "...";
char pass[] = "...";

// Sonoff B1/esp8266 pins.
#define DI_PIN  12 // GPIO12 
#define DCK_PIN 14 // GPIO14 
// Blynk virtual pins.
// V15 = red slider [0-255].
// V16 = green slider [0-255].
// V17 = blue slider [0-255].
// V19 = button [0-1]
// V20 = warm white slider [0-255].
// V21 = cool white slider [0-255].

// Slider values, initally set OFF.
int red = 0;
int green = 0;
int blue = 0;
int warmWhite = 0;
int coolWhite = 0;
// Button status.
int button = 0;

// Connect resync flag.
bool isFirstConnect = true;

// See MY9231 driver library @ https://github.com/xoseperez/my92xx
void pulseDI(uint8_t times)
{
  for (uint8_t i = 0; i < times; i++) 
  {
    digitalWrite(DI_PIN, HIGH);
    digitalWrite(DI_PIN, LOW);
  }
}

void pulseDCK(uint8_t times)
{
  for (uint8_t i = 0; i < times; i++) 
  {
    digitalWrite(DCK_PIN, HIGH);
    digitalWrite(DCK_PIN, LOW);
  }
}

void writeData(uint8_t data)
{
  // Send 8-bit data.
  for (uint8_t i = 0; i < 4; i++) 
  {
    digitalWrite(DCK_PIN, LOW);
    digitalWrite(DI_PIN, (data & 0x80));
    digitalWrite(DCK_PIN, HIGH);
    data = data << 1;
    digitalWrite(DI_PIN, (data & 0x80));
    digitalWrite(DCK_PIN, LOW);
    digitalWrite(DI_PIN, LOW);
    data = data << 1;
  }
}

void setupLED()
{
  // GPIO setup.
  pinMode(DI_PIN, OUTPUT);
  pinMode(DCK_PIN, OUTPUT);
  
  pulseDCK(64);                           // Clear all duty registers (2 chipes * 32).
  delayMicroseconds(12);                  // TStop > 12us.
  // Send 12 DI pulse, after 6 pulse's falling edge store duty data, and 12
  // pulse's rising edge convert to command mode.
  pulseDI(12);
  delayMicroseconds(12);                  // Delay >12us, begin send CMD data.
  // Send CMD data
  for (uint8_t n = 0; n < 2; n++)         // 2 chips in SONOFF B1.
    writeData(0x18);                      // ONE_SHOT_DISABLE, REACTION_FAST, BIT_WIDTH_8, FREQUENCY_DIVIDE_1, SCATTER_APDM
  delayMicroseconds(12);                  // TStart > 12us. Delay 12 us.
  // Send 16 DI pulse, at 14 pulse's falling edge store CMD data, and
  // at 16 pulse's falling edge convert to duty mode.
  pulseDI(16);
  delayMicroseconds(12);                  // TStop > 12us.
}

void setLED(uint8_t r, uint8_t g, uint8_t b, uint8_t w, uint8_t c)
{
  uint8_t duty[6] = { w, c, 0, g, r, b }; // RGBWC channels.

  delayMicroseconds(12);                  // TStop > 12us.
  for (uint8_t channel = 0; channel < 6; channel++) 
    writeData(duty[channel]);             // Send 8-bit Data.
  delayMicroseconds(12);                  // TStart > 12us. Ready for send DI pulse.
  pulseDI(8);                             // Send 8 DI pulse. After 8 pulse falling edge, store old data.
  delayMicroseconds(12);                  // TStop > 12us.
}

void setup() 
{
  // Init bulb LEDs.
  setupLED();
  // Set LEDs (initially off).
  setLED(red, green, blue, warmWhite, coolWhite);
  // Start Blynk.
  Blynk.begin(auth, ssid, pass);
}

void loop() { Blynk.run(); }

// Sync Blynk app and bulb. 
void syncPins()
{
  setLED(red, green, blue, warm, cool);

  if (button == 0 && (red || blue || green || warmWhite || coolWhite))
  { 
    Blynk.virtualWrite(V19, 1);
    button = 1;
  }
  
  if (button == 1 && !red && !blue && !green && !warmWhite && !coolWhite)
  {
    Blynk.virtualWrite(V19, 0);
    button = 0;
  }
}

// Read value from red, green, blue and brightness sliders.
BLYNK_WRITE(V15) { red = param.asInt(); syncPins(); }
BLYNK_WRITE(V16) { green = param.asInt(); syncPins(); }
BLYNK_WRITE(V17) { blue = param.asInt(); syncPins(); }
BLYNK_WRITE(V20) { warm = param.asInt(); syncPins(); }
BLYNK_WRITE(V21) { cool = param.asInt(); syncPins(); }

// Process Blynk ON/OFF button.
BLYNK_WRITE(V19)
{
  button = param.asInt();

  if (button == 1 && (red || blue || green || warmWhite || coolWhite))
    setLED(red, green, blue, warm, cool);
  else
  {
    setLED(0, 0, 0, 0, 0);
    Blynk.virtualWrite(V19, 0);
    button = 0;
  }
}

// Initial wifi connection sync.
BLYNK_CONNECTED()
{
  if (isFirstConnect)
  {
    Blynk.syncAll();
    isFirstConnect = false;
  }
}
Posted in iot | Tagged , , | Leave a comment

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, we can use the S31 to monitor energy usage by keeping track of real-time power, current and voltage of the connected appliance.

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

The S31 device was constructed in such a way that taking them apart is easy. You will need to solder 4 wires or a 4-pin header to the test points on the exposed PCB in order to upload the new firmware. Instead of duplicating the instructions for disassembly and reflashing of the Sonoff S31, simply 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 );
}
Posted in arduino, iot | Tagged , , | Leave a comment

Blynk & Amazon Echo (Alexa) Controlled Lights via Smart Wall Switch

Works like a charm. See this post for a discussion about reverse-engineering the wifi smart wall switch and how to program it. The Blynk application uploaded to the Lyasi smart switch is below.

/*************************************************************************
 * Title: Simple ESP-8266 Amazon Echo/sonoff wifi relay control
 * File: sonoff_echo.ino
 * Author: James Eli
 * Date: 5/1/2017
 *
 * For ESP8266 #5, "kitchen".
 * Uses Blynk virtual buttons:
 *   V0 button as switch
 *   V1 an indicator LED
 *   V2 on/off timer.
 *
 * This program controls an esp-8285 wifi switch/relay module communicating
 * either through an amazon echo or the Blynk android application to the
 * onboard esp-8285 module. Amazon Echo responds to ECHO_KEY_WORD.
 *************************************************************************
 * Change Log:
 *   1/26/2017: Initial release. JME
 *   5/12/2017: Modified for Vail house. JME
 *   6/11/2017: Added support for Blynk timer widget. JME
 *************************************************************************/
#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 goes here.
char auth[] = "...";

boolean SwitchReset = true;       // Flag indicating that the hardware button
                                  // has been released
// Esp8285 pins.
#define ESP8266_GPIO13 13         // blue LED (LOW == ON).
#define ESP8266_GPIO0   0         // proximity switching.
#define ESP8266_GPIO12 12         // relay (HIGH == ON).
const int RELAY = ESP8266_GPIO12; // Relay switching pin.
const int LED = ESP8266_GPIO13;   // On/Off indicator LED.
const int SWITCH = ESP8266_GPIO0; // Proximity switch.

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

WemoManager wemoManager;
WemoSwitch *device = NULL;
SimpleTimer timer;

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

  pinMode( RELAY, OUTPUT );       // Init pin modes.
  pinMode( LED, OUTPUT );
  pinMode( SWITCH, INPUT_PULLUP );

  delay( 10 );

  digitalWrite( RELAY, LOW );        // Turn relay off.
  digitalWrite( LED, LOW );          // Turn status LED on.
  wifi.autoConnect( ECHO_KEY_WORD ); // Create AP, if necessary
  wemoManager.begin();

  WiFi.mode( WIFI_STA );          // Set WIFI module to STA mode

  // Format: Alexa invocation name, local port no, on callback, off callback
  device = new WemoSwitch( ECHO_KEY_WORD, 80, RelayOn, RelayOff );
  wemoManager.addDevice( *device );
  Blynk.config( auth );
  //ArduinoOTA.begin();
  timer.setInterval( 100, ButtonCheck );
  digitalWrite( LED, HIGH );
}

void loop() {
  wemoManager.serverLoop();
  Blynk.run();
  //ArduinoOTA.handle();
  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 );            //debounce
  } else if ( SwitchState ) {
    // reset flag the physical button release
    SwitchReset = true;
  }
}
Posted in arduino, iot | Tagged , , | 2 Comments

Using Blynk to Control an RGB LED Strip with an ESP-8266

I am installing recessed RGB LED strip lighting under our kitchen cabinets and wanted to control them via Wifi and Amazon Alexa. I found and purchased an inexpensive ESP-8266 based Wifi controller on Amazon (see photo below). After a quick disassembly, I was easily able to reverse-engineer the device, and reprogram it for my purpose. This device came with an IR detector for use with an IR remote control, however, I didn’t use this and therefore removed the detector. I’ve subsequently noticed versions of the device are available on Amazon without the IR remote option.

Wifi RGB LED Strip Controller

The pinouts on the device were labelled R, G, B, with an additional common cathode pin and an un-populated pin labelled W (a fourth MOSFET is installed for this pin). The R-G-B pins are controlled by MOSFETs and connected to the following ESP-8266 GPIOs:

  • G – Q1 – Pin #14 GPIO05
  • R – Q2 – Pin #05 GPIO14
  • B – Q3 – Pin #06 GPIO12
  • IR – Pin #13 GPIO04
  • I didn’t trace the un-populated pin and MOSFET (labelled Q4).

    As suspected, the test points on the reverse side of the PCB are for programming the onboard ESP-8266 chip. Two of the test points were labelled RX and TX. I traced the other two points to GROUND and GPIO0, which are used to place the ESP-8266 into flash mode. Perfect! All I needed to do, was solder a few jumper wires to enable programming via a USB-Serial type device. Once the new firmware was uploaded I simply de-soldered the wires.

    For testing, I reprogrammed the device to respond to a simple Blynk IoT program with 3 slider widgets, one each for the R, G, B pins:

    /*************************************************************************
     * Title: Simple ESP-8266 Wifi RGB LED Strip Light Controller
     * File: esp8266_rgb_led_controller.ino
     * Author: James Eli
     * Date: 1/14/2018
     *
     * Blynk slider #1 (r) GPIO14
     * Blynk slider #2 (g) GPIO05
     * Blynk slider #3 (b) GPIO12
     * 
     * This program controls an RGB LED strip light. 
     * 
     * Notes:
     *  (1) Requires the following arduino libraries:
     *      ESP8266
     *  (2) Compiled with arduino ide 1.8.3
     *  (4) To place an ESP8266 into program mode, GPIO0 must be LOW during power up. 
     *  (5) Test Pads on PCB back:
     *        #1 – GND
     *        #2 – Pin #12 IO00
     *        TX – Pin #16 TX
     *        RX – Pin #15 RX
    *************************************************************************/
    #include <ESP8266WiFi.h>
    #include <BlynkSimpleEsp8266.h>
    
    // Blynk App authentication token, wifi ssid and password.
    char auth[] = "***";
    char ssid[] = "***";
    char pass[] = "***";
    
    // Esp8266 pins.
    const int RED_PIN   = 14;
    const int GREEN_PIN = 5;
    const int BLUE_PIN  = 12;
    
    void setup() {
      pinMode( RED_PIN, OUTPUT );
      pinMode( GREEN_PIN, OUTPUT );
      pinMode( BLUE_PIN, OUTPUT );
      Blynk.begin( auth, ssid, pass );
    }
    
    void loop() {
      Blynk.run();
    }
    
    Posted in iot | Tagged , , | Leave a comment

    Re-programming a Lyasi ESP-8285 WiFi Wall Switch

    I purchased a couple of Lyasi wall switches from amazon.com for $24.99 USD. There are several versions (GresaTek, Jesiya, NewRice, etc) available through amazon, ebay and various other web outlets, however they appear to be very similar with only minor differences. After disassembly and a cursory inspection, I determined a few specifics about how they operate.

    To disassemble the switch, remove the 4 screws holding the face plate to the body. On several of my devices, these screws were extremely tight and required careful use of an appropriately matched/sized screwdriver to prevent damaging the screw heads. This will expose the underlying PCB. The PCB is held in place by 3 additional screws. The switch plate is also connected via a 2-wire JST style connector which can be wiggled apart. Be careful, the connector-to-board solder joint on my device was weak and could have easily been damaged.

    The device incorporate an Espressif Systems ESP-8285 Wifi SOC chip (datasheet), which is great news because this chip is virtually the same as the well-liked ESP-8266 used in the highly popular Sonoff line of products. The primary differences being the 8285 has 1 Meg of built-in memory. This is more than enough for programming simple IoT projects. However, if you need more memory, consider the un-populated chip outline (U2) on the PCB appears to have the appropriate traces running to add an auxiliary SD memory chip.

    My PCB was labelled “DLX Control Solutions” along with a “KS-602” part number which corresponded to the cardboard packaging part number. However, a web search turned up nothing under the text of “DLX Control Solutions”.

    The backside of the board incorporates an SGL-8022 proximity sensing chip which operates the touch sense face-plate. The PCB has componentry for producing the necessary ESP-8285 (3.3V) power and also includes a blue status LED and SRA05 20A relay for AC power switching. There are a few poorly-accomplished routered slots between the high voltage PCB traces for circuit isolation and arc-over protection, but BEWARE, my device didn’t have any indications of being UL approved.

    Regardless, the good news is that the ESP-8285 GROUND, VCC, RXD and TXD pins are brought out to an un-populated four-hole connector, labelled J1 on my board. I highlighted this J1 connector in red on the picture below. However, on some of my boards these were already filled with solder. So, I fired up my trusty de-solder station and was able to remove the solder from 3 of the holes, but the boxed ground hole was filled with something more akin to cement than solder. I was never able to extract whatever that was, so instead, I simply soldered a 4-pin male 90-degree header in place, neglecting to use the holes. That worked well.

    On my device the pins were connected in this order, left to right on the “J1” label side of the PCB: VCC, TXD, RXD and GROUND (individual box label). Remember to connect the TX output from your FTDI programmer to the J1 RXD pin.

    The final hurdle to re-programming would be grounding the ESP-8285 GPIO0 pin on power-up, in order to get the chip into flash mode. Since the GPIO0 pin is NOT brought out to the J1 connector, an alternate method is required. I read online where someone was able to jump the opposite edges of the R6 resister and C15 capacitor together using small tweezers and get the chip into boot mode. The R6 resistor is directly connected to the ESP-8285 GPIO0 pin. I tried this method several times, but was unable to get it to work. My method was to solder a length of wire to the ESP-8285 chip side R6 resistor (see the picture), and ground this wire on power-up. This worked first time like a charm.

    I used the Arduino IDE for programming, setting the board to “generic ESP8285 module”, and selected “1M (64K SPIFFS)”. My Sparkfun 3.3V FTDI VCC out was sufficient to power the device for re-programming. I used virtually the same program from this post to allow my switch to respond to a Blynk application and amazon Echo/Alexa voice commands.

    Just like the Sonoff Wifi power switches, this device uses the same ESP-8285 connections. The blue status LED is controlled by GPIO13. This LED is on when the GPIO is low. The relay is controlled by GPIO12 and makes contact when the GPIO is high. The proximity (touch sensing) switch is connected to GPIO0 and the GPIO goes low when sensed. A majority of the other ESP-8285 GPIOs are not used.

    I have since noticed that some of the manufactured PCB boards have two front-side test-points labelled, RES and SW. I wonder if these connections are for placing the ESP-8285 into programming mode? Some of my boards did not have these test-points.

    [UPDATE: 11/15/2017]

    It’s just as I thought. Further investigation has shown that the lower test point on the above picture, the one I labeled ‘A’ is connected to GPIO0. The upper test point, the one labeled ‘B’ is connected to the ESP-8285 pin 33 and ground. Connecting these points together should place the chip into boot mode for programming.

    Posted in arduino, iot | Tagged , , , , | 6 Comments

    Modify Arduino Optimization Levels on the Fly

    fly

    Do you know and understand how compiler optimization is altering your code? Higher optimization levels can reveal problems in some programs that are not apparent at lower optimization levels.

    Volatility

    One method to prevent optimization issues with variables is to declare them as volatile. The declaration of a variable as volatile tells the compiler that the variable can be modified at any time externally to the implementation, for example, by the operating system, by an interrupt handler, or by hardware. Because the value of a volatile-qualified variable can change at any time, the actual variable in memory must always be accessed whenever its referenced in code. This means the compiler cannot perform optimizations on the variable.

    Arduino defines the volatile keyword on this webpage using the following example. However, this code neglects to access the 2-byte variable, state atomically. This code probably will survive because the variable, being a 2-byte integer, only uses the lower byte. See the first example on this page for further information.

    Arduino Example:

    // toggles LED when interrupt pin changes state
    int pin = 13;
    volatile int state = LOW;

    void setup() {
    pinMode(pin, OUTPUT);
    attachInterrupt(0, blink, CHANGE);
    }

    void loop() {
    digitalWrite(pin, state);
    }

    void blink() {
    state = !state;
    }

    Non-Optimum

    Another method is to compare versions of your code with different levels of optimization. You may not realize that changing the optimization level of your code is actually very easy once you know how. Here are two techniques to change the optimization level on the fly.

    Pragma-tic Programming

    To change optimization for large areas of code, I recommend using the optimize pragma. Surrounding the code with the push and pop option pragma is also a good practice. Here is an example:


    #pragma GCC optimize ("-O0")
    #pragma GCC push_options

    void setup() { }
    void loop() { }

    #pragma GCC pop_options

    Know Your Attributes

    If you want to effect the optimization of just a single function, use the optimize attribute like so:


    void loop() __attribute__((optimize("-O0")));
    void loop() { }

    References

    More information on GCC optimization can be found here.
    More information on GCC function attributes can be found here.
    More information on GCC pragmas can be found here.

    Posted in arduino, avr | Tagged , , , | 1 Comment