Sonoff Basic DIY Setup without flashing

OLD Example dosen’t work on 3.3.0 firmware.

New one here https://github.com/JAMESBOWLER/SONOFF_DIY

Sonoff DIY Example

To setup I created rules to control. Openhabian on RPI 3+

Product Page Link
Documentation/Tools Github

Steps

  • Wire power and Install header pin
  • Update Firmware using eWelink APP as it won’t work with shipped firmware 3.0.0
  • DIY will connect to wifi ssid:sonoffDiy password:20170618sn Youtube Demo video
    I created wifi with spare router I had lying around and mDNS is required
  • Use DIY mode tool to change SSID to same as pi found here Github
  • Setup power on state
  • Set up DHCP reservation using router (192.168.1.50)
  • Create Items Sitemap and Rules


You can flash 3rd party firmware using this tool to void warranty.

I use the openhab config files edited through VScode

Item

Switch sonoff_one "Lounge Light" <switch> (gLounge) [ "Lighting" ]

Sitemap

sitemap sonoff label="sonoff" {
  Frame label="Lounge" {
    Switch item=sonoff_one icon="light"
  }
}

Rules

rule "Sonoff control Rule"

  when
    Item sonoff_one received update 
  then
  
  try {

    var CommandState = sonoff_one.state.toString.toLowerCase   // Change Sonoff_one to the item in your .items file e.g. Switch sonoff_one "Lounge Light" <switch> (gLounge) [ "Switchable" ] 
    val String Ip = '192.168.1.50'      // Ip of Sonoff
    val String Deviceid = '100086f3e5'  // Device ID - Works without it 

    // Shouldn't need to change anything below 
       
    var CommandURL = 'http://' + Ip +':8081/zeroconf/switch'
    var String CommandData = '{ "sequence": "1560835774", "deviceid": "' + Deviceid + '", "encrypt": false, "iv": "1234567890123456", "selfApikey": "123", "data": "{\\"switch\\": \\"' + CommandState + '\\"}"}'
    var String UpdateResult
    var Number Attempts = 0
    var CommandExec = 'curl@@-sSH@@"Accept: application/json"@@-H@@"Content-Type: application/json"@@-X@@POST@@-d@@' + CommandData + '@@' + CommandURL
  
    do {
      Attempts += 1
      UpdateResult = executeCommandLine(CommandExec, 2000)       
      if (UpdateResult.contains('"error":0')) { logInfo("Sonoff ID:"+ Deviceid +" Control", "IP: " + Ip + " Turned: " + CommandState) } // Success
     
  
    } while (!UpdateResult.contains('"error":0') && Attempts < 5) // Attempt to send 5 times
  
    if (Attempts == 5) {
      logError("Sonoff write", "5 failures updating Sonoff")
    }
          
    if (UpdateResult.contains('"error":400')) {
    logError("Sonoff ID:"+ Deviceid +" Error", "The operation failed and the request was formatted incorrectly. The request body is not a valid JSON format")
    }

    if (UpdateResult.contains('"error":401')) {
    logError("Sonoff Error", "The operation failed and the request was unauthorized. Device information encryption is enabled on the device, but the request is not encrypted")
    }

    if (UpdateResult.contains('"error":404')) {
    logError("Sonoff Error", "The operation failed and the device does not exist. The device does not support the requested deviceid")
    }

    if (UpdateResult.contains('"error":422')) {
    logError("Sonoff Error", "The operation failed and the request parameters are invalid. For example, the device does not support setting specific device information")
    }
  }

  catch(Throwable t) {
    logError("Sonoff write", "Error was caught: {}", t)
   }

end

Google intergration


Now in the google routine for bedtime you can add the light to the sequence.

Layout of Data

Cons

  • If you switch it too fast using sitemap it light may be out of sync. I working on making it more reliable.
4 Likes

James, I’m new on this community and just starting to create a system based on openhab for my home. What a great piece of work here, thanks!

Thank you. After three hours of fiddling round I eventually got my Sonoff connected to openhab.

My solution involved:

  • setting up a temporay wireless diy network on our Unifi network
  • updating the factory firmware on the sonoff
  • realizing that setting the diy mode resets the sonoff device!
  • using the diy software from sonoff
  • adding the manual code into openhab

