Compare Device States

Posted on
Sat Sep 14, 2013 11:59 am
autolog offline
Posts: 3991
Joined: Sep 10, 2013
Location: West Sussex, UK [GMT aka UTC]

Compare Device States

I want to do something that should be quite simple but am struggling to get started :?

I want to trigger an action by comparing two state values in my thermostat device - if my measured thermostat temperature changes and is less than or greater than the thermostat set point then control lthe boiler.

In essence something like:

Code: Select all
If thermostat measured  temperature is less than thermostat Heat Setpoint
  turn boiler switch on
else
  turn boiler switch off


I was looking at triggers but I seem to be only compare against a fixed value.
So I thought I would look at Condition scripting. Not sure if this understands Python?
I would much prefer to use Python as I have coded in that and have never used Applescript.

Maybe I am making it too complicated - any pointers would be appreciated :)

Posted on
Sun Sep 15, 2013 9:52 am
autolog offline
Posts: 3991
Joined: Sep 10, 2013
Location: West Sussex, UK [GMT aka UTC]

Re: Compare Device States

autolog wrote:
... Maybe I am making it too complicated - any pointers would be appreciated :)


I was making it too complicated :wink:

I have resolved the issue by using a combination of variables and triggers with no scripting involved.

Posted on
Mon Sep 16, 2013 9:34 am
jay (support) offline
Site Admin
User avatar
Posts: 18260
Joined: Mar 19, 2008
Location: Austin, Texas

Re: Compare Device States

Make this the action of a trigger that fires whenever the temperature changes (and I suppose whenever the heat set point changes as well):

Code: Select all
myThermostat = indigo.devices[123]
if myThermostat.temperatures[0] < myThermostat.heatSetpoint:
    indigo.device.turnOn(123) # id of boiler switch
else:
    indigo.device.turnOff(123) # id of boiler switch

Jay (Indigo Support)
Twitter | Facebook | LinkedIn

Posted on
Mon Sep 16, 2013 10:09 am
nsheldon offline
Posts: 2469
Joined: Aug 09, 2010
Location: CA

Re: Compare Device States

autolog wrote:
I was looking at triggers but I seem to be only compare against a fixed value.
So I thought I would look at Condition scripting. Not sure if this understands Python?
I would much prefer to use Python as I have coded in that and have never used Applescript.

Maybe I am making it too complicated - any pointers would be appreciated :)


To accomplish this using condition scripting, you'd need 2 triggers. The first would turn on the boiler. The second would turn it off. Condition scripts only accept AppleScript. To script this test, you'd put the below script as the condition for the boiler on trigger.
Code: Select all
set thermostat to device "<Your Thermostat Device Name>"
if (temperatures of thermostat) as number < heat setpoint of thermostat then
   return true
else
   return false
end if

and this code for the boiler off trigger.
Code: Select all
set thermostat to device "<Your Thermostat Device Name>"
if (temperatures of thermostat) as number >= heat setpoint of thermostat then
   return true
else
   return false
end if


Jay's solution seem more efficient as it doesn't require 2 triggers. Plus it uses Python instead (in the action, not the condition).

Posted on
Mon Sep 16, 2013 12:14 pm
autolog offline
Posts: 3991
Joined: Sep 10, 2013
Location: West Sussex, UK [GMT aka UTC]

Re: Compare Device States

Thanks for the replies Jay & Nathan which have given me inspiration :)

I am trying to handle multiple radiators and so have come up with this script which I will use for each radiator. I will also have a Server startup trigger to intialise the various variables. The script I have developed so far is below - I am still figuring out some of the Indigo/Python syntax so its not quite correct yet :wink: It will be triggered if either the Heat Setpoint or Temperature changes (as suggested).

Code: Select all
# Script to turn boiler on & off depending on demand from multiple radiator thermostats
#
# There is a "global" variable that counts the number of radiators calling for heat: RadiatorsCallingForHeat
# Each radiator has an Indigo variable associated with it: Rnn_CallingForHeat where nn is the number of the radiator
#
# In essence, if the number of radiators calling for heat is greater than zero, the boiler is turned off else it is turned off
#
radiator = indigo.devices[1861700997] # "Radiator 01 Thermostat"
boiler = indigo.devices[862509423] # "Boiler Connect"
callingForHeat = indigo.variables[789502641] # "R01_CallingForHeat" = true if heating required else false
radiatorsCallingForHeat = indigo.variables[1167250639] # "RadiatorsCallingForHeat" = Number of radiators calling for heat

