Another Light widget - Suitable for Switch, dimmer, and CCT lighting. Motion detector and timer functionality

Expandable Lighting Widget

I’ve been hacking this together for a while now. I started with an expandable widget I found on the forums (if I can find it again I will link it here) and tweaked it into something more suibable for my needs.

I have a few different types of lights: Switch on/off, dimmer/CCT bulbs, and some with motion detectors.
This widget will adjust itself to be suitable for all of these light types. Just leave some of the props empty, and the widget will know what type of light you have.

I have also designed it to fit nicely on my iPhone, tablet, and desktop nicely without over lapping text and graphics.

There are icons to show; Motion detection, Timer status, Illuminance, and Illuminace Threshold.
When expanded, there are more detailed controls - Scene Brightness, timer settings and Motion detector light threshold.

Here are some screenshots-

Here is the widget code:

props:
  parameters:
    - description: Location
      label: Location
      name: location
      required: false
      type: TEXT
    - context: item
      label: Brightness
      name: bright
      required: false
      type: TEXT
    - context: item
      label: Motion Detection
      name: motion
      required: false
      type: TEXT
    - context: item
      label: Illuminance threshold
      name: illumt
      required: false
      type: TEXT
    - context: item
      label: Illuminance
      name: illum
      required: false
      type: TEXT
    - context: item
      label: Scene brightness
      name: sbright
      required: false
      type: TEXT
    - context: item
      label: Motion Timer
      name: mtimer
      required: false
      type: TEXT
    - context: item
      label: "'On Time' Timer"
      name: oTtimer
      required: false
      type: TEXT
    - context: item
      label: Motion Detection On/Off
      name: motionOnOff
      required: false
      type: TEXT
timestamp: Jun 21, 2021, 11:03:01 PM
component: f7-card
config:
  expandable: true
  swipeToClose: true
  backdrop: true
  class:
    - card-expandable-animate-width
  style:
    background: '=(items[props.bright].state > 0 || items[props.bright].state == "ON") ? "linear-gradient(to bottom, hsla(48, 90%, 54%, 1), hsla(48, 90%, 95%, 1), 10% , hsla(0, 0%, 100%, 1) )": "white"'
    height: 170px
    margin: 5px
    margin-top: 10px
