Start a new topic

protocol for Sonoff Dual to replace original firmware

With the following information it should be possible to replace the original firmware.


Details from my protocol analyzer (attached to ERX/ETX:


19230,8,N,1


Sampled from pushing the button on the internet

0xA0

0x04

0xxx

0xA1


where 0xxx seems to be:

0x00 both off

0x01 relay one on

0x02 relay two on

0x03 both relays on


If you push attached buttons, the protocol seems to be the same in the other direction:


Sampled from pushing attached physical buttons:

0xA0

0x04

0xxx

0xA1


where 0xxx seems to be:

0x00 both off

0x01 relay one on

0x02 relay two on

0x03 both relays on


3 people like this

Just made a working test sketch:

  

// the setup function runs once when you press reset or power the board
void setup() {
  // initialize digital pin LED_BUILTIN as an output.
  Serial.begin(19200);
}

// the loop function runs over and over again forever
void loop() {

  Serial.write(0xA0);
  Serial.write(0x04);
  Serial.write(0x01); 
  Serial.write(0xA1);
  Serial.flush();
  delay(1000);                       // wait for a second
  Serial.write(0xA0);
  Serial.write(0x04);
  Serial.write(0x03); 
  Serial.write(0xA1);
  Serial.flush();
  delay(1000);                       // wait for a second 
  Serial.write(0xA0);
  Serial.write(0x04);
  Serial.write(0x00); 
  Serial.write(0xA1);
  Serial.flush();
  delay(1000);                       // wait for a second
}

  


And a simple MQTT sketch:

  

// derived from the Basic MQTT example https://github.com/knolleary/pubsubclient/blob/master/examples/mqtt_basic/mqtt_basic.ino

#include <ESP8266WiFi.h>
#include <PubSubClient.h>

// Update these with values suitable for your network.

const char* ssid = "xxx";
const char* password = "yyy";
const char* mqtt_server = "zzz";

boolean relay1 = false;
boolean relay2 = false;


WiFiClient espClient;
PubSubClient client(espClient);

void setup_wifi() {
  delay(10);
  // We start by connecting to a WiFi network
  //Serial.println();
  //Serial.print("Connecting to ");
  //Serial.println(ssid);

  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    //Serial.print(".");
  }

  randomSeed(micros());

  //Serial.println("");
  //Serial.println("WiFi connected");
  //Serial.println("IP address: ");
  //Serial.println(WiFi.localIP());
}

void callback(char* topic, byte* payload, unsigned int length) {
  boolean bset = false;
  payload[length] = '\0';
  String sPayload = String((char *)payload);
  String sTopic = String(topic);
  if (sTopic == "sonoff/1/relay1/set") {
    if (sPayload == "1") {
      if (relay1 == false) bset = true;
      relay1 = true;
    } else {
      if (relay1) bset = true;
      relay1 = false;
    }
  }
  if (sTopic == "sonoff/1/relay2/set") {
    if (sPayload == "1") {
      if (relay2 == false) bset = true;
      relay2 = true;
    } else {
      if (relay2) bset = true;
      relay2 = false;
    }
  }
  if (bset) setrelays();
}

void reconnect() {
  // Loop until we're reconnected
  while (!client.connected()) {
    //Serial.print("Attempting MQTT connection...");
    // Create a random client ID
    String clientId = "sonoff-";
    clientId += String(random(0xffff), HEX);
    // Attempt to connect
    if (client.connect(clientId.c_str())) {
      // Once connected, publish an announcement...
      client.publish("sonoff/1", "connected");
      setrelays();
      // ... and resubscribe
      client.subscribe("sonoff/1/#");
    } else {
      // Wait 5 seconds before retrying
      delay(5000);
    }
  }
}

void setup() {
  Serial.begin(19200);
  setup_wifi();
  client.setServer(mqtt_server, 1883);
  client.setCallback(callback);
  client.loop();
}

void loop() {

  if (!client.connected()) {
    reconnect();
  }
  client.loop();
}

