Jump to content
🌟 NOTIFICATION/Benachrichtigung: Welcome to our New Store! - shelly.com 🌟 ×

Script to Switch on a relay if timing of a button press is made within one of three daily Scheduled time windows?


Recommended Posts

Hi,

Pretty new to all this and pretty overwhelmed TBH, just need some high level pointers to start off with (but feel free to throw some suggested code at me too!).
Pointing to the documentation is fine, but if you do so, could you point to the specific area(s) of it I should look at it please, rather than the whole lot, thanks! 

So I have got a Shelly Button 1, with a simple URL link (placed via the App menu) to a Central Heating (CH) Shelly mini 3G relay, to turn it on (or off) based on a one or two button click, that works fine.

http://192.168.47.127/relay/0?turn=on or http://192.168.47.127/relay/0?turn=off

I have also got a script (function actually) made with a great deal of help from someone else, that turns on or off the 3 "ON" schedules of a Daily Set of 3# ON/OFF Windows (for the Central Heating). The function is on the Shelly that has the Schedules on it.

I added a link to this function as second URL of the Shelly Button 1 press and this also works fine, turning off the CH Schedule (so if I just turn off the relay it does not turn back on again 2 minutes later, using the schedule).

This is all done natively in the Shelly ecosystem.

What I would like to develop, is a simple "If Then Else" logic, such that if I turn the schedule (only) back on, IF the current time lies WITHIN one of the three time windows, it turns the CH relay on; if not, then not. After that the schedule takes over as normal.

So I believe the logic is simply:

If
Window1Start < "TIMENOW" < Window1End

OR
Window2Start < "TIMENOW" < Window2End

OR
Window3Start < "TIMENOW" < Window3End, 

THEN
Turn CH Auto Relay = ON

OTHERWISE
No Action

So, what I believe I need to do is this (but please correct me):

a. Mine the Schedule for the Start and End Times of the 3 Daily Time Windows and get the 6# Constants WindowXStartX and WindowXEnd, where X =1,2, and 3.

I believe http://192.168.47.127/rpc/Schedule.List provides the schedule (see output below) I assume I can get the time from this output for each pair of IDs, (ID3-ID5, ID6-ID8, ID9-ID12) but I am not sure which is the best mechanism for extracting the variables? Should I use a Function (on the Shelly itself) or use a Script or a Template?

{
  "jobs": [
    {
      "id": 2,
      "enable": true,
      "timespec": "0 30 6 * * 0,1,2,3,4,5,6",
      "calls": [
        {
          "method": "switch.set",
          "params": {
            "id": 0,
            "on": true
          }
        }
      ]
    },
    {
      "id": 3,
      "enable": true,
      "timespec": "0 30 8 * * 0,1,2,3,4,5,6",
      "calls": [
        {
          "method": "switch.set",
          "params": {
            "id": 0,
            "on": false
          }
        }
      ]
    },
    {
      "id": 5,
      "enable": true,
      "timespec": "0 0 12 * * 0,1,2,3,4,5,6",
      "calls": [
        {
          "method": "switch.set",
          "params": {
            "id": 0,
            "on": true
          }
        }
      ]
    },
    {
      "id": 6,
      "enable": true,
      "timespec": "0 30 13 * * 0,1,2,3,4,5,6",
      "calls": [
        {
          "method": "switch.set",
          "params": {
            "id": 0,
            "on": false
          }
        }
      ]
    },
    {
      "id": 8,
      "enable": true,
      "timespec": "0 30 15 * * 0,1,2,3,4,5,6",
      "calls": [
        {
          "method": "switch.set",
          "params": {
            "id": 0,
            "on": true
          }
        }
      ]
    },
    {
      "id": 9,
      "enable": true,
      "timespec": "0 0 21 * * 0,1,2,3,4,5,6",
      "calls": [
        {
          "method": "switch.set",
          "params": {
            "id": 0,
            "on": false
          }
        }
      ]
    },
    {
      "id": 12,
      "enable": true,
      "timespec": "0 0 0 * * 0,1,2,3,4,5,6",
      "calls": [
        {
          "method": "Shelly.Update",
          "params": {
            "stage": "stable"
          },
          "origin": "shelly_service"
        }
      ]
    },
    {
      "id": 13,
      "enable": true,
      "timespec": "0 0 23 * * 0,1,2,3,4,5,6",
      "calls": [
        {
          "method": "switch.set",
          "params": {
            "on": false,
            "id": 0
          }
        }
      ]
    }
  ],
  "rev": 402
}