if myThermostat.temperatures[0] < myThermostat.heatSetpoint:
    if callingForHeat is false: # Only increment number of radiators calling for heat if not currently calling for heat
        indigo.variable.updateValuecallingForHeat = true
        indigo.variable.radiatorsCallingForHeat += 1
else:
    if callingForHeat is true: # Only decrement number of radiators calling for heat if radiator currently calling for heat
        indigo.variable.updateValuecallingForHeat = false
        indigo.variable.radiatorsCallingForHeat -= 1

if indigo.variable.radiatorsCallingForHeat > 0:
    if indigo.device.boiler.mode is 'off': # Only turn boiler off if it is on [BAD PYTHON]
        indigo.device.turnOn(862509423) # id of boiler switch
else:
    if indigo.device.boiler.mode is 'on':  # Only turn boiler on if it is currently off [BAD PYTHON]
        indigo.device.turnOff(862509423) # id of boiler switch

Posted on
Mon Sep 16, 2013 1:01 pm
jay (support) offline
Site Admin
User avatar
Posts: 18260
Joined: Mar 19, 2008
Location: Austin, Texas

Re: Compare Device States

Code: Select all
    if indigo.device.boiler.mode is 'off': # Only turn boiler off if it is on [BAD PYTHON]


translation:

Code: Select all
if not indigo.device[123].onState:  #id of boiler switch


although it won't hurt to tell a device that's already off to turn off... ;)

And your variable setters won't work but I'll let you figure that one out.

Jay (Indigo Support)
Twitter | Facebook | LinkedIn

Posted on
Mon Sep 16, 2013 3:51 pm
autolog offline
Posts: 3991
Joined: Sep 10, 2013
Location: West Sussex, UK [GMT aka UTC]

Re: Compare Device States

jay (support) wrote:
... translation:
Code: Select all
if not indigo.device[123].onState:  #id of boiler switch
...

Thanks for the translation :)
I am having a few problems with the boiler connect device. It is seen by Indigo as the following device:
Code: Select all
Indigo Device "Boiler Connect" Z-Wave Properties:
Indigo Z-Wave Version: 1.0.118
Node ID: 4
Model: 1 Channel Boiler Actuator (SSR303 / ASR-ZW)
Model ID: 00030001
Manufacturer: Horstmann
Manufacturer ID: 0059
Protocol Version: 2.78
Application Version: 2.00
Model Definition Version: 1
Library Type: 6
Class Name: Thermostat
Class Hierarchy: 04 : 08 : 00
Command Class Base: 00
Command Versions: 20v1 40v1 72v1 25v1 86v1
Multi-Endpoint Types: - none -
Multi-Endpoint Classes: - none -
Multi-Instance Counts: - none -
Features: routing, beaming
Neighbors: 1, 2
Associations: - none -
Config Values: - none -

I tried your translation but it didn't like the 'onState', so I tried 'heatIsOn' which worked.
When it executes the line:
Code: Select all
indigo.device.turnOn(862509423) # id of boiler switch

I get an error:
Script Error embedded script: ElementWrongTypeError -- device "Boiler Connect" class type indigo.thermostat is not compatible with request for type indigo.relay

It seems that Indigo thinks it is a thermostat rather than a relay switch which would explain why the 'onState' doesn't work AFAIK.

It's getting towards late PM here in the UK, so I will pick up on this again tomorrow :)

Posted on
Mon Sep 16, 2013 4:57 pm
jay (support) offline
Site Admin
User avatar
Posts: 18260
Joined: Mar 19, 2008
Location: Austin, Texas

Re: Compare Device States

Ah - it IS a thermostat. I assumed that when you said "boiler switch" you had a separate standard switch that controlled the boiler.

I think maybe Matt's going to have to chime in here since he implemented that thermostat.

Jay (Indigo Support)
Twitter | Facebook | LinkedIn

Posted on
Mon Sep 16, 2013 5:10 pm
jay (support) offline
Site Admin
User avatar
Posts: 18260
Joined: Mar 19, 2008
Location: Austin, Texas

Re: Compare Device States

Matt just explained this device to me. What an incredibly stupid implementation - if a device has two states which correspond to on/off, it's a RELAY device not a THERMOSTAT. Thermostat implies that it, you know, KNOWS temperature and will automatically turn itself on/off. Sorry, I haven't been paying close attention to this particular debacle.

Ok, so, for this particularly silly device, you'll want to switch it's mode from off to heat (or vice versa):

