Shelly pro 2 internal watchdog script

Hi folks

As I understand my shelly pro 2 has a built in support for running scripts. I was interested to see if anyone uses that and may know if its possible to make a watchdog script, that verify connection to openHAB server and if connection is lost it changes mode from detached to attached or simply just shuts output off.

I stumbled a little with Shellys docs for script and the say it’s some kind of simplified or modified Espruino, and the docs didn’t get me the information needed for med to understand if it’s possible and the how to achive what I wanted.

Maybe someone on this forum may know avout it.

yes, you can upload scripts with a lot of use-cases.
I currently have two:

  • React on commands from a shelly blu-button so that my rollershutters can be opened and closed without openhab.
  • immediately re-open a relais without delay.

Your use-case should be easily possible too.
There are many examples in the library

I found a script that checks for connectivity whit the code below. How ever it’s not a real ping but it seems to be a HTTPSGET thing, my openHAB server won’t reply to that. Is there any better approach or do I have do deploy a web service of some kind thats reply on httpget commands?

// This script tries to execute HTTP GET requests within a set time, against a set of endpoints
// After certain number of failures the script resets the shelly

let CONFIG = {
  endpoints: [
    "https://global.gcping.com/ping",
      ],
  //number of failures that trigger the reset
  numberOfFails: 5,
  //time in seconds after which the http request is considered failed
  httpTimeout: 10,
  //time in seconds to retry a "ping"
  pingTime: 60,
};

let endpointIdx = 0;
let failCounter = 0;
let pingTimer = null;

function pingEndpoints() {
  Shelly.call(
    "http.get",
    { url: CONFIG.endpoints[endpointIdx], timeout: CONFIG.httpTimeout },
    function (response, error_code, error_message) {
      //http timeout, magic number, not yet documented
      if (error_code === -114) {
        print("Failed to fetch ", CONFIG.endpoints[endpointIdx]);
        failCounter++;
        print("Rotating through endpoints");
        endpointIdx++;
        endpointIdx = endpointIdx % CONFIG.endpoints.length;
      } else {
        failCounter = 0;
      }

      if (failCounter >= CONFIG.numberOfFails) {
        print("Too many fails, resetting...");
        failCounter = 0;
        Timer.clear(pingTimer);
        Shelly.call("Shelly.Reboot");
      }
    }
  );
}

print("Start watchdog timer");
pingTimer = Timer.set(CONFIG.pingTime * 1000, true, pingEndpoints);

As far as I understand the shelly doc

it states that using the GET method it can connect to HTTP and HTTPS ‘pages’.
This means that your OH instance is able to reply.
Of course you need to change the endpoint configuration in the script and replace it with your OH instance.

Hi

I have set only one endpoint to the openhab server “http://ip.adress:port”

The result i get in in powershell from running
Invoke-WebRequest -UseBasicParsing -Uri http://ip.adress:port is

StatusCode        : 200
StatusDescription : OK
Content           : <!doctype html><html><head><meta charset="utf-8"><meta http-equiv="Content-Security-Policy" content="default-src * 'self' 'unsafe-inline' 'unsafe-eval' data: gap: content
                    : blob:; style-src 'self' 'uns...
RawContent        : HTTP/1.1 200 OK
                    Accept-Ranges: bytes
                    Content-Length: 1257
                    Content-Type: text/html
                    ETag: W/"HYBGhN5quwgHYBHCSqc3oE"
                    Last-Modified: Thu, 29 Feb 2024 13:03:24 GMT
                    Server: Jetty(9.4.52.v20230823)
                    ...
Forms             : 
Headers           : {[Accept-Ranges, bytes], [Content-Length, 1257], [Content-Type, text/html], [ETag, W/"HYBGhN5quwgHYBHCSqc3oE"]...}
Images            : {}
InputFields       : {}
Links             : {}
ParsedHtml        : 
RawContentLength  : 1257

The script recon this and reset failcounter but it even reset failcounter when i unplug openHAB server.

My guess is that there is an issue for the script to understand the response using errorcode ====- 114, is there any ideas of how to change that if it’s possible.

Before I added port to openhab server all test failed and shelle rebooted every 5 minutes but after adding port all tests succeed even when powershell script times out.

Logs from shelly script engine says

#when openHAB server is offline
shelly_http_client.:606 0x3ffe3648: Finished; bytes 0, code 0, redir 0/3, auth 0, status DEADLINE_EXCEEDED: Timed out

06:54:02

Success reset failcounter 0

06:54:02

shelly_ejs_rpc.cpp:41 Shelly.call http.get {"url":"http://ip.adress:port","timeout":10}


06:54:51

