OH3 MainUI Widget Showcase: TimeScheduler

Hi guys, had time to finish my TimeScheduler/Cosntructor. It is just basically a timetable for the automated blind control. The table is stored as JSON insidide an item. The Scheduler Widget (upper) just displays the values as “table”, the constructor injects the new values into the JSON (via an item and javascript rule because it is not directly possible inside a widget), a second rule checks via cron if the times are matching and then act accordingly.
Had this already in HabPanel but did rewrite it fully.

  • Due to its highly customized to my items and needs I post it to show users the possibilities of the UI
  • I only post the widget code and the rule for the JSON ‘injection’ , other rules etc. do not matter

Resources

Schedule Widget:
uid: TimeSchedule
tags: []
props:
  parameters:
    - description: A text prop
      label: Prop 1
      name: prop1
      required: false
      type: TEXT
    - context: item
      description: An item to control
      label: Item
      name: item
      required: false
      type: TEXT
  parameterGroups: []
timestamp: Jan 14, 2021, 1:40:36 PM
component: f7-card
slots:
  default:
    - component: oh-toggle
      config:
        variable: mobile
      slots: {}
    - component: Label
      config:
        text: "=(vars.mobile === true) ? 'Mobil' : 'Tab/Pc'"
      slots: {}
    - component: f7-swiper
      config:
        navigation: true
        class:
          - padding-top
        params:
          initalSlide: 0
          runCallbacksOnInit: true
          grabCursor: true
          observer: true
          observeSlideChildren: true
          updateOnWindowResize: true
          spaceBetween: 5
          mousewheel: true
          keyboard: true
          watchOverflow: true
          slidesPerView: 1
        style:
          --swiper-navigation-size: 30px
          --swiper-navigation-color: var(--weather-card-text-color)
      slots:
        default:
          - component: oh-repeater
            config:
              sourceType: range
              for: room
              rangeStart: 0
              rangeStop: 13
              fragment: true
            slots:
              default:
                - component: f7-swiper-slide
                  config:
                    id: =loop.room
                    expandable: true
                  slots:
                    default:
                      - component: f7-row
                        config:
                          class:
                            - justify-content-center
                            - align-items-center
                        slots:
                          default:
                            - component: Label
                              config:
                                text: "=(loop.room === 0) ? 'Wohnzimmer Fenster' : (loop.room === 1) ? 'Wohnzimmer Tisch' : (loop.room === 2) ? 'Wohnzimmer Couch' : (loop.room === 3) ? 'Badezimmer' : (loop.room === 4) ? 'Michael Nord' : (loop.room === 5) ? 'Michael Fenster 1' : (loop.room === 6) ? 'Michael Fenster 2' : (loop.room === 7) ? 'Maxi Fenster 1' : (loop.room === 8) ? 'Maxi Fenster 2' : (loop.room === 9) ? 'Maxi Fenster 3' : (loop.room === 10) ? 'Küche Fenster' : (loop.room === 11) ? 'Küche Panorama' : (loop.room === 12) ? 'Küche Tür' : (loop.room === 13) ? 'Eltern Fenster' : 'unknown' "
                      - component: f7-col
                        config:
                          class:
                            - justify-content-center
                            - align-items-center
                            - text-align-center
                        slots:
                          default:
                            - component: f7-row
                              slots:
                                default:
                                  - component: f7-col
                                    slots:
                                      default:
                                        - component: oh-list
                                          config:
                                            title: Funktion
                                          slots:
                                            default:
                                              - component: oh-list-item
                                                config:
                                                  title: ""
                                                slots: {}
                                              - component: oh-list-item
                                                config:
                                                  title: "=(vars.mobile === true) ? 'S1' : 'Stufe 1'"
                                                  style:
                                                    font-size: "=(vars.mobile === true) ? '15px' : '20px'"
                                                slots: {}
                                              - component: oh-list-item
                                                config:
                                                  title: "=(vars.mobile === true) ? 'S2' : 'Stufe 2'"
                                                  style:
                                                    font-size: "=(vars.mobile === true) ? '15px' : '20px'"
                                                slots: {}
                                              - component: oh-list-item
                                                config:
                                                  title: Öffnen
                                                  visible: =!vars.mobile
                                                  style:
                                                    font-size: "=(vars.mobile === true) ? '15px' : '20px'"
                                                slots: {}
                                              - component: oh-list-item
                                                config:
                                                  icon: f7:arrow_up
                                                  visible: =vars.mobile
                                                slots: {}
                                              - component: oh-list-item
                                                config:
                                                  title: Schließen
                                                  visible: =!vars.mobile
                                                  style:
                                                    font-size: "=(vars.mobile === true) ? '15px' : '20px'"
                                                slots: {}
                                              - component: oh-list-item
                                                config:
                                                  icon: f7:arrow_down
                                                  visible: =vars.mobile
                                                slots: {}
                                  - component: oh-repeater
                                    config:
                                      sourceType: range
                                      for: day
                                      rangeStart: 1
                                      rangeStop: 7
                                      fragment: true
                                    slots:
                                      default:
                                        - component: f7-col
                                          slots:
                                            default:
                                              - component: oh-list
                                                config:
                                                  title: Tage
                                                slots:
                                                  default:
                                                    - component: oh-list-item
                                                      config:
                                                        title: "=(vars.mobile === true) ? ((loop.day === 1) ? 'Mo' : (loop.day === 2) ? 'Di' : (loop.day === 3) ? 'Mi' : (loop.day === 4) ? 'Do' : (loop.day === 5) ? 'Fr' : (loop.day === 6) ? 'Sa' : (loop.day === 7) ? 'So' : 'unknown') : ((loop.day === 1) ? 'Montag' : (loop.day === 2) ? 'Dienstag' : (loop.day === 3) ? 'Mittwoch' : (loop.day === 4) ? 'Donnerstag' : (loop.day === 5) ? 'Freitag' : (loop.day === 6) ? 'Samstag' : (loop.day === 7) ? 'Sonntag' : 'unknown')"
                                                        style:
                                                          font-size: "=(vars.mobile === true) ? '15px' : '20px'"
                                                      slots: {}
                                                    - component: oh-list-item
                                                      config:
                                                        title: =JSON.parse(items.SavedSchedule.state)[loop.room.toString()][loop.day.toString()]['LAM1']
                                                        style:
                                                          font-size: "=(vars.mobile === true) ? '12px' : '18px'"
                                                      slots: {}
                                                    - component: oh-list-item
                                                      config:
                                                        title: =JSON.parse(items.SavedSchedule.state)[loop.room.toString()][loop.day.toString()]['LAM2']
                                                        style:
                                                          font-size: "=(vars.mobile === true) ? '12px' : '18px'"
                                                      slots: {}
                                                    - component: oh-list-item
                                                      config:
                                                        title: =JSON.parse(items.SavedSchedule.state)[loop.room.toString()][loop.day.toString()]['AUF']
                                                        style:
                                                          font-size: "=(vars.mobile === true) ? '12px' : '18px'"
                                                      slots: {}
                                                    - component: oh-list-item
                                                      config:
                                                        title: =JSON.parse(items.SavedSchedule.state)[loop.room.toString()][loop.day.toString()]['ZU']
                                                        style:
                                                          font-size: "=(vars.mobile === true) ? '12px' : '18px'"
                                                      slots: {}