slots:
  default:
    - component: oh-button
      config:
        iconF7: ellipsis
        iconSize: 30px
        color: gray
        style:
          position: absolute
          top: 0
          right: 0
          padding-top: 10px
          padding-right: 15px
          padding-bottom: 40px
          z-index: 999
        class:
          - cell-open-button
          - card-opened-fade-out
    - component: f7-card-content
      config:
        style:
          width: 100%
          padding-top: 30px
      slots:
        default:
          - component: f7-icon
            config:
              material: lightbulb
              size: '=(items[props.bright].state > 0 || items[props.bright].state == "ON") ? "120px": "90px"'
              color: '=(items[props.bright].state > 0 || items[props.bright].state == "ON") ? "yellow": "gray"'
              style:
                opacity: '=(items[props.bright].state > 0 || items[props.bright].state == "ON") ? "90%": "10%"'
                position: absolute
                top: '=(items[props.bright].state > 0 || items[props.bright].state == "ON") ? "45px": "60px"'
                right: '=(items[props.bright].state > 0 || items[props.bright].state == "ON") ? "-15px": "0px"'
          - component: oh-button
            config:
              iconF7: xmark_circle_fill
              iconSize: 30px
              color: black
              style:
                position: absolute
                top: 0
                right: 0
                padding-top: 10px
                padding-bottom: 35px
                z-index: 999
              class:
                - card-opened-fade-in
                - cell-close-button
                - card-close
          - component: oh-link
            config:
              action: toggle
              actionItem: =props.bright
              actionCommand: '=(props.sbright) ? items[props.sbright].state: "ON"'
              actionCommandAlt: OFF
              class:
                - card-prevent-open
              style:
                width: 100%
                height: 100%
                position: absolute
                top: 0
                left: 0
                z-index: 0
          - component: f7-block
            config:
              class:
                - no-padding
              style:
                margin: 0px
                height: 200px
            slots:
              default:
                - component: f7-row
                  config:
                    style:
                      height: 65px
                      width: auto
                      white-space: nowrap
                      flex-wrap: nowrap
                  slots:
                    default:
                      - component: f7-col
                        slots:
                          default:
                            - component: Label
                              config:
                                text: =props.location
                                style:
                                  font-weight: 600
                                  text-overflow: ellipsis
                                  overflow: hidden
                                  white-space: nowrap
                            - component: f7-chip
                              config:
                                text: '=(items[props.bright].state > 0) ? items[props.bright].state + " %": (items[props.bright].state == "ON") ? "ON": "OFF"'
                                color: '=(items[props.bright].state > 0) ? "yellow": (items[props.bright].state == "ON") ? "yellow": "gray"'
                - component: f7-row
                  config:
                    style:
                      white-space: nowrap
                      flex-wrap: nowrap
                      height: auto
                    class:
                      - justify-content-flex-start
                  slots:
                    default:
                      - component: f7-icon
                        config:
                          visible: '=(props.motionOnOff) ? "true": "false"'
                          f7: dot_radiowaves_right
                          size: 20px
                          color: '=(items[props.motion].state == "OFF" && items[props.motionOnOff].state == "ON") ? "blue" : (items[props.motion].state == "ON") ? "red" : (items[props.motionOnOff].state == "OFF") ? "gray" : "gray"'
                          style:
                            margin-right: 5px
                      - component: f7-icon
                        config:
                          f7: timer
                          size: 20px
                          color: '=(items[props.oTtimer].state != "0" && (items[props.bright].state == 0 || items[props.bright].state == "OFF")) ? "blue": (items[props.oTtimer].state != "0" && (items[props.bright].state > 0 || items[props.bright].state != "OFF")) ? "red": "gray"'
                          visible: '=(props.oTtimer) ? "true": "false"'
                          style:
                            margin-right: 5px
                - component: f7-row
                  config:
                    visible: '=(props.illum) ? "true": "false"'
                    style:
                      height: auto
                      width: 100%
                      overflow: hidden
                  slots:
                    default:
                      - component: f7-col
                        config:
                          style:
                            flex-wrap: nowrap
                            align-self: flex-end
                        slots:
                          default:
                            - component: f7-row
                              config:
                                class:
                                  - justify-content-flex-start
                                style:
                                  margin-top: 5px
                                  flex-wrap: nowrap
                              slots:
                                default:
                                  - component: f7-icon
                                    config:
                                      f7: light_max
                                      color: yellow
                                      size: 18px
                                      style:
                                        margin-top: 0px
                                        margin-left: 0px
                                  - component: Label
                                    config:
                                      text: =items[props.illum].displayState
                                      style:
                                        margin-left: 8px
                                        font-size: 12px
                                        color: gray
                                        text-overflow: ellipsis
                                        overflow: hidden
                                        white-space: nowrap
                - component: f7-row
                  config:
                    visible: '=(props.illumt) ? "true": "false"'
                    style:
                      height: 20px
                      width: 100%
                      overflow: hidden
                  slots:
                    default:
                      - component: f7-col
                        config:
                          style:
                            flex-wrap: nowrap
                            align-self: flex-end
                        slots:
                          default:
                            - component: f7-row
                              config:
                                class:
                                  - justify-content-flex-start
                                style:
                                  margin-top: 5px
                                  flex-wrap: nowrap
                              slots:
                                default:
                                  - component: f7-icon
                                    config:
                                      f7: increase_indent
                                      color: '=(items[props.illumt].state < (items[props.illum].state | ".0")) ? "gray": (items[props.motionOnOff].state == "OFF") ? "gray": "green"'
                                      size: 18px
                                      style:
                                        margin-top: 0px
                                        margin-left: 0px
                                  - component: Label
                                    config:
                                      text: =items[props.illumt].displayState
                                      style:
                                        margin-left: 8px
                                        font-size: 12px
                                        color: gray
                                        text-overflow: ellipsis
                                        overflow: hidden
                                        white-space: nowrap
          - component: f7-block
            config:
              class:
                - card-prevent-open
                - card-content-padding
            slots:
              default:
                - component: oh-list
                  config:
                    class:
                      - padding
                  slots:
                    default:
                      - component: oh-toggle-item
                        config:
                          title: On/Off
                          item: =props.bright
                          icon: f7:power
                          color: yellow
                          actionCommand: '=(props.sbright) ? items[props.sbright].state: "ON"'
                          actionCommandAlt: OFF
                      - component: oh-stepper-item
                        config:
                          title: Brightness
                          item: =props.bright
                          icon: oh:slider
                          iconUseState: true
                          color: '=(items[props.bright].state > 0) ? "yellow" : "gray"'
                          visible: '=(props.sbright) ? "true": "false"'
                          min: 0
                          max: 100
                      - component: oh-stepper-item
                        config:
                          title: Scene Brightness
                          item: =props.sbright
                          icon: oh:slider
                          iconUseState: true
                          color: blue
                          min: 0
                          max: 100
                          visible: '=(props.sbright) ? "true": "false"'
                      - component: oh-list-item
                        config:
                          title: Motion detection
                          icon: f7:dot_radiowaves_right
                          badge: '=(items[props.motionOnOff].state == "ON" && items[props.illumt].state > (items[props.illum].state | ".0")) ? "AUTO": (items[props.motionOnOff].state == "OFF") ? "OFF": (items[props.motionOnOff].state == "ON") ? "AUTO": (items[props.motionOnOff].state == "ON" && items[props.illumt].state < (items[props.illum].state | ".0")) ? "TOO BRIGHT": items[props.motionOnOff].displayState'
                          badge-color: '=(items[props.motionOnOff].state == "ON" && items[props.illumt].state > (items[props.illum].state | ".0") && items[props.motion].state == "OFF") ? "blue": (items[props.motionOnOff].state == "ON") ? "blue" : (items[props.motionOnOff].state == "OFF") ? "black": (items[props.motionOnOff].state == "ON" && items[props.illumt].state < (items[props.illum].state | ".0") ) ? "yellow": (items[props.motion].state == "ON" && items[props.motionOnOff].state == "ON" &&  items[props.illumt].state > (items[props.illum].state | ".0") ) ? "green": "red"'
                          action: toggle
                          actionItem: =props.motionOnOff
                          actionCommand: ON
                          actionCommandAlt: OFF
                          visible: '=(props.motionOnOff) ? "true": "false"'
                      - component: oh-stepper-item
                        config:
                          title: Motion Timer
                          item: =props.mtimer
                          footer: in Minutes
                          color: '=(items[props.motionOnOff].state == "ON") ? "blue": "gray"'
                          fill: true
                          round: true
                          autorepeat: true
                          autorepeatDynamic: true
                          small: true
                          min: 1
                          max: 600
                          icon: f7:timer
                          visible: "=(props.mtimer) ? true: false"
                      - component: oh-stepper-item
                        config:
                          title: lux Threshold
                          item: =props.illumt
                          color: '=(items[props.illumt].state < (items[props.illum].state | ".0")) ? "gray": (items[props.motionOnOff].state == "OFF") ? "gray": "green"'
                          fill: true
                          round: true
                          autorepeat: true
                          autorepeatDynamic: true
                          small: true
                          max: 600
                          icon: f7:light_max
                          footer: ="Currently " + items[props.illum].displayState
                          visible: "=(props.illumt) ? true: false"
                      - component: oh-stepper-item
                        config:
                          title: Auto Off
                          item: =props.oTtimer
                          footer: in Minutes
                          color: '=(items[props.oTtimer].state == "0") ? "gray": "blue"'
                          fill: true
                          round: true
                          autorepeat: true
                          autorepeatDynamic: true
                          small: true
                          min: 0
                          max: 600
                          icon: f7:timer
                          visible: "=(props.oTtimer) ? true: false"

