End-user configurable, Time-Profile-based control (For Lights, Heating, and more)!

Design Goal:

I wanted to create a system which allows “Profiles” to control or provide input to various devices within my home, notably Lights and Heaters. These Profiles should:

  • Allow one-hour resolution for providing input on a daily schedule
  • Be user-adjustable and easy to understand via widgets in the UI, even to non-programmers
  • Accommodate multiple data types: Percentages, Temperatures, and Booleans
  • Be easy to set up and easy to implement wherever a profile might be needed
  • Utilize only standard rules DSL and items

I’ve made a similar system before, however due to changes with how widgets work, it wasn’t compatible with OH3 – some reworking was required.

Profile Control: Widgets

There are two main widgets that sit at the core of profile adjustment, and a popup for data entry. These are:

  • profile_looper: this allows the adjustment of profiles via sliders
  • profile_looper_popup: this allows users to configure profiles to their liking
  • profile_graph: this displays a graphical representation of a single profile

Basically, a user can first choose an unused profile (empty templates are easy to create via copy-paste) and then they can configure it using the profile_looper widget. This has a simple slider for each time period, and depending on what type it is set to, will allow the user to enter appropriate values. Additionally, by clicking on the gear-icon, profile_looper_popup is shown, which allows the user to do things like change the name, type, and interpolation setting (when active, this interpolates values between, useful for lighting).

profile_looper:

image

profile_looper_popup:

image

profile_graph:

image

Profile Control: Results

The Profile system results in a calculated value which sits in the profile’s accompanying _Calculated item and can, from there, be easily fetched by any other rule, light, thermostat, etc. Below are two examples of this calculated value: on the left, Profile 5 “Workday” is shown and on the right a light-brightness profile is shown which includes interpolation for smooth transitions throughout the day.

The profile calculator also calculates some extra values, like the minimmum and maximum values within a specific profile (which will be useful later) and has the ability to reset and revert the profiles to hard-coded values in the rules file should one wish to do so.

The biggest issue with this system that I’ve come across is that OpenHab gets a bit weak in the knees when opening multiple instances of the profile_looper widget, so much so that opening all 10 configurateable profiles usually “'breaks” the tab of the browser its in. Values can still be adjusted and everything works on that page, however the rest of mainUI in that tab is completly borked and to regain usability, I need to kill the tab and open mainUI again. This can be avoided by only viewing one profile at a time.

Also, it is important to include restoreOnStartup persistence for all profile items! Since many of the items that make this system work are not numbers, I use mapdb to keep all these values. If they are not persisted, your profiles will not survive a restart of OH.

In another post, I’ll show how I use these profiles to control lights and heating, and the accompanying widgets :smiley:

Profile Control: Source Code

YAML Widgets

profile_looper

uid: profile_looper
props:
  parameters:
    - description: Title on top
      label: Title
      name: title
      required: false
      type: TEXT
    - context: item
      description: Profile
      label: profile core item
      name: profilecore
      required: true
      type: TEXT
      filterCriteria:
        - value: Group
          name: type
  parameterGroups: []
timestamp: Dec 22, 2022, 4:26:06 PM
component: f7-card
config:
  style:
    --f7-range-bar-active-bg-color: "#090909"
    --f7-range-bar-bg-color: "#111111"
    --f7-range-bar-border-radius: 1em
    --f7-range-bar-size: 0.6em
    height: 400px
    position: relative
    width: 100%
  title: Profile Adjustment Widget
slots:
  default:
    - component: f7-block
      slots:
        default:
          - component: f7-row
            slots:
              default:
                - component: Label
                  config:
                    style:
                      font-size: 18pt
                      padding: 10px
                    text: =items[props.profilecore + "_Name"].state
                - component: oh-button
                  config:
                    action: popup
                    actionModal: widget:profile_looper_popup
                    actionModalConfig:
                      profile: =props.profilecore
                      title: =props.title
                    color: white
                    iconF7: gear_alt_fill
                    large: true
    - component: f7-block
      config:
        style:
          display: flex
          flex-direction: row
          height: 100%
          left: 30px
          top: 20px
          width: 95%
      slots:
        default:
          - component: oh-repeater
            config:
              filter: loop.item.type == "Number"
              for: item
              fragment: true
              groupItem: =props.profilecore + '_val'
              sourceType: itemsInGroup
            slots:
              default:
                - component: f7-col
                  config:
                    style:
                      height: 200px
                      width: 100%
                  slots:
                    default:
                      - component: f7-row
                        config:
                          visible: "=(items[props.profilecore + '_Type'].state == 'Percentage') ? true : false"
                        slots:
                          default:
                            - component: oh-slider
                              config:
                                item: =loop.item.name
                                label: true
                                max: 100
                                min: 0
                                scale: "=(loop.item.label == '00') ? true : false"
                                scaleSteps: 4
                                step: 1
                                style:
                                  --f7-range-bar-active-bg-color: "=(dayjs().format('HH') == loop.item.label) ? '#FFAF80' : '' "
                                  --f7-range-knob-color: "=(dayjs().format('HH') == loop.item.label) ? '#FF6700' : '' "
                                  height: 200px
                                title: =loop.item.label
                                vertical: 1
                      - component: f7-row
                        config:
                          visible: "=(items[props.profilecore + '_Type'].state == 'Temperature') ? true : false"
                        slots:
                          default:
                            - component: oh-slider
                              config:
                                item: =loop.item.name
                                label: true
                                max: 25
                                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) ? '#FFAF80' : '' "
                                  --f7-range-knob-color: "=(dayjs().format('HH') == loop.item.label) ? '#FF6700' : '' "
                                  height: 200px
                                title: =loop.item.label
                                vertical: 1
                      - component: f7-row
                        config:
                          visible: "=(items[props.profilecore + '_Type'].state == 'Boolean') ? true : false"
                        slots:
                          default:
                            - component: oh-slider
                              config:
                                item: =loop.item.name
                                label: true
                                max: 1
                                min: 0
                                scale: "=(loop.item.label == '00') ? true : false"
                                scaleSteps: 1
                                step: 1
                                style:
                                  --f7-range-bar-active-bg-color: "=(dayjs().format('HH') == loop.item.label) ? '#FFAF80' : '' "
                                  --f7-range-knob-color: "=(dayjs().format('HH') == loop.item.label) ? '#FF6700' : '' "
                                  height: 200px
                                title: =loop.item.label
                                vertical: 1
                      - component: f7-row
                        slots:
                          default:
                            - component: Label
                              config:
                                style:
                                  position: relative
                                  top: 30px
                                  transform: rotate(-70deg)
                                  width: 1px
                                text: =loop.item.label + ":00"
                                visible: true
    - component: Label
      config:
        style:
          height: 1px
          left: 5px
          position: absolute
          top: 210px
          transform: rotate(-90deg)
          width: 1px
        text: =items[props.profilecore + "_Type"].state

profile_looper_popup

uid: profile_looper_popup
tags: []
props:
  parameters:
    - description: Title of the card
      label: Title
      name: title
      required: false
      type: TEXT
    - context: item
      description: Profile to manipulate
      label: Item
      name: profile
      required: false
      type: TEXT
  parameterGroups: []
timestamp: Dec 22, 2022, 4:26:27 PM
component: f7-card
config:
  title: ="Profile settings of " + 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: Profile Name
                - 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
                  slots:
                    default:
                      - component: f7-segmented
                        slots:
                          default:
                            - component: oh-button
                              config:
                                action: command
                                actionCommand: Percentage
                                actionItem: =props.profile + "_Type"
                                active: "=(items[props.profile + '_Type'].state == 'Percentage') ? true : false"
                                outline: true
                                round: true
                                text: Percentage
                            - component: oh-button
                              config:
                                action: command
                                actionCommand: Temperature
                                actionItem: =props.profile + "_Type"
                                active: "=(items[props.profile + '_Type'].state == 'Temperature') ? true : false"
                                outline: true
                                round: true
                                text: Temperature
                            - component: oh-button
                              config:
                                action: command
                                actionCommand: Boolean
                                actionItem: =props.profile + "_Type"
                                active: "=(items[props.profile + '_Type'].state == 'Boolean') ? true : false"
                                outline: true
                                round: true
                                text: Boolean
          - component: f7-row
            slots:
              default:
                - component: f7-col
                  config:
                    style:
                      font-size: 12pt
                      height: 2em
                      margin-top: 0.5em
                      text-align: right
                  slots:
                    default:
                      - component: Label
                        config:
                          text: "Interpolation mode:"
                - component: f7-col
                  config:
                    style:
                      margin-top: 0.7em
                  slots:
                    default:
                      - component: oh-toggle
                        config:
                          item: =props.profile + "_Interpolate"
          - component: f7-row
            slots:
              default:
                - component: f7-col
                  config: {}
                  slots:
                    default:
                      - component: oh-button
                        config:
                          action: command
                          actionCommand: RESET
                          actionItem: =props.profile + '_Command'
                          large: true
                          outline: true
                          style:
                            margin-top: 1em
                          text: Reset Profile?

profile_graph