I started looking at this example from the Shelly folks, but it's pretty daunting.

b. Next, to run the IF-THEN-ELSE- statements, should I write this in a "Template" which evaluates the IF/Then logic above to True or False, If True then Turn the Relay On (and the Schedules). If not a Template then, what is the suggested mechanism please?

If anyone has some starting code examples or code library, I can play around with it.

Thank you!

k.

Edited by SmurfnSurf
Proper formatting of Schedule.List
Link to comment
Share on other sites

@SmurfnSurf

Below you will find a code sample which should meet your functional specification.

/// Switch between different times  V1.0
////////////////////////////////////////////////////////////////////////////////////////////
///
/// Created by "If then else" © 2024
///
////////////////////////////////////////////////////////////////////////////////////////////
function timerHandler() {
    // create a new Date object
    let now = new Date();
    // get the current hour (from 0 to 23)
    let hour = now.getHours();
    // get the current minute (from 0 to 59)
    let minute = now.getMinutes();
    // get the current minutes of the actual day
    actdayminutes = (hour * 60) + minute;
    ///
    if (
        ((actdayminutes >= (09 * 60) + 00) && (actdayminutes < (11 * 60) + 00)) /// (time greater than  9:00) AND (time less than 11:00)
        ||
        ((actdayminutes >= (13 * 60) + 00) && (actdayminutes < (15 * 60) + 00)) /// (time greater than 13:00) AND (time less than 15:00)
        ||
        ((actdayminutes >= (15 * 60) + 00) && (actdayminutes < (17 * 60) + 00))) { /// (time greater than 15:00) AND (time less than 17:00)
        //
        print('Time window activ');
        //
    } else {
        //
        print('Time window not activ');
        // 
    };
};
////////////////////////////////////////////////////////////////////////////////////////////
Timer.set(1000, true, timerHandler, null);
////////////////////////////////////////////////////////////////////////////////////////////

 

Link to comment
Share on other sites

Wow thank you, that is awesome (love your username btw, very apt!).

I will see how I can integrate this into the CH system relay and let you know how I’m doing. 
 

If the times could be mined from the schedule ID so they were dynamic that would be a really nice enhancement.

Thank you once again!

k.

Link to comment
Share on other sites

On 10/9/2024 at 12:13 AM, If_then_else said:

@SmurfnSurf

Below you will find a code sample which should meet your functional specification.

 

Hi me again,

OK I have spent some time trying to see whether I could possibly write any part of a script using what I have gleaned from your script (thank you again) and other scripts I have seen, so you don't think I am just expecting it to just be done for me, but that I have tried to work it out :-).

I believe here, in your function (correct me if I am wrong, very newbie like especially with this syntax..):

  • the timerHandler function is entirely bound by the first set of curly brackets {}
  • the result of the 3 'if' lines being true i.e. print('Time window active') is within the second set of curly brackets {} i.e. 'then'; and
  • the 'else' is just by default "not active" and is within the third set of curly brackets {}.
  • timer.set 'arms' the timer, but I am not 100% sure what that means. 1000ms is 1s so I assume that it "x" (see below), while 'true' means it will repeat and 'timerhandler' is the function that repeats?

Some questions please, if I may, to use it to actually enable a switch:

  1. Could the times be extracted from a set of fixed (stated) schedule IDs rather than a fixed value?
  2. Could the period 'x' be increased (assuming this is the 1000ms?)
  3. Could I just run it (timeHandler) for only as long as it takes to confirm the period is "active" then turn on a relay and then STOP running the function (until the next time the function was called) so it does not run constantly?

For (3), the reason for this request (and indeed the timer) is that I have a function (kindly provided by @towiat here) which helps me switch on or off (see pic) the schedules running a CH relay. The function is below FYI.

Essentially (from a Shelly Button 1), I turn off the Manual Button (A) even if it is off, I turn in the Auto Schedule (B) and I start (unnecessarily) the Relay that switches the CH ON.

