Sonoff - failsafe switching

From Domoticz
Jump to: navigation, search

Failsafe switching of Sonoff relay devices

If you have flashed a Sonoff relay device with ESPeasy, you can have Domoticz turn the device on/off from a dummy switch using a http:// call to the relevant URL in the action field. For example, for a device at octet 27,12,1 

turns the relay on, while ?cmd=gpio,12,0 turns it off again. In the real world, you will regularly encounter missed packets with this approach (it's as if the Sonoffs aren't always listening). When this happens, Domoticz and the Sonoff are out of sync - you think/hope you've switched something on or off but in fact there was no response from the Sonoff and Domoticz is none the wiser.

One way around this is to use script:// instead of http:// in the dummy-switch action field and have an external script send the http:// command to the Sonoff, poll the Sonoff to ensure that the relay has actually switched, and resend the http:// if needed (repeatedly) in case a discrepancy is found. To check the current state of a Sonoff, we can use 

and we will then see something along the lines of:

"Build": 120,
"Unit": 1,
"Uptime": 78869,
"Free RAM": 27608
"TaskName": "relay",
"state": 0.00
"TaskName": "button",
"push": 0.00

The TaskName of 'relay' has a state of 0.00, indicating the relay is currently off - the state is 1.00 if it's on. Our script uses this approach to verify that the Sonoff received a command and will keep resending until the state changes as required. With our script installed, in the on-action / off-action fields of a Domoticz switch, you can use a construct along the lines of

script:// 27 12 0
script:// 148 12 1

where the 27 or 148 (say) are the least-significant octet of the IP address of the Sonoff, the 12 is the gpio number to switch, and 0 or 1 is the required state.


Our script is written in perl but when using it with Domoticz we will call it from a pre-processor script written in bash. The reason for this is that the perl script has a deliberate (short) sleep-delay built-in before it retries a failed command. As Domoticz waits for scripts to finish before continuing its own execution, this delay would hold up Domoticz and we don't want that. The bash wrapper-script kicks off execution of the perl script and passes along our command-line parameters, but suppresses output from the perl and - most importantly - makes it run as a separate process (thanks to the final '&') so that control immediately returns to Domoticz. Perfect.

Create a bash script called with the following content, and put it in your /home/pi/domoticz/scripts directory (and of course, do a chmod +x).

#! /bin/sh
/usr/bin/perl /home/pi/domoticz/scripts/ $1 $2 $3 > /dev/null 2>&1 &

Make sure perl is installed (it very likely is on a linux-based OS) and then install the modules from CPAN that we need:

sudo apt-get update
sudo cpan install JSON::XS
sudo cpan install LWP::UserAgent
sudo cpan install URI::Escape

Now create a perl script called with the following content, and put it in your /home/pi/domoticz/scripts directory (and of course, do a chmod +x). You will need to edit the $domoticz and $baseUrl variables to match your particular setup.

use JSON::XS;
use LWP::UserAgent;
use URI::Escape;

no warnings 'uninitialized';

# Switches a Sonoff device on/off on behalf of Domoticz and then checks that it actually switched accordingly (in case of packet-loss)
# Autor: philchillbill, 2018

($octet, $gpio, $value)=@ARGV;

$domoticz=''; # full URL of your Domoticz instance
$baseUrl='http://192.168.1.'; # The most significant 3 octets of your local area IP addresses for the Sonoffs

sub postDomoticzLog {
 my $msg=shift;
 $ua=LWP::UserAgent->new; $ua->timeout(5); $res=$ua->put($msg_url);
 unless ($res->is_success) { warn "oops... ", $res->status_line };

$ua=LWP::UserAgent->new; $ua->timeout(1); $ua->env_proxy;

# first check we are not clashing with another process handling the same Sonoff device
while ( -e "/tmp/.busy.$octet" ) {
 $secs++; sleep 1; $waited=1;
 if ($secs==30) {
  unlink ("/tmp/.busy.$octet") or die $!;
  &postDomoticzLog("Early exit of lockfile-wait loop with Sonoff '$octet'");
if ($waited) { print "Waited for other process...\n"; sleep 5 };
# now place our own lockfile to hold off other processes
open(TMP, '>', "/tmp/.busy.$octet") or die $!;
do {
 # First send the command to the Sonoff
 $url=$baseUrl.$octet."/control?cmd=gpio,".$gpio.",".$value; # e.g.,12,0
 do { $c++;
 } until ( ($response->is_success) || ($c==5) );

 # Now check if it reacted, using the /json endpoint
 do {
  sleep 1; $d++;
 } until ( ($response->is_success) || ($d==10) );

 if ($response->is_success) {
  $devices=$response->decoded_content; $data=decode_json $devices;
  foreach $entry (@{$$data{Sensors}}) {
   foreach $x (sort keys %{$entry}) { if ($$entry{$x} eq 'relay') { $state=$$entry{state} } }; 
 else { &postDomoticzLog("No response from Sonoff '$octet'"); exit 0 }; 

 $ok=( ($value eq '0') && ($state==0) ) || ( ($value eq '1') && ($state==1) ); $tries++;
} until ( ($ok) || ($tries==5) );

if ( -e "/tmp/.busy.$octet" ) { unlink ("/tmp/.busy.$octet") or die $!};

if (!$ok) { &postDomoticzLog("Error setting Sonoff '$octet' to state '$value'") };

print "c: $c, d: $d, tries: $tries\n";

exit 0;

That's it. You can debug things if necessary by running the perl script directly from the linux command line: e.g. ./ 27 12 1 and seeing if any error messages are thrown. Also, if the bash script won't run from Domoticz, try specifying the full path to the bash file with a triple-slash:

script:///home/pi/domoticz/scripts/ 27 12 0