The motion detection and Auto Off time should never be operating at the same time, so I have a rule to turn off the appropriate timer when the other one is interacted with -

rule "Lights, Kitchen Sink - 'On Time' turns OFF 'Motion Detection'"
when
    Item Kitchen_Sink_Ceiling_Light_OnTime changed from 0
then
    if (newState > 0 && Kitchen_Sink_Ceiling_Light_MotionOnOff.state == ON) {
        Kitchen_Sink_Ceiling_Light_MotionOnOff.sendCommand(OFF)
        logInfo("Lights - Kitchen Sink", "'On Time' turned ON, so Motion Detection is turning OFF")
    }
end

rule "Lights, Kitchen Sink - 'Motion Detection' turns OFF 'On Time'"
when
    Item Kitchen_Sink_Ceiling_Light_MotionOnOff changed to ON
then
    if (Kitchen_Sink_Ceiling_Light_OnTime.state != 0) {
        Kitchen_Sink_Ceiling_Light_OnTime.sendCommand(0)
        logInfo("Lights - Kitchen Sink", "'Motion Detection' turned ON, so 'On Time' set to 0 min")
    }
end

rule "Lights, Understairs - 'On Time' turns OFF 'Motion Detection'"
when
    Item Hall_Understairs_Lamp_OnTime changed from 0
then
    if (Understairs_MotionLightSensor_OnOff.state == ON) {
        Understairs_MotionLightSensor_OnOff.sendCommand(OFF)
        logInfo("Lights - Understairs", "'On Time' turned ON, so Motion Detection is turning OFF")
    }
end

rule "Lights, Understairs - 'Motion Detection' turns OFF 'On Time'"
when
    Item Understairs_MotionLightSensor_OnOff changed to ON
then
    if (Hall_Understairs_Lamp_OnTime.state != 0) {
        Hall_Understairs_Lamp_OnTime.sendCommand(0)
        logInfo("Lights - Understairs", "'Motion Detection' turned ON, so 'On Time' set to 0 min")
    }
end

rule "Lights, Kitchen Endboard - 'On Time' turns OFF 'Motion Detection'"
when
    Item Kitchen_Endboard_Ceiling_Light_OnTime changed from 0
then
    if (newState != 0 && Kitchen_Endboard_Ceiling_Light_MotionOnOff.state == ON) {
        Kitchen_Endboard_Ceiling_Light_MotionOnOff.sendCommand(OFF)
        logInfo("Lights - Kitchen Endboard", "'On Time' turned ON, so Motion Detection is turning OFF")
    }
end

rule "Lights, Kitchen Endboard - 'Motion Detection' turns OFF 'On Time'"
when
    Item Kitchen_Endboard_Ceiling_Light_MotionOnOff changed to ON
then
    if (Kitchen_Endboard_Ceiling_Light_OnTime.state != 0) {
        Kitchen_Endboard_Ceiling_Light_OnTime.sendCommand(0)
        logInfo("Lights - Kitchen Endboard", "'Motion Detection' turned ON, so 'On Time' set to 0 min")
    }
end

rule "Lights, Hall - 'On Time' turns OFF 'Motion Detection'"
when
    Item Hall_Ceiling_Light_OnTime changed from 0