uid: profile_graph
tags: []
props:
  parameters:
    - context: item
      description: A Profile to graph
      label: Item
      name: profilecore
      required: false
      type: TEXT
  parameterGroups: []
timestamp: Dec 22, 2022, 4:25:51 PM
component: f7-card
config:
  style:
    height: 18em
slots:
  default:
    - component: f7-block
      config:
        style:
          display: flex
          flex-direction: row
          height: 14em
          left: 1.5em
          position: relative
          width: 95%
      slots:
        default:
          - component: oh-repeater
            config:
              filter: loop.item.type == "Number"
              for: item
              fragment: true
              groupItem: =props.profilecore + '_val'
              sourceType: itemsInGroup
            slots:
              default:
                - component: f7-col
                  config:
                    style:
                      height: 14em
                      width: 100%
                  slots:
                    default:
                      - component: f7-row
                        config:
                          style:
                            bottom: 0px
                            height: =loop.item.state / 8 + 'em'
                            position: absolute
                            width: 4.11%
                          visible: "=(items[props.profilecore + '_Type'].state == 'Percentage') ? true : false"
                        slots:
                          default:
                            - component: Label
                              config:
                                style:
                                  background: "=( Number.parseInt(dayjs().format('HH')) > Number.parseInt(loop.item.label)) ? '#E5B710' : ( Number.parseInt(dayjs().format('HH')) == Number.parseInt(loop.item.label)) ? '#AC890C' : '#F2CB40' "
                                  color: black
                                  font-size: 7pt
                                  font-weight: bold
                                  height: 100%
                                  text-align: center
                                  width: 100%
                                text: =loop.item.state
                      - component: f7-row
                        config:
                          style:
                            bottom: 0px
                            height: =(loop.item.state - 13) * 1.3 + 'em'
                            position: absolute
                            width: 4.11%
                          visible: "=(items[props.profilecore + '_Type'].state == 'Temperature') ? true : false"
                        slots:
                          default:
                            - component: Label
                              config:
                                style:
                                  background: "=( Number.parseInt(dayjs().format('HH')) > Number.parseInt(loop.item.label)) ? '#C30F09' : ( Number.parseInt(dayjs().format('HH')) == Number.parseInt(loop.item.label)) ? '#FFFFFF' : '#F53029' "
                                  color: black
                                  font-size: 7pt
                                  font-weight: bold
                                  height: 100%
                                  text-align: center
                                  width: 100%
                                text: =loop.item.state
                      - component: f7-row
                        config:
                          style:
                            bottom: 0px
                            height: =(loop.item.state + 2) * 1 + 'em'
                            position: absolute
                            width: 4.11%
                          visible: "=(items[props.profilecore + '_Type'].state == 'Boolean') ? true : false"
                        slots:
                          default:
                            - component: Label
                              config:
                                style:
                                  background: "=( Number.parseInt(dayjs().format('HH')) > Number.parseInt(loop.item.label)) ? '#43A43D' : ( Number.parseInt(dayjs().format('HH')) == Number.parseInt(loop.item.label)) ? '#4CB944' : '#70C86A' "
                                  color: black
                                  font-size: 9pt
                                  font-weight: bold
                                  height: 100%
                                  text-align: center
                                  width: 100%
                                text: =loop.item.state
                      - component: f7-row
                        slots:
                          default:
                            - component: Label
                              config:
                                style:
                                  position: absolute
                                  top: 16em
                                  transform: rotate(-70deg)
                                  width: 1px
                                text: =loop.item.label + ":00"
                                visible: true
    - component: Label
      config:
        style:
          height: 1px
          left: 5px
          position: absolute
          top: 10em
          transform: rotate(-90deg)
          width: 1px
        text: =items[props.profilecore + "_Type"].state

Virtual Items

.items file template

//GENERAL VIRTUAL ITEMS
Group ProfileCMD "Profile command channel"
Group ProfileList "List of all available profiles"
Group ProfileType "Profile type command channel"
Group ProfileSlider "All Profile Sliders"

Switch ProfileMasterReset "Reset all profiles"

//BEGIN PROFILE BLOCK
Group Away (ProfileList)
Group Away_val (Away)

String Away_Name        (Away) 
String Away_Command     (Away, ProfileCMD) 
String Away_Type        (Away, ProfileType) 
Switch Away_Interpolate (Away) 
Number Away_Calculated  (Away)
Number Away_MaxValue    (Away)
Number Away_MinValue    (Away)

Number Away_0 "00" (Away_val, ProfileSlider) 
Number Away_1 "01" (Away_val, ProfileSlider)
Number Away_2 "02" (Away_val, ProfileSlider)
Number Away_3 "03" (Away_val, ProfileSlider)
Number Away_4 "04" (Away_val, ProfileSlider)
Number Away_5 "05" (Away_val, ProfileSlider)
Number Away_6 "06" (Away_val, ProfileSlider)
Number Away_7 "07" (Away_val, ProfileSlider)
Number Away_8 "08" (Away_val, ProfileSlider)
Number Away_9 "09" (Away_val, ProfileSlider)
Number Away_10 "10" (Away_val, ProfileSlider)
Number Away_11 "11" (Away_val, ProfileSlider)
Number Away_12 "12" (Away_val, ProfileSlider)
Number Away_13 "13" (Away_val, ProfileSlider)
Number Away_14 "14" (Away_val, ProfileSlider)
Number Away_15 "15" (Away_val, ProfileSlider)
Number Away_16 "16" (Away_val, ProfileSlider)
Number Away_17 "17" (Away_val, ProfileSlider)
Number Away_18 "18" (Away_val, ProfileSlider)
Number Away_19 "19" (Away_val, ProfileSlider)
Number Away_20 "20" (Away_val, ProfileSlider)
Number Away_21 "21" (Away_val, ProfileSlider)
Number Away_22 "22" (Away_val, ProfileSlider)
Number Away_23 "23" (Away_val, ProfileSlider)

//BEGIN PROFILE BLOCK
Group Present (ProfileList)
Group Present_val (Present)

String Present_Name        (Present) 
String Present_Command     (Present, ProfileCMD) 
String Present_Type        (Present, ProfileType) 
Switch Present_Interpolate (Present) 
Number Present_Calculated  (Present)
Number Present_MaxValue    (Present)
Number Present_MinValue    (Present)

Number Present_0 "00" (Present_val, ProfileSlider) 
Number Present_1 "01" (Present_val, ProfileSlider)
Number Present_2 "02" (Present_val, ProfileSlider)
Number Present_3 "03" (Present_val, ProfileSlider)
Number Present_4 "04" (Present_val, ProfileSlider)
Number Present_5 "05" (Present_val, ProfileSlider)
Number Present_6 "06" (Present_val, ProfileSlider)
Number Present_7 "07" (Present_val, ProfileSlider)
Number Present_8 "08" (Present_val, ProfileSlider)
Number Present_9 "09" (Present_val, ProfileSlider)
Number Present_10 "10" (Present_val, ProfileSlider)
Number Present_11 "11" (Present_val, ProfileSlider)
Number Present_12 "12" (Present_val, ProfileSlider)
Number Present_13 "13" (Present_val, ProfileSlider)
Number Present_14 "14" (Present_val, ProfileSlider)
Number Present_15 "15" (Present_val, ProfileSlider)
Number Present_16 "16" (Present_val, ProfileSlider)
Number Present_17 "17" (Present_val, ProfileSlider)
Number Present_18 "18" (Present_val, ProfileSlider)
Number Present_19 "19" (Present_val, ProfileSlider)
Number Present_20 "20" (Present_val, ProfileSlider)
Number Present_21 "21" (Present_val, ProfileSlider)
Number Present_22 "22" (Present_val, ProfileSlider)
Number Present_23 "23" (Present_val, ProfileSlider)

//BEGIN PROFILE BLOCK
Group Frostguard (ProfileList)
Group Frostguard_val (Frostguard)

String Frostguard_Name        (Frostguard) 
String Frostguard_Command     (Frostguard, ProfileCMD) 
String Frostguard_Type        (Frostguard, ProfileType) 
Switch Frostguard_Interpolate (Frostguard) 
Number Frostguard_Calculated  (Frostguard)
Number Frostguard_MaxValue    (Frostguard)
Number Frostguard_MinValue    (Frostguard)

Number Frostguard_0 "00" (Frostguard_val, ProfileSlider) 
Number Frostguard_1 "01" (Frostguard_val, ProfileSlider)
Number Frostguard_2 "02" (Frostguard_val, ProfileSlider)
Number Frostguard_3 "03" (Frostguard_val, ProfileSlider)
Number Frostguard_4 "04" (Frostguard_val, ProfileSlider)
Number Frostguard_5 "05" (Frostguard_val, ProfileSlider)
Number Frostguard_6 "06" (Frostguard_val, ProfileSlider)
Number Frostguard_7 "07" (Frostguard_val, ProfileSlider)
Number Frostguard_8 "08" (Frostguard_val, ProfileSlider)
Number Frostguard_9 "09" (Frostguard_val, ProfileSlider)
Number Frostguard_10 "10" (Frostguard_val, ProfileSlider)
Number Frostguard_11 "11" (Frostguard_val, ProfileSlider)
Number Frostguard_12 "12" (Frostguard_val, ProfileSlider)
Number Frostguard_13 "13" (Frostguard_val, ProfileSlider)
Number Frostguard_14 "14" (Frostguard_val, ProfileSlider)
Number Frostguard_15 "15" (Frostguard_val, ProfileSlider)
Number Frostguard_16 "16" (Frostguard_val, ProfileSlider)
Number Frostguard_17 "17" (Frostguard_val, ProfileSlider)
Number Frostguard_18 "18" (Frostguard_val, ProfileSlider)
Number Frostguard_19 "19" (Frostguard_val, ProfileSlider)
Number Frostguard_20 "20" (Frostguard_val, ProfileSlider)
Number Frostguard_21 "21" (Frostguard_val, ProfileSlider)
Number Frostguard_22 "22" (Frostguard_val, ProfileSlider)
Number Frostguard_23 "23" (Frostguard_val, ProfileSlider)

