OpenHAB Animation zur Sun2000

Aus Debacher-Wiki
Zur Navigation springenZur Suche springen

Für die schnelle Übersicht für die Lage auf dem heimischen Stromsektor habe ich mir ein kleines Widget mit Animation und Popups erstellt. Als Grundlage für die Entwicklung diente mir


Das Widget

Hier nur als Screenshot im OpenHAB sind die Kreise animiert. Die Geschwindigkeit dieser Animation hängt von der jeweiligen Leistung ab.

Fd9e9a1d091221d227ee9c25c0b25494d4a410e3.gif

Das Bild stellt die wesentlichen Stellen im Netz als bunte Rechtecke dar:

  • PV-Module
  • Speicher
  • Wechselrichter
  • Stromnetz
  • Haus als Verbraucher

Bei den Rechtecken ist jeweils die Leistung angegeben, die die jeweilige Komponente bezieht oder liefert. Die Richtung ist zusätzlich durch einen farbigen Pfeil gekennzeichnet:

  • Grün: Strom wird abgegeben
  • Rot: Strom wird bezogen

Die Icons und Texte in den Rechtecken dienen als Link, beim Anklicken öffnet sich ein Popup mit weiteren Werten:

85fe6605007e6225a290d81153d26d53e7ba47aa.gif

Die Kästen sind jeweils durch eine Linie mit dem Wechselrichter verbunden. Fließt auf dieser Strecke ein Strom, so besteht die Animation darin, dass eine kleine Kugel sich in der Stromrichtung bewegt. Die Geschwindigkeit der Animation ist von der jeweiligen Stromstärke abhängig.

Das Listing zum Widget

uid: Sun2000_Widget
tags:
  - Für Huawei Sun2000 Wechselrichter gem. https://www.debacher.de/wiki/Huawei_Sun2000_mit_OpenHAB
  - Version 2.0
props:
  parameters:
    - description: Widget Titel
      label: Widget Title
      name: title
      required: false
      type: TEXT
    - context: item
      description: String1 Spannung (32016)
      label: PV1 Voltage
      name: item32016
      required: false
      type: TEXT
    - context: item
      description: String1 Strom (32017)
      label: PV1 current
      name: item32017
      required: false
      type: TEXT
    - context: item
      description: String2 Spannung (32018)
      label: PV2 Voltage
      name: item32018
      required: false
      type: TEXT
    - context: item
      description: String2 Strom (32019)
      label: PV2 current
      name: item32019
      required: false
      type: TEXT
    - context: item
      description: Solar Ertrag (32064)
      label: Solar Production
      name: item32064
      required: false
      type: TEXT
    - context: item
      description: Maximaler Ertrag (32078)
      label: Peak active today
      name: item32078
      required: false
      type: TEXT
    - context: item
      description: Leistung der Module (32080)
      label: Active Power
      name: item32080
      required: false
      type: TEXT
    - context: item
      description: Netz-Frequenz (32085)
      label: Grid frequency
      name: item32085
      required: false
      type: TEXT
    - context: item
      description: Inverter Temperatur (32087)
      label: Inverter Temperature
      name: item32087
      required: false
      type: TEXT
    - context: item
      description: Erzeugte Energie (32106)
      label: Acc. energy yield
      name: item32106
      required: false
      type: TEXT
    - context: item
      description: Heutiger Solar Ertrag (32114)
      label: Solar Daily Production
      name: item32114
      required: false
      type: TEXT
    - context: item
      description: Batterie Temperatur (37022)
      label: Battery Temperature
      name: item37022
      required: false
      type: TEXT
    - context: item
      description: Einspeisung/Bezug vom Netz (37113)
      label: Active power
      name: item37113
      required: false
      type: TEXT
    - context: item
      description: Gesamt-Einspeisung Netz (37119)
      label: Positive active electricity
      name: item37119
      required: false
      type: TEXT
    - context: item
      description: Gesamt-Bezug Netz (37121)
      label: Reverse active power
      name: item37121
      required: false
      type: TEXT
    - context: item
      description: Batterie Ladung (37760)
      label: Battery SOC
      name: item37760
      required: false
      type: TEXT
    - context: item
      description: Verbrauch/Einspeisung Batterie (37765)
      label: Charge/ Discharge power
      name: item37765
      required: false
      type: TEXT
    - context: item
      description: Heutige Einspeisung Batterie (37784)
      label: Daily Battery Charge
      name: item37784
      required: false
      type: TEXT
    - context: item
      description: Heutiger Verbrauch von Batterie (37786)
      label: Daily Battery Consumption
      name: item37786
      required: false
      type: TEXT
    - context: item
      description: Heutiger Netz-Bezug
      label: Daily Consumption From Grid
      name: itemDailyConsumptionFromGrid
      required: false
      type: TEXT
    - context: item
      description: Heutige Netz-Einspeisung
      label: Daily Production To Grid
      name: itemDailyProductionToGrid
      required: false
      type: TEXT
  parameterGroups: []