then
    if (newState != 0 && Hall_Ceiling_Light_MotionOnOff.state == ON) {
        Hall_Ceiling_Light_MotionOnOff.sendCommand(OFF)
        logInfo("Lights - Hall", "'On Time' turned ON, so Motion Detection is turning OFF")
    }
end

rule "Lights, Hall - 'Motion Detection' turns OFF 'On Time'"
when
    Item Hall_Ceiling_Light_MotionOnOff changed to ON
then
    if (Hall_Ceiling_Light_OnTime.state != 0) {
        Hall_Ceiling_Light_OnTime.sendCommand(0)
        logInfo("Lights - Hall", "'Motion Detection' turned ON, so 'On Time' set to 0 min")
    }
end

rule "Lights, Back Hall - 'On Time' turns OFF 'Motion Detection'"
when
    Item BackHall_Ceiling_Light_OnTime changed from 0
then
    if (newState != 0 && BackHall_Ceiling_Light_MotionOnOff.state == ON) {
        BackHall_Ceiling_Light_MotionOnOff.sendCommand(OFF)
        logInfo("Lights - Back Hall", "'On Time' turned ON, so Motion Detection is turning OFF")
    }
end

rule "Lights, Back Hall - 'Motion Detection' turns OFF 'On Time'"
when
    Item BackHall_Ceiling_Light_MotionOnOff changed to ON
then
    if (BackHall_Ceiling_Light_OnTime.state != 0) {
        BackHall_Ceiling_Light_OnTime.sendCommand(0)
        logInfo("Lights - Back Hall", "'Motion Detection' turned ON, so 'On Time' set to 0 min")
    }
end

rule "Lights, Kitchen Under Cabinet - 'Motion Detection' turns OFF 'On Time'"
when
    Item Kitchen_UnderCabinet_Light_MotionOnOff changed to ON
then
    if (Kitchen_UnderCabinet_Light_OnTime.state != 0) {
        Kitchen_UnderCabinet_Light_OnTime.sendCommand(0)
        logInfo("Lights - Kitchen Under Cabinet", "'Motion Detection' turned ON, so 'On Time' set to 0 min")
    }
end

rule "Lights, Kitchen Under Cabinet - 'On Time' turns OFF 'Motion Detection'"
when
    Item Kitchen_UnderCabinet_Light_OnTime changed from 0
then
    if (newState != 0 && Kitchen_UnderCabinet_Light_MotionOnOff.state == ON) {
        Kitchen_UnderCabinet_Light_MotionOnOff.sendCommand(OFF)
        logInfo("Lights - Kitchen Under Cabinet", "'On Time' turned ON, so Motion Detection is turning OFF")
    }
end

These are the rules that make ‘On Time’ work -

var Timer timer_backhall = null
var Timer timer_bathroom = null
var Timer timer_hall = null
var Timer timer_frontdoor = null
var Timer timer_kEndboard = null
var Timer timer_kSink = null
var Timer timer_landing = null
var Timer timer_jBed = null
var Timer timer_jCeiling = null

rule "Timer Light - Back Hall"
when
    Item BackHall_Ceiling_Light_Brightness changed from OFF to ON 
then
    val bright = BackHall_Ceiling_Light_Brightness
    val timerItem = BackHall_Ceiling_Light_OnTime

    if (timerItem.state > 0) {    
        if (timer_backhall === null) {
            // logInfo("Timer Lights - " + getLocation(bright).label, "Motion detected") 

                timer_backhall = createTimer(now.plusMinutes(Integer::parseInt(timerItem.state.toString)), [ | 
                    bright.sendCommand(OFF)
                    timer_backhall.cancel()
                    timer_backhall = null
                    logInfo("Timer Lights - " + getLocation(bright).label, "Timer elapsed. " + getLocation(bright).label + " " + getEquipment(bright).label + " turned OFF")
                ])
                logInfo("Timer Lights - " + getLocation(bright).label, timerItem.state + " min Timer started")

        } else if (timer_backhall !== null) {
            timer_backhall.reschedule(now.plusMinutes(Integer::parseInt(timerItem.state.toString)))
            logInfo("Timer Lights - " + getLocation(bright).label, timerItem.state + " min Timer rescheduled")
        } 
    }
end

rule "Timer Light - Bath Room"
when
    Item BathRoom_Ceiling_Light_Brightness changed from OFF to ON 
then
    val bright = BathRoom_Ceiling_Light_Brightness
    val timerItem = BathRoom_Ceiling_Light_OnTime

    if (timerItem.state > 0) {    
        if (timer_bathroom === null) {
            // logInfo("Timer Lights - " + getLocation(bright).label, "Motion detected") 

                timer_bathroom = createTimer(now.plusMinutes(Integer::parseInt(timerItem.state.toString)), [ | 
                    bright.sendCommand(OFF)
                    timer_bathroom.cancel()
                    timer_bathroom = null
                    logInfo("Timer Lights - " + getLocation(bright).label, "Timer elapsed. " + getLocation(bright).label + " " + getEquipment(bright).label + " turned OFF")
                ])
                logInfo("Timer Lights - " + getLocation(bright).label, timerItem.state + " min Timer started")

        } else if (timer_bathroom !== null) {
            timer_bathroom.reschedule(now.plusMinutes(Integer::parseInt(timerItem.state.toString)))
            logInfo("Timer Lights - " + getLocation(bright).label, timerItem.state + " min Timer rescheduled")
        } 
    }
end

rule "Timer Light - Front Door"
when
    Item Hall_FrontDoor_Light_Brightness changed from OFF to ON 
then
    val bright = Hall_FrontDoor_Light_Brightness
    val timerItem = Hall_FrontDoor_Light_OnTime

    if (timerItem.state > 0) {    
        if (timer_frontdoor === null) {
            // logInfo("Timer Lights - " + getLocation(bright).label, "Motion detected") 

                timer_frontdoor = createTimer(now.plusMinutes(Integer::parseInt(timerItem.state.toString)), [ | 
                    bright.sendCommand(OFF)
                    timer_frontdoor.cancel()
                    timer_frontdoor = null
                    logInfo("Timer Lights - " + getLocation(bright).label, "Timer elapsed. " + getLocation(bright).label + " " + getEquipment(bright).label + " turned OFF")
                ])
                logInfo("Timer Lights - " + getLocation(bright).label, timerItem.state + " min Timer started")

        } else if (timer_frontdoor !== null) {
            timer_frontdoor.reschedule(now.plusMinutes(Integer::parseInt(timerItem.state.toString)))
            logInfo("Timer Lights - " + getLocation(bright).label, timerItem.state + " min Timer rescheduled")
        } 
    }
end

rule "Timer Light - Hall Ceiling Light"
when
    Item Hall_Ceiling_Light_Brightness changed from OFF to ON 
then
    val bright = Hall_Ceiling_Light_Brightness
    val timerItem = Hall_Ceiling_Light_OnTime

    if (timerItem.state > 0) {    
        if (timer_hall === null) {
            // logInfo("Timer Lights - " + getLocation(bright).label, "Motion detected") 

                timer_hall = createTimer(now.plusMinutes(Integer::parseInt(timerItem.state.toString)), [ | 
                    bright.sendCommand(OFF)
                    timer_hall.cancel()
                    timer_hall = null
                    logInfo("Timer Lights - " + getLocation(bright).label, "Timer elapsed. " + getLocation(bright).label + " " + getEquipment(bright).label + " turned OFF")
                ])
                logInfo("Timer Lights - " + getLocation(bright).label, timerItem.state + " min Timer started")

        } else if (timer_hall !== null) {
            timer_hall.reschedule(now.plusMinutes(Integer::parseInt(timerItem.state.toString)))
            logInfo("Timer Lights - " + getLocation(bright).label, timerItem.state + " min Timer rescheduled")
        } 
    }
end

rule "Timer Light - Landing Ceiling Light"
when
    Item Landing_Ceiling_Light_Brightness changed from OFF to ON 
then
    val bright = Landing_Ceiling_Light_Brightness
    val timerItem = Landing_Ceiling_Light_OnTime

    if (timerItem.state > 0) {    
        if (timer_landing === null) {
            // logInfo("Timer Lights - " + getLocation(bright).label, "Motion detected") 

                timer_landing = createTimer(now.plusMinutes(Integer::parseInt(timerItem.state.toString)), [ | 
                    bright.sendCommand(OFF)
                    timer_landing.cancel()
                    timer_landing = null
                    logInfo("Timer Lights - " + getLocation(bright).label, "Timer elapsed. " + getLocation(bright).label + " " + getEquipment(bright).label + " turned OFF")
                ])
                logInfo("Timer Lights - " + getLocation(bright).label, timerItem.state + " min Timer started")

        } else if (timer_landing !== null) {
            timer_landing.reschedule(now.plusMinutes(Integer::parseInt(timerItem.state.toString)))
            logInfo("Timer Lights - " + getLocation(bright).label, timerItem.state + " min Timer rescheduled")
        } 
    }
end

rule "Timer Light - Jimmy's Bed Lamp"
when
    Item JimmysRoom_Bedside_Lamp_OnOff changed from OFF to ON 
then
    val bright = JimmysRoom_Bedside_Lamp_OnOff
    val timerItem = JimmysRoom_Bedside_Lamp_OnTime

    if (timerItem.state > 0) {    
        if (timer_jBed === null) {
            // logInfo("Timer Lights - " + getLocation(bright).label, "Motion detected") 

                timer_jBed = createTimer(now.plusMinutes(Integer::parseInt(timerItem.state.toString)), [ | 
                    bright.sendCommand(OFF)
                    timer_jBed.cancel()
                    timer_jBed = null
                    logInfo("Timer Lights - " + getLocation(bright).label, "Timer elapsed. " + getLocation(bright).label + " " + getEquipment(bright).label + " turned OFF")
                ])
                logInfo("Timer Lights - " + getLocation(bright).label, timerItem.state + " min Timer started")

        } else if (timer_jBed !== null) {
            timer_jBed.reschedule(now.plusMinutes(Integer::parseInt(timerItem.state.toString)))
            logInfo("Timer Lights - " + getLocation(bright).label, timerItem.state + " min Timer rescheduled")
        } 
    }
