Light Widget with temperature control

This is nothing new really but a simpler version of my color/white light widget that you can find here:
https://community.openhab.org/t/oh3-color-white-bulb-widget/118332/

light2
Left: Color widget - Right: White widget (Timer not configured here)

This version of the widget is used to control lightbulbs/led-stripes without a color feature. I therefore removed the color mode switch button and made the following other changes that had to be done because I use a different binding underneath:

  • The widget does not require a switch item to turn the lights on or off but simply relies on the brightness channel (I use the color widget to control a miio thing and with this binding you need to use a switch. Here I use the espmigliht binding that simply works with the brightness channel).
  • The slider for the temperature is now a percentage slider as opposed to the color temperature slider (again different binding implementations). To keep this as similar as possible to the color widget I still use the color temperature as a label with the following code (you can find this part in the code below): 2700 + ((71 - 27) * items[props.temperature].state) + "K". This code is designed to work for a light with a K-range from 2700 to 7100. Change values according to the hardware you use. The logic behind this is: <LOWEST VALUE> + ((<HIGHEST VALUE / 100> - <LOWEST VALUE / 100>) * items[props.temperature].state) + "K" You will still see values from 0 to 100% in the slider label when you move it which is the only difference compared to the color widget slider.

And that’s it. The rest of the widget works like the color widget!

Code

uid: white_light_v1
tags: []
props:
  parameters:
    - description: Header text
      label: Header
      name: header
      required: false
      type: TEXT
    - context: item
      description: Brightness
      label: Brightness
      name: brightness
      required: true
      type: TEXT
    - context: item
      description: Temperature in K
      label: Temperature
      name: temperature
      required: true
      type: TEXT
    - context: item
      description: Timer
      label: Shutdown Timer Item
      name: shutdowntimer
      required: false
      type: TEXT
  parameterGroups: []