Code: Select all
if indigo.device[123].heatIsOn:
    # it's heat is on which I assume means the boiler is on
    indigo.device.setHvacMode(123, indigo.kHvacMode.Off)
else:
    # the device is off so turn on the heat which i assume means turn on the boiler
    indigo.device.setHvacMode(123, indigo.kHvacMode.Heat)


Details for controlling all of Indigo's built-in types can be found on the Devices IOM Documentation page.

Jay (Indigo Support)
Twitter | Facebook | LinkedIn

Posted on
Tue Sep 17, 2013 9:25 am
autolog offline
Posts: 3991
Joined: Sep 10, 2013
Location: West Sussex, UK [GMT aka UTC]

Re: Compare Device States

Thanks for all the tips and suggestions :-)

I now have a revised and better documented script (see below).
I am not sure it is the highest quality Python code but I have tested it and it works.:D


Code: Select all
# Script to turn a boiler on & off depending on demand from multiple radiator thermostats
#
# There is a "global" variable that counts the number of radiators calling for heat: RadiatorsCallingForHeat
# Each radiator has an Indigo variable associated with it: radiator_nn_CallingForHeat where nn is the number of the radiator
#
# This script handles the change of Heat Setpoint or Temperature for an invidual radiator thermostat. 
#
# In essence; if the number of radiators calling for heat is greater than zero, the boiler is turned 'on' else it is turned 'off'
#

# The following two variables should be altered depending on the radiator being handled by the script
radiator_id = 1861700997                       # 'Radiator 01 Thermostat'
radiatorCallingForHeat_id = 789502641          # 'radiator_01_CallingForHeat'

radiatorsCallingForHeatNumber_id = 1167250639  # 'radiatorsCallingForHeatNumber'
boiler_id = 862509423                          # 'Boiler Actuator'

radiator = indigo.devices[radiator_id]

radiatorCallingForHeat = indigo.variables[radiatorCallingForHeat_id].value  # "radiator_nn_CallingForHeat" = 'true' if heating required else 'false'
radiatorsCallingForHeatNumber = int(indigo.variables[1167250639].value)   # "radiatorsCallingForHeatNumber" = Number of radiators calling for heat

if radiator.temperatures[0] < radiator.heatSetpoint:
    if radiatorCallingForHeat == "false": # Only increment number of radiators calling for heat if this radiator not currently calling for heat
        indigo.variable.updateValue(radiatorCallingForHeat_id, "true")  # Indicate radiator calling for heat
        radiatorsCallingForHeatNumber += 1  # Increment number of radiators calling for heat
        indigo.variable.updateValue(radiatorsCallingForHeatNumber_id, str(radiatorsCallingForHeatNumber))
else:
    if radiatorCallingForHeat == "true": # Only decrement number of radiators calling for heat if this radiator currently calling for heat
        indigo.variable.updateValue(radiatorCallingForHeat_id, "false")  # Indicate radiator no longer calling for heat
        if radiatorsCallingForHeatNumber > 0:  # A check to prevent number going negative (just in case!)
            radiatorsCallingForHeatNumber -= 1  # Decrement number of radiators calling for heat
        indigo.variable.updateValue(radiatorsCallingForHeatNumber_id, str(radiatorsCallingForHeatNumber))

if radiatorsCallingForHeatNumber > 0:
    if str(indigo.devices[boiler_id].hvacMode) == "Off": # Only turn boiler 'on' if it is currently 'off'
        indigo.thermostat.setHvacMode(boiler_id, value=indigo.kHvacMode.Heat) # Turn boiler 'on'
else:
    if not str(indigo.devices[boiler_id].hvacMode) == "Off":  # Only turn boiler 'off' if it is currently 'on'
        indigo.thermostat.setHvacMode(boiler_id, value=indigo.kHvacMode.Off) # Turn boiler 'off'


While it works OK as it stands I would like to be able to do a few other things (if possible).

    1. It would be nice to generalise the script so that one script would handle all radiators. If it was possible to derive the id of the triggering device then that might do it as I could then work out (possibly) what the name of the associated variable was.

    2. I want to be able to set a 5 minute delay on starting the boiler as the radiator thermostat only wakes up every 5 minutes and I don't want the boiler on before the radiator is ready (I don't mind the radiator waiting for heat if it is on early). Looking in the device class documentation, it doesn't look like you can add a delay on a thermostat device? I can't use the global trigger delay as I want it to turn off immediately.

    3. Is it feasible to have this script run as a file so that I don't have to duplicate it in multiple triggers. As I see it at the moment, I have to setup two triggers for each of my 12 radiators (one for Heat Setpoint change and one for Temperature change. So I will end up with 24 triggers altogether. I looking to minimise the work, reduce the chance of error and KISS :wink:

Any thoughts or sugesstions would be much appreciated :)

Posted on
Tue Sep 17, 2013 10:13 am
jay (support) offline
Site Admin
User avatar
Posts: 18260
Joined: Mar 19, 2008
Location: Austin, Texas

Re: Compare Device States

autolog wrote:
Code: Select all
if radiatorsCallingForHeatNumber > 0:
    if str(indigo.devices[boiler_id].hvacMode) == "Off": # Only turn boiler 'on' if it is currently 'off'
        indigo.thermostat.setHvacMode(boiler_id, value=indigo.kHvacMode.Heat) # Turn boiler 'on'
else:
    if not str(indigo.devices[boiler_id].hvacMode) == "Off":  # Only turn boiler 'off' if it is currently 'on'
        indigo.thermostat.setHvacMode(boiler_id, value=indigo.kHvacMode.Off) # Turn boiler 'off'


No need to convert to strings, just compare directly to the constants defined in the API:

Code: Select all
if radiatorsCallingForHeatNumber > 0:
    if indigo.devices[boiler_id].hvacMode == indigo.kHvacMode.Off: # Only turn boiler 'on' if it is currently 'off'
        indigo.thermostat.setHvacMode(boiler_id, value=indigo.kHvacMode.Heat) # Turn boiler 'on'
else:
    if not indigo.devices[boiler_id].hvacMode == indigo.kHvacMode.Heat:  # Only turn boiler 'off' if it is currently 'on'
        indigo.thermostat.setHvacMode(boiler_id, value=indigo.kHvacMode.Off) # Turn boiler 'off'


Using the constants rather than the string representation is future proof: if for some reason we change the underlying value for any constant it won't matter to your script since you're using the constant directly rather than the value it represents. Of course, you don't really need the comparison at all - just turn it heat/off. If it's already on HEAT then setting it again is a noop.

autolog wrote:
1. It would be nice to generalise the script so that one script would handle all radiators. If it was possible to derive the id of the triggering device then that might do it as I could then work out (possibly) what the name of the associated variable was.


There is currently no information passed to the actions about what caused the action to fire. It's something we eventually want to add but it will require a rather large effort to add it so it won't happen in the short-term.

autolog wrote:
2. I want to be able to set a 5 minute delay on starting the boiler as the radiator thermostat only wakes up every 5 minutes and I don't want the boiler on before the radiator is ready (I don't mind the radiator waiting for heat if it is on early). Looking in the device class documentation, it doesn't look like you can add a delay on a thermostat device? I can't use the global trigger delay as I want it to turn off immediately.


As a specific implementation you could probably use a Timer device, though I'm not following exactly what you're describing.

autolog wrote:
3. Is it feasible to have this script run as a file so that I don't have to duplicate it in multiple triggers. As I see it at the moment, I have to setup two triggers for each of my 12 radiators (one for Heat Setpoint change and one for Temperature change. So I will end up with 24 triggers altogether. I looking to minimise the work, reduce the chance of error and KISS :wink:


Running the script as a file is simple. Having it replace all your triggers is a separate exercise which could be done. It would require that the script "poll" the status of each thermostat periodically, determining if they want the boiler on, then turning the boiler on/off depending on the result. You have the last part, you'd need to add the previous 2 parts. If you designed your python script as a stay-open script which just ran in an infinite loop (started by, say, a startup trigger), then each time through the loop it would do the temp vs setpoint check and turn the boiler on/off as needed.

It occurs to me, however, that building a plugin might be a better solution. Basically, what you'd do is build a plugin that allows you to map radiator thermostats to boilers. This could be done in the plugin's config dialog. So, you'd specify Thermostats 1-5 map to boiler A and Thermostats 6-10 map to boiler B (in your case, they all would map to boiler A). Then the plugin would subscribe to any change of Thermostats 1-10. Each time a thermostat change came in, you would determine which (if any) boiler needed to be active and would turn it on/off as necessary. So the plugin wouldn't actually create any new devices, events, or actions: all it would do is watch for thermostats to change and when the conditions were met it would kick on the boiler (or turn it off as necessary).

Interesting idea for a plugin - one that doesn't actually present any devices, events, or actions, but it still quite functional. I suppose you could create a device for each boiler and then add the thermostats to that device. Not sure if there's a reason to do that though since the boiler already has an Indigo device associated with it. But, in any event, an interesting idea.