end

rule "Timer Light - Jimmy's Ceiling Light"
when
    Item JimmysRoom_Ceiling_Light_Brightness changed from OFF to ON 
then
    val bright = JimmysRoom_Ceiling_Light_Brightness
    val timerItem = JimmysRoom_Ceiling_Light_OnTime

    if (timerItem.state > 0) {    
        if (timer_jCeiling === null) {
            // logInfo("Timer Lights - " + getLocation(bright).label, "Motion detected") 

                timer_jCeiling = createTimer(now.plusMinutes(Integer::parseInt(timerItem.state.toString)), [ | 
                    bright.sendCommand(OFF)
                    timer_jCeiling.cancel()
                    timer_jCeiling = null
                    logInfo("Timer Lights - " + getLocation(bright).label, "Timer elapsed. " + getLocation(bright).label + " " + getEquipment(bright).label + " turned OFF")
                ])
                logInfo("Timer Lights - " + getLocation(bright).label, timerItem.state + " min Timer started")

        } else if (timer_jCeiling !== null) {
            timer_jCeiling.reschedule(now.plusMinutes(Integer::parseInt(timerItem.state.toString)))
            logInfo("Timer Lights - " + getLocation(bright).label, timerItem.state + " min Timer rescheduled")
        } 
    }
end

These are the rules that make the Motion detection work -

var Timer timer_backhall = null
var Timer timer_hall = null
var Timer timer_kEndboard = null
var Timer timer_kSink = null
var Timer timer_kCabinets = null

rule "Motion Light - Back Hall"
when
    Item BackHall_MotionLightSensor_Motion received update ON 
then
    val bright = BackHall_Ceiling_Light_Brightness
    val sceneB = BackHall_Ceiling_Light_SceneBrightness
    val ctemp = BackHall_Ceiling_Light_ColorTemperature
    val illum = BackHall_MotionLightSensor_Illuminance
    val illumt = BackHall_Ceiling_Light_IlluminanceThreshold
    val timerItem = BackHall_MotionLightSensor_Timer
    val onOff = BackHall_Ceiling_Light_MotionOnOff

    if (onOff.state == ON) {    
        if (timer_backhall === null) {
            logInfo("Motion Lights - " + getLocation(bright).label, "Motion detected") 

            if ((illum.state as Number) <= (illumt.state as Number)) {
                logInfo("Motion Lights - " + getLocation(bright).label, "Illuminance (" + illum.state + ") is below Threshold (" + illumt.state + "). Turning " + getLocation(bright).label + " " + getEquipment(bright).label + " ON")
                ctemp.sendCommand(Calculated_ColorTemperature.state.toString)
                if (bright.state != sceneB.state) {
                    bright.sendCommand(sceneB.state.toString)
                }

                timer_backhall = createTimer(now.plusMinutes(Integer::parseInt(timerItem.state.toString)), [ | 
                    bright.sendCommand(OFF)
                    timer_backhall.cancel()
                    timer_backhall = null
                    logInfo("Motion Lights - " + getLocation(bright).label, "Timer elapsed. " + getLocation(bright).label + " " + getEquipment(bright).label + " turned OFF")
                ])
                logInfo("Motion Lights - " + getLocation(bright).label, timerItem.state + " min Timer started")

            } else {
                logInfo("Motion Lights - " + getLocation(bright).label, "Illuminance (" + illum.state + ") is above Threshold (" + illumt.state + "). Not turning " + getLocation(bright).label + " " + getEquipment(bright).label + " ON")
            }

        } else if (timer_backhall !== null) {
            timer_backhall.reschedule(now.plusMinutes(Integer::parseInt(timerItem.state.toString)))
            logInfo("Motion Lights - " + getLocation(bright).label, timerItem.state + " min Timer rescheduled")
        } 

        Kitchen_Endboard_MotionLightSensor_Motion.sendCommand(ON)
        Kitchen_Endboard_MotionLightSensor_Motion.sendCommand(OFF)

    }
end

rule "Motion Light - Hall Ceiling Light"
when
    Item Hall_MotionLightSensor_Motion received update ON 