A. http://192.168.47.128/relay/0?turn=off
B. http://192.168.47.127/rpc/Script.Eval?id=3&code=switchSchedules(true)
C. http://192.168.47.127/relay/0?turn=on

When I switch the schedules on (B), there are two states, either the schedule SHOULD BE running as it is in a Schedule ON Window or it should not.

However so that it is not freezing cold for a long period until the next ON Window, I currently just switch it ON (C) anyway, to be sure. This means however that it might be ON (unnecessarily) for a long time, just wasting power (Diesel actually), when the schedule would normally have it OFF.

Whether this could (should?) run from within function switchSchedules() or whether it should be an independent function that calls your function timerHandler() or runs from within it, I am not sure which is the best approach?

So, assuming the schedules have been turned on (B), I am looking for a function that (I can replace (C) with) that:

  1. First, STARTs your function timeHandler // i.e. it does not need to be running all the time right?
  2. (Preferably) allows function timeHandler to use the IDs from function switchSchedules (or just uses IDs that I put in the function) and checks whether the timer window "is active" or is "not active" based on the start and stop times for each set of IDs.
  3. If timer window "is active" then issues http://192.168.47.127/relay/0?turn=on to start the relay // needs amending of function timerHandler() so that it assigns a variable to "is active" e.g. UNDER "print('Time window active')", just add  a line let tH=1 say?
  4. STOPs function timeHandler, so it does nor use CPU cycles unnecessarily, as it only needs to know it for a brief (almost instantaneous) period, the Auto schedule will take care of the rest.

So, having a go at this myself (excuse the crude attempt!) , something like this, called by C. http://192.168.47.127/rpc/Script.Eval?id=3&code=turnonAutoinWindow

function turnonAutoinWindow() // name of my new function that replace the C line in Shelly Button 1
Script.Start?id=2 // START function timerHandler() using Start and End Times from ID= X, ID=Y, ID=Z// call AND start the timerHandler function
if tH=1 {
http://192.168.47.127/relay/0?turn=on
} else {
Script.Stop?id=2 // STOP function timerHandler()
}

FYI: This is the function from @towiat

function switchSchedules(value) {
  let schedules = [2, 5, 8]; // <-- example: change only the 1st, 3rd, 6th and 7th schedule
  Shelly.call("Schedule.List", {}, function (result) {
    for (let job of result.jobs) {
      if (schedules.indexOf(job.id) !== -1) {
        job.enable = value;
        Shelly.call("Schedule.Update", job);
      }
    }
  });
}

 

TimerHandler.jpg

CurrentButton.jpg

Edited by SmurfnSurf
clarity
Link to comment
Share on other sites

@SmurfnSurf
I would like to set a few facts straight.
My script proposal would be created based on the specification you formulated. (See below)
- I am convinced that this will also be fulfilled with my proposal.

Quote

What I would like to develop, is a simple "If Then Else" logic, such that if I turn the schedule (only) back on, IF the current time lies WITHIN one of the three time windows, it turns the CH relay on; if not, then not. After that the schedule takes over as normal.

So I believe the logic is simply:

If
Window1Start < "TIMENOW" < Window1End

OR
Window2Start < "TIMENOW" < Window2End

OR
Window3Start < "TIMENOW" < Window3End, 

THEN
Turn CH Auto Relay = ON

OTHERWISE
No Action

So, what I believe I need to do is this (but please correct me):

-------------------------------------------------------------------------------------------------------------

I can tell from your last posting that you have very limited programming experience.😉

  • You are mixing actions, cyclic program sequences and event-triggered functions.
  • You are also mixing various code examples from different authors.
     

I will therefore take myself out of this matter and would advise you to read through this Shelly documentation.{Shelly documentations}

 

Edited by If_then_else
Link to comment
Share on other sites

30 minutes ago, If_then_else said:

I can tell from your last posting that you have very limited programming experience.

You are correct and my apologies if I’ve overstepped the mark with my request for help here. I very much appreciate the sample you posted.

I appreciate that having a set of powerful instructions makes the device utilisation very powerful but I didn’t think it would be (or should be) this hard to get them to do a few simple things.