Jay (Indigo Support)
Twitter | Facebook | LinkedIn

Posted on
Tue Sep 17, 2013 1:43 pm
autolog offline
Posts: 3991
Joined: Sep 10, 2013
Location: West Sussex, UK [GMT aka UTC]

Re: Compare Device States

jay (support) wrote:
... Using the constants rather than the string representation is future proof: if for some reason we change the underlying value for any constant it won't matter to your script since you're using the constant directly rather than the value it represents. Of course, you don't really need the comparison at all - just turn it heat/off. If it's already on HEAT then setting it again is a noop.

I have now modified the code - thanks :-)

jay (support) wrote:
autolog wrote:
1. It would be nice to generalise the script so that one script would handle all radiators. If it was possible to derive the id of the triggering device then that might do it as I could then work out (possibly) what the name of the associated variable was.
There is currently no information passed to the actions about what caused the action to fire. It's something we eventually want to add but it will require a rather large effort to add it so it won't happen in the short-term.

I have implemented a work around that means I can have just one script. I have set up another trigger action to run before my script invoking action which puts the two ids which vary by radiator into a variable (the two ids separated by a ';'), and so my script code now says:
Code: Select all
radiatorIDs = indigo.variables[1933954418].value            # radiatorIDs variable
radiator_id = int(radiatorIDs.split(';')[0])                # id of 'Radiator nn Thermostat' device
radiatorCallingForHeat_id = int(radiatorIDs.split(';')[1])  # id of 'radiator_nn_CallingForHeat' variable
The script is now radiator independant :)

jay (support) wrote:
autolog wrote:
2. I want to be able to set a 5 minute delay on starting the boiler as the radiator thermostat only wakes up every 5 minutes and I don't want the boiler on before the radiator is ready (I don't mind the radiator waiting for heat if it is on early). Looking in the device class documentation, it doesn't look like you can add a delay on a thermostat device? I can't use the global trigger delay as I want it to turn off immediately.


As a specific implementation you could probably use a Timer device, though I'm not following exactly what you're describing.
I have solved this by setting up a new trigger that monitors the radiatorsCallingForHeatNumber variable. If it changes then the trigger is kicked off which then runs a script after 5 minutes which checks whether the boiler should still be turned on (i.e. that the number of radiators calling for heat is still greater than zero) as it could have changed back to zero in the intervening 5 minutes.

jay (support) wrote:
autolog wrote:
3. Is it feasible to have this script run as a file so that I don't have to duplicate it in multiple triggers. As I see it at the moment, I have to setup two triggers for each of my 12 radiators (one for Heat Setpoint change and one for Temperature change. So I will end up with 24 triggers altogether. I looking to minimise the work, reduce the chance of error and KISS :wink:


Running the script as a file is simple.

I have now done this :-)

jay (support) wrote:
Having it replace all your triggers is a separate exercise which could be done. It would require that the script "poll" the status of each thermostat periodically, determining if they want the boiler on, then turning the boiler on/off depending on the result. You have the last part, you'd need to add the previous 2 parts. If you designed your python script as a stay-open script which just ran in an infinite loop (started by, say, a startup trigger), then each time through the loop it would do the temp vs setpoint check and turn the boiler on/off as needed.

It occurs to me, however, that building a plugin might be a better solution. Basically, what you'd do is build a plugin that allows you to map radiator thermostats to boilers. This could be done in the plugin's config dialog. So, you'd specify Thermostats 1-5 map to boiler A and Thermostats 6-10 map to boiler B (in your case, they all would map to boiler A). Then the plugin would subscribe to any change of Thermostats 1-10. Each time a thermostat change came in, you would determine which (if any) boiler needed to be active and would turn it on/off as necessary. So the plugin wouldn't actually create any new devices, events, or actions: all it would do is watch for thermostats to change and when the conditions were met it would kick on the boiler (or turn it off as necessary).

Interesting idea for a plugin - one that doesn't actually present any devices, events, or actions, but it still quite functional. I suppose you could create a device for each boiler and then add the thermostats to that device. Not sure if there's a reason to do that though since the boiler already has an Indigo device associated with it. But, in any event, an interesting idea.


I'll have a go at doing a plugin before too long but in the meanwhile I think I have managed now to get a working system, due in no small part to your help and assistance :wink:
Thank you so much for your help and support. Needless to say I am very impressed with the help that you an Matt have given me as a someone just starting out with Indigo and also with the help I have received from various forum members.
Thanks again :D

Page 1 of 1

Who is online

Users browsing this forum: No registered users and 1 guest