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.

2 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.
1 Like