I will go through the documentation as suggested but I’d imagine that I will probably end up just being daunted by it and I’d imagine I am not the first nor the last.

Nonetheless, appreciate your input.

Link to comment
Share on other sites

Okay, I'll give this a try - in our last conversation, I answered your question about the Schedule.Update thing without a real understanding of why you need it.

Your schedule-based solution is creative but also difficult to handle and I don't think that you ever had a chance of solving this completely without some coding. And I wouldn't exactly call this 'a few simple things' ;).

If I understand your requirements correctly, then this problem can be solved in a much easier (well, easier for programmers ;)) way with approximately 30 lines of code and a single schedule. But before I show you a possible solution, I need to make absolutely sure that I really understand what you want to achieve. So can you read my interpretation of your requirements and tell me if it is accurate:

  1. Your Shelly should be able to control your central heating autonomously. When in that 'autonomous mode', it should BY ITSELF make sure that the heating is switched on during certain times of the day and switched off for the remaining time. In your current setup, it should be switched on from 06:30 to 08:30, 12:00 to 13:30 and 15:30 to 21:00, but you may need different/more/fewer time windows in the future.
  2. You want the ability to disable the autonomous mode at will by sending an HTTP request. When the autonomous mode is disabled, the Shelly should do nothing by itself, but you should be able to manually turn heating on or off (again with HTTP requests).
  3. You also want the ability to re-enable autonomous mode with an HTTP request. When you do that, the Shelly should immediately resume the logic that is described in #1.

Is this somewhat close or did I get it wrong or miss something?

Link to comment
Share on other sites

3 hours ago, towiat said:

Okay, I'll give this a try - in our last conversation, I answered your question about the Schedule.Update thing without a real understanding of why you need it.

Your schedule-based solution is creative but also difficult to handle and I don't think that you ever had a chance of solving this completely without some coding. And I wouldn't exactly call this 'a few simple things' ;).

If I understand your requirements correctly, then this problem can be solved in a much easier (well, easier for programmers ;)) way with approximately 30 lines of code and a single schedule. But before I show you a possible solution, I need to make absolutely sure that I really understand what you want to achieve. So can you read my interpretation of your requirements and tell me if it is accurate:

  1. Your Shelly should be able to control your central heating autonomously. When in that 'autonomous mode', it should BY ITSELF make sure that the heating is switched on during certain times of the day and switched off for the remaining time. In your current setup, it should be switched on from 06:30 to 08:30, 12:00 to 13:30 and 15:30 to 21:00, but you may need different/more/fewer time windows in the future.
  2. You want the ability to disable the autonomous mode at will by sending an HTTP request. When the autonomous mode is disabled, the Shelly should do nothing by itself, but you should be able to manually turn heating on or off (again with HTTP requests).
  3. You also want the ability to re-enable autonomous mode with an HTTP request. When you do that, the Shelly should immediately resume the logic that is described in #1.

Is this somewhat close or did I get it wrong or miss something?

Thank you (and genuinely no disrespect to @If_then_else here).

Yes I believe you have it well-described in your points 1 to 3, but perhaps let me give you some further background so it makes sense.

  • My parents have a Diesel boiler that runs the Central Heating.
  • It was controlled by an (equivalent) Honeywell ST-6400C timer in which you can set 3 (and only 3) on-ff time windows a day.
  • It had a slide switch where you could either (a) turn it off completely (OFF) (b) have it on the timer (AUTO) (c) run it continuously (MANUAL) or (d) run it once (ONCE), a feature we do not need and do not use.
  • We had the Controller replaced with 2# Shelly Switches (Relays), one for the AUTO mode and one for the MANUAL. I appreciate we could probably have used one relay but it would have got too complicated, so we decided to try to emulate the slide switch (and provide some redundancy) with two switches.
  • The AUTO Switch is controlled by a Shelly Button 1 (a white one). Its IP address is 192.168.47.127
  • The MANUAL Switch is controlled by a separate Shelly Button 1 (a black one). Its IP address is 192.168.47.128.
  • The actions of each Button are shown in the pictures attached and in the Table below. My brother and I decided utilising two separate Buttons was the easiest way to emulate the previous controller and we purposely shied away from more than two sets of button-presses per button.
  • We also decided turning both OFF when folks are away for an extended period was a reasonable compromise to emulate the slide switch off; and we can see the relays go off in the App just to be sure.
  • One point to note is that the Auto-on mode or manual-off Mode (switchSchedules(true)) turns ON the Diesel Boiler EVEN IF the time(now) is outside the (Auto) Time WIndow in which they would ordinarily run. This happens in two scenarios, at either Auto On or Manual Off (and wastes fuel unnecessarily). 