Constructor Widget
uid: TimeController
tags: []
props:
  parameters:
    - description: Switch on Debug Mode
      label: Debug Mode
      name: debugmode
      required: false
      type: BOOLEAN
    - context: item
      description: An item to control
      label: Item
      name: item
      required: false
      type: TEXT
  parameterGroups: []
timestamp: Jan 16, 2021, 7:29:46 PM
component: f7-card
slots:
  default:
    - component: f7-row
      config:
        class:
          - justify-content-center
          - align-items-center
          - text-align-center
      slots:
        default:
          - component: Label
            config:
              text: "Bitte wähle ein Fenster/Raffstore aus:"
              style:
                color: rgb(255,59,48)
                font-weight: 600
    - component: f7-row
      config:
        class:
          - justify-content-center
          - align-items-center
          - text-align-center
      slots:
        default:
          - component: oh-list
            config:
              title: Liste
              accordionList: true
              style:
                margin-top: 5px
            slots:
              default:
                - component: oh-list-item
                  config:
                    title: "=(vars.roomS === NULL) ? 'Wählen Sie ein Fenster' : vars.roomS"
                    accordionItem: true
                  slots:
                    accordion:
                      - component: oh-list
                        config:
                          accordionList: true
                        slots:
                          default:
                            - component: oh-repeater
                              config:
                                sourceType: range
                                for: room
                                rangeStart: 0
                                rangeStop: 13
                                fragment: true
                              slots:
                                default:
                                  - component: oh-list-item
                                    config:
                                      title: "=(loop.room === 0) ? 'Wohnzimmer Fenster' : (loop.room === 1) ? 'Wohnzimmer Tisch' : (loop.room === 2) ? 'Wohnzimmer Couch' : (loop.room === 3) ? 'Badezimmer' : (loop.room === 4) ? 'Michael Nord' : (loop.room === 5) ? 'Michael Fenster 1' : (loop.room === 6) ? 'Michael Fenster 2' : (loop.room === 7) ? 'Maxi Fenster 1' : (loop.room === 8) ? 'Maxi Fenster 2' : (loop.room === 9) ? 'Maxi Fenster 3' : (loop.room === 10) ? 'Küche Fenster' : (loop.room === 11) ? 'Küche Panorama' : (loop.room === 12) ? 'Küche Tür' : (loop.room === 13) ? 'Eltern Fenster' : 'unknown' "
                                      action: variable
                                      actionVariable: roomS
                                      actionVariableValue: "=(loop.room === 0) ? 'Wohnzimmer Fenster' : (loop.room === 1) ? 'Wohnzimmer Tisch' : (loop.room === 2) ? 'Wohnzimmer Couch' : (loop.room === 3) ? 'Badezimmer' : (loop.room === 4) ? 'Michael Nord' : (loop.room === 5) ? 'Michael Fenster 1' : (loop.room === 6) ? 'Michael Fenster 2' : (loop.room === 7) ? 'Maxi Fenster 1' : (loop.room === 8) ? 'Maxi Fenster 2' : (loop.room === 9) ? 'Maxi Fenster 3' : (loop.room === 10) ? 'Küche Fenster' : (loop.room === 11) ? 'Küche Panorama' : (loop.room === 12) ? 'Küche Tür' : (loop.room === 13) ? 'Eltern Fenster' : 'unknown' "
    - component: f7-row
      config:
        class:
          - justify-content-center
          - align-items-center
          - text-align-center
      slots:
        default:
          - component: Label
            config:
              text: "Bitte wähle einen Tag aus:"
              style:
                color: rgb(255,59,48)
                font-weight: 600
                margin-top: 15px
    - component: f7-row
      config:
        class:
          - justify-content-center
          - align-items-center
          - text-align-center
      slots:
        default:
          - component: f7-segmented
            config:
              strong: false
              style:
                width: 450px
                margin-top: 5px
            slots:
              default:
                - component: oh-button
                  config:
                    text: Mo
                    action: variable
                    actionVariable: day
                    actionVariableValue: 1
                    disabled: =(vars.day === 1)
                    textColor: "=(vars.day === 1) ? 'green' : 'red'"
                    style:
                      font-weight: 400
                - component: oh-button
                  config:
                    text: Di
                    action: variable
                    actionVariable: day
                    actionVariableValue: 2
                    disabled: =(vars.day === 2)
                    textColor: "=(vars.day === 2) ? 'green' : 'red'"
                    style:
                      font-weight: 400
                - component: oh-button
                  config:
                    text: Mi
                    action: variable
                    actionVariable: day
                    actionVariableValue: 3
                    disabled: =(vars.day === 3)
                    textColor: "=(vars.day === 3) ? 'green' : 'red'"
                    style:
                      font-weight: 400
                - component: oh-button
                  config:
                    text: Do
                    action: variable
                    actionVariable: day
                    actionVariableValue: 4
                    disabled: =(vars.day === 4)
                    textColor: "=(vars.day === 4) ? 'green' : 'red'"
                    style:
                      font-weight: 400
                - component: oh-button
                  config:
                    text: Fr
                    action: variable
                    actionVariable: day
                    actionVariableValue: 5
                    disabled: =(vars.day === 5)
                    textColor: "=(vars.day === 5) ? 'green' : 'red'"
                    style:
                      font-weight: 400
                - component: oh-button
                  config:
                    text: Sa
                    action: variable
                    actionVariable: day
                    actionVariableValue: 6
                    disabled: =(vars.day === 6)
                    textColor: "=(vars.day === 6) ? 'green' : 'red'"
                    style:
                      font-weight: 400
                - component: oh-button
                  config:
                    text: So
                    action: variable
                    actionVariable: day
                    actionVariableValue: 7
                    disabled: =(vars.day === 7)
                    textColor: "=(vars.day === 7) ? 'green' : 'red'"
                    style:
                      font-weight: 400
    - component: f7-row
      config:
        class:
          - justify-content-center
          - align-items-center
          - text-align-center
      slots:
        default:
          - component: Label
            config:
              text: "Bitte wähle ein Funktion aus:"
              style:
                color: rgb(255,59,48)
                font-weight: 600
                margin-top: 15px
    - component: f7-row
      config:
        class:
          - justify-content-center
          - align-items-center
          - text-align-center
      slots:
        default:
          - component: f7-segmented
            config:
              strong: false
              style:
                width: 450px
                margin-top: 5px
            slots:
              default:
                - component: oh-button
                  config:
                    text: Stufe 1
                    action: variable
                    actionVariable: function
                    actionVariableValue: LAM1
                    disabled: =(vars.function === 'LAM1')
                    textColor: "=(vars.function === 'LAM1') ? 'green' : 'red'"
                    style:
                      font-weight: 400
                - component: oh-button
                  config:
                    text: Stufe 2
                    action: variable
                    actionVariable: function
                    actionVariableValue: LAM2
                    disabled: =(vars.function === 'LAM2')
                    textColor: "=(vars.function === 'LAM2') ? 'green' : 'red'"
                    style:
                      font-weight: 400
                - component: oh-button
                  config:
                    text: Öffnen
                    action: variable
                    actionVariable: function
                    actionVariableValue: AUF
                    disabled: =(vars.function === 'AUF')
                    textColor: "=(vars.function === 'AUF') ? 'green' : 'red'"
                    style:
                      font-weight: 400
                - component: oh-button
                  config:
                    text: Schließen
                    action: variable
                    actionVariable: function
                    actionVariableValue: ZU
                    disabled: =(vars.function === 'ZU')
                    textColor: "=(vars.function === 'ZU') ? 'green' : 'red'"
                    style:
                      font-weight: 400
    - component: f7-row
      config:
        class:
          - justify-content-center
          - align-items-center
          - text-align-center
      slots:
        default:
          - component: f7-segmented
            config:
              strong: false
              style:
                width: 450px
                margin-top: 5px
            slots:
              default:
                - component: oh-button
                  config:
                    text: Uhrzeit ändern
                    action: variable
                    actionVariable: variante
                    actionVariableValue: change
                    disabled: =(vars.variante === 'change')
                    textColor: "=(vars.variante === 'change') ? 'green' : 'red'"
                    style:
                      font-weight: 400
                - component: oh-button
                  config:
                    text: Deaktivieren
                    action: variable
                    actionVariable: variante
                    actionVariableValue: deactivate
                    disabled: =(vars.variante === 'deactivate')
                    textColor: "=(vars.variante === 'deactivate') ? 'green' : 'red'"
                    style:
                      font-weight: 400
    - component: f7-row
      config:
        class:
          - justify-content-center
          - align-items-center
          - text-align-center
      slots:
        default:
          - component: Label
            config:
              text: "Bitte wähle eine Zeit aus:"
              visible: =!(vars.variante === 'deactivate')
              style:
                color: rgb(255,59,48)
                font-weight: 600
                margin-top: 15px
    - component: f7-row
      config:
        visible: =!(vars.variante === 'deactivate')
        class:
          - justify-content-center
          - align-items-center
          - text-align-center
        style:
          margin-top: 5px
      slots:
        default:
          - component: oh-input
            config:
              type: time
              defaultValue: =dayjs().format('HH:mm')
              validate: true
              step: 900
              min: 00:00
              max: 23:59
              required: true
              variable: time
              disabled: =(vars.variante === 'deactivate')
            slots: {}
    - component: f7-row
      config:
        class:
          - justify-content-center
          - align-items-center
          - text-align-center
        style:
          margin-top: 20px
      slots:
        default:
          - component: oh-button
            config:
              raised: true
              text: Ändern
              action: command
              actionItem: ScheduleSaving
              actionCommand: "=((vars.roomS === 'Wohnzimmer Fenster') ? '0' : (vars.roomS === 'Wohnzimmer Tisch') ? '1' : (vars.roomS === 'Wohnzimmer Couch') ? '2' : (vars.roomS === 'Badezimmer') ? '3' : (vars.roomS === 'Michael Nord') ? '4' : (vars.roomS === 'Michael Fenster 1') ? '5' : (vars.roomS === 'Michael Fenster 2') ? '6' : (vars.roomS === 'Maxi Fenster 1') ? '7' : (vars.roomS === 'Maxi Fenster 2') ? '8' : (vars.roomS === 'Maxi Fenster 3') ? '9' : (vars.roomS === 'Küche Fenster') ? '10' : (vars.roomS === 'Küche Panorama') ? '11' : (vars.roomS === 'Küche Tür') ? '12' : (vars.roomS === 'Eltern Fenster') ? '13' : 'unknown') + ',' + vars.day + ',' + vars.function + ',' + ((vars.variante === 'change') ? vars.time : 'AUS')"
              actionFeedback: ok
              disabled: =(vars.roomS === NULL || vars.day === undefined || vars.function === undefined)
    - component: f7-row
      config:
        visible: =props.debugmode
      slots:
        default:
          - component: Label
            config:
              text: "=vars.day + '.' + vars.function + '||' + ((vars.variante === 'change') ? vars.time : 'AUS')"
          - component: Label
            config:
              text: "=(vars.roomS === NULL) ? 'true' : 'false'"