Easy!

Is this any easier than using the tamoto firmware? Not sure. It certainly feels safer as I have not had to try to backup/flash the firmware or use a soldering iron.

1 Like

For those following along this dosen’t work on the new 3.3.0 Firmware

I have updated it here https://github.com/JAMESBOWLER/SONOFF_DIY

I added polling so when something else changes switch state it updates to openhab.

1 Like

Thanks!

Hi James,

I have been struggling to get the poll rule working in your example.

What I see is this.

Data received from sonoff.

 {"seq":2,"error":0,"data":"{\"switch\":\"off\",\"startup\":\"off\",\"pulse\":\"off\",\"pulseWidth\":500,\"ssid\":\"peglegpete\",\"otaUnlock\":true}"}

Data returned from JSONPATH transform no matter what tag is specified.


 {"seq":2,"error":0,"data":"{\"switch\":\"off\",\"startup\":\"off\",\"pulse\":\"off\",\"pulseWidth\":500,\"ssid\":\"peglegpete\",\"otaUnlock\":true}"}

I understand returning a copy of the original means that JSONPATH thinks that the input is malformed. I have tried various processes on the incoming data, including stripping out all the forward slashes, and removing the qoutes from around the nested JSON object. I have not got anything to work.

Have you got any ideas about what I am doing wrong.

Here is a little test rule I have been playing with.

rule "Mimi Polling"

  when
    Time cron "0/60 * * * * ?" // Run every 60 seconds
  then

  try {
    var String FromSonoff
    var String json

    FromSonoff =  "{\"seq\":2,\"error\":0,\"data\":\"{\\\"switch\\\":\\\"off\\\",\\\"startup\\\":\\\"off\\\",\\\"pulse\\\":\\\"off\\\",\\\"pulseWidth\\\":500,\\\"ssid\\\":\\\"peglegpete\\\",\\\"otaUnlock\\\":true}\"}"
    logInfo("mimi", "Data from sonoff = " + FromSonoff)
    json = FromSonoff.replace("\\\"", "\"")
    //json = FromSonoff
    logInfo("mimi", "Data from sonoff(munged) = " + json)

    logInfo("mimi", "seq = " + transform("JSONPATH", "$.seq", json))
    logInfo("mimi", "error = " + transform("JSONPATH", "$.error", json))
    logInfo("mimi", "data = " + transform("JSONPATH", "$.data", json))
  }
  catch(Exception e){
    logError("Polling", "Error occured in Mimi poll! " + e.toString)
  }

end

Thanks for pesisting and trying things to fix yourself. Also thankyou for posting a well formed question with heaps of info.