timestamp: Mar 20, 2024, 8:47:11 PM
component: f7-card
config:
  title: = props.title
slots:
  content:
    - component: f7-block
      config:
        style:
          margin: 0
          padding: 0
      slots:
        default:
          - component: f7-block
            config:
              style:
                --f7-block-font-size: 12px
                --f7-theme-color: var(--f7-text-color)
                display: flex
                justify-content: center
                margin: 0
                padding: 0
            slots:
              default:
                - component: f7-col
                  config:
                    style:
                      align-items: center
                      display: flex
                      flex-direction: column
                  slots:
                    default:
                      - component: f7-block
                        config:
                          style:
                            align-items: center
                            border: 3px solid orange
                            border-radius: 20px
                            display: flex
                            flex-direction: column
                            height: 100px
                            justify-content: center
                            width: 100px
                        slots:
                          default:
                            - component: oh-link
                              config:
                                action: popover
                                popoverOpen: =".popover.info-solar"
                              slots:
                                default:
                                  - component: oh-icon
                                    config:
                                      color: orange
                                      height: 50px
                                      icon: if:game-icons:solar-power
                            - component: oh-link
                              config:
                                action: popover
                                iconColor: green
                                iconF7: arrow_right
                                iconSize: 12px
                                popoverOpen: =".popover.info-solar"
                                style:
                                  font-size: 14px
                                  white-space: nowrap
                                text: =items[props.item32064].state+" kW"
                      - component: f7-block
                        config:
                          style:
                            align-items: center
                            flex-direction: column
                            height: 70px
                            justify-content: center
                            width: 100px
                      - component: f7-block
                        config:
                          style:
                            align-items: center
                            border: 3px solid teal
                            border-radius: 20px
                            display: flex
                            flex-direction: column
                            height: 100px
                            justify-content: center
                            width: 100px
                        slots:
                          default:
                            - component: Label
                              config:
                                style:
                                  white-space: nowrap
                                text: =items[props.item37760].displayState+" %"
                            - component: oh-link
                              config:
                                action: popover
                                align-item: center
                                popoverOpen: =".popover.info-batt"
                              slots:
                                default:
                                  - component: oh-icon
                                    config:
                                      height: 40px
                                      icon: battery
                                      state: =items[props.item37760].displayState
                            - component: oh-link
                              config:
                                action: popover
                                iconColor: "=(Number.parseFloat(items[props.item37765].state) < 0) ? 'red' : 'green'"
                                iconF7: "=(Number.parseFloat(items[props.item37765].state) < 0) ? 'arrow_right' : 'arrow_left'"
                                iconSize: 12px
                                popoverOpen: =".popover.info-batt"
                                style:
                                  font-size: 14px
                                  white-space: nowrap
                                text: = items[props.item37765].state+" kW"
                - component: f7-col
                  config:
                    style:
                      align-items: center
                      display: flex
                      flex-direction: column
                  slots:
                    default:
                      - component: svg
                        config:
                          style:
                            width: 20px
                            height: 270px
                          xmlns: http://www.w3.org/2000/svg
                        slots:
                          default:
                            - component: path
                              config:
                                d: M0,65 h2 q8,0 8,35 t8,35 h2
                                fill: none
                                id: solarpath
                                stroke: orange
                                stroke-width: 2
                            - component: circle
                              config:
                                fill: orange
                                r: 4
                                style:
                                  stroke-width: 4
                                visible: "=(Math.abs(Number.parseFloat(items[props.item32064].state)) < 0.01) ? false : true"
                              slots:
                                default:
                                  - component: animateMotion
                                    config:
                                      dur: >-
                                        =(Number.parseFloat(items[props.item32064].state) > 8) ? '1s' :
                                         (Number.parseFloat(items[props.item32064].state) > 4) ? '2s' :
                                         (Number.parseFloat(items[props.item32064].state) > 2) ? '3s' :
                                         (Number.parseFloat(items[props.item32064].state) > 1) ? '4s' :
                                         (Number.parseFloat(items[props.item32064].state) > 0.5) ? '6s' :
                                         (Number.parseFloat(items[props.item32064].state) > 0.1) ? '8s' :'16s'
                                      keyPoints: "=(Number.parseFloat(items[props.item32064].state) < 0) ? '1;0' : '0;1'"
                                      keyTimes: 0;1
                                      repeatCount: indefinite
                                    slots:
                                      default:
                                        - component: mpath
                                          config:
                                            xlink:href: "#solarpath"
                            - component: path
                              config:
                                d: M0,230 h2 q8,0 8,-35 t8,-35 h2
                                fill: none
                                id: batterypath
                                stroke: teal
                                stroke-width: 2
                            - component: circle
                              config:
                                fill: teal
                                r: 4
                                style:
                                  stroke-width: 4
                                visible: "=(Number.parseFloat(items[props.item37765].state) == 0) ? false : true"
                              slots:
                                default:
                                  - component: animateMotion
                                    config:
                                      dur: >-
                                        =(Math.abs(Number.parseFloat(items[props.item37765].state)) > 8) ? '1s' :
                                         (Math.abs(Number.parseFloat(items[props.item37765].state)) > 4) ? '2s' :
                                         (Math.abs(Number.parseFloat(items[props.item37765].state)) > 2) ? '3s' :
                                         (Math.abs(Number.parseFloat(items[props.item37765].state)) > 1) ? '4s' :
                                         (Math.abs(Number.parseFloat(items[props.item37765].state)) > 0.5) ? '6s' :
                                         (Math.abs(Number.parseFloat(items[props.item37765].state)) > 0.1) ? '8s' :'16s'
                                      keyPoints: "=(Number.parseFloat(items[props.item37765].state) > 0) ? '1;0' : '0;1'"
                                      keyTimes: 0;1
                                      repeatCount: indefinite
                                    slots:
                                      default:
                                        - component: mpath
                                          config:
                                            xlink:href: "#batterypath"
                - component: f7-col
                  config:
                    style:
                      align-items: center
                      display: flex
                      flex-direction: column
                  slots:
                    default:
                      - component: f7-block
                        config:
                          style:
                            align-items: center
                            flex-direction: column
                            height: 82px
                            justify-content: center
                            width: 100px
                      - component: f7-block
                        config:
                          style:
                            align-items: center
                            border: 2px solid green
                            border-radius: 20px
                            display: flex
                            flex-direction: column
                            height: 100px
                            justify-content: center
                            width: 100px
                        slots:
                          default:
                            - component: oh-link
                              config:
                                action: popover
                                popoverOpen: =".popover.info-inverter"
                              slots:
                                default:
                                  - component: oh-icon
                                    config:
                                      color: lightgreen
                                      height: 50px
                                      icon: if:cbi:huawei-solar-inverter
                            - component: oh-link
                              config:
                                action: popover
                                iconColor: green
                                iconF7: arrow_right
                                iconSize: 12px
                                popoverOpen: =".popover.info-inverter"
                                style:
                                  font-size: 14px
                                  white-space: nowrap
                                text: =items[props.item32080].state+" kW"
                - component: f7-col
                  config:
                    style:
                      align-items: center
                      display: flex
                      flex-direction: column
                  slots:
                    default:
                      - component: svg
                        config:
                          preserveAspectRatio: xMidYMid slice
                          style:
                            width: 20px
                            height: 270px
                          xmlns: http://www.w3.org/2000/svg
                        slots:
                          default:
                            - component: path
                              config:
                                d: M20,65 h-2 q-8,0 -8,35 t-8,35 h-2
                                fill: none
                                id: gridpath
                                stroke: darkred
                                stroke-width: 2
                            - component: circle
                              config:
                                fill: darkred
                                r: 4
                                style:
                                  stroke-width: 4
                                tag: circle
                                visible: "=(Math.abs(Number.parseFloat(items[props.item37113].state)) < 0.01) ? false : true"
                              slots:
                                default:
                                  - component: animateMotion
                                    config:
                                      dur: >-
                                        =(Math.abs(Number.parseFloat(items[props.item37113].state)) > 8) ? '1s' :
                                         (Math.abs(Number.parseFloat(items[props.item37113].state)) > 4) ? '2s' :
                                         (Math.abs(Number.parseFloat(items[props.item37113].state)) > 2) ? '3s' :
                                         (Math.abs(Number.parseFloat(items[props.item37113].state)) > 1) ? '4s' :
                                         (Math.abs(Number.parseFloat(items[props.item37113].state)) > 0.5) ? '6s' :
                                         (Math.abs(Number.parseFloat(items[props.item37113].state)) > 0.1) ? '8s' :'16s'
                                      keyPoints: "=(Number.parseFloat(items[props.item37113].state) > 0) ? '1;0' : '0;1'"
                                      keyTimes: 0;1
                                      repeatCount: indefinite
                                    slots:
                                      default:
                                        - component: mpath
                                          config:
                                            xlink:href: "#gridpath"
                            - component: path
                              config:
                                d: M20,230 h-2 q-8,0 -8,-35 t-8,-35 h-2
                                fill: none
                                id: loadpath
                                stroke: blue
                                stroke-width: 2
                            - component: circle
                              config:
                                fill: blue
                                r: 4
                                style:
                                  stroke-width: 4
                                tag: circle
                                visible: "=(Math.abs(Number.parseFloat(items[props.item32080].state)-Number.parseFloat(items[props.item37113].state)) <= 0.01) ? false : true"
                              slots:
                                default:
                                  - component: animateMotion
                                    config:
                                      dur: >-
                                        =(Math.abs(Number.parseFloat(items[props.item32080].state)-Number.parseFloat(items[props.item37113].state)) > 8) ? '1s' :
                                         (Math.abs(Number.parseFloat(items[props.item32080].state)-Number.parseFloat(items[props.item37113].state)) > 4) ? '2s' :
                                         (Math.abs(Number.parseFloat(items[props.item32080].state)-Number.parseFloat(items[props.item37113].state)) > 2) ? '3s' :
                                         (Math.abs(Number.parseFloat(items[props.item32080].state)-Number.parseFloat(items[props.item37113].state)) > 1) ? '4s' :
                                         (Math.abs(Number.parseFloat(items[props.item32080].state)-Number.parseFloat(items[props.item37113].state)) > 0.5) ? '6s' :
                                         (Math.abs(Number.parseFloat(items[props.item32080].state)-Number.parseFloat(items[props.item37113].state)) > 0.1) ? '8s' :'16s'
                                      keyPoints: "=(Number.parseFloat(items[props.item32080].state)-Number.parseFloat(items[props.item37113].state) > 0) ? '1;0' : '0;1'"
                                      keyTimes: 0;1
                                      repeatCount: indefinite
                                    slots:
                                      default:
                                        - component: mpath
                                          config:
                                            xlink:href: "#loadpath"
                - component: f7-col
                  config:
                    style:
                      align-items: center
                      display: flex
                      flex-direction: column
                  slots:
                    default:
                      - component: f7-block
                        config:
                          style:
                            align-items: center
                            border: 3px solid darkred
                            border-radius: 20px
                            display: flex
                            flex-direction: column
                            height: 100px
                            justify-content: center
                            width: 100px
                        slots:
                          default:
                            - component: oh-link
                              config:
                                action: popover
                                popoverOpen: =".popover.info-grid"
                              slots:
                                default:
                                  - component: oh-icon
                                    config:
                                      color: darkred
                                      height: 50px
                                      icon: if:mdi:transmission-tower
                            - component: oh-link
                              config:
                                action: popover
                                iconColor: "=(Number.parseFloat(items[props.item37113].state) < 0) ? 'red' : 'green'"
                                iconF7: "=(Number.parseFloat(items[props.item37113].state) > 0) ? 'arrow_right' : 'arrow_left'"
                                iconSize: 12px
                                popoverOpen: =".popover.info-grid"
                                style:
                                  font-size: 14px
                                  white-space: nowrap
                                text: = Number.parseFloat(items[props.item37113].state).toFixed(3)+" kW"
                      - component: f7-block
                        config:
                          style:
                            align-items: center
                            flex-direction: column
                            height: 70px
                            justify-content: center
                            width: 100px
                      - component: f7-block
                        config:
                          style:
                            align-items: center
                            border: 3px solid blue
                            border-radius: 20px
                            display: flex
                            flex-direction: column
                            height: 100px
                            justify-content: center
                            width: 100px
                        slots:
                          default:
                            - component: oh-link
                              config:
                                action: popover
                                popoverOpen: =".popover.info-home"
                              slots:
                                default:
                                  - component: oh-icon
                                    config:
                                      color: blue
                                      height: 50px
                                      icon: if:healthicons:home-outline
                            - component: oh-link
                              config:
                                action: popover
                                iconColor: "=((Number.parseFloat(items[props.item32080].state)-Number.parseFloat(items[props.item37113].state)) > 0) ? 'red' : 'green'"
                                iconF7: "=((Number.parseFloat(items[props.item32080].state)-Number.parseFloat(items[props.item37113].state)) > 0) ? 'arrow_right' : 'arrow_left'"
                                iconSize: 12px
                                popoverOpen: =".popover.info-home"
                                style:
                                  font-size: 14px
                                  white-space: nowrap
                                text: >
                                  =(Number.parseFloat(items[props.item32080].state)
                                   - Number.parseFloat(items[props.item37113].state)).toFixed(3) + ' kW'
    - component: f7-popover
      config:
        class: ="info-solar"
        closeByBackdropClick: true
        closeByOutsideClick: true
        closeOnEscape: true
        style:
          background-color: snow2
          color: rgb(238,118,0)
          height: auto
          width: 250px
      slots:
        default:
          - component: oh-list
            config:
              simpleList: false
            slots:
              default:
                - component: oh-list-item
                  config:
                    after: >
                      =(Number.parseFloat(items[props.item32114].state)  
                       + Number.parseFloat(items[props.item37784].state)  
                       - Number.parseFloat(items[props.item37786].state)).toFixed(3) + ' kWh'
                    title: Ertrag Module
                - component: oh-list-item
                  config:
                    after: =items[props.item32078].displayState
                    title: Maximal Leistung
                - component: oh-list-item
                  config:
                    after: =(Number.parseFloat(items[props.item32016].state*items[props.item32017].state)/1000).toFixed(3) + ' kW'
                    title: Leistung String 1
                - component: oh-list-item
                  config:
                    after: =(Number.parseFloat(items[props.item32018].state*items[props.item32019].state)/1000).toFixed(3) + ' kW'
                    title: Leistung String 2
    - component: f7-popover
      config:
        class: ="info-batt"
        closeByBackdropClick: true
        closeByOutsideClick: true
        closeOnEscape: true
        style:
          background-color: snow2
          color: teal
          height: auto
          width: 250px
      slots:
        default:
          - component: oh-list
            config:
              simpleList: false
            slots:
              default:
                - component: oh-label-item
                  config:
                    item: =props.item37784
                    title: Geladen
                - component: oh-label-item
                  config:
                    item: =props.item37786
                    title: Entladen
                - component: oh-label-item
                  config:
                    item: =props.item37022
                    title: Temp. Batterie
    - component: f7-popover
      config:
        class: ="info-inverter"
        closeByBackdropClick: true
        closeByOutsideClick: true
        closeOnEscape: true
        style:
          background-color: snow2
          color: green
          height: auto
          width: 250px
      slots:
        default:
          - component: oh-list
            config:
              simpleList: false
            slots:
              default:
                - component: oh-list-item
                  config:
                    after: =(Number.parseFloat(items[props.item32114].state)).toFixed(3) + ' kWh'
                    title: Ertrag Inverter
                - component: oh-list-item
                  config:
                    after: =(Number.parseFloat(items[props.item32087].state)).toFixed(1) + ' °C'
                    title: Temperatur
                - component: oh-label-item
                  config:
                    item: =props.item32085
                    title: Frequenz
    - component: f7-popover
      config:
        class: ="info-grid"
        closeByBackdropClick: true
        closeByOutsideClick: true
        closeOnEscape: true
        style:
          background-color: snow2
          color: darkred
          height: auto
          width: 250px
      slots:
        default:
          - component: oh-list
            config:
              simpleList: false
            slots:
              default:
                - component: oh-label-item
                  config:
                    item: =props.itemDailyProductionToGrid
                    title: Netzeinspeisung
                - component: oh-label-item
                  config:
                    item: =props.itemDailyConsumptionFromGrid
                    title: Netzbezug
    - component: f7-popover
      config:
        class: ="info-home"
        closeByBackdropClick: true
        closeByOutsideClick: true
        closeOnEscape: true
        style:
          background-color: snow2
          color: blue
          height: auto
          width: 250px
      slots:
        default:
          - component: oh-list
            config:
              simpleList: false
            slots:
              default:
                - component: oh-list-item
                  config:
                    after: >
                      = (Number.parseFloat(items[props.item32114].state) 
                        -Number.parseFloat(items[props.itemDailyProductionToGrid].state) 
                        +Number.parseFloat(items[props.itemDailyConsumptionFromGrid].state) ).toFixed(2)+ ' kWh'
                    title: Haus gesamt
                - component: oh-list-item
                  config:
                    after: >
                      = (Number.parseFloat(items[props.item32114].state) 
                        -Number.parseFloat(items[props.itemDailyProductionToGrid].state) ).toFixed(2)+ ' kWh'
                    title: Haus von PV
                - component: oh-list-item
                  config:
                    after: >
                      =(100-(Number.parseFloat(items[props.itemDailyProductionToGrid].state) 
                        / (Number.parseFloat(items[props.item32114].state) 
                         - Number.parseFloat(items[props.item37786].state) 
                         + Number.parseFloat(items[props.item37784].state)) )*100).toFixed(2) + ' %'
                    title: Eigenverbrauch heute
                - component: oh-list-item
                  config:
                    after: >
                      = (100*(Number.parseFloat(items[props.item32114].state) 
                          -Number.parseFloat(items[props.itemDailyProductionToGrid].state)) 
                         /(Number.parseFloat(items[props.item32114].state) 
                          -Number.parseFloat(items[props.itemDailyProductionToGrid].state) 
                          +Number.parseFloat(items[props.itemDailyConsumptionFromGrid].state))).toFixed(2)+ ' %'
                    title: Autarkie
                - component: oh-list-item
                  config:
                    after: >
                      =((Number.parseFloat(items[props.item32106].state) 
                        - Number.parseFloat(items[props.item37119].state)) 
                       / Number.parseFloat(items[props.item32106].state)*100).toFixed(1) + ' %'
                    title: Eigenverbrauch global
                - component: oh-list-item
                  config:
                    after: >
                      =100-((Number.parseFloat(items[props.item37121].state)) 
                          / (Number.parseFloat(items[props.item32106].state) 
                           + Number.parseFloat(items[props.item37121].state) 
                           - Number.parseFloat(items[props.item37119].state) )*100).toFixed(1) + ' %'
                    title: Autarkie global