shelly_http_client.:306 0x3ffe3648: HTTP GET http://ip.adress:port

06:54:51
#when openHAB server is online
shelly_http_client.:606 0x3ffe3648: Finished; bytes 1477, code 200, redir 0/3, auth 0, status OK

So i found a work around changing “error_code === -114” to “error_code === -104” simply meaning request timed out, is that a proper solution or is it a bad solution.

My complete script for successfully verify if openHAB server is reachable


// This script tries to execute HTTP GET requests within a set time, against a set of endpoints
// After certain number of failures the script resets the shelly




let CONFIG = {
  endpoints: [
    "http://ip.adress:port",
      ],
  //number of failures that trigger the reset
  numberOfFails: 5,
  //time in seconds after which the http request is considered failed
  httpTimeout: 10,
  //time in seconds to retry a "ping"
  pingTime: 60,
};

let endpointIdx = 0;
let failCounter = 0;
let pingTimer = null;

function pingEndpoints() {
  Shelly.call(
    "http.get",
    { url: CONFIG.endpoints[endpointIdx], timeout: CONFIG.httpTimeout },
    function (response, error_code, error_message) {
      //http timeout, magic number, not yet documented
      if (error_code === -104) {
        print("Failed to fetch ", CONFIG.endpoints[endpointIdx]);
        failCounter++;
        print("Rotating through endpoints");
        endpointIdx++;
        endpointIdx = endpointIdx % CONFIG.endpoints.length;
        print("Error openHAB server not reachable number of errors:", failCounter);        
        } else {
        failCounter = 0;
        print("Success openHAB server online setting failconter to:", failCounter);
      }

      if (failCounter >= CONFIG.numberOfFails) {
        print("Too many fails, resetting...");
        failCounter = 0;
        Timer.clear(pingTimer);
        Shelly.call("Shelly.Reboot");
      }
    }
  );
}

print("Start watchdog timer");
pingTimer = Timer.set(CONFIG.pingTime * 1000, true, pingEndpoints);

All this seems to work but how do one handle to check status on input for a relay in detached mode and set output to the same state or set relay mode to attached and sync input and output when openHAB server is not reachable?

I did try to implement the snippet in web interface for that but I was not sure how to make it run smooth.

Here’s the solution I came up with in case soemone need to make their own and need isnpiration. Most certainly one could make this much better but it works. I also made a HTTP binding thing and a switch channel to verify that the watchdog is running.

 let input_state = "";
 let output_state ="";
 let input_obj = "";
 let output_obj ="";
function timerCode() {
  
  // Set your input to Switch and in Detached mode for this code to function correctly.
 input_state = Shelly.getComponentStatus("input:0");
 output_state = Shelly.getComponentStatus("switch:0");
 input_obj = (JSON.parse(input_state.state));
 output_obj = (JSON.parse(output_state.output));
 if (input_obj != output_obj && failCounter >= 4){
   print("openHAB server not reachable and Input & Output not in sync input:", input_obj,", Output:", output_obj);
   print("Toggle switc to get input and output in sync");
   Shelly.call("Switch.toggle", {'id': 0});
 }
 if (input_obj != output_obj && failCounter === 0){
   print("openHAB server reachable and Input & Output not in sync input:", input_obj,", Output:", output_obj);
 }
 if (input_obj === output_obj && failCounter === 0){
   print("openHAB server reachable Input & Output is in sync input:", input_obj,", Output:", output_obj);
 }
 //print(obj);
 //print(obj.id);
};

Timer.set(
  /* number of miliseconds */ 5000,
  /* repeat? */ true,
  /* callback */ timerCode
);



// This script tries to execute HTTP GET requests within a set time, against a set of endpoints
// After certain number of failures the script resets the shelly




let CONFIG = {
  endpoints: [
    "http://openHAB.server.ipadress:port",
      ],
  //number of failures that trigger the reset
  numberOfFails: 5,
  //time in seconds after which the http request is considered failed
  httpTimeout: 10,
  //time in seconds to retry a "ping"
  pingTime: 15,
};

let endpointIdx = 0;
let failCounter = 0;
let pingTimer = null;

function pingEndpoints() {
  Shelly.call(
    "http.get",
    { url: CONFIG.endpoints[endpointIdx], timeout: CONFIG.httpTimeout },
    function (response, error_code, error_message) {
      //http timeout, magic number, not yet documented
      if (error_code === -104) {
        print("Failed to fetch ", CONFIG.endpoints[endpointIdx]);
        failCounter++;
        print("Rotating through endpoints");
        endpointIdx++;
        endpointIdx = endpointIdx % CONFIG.endpoints.length;
        print("Error openHAB server not reachable number of errors:", failCounter, output_obj);
                
        } else {
        failCounter = 0;
        print("Success openHAB server online setting failcounter to:", failCounter, output_obj);
      }

      if (failCounter >= CONFIG.numberOfFails) {
        print("Too many fails, resetting...");
        failCounter = 0;
        Timer.clear(pingTimer);
        Shelly.call("Shelly.Reboot");
      }
    }
  );
}