Javascript Rule for JSON editing
triggers:
  - id: "1"
    configuration:
      itemName: ScheduleSaving
    type: core.ItemStateChangeTrigger
conditions: []
actions:
  - inputs: {}
    id: "2"
    configuration:
      type: application/javascript
      script: >
        var logger = Java.type('org.slf4j.LoggerFactory').getLogger('org.openhab.rule.' + ctx.ruleUID);

        var schedule = itemRegistry.getItem('SavedSchedule').getState();
        var id = itemRegistry.getItem('ScheduleSaving').getState().toString().split(',')[0];  
        var day = itemRegistry.getItem('ScheduleSaving').getState().toString().split(',')[1];   
        var fun = itemRegistry.getItem('ScheduleSaving').getState().toString().split(',')[2];  
        var time = itemRegistry.getItem('ScheduleSaving').getState().toString().split(',')[3]; 

        var json_obj = JSON.parse(schedule); 

        json_obj[id][day][fun] = time;

        events.sendCommand('SavedSchedule', JSON.stringify(json_obj));

        var feedback = JSON.parse(itemRegistry.getItem('SavedSchedule').getState());

        logger.info("Input:  " + json_obj[id][day][fun]); 
    type: script.ScriptAction

@ysc Do you have any clue why editing the ‘scheduler’ is so laggy with 2 oh-repeat (editing widget itself and page on it)? Should multiple oh-repeat be avoided?

Your TimeScheduler may help me to find a solution for a similar task. I tried to implement it, but I don’t understand what really has to be done to get it work. I appcreciate to get a short advice which steps and items have to be predefined and so on… Thanks in advance Paul

Hello @maxwie1093, can you please post the structure of the JSON string? It has to be pre-initialized in some way or?