{\"switch\":\"off\",\"startup\":\"off\",\"pulse\":\"off\",\"pulseWidth\":500,\"ssid\":\"peglegpete\",\"otaUnlock\":true}

It is not valid json :frowning: I/WE tryed to get itead to fix Github issue

Did you try the rule.

rule "Sonoff_one Polling"

  when
    Time cron "0/3 * * * * ?" // Run every 3 seconds
  then
  
  try {

        var CommandURL = 'http://' + Ip +':8081/zeroconf/info'
        var String CommandData = '{"deviceid": "'+ Deviceid +'", "data": {}}'
        var String UpdateResult
        var Number Attempts = 0
        var CommandExec = 'curl@@-sSH@@"Accept: */*"@@-H@@"Content-Type: text/plain"@@-X@@POST@@-d@@' + CommandData + '@@' + CommandURL
        var String JSONdata
        var String update_sswitch
        var String update_startup
        var String update_pulse
        var String update_pulseWidth
        var String update_ssid
        var String update_otaUnlock


    do {
        Attempts += 1
        // logInfo("Sonoff command", CommandData)
        UpdateResult = executeCommandLine(CommandExec, 1500)

      if (UpdateResult.contains('"error":0')) { 
        JSONdata = transform("JSONPATH", "$.data", UpdateResult)      
        // logInfo("Sonoff Poll JSONdata", JSONdata)


      if (JSONdata != Last_update) {
       
          update_sswitch = transform("JSONPATH", "$.switch", JSONdata)
          update_startup = transform("JSONPATH", "$.startup", JSONdata)
          update_pulse = transform("JSONPATH", "$.pulse", JSONdata)
          update_pulseWidth = transform("JSONPATH", "$.pulseWidth", JSONdata)
          update_ssid = transform("JSONPATH", "$.ssid", JSONdata)
          update_otaUnlock = transform("JSONPATH", "$.otaUnlock", JSONdata)

          if (update_sswitch != sonoff.state.toString.toLowerCase) {
            logInfo("sonoff switch", " Syncing state to " + update_sswitch)
            sonoff.postUpdate(update_sswitch.toString.toUpperCase)
          }

          if (update_startup != startup.state.toString) {
            logInfo("sonoff switch", " Syncing startup to " + update_startup)
            startup.postUpdate(update_startup.toString)
          }
          
          if (update_pulse != pulse.state.toString) {
            logInfo("sonoff switch", " Syncing pulse to " + update_pulse )
            pulse.postUpdate(update_pulse.toString)
          }
          
          if (update_pulseWidth != pulseWidth.state.toString) {
            logInfo("sonoff switch", " Syncing pulseWidth to " + update_pulseWidth + "ms")
            pulseWidth.postUpdate(update_pulseWidth.toString)
          }
          
          if (update_ssid != ssid.state.toString) {
            logInfo("sonoff switch", " Syncing ssid")
            ssid.postUpdate(update_ssid.toString)
          }
          
          if (update_otaUnlock != otaUnlock.state.toString) {
            logInfo("sonoff switch", " Syncing otaUnlock")
            otaUnlock.postUpdate(update_otaUnlock.toString)
          }
          
          Last_update = JSONdata
          }     
        }    
  
      } while (!UpdateResult.contains('"error":0') && Attempts < 2) // Attempt to send 2 times

    if (Attempts == 2) {
      logError("Sonoff Poll Switch", "2 failures polling sonoff_one ")
    }
      
        if (UpdateResult.contains('"error":400')) {
          logError("Sonoff ID:"+ Deviceid +" Error", "The operation failed and the request was formatted incorrectly. The request body is not a valid JSON format")
        } else if  (UpdateResult.contains('"error":401')) {
          logError("Sonoff Error", "The operation failed and the request was unauthorized. Device information encryption is enabled on the device, but the request is not encrypted")
        } else if (UpdateResult.contains('"error":404')) {
          logError("Sonoff Error", "The operation failed and the device does not exist. The device does not support the requested deviceid")
        }    else if (UpdateResult.contains('"error":422')) {
          logError("Sonoff Error", "The operation failed and the request parameters are invalid. For example, the device does not support setting specific device information")
        }
    
    }
    catch(Exception e){
      logError("Polling", "Error occured in Sonoff Poling Rule! " + e.toString)
    }

end  

Follow this example https://github.com/JAMESBOWLER/SONOFF_DIY

Hi James,

Thanks for your response. Comments inline.

The answer to that is yes. See below.

This test works because it just looking at the result as a string

Sending this again as first time was chopped.

Hi James,

Thanks for your response. Comments inline.

{“seq”:2,“error”:0,“data”{"switch":"off","startup":"off","pulse":"off","pulseWidth":500,"ssid":"peglegpete","otaUnlock":true}"}

Had a quick look at the Github issue. Thanks for the link. As far as I can see what is sent is actually valid json. It is just that the third tag has a string type as its value. If you were able to correctly extract the string from the json it should come out as another valid piece of json with all the escapes removed.(I think!).

Did you try the rule.

The answer to that is yes. See below.

JSONdata = transform(“JSONPATH”, “$.data”, UpdateResult)

My problem started here because what was being returned was not the contents of the “data” object but just a copy of the UpdateResult string. The same is true of all the calls to transform after this.

As far as I know this behaviour is intentional and indicates that the JSONPATH transform thinks it has been given invalid data. I have not got round to trying any of the other rules as I have not configured anything to use them yet. I just wanted to test connectivity to a sonoff module using the poll function. When I got that right I planned to have a go to writing a channel for the sonoff devices in stock DIY mode (Unless somebody has already done that. I have not found one yet).

it would be great if you could verify this behaviour for me. I plan to go back to a clean copy of your code and put in the minimal changes at the top of the file to talk to one of my switches. Then verify the behaviour again from scratch. I have messed about soo much trying to change the curl command and then munge the result that I have lost track of hat I have changed. There is either a bug in the transform stuff or a difference in my environment.

Cheers

Roger

I have done the test now on a clean copy of your code with just the ip address and device serial number changed, and the relevant log lines uncommented. This confirms the behaviour I observed before.

Roger

Yes this is the problem I ran into. As a workaround I figured out if you tansform “data” it returns the string as valid json. It is a workaround due to how the sonoff software sends the data.

I beleve you ran into problem because you looked and used only part of the rule file.

Create a new rule file and enter the following

import org.eclipse.smarthome.model.script.ScriptServiceUtil

val String Ip = '192.168.1.14'      // Ip of Sonoff or mDNS eWeLink_100098757f.local
val String Deviceid = '100098757f'              // Can get from DIY tool 

//---------------------------------------------------Your Item Name between ""
val sonoff = ScriptServiceUtil.getItemRegistry.getItem("sonoff_one")
val startup = ScriptServiceUtil.getItemRegistry.getItem("sonoff_one_startup")
val pulse = ScriptServiceUtil.getItemRegistry.getItem("sonoff_one_pulse")
val pulseWidth = ScriptServiceUtil.getItemRegistry.getItem("sonoff_one_pulseWidth")
val ssid = ScriptServiceUtil.getItemRegistry.getItem("sonoff_one_ssid")
val otaUnlock = ScriptServiceUtil.getItemRegistry.getItem("sonoff_one_otaUnlock")
var String Last_update

rule "Sonoff_one Polling"

  when
    Time cron "0/3 * * * * ?" // Run every 3 seconds
  then
  
  try {

        var CommandURL = 'http://' + Ip +':8081/zeroconf/info'
        var String CommandData = '{"deviceid": "'+ Deviceid +'", "data": {}}'
        var String UpdateResult
        var Number Attempts = 0
        var CommandExec = 'curl@@-sSH@@"Accept: */*"@@-H@@"Content-Type: text/plain"@@-X@@POST@@-d@@' + CommandData + '@@' + CommandURL
        var String JSONdata
        var String update_sswitch
        var String update_startup
        var String update_pulse
        var String update_pulseWidth
        var String update_ssid
        var String update_otaUnlock

    do {
        Attempts += 1
        // logInfo("Sonoff command", CommandData)
        UpdateResult = executeCommandLine(CommandExec, 1500)

      if (UpdateResult.contains('"error":0')){ 
        JSONdata = transform("JSONPATH", "$.data", UpdateResult)      
        logInfo("Sonoff Poll JSONdata", JSONdata)


      if (JSONdata != Last_update){
          update_sswitch = transform("JSONPATH", "$.switch", JSONdata)
          update_startup = transform("JSONPATH", "$.startup", JSONdata)
          update_pulse = transform("JSONPATH", "$.pulse", JSONdata)
          update_pulseWidth = transform("JSONPATH", "$.pulseWidth", JSONdata)
          update_ssid = transform("JSONPATH", "$.ssid", JSONdata)
          update_otaUnlock = transform("JSONPATH", "$.otaUnlock", JSONdata)

          if (update_sswitch != sonoff.state.toString.toLowerCase) {
            logInfo("sonoff switch", " Syncing state to " + update_sswitch)
            // sonoff.postUpdate(update_sswitch.toString.toUpperCase)
          }

          if (update_startup != startup.state.toString) {
            logInfo("sonoff switch", " Syncing startup to " + update_startup)
            // startup.postUpdate(update_startup.toString)
          }
          
          if (update_pulse != pulse.state.toString) {
            logInfo("sonoff switch", " Syncing pulse to " + update_pulse )
            // pulse.postUpdate(update_pulse.toString)
          }
          
          if (update_pulseWidth != pulseWidth.state.toString) {
            logInfo("sonoff switch", " Syncing pulseWidth to " + update_pulseWidth + "ms")
            // pulseWidth.postUpdate(update_pulseWidth.toString)
          }
          
          if (update_ssid != ssid.state.toString) {
            logInfo("sonoff switch", " Syncing ssid")
            // ssid.postUpdate(update_ssid.toString)
          }
          
          if (update_otaUnlock != otaUnlock.state.toString) {
            logInfo("sonoff switch", " Syncing otaUnlock")
            // otaUnlock.postUpdate(update_otaUnlock.toString)
          }
          
          Last_update = JSONdata
          }     
        }    
  
      } while (!UpdateResult.contains('"error":0') && Attempts < 2) // Attempt to send 2 times

    if (Attempts == 2) {
      logError("Sonoff Poll Switch", "2 failures polling sonoff_one ")
    }
      
        if (UpdateResult.contains('"error":400')) {
          logError("Sonoff ID:"+ Deviceid +" Error", "The operation failed and the request was formatted incorrectly. The request body is not a valid JSON format")
        } else if  (UpdateResult.contains('"error":401')) {
          logError("Sonoff Error", "The operation failed and the request was unauthorized. Device information encryption is enabled on the device, but the request is not encrypted")
        } else if (UpdateResult.contains('"error":404')) {
          logError("Sonoff Error", "The operation failed and the device does not exist. The device does not support the requested deviceid")
        }    else if (UpdateResult.contains('"error":422')) {
          logError("Sonoff Error", "The operation failed and the request parameters are invalid. For example, the device does not support setting specific device information")
        }
    
    }
    catch(Exception e){
      logError("Polling", "Error occured in Sonoff Poling Rule! " + e.toString)
    }

end  

You only need this file and to change the values
sonset

In your openhab.log file you should see

[INFO ] [me.model.script.Sonoff Poll JSONdata] - {"switch":"off","startup":"stay","pulse":"off","pulseWidth":500,"ssid":"WIFI","otaUnlock":false}

Toggle the state of the swich using the DIY tool or pressing the button on sonoff and you should see in your log.

[INFO ] [smarthome.model.script.sonoff switch] -  Syncing state to on
[INFO ] [smarthome.model.script.sonoff switch] -  Syncing startup to stay
[INFO ] [smarthome.model.script.sonoff switch] -  Syncing pulse to off
[INFO ] [smarthome.model.script.sonoff switch] -  Syncing pulseWidth to 500ms
[INFO ] [smarthome.model.script.sonoff switch] -  Syncing ssid
[INFO ] [smarthome.model.script.sonoff switch] -  Syncing otaUnlock

If you are not seeing this json string can you confirm you have the JsonPath Transformation Service is installed
http://openhabianpi:8080/paperui/index.html#/extensions?tab=transformation

In the rule above I commented out the updating of the item for testing.

// sonoff.postUpdate(update_sswitch.toString.toUpperCase)

I have some sonoffs running with TASMOTA using MQTT to openhab and some other hacked products running it aswell.

Also AlexxIT at home assistant has this

Argh forget this post. I am an idiot. At some point I have done sonething that screwed up my installation of the JSONPATH transform. Mea culpa. I am now reinstalling from scratch

James,

If that code is the same as what is in sonoff_one.rules in your repository. That is exactly the test I have just done.

It looks the same to me. Am I missing sonething?

Roger

1 Like

Why use mDNS, then setup a static reservation on your router? You should do one or the other. Doesn’t make sense what the hostname will be if you already know the IP. Unless you create a static ARP route based on the client ID when they request DHCP lease. In that case, you can setup your router to add the client ID to the hostname resolution (dnamasq, unbound, etc). mDNS is usually used for devices that are not smart enough to register their names on the DHCP request. Just a thought unless I read you totally wrong.

Everything working now as expected. Forty five years of writing software and I am still making stupid mistakes. You ignore Occam’s Razor at your peril should be tatooed on my forehead.

Roger

IP us used for hostname you can put in eWeLink_100098757f.local or ip

The val Deviceid is sent to the sonoff when requesting data from it.

My intenet provider router dosen’t dosent do mdns reliably.

Yes the Transform needs to be working for it to workn :slight_smile:

Thanks for letting us know as I guess I didn’t make that clear.

I still do not know what I did to break the transform. But reinstalling it did the trick. Only problem now is when I turn the switch on it goes off a couple of seconds later. It even does that when cntrolled by the rf remote. I gues that might have sonething to do with the junk I have been throwing at it.

Just trying to point out that if you are using IP address, then mDNS is not required as you mention in your OP

1 Like

Turn Inching mode off using the DIY tool.