then
    val bright = Hall_Ceiling_Light_Brightness
    val sceneB = Hall_Ceiling_Light_SceneBrightness
    val ctemp = Hall_Ceiling_Light_ColorTemperature
    val illum = Hall_MotionLightSensor_Illuminance
    val illumt = Hall_Ceiling_Light_IlluminanceThreshold
    val motion = Hall_MotionLightSensor_Motion
    val timerItem = Hall_MotionLightSensor_Timer
    val onOff = Hall_Ceiling_Light_MotionOnOff
    
    if (onOff.state == ON) {    
        if (timer_hall === null) {
            logInfo("Motion Lights - " + getLocation(bright).label, "Motion detected") 

            if ((illum.state as Number) <= (illumt.state as Number)) {
                logInfo("Motion Lights - " + getLocation(bright).label, "Illuminance (" + illum.state + ") is below Threshold (" + illumt.state + "). Turning " + getLocation(bright).label + " " + getEquipment(bright).label + " ON")
                ctemp.sendCommand(Calculated_ColorTemperature.state.toString)
                if (bright.state != sceneB.state) {
                    bright.sendCommand(sceneB.state.toString)
                }
                

                timer_hall = createTimer(now.plusMinutes(Integer::parseInt(timerItem.state.toString)), [ | 
                    bright.sendCommand(OFF)
                    timer_hall.cancel()
                    timer_hall = null
                    logInfo("Motion Lights - " + getLocation(bright).label, "Timer elapsed. " + getLocation(bright).label + " " + getEquipment(bright).label + " turned OFF")
                    if (motion.state == ON) {
                        motion.sendCommand(OFF)
                    }
                ])
                logInfo("Motion Lights - " + getLocation(bright).label, timerItem.state + " min Timer started")

            } else {
                logInfo("Motion Lights - " + getLocation(bright).label, "Illuminance (" + illum.state + ") is above Threshold (" + illumt.state + "). Not turning " + getLocation(bright).label + " " + getEquipment(bright).label + " ON")
            }

        } else if (timer_hall !== null) {
            timer_hall.reschedule(now.plusMinutes(Integer::parseInt(timerItem.state.toString)))
            logInfo("Motion Lights - " + getLocation(bright).label, timerItem.state + " min Timer rescheduled")
        } 
    }
end

rule "Motion Light - Kitchen Endboard"
when
    Item Kitchen_Endboard_MotionLightSensor_Motion received update ON 
then
    val bright = Kitchen_Endboard_Ceiling_Light_Brightness
    val sceneB = Kitchen_Endboard_Ceiling_Light_SceneBrightness
    val ctemp = Kitchen_Endboard_Ceiling_Light_ColorTemperature
    val illum = Kitchen_Endboard_MotionLightSensor_Illuminance
    val illumt = Kitchen_Endboard_Ceiling_Light_IlluminanceThreshold
    val timerItem = Kitchen_Endboard_MotionLightSensor_Timer
    val onOff = Kitchen_Endboard_Ceiling_Light_MotionOnOff

    if (onOff.state == ON) {    
        if (timer_kEndboard === null) {
            logInfo("Motion Lights - " + getLocation(bright).label + " " + getEquipment(bright).label, "Motion detected") 

            if ((illum.state as Number) <= (illumt.state as Number)) {
                logInfo("Motion Lights - " + getLocation(bright).label + getEquipment(illum).label, "Illuminance (" + illum.state + ") is below Threshold (" + illumt.state + "). Turning " + getLocation(bright).label + " " + getEquipment(bright).label + " ON")
                ctemp.sendCommand(Calculated_ColorTemperature.state.toString)
                if (bright.state != sceneB.state) {
                    bright.sendCommand(sceneB.state.toString)
                }

                timer_kEndboard = createTimer(now.plusMinutes(Integer::parseInt(timerItem.state.toString)), [ |
                    bright.sendCommand(OFF)
                    timer_kEndboard.cancel()
                    timer_kEndboard = null
                    logInfo("Motion Lights - " + getLocation(bright).label + " " + getEquipment(bright).label, "Timer elapsed. " + getLocation(bright).label + " " + getEquipment(bright).label + " turned OFF")
                ])
                logInfo("Motion Lights - " + getLocation(bright).label + " " + getEquipment(bright).label, timerItem.state + " min Timer started")

            } else {
                logInfo("Motion Lights - " + getLocation(bright).label + " " + getEquipment(illum).label, "Illuminance (" + illum.state + ") is above Threshold (" + illumt.state + "). Not turning " + getLocation(bright).label + " " + getEquipment(bright).label + " ON")
            }

        } else if (timer_kEndboard !== null) {
            timer_kEndboard.reschedule(now.plusMinutes(Integer::parseInt(timerItem.state.toString)))
            logInfo("Motion Lights - " + getLocation(bright).label + " " + getEquipment(bright).label, timerItem.state + " min Timer rescheduled")
        } 
    }
end




rule "Motion Light - Kitchen Sink"
when
    Item Kitchen_Sink_MotionLightSensor_Motion received update ON 