timestamp: Mar 26, 2021, 12:57:40 PM
component: f7-card
slots:
  default:
    - component: f7-card-content
      config:
        class:
          - display-flex
          - flex-direction-column
          - justify-content-flex-start
          - align-items-center
        style:
          height: 175px
      slots:
        default:
          - component: f7-row
            config:
              style:
                position: absolute
            slots:
              default:
                - component: f7-block-header
                  slots:
                    default:
                      - component: Label
                        config:
                          text: =props.header
          - component: f7-block
            config:
              class:
                - no-padding
              style:
                width: 100%
                height: 100%
                margin-top: 2px
            slots:
              default:
                - component: f7-block
                  config:
                    class:
                      - display-flex
                      - flex-direction-column
                      - justify-content-flex-end
                    style:
                      animation: f7-fade-in 300ms
                      height: 60%
                      margin-top: 10px
                  slots:
                    default:
                      - component: f7-row
                        config:
                          class:
                            - display-flex
                            - justify-content-space-between
                            - align-items-center
                          style:
                            width: calc(100% + 20px)
                            margin-left: -10px
                        slots:
                          default:
                            - component: f7-icon
                              config:
                                f7: thermometer
                                size: 20
                                style:
                                  color: var(--f7-block-header-text-color)
                            - component: Label
                              config:
                                text: =2700 + ((71 - 27) * items[props.temperature].state) + "K"
                                style:
                                  color: var(--f7-block-header-text-color)
                      - component: f7-row
                        config:
                          class:
                            - display-flex
                            - justify-content-center
                            - align-items-center
                          style:
                            width: 100%
                        slots:
                          default:
                            - component: oh-slider
                              config:
                                color: white
                                label: true
                                min: 0
                                max: 100
                                item: =props.temperature
                                unit: "%"
                                style:
                                  --f7-range-bar-size: 18px
                                  --f7-range-bar-border-radius: 10px
                                  --f7-range-knob-size: 20px
                                  --f7-range-bar-active-bg-color: transparent
                                  --f7-range-bar-bg-color: linear-gradient(to right, rgba(246,158,81,0.8), rgba(246,158,81,0))
                                  --f7-range-knob-box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3)
                                  --f7-range-label-text-color: black
                                  width: 100%
                                  z-index: 99 !important
                      - component: f7-row
                        config:
                          class:
                            - display-flex
                            - justify-content-space-between
                            - align-items-center
                          style:
                            width: calc(100% + 20px)
                            margin-left: -10px
                        slots:
                          default:
                            - component: f7-icon
                              config:
                                f7: sun_min
                                size: 20
                                style:
                                  color: var(--f7-block-header-text-color)
                            - component: Label
                              config:
                                text: =items[props.brightness].state + "%"
                                style:
                                  color: var(--f7-block-header-text-color)
                      - component: f7-row
                        config:
                          class:
                            - display-flex
                            - justify-content-center
                            - align-items-center
                          style:
                            width: 100%
                        slots:
                          default:
                            - component: oh-slider
                              config:
                                color: white
                                label: true
                                item: =props.brightness
                                style:
                                  --f7-range-bar-size: 18px
                                  --f7-range-bar-border-radius: 10px
                                  --f7-range-knob-size: 20px
                                  --f7-range-bar-active-bg-color: rgba(246,246,0,0.5)
                                  --f7-range-bar-bg-color: linear-gradient(to right, rgba(169,169,169,0.8), rgba(246,158,81,0))
                                  --f7-range-knob-box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3)
                                  --f7-range-label-text-color: black
                                  width: 100%
                                  z-index: 99 !important
          - component: f7-block
            config:
              class:
                - display-flex
                - justify-content-center
                - align-items-center
                - no-padding
                - no-margin
              style:
                position: absolute
                width: 40px
                height: 40px
                left: 10px
                bottom: 10px
                border-top: '=items[props.brightness].state > 0 ? "none" : "2px solid #ddd"'
                box-shadow: "inset 0px 1px 2px #eee"
                background: white
                border-radius: 50%
            slots:
              default:
                - component: f7-block
                  config:
                    class:
                      - no-margin
                    style:
                      animation: '=items[props.shutdowntimer].state.split(" ")[0] > 0 ? "skeleton-effect-fade 2s linear infinite" : "none"'
                      position: absolute
                      width: 100%
                      height: 100%
                      box-shadow: '=items[props.brightness].state > 0 ? "0 0 20px #fff, 0px 0px 30px rgba(0,255,0,0.5)" : "none"'
                      border-radius: 50%
                      transition: transform 0.2s
                      background: '=items[props.brightness].state > 0 ? "rgba(124, 252, 0, 0.5)" : "transparent"'
                      transform: '=items[props.brightness].state > 0 ? "scale(1,1)" : "scale(0,0)"'
                - component: f7-block
                  config:
                    style:
                      position: absolute
                      width: calc(100% - 10px)
                      height: calc(100% - 10px)
                      background: rgba(255,255,255,0.8)
                      border-radius: 50%
                - component: oh-link
                  config:
                    iconOnly: true
                    iconF7: power
                    iconSize: 17
                    action: toggle
                    actionItem: =props.brightness
                    actionCommand: '=items[props.brightness].state > 0 ? "OFF" : "ON"'
                    style:
                      border: solid 2pt white
                      border-radius: 50%
                      background-color: "#f7f7f7"
                      background-image: "linear-gradient(135deg, #f7f7f7, #e7e7e7)"
                      color: "#a7a7a7"
                      width: calc(100% - 10px)
                      height: calc(100% - 10px)
                      text-align: center
                      box-shadow: 0px 3px 8px
                      backdrop-filter: opacity(88%)
                      z-index: 99 !important
          - component: oh-link
            config:
              text: Timer
              visible: "=props.shutdowntimer ? true : false"
              action: popover
              popoverOpen: .timerpopover
              style:
                position: absolute
                bottom: 10px
            slots:
              default:
                - component: f7-popover
                  config:
                    class:
                      - timerpopover
                  slots:
                    default:
                      - component: oh-stepper-card
                        config:
                          item: =props.shutdowntimer
                          title: Ausschalten in Minuten
    - component: f7-card-footer
      slots:
        default:
          - component: Label
            config:
              text: '=items[props.brightness].state > 0 ? "Licht an" : "Licht aus"'
