# The ultimate full week Heating/Thermostat control widget and ruleset (using Time-Profiles)

**URL:** <https://community.openhab.org/t/the-ultimate-full-week-heating-thermostat-control-widget-and-ruleset-using-time-profiles/144122>\
**Category:** Solutions\
**Tags:** widget, customwidget, rules-dsl, openhab3, main-ui\
**Created:** [February 5, 2023, 4:10pm UTC](https://community.openhab.org/t/the-ultimate-full-week-heating-thermostat-control-widget-and-ruleset-using-time-profiles/144122 "2023-02-05T16:10:37Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![Marmoset\_Threat](https://community.openhab.org/user_avatar/community.openhab.org/marmoset_threat/32/41469_2.png) [@Marmoset\_Threat](https://community.openhab.org/u/Marmoset_Threat)\
**Post date:** [February 5, 2023, 4:10pm UTC](https://community.openhab.org/t/the-ultimate-full-week-heating-thermostat-control-widget-and-ruleset-using-time-profiles/144122/1 "2023-02-05T16:10:37Z")

</div>

This widget/system uses [my Time-Profiles, as presented in this post.](https://community.openhab.org/t/end-user-configurable-time-profile-based-control-for-lights-heating-and-more/142733)

# 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

[![](https://i.ytimg.com/vi/X57KAVgMEKs/hqdefault.jpg "Thermostat Widget demo (OpenHab 3 MainUI)") ](https://www.youtube.com/watch?v=X57KAVgMEKs)

## 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.

 ![image](https://community-openhab-org.s3.dualstack.eu-central-1.amazonaws.com/original/3X/1/5/15ef5310203af6cc4e4fd0655b79f80099949793.png)

## 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.

 ![image](https://community-openhab-org.s3.dualstack.eu-central-1.amazonaws.com/original/3X/9/d/9d34226dec06fc7ecf41737d1ab8f9aff76210a9.png)

# Source Code

Note that this system is deeply intertwined with the “Profiles” system, it is a prerequisite.

> **YAML Widgets**
>
> **`Main Widget`**
> 
> ```auto
> 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`**
> 
> ```auto
> 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`**
> 
> ```auto
> 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`**
> 
> ```auto
> //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`**
> 
> ```auto
> 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
> 
> ```

---

<div class="post-metadata">

**Author:** ![wAvE](https://community.openhab.org/user_avatar/community.openhab.org/wave/32/112648_2.png) [@wAvE](https://community.openhab.org/u/wAvE)\
**Post date:** [September 15, 2023, 1:54pm UTC](https://community.openhab.org/t/the-ultimate-full-week-heating-thermostat-control-widget-and-ruleset-using-time-profiles/144122/2 "2023-09-15T13:54:54Z")

</div>

First of all I would like to thank you very much for your widgets and rules. I was able to set up the profile part withouth issues.  
However I am struggling with the widgets here. The Profile Selection widget shows the right amount of buttons (I have 7 profiles with type Temperature and it shows 7 lines so the oh:repeater does read through the group right). However the buttons are empty and I cant alos click on them. Any idea why?

---

<div class="post-metadata">

**Author:** ![Marmoset\_Threat](https://community.openhab.org/user_avatar/community.openhab.org/marmoset_threat/32/41469_2.png) [@Marmoset\_Threat](https://community.openhab.org/u/Marmoset_Threat)\
**Post date:** [September 15, 2023, 2:12pm UTC](https://community.openhab.org/t/the-ultimate-full-week-heating-thermostat-control-widget-and-ruleset-using-time-profiles/144122/3 "2023-09-15T14:12:17Z")

</div>

If the right amount of buttons are there, but no names, then it sounds to me like the “\_Name” items are empty. For example, if you have these Virtual Items:

```auto
//BEGIN PROFILE BLOCK
Group Profile2 (ProfileList)
Group Profile2_val (Profile2)

String Profile2_Name (Profile2) 
String Profile2_Command (Profile2, ProfileCMD) 
String Profile2_Type (Profile2, ProfileType) 
Switch Profile2_Interpolate (Profile2) 
Number Profile2_Calculated (Profile2)
Number Profile2_MaxValue (Profile2)
Number Profile2_MinValue (Profile2)

Number Profile2_0 "00" (Profile2_val, ProfileSlider) 
Number Profile2_1 "01" (Profile2_val, ProfileSlider)
//continued..

```

They will show up in the list if `Profile2_Type` is “Temperature” and then it will populate the list with the matching values of the `Profile2_Name` items.

So, check if the `<profile>_Name` items are set to values. If they aren’t–if they are set to `Null` it will not work. In the other post where the profile rules are laid out, there are multiple rules which show examples of how these names can be set, for example in the rule `rule "Profile Calculator - load defaults"` there are lines that set the names like so: `Profile1_Name.sendCommand("Standard Light Brightness")`.

Alternatively, you can open the `profile_looper` widget, and hit the gear to open a popup of `profile_looper_popup` which should let you set a name if none is set

---

<div class="post-metadata">

**Author:** ![wAvE](https://community.openhab.org/user_avatar/community.openhab.org/wave/32/112648_2.png) [@wAvE](https://community.openhab.org/u/wAvE)\
**Post date:** [September 15, 2023, 2:21pm UTC](https://community.openhab.org/t/the-ultimate-full-week-heating-thermostat-control-widget-and-ruleset-using-time-profiles/144122/4 "2023-09-15T14:21:16Z")

</div>

Unfortunately that is not the issue, the items do have names. I did trigger the load defaults rule. Basically all items with Type “Temperature” have actually a name (Away, Present, Frostguard, Workday, Weekend, Hot Morning, Alternate Heat). Funnily if i change another profile to temperature it is going to add another line - so it identifies the number of Profile Items with “Temperature” right but it somehow doesnt populate the buttons with text

---

<div class="post-metadata">

**Author:** ![Marmoset\_Threat](https://community.openhab.org/user_avatar/community.openhab.org/marmoset_threat/32/41469_2.png) [@Marmoset\_Threat](https://community.openhab.org/u/Marmoset_Threat)\
**Post date:** [September 15, 2023, 2:53pm UTC](https://community.openhab.org/t/the-ultimate-full-week-heating-thermostat-control-widget-and-ruleset-using-time-profiles/144122/5 "2023-09-15T14:53:56Z")

</div>

Okay, that’s unusual. What version of OpenHab are you using?

Here are some more things you can try:

- Does it work if you open the widget `temperature_control_v1_profilepopup` in the Developer Widget editor?

- Try this widget and see if the item states are displaying properly for debug purposes:

```auto
uid: profile_widget_debug
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:
              for: item
              fragment: true
              groupItem: ProfileList
              sourceType: itemsInGroup
            slots:
              default:
                - component: oh-list-item
                  config:
                    title: =" Profile:" + " " + loop.item.name + " |" + " Name Item Value:" + " " + items[loop.item.name + "_Name"].state + " |" + " Name Item Value:" + " " + items[loop.item.name + "_Type"].state
                      

```

---

<div class="post-metadata">

**Author:** ![wAvE](https://community.openhab.org/user_avatar/community.openhab.org/wave/32/112648_2.png) [@wAvE](https://community.openhab.org/u/wAvE)\
**Post date:** [September 15, 2023, 3:00pm UTC](https://community.openhab.org/t/the-ultimate-full-week-heating-thermostat-control-widget-and-ruleset-using-time-profiles/144122/6 "2023-09-15T15:00:47Z")

</div>

I fixed it. The issue was with the the f7-list-item, i took this component away and directly made it the oh-button and now it works. I don’t exactly know why, maybe something to do with the Openhab version.

Find attached the code (In case someone else runs into the same issue)

```yaml
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: Sep 15, 2023, 4:52:03 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: 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

```

```auto

```

---

<div class="post-metadata">

**Author:** ![Marmoset\_Threat](https://community.openhab.org/user_avatar/community.openhab.org/marmoset_threat/32/41469_2.png) [@Marmoset\_Threat](https://community.openhab.org/u/Marmoset_Threat)\
**Post date:** [September 15, 2023, 3:10pm UTC](https://community.openhab.org/t/the-ultimate-full-week-heating-thermostat-control-widget-and-ruleset-using-time-profiles/144122/7 "2023-09-15T15:10:48Z")

</div>

Ah, great! Very likely that this is something to do with OH versions, considering the code is almost a year old and I habitually avoid upgrading unless necessary because upgrades always break something.

---

<div class="post-metadata">

**Author:** ![HolBaum5](https://community.openhab.org/user_avatar/community.openhab.org/holbaum5/32/110068_2.png) [@HolBaum5](https://community.openhab.org/u/HolBaum5)\
**Post date:** [November 5, 2023, 10:22am UTC](https://community.openhab.org/t/the-ultimate-full-week-heating-thermostat-control-widget-and-ruleset-using-time-profiles/144122/8 "2023-11-05T10:22:59Z")

</div>

I have installed the profile widget/system and can view and change the profiles etc.

I am on openHAB 4.0.4.

The heat control widget /system generates the following for me.

 ![grafik](https://community-openhab-org.s3.dualstack.eu-central-1.amazonaws.com/original/3X/9/c/9cb9f428b23107e1739587ec4a20b45db597220f.png)

Does anyone have an idea what the problem could be?

---

<div class="post-metadata">

**Author:** ![Marmoset\_Threat](https://community.openhab.org/user_avatar/community.openhab.org/marmoset_threat/32/41469_2.png) [@Marmoset\_Threat](https://community.openhab.org/u/Marmoset_Threat)\
**Post date:** [November 5, 2023, 5:04pm UTC](https://community.openhab.org/t/the-ultimate-full-week-heating-thermostat-control-widget-and-ruleset-using-time-profiles/144122/9 "2023-11-05T17:04:29Z")

</div>

Probably some change to styling in OH4. Does the widget look correct in the Widget Development tool?

---

<div class="post-metadata">

**Author:** ![HolBaum5](https://community.openhab.org/user_avatar/community.openhab.org/holbaum5/32/110068_2.png) [@HolBaum5](https://community.openhab.org/u/HolBaum5)\
**Post date:** [November 6, 2023, 6:41am UTC](https://community.openhab.org/t/the-ultimate-full-week-heating-thermostat-control-widget-and-ruleset-using-time-profiles/144122/10 "2023-11-06T06:41:31Z")

</div>

Yes, the widgets are displayed correctly in the widget development tool.  
Can I also use them directly?  
For this, something must surely be deleted in the rules so that they are not used in the MainUI.

---

<div class="post-metadata">

**Author:** ![wAvE](https://community.openhab.org/user_avatar/community.openhab.org/wave/32/112648_2.png) [@wAvE](https://community.openhab.org/u/wAvE)\
**Post date:** [November 7, 2023, 1:24pm UTC](https://community.openhab.org/t/the-ultimate-full-week-heating-thermostat-control-widget-and-ruleset-using-time-profiles/144122/11 "2023-11-07T13:24:19Z")

</div>

As far as I remember when i tried to set it up, I did have a lot of issues with the widgets and ended up recreating them based on Marmoset\_Threat’s Widgets (I still used a lot of his awesome work as basis, thats also the reason I didnt want to share this here as it looks like i created something on my own when I all actually did was using the presets and tweaked them).

It looks like this for me:

Basic Heating Set-Up:

 ![image](https://community-openhab-org.s3.dualstack.eu-central-1.amazonaws.com/original/3X/c/4/c43e3b7ac95be1e6a793dba03123f799b1c2e17d.png)

If you click on “Einstellungen Heizprofile” it will open up the profile list.

 ![image](https://community-openhab-org.s3.dualstack.eu-central-1.amazonaws.com/original/3X/1/3/13cd3e1735a7795e77311913f14717f959c83637.png)

You can then click on the profiles to modify them

 ![image](https://community-openhab-org.s3.dualstack.eu-central-1.amazonaws.com/original/3X/f/5/f58a9fa570a61d81679283540576a57bb60f98c8.png)

If you click on any of the rooms it will open up the room specific settings:

 ![image](https://community-openhab-org.s3.dualstack.eu-central-1.amazonaws.com/original/3X/d/6/d6c6ff85dfa753a1374c7cc2cf07d84dff569242.png)  
 ![image](https://community-openhab-org.s3.dualstack.eu-central-1.amazonaws.com/original/3X/7/3/73bc6b6e03232e6862362c7443fc35188f1694a9.png)

I can share this with you but I also did end up rewriting all the code in blockly cause I didnt get it the code from Marmoset\_Threat to work and I also wanted to learn how this works and set it up the way I wanted..

Howver as stated I dont want to just post this here - only if it is ok for Marmoset\_Threat

---

<div class="post-metadata">

**Author:** ![Marmoset\_Threat](https://community.openhab.org/user_avatar/community.openhab.org/marmoset_threat/32/41469_2.png) [@Marmoset\_Threat](https://community.openhab.org/u/Marmoset_Threat)\
**Post date:** [November 7, 2023, 1:47pm UTC](https://community.openhab.org/t/the-ultimate-full-week-heating-thermostat-control-widget-and-ruleset-using-time-profiles/144122/12 "2023-11-07T13:47:54Z")

</div>

Sounds awesome, go ahead and share!

---

<div class="post-metadata">

**Author:** ![wAvE](https://community.openhab.org/user_avatar/community.openhab.org/wave/32/112648_2.png) [@wAvE](https://community.openhab.org/u/wAvE)\
**Post date:** [November 8, 2023, 12:28pm UTC](https://community.openhab.org/t/the-ultimate-full-week-heating-thermostat-control-widget-and-ruleset-using-time-profiles/144122/13 "2023-11-08T12:28:05Z")

</div>

Ok with the blessing here it comes and some details of what I added or changed:

I just copied the “Code” Part from the Blockly rules (minus that blockSource stuff as it made everything very messy), I hope this works for everyone - otherwise if anyone can tell me how to export it in a better way - please do tell and i’ll update my post.  
Also need to preface: I am not an programmer or IT guy. I do this on my free time, so it might be not so good code or contain bugs. So far it works for us though.

I will separate this into 2 parts as I also reworked the whole profile list rules.

First part is the Adjusted Profile List. I dont use anything else then heating, therefore I only use °C and did skip all the part with other profile types.

> **Items**
>
> ```csv
> Group ProfileList "List of all available profiles"
> Group Profile_Reset 
> Group ProfileList
> Switch ProfileMasterReset
> 
> //BEGIN PROFILE BLOCK
> Group Profile1 (ProfileList)
> Group Profile1_val (Profile1)
> 
> String Profile1_Name (Profile1) 
> Number Profile1_Calculated (Profile1)
> Number Profile1_MinValue (Profile1)
> Number Profile1_MaxValue (Profile1)
> Switch Profile1_Reset (Profile1, Profile_Reset)
> 
> Number Profile1_0 "00" (Profile1_val) 
> Number Profile1_1 "01" (Profile1_val)
> Number Profile1_2 "02" (Profile1_val)
> Number Profile1_3 "03" (Profile1_val)
> Number Profile1_4 "04" (Profile1_val)
> Number Profile1_5 "05" (Profile1_val)
> Number Profile1_6 "06" (Profile1_val)
> Number Profile1_7 "07" (Profile1_val)
> Number Profile1_8 "08" (Profile1_val)
> Number Profile1_9 "09" (Profile1_val)
> Number Profile1_10 "10" (Profile1_val)
> Number Profile1_11 "11" (Profile1_val)
> Number Profile1_12 "12" (Profile1_val)
> Number Profile1_13 "13" (Profile1_val)
> Number Profile1_14 "14" (Profile1_val)
> Number Profile1_15 "15" (Profile1_val)
> Number Profile1_16 "16" (Profile1_val)
> Number Profile1_17 "17" (Profile1_val)
> Number Profile1_18 "18" (Profile1_val)
> Number Profile1_19 "19" (Profile1_val)
> Number Profile1_20 "20" (Profile1_val)
> Number Profile1_21 "21" (Profile1_val)
> Number Profile1_22 "22" (Profile1_val)
> Number Profile1_23 "23" (Profile1_val)
> 
> //BEGIN PROFILE BLOCK
> Group Profile2 (ProfileList)
> Group Profile2_val (Profile2)
> 
> String Profile2_Name (Profile2) 
> Number Profile2_Calculated (Profile2)
> Number Profile2_MinValue (Profile2)
> Number Profile2_MaxValue (Profile2)
> Switch Profile2_Reset (Profile2, Profile_Reset)
> 
> Number Profile2_0 "00" (Profile2_val) 
> Number Profile2_1 "01" (Profile2_val)
> Number Profile2_2 "02" (Profile2_val)
> Number Profile2_3 "03" (Profile2_val)
> Number Profile2_4 "04" (Profile2_val)
> Number Profile2_5 "05" (Profile2_val)
> Number Profile2_6 "06" (Profile2_val)
> Number Profile2_7 "07" (Profile2_val)
> Number Profile2_8 "08" (Profile2_val)
> Number Profile2_9 "09" (Profile2_val)
> Number Profile2_10 "10" (Profile2_val)
> Number Profile2_11 "11" (Profile2_val)
> Number Profile2_12 "12" (Profile2_val)
> Number Profile2_13 "13" (Profile2_val)
> Number Profile2_14 "14" (Profile2_val)
> Number Profile2_15 "15" (Profile2_val)
> Number Profile2_16 "16" (Profile2_val)
> Number Profile2_17 "17" (Profile2_val)
> Number Profile2_18 "18" (Profile2_val)
> Number Profile2_19 "19" (Profile2_val)
> Number Profile2_20 "20" (Profile2_val)
> Number Profile2_21 "21" (Profile2_val)
> Number Profile2_22 "22" (Profile2_val)
> Number Profile2_23 "23" (Profile2_val)
> 
> //BEGIN PROFILE BLOCK
> Group Profile3 (ProfileList)
> Group Profile3_val (Profile3)
> 
> String Profile3_Name (Profile3) 
> Number Profile3_Calculated (Profile3)
> Number Profile3_MinValue (Profile3)
> Number Profile3_MaxValue (Profile3)
> Switch Profile3_Reset (Profile3, Profile_Reset)
> 
> Number Profile3_0 "00" (Profile3_val) 
> Number Profile3_1 "01" (Profile3_val)
> Number Profile3_2 "02" (Profile3_val)
> Number Profile3_3 "03" (Profile3_val)
> Number Profile3_4 "04" (Profile3_val)
> Number Profile3_5 "05" (Profile3_val)
> Number Profile3_6 "06" (Profile3_val)
> Number Profile3_7 "07" (Profile3_val)
> Number Profile3_8 "08" (Profile3_val)
> Number Profile3_9 "09" (Profile3_val)
> Number Profile3_10 "10" (Profile3_val)
> Number Profile3_11 "11" (Profile3_val)
> Number Profile3_12 "12" (Profile3_val)
> Number Profile3_13 "13" (Profile3_val)
> Number Profile3_14 "14" (Profile3_val)
> Number Profile3_15 "15" (Profile3_val)
> Number Profile3_16 "16" (Profile3_val)
> Number Profile3_17 "17" (Profile3_val)
> Number Profile3_18 "18" (Profile3_val)
> Number Profile3_19 "19" (Profile3_val)
> Number Profile3_20 "20" (Profile3_val)
> Number Profile3_21 "21" (Profile3_val)
> Number Profile3_22 "22" (Profile3_val)
> Number Profile3_23 "23" (Profile3_val)
> 
> //BEGIN PROFILE BLOCK
> Group Profile4 (ProfileList)
> Group Profile4_val (Profile4)
> 
> String Profile4_Name (Profile4) 
> Number Profile4_Calculated (Profile4)
> Number Profile4_MinValue (Profile4)
> Number Profile4_MaxValue (Profile4)
> Switch Profile4_Reset (Profile4, Profile_Reset)
> 
> Number Profile4_0 "00" (Profile4_val) 
> Number Profile4_1 "01" (Profile4_val)
> Number Profile4_2 "02" (Profile4_val)
> Number Profile4_3 "03" (Profile4_val)
> Number Profile4_4 "04" (Profile4_val)
> Number Profile4_5 "05" (Profile4_val)
> Number Profile4_6 "06" (Profile4_val)
> Number Profile4_7 "07" (Profile4_val)
> Number Profile4_8 "08" (Profile4_val)
> Number Profile4_9 "09" (Profile4_val)
> Number Profile4_10 "10" (Profile4_val)
> Number Profile4_11 "11" (Profile4_val)
> Number Profile4_12 "12" (Profile4_val)
> Number Profile4_13 "13" (Profile4_val)
> Number Profile4_14 "14" (Profile4_val)
> Number Profile4_15 "15" (Profile4_val)
> Number Profile4_16 "16" (Profile4_val)
> Number Profile4_17 "17" (Profile4_val)
> Number Profile4_18 "18" (Profile4_val)
> Number Profile4_19 "19" (Profile4_val)
> Number Profile4_20 "20" (Profile4_val)
> Number Profile4_21 "21" (Profile4_val)
> Number Profile4_22 "22" (Profile4_val)
> Number Profile4_23 "23" (Profile4_val)
> 
> //BEGIN PROFILE BLOCK
> Group Profile5 (ProfileList)
> Group Profile5_val (Profile5)
> 
> String Profile5_Name (Profile5) 
> Number Profile5_Calculated (Profile5)
> Number Profile5_MinValue (Profile5)
> Number Profile5_MaxValue (Profile5)
> Switch Profile5_Reset (Profile5, Profile_Reset)
> 
> Number Profile5_0 "00" (Profile5_val) 
> Number Profile5_1 "01" (Profile5_val)
> Number Profile5_2 "02" (Profile5_val)
> Number Profile5_3 "03" (Profile5_val)
> Number Profile5_4 "04" (Profile5_val)
> Number Profile5_5 "05" (Profile5_val)
> Number Profile5_6 "06" (Profile5_val)
> Number Profile5_7 "07" (Profile5_val)
> Number Profile5_8 "08" (Profile5_val)
> Number Profile5_9 "09" (Profile5_val)
> Number Profile5_10 "10" (Profile5_val)
> Number Profile5_11 "11" (Profile5_val)
> Number Profile5_12 "12" (Profile5_val)
> Number Profile5_13 "13" (Profile5_val)
> Number Profile5_14 "14" (Profile5_val)
> Number Profile5_15 "15" (Profile5_val)
> Number Profile5_16 "16" (Profile5_val)
> Number Profile5_17 "17" (Profile5_val)
> Number Profile5_18 "18" (Profile5_val)
> Number Profile5_19 "19" (Profile5_val)
> Number Profile5_20 "20" (Profile5_val)
> Number Profile5_21 "21" (Profile5_val)
> Number Profile5_22 "22" (Profile5_val)
> Number Profile5_23 "23" (Profile5_val)
> 
> ```

> **Rule: Profile Masterreset**
>
> ```javascript
> configuration: {}
> triggers:
> - id: "1"
> configuration:
> command: ON
> itemName: ProfileMasterReset
> type: core.ItemCommandTrigger
> conditions: []
> actions:
> - inputs: {}
> id: "2"
> configuration:
> type: application/javascript
> script: >
> var items_in_reset_group, profile_reset, children_in_reset_group;
> 
> var thread = Java.type('java.lang.Thread')
> 
> var items_in_reset_group_list = items.getItem('Profile_Reset').members;
> 
> for (var items_in_reset_group_index in items_in_reset_group_list) {
> items_in_reset_group = items_in_reset_group_list[items_in_reset_group_index];
> profile_reset = items_in_reset_group.name;
> items.getItem(profile_reset).sendCommand('ON');
> }
> 
> thread.sleep(5000);
> 
> items.getItem('Abwesend_Name').sendCommand('Abwesend');
> 
> var children_in_reset_group_list = items.getItem('Abwesend_val').members;
> 
> for (var children_in_reset_group_index in children_in_reset_group_list) {
> children_in_reset_group = children_in_reset_group_list[children_in_reset_group_index];
> children_in_reset_group.sendCommand(15);
> }
> 
> thread.sleep(3000);
> 
> items.getItem('Aus_Name').sendCommand('Aus');
> 
> var children_in_reset_group_list2 = items.getItem('Aus_val').members;
> 
> for (var children_in_reset_group_index2 in children_in_reset_group_list2) {
> children_in_reset_group = children_in_reset_group_list2[children_in_reset_group_index2];
> children_in_reset_group.sendCommand(5);
> }
> 
> items.getItem('Boost_Name').sendCommand('Boost');
> 
> thread.sleep(3000);
> 
> var children_in_reset_group_list3 = items.getItem('Boost_val').members;
> 
> for (var children_in_reset_group_index3 in children_in_reset_group_list3) {
> children_in_reset_group = children_in_reset_group_list3[children_in_reset_group_index3];
> children_in_reset_group.sendCommand(30);
> }
> 
> items.getItem('ProfileMasterReset').sendCommand('OFF');
> type: script.ScriptAction
> 
> ```

> **Rule: Profile Reset**
>
> ```javascript
> configuration: {}
> triggers:
> - id: "1"
> configuration:
> command: ON
> groupName: Profile_Reset
> type: core.GroupCommandTrigger
> conditions: []
> actions:
> - inputs: {}
> id: "2"
> configuration:
> 
> type: application/javascript
> script: >
> var profile_to_reset, Profile_Identificator, Profile_Group,
> children_in_reset_group, Profile_Name, Profile_MinValue,
> Profile_MaxValue, Profile_Calculated;
> 
> profile_to_reset = event.itemName;
> 
> Profile_Identificator = profile_to_reset.slice(0, (profile_to_reset.indexOf('_') + 1) - 1);
> 
> console.info(('Profile identified: ' + String(Profile_Identificator)));
> 
> Profile_Group = [Profile_Identificator,'_','val'].join('');
> 
> var children_in_reset_group_list = items.getItem(Profile_Group).members;
> 
> for (var children_in_reset_group_index in children_in_reset_group_list) {
> children_in_reset_group = children_in_reset_group_list[children_in_reset_group_index];
> children_in_reset_group.sendCommand(5);
> }
> 
> Profile_Name = [Profile_Identificator,'_','Name'].join('');
> 
> items.getItem(Profile_Name).sendCommand(Profile_Identificator);
> 
> Profile_MinValue = [Profile_Identificator,'_','MinValue'].join('');
> 
> items.getItem(Profile_MinValue).sendCommand(0);
> 
> Profile_MaxValue = [Profile_Identificator,'_','MinValue'].join('');
> 
> items.getItem(Profile_MaxValue).sendCommand(0);
> 
> Profile_Calculated = [Profile_Identificator,'_','Calculated'].join('');
> 
> items.getItem(Profile_Calculated).sendCommand(0);
> 
> items.getItem(profile_to_reset).sendCommand('OFF');
> type: script.ScriptAction
> 
> ```

> **Rule: Calculate Current Value**
>
> ```javascript
> configuration: {}
> triggers:
> - id: "1"
> configuration:
> cronExpression: 0 0/1 * * * ? *
> type: timer.GenericCronTrigger
> conditions: []
> actions:
> - inputs: {}
> id: "2"
> configuration:
> type: application/javascript
> script: >
> var aktuelles_intervall, available_profiles,
> identifikator_val_gruppe_aktuelle_stunde, profil_variable_target_wert,
> wert_aktuelles_intervall_gruppe;
> 
> aktuelles_intervall = ((time.ZonedDateTime.now()).hour());
> 
> var available_profiles_list = items.getItem('ProfileList').members;
> 
> for (var available_profiles_index in available_profiles_list) {
> available_profiles = available_profiles_list[available_profiles_index];
> available_profiles = available_profiles.name;
> identifikator_val_gruppe_aktuelle_stunde = [available_profiles,'_',aktuelles_intervall].join('');
> profil_variable_target_wert = [available_profiles,'_','Calculated'].join('');
> wert_aktuelles_intervall_gruppe = items.getItem(identifikator_val_gruppe_aktuelle_stunde).state;
> items.getItem(profil_variable_target_wert).sendCommand(wert_aktuelles_intervall_gruppe);
> }
> type: script.ScriptAction
> 
> ```

> **Widget: Profile Picker**
>
> ```yaml
> uid: Auswahl_Heizungsprofile
> tags: []
> props:
> parameters: []
> parameterGroups: []
> timestamp: Sep 19, 2023, 12:27:07 PM
> component: f7-card
> config:
> title: Heizungsprofile
> slots:
> default:
> - component: f7-list
> slots:
> default:
> - component: oh-repeater
> config:
> for: item
> fragment: true
> groupItem: ProfileList
> sourceType: itemsInGroup
> 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)"
> text: =items[loop.item.name + '_Name'].state
> 
> ```

> **Widget: Profile Settings**
>
> ```yaml
> uid: Heizeinstellungen_Profil_Popup
> tags: []
> props:
> parameters:
> - context: item
> description: Zu änderndes Profil
> label: Item
> name: profile
> required: false
> type: TEXT
> parameterGroups: []
> timestamp: Sep 16, 2023, 6:25:40 PM
> component: f7-card
> config:
> title: ="Profileinstellungen für " + items[props.profile + "_Name"].state
> slots:
> default:
> - {}
> - component: f7-card-content
> slots:
> default:
> - component: f7-row
> config:
> style:
> height: 3em
> slots:
> default:
> - component: f7-col
> config:
> width: 25
> slots:
> default:
> - component: Label
> config:
> text: Profilname
> - component: f7-col
> config:
> width: 75
> slots:
> default:
> - component: oh-input
> config:
> item: =props.profile + '_Name'
> name: Name
> outline: true
> placeholder: = items[props.profile + '_Name'].state
> sendButton: true
> type: text
> - component: f7-row
> slots:
> default:
> - component: f7-col
> config: {}
> slots:
> default:
> - component: oh-button
> config:
> action: toggle
> actionAltCommand: On
> actionCommand: ON
> actionItem: =props.profile + '_Reset'
> large: true
> outline: true
> style:
> margin-top: 1em
> text: Profilreset?
> 
> ```

> **Widget: List of Profiles**
>
> ```yaml
> uid: Liste_Heizungsprofile
> tags: []
> props:
> parameters: []
> parameterGroups: []
> timestamp: Sep 16, 2023, 10:47:43 PM
> component: f7-card
> config:
> title: Heizungsprofile
> slots:
> default:
> - component: f7-list
> slots:
> default:
> - component: oh-repeater
> config:
> for: item
> fragment: true
> groupItem: ProfileList
> sourceType: itemsInGroup
> slots:
> default:
> - component: oh-button
> config:
> action: popup
> actionModal: widget:Heizungsprofil_Einstellungen
> actionModalConfig:
> profile: =loop.item.name
> text: =items[loop.item.name + '_Name'].state
> 
> ```

Now for the Automated Heating part:

I did add groups for open windows per room (Deactivates the heating in the room if any window of that room is open), as well a group checking wether me or my wife or both are away (For example: If my wife is away, the heating in her office will be automatically set to away mode but in all the “common” rooms it will stay on. If we both are away then all the heatings will be put to away mode)  
I also did add a group Boostmode that helps me reset the boost mode 30 min after it being activated (to make sure boostmode is not running endless) and a group called override which helps me do the same for the “Temperature Calibration” setting (So if someone manually adjusts the temperature, it will reset it back to the profile value after 3 hours)

> **Items**
>
> ```csv
> //Heating Control Items (Virtual)
> //Global Groups
> Switch Global_Vacation_Override "Heizung aus / Sommermodus / Away Modus"
> Group windowsensor
> Group:Number:COUNT(ON) boostmode
> Switch Trigger_HeatAutomation_Script
> Group away
> Group override
> 
> //Heat Specific Groups
> Group Heating_Input
> Group Master_Heating_Switch 
> 
> //Heating items for Wohnzimmer
> 
> Group Heating_Wohnzimmer (wohnzimmer)
> 
> Switch wohnzimmer_Heating_Active "Heating Active" (Master_Heating_Switch, Heating_Wohnzimmer, Heating_Input)  
> Number wohnzimmer_Heating_Offset "Heating Offset" (Heating_Wohnzimmer, Heating_Input, override)  
> Number wohnzimmer_Heating_Target "Heating Target" (Heating_Wohnzimmer, Heating_Input)  
> String wohnzimmer_Heating_AutomationMode "Heating Automation Mode" (Heating_Wohnzimmer, Heating_Input)  
> String wohnzimmer_Heating_AutomationProfilePrimary "Heating Automation Primary Profile" (Heating_Wohnzimmer, Heating_Input)  
> String wohnzimmer_Heating_AutomationProfileAlternate "Heating Automation Alternate Profile" (Heating_Wohnzimmer, Heating_Input)  
> String wohnzimmer_Heating_AutomationProfileActive "Heating Automation Active Profile" (Heating_Wohnzimmer, Heating_Input)  
> String wohnzimmer_Heating_AutomationProfileActiveID "Heating Automation Active Profile ID" (Heating_Wohnzimmer)  
> Switch wohnzimmer_Heating_BoostMode "Heating Boost Mode" (Heating_Wohnzimmer, boostmode, Heating_Input)  
> Switch wohnzimmer_Heating_WindowSuspension "Heating Window Suspension" (Heating_Wohnzimmer, Heating_Input)
> Group:Number:AVG wohnzimmer_Temperature "Average Temperature" <temperature> (Heating_Wohnzimmer, Heating_Input)  
> Group:Number:COUNT(false) wohnzimmer_windowsensor "Anzahl offene Fenster" (Heating_Wohnzimmer, windowsensor, Heating_Input) 
> Group:Number:COUNT(OFF) wohnzimmer_away "Person1 / Person2 Away" (Heating_Wohnzimmer, away, Heating_Input)
> Switch wohnzimmer_Heating_AwaySwitch "Wohnzimmer Away Switch" (Heating_Wohnzimmer, Heating_Input)
> 
> ```

I do have Zigbee Thermostats that are not very smart (they stop heating once the local temperature measured on the thermostat is higher than the setpoint - which happens pretty fast as the thermostat temperature is directly next to the heating and therefore quickly higher than room temperature). In order to counter that, I added a rule that checks if the room temperature matches the target temperature and than variablly adjusts the temperature offset so that room temperature = thermostat temperature.

> **Rule: Regular Heating Check**
>
> ```javascript
> configuration: {}
> triggers:
> - id: "1"
> configuration:
> cronExpression: 0 0/10 * * * ? *
> type: timer.GenericCronTrigger
> - id: "3"
> configuration:
> command: ON
> itemName: Trigger_HeatAutomation_Script
> type: core.ItemCommandTrigger
> - id: "4"
> configuration:
> itemName: Global_Vacation_Override
> type: core.ItemStateChangeTrigger
> conditions: []
> actions:
> - inputs: {}
> id: "2"
> configuration:
> type: application/javascript
> script: >
> var Kindersicherung, member_of_master_heating_switch_group, root_room,
> Heating_Active, Heating_AutomationMode,
> Heating_AutomationProfilePrimary, Heating_AutomationProfileAlternate,
> Heating_Offset, Heating_WindowSuspension,
> Heating_AutomationProfileActive, Heating_BoostMode, Heating_AwaySwitch,
> Heating_AutomationProfileActiveID, TemperatureCalibration,
> CurrentSetPoint, SystemMode, Temperature, ThermostatTemperature,
> AutomationMode, member_of_profile_list, ActiveProfile, Profile_Group,
> Name_of_Profile, Target_Temperature, profile_calculated, Heating_Target,
> Pulled_Target_Temperature, Heating_Offset_Value, CurrentTemperatureRoom,
> UpdatedCurrentTemperature_Room, Weekend, TemperatureOffset,
> TemperatureOffsetRounded;
> 
> var thread = Java.type('java.lang.Thread')
> 
> items.getItem('Trigger_HeatAutomation_Script').sendCommand('OFF');
> 
> thread.sleep(2000);
> 
> var member_of_master_heating_switch_group_list = items.getItem('Master_Heating_Switch').members;
> 
> for (var member_of_master_heating_switch_group_index in member_of_master_heating_switch_group_list) {
> member_of_master_heating_switch_group = member_of_master_heating_switch_group_list[member_of_master_heating_switch_group_index];
> root_room = member_of_master_heating_switch_group.name.slice(0, (member_of_master_heating_switch_group.name.indexOf('_') + 1) - 1);
> Heating_Active = String(root_room) + '_Heating_Active';
> Heating_AutomationMode = String(root_room) + '_Heating_AutomationMode';
> Heating_AutomationProfilePrimary = String(root_room) + '_Heating_AutomationProfilePrimary';
> Heating_AutomationProfileAlternate = String(root_room) + '_Heating_AutomationProfileAlternate';
> Heating_Offset = String(root_room) + '_Heating_Offset';
> Heating_WindowSuspension = String(root_room) + '_Heating_WindowSuspension';
> Heating_AutomationProfileActive = String(root_room) + '_Heating_AutomationProfileActive';
> Heating_BoostMode = String(root_room) + '_Heating_BoostMode';
> Heating_AwaySwitch = String(root_room) + '_Heating_AwaySwitch';
> Heating_AutomationProfileActiveID = String(root_room) + '_Heating_AutomationProfileActiveID';
> TemperatureCalibration = String(root_room) + '_thermostat_Temperature_Calibration';
> Kindersicherung = String(root_room) + '_thermostat_Kindersicherung';
> CurrentSetPoint = String(root_room) + '_thermostat_Current_Setpoint';
> SystemMode = String(root_room) + '_thermostat_System_Mode';
> Temperature = String(root_room) + '_Temperature';
> ThermostatTemperature = String(root_room) + '_thermostat_Local_Temperature';
> if (items.getItem(Heating_Active).state == 'NULL') {
> items.getItem(Heating_Active).postUpdate('OFF');
> }
> if (items.getItem(Heating_AutomationMode).state == 'NULL') {
> items.getItem(Heating_AutomationMode).postUpdate('Static');
> }
> if (items.getItem(Heating_AutomationProfilePrimary).state == 'NULL') {
> items.getItem(Heating_AutomationProfilePrimary).postUpdate('Abwesend');
> if (items.getItem(Heating_AutomationProfileAlternate).state == 'NULL') {
> items.getItem(Heating_AutomationProfileAlternate).postUpdate('Abwesend');
> }
> }
> if (items.getItem(Heating_Offset).state == 'NULL') {
> items.getItem(Heating_Offset).postUpdate(0);
> }
> if (items.getItem(Heating_WindowSuspension).state == 'NULL') {
> items.getItem(Heating_WindowSuspension).postUpdate('OFF');
> }
> if (items.getItem(Heating_BoostMode).state == 'NULL') {
> items.getItem(Heating_BoostMode).postUpdate('OFF');
> }
> AutomationMode = items.getItem(Heating_AutomationMode).state;
> if (AutomationMode != 'Auto' && AutomationMode != 'Static') {
> items.getItem(Heating_AutomationMode).postUpdate('Static');
> } else {
> }
> if (items.getItem('Global_Vacation_Override').state == 'ON') {
> items.getItem(Heating_AutomationProfileActive).postUpdate('Aus');
> ActiveProfile = items.getItem(Heating_AutomationProfileActive).state;
> } else {
> if (items.getItem(Heating_Active).state == 'ON') {
> if (items.getItem(Heating_WindowSuspension).state == 'ON') {
> items.getItem(Heating_AutomationProfileActive).postUpdate('Aus');
> ActiveProfile = items.getItem(Heating_AutomationProfileActive).state;
> } else {
> if (items.getItem(Heating_AwaySwitch).state == 'ON') {
> items.getItem(Heating_AutomationProfileActive).postUpdate('Abwesend');
> ActiveProfile = items.getItem(Heating_AutomationProfileActive).state;
> } else {
> if (items.getItem(Heating_BoostMode).state == 'ON') {
> items.getItem(Heating_AutomationProfileActive).postUpdate('Boost');
> ActiveProfile = items.getItem(Heating_AutomationProfileActive).state;
> } else {
> if (items.getItem(Heating_AutomationMode).state == 'Static') {
> items.getItem(Heating_AutomationProfileActive).postUpdate(items.getItem(Heating_AutomationProfilePrimary).state);
> ActiveProfile = items.getItem(Heating_AutomationProfilePrimary).state;
> } else {
> if (items.getItem(Heating_AutomationMode).state == 'Auto') {
> Weekend = ((time.ZonedDateTime.now()).dayOfWeek().value());
> if (Weekend < 6) {
> items.getItem(Heating_AutomationProfileActive).postUpdate(items.getItem(Heating_AutomationProfilePrimary).state);
> ActiveProfile = items.getItem(Heating_AutomationProfilePrimary).state;
> } else {
> items.getItem(Heating_AutomationProfileActive).postUpdate(items.getItem(Heating_AutomationProfileAlternate).state);
> ActiveProfile = items.getItem(Heating_AutomationProfileAlternate).state;
> }
> }
> }
> }
> }
> }
> } else {
> items.getItem(Heating_AutomationProfileActive).postUpdate('Aus');
> ActiveProfile = items.getItem(Heating_AutomationProfileActive).state;
> }
> }
> var member_of_profile_list_list = items.getItem('ProfileList').members;
> for (var member_of_profile_list_index in member_of_profile_list_list) {
> member_of_profile_list = member_of_profile_list_list[member_of_profile_list_index];
> Profile_Group = [member_of_profile_list.name,'_','Name'].join('');
> Name_of_Profile = items.getItem(Profile_Group).state;
> if (Name_of_Profile == ActiveProfile) {
> items.getItem(Heating_AutomationProfileActiveID).sendCommand(member_of_profile_list.name);
> profile_calculated = [member_of_profile_list.name,'_','Calculated'].join('');
> Heating_Target = String(root_room) + '_Heating_Target';
> Pulled_Target_Temperature = items.getItem(profile_calculated).state;
> Heating_Offset = String(root_room) + '_Heating_Offset';
> Heating_Offset_Value = items.getItem(Heating_Offset).state;
> Target_Temperature = (Quantity(Pulled_Target_Temperature).add(Quantity(Heating_Offset_Value)));
> items.getItem(Heating_Target).sendCommand(Target_Temperature);
> }
> }
> if (items.getItem(Kindersicherung).state == 'UNLOCK') {
> items.getItem(Kindersicherung).sendCommand('LOCK');
> } else if (items.getItem(Kindersicherung).state == 'LOCK') {
> }
> if (ActiveProfile == 'Aus') {
> if (items.getItem(SystemMode).state == 'heat' || items.getItem(SystemMode).state == 'auto') {
> items.getItem(SystemMode).sendCommand('off');
> }
> } else if (ActiveProfile != 'Aus') {
> if (items.getItem(SystemMode).state != 'heat') {
> items.getItem(SystemMode).sendCommand('heat');
> }
> }
> console.info(('Check if Current SetPoint of Thermostat matches the Target Temperature and change it if needed for room: ' + String(root_room)));
> console.info(('Current Setpoint is: ' + String(items.getItem(CurrentSetPoint).state)));
> console.info(('Current Target Temp is ' + String(Target_Temperature)));
> if (items.getItem(CurrentSetPoint).state == Target_Temperature) {
> console.info('Thermostat Temperature already set to Target Temperature - do nothing');
> } else {
> console.info('Thermostat Temperature not matching Target Temperature - Change Temperature');
> items.getItem(CurrentSetPoint).sendCommand(Target_Temperature);
> }
> console.info('------------------------------------------------------------------------');
> console.info('Start Thermostat Calibration Script');
> CurrentTemperatureRoom = Quantity(items.getItem(Temperature).state);
> console.info('Adding 0.5°C to CurrentTemperature for less sensivity');
> UpdatedCurrentTemperature_Room = (Quantity(CurrentTemperatureRoom).add(Quantity('0.5')));
> console.info((['Actual Room Temperature : ',CurrentTemperatureRoom,' Updated Room Temperature: ',UpdatedCurrentTemperature_Room].join('')));
> if (UpdatedCurrentTemperature_Room < Target_Temperature) {
> console.info('Room Temperature Lower than Target_Temperature modify Temperature Offset to adjust it');
> console.info('Calculate needed Offset to match Sensor Temperature and Thermostat Temperature');
> console.info('Check if Thermostat Temp is under Room Temp');
> console.info('Room Temperature is: ');
> console.info(Quantity(items.getItem(Temperature).state));
> console.info('Thermostat Temperature is: ');
> console.info(Quantity(items.getItem(ThermostatTemperature).state));
> TemperatureOffset = (Quantity(items.getItem(Temperature).state).subtract(Quantity(items.getItem(ThermostatTemperature).state)));
> TemperatureOffsetRounded = Math.round(TemperatureOffset);
> if (TemperatureOffset < -6) {
> TemperatureOffsetRounded = -6;
> } else if (TemperatureOffset > 6) {
> TemperatureOffsetRounded = 6;
> }
> items.getItem(TemperatureCalibration).sendCommand(TemperatureOffsetRounded);
> console.info(('Offset Calculated and updated to: ' + String(TemperatureOffsetRounded)));
> console.info('Room Temperature Higher than Target temperature, reset Temperature Calibration');
> console.info(('Current Temperature Calibration is:' + String(items.getItem(TemperatureCalibration).state)));
> } else if (UpdatedCurrentTemperature_Room > CurrentSetPoint && items.getItem(TemperatureCalibration).state != '0') {
> console.info('Room Temperature Higher than Target temperature, reset Temperature Calibration');
> items.getItem(TemperatureCalibration).sendCommand('0');
> } else {
> console.info('Temperature already properly calibrated - do nothing');
> }
> console.info('----------------------------------------------------------------');
> }
> 
> Kindersicherung;
> 
> Kindersicherung;
> type: script.ScriptAction
> 
> ```

> **Rule: Heating Input Trigger**
>
> ```javascript
> configuration: {}
> triggers:
> - id: "1"
> configuration:
> groupName: Heating_Input
> type: core.GroupStateChangeTrigger
> conditions: []
> actions:
> - inputs: {}
> id: "2"
> configuration:
> type: application/javascript
> script: >
> var root_room, Triggering_item, Heating_Active, Heating_AutomationMode,
> Heating_AutomationProfilePrimary, Heating_AutomationProfileAlternate,
> Heating_Offset, Heating_WindowSuspension,
> Heating_AutomationProfileActive, Heating_BoostMode, Heating_AwaySwitch,
> Heating_AutomationProfileActiveID, TemperatureCalibration,
> Kindersicherung, CurrentSetPoint, SystemMode, Temperature,
> ThermostatTemperature, AutomationMode, member_of_profile_list,
> ActiveProfile, Profile_Group, Name_of_Profile, Target_Temperature,
> profile_calculated, Heating_Target, CurrentTemperatureRoom,
> Pulled_Target_Temperature, UpdatedCurrentTemperature_Room,
> Heating_Offset_Value, TemperatureOffset, TemperatureOffsetRounded,
> Weekend;
> 
> Triggering_item = event.itemName;
> 
> root_room = Triggering_item.slice(0, (Triggering_item.indexOf('_') + 1) - 1);
> 
> Heating_Active = String(root_room) + '_Heating_Active';
> 
> Heating_AutomationMode = String(root_room) + '_Heating_AutomationMode';
> 
> Heating_AutomationProfilePrimary = String(root_room) + '_Heating_AutomationProfilePrimary';
> 
> Heating_AutomationProfileAlternate = String(root_room) + '_Heating_AutomationProfileAlternate';
> 
> Heating_Offset = String(root_room) + '_Heating_Offset';
> 
> Heating_WindowSuspension = String(root_room) + '_Heating_WindowSuspension';
> 
> Heating_AutomationProfileActive = String(root_room) + '_Heating_AutomationProfileActive';
> 
> Heating_BoostMode = String(root_room) + '_Heating_BoostMode';
> 
> Heating_AwaySwitch = String(root_room) + '_Heating_AwaySwitch';
> 
> Heating_AutomationProfileActiveID = String(root_room) + '_Heating_AutomationProfileActiveID';
> 
> TemperatureCalibration = String(root_room) + '_thermostat_Temperature_Calibration';
> 
> Kindersicherung = String(root_room) + '_thermostat_Kindersicherung';
> 
> CurrentSetPoint = String(root_room) + '_thermostat_Current_Setpoint';
> 
> SystemMode = String(root_room) + '_thermostat_System_Mode';
> 
> Temperature = String(root_room) + '_Temperature';
> 
> ThermostatTemperature = String(root_room) + '_thermostat_Local_Temperature';
> 
> if (items.getItem(Heating_Active).state == 'NULL') {
> items.getItem(Heating_Active).postUpdate('OFF');
> }
> 
> if (items.getItem(Heating_AutomationMode).state == 'NULL') {
> items.getItem(Heating_AutomationMode).postUpdate('Static');
> }
> 
> if (items.getItem(Heating_AutomationProfilePrimary).state == 'NULL') {
> items.getItem(Heating_AutomationProfilePrimary).postUpdate('Abwesend');
> if (items.getItem(Heating_AutomationProfileAlternate).state == 'NULL') {
> items.getItem(Heating_AutomationProfileAlternate).postUpdate('Abwesend');
> }
> }
> 
> if (items.getItem(Heating_Offset).state == 'NULL') {
> items.getItem(Heating_Offset).postUpdate(0);
> }
> 
> if (items.getItem(Heating_WindowSuspension).state == 'NULL') {
> items.getItem(Heating_WindowSuspension).postUpdate('OFF');
> }
> 
> if (items.getItem(Heating_BoostMode).state == 'NULL') {
> items.getItem(Heating_BoostMode).postUpdate('OFF');
> }
> 
> AutomationMode = items.getItem(Heating_AutomationMode).state;
> 
> if (AutomationMode != 'Auto' && AutomationMode != 'Static') {
> items.getItem(Heating_AutomationMode).postUpdate('Static');
> } else {
> 
> }
> 
> if (items.getItem('Global_Vacation_Override').state == 'ON') {
> items.getItem(Heating_AutomationProfileActive).postUpdate('Aus');
> ActiveProfile = items.getItem(Heating_AutomationProfileActive).state;
> } else {
> if (items.getItem(Heating_Active).state == 'ON') {
> if (items.getItem(Heating_WindowSuspension).state == 'ON') {
> items.getItem(Heating_AutomationProfileActive).postUpdate('Aus');
> ActiveProfile = items.getItem(Heating_AutomationProfileActive).state;
> } else {
> if (items.getItem(Heating_AwaySwitch).state == 'ON') {
> items.getItem(Heating_AutomationProfileActive).postUpdate('Abwesend');
> ActiveProfile = items.getItem(Heating_AutomationProfileActive).state;
> } else {
> if (items.getItem(Heating_BoostMode).state == 'ON') {
> items.getItem(Heating_AutomationProfileActive).postUpdate('Boost');
> ActiveProfile = items.getItem(Heating_AutomationProfileActive).state;
> } else {
> if (items.getItem(Heating_AutomationMode).state == 'Static') {
> items.getItem(Heating_AutomationProfileActive).postUpdate(items.getItem(Heating_AutomationProfilePrimary).state);
> ActiveProfile = items.getItem(Heating_AutomationProfilePrimary).state;
> } else {
> if (items.getItem(Heating_AutomationMode).state == 'Auto') {
> Weekend = ((time.ZonedDateTime.now()).dayOfWeek().value());
> if (Weekend < 6) {
> items.getItem(Heating_AutomationProfileActive).postUpdate(items.getItem(Heating_AutomationProfilePrimary).state);
> ActiveProfile = items.getItem(Heating_AutomationProfilePrimary).state;
> } else {
> items.getItem(Heating_AutomationProfileActive).postUpdate(items.getItem(Heating_AutomationProfileAlternate).state);
> ActiveProfile = items.getItem(Heating_AutomationProfileAlternate).state;
> }
> }
> }
> }
> }
> }
> } else {
> items.getItem(Heating_AutomationProfileActive).postUpdate('Aus');
> ActiveProfile = items.getItem(Heating_AutomationProfileActive).state;
> }
> }
> 
> var member_of_profile_list_list = items.getItem('ProfileList').members;
> 
> for (var member_of_profile_list_index in member_of_profile_list_list) {
> member_of_profile_list = member_of_profile_list_list[member_of_profile_list_index];
> Profile_Group = [member_of_profile_list.name,'_','Name'].join('');
> Name_of_Profile = items.getItem(Profile_Group).state;
> if (Name_of_Profile == ActiveProfile) {
> items.getItem(Heating_AutomationProfileActiveID).sendCommand(member_of_profile_list.name);
> profile_calculated = [member_of_profile_list.name,'_','Calculated'].join('');
> Heating_Target = String(root_room) + '_Heating_Target';
> Pulled_Target_Temperature = items.getItem(profile_calculated).state;
> Heating_Offset = String(root_room) + '_Heating_Offset';
> Heating_Offset_Value = items.getItem(Heating_Offset).state;
> Target_Temperature = (Quantity(Pulled_Target_Temperature).add(Quantity(Heating_Offset_Value)));
> items.getItem(Heating_Target).sendCommand(Target_Temperature);
> }
> }
> 
> if (items.getItem(Kindersicherung).state == 'UNLOCK') {
> items.getItem(Kindersicherung).sendCommand('LOCK');
> } else if (items.getItem(Kindersicherung).state == 'LOCK') {
> 
> }
> 
> if (ActiveProfile == 'Aus') {
> if (items.getItem(SystemMode).state == 'heat' || items.getItem(SystemMode).state == 'auto') {
> items.getItem(SystemMode).sendCommand('off');
> }
> } else if (ActiveProfile != 'Aus') {
> if (items.getItem(SystemMode).state != 'heat') {
> items.getItem(SystemMode).sendCommand('heat');
> }
> }
> 
> if (items.getItem(CurrentSetPoint).state == Target_Temperature) {
> 
> } else {
> items.getItem(CurrentSetPoint).sendCommand(Target_Temperature);
> }
> 
> CurrentTemperatureRoom = Quantity(items.getItem(Temperature).state);
> 
> UpdatedCurrentTemperature_Room = (Quantity(CurrentTemperatureRoom).add(Quantity('0.5')));
> 
> if (UpdatedCurrentTemperature_Room < Target_Temperature) {
> TemperatureOffset = (Quantity(items.getItem(Temperature).state).subtract(Quantity(items.getItem(ThermostatTemperature).state)));
> TemperatureOffsetRounded = Math.round(TemperatureOffset);
> if (TemperatureOffset < -6) {
> TemperatureOffsetRounded = -6;
> } else if (TemperatureOffset > 6) {
> TemperatureOffsetRounded = 6;
> }
> items.getItem(TemperatureCalibration).sendCommand(TemperatureOffsetRounded);
> } else if (UpdatedCurrentTemperature_Room > CurrentSetPoint && items.getItem(TemperatureCalibration).state != '0') {
> items.getItem(TemperatureCalibration).sendCommand('0');
> }
> type: script.ScriptAction
> 
> ```

> **Rule: Reset Boost Mode**
>
> ```javascript
> configuration: {}
> triggers:
> - id: "1"
> configuration:
> groupName: boostmode
> state: ON
> type: core.GroupStateUpdateTrigger
> conditions: []
> actions:
> - inputs: {}
> id: "2"
> configuration:
> type: application/javascript
> script: >
> var trigger_room, root_room, Boost_Item_Name;
> 
> trigger_room = event.itemName;
> 
> root_room = items.getItem(trigger_room).name.slice(0, (items.getItem(trigger_room).name.indexOf('_') + 1) - 1);
> 
> Boost_Item_Name = String(root_room) + '_Heating_BoostMode';
> 
> if (cache.private.exists('Boost_Off_Timer') === false || cache.private.get('Boost_Off_Timer').hasTerminated()) {
> cache.private.put('Boost_Off_Timer', actions.ScriptExecution.createTimer('Boost_Off_Timer', time.ZonedDateTime.now().plusMinutes(30), function () {
> items.getItem(Boost_Item_Name).postUpdate('OFF');
> cache.private.remove('Boost_Off_Timer');
> }));
> };
> type: script.ScriptAction
> 
> ```

> **Rule: Reset Temperature Override**
>
> ```javascript
> configuration: {}
> triggers:
> - id: "1"
> configuration:
> groupName: override
> type: core.GroupStateChangeTrigger
> conditions: []
> actions:
> - inputs: {}
> id: "2"
> configuration:
> type: application/javascript
> script: >
> var trigger_room, root_room, Offset_Item_Name;
> 
> trigger_room = event.itemName;
> 
> root_room = items.getItem(trigger_room).name.slice(0, (items.getItem(trigger_room).name.indexOf('_') + 1) - 1);
> 
> Offset_Item_Name = String(root_room) + '_Heating_Offset';
> 
> if (cache.private.exists('Offset_Off_Timer') === false || cache.private.get('Offset_Off_Timer').hasTerminated()) {
> cache.private.put('Offset_Off_Timer', actions.ScriptExecution.createTimer('Offset_Off_Timer', time.ZonedDateTime.now().plusHours(3), function () {
> items.getItem(Offset_Item_Name).postUpdate('0');
> cache.private.remove('Offset_Off_Timer');
> }));
> };
> type: script.ScriptAction
> 
> ```

> **Rule: Open Windows**
>
> ```javascript
> configuration: {}
> triggers:
> - id: "1"
> configuration:
> groupName: windowsensor
> type: core.GroupStateChangeTrigger
> conditions: []
> actions:
> - inputs: {}
> id: "2"
> configuration:
> type: application/javascript
> script: >
> var triggered_window_group, root_room, Heating_WindowSuspension;
> 
> console.info('-----------------------------------');
> 
> console.info('Window opened / closed - initiating update of Heating_WindowSuspension ');
> 
> triggered_window_group = event.itemName;
> 
> root_room = triggered_window_group.slice(0, (triggered_window_group.indexOf('_') + 1) - 1);
> 
> Heating_WindowSuspension = String(root_room) + '_Heating_WindowSuspension';
> 
> if (items.getItem(triggered_window_group).state != '0') {
> items.getItem(Heating_WindowSuspension).sendCommand('ON');
> console.info((['Window(s) open in room: ',root_room,'Set window Suspension switch to ON'].join('')));
> } else {
> items.getItem(Heating_WindowSuspension).sendCommand('OFF');
> console.info((['Window(s) closed in room: ',root_room,'Set Window Suspension Switch to OFF'].join('')));
> }
> type: script.ScriptAction
> 
> ```

> **Rule Away Mode**
>
> ```javascript
> configuration: {}
> triggers:
> - id: "1"
> configuration:
> groupName: away
> type: core.GroupStateChangeTrigger
> conditions: []
> actions:
> - inputs: {}
> id: "2"
> configuration:
> 
> type: application/javascript
> script: >
> var item_name_triggered, Count_Away, root_room, awaymode_switch,
> member_of_triggering_group;
> 
> item_name_triggered = event.itemName;
> 
> Count_Away = 0;
> 
> root_room = items.getItem(item_name_triggered).name.slice(0, (items.getItem(item_name_triggered).name.indexOf('_') + 1) - 1);
> 
> awaymode_switch = String(root_room) + '_Heating_AwaySwitch';
> 
> var member_of_triggering_group_list = items.getItem(item_name_triggered).members;
> 
> for (var member_of_triggering_group_index in member_of_triggering_group_list) {
> member_of_triggering_group = member_of_triggering_group_list[member_of_triggering_group_index];
> Count_Away = Count_Away + 1;
> }
> 
> if (items.getItem(item_name_triggered).state == Count_Away) {
> items.getItem(awaymode_switch).sendCommand('ON');
> } else {
> items.getItem(awaymode_switch).sendCommand('OFF');
> }
> type: script.ScriptAction
> 
> ```

> **Widget: Heating Settings Room**
>
> ```yaml
> uid: Heizungseinstellungen_Raum_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: Sep 20, 2023, 1:22:02 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: -25px
> slots:
> default:
> - component: f7-col
> config:
> width: 95
> slots:
> default:
> - component: oh-icon
> config:
> align: right
> icon: oh:away.svg
> visible: =(items[props.root_item.split('_')[0] + '_Heating_AwaySwitch'].state == 'ON')? true:false
> width: 32px
> - component: f7-col
> config:
> width: 5
> slots:
> default:
> - component: oh-icon
> config:
> align: right
> icon: oh:window-false.svg
> visible: =(items[props.root_item.split('_')[0] + '_Heating_WindowSuspension'].state == 'ON')? true:false
> width: 32px
> - 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: Heizungszone aktiv
> - 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: Aktuelle Temperatur
> - 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: Zieltemperatur
> - 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: 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: Automatisch
> - 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: Statisch
> - 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: Aktives Profil
> - 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 == 'Abwesend' ? '#919191' : ( items[props.root_item.split('_')[0] + '_Heating_AutomationProfileActive'].state == 'Aus' ? '#00ACB5' : ( items[props.root_item.split('_')[0] + '_Heating_AutomationProfileActive'].state == 'Boost' ? '#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: Montag - Freitag
> - 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:Auswahl_Heizungsprofile
> 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: Samstag - Sonntag
> - 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:Auswahl_Heizungsprofile
> 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 == 'Abwesend' ? '#919191' : ( items[props.root_item.split('_')[0] + '_Heating_AutomationProfileAlternate'].state == 'Aus' ? '#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:Profilgraph
> config:
> profilecore: =items[props.root_item.split('_')[0] + '_Heating_AutomationProfileActiveID'].state
> 
> ```

> **Widget: Modify Temperature per Room**
>
> ```yaml
> uid: Heizungsprofil_Einstellungen
> tags: []
> props:
> parameters:
> - context: item
> description: Profile
> label: Profil-Gruppe
> name: profile
> required: true
> type: TEXT
> parameterGroups: []
> timestamp: Sep 19, 2023, 8:37:38 PM
> component: f7-card
> config:
> style:
> --f7-range-bar-active-bg-color: "#ffb17d"
> --f7-range-bar-bg-color: "#fae2d2"
> --f7-range-bar-border-radius: 1em
> --f7-range-bar-size: 0.6em
> position: relative
> title: ="Feineinstellung Heizungsprofil für " + items[props.profile + "_Name"].state
> width: 100%
> slots:
> default:
> - component: f7-row
> slots:
> default:
> - component: f7-col
> config: {}
> slots:
> default:
> - component: Label
> config:
> style:
> font-size: 12pt
> padding: 10px
> - component: f7-col
> config:
> style:
> width: 5%
> slots:
> default:
> - component: oh-button
> config:
> action: popup
> actionModal: widget:Heizeinstellungen_Profil_Popup
> actionModalConfig:
> profile: =props.profile
> color: black
> iconF7: gear_alt_fill
> large: true
> - component: f7-block
> config:
> style:
> display: flex
> flex-direction: row
> height: 230px
> left: 2.5%
> top: 0%
> width: 100%
> slots:
> default:
> - component: oh-repeater
> config:
> for: item
> fragment: true
> groupItem: =props.profile + '_val'
> sourceType: itemsInGroup
> slots:
> default:
> - component: f7-col
> config:
> style:
> height: 210px
> width: 100%
> slots:
> default:
> - component: f7-row
> slots:
> default:
> - component: oh-slider
> config:
> item: =loop.item.name
> label: true
> max: 30
> min: 5
> releaseOnly: true
> scale: "=(loop.item.label == '00') ? true : false"
> scaleSteps: 5
> step: 0.5
> style:
> --f7-range-bar-active-bg-color: "=(dayjs().format('HH') == loop.item.label) ? '#298a29' : '' "
> height: 150px
> title: =loop.item.label
> vertical: 1
> - component: f7-row
> slots:
> default:
> - component: Label
> config:
> style:
> position: relative
> top: 35px
> transform: rotate(-70deg)
> width: 1px
> text: =loop.item.label + ":00"
> visible: true
> - component: Label
> config:
> style:
> height: 10px
> left: 0px
> position: absolute
> top: 205px
> transform: rotate(-90deg)
> width: 1px
> text: Temperatur
> 
> ```

---

<div class="post-metadata">

**Author:** ![HolBaum5](https://community.openhab.org/user_avatar/community.openhab.org/holbaum5/32/110068_2.png) [@HolBaum5](https://community.openhab.org/u/HolBaum5)\
**Post date:** [November 10, 2023, 6:52am UTC](https://community.openhab.org/t/the-ultimate-full-week-heating-thermostat-control-widget-and-ruleset-using-time-profiles/144122/14 "2023-11-10T06:52:25Z")

</div>

@wAvE @Marmoset_Threat  
Thanks for sharing and creating!

---

<div class="post-metadata">

**Author:** ![HolBaum5](https://community.openhab.org/user_avatar/community.openhab.org/holbaum5/32/110068_2.png) [@HolBaum5](https://community.openhab.org/u/HolBaum5)\
**Post date:** [November 15, 2023, 11:58am UTC](https://community.openhab.org/t/the-ultimate-full-week-heating-thermostat-control-widget-and-ruleset-using-time-profiles/144122/15 "2023-11-15T11:58:26Z")

</div>

Thanks again to @Marmoset_Threat and @wAvE for the great work.

I am under openHAB 4.0.4

Here are a few more comments from me on the post by @wAvE.

It is important to note that the items for the heater thermostat must be adjusted in the rule. I have put my rule **Heating Control** (Rule: **Regular Heating Check** and Rule: **Heating Input Trigger** combined) below again. In the rule states in the comments which items of the thermostat and must be adjusted. I have also removed the _Kindersicherung_ as I don’t have it and I still have to look at the calibration, but I don’t think it works with my heater thermostat.

Enclosed is also the page ( **Page Heating Control** ) in my openHAB as an example, because at the beginning it was not clear to me where and how to call the widgets. Here you have to adapt the settings to your items. My shortened items file is also attached.

> **Rule Heating Control**
>
> ```yaml
> configuration: {}
> triggers:
> - id: "1"
> configuration:
> cronExpression: 0 0/10 * * * ? *
> type: timer.GenericCronTrigger
> - id: "3"
> configuration:
> command: ON
> itemName: Trigger_HeatAutomation_Script
> type: core.ItemCommandTrigger
> - id: "4"
> configuration:
> itemName: Global_Vacation_Override
> type: core.ItemStateChangeTrigger
> - id: "5"
> configuration:
> groupName: Heating_Input
> type: core.GroupStateChangeTrigger
> conditions: []
> actions:
> - inputs: {}
> id: "2"
> label: JavaScript
> description: JavaScript
> configuration:
> type: application/javascript
> script: >
> /*
> * Version Description Date
> * ---------|------------------------------------------------|---------
> * 1.0 Original
> * 1.1 Added Quantity types 22.08.24
> *
> * Note:
> * The current temperature of the room must be added manually to each _temperature group.
> */
> 
> var activeProfile;
> 
> var automationMode;
> 
> var heatingOffsetValue;
> 
> var itemCurrentSetPoint;
> 
> var itemHeatingActive;
> 
> var itemHeatingAutoMode;
> 
> var itemHeatingAutoProfileActive;
> 
> var itemHeatingAutoProfileActiveID;
> 
> var itemHeatingAutoProfileAlternate;
> 
> var itemHeatingAutoProfilePrimary;
> 
> var itemHeatingAwaySwitch;
> 
> var itemHeatingBoostMode;
> 
> var itemHeatingOffset;
> 
> var itemHeatingWindowSuspension;
> 
> var itemSystemMode;
> 
> var itemTemperature;
> 
> var itemTemperatureAtThermostat;
> 
> var itemTemperatureCalibration;
> 
> var itemTemperatureTarget;
> 
> var memberOfMasterHeatingSwitchGroup; 
> 
> var memberOfProfileList;
> 
> var log =
> items.Heating_AutomationLogging.state;
> 
> var loopSingleRoom; 
> 
> var loopTroughAllRooms; 
> 
> var nameTriggeringItem;
> 
> var profileCalculated;
> 
> var profileGroup;
> 
> var profileName;
> 
> var rootRoom;
> 
> var temperatureOffset;
> 
> var temperatureOffsetRounded;
> 
> var temperaturePulledToTarget;
> 
> var temperatureRoomCurrent;
> 
> var temperatureRoomCurrentUpdated;
> 
> var temperatureSetPointCurrent;
> 
> var temperatureSetPointUpdated;
> 
> var temperatureTarget;
> 
> var thread = Java.type('java.lang.Thread');
> 
> var weekend;
> 
> if (log == 'ON') console.info ('this.event.type: ' + this.event.type )
> 
> switch(this.event.type) {
> case 'TimerEvent': // Cron
> case 'ItemCommandEvent': // Trigger_HeatAutomation_Script
> loopTroughAllRooms = true;
> break;
> case 'ItemStateChangedEvent':
> if (event.itemName == 'Global_Vacation_Override') {
> loopTroughAllRooms = true;
> } else {
> loopSingleRoom = true;
> }
> break;
> default:
> loopTroughAllRooms = true;
> break;
> } 
>           
> 
> if (loopTroughAllRooms) {
> items.getItem('Trigger_HeatAutomation_Script').sendCommand('OFF');
> 
> thread.sleep(2000);
> 
> var memberOfMasterHeatingSwitchGroupList = items.getItem('Master_Heating_Switch').members;
> for (var memberOfMasterHeatingSwitchGroupIndex in memberOfMasterHeatingSwitchGroupList) {
> memberOfMasterHeatingSwitchGroup = memberOfMasterHeatingSwitchGroupList[memberOfMasterHeatingSwitchGroupIndex];
> rootRoom = memberOfMasterHeatingSwitchGroup.name.slice(0, (memberOfMasterHeatingSwitchGroup.name.indexOf('_') + 1) - 1).toString();
> mainHeatingControl();
> }
> } else if (loopSingleRoom) {
> nameTriggeringItem = event.itemName;
> rootRoom = nameTriggeringItem.slice(0, (nameTriggeringItem.indexOf('_') + 1) - 1).toString();
> mainHeatingControl();  
> }
> 
>           
> /*------------------------------------------------*/
> 
> /* Functions */
> 
> /*------------------------------------------------*/
> 
> function mainHeatingControl() {  
> itemCurrentSetPoint = rootRoom + 'Heizkorperthermostat_Setpointheat';
> itemHeatingActive = rootRoom + '_Heating_Active';
> itemHeatingAutoMode = rootRoom + '_Heating_AutomationMode';
> itemHeatingAutoProfileActive = rootRoom + '_Heating_AutomationProfileActive';
> itemHeatingAutoProfileActiveID = rootRoom + '_Heating_AutomationProfileActiveID';
> itemHeatingAutoProfileAlternate = rootRoom + '_Heating_AutomationProfileAlternate';
> itemHeatingAutoProfilePrimary = rootRoom + '_Heating_AutomationProfilePrimary';
> itemHeatingAwaySwitch = rootRoom + '_Heating_AwaySwitch';
> itemHeatingBoostMode = rootRoom + '_Heating_BoostMode';
> itemHeatingOffset = rootRoom + '_Heating_Offset';
> itemHeatingWindowSuspension = rootRoom + '_Heating_WindowSuspension';
> itemSystemMode = rootRoom + 'Heizkorperthermostat_Thermostatmode'; // adjust to set your Heater thermostat
> itemTemperature = rootRoom + '_Temperature'; // add to this group your temperature room items
> itemTemperatureAtThermostat = rootRoom + 'Heizkorperthermostat_Currenttemperature'; // adjust to set your Heater thermostat
> //itemTemperatureCalibration = rootRoom +
> 'Heizkorperthermostat_Temperature_Calibration';// adjust to set your
> Heater thermostat
> 
> if (items.getItem(itemHeatingActive).state == 'NULL') {
> items.getItem(itemHeatingActive).postUpdate('OFF');
> }
> if (items.getItem(itemHeatingAutoMode).state == 'NULL') {
> items.getItem(itemHeatingAutoMode).postUpdate('Static');
> }
> if (items.getItem(itemHeatingAutoProfilePrimary).state == 'NULL') {
> items.getItem(itemHeatingAutoProfilePrimary).postUpdate('Abwesend');
> if (items.getItem(itemHeatingAutoProfileAlternate).state == 'NULL') {
> items.getItem(itemHeatingAutoProfileAlternate).postUpdate('Abwesend');
> }
> }
> if (items.getItem(itemHeatingOffset).state == 'NULL') {
> items.getItem(itemHeatingOffset).postUpdate(0);
> }
> if (items.getItem(itemHeatingWindowSuspension).state == 'NULL') {
> items.getItem(itemHeatingWindowSuspension).postUpdate('OFF');
> }
> if (items.getItem(itemHeatingBoostMode).state == 'NULL') {
> items.getItem(itemHeatingBoostMode).postUpdate('OFF');
> }
> automationMode = items.getItem(itemHeatingAutoMode).state;
> if (automationMode != 'Auto' && automationMode != 'Static') {
> items.getItem(itemHeatingAutoMode).postUpdate('Static');
> } else {
> }
> if (items.getItem('Global_Vacation_Override').state == 'ON') {
> items.getItem(itemHeatingAutoProfileActive).postUpdate('Away');
> activeProfile = items.getItem(itemHeatingAutoProfileActive).state;
> } else {
> if (items.getItem(itemHeatingActive).state == 'ON') {
> if (items.getItem(itemHeatingWindowSuspension).state == 'ON') {
> items.getItem(itemHeatingAutoProfileActive).postUpdate('Aus');
> activeProfile = items.getItem(itemHeatingAutoProfileActive).state;
> } else {
> if (items.getItem(itemHeatingAwaySwitch).state == 'ON') {
> items.getItem(itemHeatingAutoProfileActive).postUpdate('Away');
> activeProfile = items.getItem(itemHeatingAutoProfileActive).state;
> } else {
> if (items.getItem(itemHeatingBoostMode).state == 'ON') {
> items.getItem(itemHeatingAutoProfileActive).postUpdate('Boost');
> activeProfile = items.getItem(itemHeatingAutoProfileActive).state;
> } else {
> if (items.getItem(itemHeatingAutoMode).state == 'Static') {
> items.getItem(itemHeatingAutoProfileActive).postUpdate(items.getItem(itemHeatingAutoProfilePrimary).state);
> activeProfile = items.getItem(itemHeatingAutoProfilePrimary).state;
> } else {
> if (items.getItem(itemHeatingAutoMode).state == 'Auto') {
> weekend = ((time.ZonedDateTime.now()).dayOfWeek().value());
> if (weekend < 6) {
> items.getItem(itemHeatingAutoProfileActive).postUpdate(items.getItem(itemHeatingAutoProfilePrimary).state);
> activeProfile = items.getItem(itemHeatingAutoProfilePrimary).state;
> } else {
> items.getItem(itemHeatingAutoProfileActive).postUpdate(items.getItem(itemHeatingAutoProfileAlternate).state);
> activeProfile = items.getItem(itemHeatingAutoProfileAlternate).state;
> }
> }
> }
> }
> }
> }
> } else {
> items.getItem(itemHeatingAutoProfileActive).postUpdate('Aus');
> activeProfile = items.getItem(itemHeatingAutoProfileActive).state;
> }
> }
> var memberOfProfileListList = items.getItem('ProfileList').members;
> for (var memberOfProfileListIndex in memberOfProfileListList) {
> memberOfProfileList = memberOfProfileListList[memberOfProfileListIndex];
> profileGroup = [memberOfProfileList.name,'_','Name'].join('');
> profileName = items.getItem(profileGroup).state;
> if (profileName == activeProfile) {
> items.getItem(itemHeatingAutoProfileActiveID).sendCommand(memberOfProfileList.name);
> profileCalculated = [memberOfProfileList.name,'_','Calculated'].join('');
> itemTemperatureTarget = rootRoom + '_Heating_Target';
> temperaturePulledToTarget = items.getItem(profileCalculated).state;
> itemHeatingOffset = rootRoom + '_Heating_Offset';
> temperatureOffsetValue = items.getItem(itemHeatingOffset).state;
> temperatureTarget = temperaturePulledToTarget + temperatureOffsetValue;
> items.getItem(itemTemperatureTarget).sendCommand(temperatureTarget);
> }
> }
> 
> if (log == 'ON') console.info('------------------------------------------------------------------------');
> if (log == 'ON') console.info('Room: ' + rootRoom);
> if (log == 'ON') console.info('------------------------------------------------------------------------');
> 
> if (activeProfile == 'Aus') {
> // OFF = 0, HEAT = 1, HEAT_ECON = 2, FULL_POWER = 3
> if (items.getItem(itemSystemMode).state == '1' || items.getItem(itemSystemMode).state == '2' || items.getItem(itemSystemMode).state == '3') {
> items.getItem(itemSystemMode).sendCommand('0');
> }
> if (log == 'ON') console.info('No active profile');
> } else if (activeProfile != 'Aus') {
> if (items.getItem(itemSystemMode).state != '1') {
> items.getItem(itemSystemMode).sendCommand('1');
> }
> if (log == 'ON') console.info('Check if Current SetPoint of Thermostat matches the Target temperature and change it if needed');
> temperatureSetPointCurrent = (Quantity(items.getItem(itemCurrentSetPoint).state));
> temperatureSetPointUpdated = (Quantity(items.getItem(itemTemperatureTarget).state));
> if (log == 'ON') console.info('Current Setpoint is: ' + (Quantity(temperatureSetPointCurrent)));
> if (log == 'ON') console.info('Current Target Temp is: ' + (Quantity(temperatureSetPointUpdated)));
> if (Quantity(temperatureSetPointCurrent).equal(Quantity(temperatureSetPointUpdated))) {  
> if (log == 'ON') console.info('Thermostat temperature already set to target temperature - do nothing');
> } else {
> if (log == 'ON') console.info('Thermostat temperature not matching target temperature - Change temperature: ' + (Quantity(temperatureSetPointUpdated)));
> items.getItem(itemCurrentSetPoint).sendCommand(temperatureSetPointUpdated);
> }
> if (log == 'ON') console.info('Start Thermostat Calibration Script for ' + rootRoom);
> if (log == 'ON') console.info('Adding 0.5 °C to CurrentTemperature for less sensivity');
> temperatureRoomCurrent = (Quantity(items.getItem(itemTemperature).state));
> temperatureRoomCurrentUpdated = (Quantity(temperatureRoomCurrent).add(Quantity('0.5' + '°C')));
>             
> if (Quantity(temperatureRoomCurrentUpdated).lessThan(Quantity(temperatureSetPointUpdated))) {
> if (log == 'ON') console.info('Room Temperature Lower than temperatureTarget modify Temperature Offset to adjust it');
> if (log == 'ON') console.info('Calculate needed Offset to match Sensor Temperature and Thermostat Temperature');
> if (log == 'ON') console.info('Check if Thermostat Temp is under Room Temp');
> if (log == 'ON') console.info('Room Temperature is: ' + (Quantity(items.getItem(itemTemperature).state)));
> if (log == 'ON') console.info('Thermostat Temperature is: ' + (Quantity(items.getItem(itemTemperatureAtThermostat).state)));
> temperatureOffset = (Quantity(items.getItem(itemTemperature).state).subtract(Quantity(items.getItem(itemTemperatureAtThermostat).state)));
> temperatureOffsetRounded = Math.round(temperatureOffset);
> if (temperatureOffset < -6) {
> temperatureOffsetRounded = -6;
> } else if (temperatureOffset > 6) {
> temperatureOffsetRounded = 6;
> }
> //items.getItem(itemTemperatureCalibration).sendCommand(temperatureOffsetRounded);
> if (log == 'ON') console.info('Offset Calculated and updated to: ' + (Quantity(temperatureOffsetRounded)));
> if (log == 'ON') console.info('Room Temperature Higher than Target temperature, reset Temperature Calibration');
> //console.info('Current Temperature Calibration is: ' + items.getItem(itemTemperatureCalibration).state);
> /*} else if (temperatureRoomCurrentUpdated > Number.parseFloat(items.getItem(itemCurrentSetPoint).state) && items.getItem(itemTemperatureCalibration).state != '0') {
> console.info('Room Temperature Higher than Target temperature, reset Temperature Calibration');
> items.getItem(itemTemperatureCalibration).sendCommand('0');*/
> } else {
> if (log == 'ON') console.info('Temperature already properly calibrated - do nothing');
> }
> }
> }
> type: script.ScriptAction
> 
> ```

> **Items Heating Control**
>
> ```yaml
> //Heating Control Items (Virtual)
> //Global Groups
> Switch Global_Vacation_Override "Heizung aus / Sommermodus / Away Modus"
> Group windowsensor
> Group:Number:COUNT(ON) boostmode
> Switch Trigger_HeatAutomation_Script
> Switch Heating_AutomationLogging "Heating Automation Logging On/Off" 
> Group away
> Group override
> 
> //Heat Specific Groups
> Group Heating_Input
> Group Master_Heating_Switch 
> 
> //Heating items for Schlafzimmer
> Group Heating_Schlafzimmer (Schlafzimmer)
> 
> Switch Schlafzimmer_Heating_Active "Heating Active" (Master_Heating_Switch, Heating_Schlafzimmer, Heating_Input)  
> Number:Temperature Schlafzimmer_Heating_Offset "Heating Offset" (Heating_Schlafzimmer, Heating_Input, override)  
> Number:Temperature Schlafzimmer_Heating_Target "Heating Target" (Heating_Schlafzimmer, Heating_Input)  
> String Schlafzimmer_Heating_AutomationMode "Heating Automation Mode" (Heating_Schlafzimmer, Heating_Input)  
> String Schlafzimmer_Heating_AutomationProfilePrimary "Heating Automation Primary Profile" (Heating_Schlafzimmer, Heating_Input)  
> String Schlafzimmer_Heating_AutomationProfileAlternate "Heating Automation Alternate Profile" (Heating_Schlafzimmer, Heating_Input)  
> String Schlafzimmer_Heating_AutomationProfileActive "Heating Automation Active Profile" (Heating_Schlafzimmer, Heating_Input)  
> String Schlafzimmer_Heating_AutomationProfileActiveID "Heating Automation Active Profile ID" (Heating_Schlafzimmer)  
> Switch Schlafzimmer_Heating_BoostMode "Heating Boost Mode" (Heating_Schlafzimmer, boostmode, Heating_Input)  
> Switch Schlafzimmer_Heating_WindowSuspension "Heating Window Suspension" (Heating_Schlafzimmer, Heating_Input)
> Group:Number:Temperature:AVG Schlafzimmer_Temperature "Average Temperature" <temperature> (Heating_Schlafzimmer, Heating_Input)  
> Group:Number:COUNT(FALSE) Schlafzimmer_Windowsensor "Anzahl offene Fenster" (Heating_Schlafzimmer, windowsensor, Heating_Input) 
> Group:Number:COUNT(OFF) Schlafzimmer_Away "Person1 / Person2 Away" (Heating_Schlafzimmer, away, Heating_Input)
> Switch Schlafzimmer_Heating_AwaySwitch "Schlafzimmer Away Switch" (Heating_Schlafzimmer, Heating_Input)
> 
> ```

> **Page Heating Control**
>
> ```yaml
> config:
> label: Heizungsteuerung
> sidebar: true
> order: "40"
> blocks:
> - component: oh-block
> config: {}
> slots:
> default:
> - component: oh-grid-row
> config: {}
> slots:
> default:
> - component: oh-grid-col
> config: {}
> slots:
> default:
> - component: oh-list-card
> config:
> accordionList: false
> mediaList: true
> slots:
> default:
> - component: oh-list-item
> config:
> title: Temperaturen
> - component: oh-label-item
> config:
> action: popup
> actionModal: widget:Heizungseinstellungen_Raum_Popup
> actionModalConfig:
> root_item: Schlafzimmer_Heating_Active
> icon: temperature
> title: Schlafzimmer
> item: SchlafzimmerHeizkorperthermostat_Currenttemperature
> - component: oh-grid-row
> config: {}
> slots:
> default:
> - component: oh-grid-col
> config: {}
> slots:
> default:
> - component: oh-list-card
> config: {}
> slots:
> default:
> - component: oh-list-item
> config:
> title: Einstellungen
> - component: oh-label-item
> config:
> icon: oh:switch
> iconUseState: true
> item: Schlafzimmer_Heating_Active
> title: Heizung Schalfzimmer
> - component: oh-label-item
> config:
> action: popup
> actionModal: widget:Liste_Heizungsprofile
> icon: temperature
> title: Einstellungen Heizungsprofile
> - component: oh-grid-row
> config: {}
> slots:
> default:
> - component: oh-grid-col
> config: {}
> slots:
> default:
> - component: oh-list-card
> config: {}
> slots:
> default:
> - component: oh-list-item
> config:
> title: Aktivieren des Logging
> - component: oh-toggle-item
> config:
> icon: oh:switch
> item: Heating_AutomationLogging
> title: Logging
> - component: oh-grid-row
> config: {}
> slots:
> default: []
> masonry: null
> grid: null
> canvas: null
> 
> ```

> **Rule Heating Control Open Windows**
>
> ```php
> configuration: {}
> triggers:
> - id: "2"
> configuration:
> groupName: windowsensor
> type: core.GroupStateChangeTrigger
> conditions: []
> actions:
> - inputs: {}
> id: "3"
> label: Blockly
> description: Blockly
> configuration:
> 
> ```
> 
> ![image](https://community-openhab-org.s3.dualstack.eu-central-1.amazonaws.com/original/3X/6/a/6a296be28f88a749ac59d22755c40b2ae88e0465.png)

---

<div class="post-metadata">

**Author:** ![pking](https://community.openhab.org/user_avatar/community.openhab.org/pking/32/39673_2.png) [@pking](https://community.openhab.org/u/pking)\
**Post date:** [April 23, 2024, 7:51am UTC](https://community.openhab.org/t/the-ultimate-full-week-heating-thermostat-control-widget-and-ruleset-using-time-profiles/144122/16 "2024-04-23T07:51:19Z")

</div>

Hi @HolBaum5 is this your full setup working with in OH4 or just the changes you did to the full logic from @wAvE?

Also the items you still create it \*.items file, correct?

---

<div class="post-metadata">

**Author:** ![HolBaum5](https://community.openhab.org/user_avatar/community.openhab.org/holbaum5/32/110068_2.png) [@HolBaum5](https://community.openhab.org/u/HolBaum5)\
**Post date:** [April 23, 2024, 5:07pm UTC](https://community.openhab.org/t/the-ultimate-full-week-heating-thermostat-control-widget-and-ruleset-using-time-profiles/144122/17 "2024-04-23T17:07:42Z")

</div>

Hello @pking,

I am currently on 4.1.2 and everything is running fine.

You need everything that @wAvE has described (I have only summarized the two rules), including the profiles, items and widgets.

For the profile items I have created a \*.item file for the sake of simplicity.

---

<div class="post-metadata">

**Author:** ![pking](https://community.openhab.org/user_avatar/community.openhab.org/pking/32/39673_2.png) [@pking](https://community.openhab.org/u/pking)\
**Post date:** [April 26, 2024, 4:50am UTC](https://community.openhab.org/t/the-ultimate-full-week-heating-thermostat-control-widget-and-ruleset-using-time-profiles/144122/18 "2024-04-26T04:50:04Z")

</div>

Hi @HolBaum5 ,thanks for the feedback.  
Understood. But would you say it is easier to install with your combined setup plus the remaining from @wAvE or ratherstart off with the initall setup from @wAvE ?

---

<div class="post-metadata">

**Author:** ![HolBaum5](https://community.openhab.org/user_avatar/community.openhab.org/holbaum5/32/110068_2.png) [@HolBaum5](https://community.openhab.org/u/HolBaum5)\
**Post date:** [April 26, 2024, 3:53pm UTC](https://community.openhab.org/t/the-ultimate-full-week-heating-thermostat-control-widget-and-ruleset-using-time-profiles/144122/19 "2024-04-26T15:53:34Z")

</div>

Follow the instructions from wAvE and follow my instructions then it should work.

---

<div class="post-metadata">

**Author:** ![pking](https://community.openhab.org/user_avatar/community.openhab.org/pking/32/39673_2.png) [@pking](https://community.openhab.org/u/pking)\
**Post date:** [April 29, 2024, 10:06am UTC](https://community.openhab.org/t/the-ultimate-full-week-heating-thermostat-control-widget-and-ruleset-using-time-profiles/144122/20 "2024-04-29T10:06:40Z")

</div>

Thanks @HolBaum5 . I managed to install but ending up similarily with the name errors. See below:

 ![image](https://community-openhab-org.s3.dualstack.eu-central-1.amazonaws.com/original/3X/9/3/93b116286cddd6ffc14c7556f677c9f60460d3d5.png)

I just cant find in the settings what is wrong. Any ideas.

[Next page](https://community.openhab.org/t/the-ultimate-full-week-heating-thermostat-control-widget-and-ruleset-using-time-profiles/144122.md?page=2)