then
    val bright = Kitchen_Sink_Ceiling_Light_Brightness
    val sceneB = Kitchen_Sink_Ceiling_Light_SceneBrightness
    val ctemp = Kitchen_Sink_Ceiling_Light_ColorTemperature
    val illum = Kitchen_Sink_MotionLightSensor_Illuminance
    val illumt = Kitchen_Sink_Ceiling_Light_IlluminanceThreshold
    val timerItem = Kitchen_Sink_MotionLightSensor_Timer
    val onOff = Kitchen_Sink_Ceiling_Light_MotionOnOff

    if (onOff.state == ON) {    
        if (timer_kSink === null) {
            logInfo("Motion Lights - " + getLocation(bright).label + " " + getEquipment(bright).label, "Motion detected") 

            if ((illum.state as Number) <= (illumt.state as Number)) {
                logInfo("Motion Lights - " + getLocation(bright).label + getEquipment(illum).label, "Illuminance (" + illum.state + ") is below Threshold (" + illumt.state + "). Turning " + getLocation(bright).label + " " + getEquipment(bright).label + " ON")
                ctemp.sendCommand(Calculated_ColorTemperature.state.toString)
                if (bright.state != sceneB.state) {
                    bright.sendCommand(sceneB.state.toString)
                }

                timer_kSink = createTimer(now.plusMinutes(Integer::parseInt(timerItem.state.toString)), [ | 
                    bright.sendCommand(OFF)
                    timer_kSink.cancel()
                    timer_kSink = null
                    logInfo("Motion Lights - " + getLocation(bright).label + " " + getEquipment(bright).label, "Timer elapsed. " + getLocation(bright).label + " " + getEquipment(bright).label + " turned OFF")
                ])
                logInfo("Motion Lights - " + getLocation(bright).label + " " + getEquipment(bright).label, timerItem.state + " min Timer started")

            } else {
                logInfo("Motion Lights - " + getLocation(bright).label + " " + getEquipment(bright).label, "Illuminance (" + illum.state + ") is above Threshold (" + illumt.state + "). Not turning " + getLocation(bright).label + " " + getEquipment(bright).label + " ON")
            }

        } else if (timer_kSink !== null) {
            timer_kSink.reschedule(now.plusMinutes(Integer::parseInt(timerItem.state.toString)))
            logInfo("Motion Lights - " + getLocation(bright).label + " " + getEquipment(bright).label, timerItem.state + " min Timer rescheduled")
        }
    } 
end

rule "Motion Light - Kitchen Under Cabinet"
when
    Item Kitchen_Sink_MotionLightSensor_Motion received update ON 
then
    val bright = Kitchen_UnderCabinet_Light_Brightness
    val sceneB = Kitchen_UnderCabinet_Light_SceneBrightness
    val ctemp = Kitchen_UnderCabinet_Light_ColorTemperature
    val illum = Kitchen_Sink_MotionLightSensor_Illuminance
    val illumt = Kitchen_UnderCabinet_Light_IlluminanceThreshold
    val timerItem = Kitchen_Sink_MotionLightSensor_Timer
    val onOff = Kitchen_UnderCabinet_Light_MotionOnOff

    if (onOff.state == ON) {    
        if (timer_kCabinets === null) {
            logInfo("Motion Lights - " + getLocation(bright).label + " " + getEquipment(bright).label, "Motion detected") 

            if ((illum.state as Number) <= (illumt.state as Number)) {
                logInfo("Motion Lights - " + getLocation(bright).label + getEquipment(illum).label, "Illuminance (" + illum.state + ") is below Threshold (" + illumt.state + "). Turning " + getLocation(bright).label + " " + getEquipment(bright).label + " ON")
                ctemp.sendCommand(Calculated_ColorTemperature.state.toString)
                if (bright.state != sceneB.state) {
                    bright.sendCommand(sceneB.state.toString)
                }

                timer_kCabinets = createTimer(now.plusMinutes(Integer::parseInt(timerItem.state.toString)), [ | 
                    bright.sendCommand(OFF)
                    timer_kCabinets.cancel()
                    timer_kCabinets = null
                    logInfo("Motion Lights - " + getLocation(bright).label + " " + getEquipment(bright).label, "Timer elapsed. " + getLocation(bright).label + " " + getEquipment(bright).label + " turned OFF")
                ])
                logInfo("Motion Lights - " + getLocation(bright).label + " " + getEquipment(bright).label, timerItem.state + " min Timer started")

            } else {
                logInfo("Motion Lights - " + getLocation(bright).label + " " + getEquipment(bright).label, "Illuminance (" + illum.state + ") is above Threshold (" + illumt.state + "). Not turning " + getLocation(bright).label + " " + getEquipment(bright).label + " ON")
            }

        } else if (timer_kCabinets !== null) {
            timer_kCabinets.reschedule(now.plusMinutes(Integer::parseInt(timerItem.state.toString)))
            logInfo("Motion Lights - " + getLocation(bright).label + " " + getEquipment(bright).label, timerItem.state + " min Timer rescheduled")
        }
    } 
end


PROBLEMS
It’s working quite well but there is a few things I can’t get right:

1-
The two slider items hit 100% only about 1/4 of the total length of the slider ** I have temporarily changed my widget to use steppers until I can figure this out.

I’m sure it has something to do with the column width of something above, but I can’t figure it out.

2-
When the window expands on my iPhone 12, because of the way the fancy screen goes up into the very top of the phone, the closing X button is obscured by my iPhone time and other icons that live in this space.

2 Likes

I really like this and I started to use it. However I have one major issue with it. Blur.
Any idea why this happens? BTW this screenshot is not edited!

But it works like this

Not sure. I think that happens in the editor, but works in when loaded in a real page. It also seems to get messed up if you resize the window when open.

I think you can turn off the blur by removing backdrop: true

Let me know if you figure out why the sliders are glitchy. I’ve swapped over to a stepper until I can figure out the sliders.

This topic was automatically closed 41 days after the last reply. New replies are no longer allowed.