Watertank Heating Overview

Hi,
I had a habpanel with my wishes that worked, meanwhile I want to change to mainUI, but I dont able to create widgets with my wishes.
Now I try to create a Heating Overview, but I didnt successful.
I attached a picture with my status and my wish and my latest code.
I hope someone can help me…

uid: Heizung
tags: []
props:
  parameters:
    - description: Title
      label: Title
      name: title
      required: false
      type: TEXT
    - context: item
      description: Temperature sensor at top
      label: Temp Top
      name: item_top
      required: false
      type: TEXT
  parameterGroups: []
timestamp: Feb 26, 2025, 1:46:11 PM
component: f7-card
config:
  class:
    - padding
  style:
    background-color: "=props.bgcolor ? props.bgcolor : ''"
    border-radius: var(--f7-card-expandable-border-radius)
    box-shadow: 5px 5px 10px 1px rgba(0,0,0,0.1)
    height: 120px
    margin-left: 5px
    margin-right: 5px
    noShadow: false
    padding: 0px
slots:
  default:
    - component: f7-block
      config:
        style:
          min-height: 180px
          max-height: 180px
          max-width: 120px
          min-width: 120px
          background: url(/static/boiler.png)
          background-size: cover
          background-position: right bottom
          display: flex
          flex-direction: column
          align-items: start
      slots:
        default:
          - component: f7-row
            config:
              class:
                - display-flex
                - justify-content-space-between
                - align-content-stretch
                - align-items-center
            slots:
              default:
                - component: f7-col
                  config:
                    class:
                      - display-flex
                      - flex-direction-column
                      - align-items-center
                  slots:
                    default:
                      - component: Label
                        config:
                          text: Puffer
                          style:
                            font-size: 12px
                      - component: f7-icon
                        config:
                          f7: rectangle_fill
                          color: '=(items[props.item_top].state.split(".")[0] < 46) ? "blue" : ((items[props.item_top].state.split(".")[0] < 60) ? "orange" : "red")'
                          size: 120
                          style:
                            top: 2px
                            left: 10px
                            transform: rotate(90deg)
                            filter: opacity(40%)
                      - component: oh-link
                        config:
                          text: =items[props.item_top].state + " °C"
                          color: '=(items[props.item_top].state.split(".")[0] < 46) ? "blue" : ((items[props.item_top].state.split(".")[0] < 60) ? "orange" : "red")'
                          action: analyzer
                          actionAnalyzerItems: =[props.item_top]
                          style:
                            top: 2px
                            font-size: 14px
                            font-weight: bold
                - component: f7-col
                  config:
                    class:
                      - display-flex
                      - flex-direction-column
                      - align-items-center
                  slots:
                    default:
                      - component: Label
                        config:
                          text: "Vorlauf :"
                          style:
                            font-size: 12px

uid: wid_Test_boiler
tags: []
props:
  parameters:
    - context: item
      default: Test_GroupOne_NumberItem
      description: item for Vorlauf
      label: Item
      name: itmVorlauf
      required: false
      type: TEXT
    - context: item
      default: Test_GroupOne_NumberItem
      description: item for Leistung
      label: Item
      name: itmLeistung
      required: false
      type: TEXT
    - default: /static/boiler.png
      description: /static/myImage.jpg
      label: Background image-url 
      name: backgroundUrl
      required: false
      type: TEXT
  parameterGroups: []
timestamp: Feb 26, 2025, 8:22:46 PM
component: f7-card
config:
  class:
    #- padding
  style:
    background-color: "=props.bgcolor ? props.bgcolor : ''"
    border-radius: var(--f7-card-expandable-border-radius)
    #
    box-shadow: 5px 5px 10px 1px rgba(0,0,0,0.1)
    height: 120px
    margin-left: 5px
    margin-right: 5px
    noShadow: false
    display: flex
    #padding: 0px