void setrelays() {
  byte b = 0;
  if (relay1) b++;
  if (relay2) b += 2;
  Serial.write(0xA0);
  Serial.write(0x04);
  Serial.write(b);
  Serial.write(0xA1);
  Serial.flush();
  if (relay1) client.publish("sonoff/1/relay1", "1");
  else client.publish("sonoff/1/relay1", "0");
  
  if (relay2) client.publish("sonoff/1/relay2", "1");
  else client.publish("sonoff/1/relay2", "0");
}
 

  

Markus.

This is encouraging - I've spent the morning tinkering with the Sonoff Dual and want to upload some 8266 Arduino code.


I can see data on the TX/RX pins as per your investigations when buttons are pressed.  


Can I ask how you've uploaded the code above to the board?  Is it through the same header? If so, how did you get into some sort of programming mode  (i.e. GPIO0 low) - had hoped it would be button on front, but doesn't seem to be.


Thanks


Luke.

 

Hi Luke


I soldiered a wire to EN_FW and made a connection to ground on startup. I can take a picture, if you don't find the pin.


Cu,

Markus

I had a quick look on the 8266 datasheet, but couldn't find a reference to EN_FW pin. Be great if you could post a picture.


Thanks,

Luke

   

It is pin 15:


Great - many thanks.  

Have got the blink sketch going and OTA.  

Need to fiddle a bit further with MQTT, but great to have board programmable now.

Thanks

Luke.


Got MQTT working now. Also added some generic OTA code. Copy below for anyone else interested;

   

// derived from the Basic MQTT example https://github.com/knolleary/pubsubclient/blob/master/examples/mqtt_basic/mqtt_basic.ino

#include <ESP8266WiFi.h>
#include <PubSubClient.h>

#include <ESP8266mDNS.h>
#include <WiFiUdp.h>
#include <ArduinoOTA.h>

const char* host = "DUAL-SONOFF";
const char* esppwd = "****";

#define LED 13


#define LED_ON 0
#define LED_OFF 1


// Update these with values suitable for your network.


const char* ssid = "xxxx";
const char* password = "xxxx";
const char* mqtt_server = "xxxx";

boolean relay1 = false;
boolean relay2 = false;

const unsigned long tUpdateLED  = 10000;

unsigned long tLEDFLASH;

#define LEDFLASHMILLIS 10000


WiFiClient espClient;
PubSubClient client(espClient);

void setup_wifi() {
  delay(10);
  // We start by connecting to a WiFi network
  //Serial.println();
  //Serial.print("Connecting to ");
  //Serial.println(ssid);

  WiFi.mode(WIFI_STA);

  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);

    FlashLED();


    //Serial.print(".");
  }

  randomSeed(micros());

  //Serial.println("");
  //Serial.println("WiFi connected");
  //Serial.println("IP address: ");
  //Serial.println(WiFi.localIP());
}

void callback(char* topic, byte* payload, unsigned int length) {
  boolean bset = false;
  payload[length] = '\0';
  String sPayload = String((char *)payload);
  String sTopic = String(topic);
  if (sTopic == "sonoff/1/relay1/set") {
    FlashLED();
    if (sPayload == "1") {
      if (relay1 == false) bset = true;
      relay1 = true;
    } else {
      if (relay1) bset = true;
      relay1 = false;
    }
  }
  if (sTopic == "sonoff/1/relay2/set") {
    FlashLED();
    FlashLED();
    if (sPayload == "1") {
      if (relay2 == false) bset = true;
      relay2 = true;
    } else {
      if (relay2) bset = true;
      relay2 = false;
    }
  }
  if (bset) setrelays();
}

void reconnect() {
  // Loop until we're reconnected
  while (!client.connected()) {
    //Serial.print("Attempting MQTT connection...");
    // Create a random client ID
    String clientId = "sonoff-";
    clientId += String(random(0xffff), HEX);
    // Attempt to connect
    if (client.connect(clientId.c_str())) {
      // Once connected, publish an announcement...
      client.publish("sonoff/1", "connected");
      setrelays();
      // ... and resubscribe
      client.subscribe("sonoff/1/#");
    } else {
      // Wait 5 seconds before retrying
      delay(5000);
    }
  }
}