//BEGIN PROFILE BLOCK
Group Profile1 (ProfileList)
Group Profile1_val (Profile1)

String Profile1_Name        (Profile1) 
String Profile1_Command     (Profile1, ProfileCMD) 
String Profile1_Type        (Profile1, ProfileType) 
Switch Profile1_Interpolate (Profile1) 
Number Profile1_Calculated  (Profile1)
Number Profile1_MaxValue    (Profile1)
Number Profile1_MinValue    (Profile1)

Number Profile1_0 "00" (Profile1_val, ProfileSlider) 
Number Profile1_1 "01" (Profile1_val, ProfileSlider)
Number Profile1_2 "02" (Profile1_val, ProfileSlider)
Number Profile1_3 "03" (Profile1_val, ProfileSlider)
Number Profile1_4 "04" (Profile1_val, ProfileSlider)
Number Profile1_5 "05" (Profile1_val, ProfileSlider)
Number Profile1_6 "06" (Profile1_val, ProfileSlider)
Number Profile1_7 "07" (Profile1_val, ProfileSlider)
Number Profile1_8 "08" (Profile1_val, ProfileSlider)
Number Profile1_9 "09" (Profile1_val, ProfileSlider)
Number Profile1_10 "10" (Profile1_val, ProfileSlider)
Number Profile1_11 "11" (Profile1_val, ProfileSlider)
Number Profile1_12 "12" (Profile1_val, ProfileSlider)
Number Profile1_13 "13" (Profile1_val, ProfileSlider)
Number Profile1_14 "14" (Profile1_val, ProfileSlider)
Number Profile1_15 "15" (Profile1_val, ProfileSlider)
Number Profile1_16 "16" (Profile1_val, ProfileSlider)
Number Profile1_17 "17" (Profile1_val, ProfileSlider)
Number Profile1_18 "18" (Profile1_val, ProfileSlider)
Number Profile1_19 "19" (Profile1_val, ProfileSlider)
Number Profile1_20 "20" (Profile1_val, ProfileSlider)
Number Profile1_21 "21" (Profile1_val, ProfileSlider)
Number Profile1_22 "22" (Profile1_val, ProfileSlider)
Number Profile1_23 "23" (Profile1_val, ProfileSlider)

//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)
Number Profile2_2 "02" (Profile2_val, ProfileSlider)
Number Profile2_3 "03" (Profile2_val, ProfileSlider)
Number Profile2_4 "04" (Profile2_val, ProfileSlider)
Number Profile2_5 "05" (Profile2_val, ProfileSlider)
Number Profile2_6 "06" (Profile2_val, ProfileSlider)
Number Profile2_7 "07" (Profile2_val, ProfileSlider)
Number Profile2_8 "08" (Profile2_val, ProfileSlider)
Number Profile2_9 "09" (Profile2_val, ProfileSlider)
Number Profile2_10 "10" (Profile2_val, ProfileSlider)
Number Profile2_11 "11" (Profile2_val, ProfileSlider)
Number Profile2_12 "12" (Profile2_val, ProfileSlider)
Number Profile2_13 "13" (Profile2_val, ProfileSlider)
Number Profile2_14 "14" (Profile2_val, ProfileSlider)
Number Profile2_15 "15" (Profile2_val, ProfileSlider)
Number Profile2_16 "16" (Profile2_val, ProfileSlider)
Number Profile2_17 "17" (Profile2_val, ProfileSlider)
Number Profile2_18 "18" (Profile2_val, ProfileSlider)
Number Profile2_19 "19" (Profile2_val, ProfileSlider)
Number Profile2_20 "20" (Profile2_val, ProfileSlider)
Number Profile2_21 "21" (Profile2_val, ProfileSlider)
Number Profile2_22 "22" (Profile2_val, ProfileSlider)
Number Profile2_23 "23" (Profile2_val, ProfileSlider)

//BEGIN PROFILE BLOCK
Group Profile3 (ProfileList)
Group Profile3_val (Profile3)

String Profile3_Name        (Profile3) 
String Profile3_Command     (Profile3, ProfileCMD) 
String Profile3_Type        (Profile3, ProfileType) 
Switch Profile3_Interpolate (Profile3) 
Number Profile3_Calculated  (Profile3)
Number Profile3_MaxValue    (Profile3)
Number Profile3_MinValue    (Profile3)

Number Profile3_0 "00" (Profile3_val, ProfileSlider) 
Number Profile3_1 "01" (Profile3_val, ProfileSlider)
Number Profile3_2 "02" (Profile3_val, ProfileSlider)
Number Profile3_3 "03" (Profile3_val, ProfileSlider)
Number Profile3_4 "04" (Profile3_val, ProfileSlider)
Number Profile3_5 "05" (Profile3_val, ProfileSlider)
Number Profile3_6 "06" (Profile3_val, ProfileSlider)
Number Profile3_7 "07" (Profile3_val, ProfileSlider)
Number Profile3_8 "08" (Profile3_val, ProfileSlider)
Number Profile3_9 "09" (Profile3_val, ProfileSlider)
Number Profile3_10 "10" (Profile3_val, ProfileSlider)
Number Profile3_11 "11" (Profile3_val, ProfileSlider)
Number Profile3_12 "12" (Profile3_val, ProfileSlider)
Number Profile3_13 "13" (Profile3_val, ProfileSlider)
Number Profile3_14 "14" (Profile3_val, ProfileSlider)
Number Profile3_15 "15" (Profile3_val, ProfileSlider)
Number Profile3_16 "16" (Profile3_val, ProfileSlider)
Number Profile3_17 "17" (Profile3_val, ProfileSlider)
Number Profile3_18 "18" (Profile3_val, ProfileSlider)
Number Profile3_19 "19" (Profile3_val, ProfileSlider)
Number Profile3_20 "20" (Profile3_val, ProfileSlider)
Number Profile3_21 "21" (Profile3_val, ProfileSlider)
Number Profile3_22 "22" (Profile3_val, ProfileSlider)
Number Profile3_23 "23" (Profile3_val, ProfileSlider)

//BEGIN PROFILE BLOCK
Group Profile4 (ProfileList)
Group Profile4_val (Profile4)

String Profile4_Name        (Profile4) 
String Profile4_Command     (Profile4, ProfileCMD) 
String Profile4_Type        (Profile4, ProfileType) 
Switch Profile4_Interpolate (Profile4) 
Number Profile4_Calculated  (Profile4)
Number Profile4_MaxValue    (Profile4)
Number Profile4_MinValue    (Profile4)

Number Profile4_0 "00" (Profile4_val, ProfileSlider) 
Number Profile4_1 "01" (Profile4_val, ProfileSlider)
Number Profile4_2 "02" (Profile4_val, ProfileSlider)
Number Profile4_3 "03" (Profile4_val, ProfileSlider)
Number Profile4_4 "04" (Profile4_val, ProfileSlider)
Number Profile4_5 "05" (Profile4_val, ProfileSlider)
Number Profile4_6 "06" (Profile4_val, ProfileSlider)
Number Profile4_7 "07" (Profile4_val, ProfileSlider)
Number Profile4_8 "08" (Profile4_val, ProfileSlider)
Number Profile4_9 "09" (Profile4_val, ProfileSlider)
Number Profile4_10 "10" (Profile4_val, ProfileSlider)
Number Profile4_11 "11" (Profile4_val, ProfileSlider)
Number Profile4_12 "12" (Profile4_val, ProfileSlider)
Number Profile4_13 "13" (Profile4_val, ProfileSlider)
Number Profile4_14 "14" (Profile4_val, ProfileSlider)
Number Profile4_15 "15" (Profile4_val, ProfileSlider)
Number Profile4_16 "16" (Profile4_val, ProfileSlider)
Number Profile4_17 "17" (Profile4_val, ProfileSlider)
Number Profile4_18 "18" (Profile4_val, ProfileSlider)
Number Profile4_19 "19" (Profile4_val, ProfileSlider)
Number Profile4_20 "20" (Profile4_val, ProfileSlider)
Number Profile4_21 "21" (Profile4_val, ProfileSlider)
Number Profile4_22 "22" (Profile4_val, ProfileSlider)
Number Profile4_23 "23" (Profile4_val, ProfileSlider)