slots:
  default:
    - component: div
      config:
        style:
          background-image: "='url(' + props.backgroundUrl + ')'"
          #display: flex
          flex-grow: 1
          clip-path: ="inset( 0px round var(--f7-card-expandable-border-radius))"
          #min-height: 180px
          ##max-height: 180px
          #max-width: 120px
          #min-width: 120px
          #background-size: cover
          #background-position: right bottom
          #display: flex
          #flex-direction: column
          #align-items: start
    #- component: f7-card-content
    #  config:
    #    style:
    #      display: flex
    #      #background: lightgreen
      slots:
        default:
          - component: f7-row
            config:
              class:
                #- display-flex
                #- justify-content-space-between
                #- align-content-stretch
                #- align-items-center
              style:
                #display: flex
                background: yellow)
                filter: opacity(60%)
                align-items: stretch
                #flex-grow: 1
                #border-radius: var(--f7-card-expandable-border-radius)
                border-radius: 50px
            slots:
              default:
                - component: f7-col
                  config:
                    class:
                      - display-flex
                      #- flex-direction-column
                      - align-items-center
                      - justify-content-center
                    style:
                      background: lightgrey
                      filter: opacity(60%)
                  slots:
                    default:
                      - component: f7-col
                        config:
                          class:
                            - display-flex
                            - flex-direction-column
                            - align-items-center
                            - justify-content-center
                          style:
                            background: lightgrey
                            filter: opacity(60%)
                        slots:
                          default:
                            - component: Label
                              config:
                                text: Puffer
                                style:
                                  font-size: 12px
                            - component: f7-icon
                              config:
                                f7: rectangle_fill
                                color: "=(items[props.item_top].state.split('.')[0] < 46) ? 'blue' : ((items[props.item_top].state.split('.')[0] < 60) ? 'orange' : 'red')"
                                size: 6cqh
                                style:
                                  #top: 2px
                                  #left: 10px
                                  transform: rotate(90deg)
                                  filter: opacity(40%)
                      - component: f7-col
                        config:
                          class:
                            - display-flex
                            - flex-direction-column
                            - align-items-center
                            - justify-content-center
                          style:
                            background: lightgrey
                            filter: opacity(60%)
                        slots:
                          default:
                            - component: oh-link
                              config:
                                text: =items[props.item_top].state + ' °C'
                                color: "=(items[props.item_top].state.split('.')[0] < 46) ? 'blue' : ((items[props.item_top].state.split('.')[0] < 60) ? 'orange' : 'red')"
                                action: analyzer
                                actionAnalyzerItems: =[props.item_top]
                                style:
                                  #top: 2px
                                  #font-size: 14px
                                  font-weight: bold
                                  background: purple
                - component: f7-col
                  config:
                    class:
                      - display-flex
                      - flex-direction-column
                      - align-items-center
                      - justify-content-center
                    style:
                      background: lightgreen
                      filter: opacity(60%)
                  slots:
                    default:
                      - component: Label
                        config:
                          text: "='Vorlauf: ' + items[props.itmVorlauf].state + ' °C'"
                          style:
                            font-size: 12px
                      - component: Label
                        config:
                          text: "='Leistung: ' + items[props.itmLeistung].state + ' W'"
                          style:
                            font-size: 12px
                - component: f7-col
                  config:
                    class:
                      - display-flex
                      - flex-direction-column
                      - align-items-center
                    style:
                      background: lightblue
                      filter: opacity(60%)
                  slots:
                    default:
                      - component: oh-button
                        config:
                          class:
                            - display-flex
                            - flex-direction-column
                            - align-items-center
                            - justify-content-center
                          style:
                            flex-grow: 1
                        slots:
                          default:
                            - component: Label
                              config:
                                text: 'Heizung: '
                                style:
                                  font-size: 12px
                            - component: Label
                              config:
                                text: 'ON'
                                style:
                                  font-size: 12px

Hopefully you can pick up a few useful things.
I believe you, like I did, had trouble understanding CSS Flexbox. I use for every second widget I work on: CSS3 Flexbox because I just can’t keep it all in my head.

I added some background colors to clarify where each container is located. I also left your code so that you can compare and reassign it.

Of course, this is just a code suggestion and it needs to be cleaned up.
Tester

Hi Matthias,
thank you very much for your help.
I was able to configure my widget with two open questions:

  1. My switch has no changing color to red and no hand over it like the oh-button
  2. How I can add the background picture of the water-buffer for the rectangle in your example?

uid: Heizung
tags: []
props:
  parameters:
    - context: item
      description: item for Puffer Top
      label: Item
      name: item_top
      required: false
      type: TEXT
    - context: item
      description: item for Vorlauf
      label: Item
      name: itmVorlauf
      required: false
      type: TEXT
    - context: item
      description: item for Leistung
      label: Item
      name: itmLeistung
      required: false
      type: TEXT
    - context: item
      description: item for Heizung
      label: Item
      name: itmHeizung
      required: false
      type: TEXT
    - default: /static/boiler.png
      description: /static/myImage.jpg
      label: Background image-url 
      name: backgroundUrl
      required: false
      type: TEXT
  parameterGroups: []
timestamp: Feb 26, 2025, 8:22:46 PM
component: f7-card
config:
  class:
    #- padding
  style:
    background-color: "=props.bgcolor ? props.bgcolor : ''"
    border-radius: var(--f7-card-expandable-border-radius)
    #
    box-shadow: 5px 5px 10px 1px rgba(0,0,0,0.1)
    height: 120px
    margin-left: 5px
    margin-right: 5px
    noShadow: false
    display: flex
    #padding: 0px
slots:
  default:
    - component: div
      config:
        style:
          background-image: "='url(' + props.backgroundUrl + ')'"
          #display: flex
          flex-grow: 1
          clip-path: ="inset( 0px round var(--f7-card-expandable-border-radius))"
          #min-height: 180px
          ##max-height: 180px
          #max-width: 120px
          #min-width: 120px
          #background-size: cover
          #background-position: right bottom
          #display: flex
          #flex-direction: column
          #align-items: start
    #- component: f7-card-content
    #  config:
    #    style:
    #      display: flex
    #      #background: lightgreen
      slots:
        default:
          - component: f7-row
            config:
              class:
                #- display-flex
                #- justify-content-space-between
                #- align-content-stretch
                #- align-items-center
              style:
                #display: flex
                #background: yellow)
                #filter: opacity(60%)
                align-items: stretch
                #flex-grow: 1
                #border-radius: var(--f7-card-expandable-border-radius)
                border-radius: 50px
            slots:
              default:
                - component: f7-col
                  config:
                    class:
                      - display-flex
                      #- flex-direction-column
                      - align-items-center
                      - justify-content-center
                    style:
                      # background: lightgrey
                      # filter: opacity(60%)
                  slots:
                    default:
                      - component: f7-col
                        config:
                          class:
                            - display-flex
                            - flex-direction-column
                            - align-items-center
                            - justify-content-center
                          style:
                            #background: lightgrey
                            #filter: opacity(60%)
                        slots:
                          default:
                            - component: Label
                              config:
                                text: Puffer
                                style:
                                  font-size: 12px
                            - component: f7-icon
                              config:
                                f7: rectangle_fill
                                color: "=(items[props.item_top].state.split('.')[0] < 46) ? 'blue' : ((items[props.item_top].state.split('.')[0] < 60) ? 'orange' : 'red')"
                                size: 10cqh
                                style:
                                  #top: 2px
                                  #left: 10px
                                  transform: rotate(90deg)
                                  filter: opacity(40%)
                      - component: f7-col
                        config:
                          class:
                            - display-flex
                            - flex-direction-column
                            - align-items-center
                            - justify-content-center
                          style:
                            #background: lightgrey
                            filter: opacity(60%)
                        slots:
                          default:
                            - component: oh-link
                              config:
                                text: =items[props.item_top].state + ' °C'
                                color: "=(items[props.item_top].state.split('.')[0] < 46) ? 'blue' : ((items[props.item_top].state.split('.')[0] < 60) ? 'orange' : 'red')"
                                action: analyzer
                                actionAnalyzerItems: =[props.item_top]
                                style:
                                  #top: 2px
                                  font-size: 20px
                                  font-weight: bold
                                  #background: purple
                - component: f7-col
                  config:
                    class:
                      - display-flex
                      - flex-direction-column
                      - align-items-left
                      - justify-content-center
  #                  style:
  #                    background: lightgreen
  #                    filter: opacity(60%)
                  slots:
                    default:
                      - component: Label
                        config:
                          text: "='Vorlauf: ' + items[props.itmVorlauf].state + ' °C'"
                          style:
                            font-size: 12px
                      - component: Label
                        config:
                          text: "='Leistung: ' + items[props.itmLeistung].state + ' W'"
                          style:
                            font-size: 12px
                - component: f7-col
                  config:
                    class:
                      - display-flex
                      - flex-direction-column
                      - align-items-center
                      - justify-content-center
                    style:
                      #background: lightblue
                      #filter: opacity(60%)
                  slots:
                          default:
                            - component: Label
                              config:
                                text: 'Heizung: '
                                style:
                                  font-size: 12px    
                            - component: oh-icon
                              config:
                                icon: switch
                                action: toggle
                                actionItem: =props.itmHeizung
                                actionCommand: OFF
                                actionCommandAlt: ON
                                height: 50
                                style:
                                  color: =(@props.itmHeizung == "ON") ? "green":"red"
                            - component: Label
                              config:
                                text: =(@props.itmHeizung == "ON") ? "ON":"OFF"
                                style:
                                  color: =(@props.itmHeizung == "ON") ? "green":"red"
                                  font-size: 12px

By just using icon: switch, you have selected one of the OH icons. These icons cannot be re-colored directly (there was some recent work on this, but I don’t think it is complete yet). There are a couple of different solutions: 1) find a similar or preferable icon from one of the other icon providers such as f7:power because these icon can be recolored or 2) use the state-base variants of the OH icon you want, in this case switch-on and switch-off. In case #2 you would not need the color directive in the style object at all, you would move the expression up to the actual icon property itself:

icon: =(@props.itmHeizung == "ON") ? "switch-on":"switch-off"

The oh-button becomes and html <a> tag when it is rendered on the page, which usually automatically get the pointer cursor when hovered over. The oh-icon, on the other hand gets rendered as an <img> tag. So, if you want the cursor to change when over the icon you have to do that manually with the css style:

style:
  cursor: pointer

You’ll have to do that with css too. The image needs to be some place OH can access it (usually this means placing it in the html folder in your OH conf folder). Then the url to access the image will be /static/image.name.

I already did the css job by:

        style:
          background-image: "='url(' + props.backgroundUrl + ')'"

You only need to drop your image into /etc/openhab/html/ and either…

  • …adjust the default settings in the widget code.
    - default: /static/boiler.png  #<---- just change "boiler.png" to ... 
      description: /static/boiler.png  
      label: Background image-url 
      name: backgroundUrl
      required: false
      type: TEXT
1 Like

Hi,
thank you very much, I got the switch icon and the hand-cursor, thanks.
But, if I set the picture, I have the picture in each column. I would like have the picture only behind the rectangle_fill like in the attached picture.

replace…:


                            - component: Label
                              config:
                                text: Puffer
                                style:
                                  font-size: 12px
                            - component: f7-icon
                              config:
                                f7: rectangle_fill
                                color: "=(items[props.item_top].state.split('.')[0] < 46) ? 'blue' : ((items[props.item_top].state.split('.')[0] < 60) ? 'orange' : 'red')"
                                size: 10cqh
                                style:
                                  #top: 2px
                                  #left: 10px
                                  transform: rotate(90deg)
                                  filter: opacity(40%)

…with:


                            - component: Label
                              config:
                                text: Puffer
                                style:
                                  font-size: 12px
                            - component: oh-image
                              config:
                                url: =props.backgroundUrl
                                style:
                                  margin: 0.3cqh
                                  height: 8cqh
                                  width: 5cqh

and delete the background-image from top container:

    - component: div
      config:
        style:
          #background-image: "='url(' + props.backgroundUrl + ')'"   <--- delete

for your round corners play with:
border-radius

uid: wid_boiler
tags: []
props:
  parameters:
    - context: item
      default: Test_GroupOne_NumberItem
      description: item with the Vorlauf temperature
      label: Item
      name: itmVorlauf
      required: false
      type: TEXT
    - context: item
      default: Test_GroupOne_NumberItem
      description: item with the power 
      label: Item
      name: itmLeistung
      required: false
      type: TEXT
    - context: item
      default: Test_GroupOne_NumberItem
      description: item with the fill-level
      label: Item
      name: itmLevel
      required: false
      type: TEXT
    - default: /static/bild.png
      description: (url(/static/boiler.png))
      label: Background image-url
      name: backgroundUrl
      required: false
      type: TEXT
  parameterGroups: []
timestamp: Feb 27, 2025, 9:08:58 PM
component: f7-card
config:
  style:
    background-color: "=props.bgcolor ? props.bgcolor : ''"
    border-radius: var(--f7-card-expandable-border-radius)
    box-shadow: 5px 5px 10px 1px rgba(0,0,0,0.1)
    height: 120px
    margin-left: 5px
    margin-right: 5px
    noShadow: false
    display: flex
slots:
  default:
    - component: div
      config:
        style:
          flex-grow: 1
          clip-path: ="inset( 0px round var(--f7-card-expandable-border-radius))"
      slots:
        default:
          - component: f7-row
            config:
              style:
                align-items: stretch
            slots:
              default:
                - component: f7-col
                  config:
                    class:
                      - display-flex
                      - align-items-center
                      - justify-content-center
                    style:
                      background: none
                      filter: none
                  slots:
                    default:
                      - component: f7-col
                        config:
                          class:
                            - display-flex
                            - flex-direction-column
                            - align-items-center
                            - justify-content-center
                          style:
                            background: none
                            filter: none
                        slots:
                          default:
                            - component: Label
                              config:
                                text: Puffer
                                style:
                                  font-size: 12px
#______________________________________________________________________________________
#Maybe you'd like to visualize the level based on the state of an item declared in props.
                            - component: div
                              config:
                                style:
                                  clip-path: ="inset( 0px round var(--f7-card-expandable-border-radius))"
                                  margin: 0.3cqh
                                  height: 8cqh
                                  position: relative
                              slots:
                                default:
                                  - component: oh-image
                                    config:
                                      url: =props.backgroundUrl
                                      style:
                                        height: 100%
                                  - component: div
                                    config:
                                      style:
                                        position: absolute
                                        top: "=(100 - items[props.itmLevel].state) + '%'"
                                        bottom: 0
                                        left: 0
                                        right: 0
                                        background: red
                                        filter: opacity(60%)

#______________________________________________________________________________________
                      - component: f7-col
                        config:
                          class:
                            - display-flex
                            - flex-direction-column
                            - align-items-center
                            - justify-content-center
                          style:
                            background: none
                            filter: none
                        slots:
                          default:
                            - component: oh-link
                              config:
                                text: =items[props.item_top].state + ' °C'
                                color: "=(items[props.item_top].state.split('.')[0] < 46) ? 'blue' :
                                  ((items[props.item_top].state.split('.')[0] <
                                  60) ? 'orange' : 'red')"
                                action: analyzer
                                actionAnalyzerItems: =[props.item_top]
                                style:
                                  font-weight: bold
                                  background: none
                - component: f7-col
                  config:
                    class:
                      - display-flex
                      - flex-direction-column
                      - align-items-center
                      - justify-content-center
                    style:
                      background: none
                      filter: none
                  slots:
                    default:
                      - component: Label
                        config:
                          text: "='Vorlauf: ' + items[props.itmVorlauf].state + ' °C'"
                          style:
                            font-size: 12px
                      - component: Label
                        config:
                          text: "='Leistung: ' + items[props.itmLeistung].state + ' W'"
                          style:
                            font-size: 12px
                - component: f7-col
                  config:
                    class:
                      - display-flex
                      - flex-direction-column
                      - align-items-center
                    style:
                      background: none
                      filter: none
                  slots:
                    default:
                      - component: oh-button
                        config:
                          class:
                            - display-flex
                            - flex-direction-column
                            - align-items-center
                            - justify-content-center
                          style:
                            flex-grow: 1
                        slots:
                          default:
                            - component: Label
                              config:
                                text: "Heizung: "
                                style:
                                  font-size: 12px
                            - component: Label
                              config:
                                text: ON
                                style:
                                  font-size: 12px

Tester

Hi,
Thank you very much,
I use it without the level, only a rectangle_fillwith a changing color.
The widget works, but I think I will seperate the button, because on my tablet it is not all in the box.
Thanks.