Ever since I saw widgets with animations here in the forum, I wanted to create a corresponding animation for my Huawei Sun2000 PV system. I learnt a lot about OpenHAB in the process.
My widget is based on the Deye Inverter widget from @kretzp and the tips from @JustinG in the community posts Popup for Shutter-Widget and f7-icon with custom icons.
The widget displays the most important places in the grid as coloured rectangles:
- PV modules
- storage
- inverter
- Power grid
- House as consumer
The rectangles indicate the power that the respective component draws or supplies. The direction is also indicated by a coloured arrow.
- Green: Power is supplied
- Red: Power is drawn
The icons and texts in the rectangles serve as links; clicking on them opens a pop-up with further values.
Screenshots
Changelog
Version 0.1
- initial release
Version 0.2
- redesign of SVG part
- long lines broken with multiline
- changed popup linking from class to id
Resources
uid: Sun2000_Widget
tags:
- Für Huawei Sun2000 Wechselrichter gem. https://www.debacher.de/wiki/Huawei_Sun2000_mit_OpenHAB
- Version 0.2
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 21, 2024, 9:57:43 AM
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: ="#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: ="#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: ="#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: ="#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: ="#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: ="#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: ="#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: ="#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: ="#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: ="#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:
id: ="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:
id: ="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:
id: ="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:
id: ="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:
id: ="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