//BEGIN PROFILE BLOCK
Group Profile5 (ProfileList)
Group Profile5_val (Profile5)

String Profile5_Name        (Profile5) 
String Profile5_Command     (Profile5, ProfileCMD) 
String Profile5_Type        (Profile5, ProfileType) 
Switch Profile5_Interpolate (Profile5) 
Number Profile5_Calculated  (Profile5)
Number Profile5_MaxValue    (Profile5)
Number Profile5_MinValue    (Profile5)

Number Profile5_0 "00" (Profile5_val, ProfileSlider) 
Number Profile5_1 "01" (Profile5_val, ProfileSlider)
Number Profile5_2 "02" (Profile5_val, ProfileSlider)
Number Profile5_3 "03" (Profile5_val, ProfileSlider)
Number Profile5_4 "04" (Profile5_val, ProfileSlider)
Number Profile5_5 "05" (Profile5_val, ProfileSlider)
Number Profile5_6 "06" (Profile5_val, ProfileSlider)
Number Profile5_7 "07" (Profile5_val, ProfileSlider)
Number Profile5_8 "08" (Profile5_val, ProfileSlider)
Number Profile5_9 "09" (Profile5_val, ProfileSlider)
Number Profile5_10 "10" (Profile5_val, ProfileSlider)
Number Profile5_11 "11" (Profile5_val, ProfileSlider)
Number Profile5_12 "12" (Profile5_val, ProfileSlider)
Number Profile5_13 "13" (Profile5_val, ProfileSlider)
Number Profile5_14 "14" (Profile5_val, ProfileSlider)
Number Profile5_15 "15" (Profile5_val, ProfileSlider)
Number Profile5_16 "16" (Profile5_val, ProfileSlider)
Number Profile5_17 "17" (Profile5_val, ProfileSlider)
Number Profile5_18 "18" (Profile5_val, ProfileSlider)
Number Profile5_19 "19" (Profile5_val, ProfileSlider)
Number Profile5_20 "20" (Profile5_val, ProfileSlider)
Number Profile5_21 "21" (Profile5_val, ProfileSlider)
Number Profile5_22 "22" (Profile5_val, ProfileSlider)
Number Profile5_23 "23" (Profile5_val, ProfileSlider)

//BEGIN PROFILE BLOCK
Group Profile6 (ProfileList)
Group Profile6_val (Profile6)

String Profile6_Name        (Profile6) 
String Profile6_Command     (Profile6, ProfileCMD) 
String Profile6_Type        (Profile6, ProfileType) 
Switch Profile6_Interpolate (Profile6) 
Number Profile6_Calculated  (Profile6)
Number Profile6_MaxValue    (Profile6)
Number Profile6_MinValue    (Profile6)

Number Profile6_0 "00" (Profile6_val, ProfileSlider) 
Number Profile6_1 "01" (Profile6_val, ProfileSlider)
Number Profile6_2 "02" (Profile6_val, ProfileSlider)
Number Profile6_3 "03" (Profile6_val, ProfileSlider)
Number Profile6_4 "04" (Profile6_val, ProfileSlider)
Number Profile6_5 "05" (Profile6_val, ProfileSlider)
Number Profile6_6 "06" (Profile6_val, ProfileSlider)
Number Profile6_7 "07" (Profile6_val, ProfileSlider)
Number Profile6_8 "08" (Profile6_val, ProfileSlider)
Number Profile6_9 "09" (Profile6_val, ProfileSlider)
Number Profile6_10 "10" (Profile6_val, ProfileSlider)
Number Profile6_11 "11" (Profile6_val, ProfileSlider)
Number Profile6_12 "12" (Profile6_val, ProfileSlider)
Number Profile6_13 "13" (Profile6_val, ProfileSlider)
Number Profile6_14 "14" (Profile6_val, ProfileSlider)
Number Profile6_15 "15" (Profile6_val, ProfileSlider)
Number Profile6_16 "16" (Profile6_val, ProfileSlider)
Number Profile6_17 "17" (Profile6_val, ProfileSlider)
Number Profile6_18 "18" (Profile6_val, ProfileSlider)
Number Profile6_19 "19" (Profile6_val, ProfileSlider)
Number Profile6_20 "20" (Profile6_val, ProfileSlider)
Number Profile6_21 "21" (Profile6_val, ProfileSlider)
Number Profile6_22 "22" (Profile6_val, ProfileSlider)
Number Profile6_23 "23" (Profile6_val, ProfileSlider)

//BEGIN PROFILE BLOCK
Group Profile7 (ProfileList)
Group Profile7_val (Profile7)

String Profile7_Name        (Profile7) 
String Profile7_Command     (Profile7, ProfileCMD) 
String Profile7_Type        (Profile7, ProfileType) 
Switch Profile7_Interpolate (Profile7) 
Number Profile7_Calculated  (Profile7)
Number Profile7_MaxValue    (Profile7)
Number Profile7_MinValue    (Profile7)

Number Profile7_0 "00" (Profile7_val, ProfileSlider) 
Number Profile7_1 "01" (Profile7_val, ProfileSlider)
Number Profile7_2 "02" (Profile7_val, ProfileSlider)
Number Profile7_3 "03" (Profile7_val, ProfileSlider)
Number Profile7_4 "04" (Profile7_val, ProfileSlider)
Number Profile7_5 "05" (Profile7_val, ProfileSlider)
Number Profile7_6 "06" (Profile7_val, ProfileSlider)
Number Profile7_7 "07" (Profile7_val, ProfileSlider)
Number Profile7_8 "08" (Profile7_val, ProfileSlider)
Number Profile7_9 "09" (Profile7_val, ProfileSlider)
Number Profile7_10 "10" (Profile7_val, ProfileSlider)
Number Profile7_11 "11" (Profile7_val, ProfileSlider)
Number Profile7_12 "12" (Profile7_val, ProfileSlider)
Number Profile7_13 "13" (Profile7_val, ProfileSlider)
Number Profile7_14 "14" (Profile7_val, ProfileSlider)
Number Profile7_15 "15" (Profile7_val, ProfileSlider)
Number Profile7_16 "16" (Profile7_val, ProfileSlider)
Number Profile7_17 "17" (Profile7_val, ProfileSlider)
Number Profile7_18 "18" (Profile7_val, ProfileSlider)
Number Profile7_19 "19" (Profile7_val, ProfileSlider)
Number Profile7_20 "20" (Profile7_val, ProfileSlider)
Number Profile7_21 "21" (Profile7_val, ProfileSlider)
Number Profile7_22 "22" (Profile7_val, ProfileSlider)
Number Profile7_23 "23" (Profile7_val, ProfileSlider)

//BEGIN PROFILE BLOCK
Group Profile8 (ProfileList)
Group Profile8_val (Profile8)

String Profile8_Name        (Profile8) 
String Profile8_Command     (Profile8, ProfileCMD) 
String Profile8_Type        (Profile8, ProfileType) 
Switch Profile8_Interpolate (Profile8) 
Number Profile8_Calculated  (Profile8)
Number Profile8_MaxValue    (Profile8)
Number Profile8_MinValue    (Profile8)

Number Profile8_0 "00" (Profile8_val, ProfileSlider) 
Number Profile8_1 "01" (Profile8_val, ProfileSlider)
Number Profile8_2 "02" (Profile8_val, ProfileSlider)
Number Profile8_3 "03" (Profile8_val, ProfileSlider)
Number Profile8_4 "04" (Profile8_val, ProfileSlider)
Number Profile8_5 "05" (Profile8_val, ProfileSlider)
Number Profile8_6 "06" (Profile8_val, ProfileSlider)
Number Profile8_7 "07" (Profile8_val, ProfileSlider)
Number Profile8_8 "08" (Profile8_val, ProfileSlider)
Number Profile8_9 "09" (Profile8_val, ProfileSlider)
Number Profile8_10 "10" (Profile8_val, ProfileSlider)
Number Profile8_11 "11" (Profile8_val, ProfileSlider)
Number Profile8_12 "12" (Profile8_val, ProfileSlider)
Number Profile8_13 "13" (Profile8_val, ProfileSlider)
Number Profile8_14 "14" (Profile8_val, ProfileSlider)
Number Profile8_15 "15" (Profile8_val, ProfileSlider)
Number Profile8_16 "16" (Profile8_val, ProfileSlider)
Number Profile8_17 "17" (Profile8_val, ProfileSlider)
Number Profile8_18 "18" (Profile8_val, ProfileSlider)
Number Profile8_19 "19" (Profile8_val, ProfileSlider)
Number Profile8_20 "20" (Profile8_val, ProfileSlider)
Number Profile8_21 "21" (Profile8_val, ProfileSlider)
Number Profile8_22 "22" (Profile8_val, ProfileSlider)
Number Profile8_23 "23" (Profile8_val, ProfileSlider)

//BEGIN PROFILE BLOCK
Group Profile9 (ProfileList)
Group Profile9_val (Profile9)

String Profile9_Name        (Profile9) 
String Profile9_Command     (Profile9, ProfileCMD) 
String Profile9_Type        (Profile9, ProfileType) 
Switch Profile9_Interpolate (Profile9) 
Number Profile9_Calculated  (Profile9)
Number Profile9_MaxValue    (Profile9)
Number Profile9_MinValue    (Profile9)

Number Profile9_0 "00" (Profile9_val, ProfileSlider) 
Number Profile9_1 "01" (Profile9_val, ProfileSlider)
Number Profile9_2 "02" (Profile9_val, ProfileSlider)
Number Profile9_3 "03" (Profile9_val, ProfileSlider)
Number Profile9_4 "04" (Profile9_val, ProfileSlider)
Number Profile9_5 "05" (Profile9_val, ProfileSlider)
Number Profile9_6 "06" (Profile9_val, ProfileSlider)
Number Profile9_7 "07" (Profile9_val, ProfileSlider)
Number Profile9_8 "08" (Profile9_val, ProfileSlider)
Number Profile9_9 "09" (Profile9_val, ProfileSlider)
Number Profile9_10 "10" (Profile9_val, ProfileSlider)
Number Profile9_11 "11" (Profile9_val, ProfileSlider)
Number Profile9_12 "12" (Profile9_val, ProfileSlider)
Number Profile9_13 "13" (Profile9_val, ProfileSlider)
Number Profile9_14 "14" (Profile9_val, ProfileSlider)
Number Profile9_15 "15" (Profile9_val, ProfileSlider)
Number Profile9_16 "16" (Profile9_val, ProfileSlider)
Number Profile9_17 "17" (Profile9_val, ProfileSlider)
Number Profile9_18 "18" (Profile9_val, ProfileSlider)
Number Profile9_19 "19" (Profile9_val, ProfileSlider)
Number Profile9_20 "20" (Profile9_val, ProfileSlider)
Number Profile9_21 "21" (Profile9_val, ProfileSlider)
Number Profile9_22 "22" (Profile9_val, ProfileSlider)
Number Profile9_23 "23" (Profile9_val, ProfileSlider)

//BEGIN PROFILE BLOCK
Group Profile10 (ProfileList)
Group Profile10_val (Profile10)

String Profile10_Name        (Profile10) 
String Profile10_Command     (Profile10, ProfileCMD) 
String Profile10_Type        (Profile10, ProfileType) 
Switch Profile10_Interpolate (Profile10) 
Number Profile10_Calculated  (Profile10)
Number Profile10_MaxValue    (Profile10)
Number Profile10_MinValue    (Profile10)

Number Profile10_0 "00" (Profile10_val, ProfileSlider) 
Number Profile10_1 "01" (Profile10_val, ProfileSlider)
Number Profile10_2 "02" (Profile10_val, ProfileSlider)
Number Profile10_3 "03" (Profile10_val, ProfileSlider)
Number Profile10_4 "04" (Profile10_val, ProfileSlider)
Number Profile10_5 "05" (Profile10_val, ProfileSlider)
Number Profile10_6 "06" (Profile10_val, ProfileSlider)
Number Profile10_7 "07" (Profile10_val, ProfileSlider)
Number Profile10_8 "08" (Profile10_val, ProfileSlider)
Number Profile10_9 "09" (Profile10_val, ProfileSlider)
Number Profile10_10 "10" (Profile10_val, ProfileSlider)
Number Profile10_11 "11" (Profile10_val, ProfileSlider)
Number Profile10_12 "12" (Profile10_val, ProfileSlider)
Number Profile10_13 "13" (Profile10_val, ProfileSlider)
Number Profile10_14 "14" (Profile10_val, ProfileSlider)
Number Profile10_15 "15" (Profile10_val, ProfileSlider)
Number Profile10_16 "16" (Profile10_val, ProfileSlider)
Number Profile10_17 "17" (Profile10_val, ProfileSlider)
Number Profile10_18 "18" (Profile10_val, ProfileSlider)
Number Profile10_19 "19" (Profile10_val, ProfileSlider)
Number Profile10_20 "20" (Profile10_val, ProfileSlider)
Number Profile10_21 "21" (Profile10_val, ProfileSlider)
Number Profile10_22 "22" (Profile10_val, ProfileSlider)
Number Profile10_23 "23" (Profile10_val, ProfileSlider)

//BEGIN PROFILE BLOCK
Group AlwaysON (ProfileList)
Group AlwaysON_val (AlwaysON)

String AlwaysON_Name        (AlwaysON) 
String AlwaysON_Command     (AlwaysON, ProfileCMD) 
String AlwaysON_Type        (AlwaysON, ProfileType) 
Switch AlwaysON_Interpolate (AlwaysON) 
Number AlwaysON_Calculated  (AlwaysON)
Number AlwaysON_MaxValue    (AlwaysON)
Number AlwaysON_MinValue    (AlwaysON)

Number AlwaysON_0 "00" (AlwaysON_val, ProfileSlider) 
Number AlwaysON_1 "01" (AlwaysON_val, ProfileSlider)
Number AlwaysON_2 "02" (AlwaysON_val, ProfileSlider)
Number AlwaysON_3 "03" (AlwaysON_val, ProfileSlider)
Number AlwaysON_4 "04" (AlwaysON_val, ProfileSlider)
Number AlwaysON_5 "05" (AlwaysON_val, ProfileSlider)
Number AlwaysON_6 "06" (AlwaysON_val, ProfileSlider)
Number AlwaysON_7 "07" (AlwaysON_val, ProfileSlider)
Number AlwaysON_8 "08" (AlwaysON_val, ProfileSlider)
Number AlwaysON_9 "09" (AlwaysON_val, ProfileSlider)
Number AlwaysON_10 "10" (AlwaysON_val, ProfileSlider)
Number AlwaysON_11 "11" (AlwaysON_val, ProfileSlider)
Number AlwaysON_12 "12" (AlwaysON_val, ProfileSlider)
Number AlwaysON_13 "13" (AlwaysON_val, ProfileSlider)
Number AlwaysON_14 "14" (AlwaysON_val, ProfileSlider)
Number AlwaysON_15 "15" (AlwaysON_val, ProfileSlider)
Number AlwaysON_16 "16" (AlwaysON_val, ProfileSlider)
Number AlwaysON_17 "17" (AlwaysON_val, ProfileSlider)
Number AlwaysON_18 "18" (AlwaysON_val, ProfileSlider)
Number AlwaysON_19 "19" (AlwaysON_val, ProfileSlider)
Number AlwaysON_20 "20" (AlwaysON_val, ProfileSlider)
Number AlwaysON_21 "21" (AlwaysON_val, ProfileSlider)
Number AlwaysON_22 "22" (AlwaysON_val, ProfileSlider)
Number AlwaysON_23 "23" (AlwaysON_val, ProfileSlider)

//BEGIN PROFILE BLOCK
Group AlwaysOFF (ProfileList)
Group AlwaysOFF_val (AlwaysOFF)

String AlwaysOFF_Name        (AlwaysOFF) 
String AlwaysOFF_Command     (AlwaysOFF, ProfileCMD) 
String AlwaysOFF_Type        (AlwaysOFF, ProfileType) 
Switch AlwaysOFF_Interpolate (AlwaysOFF) 
Number AlwaysOFF_Calculated  (AlwaysOFF)
Number AlwaysOFF_MaxValue    (AlwaysOFF)
Number AlwaysOFF_MinValue    (AlwaysOFF)

Number AlwaysOFF_0 "00" (AlwaysOFF_val, ProfileSlider) 
Number AlwaysOFF_1 "01" (AlwaysOFF_val, ProfileSlider)
Number AlwaysOFF_2 "02" (AlwaysOFF_val, ProfileSlider)
Number AlwaysOFF_3 "03" (AlwaysOFF_val, ProfileSlider)
Number AlwaysOFF_4 "04" (AlwaysOFF_val, ProfileSlider)
Number AlwaysOFF_5 "05" (AlwaysOFF_val, ProfileSlider)
Number AlwaysOFF_6 "06" (AlwaysOFF_val, ProfileSlider)
Number AlwaysOFF_7 "07" (AlwaysOFF_val, ProfileSlider)
Number AlwaysOFF_8 "08" (AlwaysOFF_val, ProfileSlider)
Number AlwaysOFF_9 "09" (AlwaysOFF_val, ProfileSlider)
Number AlwaysOFF_10 "10" (AlwaysOFF_val, ProfileSlider)
Number AlwaysOFF_11 "11" (AlwaysOFF_val, ProfileSlider)
Number AlwaysOFF_12 "12" (AlwaysOFF_val, ProfileSlider)
Number AlwaysOFF_13 "13" (AlwaysOFF_val, ProfileSlider)
Number AlwaysOFF_14 "14" (AlwaysOFF_val, ProfileSlider)
Number AlwaysOFF_15 "15" (AlwaysOFF_val, ProfileSlider)
Number AlwaysOFF_16 "16" (AlwaysOFF_val, ProfileSlider)
Number AlwaysOFF_17 "17" (AlwaysOFF_val, ProfileSlider)
Number AlwaysOFF_18 "18" (AlwaysOFF_val, ProfileSlider)
Number AlwaysOFF_19 "19" (AlwaysOFF_val, ProfileSlider)
Number AlwaysOFF_20 "20" (AlwaysOFF_val, ProfileSlider)
Number AlwaysOFF_21 "21" (AlwaysOFF_val, ProfileSlider)
Number AlwaysOFF_22 "22" (AlwaysOFF_val, ProfileSlider)
Number AlwaysOFF_23 "23" (AlwaysOFF_val, ProfileSlider)
Rules code

Rules required

import java.util.List
import org.openhab.core.model.script.ScriptServiceUtil
//This file manages the profiles. It enables the control via the widgets, but also does the actual interpolation and places the values into the appropriate place. 

//Rule for initializing profile
rule "Profile Calculator - Interpret command - RESET"
when
    Member of ProfileCMD received command "RESET"
then
    triggeringItem.postUpdate("IDLE")
    var root_profile_groupname = triggeringItem.name.split('_').get(0)
    var root_group = ScriptServiceUtil.getItemRegistry.getItem(root_profile_groupname) as GroupItem
    var numb_group = ScriptServiceUtil.getItemRegistry.getItem(root_group.name + "_val") as GroupItem

    //Reset all the sliders
    for(var c = 0; c < 24; c = c + 1 ){
        ScriptServiceUtil.getItemRegistry.getItem(root_profile_groupname + "_" + c ).postUpdate(0)
    } 

    //Rest the main variables
    ScriptServiceUtil.getItemRegistry.getItem(root_profile_groupname + "_Name").postUpdate("" + root_profile_groupname)
    ScriptServiceUtil.getItemRegistry.getItem(root_profile_groupname + "_Type").postUpdate("Percentage")
    ScriptServiceUtil.getItemRegistry.getItem(root_profile_groupname + "_Interpolate").postUpdate(OFF)
end

//Rule for making sure values fit on sliders
rule "Profile Calculator - Adjust type - Fit values"
when
    Member of ProfileType received update or
    Member of ProfileSlider received command 
then
    var root_profile_groupname = triggeringItem.name.split('_').get(0)
    var root_group1 = ScriptServiceUtil.getItemRegistry.getItem(root_profile_groupname) as GroupItem
    var root_group = ScriptServiceUtil.getItemRegistry.getItem(root_group1.name + "_val") as GroupItem

    var foundMaxValue = 0
    var foundMinValue = 100

    //Check the ranges on all the sliders
    for(var c = 0; c < 24; c = c + 1 ){
        var slideritem = ScriptServiceUtil.getItemRegistry.getItem(root_profile_groupname + "_" + c) as GenericItem

        if(triggeringItem.state.toString == "Percentage"){
            if(slideritem.state > 100 || slideritem.state < 0 ){
                slideritem.sendCommand(0)
            }
        }
        if(triggeringItem.state.toString == "Temperature"){
            if(slideritem.state > 25 || slideritem.state < 5 ){
                slideritem.sendCommand(5)
            }
        }
        if(triggeringItem.state.toString == "Boolean"){
            if(slideritem.state > 1 || slideritem.state < 0 ){
                slideritem.sendCommand(0)
            }
        }

        if(slideritem.state > foundMaxValue){
            foundMaxValue = slideritem.state
        }
        if(slideritem.state < foundMinValue){
            foundMinValue = slideritem.state
        }

    } 
    logInfo("Profile Calculator","Min value: " + foundMinValue)
    logInfo("Profile Calculator","Max value: " + foundMaxValue)
    ScriptServiceUtil.getItemRegistry.getItem(root_profile_groupname + "_MinValue").postUpdate(foundMinValue)
    ScriptServiceUtil.getItemRegistry.getItem(root_profile_groupname + "_MaxValue").postUpdate(foundMaxValue)

end

//Rule for updating the values in all the profiles
rule "Profile Calculator - Calculate current value"
when
    Time cron "0 0/1 * * * ?"
then

    if(SystemReady.state != ON){
        logError("System Startup Catch", "Rule execution blocked -- Persistent variables not yet loaded!")
    }else{

        //Get the current time and put into two handy variables
        var curn_interval = (now.getHour()).intValue
        var next_interval = curn_interval + 1
        if(next_interval == 24) { next_interval = 0}

        var profilecounter = 0

        //logInfo("Profile Calculator", "Running Profile update cycle, it is currently interval " + curn_interval + " and the next interval is " + next_interval + ".")

        ProfileList.members.forEach[ root_profile |
            
            //logInfo("Profile Calculator", "Updating " + root_profile.name)

            profilecounter = profilecounter + 1
            
            var group_item = root_profile as GroupItem
            //check to see if it exists, if not, generate it!
            if(ScriptServiceUtil.getItemRegistry.getItem(group_item.name + "_Name").state == NULL){
                ScriptServiceUtil.getItemRegistry.getItem(group_item.name + "_Command").sendCommand("RESET")
                //Wait for it to generate
                Thread::sleep(1000)
            }

            //Check for interpolation
            if(ScriptServiceUtil.getItemRegistry.getItem(group_item.name + "_Interpolate").state == OFF){
                //No interpolation. Direct transfer OK
                var slidergroup = ScriptServiceUtil.getItemRegistry.getItem(group_item.name + "_val") as GroupItem
                var slideritem  = ScriptServiceUtil.getItemRegistry.getItem(group_item.name + "_" + curn_interval ) as NumberItem
                var targetitem  = ScriptServiceUtil.getItemRegistry.getItem(group_item.name + "_Calculated") as NumberItem

                targetitem.postUpdate( slideritem.state as Number)
                
            }else{
                //Interpolation required. Do some math first. 
                var slidergroup     = ScriptServiceUtil.getItemRegistry.getItem(group_item.name + "_val") as GroupItem
                var slideritem_now  = ScriptServiceUtil.getItemRegistry.getItem(group_item.name + "_" + curn_interval ) as NumberItem
                var slideritem_nxt  = ScriptServiceUtil.getItemRegistry.getItem(group_item.name + "_" + next_interval ) as NumberItem
                var targetitem      = ScriptServiceUtil.getItemRegistry.getItem(group_item.name + "_Calculated") as NumberItem

                var Number interpolatedvalue = slideritem_now.state as Number + ((now.getMinute() / 60.0 ) * (slideritem_nxt.state as Number - slideritem_now.state as Number))
                targetitem.postUpdate( interpolatedvalue.intValue )
            }
            
        ]

        logInfo("Profile Calculator", "Successfully Updated " + profilecounter + " profiles!")

    }
end

rule "Profile Calculator - load defaults"
when
    Item ProfileMasterReset received command ON
then
    ProfileMasterReset.sendCommand(OFF)

    //Light profiles
    Profile1_Name.sendCommand("Standard Light Brightness")
    Profile2_Name.sendCommand("Standard Light Temperature")
    Profile3_Name.sendCommand("Dimmer Light Brightness")
    Profile4_Name.sendCommand("Lower Light Brightness")
    Profile1_Interpolate.sendCommand(ON)
    Profile2_Interpolate.sendCommand(ON)
    Profile3_Interpolate.sendCommand(ON)
    Profile4_Interpolate.sendCommand(ON)
    Profile1_Type.sendCommand("Percentage")
    Profile2_Type.sendCommand("Percentage")
    Profile3_Type.sendCommand("Percentage")
    Profile4_Type.sendCommand("Percentage")
    //valuearrays
    var List<Integer> profile1values = newArrayList(12,13,13,13,14,15,41,61,91,99,100,99,97,96,94,92,89,85,74,57,40,30,20,15)
    var List<Integer> profile2values = newArrayList(0,0,0,0,0,12,29,56,74,83,88,89,89,85,78,70,63,53,39,26,18,10,0,0)
    var List<Integer> profile3values = newArrayList(6,3,4,6,7,9,19,22,32,30,29,28,23,9,8,8,9,11,18,21,22,19,11,10,7)
    var List<Integer> profile4values = newArrayList(8,9,9,9,9,9,13,28,36,62,81,85,86,87,89,85,81,71,64,50,36,19,11,7)

    for(var c = 0; c < 24; c = c + 1 ){
        ScriptServiceUtil.getItemRegistry.getItem( "Profile1_" + c ).postUpdate(profile1values.get(c))
    }
    for(var c = 0; c < 24; c = c + 1 ){
        ScriptServiceUtil.getItemRegistry.getItem( "Profile2_" + c ).postUpdate(profile2values.get(c))
    }
    for(var c = 0; c < 24; c = c + 1 ){
        ScriptServiceUtil.getItemRegistry.getItem( "Profile3_" + c ).postUpdate(profile3values.get(c))
    }
    for(var c = 0; c < 24; c = c + 1 ){
        ScriptServiceUtil.getItemRegistry.getItem( "Profile4_" + c ).postUpdate(profile4values.get(c))
    }

    //Thermo Profiles
    Profile5_Name.sendCommand("Workday")
    Profile6_Name.sendCommand("Weekend")
    Profile7_Name.sendCommand("Hot Morning")
    Profile8_Name.sendCommand("Alternate Heat")
    Profile5_Interpolate.sendCommand(OFF)
    Profile6_Interpolate.sendCommand(OFF)
    Profile7_Interpolate.sendCommand(OFF)
    Profile8_Interpolate.sendCommand(OFF)
    Profile5_Type.sendCommand("Temperature")
    Profile6_Type.sendCommand("Temperature")
    Profile7_Type.sendCommand("Temperature")
    Profile8_Type.sendCommand("Temperature")
    var List<Integer> profile5values = newArrayList(16,16,16,16,16,18,20,21,20,20,20,20,20,20,20,20,20,20,20,21,22,21,18,17)
    var List<Integer> profile6values = newArrayList(16,16,16,16,16,18,20,20,20,18,18,18,18,18,18,18,19,20,21,21,21,18,18,16)

    for(var c = 0; c < 24; c = c + 1 ){
        ScriptServiceUtil.getItemRegistry.getItem( "Profile5_" + c ).postUpdate(profile5values.get(c))
    }
    for(var c = 0; c < 24; c = c + 1 ){
        ScriptServiceUtil.getItemRegistry.getItem( "Profile6_" + c ).postUpdate(profile6values.get(c))
    }

    Away_Name.sendCommand("Away")
    Present_Name.sendCommand("Present")
    Frostguard_Name.sendCommand("Frostguard")
    Away_Interpolate.sendCommand(OFF)
    Present_Interpolate.sendCommand(OFF)
    Frostguard_Interpolate.sendCommand(OFF)
    Away_Type.sendCommand("Temperature")
    Present_Type.sendCommand("Temperature")
    Frostguard_Type.sendCommand("Temperature")

    for(var c = 0; c < 24; c = c + 1 ){
        ScriptServiceUtil.getItemRegistry.getItem( "Away_" + c ).postUpdate(15)
    }
    for(var c = 0; c < 24; c = c + 1 ){
        ScriptServiceUtil.getItemRegistry.getItem( "Present_" + c ).postUpdate(21)
    }
    for(var c = 0; c < 24; c = c + 1 ){
        ScriptServiceUtil.getItemRegistry.getItem( "Frostguard_" + c ).postUpdate(12)
    }
    

    //Boolean Profiles
    Profile9_Name.sendCommand("Floorheating 1")
    Profile10_Name.sendCommand("Floorheating 2")
    AlwaysON_Name.sendCommand("Always ON")
    AlwaysOFF_Name.sendCommand("Always OFF")
    Profile9_Interpolate.sendCommand(OFF)
    Profile10_Interpolate.sendCommand(OFF)
    AlwaysON_Interpolate.sendCommand(OFF)
    AlwaysOFF_Interpolate.sendCommand(OFF)
    Profile9_Type.sendCommand("Boolean")
    Profile10_Type.sendCommand("Boolean")
    AlwaysON_Type.sendCommand("Boolean")
    AlwaysOFF_Type.sendCommand("Boolean")
    var List<Integer> profile9values = newArrayList(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,0,0)
    var List<Integer> profile10values = newArrayList(0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,0)
    var List<Integer> AlwaysOFFvalues = newArrayList(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)
    var List<Integer> AlwaysONvalues = newArrayList(1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1)

    for(var c = 0; c < 24; c = c + 1 ){
        ScriptServiceUtil.getItemRegistry.getItem( "Profile9_" + c ).postUpdate(profile9values.get(c))
    }
    for(var c = 0; c < 24; c = c + 1 ){
        ScriptServiceUtil.getItemRegistry.getItem( "Profile10_" + c ).postUpdate(profile10values.get(c))
    }
    for(var c = 0; c < 24; c = c + 1 ){
        ScriptServiceUtil.getItemRegistry.getItem( "AlwaysON_" + c ).postUpdate(AlwaysONvalues.get(c))
    }
    for(var c = 0; c < 24; c = c + 1 ){
        ScriptServiceUtil.getItemRegistry.getItem( "AlwaysOFF_" + c ).postUpdate(AlwaysOFFvalues.get(c))
    }


end
3 Likes

Thanks for sharing it seems a really usefull widget!

I’m a “non-programmers” user I believe I’ve correclty imported:

  • widgets via Developer Tools > Widget
  • rule: via Scripts > new Rule DSL

But I cannot understand how to do the import for the .items file template.

If I try Settings > Items > + > Add Items from Textual Definition this create 512 items.

Thanks in advance and sorry if this is a silly question.
BR

Do the virtual items show up as items in the items menu (Settings → Items) once you add them? I’m not too familiar with how the “Add Items from Textual Definition” works but it should work. Also, check the log at <OpenHab IP>:9001 and see if there are any errors being thrown.

Thanks for reply, the virtual items are show by Settings > Items and also from cli
openhab> items list

Profile1_17 (Type=NumberItem, State=NULL, Label=17, Category=null, Groups=[Profile1_val, ProfileSlider])
Profile1_18 (Type=NumberItem, State=NULL, Label=18, Category=null, Groups=[Profile1_val, ProfileSlider])
Profile1_19 (Type=NumberItem, State=NULL, Label=19, Category=null, Groups=[Profile1_val, ProfileSlider])
Profile7 (Type=GroupItem, Members=8, State=NULL, Label=null, Category=null, Groups=[ProfileList])
Present_val (Type=GroupItem, Members=24, State=NULL, Label=null, Category=null, Groups=[Present])
Profile6 (Type=GroupItem, Members=8, State=NULL, Label=null, Category=null, Groups=[ProfileList])
AlwaysOFF_Command (Type=StringItem, State=NULL, Label=null, Category=null, Groups=[AlwaysOFF, ProfileCMD])
Profile9 (Type=GroupItem, Members=8, State=NULL, Label=null, Category=null, Groups=[ProfileList])
Profile8 (Type=GroupItem, Members=8, State=NULL, Label=null, Category=null, Groups=[ProfileList])
Profile3 (Type=GroupItem, Members=8, State=NULL, Label=null, Category=null, Groups=[ProfileList])
Profile2 (Type=GroupItem, Members=8, State=NULL, Label=null, Category=null, Groups=[ProfileList])
Profile5 (Type=GroupItem, Members=8, State=NULL, Label=null, Category=null, Groups=[ProfileList])
Profile4 (Type=GroupItem, Members=8, State=NULL, Label=null, Category=null, Groups=[ProfileList])
Profile1 (Type=GroupItem, Members=8, State=NULL, Label=null, Category=null, Groups=[ProfileList])
Profile4_MinValue (Type=NumberItem, State=NULL, Label=null, Category=null, Groups=[Profile4])

etc.

From log I see no errors only warning:

15:06:25.867 [WARN ] [se.internal.SseItemStatesEventBuilder] - Attempting to send a state update of an item which doesn't exist: TypeError: Cannot read properties of undefined (reading 'split')
15:16:51.200 [INFO ] [openhab.event.ItemUpdatedEvent       ] - Item 'Profile9_MaxValue' has been updated.
15:16:51.202 [INFO ] [openhab.event.ItemUpdatedEvent       ] - Item 'Profile9_MinValue' has been updated.

I’ve forgotten that I’m using 4.0.0.Milestone

Ok, that looks fine. What I’d try next is using the ProfileMasterReset Item and manually switching it to ON to try and trigger the "Profile Calculator - load defaults" rule since the values of the items seem to be uninitialized (their State is NULL), or just manually run the rule via UI.

Items set to null cause all sorts of issues with rules, but once they are initialized, it should be fine due to persistence.

Next step I’ve run the “Profile Calculator - load defaults” rule but someting went wrong:

08:55:21.605 [ERROR] [.internal.handler.ScriptActionHandler] - Script execution of rule with UID 'aad9ed0e01' failed:  ___ import  ___ java.util.List
import org.openhab.cor ___ e.model ___ .script.ScriptServiceUtil
//This file manages the profiles. It enables the control via the widgets, but also does the actual interpolation and places the values into the appropriate place. 

//Rule for initializing profile
rule "Profile Calculator - Interpret command - RESET"
when
    Member of ProfileCMD received command "RESET"
then
    triggeringItem.postUpdate("IDLE")
    var root_profile_groupname = triggeringItem.name.split('_').get(0)
    var root_group = ScriptServiceUtil.getItemRegistry.getItem(root_profile_groupname) as GroupItem
    var numb_group = ScriptServiceUtil.getItemReg
...all code from rule...