Shelly Button 1 URLs

Auto On (turn off Manual SW; turn on Auto Schedules; turn on Auto SW*) - WHITE BUTTON

  • http://192.168.47.128/relay/0?turn=off
  • http://192.168.47.127/rpc/Script.Eval?id=3&code=switchSchedules(true)
  • http://192.168.47.127/relay/0?turn=on

* If the schedule ON time has passed (even by just a minute) unless you turn Auto Relay ON (URL 3), it will only turn on in the next window. If we also turn ON the Auto SW, and this is just a minute past when Auto would have turned it off, then it will run for hours and burn Diesel until the end of the next Window. Need to rethink this.

Auto off (turn off Auto SW; turn off Auto Schedules; turn off Manual SW) - WHITE BUTTON

  • http://192.168.47.127/relay/0?turn=off
  • http://192.168.47.127/rpc/Script.Eval?id=3&code=switchSchedules(false)
  • http://192.168.47.128/relay/0?turn=off

Manual On (turn off Auto SW; turn off Auto Schedules; turn on Manual SW with a Short Timer) - BLACK BUTTON

  • http://192.168.47.127/relay/0?turn=off
  • http://192.168.47.127/rpc/Script.Eval?id=3&code=switchSchedules(false)
  • http://192.168.47.128/relay/0?turn=on&timer=7200

7200s = 2 hours

Manual off (turn off Manual SW*; turn on Auto Schedules; turn on Auto SW) - BLACK BUTTON

  • http://192.168.47.128/relay/0?turn=off
  • http://192.168.47.127/rpc/Script.Eval?id=3&code=switchSchedules(true)
  • http://192.168.47.127/relay/0?turn=on

See comment above re the Auto schedule and on current ON window.

I believe these are what you describe as being able to enable or disable "autonomous mode with an HTTP request".

As you have probably seen from my feeble attempts to make this work, my attempts so far have been all using the HTTP requests you refer to above, based on the Shelly Webhooks reference. This is something I can understand reasonably easily and get to work with trial and error.

This, along with your script to disable or (re)enable schedules based on schedule IDs has enabled me to almost emulate the slide switch of the original controller (ignoring ONCE), with the exception that on the original controller, Auto would run the Boiler if you switched it to that mode WITHIN the time WIndow and would NOT run it, if it was not within that Window. My current Shelly setup does not recognise Time(Now) vs Schedule, hence the initial request in this thread, to somehow marry the two.

I am quite happy with the HTTP calls, just aware (from the messages in the App) of not having too many. I was thinking about this issue again today and was wondering if If_then_else's script already identifies that you're within a time  window, whether just after:

print('Time window activ');

you could just write this and it would switch it on (but you'd still need some way of calling the function, maybe a fourth HTTP request, too many?). Anyway, this is where I started ot get a bit lost, sorry.

http://192.168.47.127/rpc/Switch.Set?id=0&on=true

My concern with the function timerHandler() was that it seemed to run continuously, which wasn't really required, only as a once-off, when you enabled the schedules (one of Auto Mode ON or Manual Mode OFF).

My comment (separate post above) regarding using IDs from your function was secondary, a nice to have in case the schedules were changed in the App (but the one in the script gets forgotten).

Hopefully this gives you some better understanding of what we woudl like ot do, without (hopefulyl) it becoming too complicated.

Thank you for your offer of help and if it looks like it's going to be too-hard-basket, that's ok.

Stanley.jpg

Shelly_Buttons_and_Switches.jpg

CH_Auto_Single_Push_Double_Push.jpg

CH_Manual_Single_Push_Double_Push.jpg

 

Edited by SmurfnSurf
spelling, clarity
Link to comment
Share on other sites

Okay, that was quite the wall of text ;), but as far as I can see, the solution that I have in mind should actually work.