Das Listing besteht grob aus drei Abschnitten:

  • Zeile 1 bis 139: Interfacedefinition, hier werden die Elemente festgelegt, die in dem Widget benutzt werden
  • Zeile 140 bis 560: Der Aufbau der Grafik mit den Kästen und der Animation
  • Ab Zeile 561: Definition der fünf Popups, für jeden Kasten eines

Interface

Bei allen Daten-Elementen, die direkt vom Wechselrichter stammen habe ich darauf Wert gelegt, dass die Registernummer im ItemBezeichner auftaucht. Das macht die Zurodnung bei einer Sun2000 eindeutig und hilft bei der Auswahl im Konfigurationsdialog.

Am Ende der Interface-Liste tauchen noch auf:

  • Heutiger Netz-Bezug
  • Heutige Netz-Einspeisung

Diese Tageswerte werden vom Inverter leider nicht geliefert. Ich werde sie später über ein Homatic-Gerät direkt am Stromzähler ablesen. Aktuell sind es aber Variable, die ich über Rules befülle:

Die erste Regel wird um Mitternacht aufgerufen, setzt den Hausverbrauch auf "0" und den Startwert für Verbrauch und Einspeisung auf die Werte der über die Lebensdauer laufenden Daten.

2024-03-16 12.44.36 0f55c3b6a827.png 2024-03-17 11.00.49 50f81d1d6417.png


Die Werte müssen dann jeweils, wenn sich das zugehörige Datenfeld der Sun2000 ändert, aktualisiert werden:

2024-03-17 11.05.06 80bb601d5e86.png 2024-03-16 12.51.27 b59d2e733ff5.png

2024-03-17 11.08.13 1e122bb24961.png 2024-03-16 12.55.13 08fdee34ab66.png

Grafik und Animation

Die eigentliche Grafik besteht dann aus 5 Spalten.

  • Die Spalten 1, 3 und 5 beinhalten Blöcke mit Daten und Grafik
  • Die Spalten 2 und 4 beinhalten Pfade und Animationen

Bei der Realisierung werden einige f7 Elemente benutzt.

Die Animationen sind im Prinzip SVG-Elemente, die von OpenHAB und f7 entsprechend umgesetzt werden.

Informationen zu den Grundlagen finden sich unter:

Ein wichtiges Element ist jeweils die Linie, der Pfad, hier der Solarpfad:

   d: M0,65 h2 q8,0 8,35 t8,35 h2

Konkret wird hier definiert (das Komma steht immer zwischen den Komponenten eines Punktes)

  • MoveTo (0,65)
  • Linie horizontel 2 Punkte
  • Quadratische Bézier-Kurve zum Punkt (8,35) mit dem Kontrollpunkt (8,0)
  • Quadratische Bézier-Kurve wie vor, aber am Punkt (8,35) gespiegelt
  • Linie horizontal 2 Punkte

Bei kleinen Buchstaben sind die Angaben relativ, bei großen Buchstaben absolut. Diese Art der Kurvenbeschreibung ist leichter nachvollziehbar und anpassbar, als die über kubische Kurven.

In Zeile 275 bekommt der Pfad dann noch eine id zugeordnet, hier solarpath

In den Zeilen darauf wird dann eine Animation definiert, die auf einem kleinen farbigen Kreis beruht und über

   xlink:href: "#solarpath"

an den oben definierte Pfad gekoppelt wird.

Für die Animation wird noch festgelegt:

  • dur: die Dauer in Sekunden, hier als komplexe Fallunterscheidung
  • keyPoints: "=(Number.parseFloat(items[props.item32064].state) < 0) ? '1;0' : '0;1'" je nach Vorzeichen ist die Bewegungsrichtung unterschiedlich
  • keyTimes: 0;1 eine gleichmäßige Bewegung von Anfang bis Ende

Popups

Mein Ziel war es, die eigentliche Grafik nicht mit Informationen zu überfrachten, aber trotzdem weitere Informationen zu ermöglichen. Dazu dienen die Popups. Wichtig war mit, dass für den Inhalt der Popups kein weiteres Widget benötigt wird, also diese Elemente im gleichen Widget untergebracht sind.

Das Element oh-link erlaubt Action Elemente

 - component: oh-link
   config:
      action: popover
      popoverOpen: =".popover.info-solar"
      iconColor: green
      iconF7: arrow_right
      iconSize: 12px

Das Action-Element ist vom Typ popover, in Zeile 197 wird ihm eine Klasse zugewiesen. Über diese Klasse wird dann das richtige Element angesteuert

    - component: f7-popover
      config:
        class: ="info-solar"
        closeByBackdropClick: true
        closeByOutsideClick: true
        closeOnEscape: true

Wichtig ist, dass sich die Klassenbezeichnungen der Popover-Elemente unterscheiden.

Textzeilen vom Typ oh-link für ein Popup zu nutzen ist relativ direkt. Wesentlich aufwändiger war es auch eine Lösung für das große Icon zu finden. Ein Block vom Type oh-icon ermöglicht kein Popup und ein Elemente vom Type oh-link keine freie Icon-Wahl. Auf eine Anleitung von JustinG hin habe ich eine Lösung gefunden:

  - component: oh-link
    config:
      action: popover
      popoverOpen: =".popover.info-solar"
    slots:
      default:
        - component: oh-icon
          config:
            icon: if:game-icons:solar-power
            height: 50px
            color: orange

Der oh-link besitzt einen slot, der ein oh-icon erlaubt. Damit kann man auch das große Icon anklickbar machen.