end
   1. List cannot be resolved to a type.; line 154, column 6832, length 4
   2. List cannot be resolved to a type.; line 155, column 6958, length 4
   3. List cannot be resolved to a type.; line 156, column 7076, length 4
   4. List cannot be resolved to a type.; line 157, column 7193, length 4
   5. List cannot be resolved to a type.; line 185, column 8454, length 4
   6. List cannot be resolved to a type.; line 186, column 8579, length 4
   7. List cannot be resolved to a type.; line 229, column 10317, length 4
   8. List cannot be resolved to a type.; line 230, column 10418, length 4
   9. List cannot be resolved to a type.; line 231, column 10520, length 4
   10. List cannot be resolved to a type.; line 232, column 10622, length 4
   11. The method or field import is undefined; line 1, column 0, length 6
   12. The method or field import is undefined; line 2, column 22, length 6
   13. The method or field rule is undefined; line 6, column 274, length 4
   14. The method or field when is undefined; line 7, column 328, length 4
   15. The method or field Member is undefined; line 8, column 337, length 6
   16. The method or field received is undefined; line 8, column 358, length 8
   17. The method or field command is undefined; line 8, column 367, length 7
   18. The method or field then is undefined; line 9, column 383, length 4
   19. The method or field ScriptServiceUtil is undefined; line 12, column 518, length 17
   20. The method or field ScriptServiceUtil is undefined; line 13, column 618, length 17
   21. The method or field ScriptServiceUtil is undefined; line 17, column 776, length 17
   22. The method or field ScriptServiceUtil is undefined; line 21, column 909, length 17
   23. The method or field ScriptServiceUtil is undefined; line 22, column 1029, length 17
   24. The method or field ScriptServiceUtil is undefined; line 23, column 1134, length 17
   25. The method or field end is undefined; line 24, column 1233, length 3
   26. The method or field rule is undefined; line 27, column 1283, length 4
   27. The method or field when is undefined; line 28, column 1336, length 4
   28. The method or field Member is undefined; line 29, column 1345, length 6
   29. The method or field received is undefined; line 29, column 1367, length 8
   30. The method or field update is undefined; line 29, column 1376, length 6
   31. The method or field or is undefined; line 29, column 1383, length 2
   32. The method or field Member is undefined; line 30, column 1390, length 6
   33. The method or field received is undefined; line 30, column 1414, length 8
   34. The method or field command is undefined; line 30, column 1423, length 7
   35. The method or field then is undefined; line 31, column 1432, length 4
   36. The method or field ScriptServiceUtil is undefined; line 33, column 1530, length 17
   37. The method or field ScriptServiceUtil is undefined; line 34, column 1630, length 17
   38. The method or field ScriptServiceUtil is undefined; line 41, column 1875, length 17
   39. The method or field ScriptServiceUtil is undefined; line 69, column 2875, length 17
   40. The method or field ScriptServiceUtil is undefined; line 70, column 2985, length 17
   41. The method or field end is undefined; line 72, column 3092, length 3
   42. The method or field rule is undefined; line 75, column 3148, length 4
   43. The method or field when is undefined; line 76, column 3200, length 4
   44. The method or field cron is undefined; line 77, column 3214, length 4
   45. The method or field then is undefined; line 78, column 3235, length 4
   46. The method or field SystemReady is undefined; line 80, column 3248, length 11
   47. The method or field ScriptServiceUtil is undefined; line 101, column 4147, length 17
   48. The method or field ScriptServiceUtil is undefined; line 102, column 4248, length 17
   49. The method or field ScriptServiceUtil is undefined; line 108, column 4487, length 17
   50. The method or field ScriptServiceUtil is undefined; line 110, column 4667, length 17
   51. The method or field ScriptServiceUtil is undefined; line 111, column 4782, length 17
   52. The method or field ScriptServiceUtil is undefined; line 112, column 4912, length 17
   53. The method or field ScriptServiceUtil is undefined; line 118, column 5206, length 17
   54. The method or field ScriptServiceUtil is undefined; line 119, column 5325, length 17
   55. The method or field ScriptServiceUtil is undefined; line 120, column 5459, length 17
   56. The method or field ScriptServiceUtil is undefined; line 121, column 5593, length 17
   57. The method or field end is undefined; line 132, column 6067, length 3
   58. The method or field rule is undefined; line 134, column 6072, length 4
   59. The method or field when is undefined; line 135, column 6114, length 4
   60. The method or field received is undefined; line 136, column 6147, length 8
   61. The method or field command is undefined; line 136, column 6156, length 7
   62. The method or field then is undefined; line 137, column 6167, length 4
   63. The method or field ScriptServiceUtil is undefined; line 160, column 7352, length 17
   64. The method or field ScriptServiceUtil is undefined; line 163, column 7501, length 17
   65. The method or field ScriptServiceUtil is undefined; line 166, column 7650, length 17
   66. The method or field ScriptServiceUtil is undefined; line 169, column 7799, length 17
   67. The method or field ScriptServiceUtil is undefined; line 189, column 8745, length 17
   68. The method or field ScriptServiceUtil is undefined; line 192, column 8894, length 17
   69. The method or field ScriptServiceUtil is undefined; line 206, column 9420, length 17
   70. The method or field ScriptServiceUtil is undefined; line 209, column 9546, length 17
   71. The method or field ScriptServiceUtil is undefined; line 212, column 9675, length 17
   72. The method or field ScriptServiceUtil is undefined; line 235, column 10764, length 17
   73. The method or field ScriptServiceUtil is undefined; line 238, column 10913, length 17
   74. The method or field ScriptServiceUtil is undefined; line 241, column 11064, length 17
   75. The method or field ScriptServiceUtil is undefined; line 244, column 11213, length 17
   76. The method or field end is undefined; line 248, column 11318, length 3
   77. Duplicate local variable root_profile_groupname; line 32, column 1445, length 22
   78. Duplicate local variable root_group; line 34, column 1617, length 10
   79. Invalid number of arguments. The method of(int) is not applicable without arguments; line 8, column 344, length 2
   80. Invalid number of arguments. The method of(int) is not applicable without arguments; line 29, column 1352, length 2
   81. Invalid number of arguments. The method of(int) is not applicable without arguments; line 30, column 1397, length 2
   82. Cannot refer to the non-final variable profilecounter inside a lambda expression; line 97, column 3966, length 14
   83. Cannot refer to the non-final variable profilecounter inside a lambda expression; line 97, column 3983, length 14
   84. Cannot refer to the non-final variable curn_interval inside a lambda expression; line 111, column 4848, length 13
   85. Cannot refer to the non-final variable curn_interval inside a lambda expression; line 119, column 5391, length 13
   86. Cannot refer to the non-final variable next_interval inside a lambda expression; line 120, column 5525, length 13
   87. Type mismatch: cannot convert from State to int; line 60, column 2607, length 16
   88. Type mismatch: cannot convert from State to int; line 63, column 2708, length 16
   89. This expression is not allowed in this context, since it doesn't cause any side effects.; line 1, column 7, length 14
   90. This expression is not allowed in this context, since it doesn't cause any side effects.; line 2, column 29, length 47
   91. This expression is not allowed in this context, since it doesn't cause any side effects.; line 6, column 279, length 48
   92. This expression is not allowed in this context, since it doesn't cause any side effects.; line 8, column 347, length 10
   93. This expression is not allowed in this context, since it doesn't cause any side effects.; line 8, column 375, length 7
   94. This expression is not allowed in this context, since it doesn't cause any side effects.; line 27, column 1288, length 47
   95. This expression is not allowed in this context, since it doesn't cause any side effects.; line 29, column 1355, length 11
   96. This expression is not allowed in this context, since it doesn't cause any side effects.; line 30, column 1400, length 13
   97. This expression is not allowed in this context, since it doesn't cause any side effects.; line 75, column 3153, length 46
   98. This expression is not allowed in this context, since it doesn't cause any side effects.; line 77, column 3209, length 4
   99. This expression is not allowed in this context, since it doesn't cause any side effects.; line 77, column 3219, length 15
   100. This expression is not allowed in this context, since it doesn't cause any side effects.; line 134, column 6077, length 36
   101. This expression is not allowed in this context, since it doesn't cause any side effects.; line 136, column 6123, length 4
   102. This expression is not allowed in this context, since it doesn't cause any side effects.; line 136, column 6128, length 18
   103. This expression is not allowed in this context, since it doesn't cause any side effects.; line 136, column 6164, length 2

Looks like there’s an issue with the java imports. Are those underscores actually in the code too?

Script execution of rule with UID 'aad9ed0e01' failed:  ___ import  ___ java.util.List
import org.openhab.cor ___ e.model ___ .script.ScriptServiceUtil

If not this may be some issue with the rules parsing or OH M4.0.0 since there are occasionally changes that break old DSL code in the updates.

I don’t see them in the code, it’s like this:

import java.util.List
import org.openhab.core.model.script.ScriptServiceUtil

Thanks for this awesome piece of work.
All the items / rules have loaded, however I get an error on the Profile Calculator rule with the error “SystemReady” cannot be resolved to an item or type. Is SystemReady supposed to be defined as an item somewhere or some global variable?

Ah, yes, SystemReady is just a Switch-type item that is always set to ON and has restoreOnStartup enabled–I must’ve forgotten to include it in the list of virtual items. This means that when the OH server boots, the rule execution will be blocked until the previous item states are loaded from memory. To fix, either remove this part of the code or add Switch SystemReady "System Ready" and manually switch it to ON once.

Thanks for this, Ive added the Item “SystemReady”
Next silly question!
The input item in the profile_looper widget is “profilecore”. I see you use this in the widget with _Name / _Type etc, yet the defined items in your item list are Profile2_Name & type, Profile2_Name & type and so on. So Im not sure what item is needed in profilecore. Im still new to widgets so apologies if this is a dumb question.
Thanks

No Problem!

The widgets work by appending a specific profile name with different underscored suffixes to access the different items of that profile. So for the “profilcore” setting, you need to set the Profile Group item eg. Profile1 or Profile5 (all the possible valid profile groups are members of the ProfileList Group) and the widget will then use this to get Profile1_Name or Profile1_12 or whatever is needed :smile: