Control smart lights with Shelly with automated detached mode

Setup: Zigbee smart lights, Shelly 1 Plus relays in detached mode and Home Assistant automations.
Problem: wall switches don’t work if Home Assistant or Wi-Fi is down.
Solution: on-device script that checks if Home Assistant is available and changes the Shelly’s switch mode.
The code:

let outputId = 0; // For Shelly 1 Plus it's always 0. Change if needed
let checkPeriod = 10 * 1000 // in ms

let currentMode = "detached";
// In the GUI, operation mode is set in the input section, but via RPC calls, it is set on the output
// "id" is the ID of the output to which the new configuration will be applied, and "config" is the actual configuration
let config = {
  id: outputId,
  config: {
    in_mode: "detached"
  }
};

function updateConfig(oldConfig) {
  // Apply the new config only if the switch is not already working in the desired mode  
  if (oldConfig.in_mode !== currentMode) {
    Shelly.call("Switch.SetConfig", config);
  }
}

function setSwitchFromRespons(resp) {
  // If HA is accessible the response code is 200
  if (resp.code === 200) {
    currentMode = "detached";
  } else {
    currentMode = "flip";
  }
  
  // Update the mode in the new configuration
  config.config.in_mode = currentMode;
  
  // Get the current output configuration and pass it to updateConfig
  Shelly.call("Switch.GetConfig", {id: outputId}, updateConfig)
}

function testHA() {
  // For RPC command HTTP.GET, both url and body are required. 
  Shelly.call("HTTP.GET", {url:'http://YOUR_HOME_ASSISTAN_URL_OR_IP:8123', body:{}}, setSwitchFromRespons);
}

// Timer.set(period_in_ms, reapeate_indefinitely, function)
Timer.set(checkPeriod, true, testHA);

This code can easily be adapted for Shelly 2 Plus and other 2nd gen Shelly devices. 1st gen devices do not support scripting.

3 Likes

I’m running this now on one of my Shellies. I’ll try it out and see how it works.

Furthermore, I’ve made some minor changes (I hope this is okay with you):

My version
  • Put home assistant URL in a separate variable so it can be easily changed
  • Changed function name setSwitchFromRespons to setSwitchFromResponse
  • Changed resp to response in function
// Adapted by Bo from AlesAlitis (Nedyalko Vasilev)
// https://community.home-assistant.io/t/control-smart-lights-with-shelly-with-automated-detached-mode/576881

let outputId = 0; // For Shelly 1 Plus it's always 0. Change if needed
let checkPeriod = 10 * 1000; // in milliseconds
let homeassistantUrl = "http://YOUR_HOME_ASSISTANT_IP:8123";

let currentMode = "detached";
// In the GUI, operation mode is set in the input section, but via RPC calls, it is set on the output
// "id" is the ID of the output to which the new configuration will be applied, and "config" is the actual configuration
let config = {
  id: outputId,
  config: {
    in_mode: "detached"
  }
};

function updateConfig(oldConfig) {
  // Apply the new config only if the switch is not already working in the desired mode  
  if (oldConfig.in_mode !== currentMode) {
    Shelly.call("Switch.SetConfig", config);
  }
}

function setSwitchFromResponse(response) {
  // If HA is accessible the response code is 200
  if (response.code === 200) {
    currentMode = "detached";
  } else {
    currentMode = "flip";
  }
  
  // Update the mode in the new configuration
  config.config.in_mode = currentMode;
  
  // Get the current output configuration and pass it to updateConfig
  Shelly.call("Switch.GetConfig", {id: outputId}, updateConfig)
}

function testHA() {
  // For RPC command HTTP.GET, both url and body are required. 
  Shelly.call("HTTP.GET", {url: homeassistantUrl, body:{}}, setSwitchFromResponse);
}

// Timer.set(period_in_ms, reapeate_indefinitely, function)
Timer.set(checkPeriod, true, testHA);

1 Like

Here is an updated version of the script:

  • Added the changes suggested by @bvhme.
  • Streamlined the code and removed some unnecessary checks and variables. This should reduce the load on the Shelly’s limited resources.
  • Added a check if switching to “detached” mode and turning the relay on because I keep the switch entity in HA disabled. If you keep it enable, you can comment it out
Version 2
// If firmware version is lower than 1, relace const with let
const switchId = 0; // For Shelly 1 Plus it's always 0. Change if needed
const checkPeriod = 10 * 1000; // in ms
const haUrl = 'http://YOUR_HOME_ASSISTAN_URL_OR_IP:8123'; // As sugested by bvhme
let mode = "detached"; // Could be "detached", "flip", "follow" or "momentary". Use "detached" when HA is in control

function updateConfig(oldConfig) {
  //Aplly the new config only if the switch is not already working in the desired mode  
  if (oldConfig.in_mode !== mode) {
    Shelly.call("Switch.SetConfig", { id: switchId, config: { in_mode: mode } });
    // If moving to HA controlled mode, make sure that the relay is switched on
    if (mode === "detached") Shelly.call("Switch.Set", { id: switchId, on: true });
  }
}

function setSwitchFromRespons(response) {
  // If HA is accessible the response code is 200 update the current mode and the new configuration
  mode = (response.code === 200)? "detached" : "flip";
  
  // Get the current output configuration and pass it to updateConfig
  Shelly.call("Switch.GetConfig", {id: switchId}, updateConfig)
}

function testHA() {
  // For RPC command HTTP.GET, both url and body are required. 
  Shelly.call("HTTP.GET", {url: haUrl, body:{}}, setSwitchFromRespons);
}

// Timer.set(period_in_ms, reapeate_indefinitely, function)
Timer.set(checkPeriod, true, testHA);
2 Likes

Thanks for posting your code @AlesAlitis. Not sure if it’s something unique about my setup, but when HA was offline I was getting an error at the “response.code” line that stopped the script. I added a tweak to the if statement and it seems to be working fine.

My Version
let switchId = 0; // For Shelly 1 Plus it's always 0. Change if needed
let checkPeriod = 10 * 1000; // in ms
let haUrl = 'http://YOUR_HOME_ASSISTAN_URL_OR_IP:8123'; // As sugested by bvhme
let mode = "detached"; // Could be "detached", "flip", "follow" or "momentary". Use "detached" when HA is in control

function updateConfig(oldConfig) {
  //Aplly the new config only if the switch is not already working in the desired mode  
  if (oldConfig.in_mode !== mode) {
    Shelly.call("Switch.SetConfig", { id: switchId, config: { in_mode: mode } });
    // If moving to HA controlled mode, make sure that the relay is switched on
    if (mode === "detached") Shelly.call("Switch.Set", { id: switchId, on: true });
  }
}

function setSwitchFromRespons(response) {
  // If HA is accessible the response code is 200 update the current mode and the new configuration
  // response.code was giving error in original code modified to flip if response is null
  if (response === null) {
    mode = "flip";
  } else if (response.code === 200) {
    mode = "detached";
  } else {
    mode = "flip";
  }

  // Get the current output configuration and pass it to updateConfig
  Shelly.call("Switch.GetConfig", {id: switchId}, updateConfig)
}

function testHA() {
  // For RPC command HTTP.GET, both url and body are required. 
  Shelly.call("HTTP.GET", {url: haUrl, body:{}}, setSwitchFromRespons);
}

// Timer.set(period_in_ms, reapeate_indefinitely, function)
Timer.set(checkPeriod, true, testHA);

For anyone else who comes to this thread. Another issue I have is I have some cheap WiFi bulbs that I connect using Local Tuya. The connection is usually solid but can have issues on a HA reboot. To make sure all the lights turn off/on as expected I added a check in my automation to see if all the lights were detected by HA. If not, I have the automation switch the mains at the Shelly.

Hi,

i stumbled over this idea and it might be the solution for my setup-problems. Now I was asking myself: as long as the switch is in detached mode, how do you control the zigbee bulbs? A script?

Would be awesome if you could help.

Regards
Philipp

Here is a slimmed down version:

Light version
function testHA() { Shelly.call("HTTP.GET", {url: 'http://homeassistant.home:8123', body:{}}, function(resp) {
    let mode = (resp && resp.code === 200)? "detached" : "flip";
    Shelly.call("Switch.GetConfig", {id: 0}, function(oldConfig) {
      if (oldConfig.in_mode !== mode) {
        Shelly.call("Switch.SetConfig", { id: 0, config: { in_mode: mode } });
        if (mode === "detached") Shelly.call("Switch.Set", { id: 0, on: true });
      }
    });
  })
}

Timer.set((30 * 1000), true, testHA);

Why:

  • The code is slimmed down to reduce its size. A Shelly’s script store is very limited and every kB counts
  • Merged everything in a single function. Shelly’s JS engine has a limit on recursion, and I think that was crashing the script for me.
3 Likes

Thanks for your work.

Do I have to implement the script in the Shelly app or in HA?

You have to use the Shelly’s web UI.

That’s awesome guys. Thank you. I will test it soon.

I’m using this for Shelly Plus 1PM. What needs to be changed to get this working for output 1 on the Plus 2 PM?

Not much.

To customize the light version:
change url to your local home assistant address or IP
id: 0 points to switch 1, id: 1 to switch 2. Default is id: 0, since with 1PM there is only one switch. If you want to apply this to switch to in Plus PM2, replace all id: 0 to id: 1
in let mode… line. “detached” : “flip” is ok, if you are using normal physical flip switch. I’m personally using button, so I changed this to “detached” : “momentary”

Hi

How about the shelly devices like 2pm and zigbee bulbs. Some times Home Assistant can be up and running, but the add-on like Zigbee2mqtt can be down or Mqtt integration “Failed to set up”. In these cases this script isn’t working. What changes need to be made so that the script takes these into account?

Main thing would of course make your z2m installation more stable.

That aside, you could add z2m addon check with the same automation you would trigger with Shelly click.

In that automation you would check first if target zigbee light is available, if not, call to turn of the relay, if it is, toggle the light itself.

1 Like

Yeah. The main problem was the MQTT integration which some reason “Failed to set up” yesterday, but now it working again.

But you made a good point here. I’m going to made my light automations to check zigbee lights status and do necessary things depending lights state.

Thanks!

When using the Lite mode of this script with the 2PM relays, how do you add the code to change BOTH switch 0 & switch 1 when HA is not available?

Will the changes here work to enable both Shelly relays on the 2PM to switch to toggle mode when HA is not available?

// Switch ID 0 & 1
function testHA() { Shelly.call("HTTP.GET", {url: 'http://homeassistant.local:8123', body:{}}, function(resp) {
    let mode = (resp && resp.code === 200)? "detached" : "flip";
    Shelly.call("Switch.GetConfig", {id: 0}, function(oldConfig) {
      if (oldConfig.in_mode !== mode) {
        Shelly.call("Switch.SetConfig", { id: 0, config: { in_mode: mode } });
        if (mode === "detached") Shelly.call("Switch.Set", { id: 0, on: true });
      }
	  {
        Shelly.call("Switch.SetConfig", { id: 1, config: { in_mode: mode } });
        if (mode === "detached") Shelly.call("Switch.Set", { id: 1, on: true });
      }
    });
  })
}

Timer.set((30 * 1000), true, testHA);```

So I think this adjustment to the script works on the 2PM shelly
Note that I change the mode for when Home assistant is unavailable from flip to follow

function testHA() { Shelly.call("HTTP.GET", {url: 'http://homeassistant.local:8123', body:{}}, function(resp) {
    let mode = (resp && resp.code === 200)? "detached" : "follow";
    Shelly.call("Switch.GetConfig", {id: 0}, function(oldConfig) {
      if (oldConfig.in_mode !== mode) {
        Shelly.call("Switch.SetConfig", { id: 0, config: { in_mode: mode } });
        if (mode === "detached") Shelly.call("Switch.Set", { id: 0, on: true });
      }
	});
	Shelly.call("Switch.GetConfig", {id: 1}, function(oldConfig) {
      if (oldConfig.in_mode !== mode) {
        Shelly.call("Switch.SetConfig", { id: 1, config: { in_mode: mode } });
        if (mode === "detached") Shelly.call("Switch.Set", { id: 1, on: true });
      }
    });
  })
}
Timer.set((30 * 1000), true, testHA);

Thanks to this thread I finally also was able to solve the dilemma between smart bulbs and working light switches.

I rewrote the script to be event baased instead of periodically checking the status. So far it works pretty good with my shelly plus 1 that I put behind normal switches:

// Preconditions:
// Shelly connected to a manual switch and to a smart light
// Shelly set to detached mode and to restore power to on after power loss
// Input mode is switch
// Shelly is added to Home Assistant
// An automation is created in Home Assistant to toggle the light when online

// config
let ha_url = "http://homeassistant:8123/api/ping";
let input_id = 0 //default for shelly plus 1
let output_id = 0 //default for shelly plus 1

let online_status = 0;

// Process response from the HA URL, the 404 error would be part of the result variable
function processHttpResponse(result, error_code, error) {
  if (error_code !== 0) {
    online_status = 0
    //print("Offline")
  } else {
    online_status = 1
    //print("Online")
  }
}

function do_online_check() {
  Shelly.call("HTTP.GET", { url: ha_url, timeout: 1 }, processHttpResponse);
}

//switch toggled event -> if we are online and ha is reachable, make sure the relay is on and leave controlling the smart bulb to home assistant
Shelly.addEventHandler(function (event) {
  if (typeof event.info.state !== 'undefined') {
    if (event.info.id === input_id) {
      do_online_check();
      if (online_status === 1) {
        Shelly.call("Switch.set", { 'id': output_id, 'on': true });       
      }
      else {
        Shelly.call("Switch.toggle", { 'id': output_id });
      }
    }
  }
});

Credits go to this thread and also to this blogpost

This script is excellent!

function testHA() { Shelly.call("HTTP.GET", {url: 'http://192.168.42.101:8123', body:{}}, function(resp) {
    let mode = (resp && resp.code === 200)? "detached" : "follow";
    Shelly.call("Switch.GetConfig", {id: 0}, function(oldConfig) {
      if (oldConfig.in_mode !== mode) {
        Shelly.call("Switch.SetConfig", { id: 0, config: { in_mode: mode } });
        if (mode === "detached") Shelly.call("Switch.Set", { id: 0, on: true });
      }
    });
  })
}

Timer.set((30 * 1000), true, testHA);

I’m trying to refine this to a seamless transition, so I configured homeassistant to match smartlight on-off calls with Input(0), and changed flip to follow so that there would be no on-off logic discrepancy between my dumb light switch in follow configuration and the smart light detatched configuration.

When this script changes shelly’s configuration from detatched to follow, it does not update Output(0) to match Input(0). I have to flip Input(0) on and off before Output(0) starts matching Input(0)'s condition.

How can I add a line like the below logic to fix this?

if (mode === "follow") Shelly.call("Switch.Set", { id: 0, on: match Input });

Thank you!