print("Start watchdog timer");
pingTimer = Timer.set(CONFIG.pingTime * 1000, true, pingEndpoints);



great. I think I’m going to adapt this so that night mode (detached) is set to momentary if there is no openhab-server.

Could you replace the code-fences with the correct (straight) ones so that code is readable. Just use the menu if your keyboard uses the wrong ones

Sorry my bad, now the code fence is updated. As I said I also made a switch item from http bindning to verify the state of the script so i can be noticed if sceipt goes down even if the device is online. I haven’t foun a way to change mode from detached yto attached so i just mad up the toggle code which schould work similar.

That’s easy: To switch to momentary, send a http-get to the ip of your shelly:

http://192.168.1.100/settings/relay/0?btn_type=momentary

To switch to detached:

http://192.168.1.100/settings/relay/0?btn_type=detached

Could that be done also via internal script upon detectionnof lost connection with openHAB server?

I think hence the shelly binding has difficultys to reconnect to a device thats offline for a while maybe better solution is to announcr heartbrats via mqtt ot so. This weekend I unplugged my network for a while and reconnected but shelly binding never reconnected even thoug ny scriot could reach openHAB server.

That was the idea. I think it should work but haven’t tried yet.

Following Rich’s advice to design “escalators not elevators” I liked the idea that a shelly relay resets itself to non-smart-home if it cannot connect to the openhab server.

I changed the above script to switch the mode to “momentary” if openhab-server is unavailable and removed everything else.
(This is for Gen2 like Shelly Plus etc; for Gen1 see the command above).

edit: changed script so that the Shelly switches back to “detached” if openhab-server is online again

let CONFIG = {
  endpoints: [ "http://openhab_server:8080", ],
  numberOfFails: 5, httpTimeout: 10, pingTime: 120,
};
let endpointIdx = 0;
let failCounter = 0;
let pingTimer = null;

function pingEndpoints() {
  Shelly.call("http.get", { url: CONFIG.endpoints[endpointIdx], timeout: CONFIG.httpTimeout },
    function (response, error_code, error_message) {
      if (error_code === -104) {
        print("Failed to fetch ", CONFIG.endpoints[endpointIdx]);
        failCounter++;
        print("Rotating through endpoints");
        endpointIdx++;
        endpointIdx = endpointIdx % CONFIG.endpoints.length;
        print("Error openHAB server not reachable number of errors:", failCounter);
                
        } else {        
        print("Success openHAB server online setting failcounter to:", failCounter);
        failCounter = 0;
      }

      if (failCounter >= CONFIG.numberOfFails) {
        print("Too many fails, resetting...");
        failCounter = 0;
        //Timer.clear(pingTimer);
        Shelly.call( "http.get", { url: "http://127.0.0.1/rpc/Switch.SetConfig?id=0&config={%22in_mode%22:%22momentary%22}", timeout: CONFIG.httpTimeout })
      }
    }
  );
}

print("Start watchdog timer");
pingTimer = Timer.set(CONFIG.pingTime * 1000, true, pingEndpoints);

Not really tested yet, so please report in case of errors or problems.

Hello

Is this a valid state, I see this in the instructions, however the script doesn’t work properly for me

oh, my mistake. I meant “detached”. It’s corrected, please try.

It seems to work really fine, but I rhink it seems like the pinging stops once failcounter exceeds numberOfFails. I would like it to continue pinging just becasue. I have the watchdog in shelly device that sets mode to follow and another watchdog in openHAB that should reset it when connection is reastablished and that concept it because sometimes if unit becomes offline whiel updating switches or so in openHAB and it doesn’t heal it self when unit is back online, so shelly device ends up having connection with openHAB but not the other way around.

I removed the clearing of the timer. Now it should run forever.

Now it runs like you said forever, I think I just want to use a in_mode status as condition to setting new_inmode to follow only if old in_mode is follow. For now I just set the if(failcounter part as this, just to make use of testing and troubleshooting it’s seems easier if in_mode isn’t sent if not requierd.

if (failCounter === CONFIG.numberOfFails) {

But when i test code below I just get undefined, but when I use same code in webbrowser via network i get a json with in_mode defined.


print(Shelly.call( "http.get", { url: "http://127.0.0.1/rpc/switch.getconfig?id=0",timeout:10}))