Bei den Listenelemente der Popups habe ich momentan zwei verschiedene Arten

  - component: oh-label-item
    config:
      item: =props.item32078
      title: Max. Leistung heute

Das ist die wohl einfachste Version, sie erlaubt aber keine Berechnungen an dem Item.

  - component: oh-list-item
    config:
      title: Max. Leistung heute
      after: =(Number.parseFloat(items[props.item32078].state)).toFixed(3) + ' kW'

Auf diese Art lassen sich auch mehrere Items miteinander verknüpfen und generell kann die Ausgabe manipuliert werden.

Der Solar-Ertrag

Mit dem Solar-Ertrag gibt es ein kleines Problem, sofern man auch über eine Batterie verfügt. Es können hier zwei unterschiedliche Leistungs Werte zum Einsatz kommen:

  • Register 32064 (Input power - DC Leistung des WR)
  • Register 32080 (Active power - AC Leistung der Module)

Die Leistungswerte unterschieden sich (+/- 2,5 kW), wenn die Batterie geladen oder entladen wird. Natürlich betrifft das im Endeffekt auch erzeugten Energiemengen. Zähle ich die Energiemenge von/zur Batterie mit oder nicht? In meinen Listings rechne ich mit dem Register 32114 (Daily energy yield) welches sich auf den Ausgang des Wechselrichters bezieht. Die Batterie wird quasi ignoriert.

Offene Punkte

Ein paar Punkte habe ich bisher nicht klären können, sondern nur umschifft.

  • Wo finde ich eine Dokumentation dazu, welche Elemente popoverOpen unterstützen?
  • Ein Problem tritt bei der Mischung auf, im ersten Fall wird ein Dezimalkomma gesetzt, zumindest bei entsprechender Einstellung der Lokale. Im zweiten Fall erzeugt Javascript die Ausgabe, hier wird dann ein Dezimalpunkt gesetzt. Wenn das stört, dann muss man entweder immer die oh-list-item Version nutzen oder über Javascript im oh-list-item generell den Punkt durch ein Komma ersetzen.