void setup() {
  Serial.begin(19200);
  pinMode(LED, OUTPUT);

  FlashLED();

  tLEDFLASH = millis();


  setup_wifi();
  client.setServer(mqtt_server, 1883);
  client.setCallback(callback);
  client.loop();
  ArduinoOTA.setHostname(host);
  ArduinoOTA.setPassword(esppwd);
  ArduinoOTA.onStart([]() { // switch off all the PWMs during upgrade
  });

  ArduinoOTA.onEnd([]() { // do a fancy thing with our board led at end
  });

  ArduinoOTA.onError([](ota_error_t error) {
    ESP.restart();
  });

  /* setup the OTA server */
  ArduinoOTA.begin();
  Serial.println("Ready");

}


void loop() {


  ArduinoOTA.handle();

  if ((millis() - tLEDFLASH) > tUpdateLED ) FlashLED();

  if (!client.connected()) {
    reconnect();
  }
  client.loop();
}

void setrelays() {
  byte b = 0;
  if (relay1) b++;
  if (relay2) b += 2;
  Serial.write(0xA0);
  Serial.write(0x04);
  Serial.write(b);
  Serial.write(0xA1);
  Serial.flush();
  if (relay1) client.publish("sonoff/1/relay1", "1");
  else client.publish("sonoff/1/relay1", "0");

  if (relay2) client.publish("sonoff/1/relay2", "1");
  else client.publish("sonoff/1/relay2", "0");
}

void FlashLED() {

  Serial.println("FlashLED triggered");
  digitalWrite(LED, LED_ON);
  delay(50);
  digitalWrite(LED, LED_OFF);
  delay(20);

  tLEDFLASH = millis();
} 

 

ArduinoOTA is new for me. I will have a look at it.


Added the buttons to my sketch. It really need cleanup (String/char* handling, code redundancy)

  

// derived from the Basic MQTT example https://github.com/knolleary/pubsubclient/blob/master/examples/mqtt_basic/mqtt_basic.ino

#include <ESP8266WiFi.h>
#include <PubSubClient.h>

// Update these with values suitable for your network.
String MQTT_SUBSCRIPTION = "sonoff/2/#";
String MQTT_PUBLISH = "sonoff/2";


const char* ssid = "xxx";
const char* password = "yyy";
const char* mqtt_server = "zzz";


boolean relay1 = false;
boolean relay2 = false;
int incomingByte = 0;
int iStep = 0;
  int iNewState = 0;

WiFiClient espClient;
PubSubClient client(espClient);

void setup_wifi() {
  delay(10);
  // We start by connecting to a WiFi network
  //Serial.println();
  //Serial.print("Connecting to ");
  //Serial.println(ssid);

  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    //Serial.print(".");
  }

  randomSeed(micros());

  //Serial.println("");
  //Serial.println("WiFi connected");
  //Serial.println("IP address: ");
  //Serial.println(WiFi.localIP());
}

void callback(char* topic, byte* payload, unsigned int length) {
  boolean bset = false;
  payload[length] = '\0';
  String sPayload = String((char *)payload);
  String sTopic = String(topic);
  if (sTopic == MQTT_PUBLISH + "/relay1/set") {
    if (sPayload == "1") {
      if (relay1 == false) bset = true;
      relay1 = true;
    } else {
      if (relay1) bset = true;
      relay1 = false;
    }
  }
  if (sTopic == MQTT_PUBLISH + "/relay2/set") {
    if (sPayload == "1") {
      if (relay2 == false) bset = true;
      relay2 = true;
    } else {
      if (relay2) bset = true;
      relay2 = false;
    }
  }
  if (bset) setrelays();
}

