This widget/system uses my Time-Profiles, as presented in this post.
Design Goal:
I wanted to create a ruleset and widgets which allow Profile-based control of thermostats on a per-room basis that’s easily adjustable and responsive. Specifically, the system:
- Allows users to adjust the currently active Profile and change it at will
- Is responsive to other events, such as windows opening or a global vacation toggle
- Enables different profiles on weekends automatically
- Makes it simple to turn the automation in a specific room “off” or “on”
- Allows extra features like a time-limited “boost” and “override” to comfort
- Visualizes heating and schedule even for non-savvy users
The Widget
Demo Video
Main Widget
This widget is what the user sees in the MainUI when visiting the “Properties” tab of a location-card. Here the user can see the currently targeted temperature, and if they wish, enable the temporary “boost” or tweak the setting using the override slider. Additionally, the user can see on the weekly calendar what profiles will be active on which days of the week, and the temperature range that they travel is shown by the differently colored red highlighting on the graph. Should the user wish to change the profile, the settings button opens up the settings widget, as shown below.
Settings Widget
The Settings widget enables the user to fine-tune the control over the room’s thermostat control. Here, different Modes can be selected which determine the Profile choice of each weekday. “Static” mode will simply always use the primary Profile each day, while setting the zone to “M-F / S-S” allows a secondary Profile to be set that is used on weekends. Finally, the “Default” mode is currently unused as to leave space for future development and features should the need arise.
Source Code
Note that this system is deeply intertwined with the “Profiles” system, it is a prerequisite.
YAML Widgets
Main Widget
uid: temperature_control_v2
tags: []
props:
parameters:
- context: item
description: Heating zone to pattern
label: Item
name: item
required: false
type: TEXT
parameterGroups: []
timestamp: Dec 22, 2022, 4:27:12 PM
component: f7-list-item
config:
class: media-item
slots:
content:
- component: f7-block
config:
style:
background: "#232323"
border-bottom: "1px solid #3e3e3f"
border-top: "1px solid #3e3e3f"
margin-bottom: 10px
margin-left: -16px
margin-right: -16px
margin-top: -8px
slots:
default:
- component: f7-row
config:
style:
height: 2.5em
position: relative
slots:
default:
- component: f7-col
config:
style:
height: 2em
margin-top: 0.25em
padding: 6px
width: 80%
slots:
default:
- component: f7-row
slots:
default:
- component: f7-col
config:
style:
font-size: 11pt
text-align: Left
slots:
default:
- component: Label
config:
style:
float: left
font-weight: bold
padding-right: 10px
text: =props.item.split('_')[0] + ':'
- component: Label
config:
text: Heat Control
- component: f7-col
config:
style:
padding: 6px
text-align: right
vertical-align: center
width: 20%
slots:
default:
- component: oh-toggle
config:
color: red
item: =props.item
style:
height: 100%
- component: f7-block
config:
style:
margin-bottom: 10px
slots:
default:
- component: f7-row
config:
style:
margin-left: -16px
margin-right: -16px
slots:
default:
- component: f7-col
config:
style:
background: "#232323"
border-radius: 0.7em
height: 3.3em
slots:
default:
- component: Label
config:
style:
font-size: 8pt
text-align: center
text: Target
- component: Label
config:
style:
font-size: 16pt
text-align: center
text: =items[props.item.split('_')[0] + '_Heating_Target'].state + '°C'
- component: f7-col
config:
style:
background: "=( items[props.item.split('_')[0] + '_Heating_BoostMode'].state == 'ON' ? '#2e853d' : '#232323' )"
border-radius: 0.7em
height: 3.3em
slots:
default:
- component: f7-row
config:
style:
height: 1.2em
slots:
default:
- component: f7-col
slots:
default:
- component: Label
config:
style:
font-size: 8pt
text-align: center
text: Boost
- component: f7-row
slots:
default:
- component: f7-col
config:
style:
text-align: center
slots:
default:
- component: oh-toggle
config:
color: green
item: =props.item.split('_')[0] + '_Heating_BoostMode'
- component: f7-col
config:
style:
background: "#232323"
border-radius: 0.7em
height: 3.3em
text-align: center
vertical-align: center
width: 20%
slots:
default:
- component: oh-button
config:
action: popup
actionModal: widget:temperature_control_v1_popup
actionModalConfig:
root_item: =props.item
color: white
iconF7: gear_alt_fill
large: true
style:
border-radius: 0.7em
height: 3.3em
- component: f7-block
config:
style:
margin-left: -1em
margin-right: -1em
text-align: center
slots:
default:
- component: f7-row
config:
style:
position: relative
slots:
default:
- component: Label
config:
style:
background: "#232323"
font-size: 6pt
left: 0px
padding: 3px
position: absolute
top: 36%
text: 20°C
- component: Label
config:
style:
background: "#232323"
font-size: 6pt
left: 0px
padding: 3px
position: absolute
top: 59%
text: 15°C
- component: oh-repeater
config:
for: count
fragment: true
rangeStart: 1
rangeStep: 1
rangeStop: 7
sourceType: range
slots:
default:
- component: f7-col
config:
style:
--f7-grid-gap: 0.1em
background: "#232323"
border-radius: "=(loop.count == '1' ? '.7em 0em 0em .7em' : (loop.count == '7' ? '0em .7em .7em 0em' : '0em 0em 0em 0em'))"
margin-bottom: 1em
outline: "=( (dayjs().day() == 0 ? 7 : dayjs().day()) == loop.count ? '1px dashed white' : '')"
slots:
default:
- component: f7-row
slots:
default:
- component: f7-col
slots:
default:
- component: Label
config:
style:
background: "#3e3e3f"
border-radius: "=(loop.count == '1' ? '.7em 0em 0em 0em' : (loop.count == '7' ? '0em .7em 0em 0em' : '0em 0em 0em 0em'))"
font-weight: bold
text: "=dayjs().day(loop.count).format('ddd').toUpperCase() "
- component: f7-row
config:
style:
background: linear-gradient(0deg, rgba(35,35,35,1) 0%, rgba(35,35,35,1) 32%, rgba(255,255,255,1) 33%, rgba(35,35,35,1) 34%, rgba(35,35,35,1) 65%, rgba(255,255,255,1) 66%, rgba(35,35,35,1) 67%, rgba(35,35,35,1) 100%)
border-radius: "=(loop.count == '1' ? '0em 0em 0em .7em' : (loop.count == '7' ? '0em 0em .7em 0em' : '0em 0em 0em 0em'))"
slots:
default:
- component: f7-col
config:
style:
border-radius: "=(loop.count == '1' ? '0em 0em 0em .7em' : (loop.count == '7' ? '0em 0em .7em 0em' : '0em 0em 0em 0em'))"
height: 6em
position: relative
slots:
default:
- component: f7-block
config:
style:
background: "=( (items[props.item.split('_')[0] + '_Heating_Active'].state == 'ON' ? ( loop.count == (dayjs().day() == 0 ? 7 : dayjs().day()) ? items[props.item.split('_')[0] + '_Heating_AutomationProfileActive'].state : ( items[props.item.split('_')[0] + '_Heating_AutomationMode'].state == 'Auto' ? (loop.count < 6 ? items[props.item.split('_')[0] + '_Heating_AutomationProfilePrimary'].state : items[props.item.split('_')[0] + '_Heating_AutomationProfileAlternate'].state) : items[props.item.split('_')[0] + '_Heating_AutomationProfilePrimary'].state ) ) : 'Frostguard') == 'Frostguard' ? 'rgba(48, 48, 227, 0.9)' : 'rgba(227, 48, 48, 0.9)')"
border-radius: "=(loop.count == '1' ? '0em 0em 0em .7em' : (loop.count == '7' ? '0em 0em .7em 0em' : '0em 0em 0em 0em'))"
bottom: 0px
height: "=( items[props.item.split('_')[0] + '_Heating_Active'].state == 'ON' ? ( loop.count == (dayjs().day() == 0 ? 7 : dayjs().day()) ? ( items[items[props.item.split('_')[0] + '_Heating_AutomationProfileActiveId'].state + '_MinValue'].state ) : ( items[props.item.split('_')[0] + '_Heating_AutomationMode'].state == 'Auto' ? ( loop.count < 6 ? ( items[items[props.item.split('_')[0] + '_Heating_AutomationProfilePrimaryId'].state + '_MinValue'].state ) : ( items[items[props.item.split('_')[0] + '_Heating_AutomationProfileAlternateId'].state + '_MinValue'].state ) ) : ( items[items[props.item.split('_')[0] + '_Heating_AutomationProfilePrimaryId'].state + '_MinValue'].state ) ) ) : ( items[items[props.item.split('_')[0] + '_Heating_AutomationProfileActiveId'].state + '_MinValue'].state ) ) * 0.4 - 4 + 'em'"
position: absolute
width: 100%
- component: f7-block
config:
style:
background: rgba(255, 0, 0, 0.4)
bottom: "=( items[props.item.split('_')[0] + '_Heating_Active'].state == 'ON' ? ( loop.count == (dayjs().day() == 0 ? 7 : dayjs().day()) ? ( items[items[props.item.split('_')[0] + '_Heating_AutomationProfileActiveId'].state + '_MinValue'].state ) : ( items[props.item.split('_')[0] + '_Heating_AutomationMode'].state == 'Auto' ? ( loop.count < 6 ? ( items[items[props.item.split('_')[0] + '_Heating_AutomationProfilePrimaryId'].state + '_MinValue'].state ) : ( items[items[props.item.split('_')[0] + '_Heating_AutomationProfileAlternateId'].state + '_MinValue'].state ) ) : ( items[items[props.item.split('_')[0] + '_Heating_AutomationProfilePrimaryId'].state + '_MinValue'].state ) ) ) : ( items[items[props.item.split('_')[0] + '_Heating_AutomationProfileActiveId'].state + '_MinValue'].state ) ) * 0.4 - 4 + 'em'"
height: "=( ( ( items[props.item.split('_')[0] + '_Heating_Active'].state == 'ON' ? ( loop.count == (dayjs().day() == 0 ? 7 : dayjs().day()) ? ( items[items[props.item.split('_')[0] + '_Heating_AutomationProfileActiveId'].state + '_MaxValue'].state ) : ( items[props.item.split('_')[0] + '_Heating_AutomationMode'].state == 'Auto' ? ( loop.count < 6 ? ( items[items[props.item.split('_')[0] + '_Heating_AutomationProfilePrimaryId'].state + '_MaxValue'].state ) : ( items[items[props.item.split('_')[0] + '_Heating_AutomationProfileAlternateId'].state + '_MaxValue'].state ) ) : ( items[items[props.item.split('_')[0] + '_Heating_AutomationProfilePrimaryId'].state + '_MaxValue'].state ) ) ) : ( items[items[props.item.split('_')[0] + '_Heating_AutomationProfileActiveId'].state + '_MaxValue'].state ) ) * 0.4 - 4 ) - ( ( items[props.item.split('_')[0] + '_Heating_Active'].state == 'ON' ? ( loop.count == (dayjs().day() == 0 ? 7 : dayjs().day()) ? ( items[items[props.item.split('_')[0] + '_Heating_AutomationProfileActiveId'].state + '_MinValue'].state ) : ( items[props.item.split('_')[0] + '_Heating_AutomationMode'].state == 'Auto' ? ( loop.count < 6 ? ( items[items[props.item.split('_')[0] + '_Heating_AutomationProfilePrimaryId'].state + '_MinValue'].state ) : ( items[items[props.item.split('_')[0] + '_Heating_AutomationProfileAlternateId'].state + '_MinValue'].state ) ) : ( items[items[props.item.split('_')[0] + '_Heating_AutomationProfilePrimaryId'].state + '_MinValue'].state ) ) ) : ( items[items[props.item.split('_')[0] + '_Heating_AutomationProfileActiveId'].state + '_MinValue'].state ) ) * 0.4 - 4) ) + 'em'"
position: absolute
width: 100%
- component: Label
config:
style:
bottom: 0px
font-size: 8pt
left: 0px
position: absolute
right: 0px
z-index: 1
text: "=( (items[props.item.split('_')[0] + '_Heating_Active'].state == 'ON' ? ( loop.count == (dayjs().day() == 0 ? 7 : dayjs().day()) ? items[props.item.split('_')[0] + '_Heating_AutomationProfileActive'].state : ( items[props.item.split('_')[0] + '_Heating_AutomationMode'].state == 'Auto' ? (loop.count < 6 ? items[props.item.split('_')[0] + '_Heating_AutomationProfilePrimary'].state : items[props.item.split('_')[0] + '_Heating_AutomationProfileAlternate'].state) : items[props.item.split('_')[0] + '_Heating_AutomationProfilePrimary'].state ) ) : '❄️') == 'Frostguard' ? '️❄️' : (items[props.item.split('_')[0] + '_Heating_Active'].state == 'ON' ? ( loop.count == (dayjs().day() == 0 ? 7 : dayjs().day()) ? items[props.item.split('_')[0] + '_Heating_AutomationProfileActive'].state : ( items[props.item.split('_')[0] + '_Heating_AutomationMode'].state == 'Auto' ? (loop.count < 6 ? items[props.item.split('_')[0] + '_Heating_AutomationProfilePrimary'].state : items[props.item.split('_')[0] + '_Heating_AutomationProfileAlternate'].state) : items[props.item.split('_')[0] + '_Heating_AutomationProfilePrimary'].state ) ) : '❄️'))"
- component: f7-block
config:
style:
background: "#232323"
border-radius: 0.7em
height: 3.7em
text-align: center
slots:
default:
- component: Label
config:
style:
margin-bottom: -8px
width: 100%
text: ◄◄ Override ►►
- component: f7-col
config:
style:
margin: 6px
slots:
default:
- component: oh-slider
config:
item: =props.item.split('_')[0] + '_Heating_Offset'
label: true
max: 3
min: -3
releaseOnly: true
scale: true
scaleSteps: 6
step: 0.5
style:
--f7-range-bar-active-bg-color: "#919191"
--f7-range-bar-bg-color: "#919191"
unit: °C
Popup Widget
uid: temperature_control_v1_popup
tags: []
props:
parameters:
- context: item
description: The master item for the heating zone
label: Master_Heating_Switch item
name: root_item
required: false
type: TEXT
parameterGroups: []
timestamp: Dec 22, 2022, 4:32:40 PM
component: f7-card
config:
style:
text-align: center
text: "='Thermostat Control: ' + props.root_item.split('_')[0]"
slots:
content:
- component: f7-block
config:
style:
margin-left: -2em
margin-right: -2em
slots:
default:
- component: f7-row
config:
style:
margin-bottom: 20px
margin-top: 10px
text-align: center
width: 100%
slots:
default:
- component: f7-col
config:
style:
margin-left: 20px
text-align: right
slots:
default:
- component: Label
config:
text: "Zone Active:"
- component: f7-col
config:
style:
text-align: left
slots:
default:
- component: oh-toggle
config:
item: =props.root_item
- component: f7-col
config:
style:
text-align: right
slots:
default:
- component: Label
config:
text: "Boost:"
- component: f7-col
config:
style:
text-align: left
slots:
default:
- component: oh-toggle
config:
item: =props.root_item.split('_')[0] + '_Heating_BoostMode'
- component: f7-row
config:
style:
margin-top: 10px
slots:
default:
- component: f7-col
config:
style:
border-right: 2px solid white
text-align: center
width: 50%
slots:
default:
- component: f7-row
slots:
default:
- component: Label
config:
style:
width: 100%
text: Measured
- component: f7-row
config:
style:
text-align: center
slots:
default:
- component: Label
config:
style:
font-size: 18pt
font-weight: bold
width: 100%
text: =items[props.root_item.split('_')[0] + '_Temperature'].state + ' °C'
- component: f7-col
config:
style:
width: 50%
slots:
default:
- component: f7-row
slots:
default:
- component: Label
config:
style:
width: 100%
text: Target
- component: f7-row
slots:
default:
- component: Label
config:
style:
font-size: 18pt
font-weight: bold
width: 100%
text: =items[props.root_item.split('_')[0] + '_Heating_Target'].state + ' °C'
- component: f7-row
config:
style:
margin-top: 20px
text-align: center
width: 100%
slots:
default:
- component: Label
config:
style:
margin-bottom: -15px
width: 100%
text: ◄◄ Override ►►
- component: f7-col
config:
style:
margin: 10px
padding: 10px
slots:
default:
- component: oh-slider
config:
item: =props.root_item.split('_')[0] + '_Heating_Offset'
label: true
max: 3
min: -3
releaseOnly: true
scale: true
scaleSteps: 6
step: 0.5
style:
--f7-range-bar-active-bg-color: "#919191"
--f7-range-bar-bg-color: "#919191"
unit: °C
- component: f7-row
config:
style:
margin-top: 10px
text-align: center
width: 100%
slots:
default:
- component: f7-segmented
config:
round: true
style:
padding: 10px
width: 100%
slots:
default:
- component: oh-button
config:
action: command
actionCommand: Static
actionItem: =props.root_item.split('_')[0] + '_Heating_AutomationMode'
active: "=(items[props.root_item.split('_')[0] + '_Heating_AutomationMode'].state == 'Static') ? true : false"
outline: true
round: true
text: Static
- component: oh-button
config:
action: command
actionCommand: Auto
actionItem: =props.root_item.split('_')[0] + '_Heating_AutomationMode'
active: "=(items[props.root_item.split('_')[0] + '_Heating_AutomationMode'].state == 'Auto') ? true : false"
outline: true
round: true
text: M-F / S-S
- component: oh-button
config:
action: command
actionCommand: Smart
actionItem: =props.root_item.split('_')[0] + '_Heating_AutomationMode'
active: "=(items[props.root_item.split('_')[0] + '_Heating_AutomationMode'].state == 'Smart') ? true : false"
outline: true
round: true
text: Default
- component: f7-row
config:
style:
margin-top: 20px
text-align: center
width: 100%
slots:
default:
- component: f7-col
slots:
default:
- component: Label
config:
style:
text-align: right
text: "Active Profile:"
- component: f7-col
slots:
default:
- component: f7-row
slots:
default:
- component: Label
config:
style:
background-color: "=( items[props.root_item.split('_')[0] + '_Heating_AutomationProfileActive'].state == 'Away' ? '#919191' : ( items[props.root_item.split('_')[0] + '_Heating_AutomationProfileActive'].state == 'Frostguard' ? '#00ACB5' : ( items[props.root_item.split('_')[0] + '_Heating_AutomationProfileActive'].state == 'Present' ? '#008c09' : '#902022')))"
border-radius: 10px
font-weight: bold
padding: 0px 10px 0px 10px
text: =items[props.root_item.split('_')[0] + '_Heating_AutomationProfileActive'].state
- component: f7-row
config:
style:
margin-bottom: 10px
margin-top: 10px
slots:
default:
- component: f7-col
config:
style:
text-align: center
width: 50%
slots:
default:
- component: f7-row
slots:
default:
- component: Label
config:
style:
width: 100%
text: Workday
- component: f7-row
config:
style:
justify-content: center
margin-top: 10px
padding: 0px 20px 0px 20px
text-align: center
slots:
default:
- component: oh-button
config:
action: popup
actionModal: widget:temperature_control_v1_profilepopup
actionModalConfig:
item: =props.root_item.split('_')[0] + '_Heating_AutomationProfilePrimary'
fill: true
round: true
style:
background-color: "=( items[props.root_item.split('_')[0] + '_Heating_AutomationProfilePrimary'].state == 'Away' ? '#919191' : ( items[props.root_item.split('_')[0] + '_Heating_AutomationProfilePrimary'].state == 'Frostguard' ? '#00ACB5' : '#902022'))"
font-weight: bold
text: =items[props.root_item.split('_')[0] + '_Heating_AutomationProfilePrimary'].state
- component: f7-col
config:
style:
width: 50%
slots:
default:
- component: f7-row
slots:
default:
- component: Label
config:
style:
width: 100%
text: Weekend
- component: f7-row
config:
style:
justify-content: center
margin-top: 10px
padding: 0px 20px 0px 20px
text-align: center
slots:
default:
- component: oh-button
config:
action: popup
actionModal: widget:temperature_control_v1_profilepopup
actionModalConfig:
item: =props.root_item.split('_')[0] + '_Heating_AutomationProfileAlternate'
fill: true
round: true
style:
background-color: "=( items[props.root_item.split('_')[0] + '_Heating_AutomationProfileAlternate'].state == 'Away' ? '#919191' : ( items[props.root_item.split('_')[0] + '_Heating_AutomationProfileAlternate'].state == 'Frostguard' ? '#00ACB5' : '#902022'))"
font-weight: bold
text: =items[props.root_item.split('_')[0] + '_Heating_AutomationProfileAlternate'].state
- component: f7-row
config:
style:
margin-bottom: 10px
margin-top: 10px
slots:
default:
- component: f7-col
slots:
default:
- component: widget:profile_graph
config:
profilecore: =items[props.root_item.split('_')[0] + '_Heating_AutomationProfileActiveId'].state
Profile Selector
uid: temperature_control_v1_profilepopup
tags: []
props:
parameters:
- context: item
description: Profile Item to be adjusted
label: Item
name: item
required: false
type: TEXT
parameterGroups: []
timestamp: Dec 22, 2022, 4:26:57 PM
component: f7-card
config:
title: "Select a profile:"
slots:
default:
- component: f7-list
slots:
default:
- component: oh-repeater
config:
filter: items[loop.item.name + '_Type'].state == 'Temperature'
for: item
fragment: true
groupItem: ProfileList
sourceType: itemsInGroup
slots:
default:
- component: f7-list-item
slots:
default:
- component: oh-button
config:
action: command
actionCommand: =items[loop.item.name + '_Name'].state
actionItem: =props.item
fill: "=(items[props.item].state == items[loop.item.name + '_Name'].state ? true : false)"
style:
width: 100%
text: =items[loop.item.name + '_Name'].state
Virtual Items
.items setup for zones
//Heating Control Items (Virtual)
//Global Groups
Switch Global_Vacation_Override "Set all temperature zones to frostguard"
Group Sensor "Sensors"
Group Temperature "Temperature" (Sensor)
Group Humidity "Humidity" (Sensor)
//Heat Specific Groups
Group Heating_Input
Switch Heat_Debug
Group Master_Heating_Switch
//Heating items for Guest Bedroom
Group:Number:AVG GuestBedroom_Temperature "Average Temperature" <temperature> (GuestBedroom,Sensor,Temperature)
Group:Number:AVG GuestBedroom_Humidity "Average Humidity" <humidity> (GuestBedroom,Sensor,Humidity)
Switch GuestBedroom_Heating_Active "Heating Active" (GuestBedroom,Heating_Input,Master_Heating_Switch) ["Temperature"] {listWidget="widget:temperature_control_v2"[item="GuestBedroom_Heating_Active"]}
Number GuestBedroom_Heating_Offset "Heating Offset" (GuestBedroom,Heating_Input)
Number GuestBedroom_Heating_Target "Heating Target" (GuestBedroom,Heating_Input)
String GuestBedroom_Heating_AutomationMode "Heating Automation Mode" (GuestBedroom,Heating_Input)
String GuestBedroom_Heating_AutomationProfilePrimary "Heating Automation Primary Profile" (GuestBedroom,Heating_Input)
String GuestBedroom_Heating_AutomationProfileAlternate "Heating Automation Alternate Profile" (GuestBedroom,Heating_Input)
String GuestBedroom_Heating_AutomationProfileActive "Heating Automation Active Profile" (GuestBedroom,Heating_Input)
String GuestBedroom_Heating_AutomationProfileActiveId "Heating Automation Active Profile ID" (GuestBedroom,Heating_Input)
String GuestBedroom_Heating_AutomationProfilePrimaryId "Heating Automation Primary Profile ID" (GuestBedroom,Heating_Input)
String GuestBedroom_Heating_AutomationProfileAlternateId "Heating Automation Alternate Profile ID" (GuestBedroom,Heating_Input)
Switch GuestBedroom_Heating_BoostMode "Heating Boost Mode" (GuestBedroom,Heating_Input)
Switch GuestBedroom_Heating_WindowSuspension "Heating Window Suspension" (GuestBedroom,Heating_Input)
Rules Code
heatAutomate.rules
import org.openhab.core.model.script.ScriptServiceUtil
//Heat automation rules
//ScriptServiceUtil.getItemRegistry.getItem( ) as GenericItem
//Thermo update rules:
val rfDelay = 100
//This rule runs once every ten minutes and updates all the thermostats to their appropriate targets
rule "Heat Automation - Interval Update GLOBAL"
when
Item Heat_Debug received command ON or
Time cron "0 0/10 * 1/1 * ? *"
then
logInfo("Heat Automation","Running global Heat-Automation update")
if(SystemReady.state != ON){
logError("System Startup Catch", "Rule execution blocked -- Persistent variables not yet loaded!")
}else{
//Wait a second or so for other things to happen
Thread::sleep(2000)
//For debugging purposes, set this to true
val debugmode = false
Master_Heating_Switch.members.forEach[ root |
var root_room = "" + root.name.split('_').get(0)
//First, check for Null items and fix them if necessary
if(root.state == NULL){
logWarn("Null-Catcher", "Caught a NULL heating zone in " + root_room + "!")
ScriptServiceUtil.getItemRegistry.getItem(root_room + "_Heating_Active").postUpdate(OFF)
ScriptServiceUtil.getItemRegistry.getItem(root_room + "_Heating_Offset").postUpdate(0.0)
ScriptServiceUtil.getItemRegistry.getItem(root_room + "_Heating_AutomationMode").postUpdate("Static")
ScriptServiceUtil.getItemRegistry.getItem(root_room + "_Heating_AutomationProfilePrimary").postUpdate("Away")
ScriptServiceUtil.getItemRegistry.getItem(root_room + "_Heating_AutomationProfileAlternate").postUpdate("Away")
ScriptServiceUtil.getItemRegistry.getItem(root_room + "_Heating_WindowSuspension").postUpdate(OFF)
//Note that the target and the active profile are not set, even in null cases. This is because they will later be calculated
logWarn("Null-Catcher", "Catch Completed")
}
// ====== STEP 1 ======
// Determine the active profile and error-check it
if(debugmode){logInfo("Heat Automation", "Beginning Step 1 for " + root_room)}
val AutomationModeItem = ScriptServiceUtil.getItemRegistry.getItem(root_room + "_Heating_AutomationMode") as GenericItem
var AutomationMode = AutomationModeItem.state.toString
val ActiveProfileItem = ScriptServiceUtil.getItemRegistry.getItem(root_room + "_Heating_AutomationProfileActive") as GenericItem
//Quickly error-check the mode
if(AutomationModeItem.state == NULL || !(AutomationMode == "Static" || AutomationMode == "Auto" || AutomationMode == "Smart")){
logWarn("Heating Automation", "Mode error in " + AutomationModeItem.name + "! Setting it to Static from " + AutomationModeItem.state)
AutomationModeItem.postUpdate("Static")
AutomationMode = "Static"
}
var ActiveProfile = ""
//Check to see if the window suspension is on. If it is, set the profile to Frostguard
val WindowSuspensionItem = ScriptServiceUtil.getItemRegistry.getItem(root_room + "_Heating_WindowSuspension")
var BoostMode = ScriptServiceUtil.getItemRegistry.getItem(root_room + "_Heating_BoostMode").state
//Catch NULL
if(BoostMode == NULL){
BoostMode = OFF
ScriptServiceUtil.getItemRegistry.getItem(root_room + "_Heating_BoostMode").postUpdate(OFF)
}
val HeatActiveSwitch = ScriptServiceUtil.getItemRegistry.getItem(root_room + "_Heating_Active") as GenericItem
if(HeatActiveSwitch.state == NULL){
HeatActiveSwitch.postUpdate(OFF)
ActiveProfile = "Frostguard"
ActiveProfileItem.postUpdate(ActiveProfile)
}
if(HeatActiveSwitch.state == ON){
if(WindowSuspensionItem.state == NULL){
WindowSuspensionItem.postUpdate(OFF)
}else if(WindowSuspensionItem.state == ON){
//Window Suspension mode is on, override to Frostguard
ActiveProfileItem.postUpdate("Frostguard")
ActiveProfile = "Frostguard"
}else{
//Check to see if boost mode is active
if(BoostMode == ON){
ActiveProfileItem.postUpdate("Present")
ActiveProfile = "Present"
}else{
//window Suspension is not active
if(AutomationMode == "Static"){
//Automation mode is Static, pass through the primary profile
ActiveProfile = ScriptServiceUtil.getItemRegistry.getItem(root_room + "_Heating_AutomationProfilePrimary").state.toString
ActiveProfileItem.postUpdate(ActiveProfile)
}else if(AutomationMode == "Auto"){
//Determine if today is a workday or weekend day
if(now.getDayOfWeek.getValue >= 6){
//It's the weekend, move the Alternate profile into the Active one
ActiveProfile = ScriptServiceUtil.getItemRegistry.getItem(root_room + "_Heating_AutomationProfileAlternate").state.toString
ActiveProfileItem.postUpdate(ActiveProfile)
}else{
//It's a workday, move the Primary profile into the Active one
ActiveProfile = ScriptServiceUtil.getItemRegistry.getItem(root_room + "_Heating_AutomationProfilePrimary").state.toString
ActiveProfileItem.postUpdate(ActiveProfile)
}
}else if(AutomationMode == "Smart"){
//The mode is set to smart
// TODO: Implement features, right now just immitates Static mode
ActiveProfile = ScriptServiceUtil.getItemRegistry.getItem(root_room + "_Heating_AutomationProfilePrimary").state.toString
ActiveProfileItem.postUpdate(ActiveProfile)
}
}
}
}else{
ActiveProfile = "Frostguard"
ActiveProfileItem.postUpdate(ActiveProfile)
}
//Check to see if the target profile is a real one
var foundProfileInList = false
var ProfileItemName = ""
var CalculationFetched = new DecimalType(0.0)
//if(ActiveProfile == "Away" || ActiveProfile == "Frostguard" || ActiveProfile == "Present"){
// //We're good, no need to do anything.
// ProfileItemName = "None"
// foundProfileInList = true
//}else{
//Search the list of profiles for one with a matching name and type
ProfileList.members.forEach[ GroupItem d |
//logInfo("Debug","Checking... " + d.name)
var ProfileName = ScriptServiceUtil.getItemRegistry.getItem("" + d.name + "_Name" ).state.toString
var ProfileType = ScriptServiceUtil.getItemRegistry.getItem("" + d.name + "_Type" ).state.toString
if(ProfileType == "Temperature" && ProfileName.equals(ActiveProfile)){
foundProfileInList = true
ProfileItemName = d.name
CalculationFetched = ScriptServiceUtil.getItemRegistry.getItem("" + d.name + "_Calculated" ).state as DecimalType
}
]
//}
//Do Global vacation check
if(Global_Vacation_Override.state == NULL){
Global_Vacation_Override.postUpdate(OFF)
}
if(Global_Vacation_Override.state == ON){
//On vacation, turn everything to frostguard
ActiveProfile = "Frostguard"
ActiveProfileItem.postUpdate(ActiveProfile)
}
//Do Local Heating Active Check
if(HeatActiveSwitch.state == OFF){
ActiveProfile = "Frostguard"
ActiveProfileItem.postUpdate(ActiveProfile)
}
//Error found, handle it
if(!foundProfileInList){
logWarn("Heat Automation", "Profile \"" + ActiveProfile + "\" not found in list of profiles. Using \"Frostguard\" Profile as stand in")
ActiveProfile = "Frostguard"
ProfileItemName = "None"
ActiveProfileItem.postUpdate(ActiveProfile)
}
//Compute the ID items
/*
String TilmanRoom_Heating_AutomationProfileActiveId "Heating Automation Active Profile ID" (TilmanRoom,Heating_Input)
String TilmanRoom_Heating_AutomationProfilePrimaryId "Heating Automation Primary Profile ID" (TilmanRoom,Heating_Input)
String TilmanRoom_Heating_AutomationProfileAlternateId "Heating Automation Alternate Profile ID" (TilmanRoom,Heating_Input)
*/
if(ActiveProfile == "Frostguard" || ActiveProfile == "Away" || ActiveProfile == "Present"){
//The profile is one of the standard ones. Simply pass-through the value
ScriptServiceUtil.getItemRegistry.getItem(root_room + "_Heating_AutomationProfileActiveId").postUpdate("" + ActiveProfile)
}else{
//Scan through the list and find the profile
var detectedprofile = false
ProfileList.members.forEach[ GroupItem d |
var ProfileName = d.members.findFirst[ p | p.name == "" + d.name + "_Name" ].state.toString
var ProfileType = d.members.findFirst[ p | p.name == "" + d.name + "_Type" ].state.toString
if(ProfileType == "Temperature" && ProfileName == ActiveProfile){
ScriptServiceUtil.getItemRegistry.getItem(root_room + "_Heating_AutomationProfileActiveId").postUpdate("" + d.name)
detectedprofile = true
}
]
//Error Handling
if(!detectedprofile){
logWarn("Heat Automation","Profile with the name of " + ActiveProfile + " does not exist in " + root_room )
}
}
//Repeat for the pirmary profile
var PrimaryProfile = "" + ScriptServiceUtil.getItemRegistry.getItem(root_room + "_Heating_AutomationProfilePrimary").state.toString
if(PrimaryProfile == "Frostguard" || PrimaryProfile == "Away" || PrimaryProfile == "Present"){
//The profile is one of the standard ones. Simply pass-through the value
ScriptServiceUtil.getItemRegistry.getItem(root_room + "_Heating_AutomationProfilePrimaryId").postUpdate("" + PrimaryProfile)
}else{
//Scan through the list and find the profile
var detectedprofile = false
ProfileList.members.forEach[ GroupItem d |
var ProfileName = d.members.findFirst[ p | p.name == "" + d.name + "_Name" ].state.toString
var ProfileType = d.members.findFirst[ p | p.name == "" + d.name + "_Type" ].state.toString
if(ProfileType == "Temperature" && ProfileName == PrimaryProfile){
ScriptServiceUtil.getItemRegistry.getItem(root_room + "_Heating_AutomationProfilePrimaryId").postUpdate("" + d.name)
detectedprofile = true
}
]
//Error Handling
if(!detectedprofile){
logWarn("Heat Automation","Profile with the name of " + PrimaryProfile + " does not exist in " + root_room )
}
}
//Repeat for the Alternate profile
var AlternateProfile = "" + ScriptServiceUtil.getItemRegistry.getItem(root_room + "_Heating_AutomationProfileAlternate").state.toString
if(AlternateProfile == "Frostguard" || AlternateProfile == "Away" || AlternateProfile == "Present"){
//The profile is one of the standard ones. Simply pass-through the value
ScriptServiceUtil.getItemRegistry.getItem(root_room + "_Heating_AutomationProfileAlternateId").postUpdate("" + AlternateProfile)
}else{
//Scan through the list and find the profile
var detectedprofile = false
ProfileList.members.forEach[ GroupItem d |
var ProfileName = d.members.findFirst[ p | p.name == "" + d.name + "_Name" ].state.toString
var ProfileType = d.members.findFirst[ p | p.name == "" + d.name + "_Type" ].state.toString
if(ProfileType == "Temperature" && ProfileName == AlternateProfile){
ScriptServiceUtil.getItemRegistry.getItem(root_room + "_Heating_AutomationProfileAlternateId").postUpdate("" + d.name)
detectedprofile = true
}
]
//Error Handling
if(!detectedprofile){
logWarn("Heat Automation","Profile with the name of " + AlternateProfile + " does not exist in " + root_room )
}
}
if(debugmode){logInfo("Heat Automation","Mode is set to \"" + AutomationMode + "\" and the active profile is \"" + ActiveProfile + "\"")}
// ====== STEP 2 ======
// Fetch the current target temp from the profile and apply the offset
if(debugmode){logInfo("Heat Automation", "Beginning Step 2, profile name is " + ProfileItemName + " with a fetch-value of " + CalculationFetched)}
var TargetTemperature = 0.0
val HeatingOffsetItem = ScriptServiceUtil.getItemRegistry.getItem(root_room + "_Heating_Offset")
if(HeatingOffsetItem.state == NULL){
HeatingOffsetItem.postUpdate(0.0)
}
var HeatingOffset = HeatingOffsetItem.state as DecimalType
if(ProfileItemName != "None"){
if(debugmode){logInfo("Heat Automation","Reached profile_calc fetch")}
TargetTemperature = CalculationFetched + HeatingOffset
}else{
if(ActiveProfile == "Frostguard"){
TargetTemperature = 12.0 + HeatingOffset
}
if(ActiveProfile == "Away"){
TargetTemperature = 15.0 + HeatingOffset
}
if(ActiveProfile == "Present"){
TargetTemperature = 21.0 + HeatingOffset
}
}
//Actually send the target temperature to the item in question
ScriptServiceUtil.getItemRegistry.getItem(root_room + "_Heating_Target").postUpdate(TargetTemperature)
if(debugmode){logInfo("Heat Automation","Calculated a value of " + TargetTemperature + " for this zone with ProfileItemName = " + ProfileItemName + ".")}
if(debugmode){logInfo("Heat Automation","Room Complete!")}
//logOutput
if(root_room.length() < 8){
if(ActiveProfile.length() < 7){
logInfo("Heat Automation","Computed " + root_room + " \t\t Active Profile: " + ActiveProfile + " \t\t Target " + TargetTemperature + " °C")
}else{
logInfo("Heat Automation","Computed " + root_room + " \t\t Active Profile: " + ActiveProfile + " \t Target " + TargetTemperature + " °C")
}
}else{
if(ActiveProfile.length() < 7){
logInfo("Heat Automation","Computed " + root_room + " \t Active Profile: " + ActiveProfile + " \t\t Target " + TargetTemperature + " °C")
}else{
logInfo("Heat Automation","Computed " + root_room + " \t Active Profile: " + ActiveProfile + " \t Target " + TargetTemperature + " °C")
}
}
// ====== STEP 3 ======
// Send that value to all the radiators in that room
Smart_Thermostat.members.forEach[ i |
if(i.getGroupNames.contains(root_room) && i.getName.contains("Target_Temperature")){
//Change the thermostat only if it's set differently than the goal temp
if(i.state as DecimalType != TargetTemperature){
//item format is something like HKTTilman1_Target_Temperature
var thermoItem = Temperature_Targetable.members.findFirst[ i2 | i2.name == i.getName.split("_").get(0) + "_Control_Mode_Manu"]
//send the TargetTemperature to the individual thermostat
thermoItem.sendCommand(TargetTemperature as Number)
logInfo("Thermo Interval", "Thermostat \"" + thermoItem.name + "\" retargeted to " + TargetTemperature + ".")
Thread::sleep(rfDelay)
}
}
]
]
}
end
//This rule runs a localized heating zone update when
rule "Heat Automation - Localized Update"
when
Member of Heating_Input received command
then
val root_room = triggeringItem.name.split('_').get(0)
var root = Master_Heating_Switch.members.findFirst[ i | i.name == "" + triggeringItem.name.split('_').get(0) + "_Heating_Active" ]
logInfo("Heat Automation","Running local Heat-Automation update in " + root_room)
val debugmode = false
//First, check for Null items and fix them if necessary
if(root.state == NULL){
logWarn("Null-Catcher", "Caught a NULL heating zone in " + root_room + "!")
ScriptServiceUtil.getItemRegistry.getItem(root_room + "_Heating_Active").postUpdate(OFF)
ScriptServiceUtil.getItemRegistry.getItem(root_room + "_Heating_Offset").postUpdate(0.0)
ScriptServiceUtil.getItemRegistry.getItem(root_room + "_Heating_AutomationMode").postUpdate("Static")
ScriptServiceUtil.getItemRegistry.getItem(root_room + "_Heating_AutomationProfilePrimary").postUpdate("Away")
ScriptServiceUtil.getItemRegistry.getItem(root_room + "_Heating_AutomationProfileAlternate").postUpdate("Away")
ScriptServiceUtil.getItemRegistry.getItem(root_room + "_Heating_WindowSuspension").postUpdate(OFF)
//Note that the target and the active profile are not set, even in null cases. This is because they will later be calculated
logWarn("Null-Catcher", "Catch Completed")
}
// ====== STEP 1 ======
// Determine the active profile and error-check it
if(debugmode){logInfo("Heat Automation", "Beginning Step 1 for " + root_room)}
val AutomationModeItem = ScriptServiceUtil.getItemRegistry.getItem(root_room + "_Heating_AutomationMode") as GenericItem
var AutomationMode = AutomationModeItem.state.toString
val ActiveProfileItem = ScriptServiceUtil.getItemRegistry.getItem(root_room + "_Heating_AutomationProfileActive") as GenericItem
//Quickly error-check the mode
if(AutomationModeItem.state == NULL || !(AutomationMode == "Static" || AutomationMode == "Auto" || AutomationMode == "Smart")){
logWarn("Heating Automation", "Mode error in " + AutomationModeItem.name + "! Setting it to Static from " + AutomationModeItem.state)
AutomationModeItem.postUpdate("Static")
AutomationMode = "Static"
}
var ActiveProfile = ""
//Check to see if the window suspension is on. If it is, set the profile to Frostguard
val WindowSuspensionItem = ScriptServiceUtil.getItemRegistry.getItem(root_room + "_Heating_WindowSuspension")
var BoostMode = ScriptServiceUtil.getItemRegistry.getItem(root_room + "_Heating_BoostMode").state
//Catch NULL
if(BoostMode == NULL){
BoostMode = OFF
ScriptServiceUtil.getItemRegistry.getItem(root_room + "_Heating_BoostMode").postUpdate(OFF)
}
val HeatActiveSwitch = ScriptServiceUtil.getItemRegistry.getItem(root_room + "_Heating_Active") as GenericItem
if(HeatActiveSwitch.state == NULL){
HeatActiveSwitch.postUpdate(OFF)
ActiveProfile = "Frostguard"
ActiveProfileItem.postUpdate(ActiveProfile)
}
if(HeatActiveSwitch.state == ON){
if(WindowSuspensionItem.state == NULL){
WindowSuspensionItem.postUpdate(OFF)
}else if(WindowSuspensionItem.state == ON){
//Window Suspension mode is on, override to Frostguard
ActiveProfileItem.postUpdate("Frostguard")
ActiveProfile = "Frostguard"
}else{
//Check to see if boost mode is active
if(BoostMode == ON){
ActiveProfileItem.postUpdate("Present")
ActiveProfile = "Present"
}else{
//window Suspension is not active
if(AutomationMode == "Static"){
//Automation mode is Static, pass through the primary profile
ActiveProfile = ScriptServiceUtil.getItemRegistry.getItem(root_room + "_Heating_AutomationProfilePrimary").state.toString
ActiveProfileItem.postUpdate(ActiveProfile)
}else if(AutomationMode == "Auto"){
//Determine if today is a workday or weekend day
if(now.getDayOfWeek.getValue >= 6){
//It's the weekend, move the Alternate profile into the Active one
ActiveProfile = ScriptServiceUtil.getItemRegistry.getItem(root_room + "_Heating_AutomationProfileAlternate").state.toString
ActiveProfileItem.postUpdate(ActiveProfile)
}else{
//It's a workday, move the Primary profile into the Active one
ActiveProfile = ScriptServiceUtil.getItemRegistry.getItem(root_room + "_Heating_AutomationProfilePrimary").state.toString
ActiveProfileItem.postUpdate(ActiveProfile)
}
}else if(AutomationMode == "Smart"){
//The mode is set to smart
// TODO: Implement features, right now just immitates Static mode
ActiveProfile = ScriptServiceUtil.getItemRegistry.getItem(root_room + "_Heating_AutomationProfilePrimary").state.toString
ActiveProfileItem.postUpdate(ActiveProfile)
}
}
}
}else{
ActiveProfile = "Frostguard"
ActiveProfileItem.postUpdate(ActiveProfile)
}
//Check to see if the target profile is a real one
var foundProfileInList = false
var ProfileItemName = ""
var CalculationFetched = new DecimalType(0.0)
//if(ActiveProfile == "Away" || ActiveProfile == "Frostguard" || ActiveProfile == "Present"){
// //We're good, no need to do anything.
// ProfileItemName = "None"
// foundProfileInList = true
//}else{
//Search the list of profiles for one with a matching name and type
ProfileList.members.forEach[ GroupItem d |
//logInfo("Debug","Checking... " + d.name)
var ProfileName = ScriptServiceUtil.getItemRegistry.getItem("" + d.name + "_Name" ).state.toString
var ProfileType = ScriptServiceUtil.getItemRegistry.getItem("" + d.name + "_Type" ).state.toString
if(ProfileType == "Temperature" && ProfileName.equals(ActiveProfile)){
foundProfileInList = true
ProfileItemName = d.name
CalculationFetched = ScriptServiceUtil.getItemRegistry.getItem("" + d.name + "_Calculated" ).state as DecimalType
}
]
//}
//Do Global vacation check
if(Global_Vacation_Override.state == NULL){
Global_Vacation_Override.postUpdate(OFF)
}
if(Global_Vacation_Override.state == ON){
//On vacation, turn everything to frostguard
ActiveProfile = "Frostguard"
ActiveProfileItem.postUpdate(ActiveProfile)
}
//Do Local Heating Active Check
if(HeatActiveSwitch.state == OFF){
ActiveProfile = "Frostguard"
ActiveProfileItem.postUpdate(ActiveProfile)
}
//Error found, handle it
if(!foundProfileInList){
logWarn("Heat Automation", "Profile \"" + ActiveProfile + "\" not found in list of profiles. Using \"Frostguard\" Profile as stand in")
ActiveProfile = "Frostguard"
ProfileItemName = "None"
ActiveProfileItem.postUpdate(ActiveProfile)
}
if(ActiveProfile == "Frostguard" || ActiveProfile == "Away" || ActiveProfile == "Present"){
//The profile is one of the standard ones. Simply pass-through the value
ScriptServiceUtil.getItemRegistry.getItem(root_room + "_Heating_AutomationProfileActiveId").postUpdate("" + ActiveProfile)
}else{
//Scan through the list and find the profile
var detectedprofile = false
ProfileList.members.forEach[ GroupItem d |
var ProfileName = d.members.findFirst[ p | p.name == "" + d.name + "_Name" ].state.toString
var ProfileType = d.members.findFirst[ p | p.name == "" + d.name + "_Type" ].state.toString
if(ProfileType == "Temperature" && ProfileName == ActiveProfile){
ScriptServiceUtil.getItemRegistry.getItem(root_room + "_Heating_AutomationProfileActiveId").postUpdate("" + d.name)
detectedprofile = true
}
]
//Error Handling
if(!detectedprofile){
logWarn("Heat Automation","Profile with the name of " + ActiveProfile + " does not exist in " + root_room )
}
}
//Repeat for the pirmary profile
var PrimaryProfile = "" + ScriptServiceUtil.getItemRegistry.getItem(root_room + "_Heating_AutomationProfilePrimary").state.toString
if(PrimaryProfile == "Frostguard" || PrimaryProfile == "Away" || PrimaryProfile == "Present"){
//The profile is one of the standard ones. Simply pass-through the value
ScriptServiceUtil.getItemRegistry.getItem(root_room + "_Heating_AutomationProfilePrimaryId").postUpdate("" + PrimaryProfile)
}else{
//Scan through the list and find the profile
var detectedprofile = false
ProfileList.members.forEach[ GroupItem d |
var ProfileName = d.members.findFirst[ p | p.name == "" + d.name + "_Name" ].state.toString
var ProfileType = d.members.findFirst[ p | p.name == "" + d.name + "_Type" ].state.toString
if(ProfileType == "Temperature" && ProfileName == PrimaryProfile){
ScriptServiceUtil.getItemRegistry.getItem(root_room + "_Heating_AutomationProfilePrimaryId").postUpdate("" + d.name)
detectedprofile = true
}
]
//Error Handling
if(!detectedprofile){
logWarn("Heat Automation","Profile with the name of " + PrimaryProfile + " does not exist in " + root_room )
}
}
//Repeat for the Alternate profile
var AlternateProfile = "" + ScriptServiceUtil.getItemRegistry.getItem(root_room + "_Heating_AutomationProfileAlternate").state.toString
if(AlternateProfile == "Frostguard" || AlternateProfile == "Away" || AlternateProfile == "Present"){
//The profile is one of the standard ones. Simply pass-through the value
ScriptServiceUtil.getItemRegistry.getItem(root_room + "_Heating_AutomationProfileAlternateId").postUpdate("" + AlternateProfile)
}else{
//Scan through the list and find the profile
var detectedprofile = false
ProfileList.members.forEach[ GroupItem d |
var ProfileName = d.members.findFirst[ p | p.name == "" + d.name + "_Name" ].state.toString
var ProfileType = d.members.findFirst[ p | p.name == "" + d.name + "_Type" ].state.toString
if(ProfileType == "Temperature" && ProfileName == AlternateProfile){
ScriptServiceUtil.getItemRegistry.getItem(root_room + "_Heating_AutomationProfileAlternateId").postUpdate("" + d.name)
detectedprofile = true
}
]
//Error Handling
if(!detectedprofile){
logWarn("Heat Automation","Profile with the name of " + AlternateProfile + " does not exist in " + root_room )
}
}
if(debugmode){logInfo("Heat Automation","Mode is set to \"" + AutomationMode + "\" and the active profile is \"" + ActiveProfile + "\"")}
// ====== STEP 2 ======
// Fetch the current target temp from the profile and apply the offset
if(debugmode){logInfo("Heat Automation", "Beginning Step 2, profile name is " + ProfileItemName + " with a fetch-value of " + CalculationFetched)}
var TargetTemperature = 0.0
val HeatingOffsetItem = ScriptServiceUtil.getItemRegistry.getItem(root_room + "_Heating_Offset")
if(HeatingOffsetItem.state == NULL){
HeatingOffsetItem.postUpdate(0.0)
}
var HeatingOffset = HeatingOffsetItem.state as DecimalType
if(ProfileItemName != "None"){
if(debugmode){logInfo("Heat Automation","Reached profile_calc fetch")}
TargetTemperature = CalculationFetched + HeatingOffset
}else{
if(ActiveProfile == "Frostguard"){
TargetTemperature = 12.0 + HeatingOffset
}
if(ActiveProfile == "Away"){
TargetTemperature = 15.0 + HeatingOffset
}
if(ActiveProfile == "Present"){
TargetTemperature = 21.0 + HeatingOffset
}
}
//Actually send the target temperature to the item in question
ScriptServiceUtil.getItemRegistry.getItem(root_room + "_Heating_Target").postUpdate(TargetTemperature)
if(debugmode){logInfo("Heat Automation","Calculated a value of " + TargetTemperature + " for this zone with ProfileItemName = " + ProfileItemName + ".")}
if(debugmode){logInfo("Heat Automation","Room Complete!")}
//logOutput
if(root_room.length() < 8){
if(ActiveProfile.length() < 7){
logInfo("Heat Automation","Computed " + root_room + " \t\t Active Profile: " + ActiveProfile + " \t\t Target " + TargetTemperature + " °C")
}else{
logInfo("Heat Automation","Computed " + root_room + " \t\t Active Profile: " + ActiveProfile + " \t Target " + TargetTemperature + " °C")
}
}else{
if(ActiveProfile.length() < 7){
logInfo("Heat Automation","Computed " + root_room + " \t Active Profile: " + ActiveProfile + " \t\t Target " + TargetTemperature + " °C")
}else{
logInfo("Heat Automation","Computed " + root_room + " \t Active Profile: " + ActiveProfile + " \t Target " + TargetTemperature + " °C")
}
}
// ====== STEP 3 ======
// Send that value to all the radiators in that room
Smart_Thermostat.members.forEach[ i |
if(i.getGroupNames.contains(root_room) && i.getName.contains("Target_Temperature")){
//Change the thermostat only if it's set differently than the goal temp
if(i.state as DecimalType != TargetTemperature){
//item format is something like HKTTilman1_Target_Temperature
var thermoItem = Temperature_Targetable.members.findFirst[ i2 | i2.name == i.getName.split("_").get(0) + "_Control_Mode_Manu"]
//send the TargetTemperature to the individual thermostat
thermoItem.sendCommand(TargetTemperature as Number)
logInfo("Thermo Interval", "Thermostat \"" + thermoItem.name + "\" retargeted to " + TargetTemperature + ".")
Thread::sleep(rfDelay)
}
}
]
end
rule "Heat Automation - Disable Boost Mode"
when
Time cron "0 0 22 ? * * *"
then
logInfo("Heat Automation","Boost deactivator rule exectued")
Heating_Input.members.forEach[ GenericItem i |
if(i.name.contains("BoostMode")){
ScriptServiceUtil.getItemRegistry.getItem("" + i.name.split('_').get(0) + "_Heating_BoostMode").sendCommand(OFF)
}
]
logInfo("Heat Automation", "All Active Boosts deactivated!")
end