Before we begin: This solution is completely different from yours. If you want to use it, you have to remove all the schedules that you currently have defined and you should also remove the script that provides the switchSchedules() function as it is no longer needed.

Before you do anything, make sure that you have read and understood this whole thing - then, you have to decide if you want to try this.

The solution has to components:

  1. A simple script that provides a function that I have named checkTime(). This function checks if the current time is within one of the time windows in which heating should be activated. If the answer is 'yes', it will make sure that the power switch is ON. If the answer is 'no', it will make sure that the power switch is OFF.
  2. One single schedule that calls checkTime() once every minute.

The single schedule represents AUTO mode. When it is enabled, the regular calls to checkTime() will make sure that heating is on or off as desired. To enable it, you use a standard Schedule.Update call:

http://192.168.47.127/rpc/Schedule.Update?id=1&enable=true  # use your schedule id!

When you disable the schedule, the regular calls to checkTime() won't happen, so you are free to switch heating on or off by other means (like your MANUAL button). Disabling the schedule also works with the standard Schedule.Update URL:

http://192.168.47.127/rpc/Schedule.Update?id=1&enable=false  # use your schedule id!

For your buttons, this means:
- The switchSchedules(true) URL must be replaced with the above enabling URL
- The switchSchedules(false) URL must be replaced with the above disabling URL

Since the schedule re-checks the heating status every minute, you no longer need to worry about whether you are in the time window or not after enabling AUTO mode. The checkTime() function will automatically do the right thing during its next execution (which may happen up to 60 seconds after the enabling call).

And... that's it.

If you want to use this, you need to install the script first. The installation routine is exactly the same as for the switchSchedules function (create script, paste code, save and start, activate 'run on startup'). This is the code that you have to copy:

// list of time windows with their respective start and end hours/minutes
// when auto mode is active, heating will be turned on only in these time windows
// sHour + sMin define the start and eHour + eMin the end of each window
// change this if you want - you can have as many windows as you need
let timeWindows = [
  { sHour: 6, sMin: 30, eHour: 8, eMin: 30 }, // 1st time window
  { sHour: 12, sMin: 0, eHour: 13, eMin: 30 }, // 2nd time window
  { sHour: 15, sMin: 30, eHour: 21, eMin: 0 }, // 3rd time window
];

// helper function to convert hour + minute into minute of the day
function minOfDay(hour, minute) {
  return hour * 60 + minute;
}

// the schedule will call this function every full minute
function checkTime() {
  // get the current minute
  let now = new Date();
  let curMin = minOfDay(now.getHours(), now.getMinutes());

  // check if the current minute is within one of the time windows
  let inTimeWindow = false;
  for (let win of timeWindows) {
    if (minOfDay(win.sHour, win.sMin) <= curMin && curMin < minOfDay(win.eHour, win.eMin)) {
      inTimeWindow = true;
      break;
    }
  }

  // get the current state of the power switch and flip it if needed
  Shelly.call("Switch.GetStatus", { id: 0 }, function (result) {
    if (result.output !== inTimeWindow) {
      Shelly.call("Switch.Set", { id: 0, on: inTimeWindow });
    }
  });
}


Note that you can change the time window definitions if you want to. Also note down the ID that the Shelly has assigned to this script as we need it in the second step.

In order to create the schedule that calls checkTime() every minute, we need to use an HTTP RPC call (the Shelly user interface unfortunately lacks the functionality to create this specific schedule).

First, you need modify the below URL so that it contains the correct script id - replace the number after "id": with the correct ID from the previous step.

Then, invoke the URL EXACTLY ONCE in the browser.

http://192.168.47.127/rpc/Schedule.Create?timespec="0 * * * * *"&calls=[{"method":"Script.Eval","params":{"id":1,"code":"checkTime()"}}]

After that, you should see the created schedule on the Shelly. It may display a message "Call my not work as expected", but this is just a quirk in the Shelly firmware. The call will, in fact, work as expected.

And that should be it. Let me know if you were brave enough to try it ;) and if it works as expected.

 

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Erstelle neue...