void reconnect() {
  // Loop until we're reconnected
  while (!client.connected()) {
    //Serial.print("Attempting MQTT connection...");
    // Create a random client ID
    String clientId = "sonoff-";
    clientId += String(random(0xffff), HEX);
    // Attempt to connect
    if (client.connect(clientId.c_str())) {
      // Once connected, publish an announcement...
      client.publish(MQTT_PUBLISH.c_str() , "connected");
      setrelays();
      // ... and resubscribe
      client.subscribe(MQTT_SUBSCRIPTION.c_str());
    } else {
      // Wait 5 seconds before retrying
      delay(5000);
    }
  }
}

void setup() {
  Serial.begin(19200);
  setup_wifi();
  client.setServer(mqtt_server, 1883);
  client.setCallback(callback);
  client.loop();
}

void loop() {

  if (!client.connected()) {
    reconnect();
  }
  client.loop();
  if (Serial.available() > 0) {
    // read the incoming byte:
    incomingByte = Serial.read();

    if (incomingByte == 0xA0) {
      iStep = 1;
    }
    else if ((iStep == 1) && (incomingByte == 0x04)) {
      iStep = 2;
    }
    else if ((iStep == 2) && (incomingByte >= 0) && (incomingByte <= 3)) {
      iStep = 3;
      iNewState = incomingByte;
    } else if ((iStep == 3) && (incomingByte == 0xA1)) {
      iStep = 0;
      if (iNewState == 0) {
        relay1 = false;
        relay2 = false;
      }
      if (iNewState == 1) {
        relay1 = true;
        relay2 = false;
      }
      if (iNewState == 2) {
        relay1 = false;
        relay2 = true;
      }
      if (iNewState == 3) {
        relay1 = true;
        relay2 = true;
      }
      // client.publish(MQTT_PUBLISH.c_str(),String(iNewState).c_str());
      if (relay1) client.publish((MQTT_PUBLISH + "/relay1").c_str(), "1");
      else client.publish((MQTT_PUBLISH + "/relay1").c_str(), "0");

      if (relay2) client.publish((MQTT_PUBLISH + "/relay2").c_str(), "1");
      else client.publish((MQTT_PUBLISH + "/relay2").c_str(), "0");
      
    } else iStep = 0;
  }
}

void setrelays() {
  byte b = 0;
  if (relay1) b++;
  if (relay2) b += 2;
  Serial.write(0xA0);
  Serial.write(0x04);
  Serial.write(b);
  Serial.write(0xA1);
  Serial.flush();
  if (relay1) client.publish((MQTT_PUBLISH + "/relay1").c_str(), "1");
  else client.publish((MQTT_PUBLISH + "/relay1").c_str(), "0");

  if (relay2) client.publish((MQTT_PUBLISH + "/relay2").c_str(), "1");
  else client.publish((MQTT_PUBLISH + "/relay2").c_str(), "0");
}
 

  

Hi,

Just stumbled over your discussion. I have 2 standard sonoff's to try them out.
One connected and configured through the app.
I intend to control them through openHab, but without tinkering with the units. Instead I want to develop 'Binding' for openHab.
You have discovered the protocol for the duals, I assume the protocol should be more or less similar.
The IP Address is known to me but i have not yet found the communication port. Could you provide me with that info?

Regards,
Marcel

 

Hi Marcel


AFAIK only the dual has the additional MCU. The other should be hackable without revere engineering.


As I only have sonoff dual, I can't help you with a sketch.


Regards,

Markus

Hi

I have developed generic mqtt software for both the original Sonoff and Sonoff TH series. If Marcel hasn't found anything, maybe this will help.

https://github.com/KmanOz/Sonoff-HomeAssistant

Regards and thanks for the reverse engineering of the the Dual. Saves me some work indeed.

 

Is there no way to control ITEAD cloud from PC/Linux/Mac etc?

I don't want to alter my firmware because I have SonOff Pro and want to use RF aswell...

but I want to be able to switch on/off from the PC.


1 person likes this
Login or Signup to post a comment