6 Likes

Btw.: If you look closely at the GIF the temperature of the widget jumps a bit after a change. I don’t know yet whiy this happens but it must be binding specific… I’ll look into that at some point…

The “jump” you see is that you’re first setting the desired color temperature through your widget, and then it might be updated to reflect the actual value the binding returned.

For instance, there may be 100, 256 or 1024 discrete steps that your Thing's channel supports. Eventually the widget will reflect the actual state of your Thing's channel, which may require adjusting the desired value into the actual value.

Yes it makes sense what you’re saying. But I would still consider this being a bug (not necessarily in the binding code but could also be in the device). Anyway I don’t think that this could be fixed on my end.l so I’ll just live with it :man_shrugging:

I remember an issue with rounding up and rounding down when either reporting the luminosity of an IKEA Trader lightbulb or the remaining battery level in a Trader 5-button remote. This was eventually addressed in the binding. Maybe you could perform yourself the rounding up / down in the widget, or report an issue for the binding in case inconsistent state hopping occurs.

@matt1 maybe you could have a look at this? I’m seeing some jumps in the colour temperature of my milighthub controlled lamp. Do you know this behaviour?

Never seen it before, you should look at the mqtt messages as anything the binding does has to pass through MQTT to reach the HUB.

I just checked the messages. The command that are issues match the reported states. Apparently the color temperature ranges from 370 when set to 0 in OH (warm) to 153 (cold) when set to 100 in OH. So this might be a rounding issue somewhere?

This only happens for some percentage settings. For example when I set the temperature to 39% it jumps to 40% (mqtt command issued is: 285). So apparently there is no linear translation between percentage values of OH and the issued commands in mqtt (285 would be way more than 39/40% otherwise), so I’m a bit lost here…

Probably sometimes the values are rounded up, and other times they are rounded down.

See for instance the following discussion:

You can easily check the results by filling all values in a spreadsheet and apply the transformation equations until you get the proper result. This may have to be addressed at the binding level. Alternatively, you can live with this minor inconvenience.

By the way I can’t get you widget to work on openHAB 3.0.2. The JavaScript console complains about a problem with a slider object:

[Error] TypeError: undefined is not an object (evaluating ‘e.$refs.rangeslider.f7Range’)
(anonymous function) (app.js:9:76365)

I have a problem with the item not returning the state in Main UI. I invariably get "-" as state value for either the brightness item or the temperature item (both linked to channels from an IKEA Trådfri white spectrum lightbulb.

Is this a problem only in the widget? The items themselves do have a meaningful value?

The items do have a valid state. The state is appparently not propagated in openHAB though.

Hm… This sounds more like you have a problem to connect to the rest API from your MainUI. Is it working for other items?

Maybe the problem relates to using basic authentication for access control. I could try if implementing it on my nginx reverse proxy instead of on openHAB could be the fix.

I also get a SSE error in the JavaScript console if I try to test the SSE connection.

Solved: In API Security, leave the “implicit role for unauthenticated users” ticked.

See:

No that is a feature that you have turned on. Look at the things settings Dimmed Colour Temp leave it blank to disable the feature.

For IKEA Trådfri white spectrum bulbs, the item state is inverted from color temperature:

  • state = 0 → cold white (4000 K according to the manual with reference AA-1893572-1)
  • state = 50 → warm white (2700 K)
  • state = 100 → warm glow (2200 K)

Hence the slider goes from 4000 K (state = 0) to 2200 K (state = 100), which is counter-intuitive.

Is there a way to invert a slider in a widget, effectively returning “100 - state” instead of “state”? Maybe through a oh-slider parameter?

The field is blank in my case.

I just noticed that at some point the percentage values of the channel must be translated to this value range. This is where a different rounding mode may be used.

I filed a feature request to support inverted oh-slider logic: