OpenHAB power usage widget
I was always jealous at the solar widget that Hass.IO offers. So I decided to create one for OpenHAB myself. The dots on the lines move faster or slower depending on the values detected. The house will glow green, yellow, orange or red depending on the power usage.
As I’m getting alot of positive feedback, I decided to share it here!
Hope you’ll like it!
Screenshots
All variable can be set in widget settings.
Changelog
Widget created
Version 0.1
- initial release
Resources
version: 1
widgets:
energy_flow_new:
tags:
- ev
- grid
- solar
- battery
- water
- energy
props:
parameters:
- context: item
label: Solar Power Item (W)
name: solarPowerItem
required: true
type: TEXT
- context: item
label: Battery Power Item (W)
name: batteryPowerItem
required: true
type: TEXT
- context: item
label: Battery %
name: batteryPercentItem
required: false
type: TEXT
- context: item
label: Battery charge rate
name: batteryChargeRateItem
required: false
type: TEXT
- context: item
label: Battery discharge rate
name: batteryDischargeRateItem
required: false
type: TEXT
- context: item
label: Grid Import Item (W)
name: gridImportItem
required: true
type: TEXT
- context: item
label: Grid Export Item (W)
name: gridExportItem
required: true
type: TEXT
- context: item
label: Home Consumption Item (W)
name: homePowerItem
required: true
type: TEXT
- context: item
label: Water Usage L/h Item
name: waterUsageLhItem
required: false
type: TEXT
- context: item
label: EV Battery %
name: EVBatteryPercentItem
required: false
type: TEXT
- context: item
label: EV Power (W)
name: evPowerItem
required: false
type: TEXT
- context: item
label: EV Charge (kWh)
name: evChargeKwhItem
required: false
type: TEXT
- context: item
label: EV Connected Item (Switch / Contact)
name: evConnectedItem
required: false
type: TEXT
- context: item
label: Mazout Price Item
name: oilPriceItem
required: false
type: TEXT
- context: item
label: Home Peak Today Item (W)
name: homePeakTodayItem
required: false
type: TEXT
- context: item
label: Solar Today (kWh)
name: solarTodayKwhItem
required: false
type: TEXT
- context: item
label: Grid Import Today (kWh)
name: gridImportTodayKwhItem
required: false
type: TEXT
- context: item
label: Home Today (kWh)
name: homeTodayKwhItem
required: false
type: TEXT
component: f7-card
config:
style:
background: transparent
box-shadow: none
margin: 0
padding: 0
slots:
content:
- component: f7-block
config:
style:
background: "#18181b"
border: "1px solid #3f3f46"
borderRadius: 20px
boxShadow: 0 10px 15px -3px rgba(0, 0, 0, 0.2), 0 4px 6px -4px rgba(0, 0, 0, 0.2)
color: "#e4e4e7"
padding: clamp(12px, 1.6vw, 20px) clamp(8px, 1.4vw, 18px)
slots:
default:
- component: f7-row
config:
style:
flexWrap: nowrap
justifyContent: space-between
marginBottom: 12px
slots:
default:
- component: f7-col
config:
style:
flex: 1 1 0
minWidth: 0
textAlign: center
slots:
default:
- component: f7-icon
config:
color: "=(items[props.solarPowerItem] && Number.parseFloat(items[props.solarPowerItem].state || 0) > 1) ? '#FACC15' : '#93c5fd'"
f7: "=(items[props.solarPowerItem] && Number.parseFloat(items[props.solarPowerItem].state || 0) > 1) ? 'sun_max' : 'moon'"
style:
fontSize: clamp(18px, 1.6vw + 12px, 26px)
- component: f7-block
config:
style:
color: "=(items[props.solarPowerItem] && Number.parseFloat(items[props.solarPowerItem].state || 0) > 1) ? '#FACC15' : '#93c5fd'"
fontSize: clamp(13px, 1.4vw + 8px, 24px)
fontWeight: "700"
lineHeight: 1.15
margin: 3px 0 1px
overflow: hidden
textOverflow: ellipsis
whiteSpace: nowrap
slots:
default:
- component: Label
config:
text: "=(items[props.solarPowerItem] && items[props.solarPowerItem].state ? Number.parseFloat(items[props.solarPowerItem].state).toFixed(0) : '0') + ' W'"
- component: f7-block
config:
style:
color: "#a1a1aa"
fontSize: clamp(8px, 0.6vw + 7px, 11px)
fontWeight: "600"
letterSpacing: 0.02em
slots:
default:
- component: Label
config:
text: SOLAR
- component: f7-col
config:
style:
flex: 1 1 0
minWidth: 0
textAlign: center
slots:
default:
- component: f7-icon
config:
color: "#14B8A6"
f7: battery_25
style:
fontSize: clamp(18px, 1.6vw + 12px, 26px)
- component: f7-block
config:
style:
color: "#14B8A6"
fontSize: clamp(13px, 1.4vw + 8px, 24px)
fontWeight: "700"
lineHeight: 1.15
margin: 3px 0 1px
overflow: hidden
textOverflow: ellipsis
whiteSpace: nowrap
slots:
default:
- component: Label
config:
text: "=(items[props.batteryPowerItem] && items[props.batteryPowerItem].state ? (Number.parseFloat(items[props.batteryPowerItem].state) >= 0 ? '+' : '') + Number.parseFloat(items[props.batteryPowerItem].state).toFixed(0) : '0') + ' W'"
- component: f7-block
config:
style:
color: "#a1a1aa"
fontSize: clamp(8px, 0.6vw + 7px, 11px)
fontWeight: "600"
letterSpacing: 0.02em
slots:
default:
- component: Label
config:
text: BATTERY
- component: f7-block
config:
style:
color: "=(items[props.batteryPowerItem] && Number.parseFloat(items[props.batteryPowerItem].state || 0) > 1) ? '#4ade80' : ((items[props.batteryPowerItem] && Number.parseFloat(items[props.batteryPowerItem].state || 0) < -1) ? '#f87171' : '#a1a1aa')"
fontSize: clamp(8px, 0.5vw + 6px, 10px)
marginTop: 1px
whiteSpace: nowrap
slots:
default:
- component: Label
config:
text: "=(items[props.batteryPowerItem] && Number.parseFloat(items[props.batteryPowerItem].state || 0) > 1) ? 'Charging' : ((items[props.batteryPowerItem] && Number.parseFloat(items[props.batteryPowerItem].state || 0) < -1) ? 'Discharging' : 'Idle')"
- component: f7-col
config:
style:
flex: 1 1 0
minWidth: 0
textAlign: center
slots:
default:
- component: f7-icon
config:
color: "#A1A1AA"
f7: house
style:
fontSize: clamp(18px, 1.6vw + 12px, 26px)
- component: f7-block
config:
style:
color: "#f6b7f6"
fontSize: clamp(13px, 1.4vw + 8px, 24px)
fontWeight: "700"
lineHeight: 1.15
margin: 3px 0 1px
overflow: hidden
textOverflow: ellipsis
whiteSpace: nowrap
slots:
default:
- component: Label
config:
text: "=(items[props.homePowerItem] && items[props.homePowerItem].state ? Number.parseFloat(items[props.homePowerItem].state).toFixed(0) : '0') + ' W'"
- component: f7-block
config:
style:
color: "#a1a1aa"
fontSize: clamp(8px, 0.6vw + 7px, 11px)
fontWeight: "600"
letterSpacing: 0.02em
slots:
default:
- component: Label
config:
text: HOME
- component: f7-col
config:
style:
flex: 1 1 0
minWidth: 0
textAlign: center
slots:
default:
- component: f7-icon
config:
color: "#1E3A8A"
f7: bolt_horizontal
style:
fontSize: clamp(18px, 1.6vw + 12px, 26px)
- component: f7-block
config:
style:
color: "=(Number.parseFloat((items[props.gridImportItem] && items[props.gridImportItem].state) || 0) > Number.parseFloat((items[props.gridExportItem] && items[props.gridExportItem].state) || 0) && Number.parseFloat((items[props.gridImportItem] && items[props.gridImportItem].state) || 0) > 1) ? '#f87171' : '#4ade80'"
fontSize: clamp(13px, 1.4vw + 8px, 24px)
fontWeight: "700"
lineHeight: 1.15
margin: 3px 0 1px
overflow: hidden
textOverflow: ellipsis
whiteSpace: nowrap
slots:
default:
- component: Label
config:
text: "=(Number.parseFloat((items[props.gridImportItem] && items[props.gridImportItem].state) || 0) > Number.parseFloat((items[props.gridExportItem] && items[props.gridExportItem].state) || 0) ? '+' + Number.parseFloat((items[props.gridImportItem] && items[props.gridImportItem].state) || 0).toFixed(0) : Number.parseFloat((items[props.gridExportItem] && items[props.gridExportItem].state) || 0).toFixed(0)) + ' W'"
- component: f7-block
config:
style:
color: "#a1a1aa"
fontSize: clamp(8px, 0.6vw + 7px, 11px)
fontWeight: "600"
letterSpacing: 0.02em
slots:
default:
- component: Label
config:
text: GRID
- component: f7-block
config:
style:
color: "=(Number.parseFloat((items[props.gridImportItem] && items[props.gridImportItem].state) || 0) > Number.parseFloat((items[props.gridExportItem] && items[props.gridExportItem].state) || 0) && Number.parseFloat((items[props.gridImportItem] && items[props.gridImportItem].state) || 0) > 1) ? '#f87171' : '#4ade80'"
fontSize: clamp(8px, 0.5vw + 6px, 10px)
marginTop: 1px
whiteSpace: nowrap
slots:
default:
- component: Label
config:
text: "=(Number.parseFloat((items[props.gridImportItem] && items[props.gridImportItem].state) || 0) > Number.parseFloat((items[props.gridExportItem] && items[props.gridExportItem].state) || 0) && Number.parseFloat((items[props.gridImportItem] && items[props.gridImportItem].state) || 0) > 1) ? 'Importing' : (Number.parseFloat((items[props.gridExportItem] && items[props.gridExportItem].state) || 0) > 1 ? 'Exporting' : 'Idle')"
- component: f7-col
config:
style:
flex: 1 1 0
minWidth: 0
textAlign: center
slots:
default:
- component: f7-icon
config:
color: "#3B82F6"
f7: drop
style:
fontSize: clamp(18px, 1.6vw + 12px, 26px)
- component: f7-block
config:
style:
color: "#3B82F6"
fontSize: clamp(13px, 1.4vw + 8px, 24px)
fontWeight: "700"
lineHeight: 1.15
margin: 3px 0 1px
overflow: hidden
textOverflow: ellipsis
whiteSpace: nowrap
slots:
default:
- component: Label
config:
text: "=(items[props.waterUsageLhItem] && items[props.waterUsageLhItem].state ? Number.parseFloat(items[props.waterUsageLhItem].state).toFixed(1) : '0') + ' L/h'"
- component: f7-block
config:
style:
color: "#a1a1aa"
fontSize: clamp(8px, 0.6vw + 7px, 11px)
fontWeight: "600"
letterSpacing: 0.02em
slots:
default:
- component: Label
config:
text: WATER
- component: f7-block
config:
style:
background: "#27272a"
border: "1px solid #3f3f46"
borderRadius: 14px
marginTop: 8px
overflow: visible
padding: 16px 8px 16px
slots:
default:
- component: f7-row
config:
class:
- no-gap
preserveAspectRatio: xMidYMid meet
style:
aspectRatio: 520 / 276
display: block
height: auto
margin: 0 auto
maxHeight: 276px
maxWidth: 560px
overflow: visible
width: 100%
tag: svg
viewBox: 50 19 520 276
width: 100%
xmlns: http://www.w3.org/2000/svg
slots:
default:
- component: f7-row
config:
d: M 108 65 Q 230 100 325 107
fill: none
opacity: "0.3"
stroke: "#CA8A04"
stroke-linecap: round
stroke-width: "=(items[props.solarPowerItem] && Number.parseFloat(items[props.solarPowerItem].state || 0) > 1) ? 10 : 3"
tag: path
- component: f7-row
config:
d: M 108 165 Q 230 125 325 123
fill: none
opacity: "0.35"
stroke: "#0F766E"
stroke-linecap: round
stroke-width: "=(items[props.batteryPowerItem] && Math.abs(Number.parseFloat(items[props.batteryPowerItem].state || 0)) > 1) ? 10 : 3"
tag: path
- component: f7-row
config:
d: M 512 65 Q 390 100 325 107
fill: none
opacity: "0.35"
stroke: "=(Number.parseFloat((items[props.gridImportItem] && items[props.gridImportItem].state) || 0) > Number.parseFloat((items[props.gridExportItem] && items[props.gridExportItem].state) || 0) && Number.parseFloat((items[props.gridImportItem] && items[props.gridImportItem].state) || 0) > 1) ? '#f87171' : '#4ade80'"
stroke-linecap: round
stroke-width: "=(Math.max(Number.parseFloat((items[props.gridImportItem] && items[props.gridImportItem].state) || 0), Number.parseFloat((items[props.gridExportItem] && items[props.gridExportItem].state) || 0)) > 1) ? 10 : 3"
tag: path
- component: f7-row
config:
d: M 317 255 Q 317 195 317 148
fill: none
opacity: "0.35"
stroke: "#2563EB"
stroke-linecap: round
stroke-width: "=(items[props.waterUsageLhItem] && Number.parseFloat(items[props.waterUsageLhItem].state || 0) > 0) ? 10 : 3"
tag: path
- component: f7-row
config:
d: M 512 165 Q 390 125 325 123
fill: none
opacity: "0.35"
stroke: "#16A34A"
stroke-linecap: round
stroke-width: "=(items[props.evPowerItem] && Number.parseFloat(items[props.evPowerItem].state || 0) > 1) ? 10 : 3"
tag: path
- component: f7-row
config:
cx: "0"
cy: "0"
fill: "#EAB308"
r: "=(items[props.solarPowerItem] && items[props.solarPowerItem].state ? Math.max(3, Math.min(7.5, 3 + ((Number.parseFloat(items[props.solarPowerItem].state) || 0) / 700))) : 3)"
style:
opacity: "=(items[props.solarPowerItem] && items[props.solarPowerItem].state && Number.parseFloat(items[props.solarPowerItem].state) > 1) ? 1 : 0"
tag: circle
slots:
default:
- component: f7-row
config:
dur: "=(items[props.solarPowerItem] && items[props.solarPowerItem].state ? Math.max(0.55, Math.min(2.2, 1.7 - ((Number.parseFloat(items[props.solarPowerItem].state) || 0) / 2200))) : 1.5)"
path: M 108 65 Q 230 100 325 107
repeatCount: indefinite
tag: animateMotion
- component: f7-row
config:
cx: "0"
cy: "0"
fill: "#EAB308"
r: "=(items[props.solarPowerItem] && items[props.solarPowerItem].state ? Math.max(2.2, Math.min(5.5, 2.5 + ((Number.parseFloat(items[props.solarPowerItem].state) || 0) / 900))) : 2.2)"
style:
opacity: "=(items[props.solarPowerItem] && items[props.solarPowerItem].state && Number.parseFloat(items[props.solarPowerItem].state) > 1) ? 0.7 : 0"
tag: circle
slots:
default:
- component: f7-row
config:
begin: -0.7s
dur: "=(items[props.solarPowerItem] && items[props.solarPowerItem].state ? Math.max(0.6, Math.min(2.0, 1.55 - ((Number.parseFloat(items[props.solarPowerItem].state) || 0) / 2400))) : 1.4)"
path: M 108 65 Q 230 100 325 107
repeatCount: indefinite
tag: animateMotion
- component: f7-row
config:
cx: "0"
cy: "0"
fill: "#2DD4BF"
r: "=(items[props.batteryPowerItem] && items[props.batteryPowerItem].state ? Math.max(3, Math.min(7.5, 3 + Math.abs(Number.parseFloat(items[props.batteryPowerItem].state) || 0) / 550)) : 3)"
style:
opacity: "=(items[props.batteryPowerItem] && items[props.batteryPowerItem].state && Math.abs(Number.parseFloat(items[props.batteryPowerItem].state)) > 1) ? 1 : 0"
tag: circle
slots:
default:
- component: f7-row
config:
dur: "=(items[props.batteryPowerItem] && items[props.batteryPowerItem].state ? Math.max(0.55, Math.min(2.2, 1.9 - (Math.abs(Number.parseFloat(items[props.batteryPowerItem].state) || 0) / 2000))) : 1.85)"
path: "=(items[props.batteryPowerItem] && items[props.batteryPowerItem].state && Number.parseFloat(items[props.batteryPowerItem].state) < 0) ? 'M 108 165 Q 230 125 325 123' : 'M 325 123 Q 230 125 108 165'"
repeatCount: indefinite
tag: animateMotion
- component: f7-row
config:
cx: "0"
cy: "0"
fill: "#2DD4BF"
r: "=(items[props.batteryPowerItem] && items[props.batteryPowerItem].state ? Math.max(2.2, Math.min(5.5, 2.5 + Math.abs(Number.parseFloat(items[props.batteryPowerItem].state) || 0) / 700)) : 2.2)"
style:
opacity: "=(items[props.batteryPowerItem] && items[props.batteryPowerItem].state && Math.abs(Number.parseFloat(items[props.batteryPowerItem].state)) > 10) ? 0.7 : 0"
tag: circle
slots:
default:
- component: f7-row
config:
begin: -0.65s
dur: "=(items[props.batteryPowerItem] && items[props.batteryPowerItem].state ? Math.max(0.6, Math.min(1.95, 1.7 - (Math.abs(Number.parseFloat(items[props.batteryPowerItem].state) || 0) / 2200))) : 1.5)"
path: "=(items[props.batteryPowerItem] && items[props.batteryPowerItem].state && Number.parseFloat(items[props.batteryPowerItem].state) < 0) ? 'M 108 165 Q 230 125 325 123' : 'M 325 123 Q 230 125 108 165'"
repeatCount: indefinite
tag: animateMotion
- component: f7-row
config:
cx: "0"
cy: "0"
fill: "=(Number.parseFloat((items[props.gridImportItem] && items[props.gridImportItem].state) || 0) > Number.parseFloat((items[props.gridExportItem] && items[props.gridExportItem].state) || 0) ? '#f87171' : '#4ade80')"
r: =(Math.max(3, Math.min(7.5, 3 + (Math.max(Number.parseFloat((items[props.gridImportItem] && items[props.gridImportItem].state) || 0), Number.parseFloat((items[props.gridExportItem] && items[props.gridExportItem].state) || 0)) / 550))))
style:
opacity: "=(Math.max(Number.parseFloat((items[props.gridImportItem] && items[props.gridImportItem].state) || 0), Number.parseFloat((items[props.gridExportItem] && items[props.gridExportItem].state) || 0)) > 1) ? 1 : 0"
tag: circle
slots:
default:
- component: f7-row
config:
dur: =(Math.max(0.5, Math.min(2.0, 1.5 - (Math.max(Number.parseFloat((items[props.gridImportItem] && items[props.gridImportItem].state) || 0), Number.parseFloat((items[props.gridExportItem] && items[props.gridExportItem].state) || 0)) / 1800))))
path: "=(Number.parseFloat((items[props.gridImportItem] && items[props.gridImportItem].state) || 0) > Number.parseFloat((items[props.gridExportItem] && items[props.gridExportItem].state) || 0) ? 'M 512 65 Q 390 100 325 107' : 'M 325 107 Q 390 100 512 65')"
repeatCount: indefinite
tag: animateMotion
- component: f7-row
config:
cx: "0"
cy: "0"
fill: "=(Number.parseFloat((items[props.gridImportItem] && items[props.gridImportItem].state) || 0) > Number.parseFloat((items[props.gridExportItem] && items[props.gridExportItem].state) || 0) ? '#f87171' : '#4ade80')"
r: =(Math.max(2.2, Math.min(5.5, 2.5 + (Math.max(Number.parseFloat((items[props.gridImportItem] && items[props.gridImportItem].state) || 0), Number.parseFloat((items[props.gridExportItem] && items[props.gridExportItem].state) || 0)) / 700))))
style:
opacity: "=(Math.max(Number.parseFloat((items[props.gridImportItem] && items[props.gridImportItem].state) || 0), Number.parseFloat((items[props.gridExportItem] && items[props.gridExportItem].state) || 0)) > 1) ? 0.7 : 0"
tag: circle
slots:
default:
- component: f7-row
config:
begin: -0.6s
dur: =(Math.max(0.55, Math.min(1.8, 1.35 - (Math.max(Number.parseFloat((items[props.gridImportItem] && items[props.gridImportItem].state) || 0), Number.parseFloat((items[props.gridExportItem] && items[props.gridExportItem].state) || 0)) / 2000))))
path: "=(Number.parseFloat((items[props.gridImportItem] && items[props.gridImportItem].state) || 0) > Number.parseFloat((items[props.gridExportItem] && items[props.gridExportItem].state) || 0) ? 'M 512 65 Q 390 100 325 107' : 'M 325 107 Q 390 100 512 65')"
repeatCount: indefinite
tag: animateMotion
- component: f7-row
config:
cx: "0"
cy: "0"
fill: "#60A5FA"
r: "=(items[props.waterUsageLhItem] && items[props.waterUsageLhItem].state ? Math.max(2.5, Math.min(6.5, 2.8 + ((Number.parseFloat(items[props.waterUsageLhItem].state) || 0) / 25))) : 2.5)"
style:
opacity: "=(items[props.waterUsageLhItem] && items[props.waterUsageLhItem].state && Number.parseFloat(items[props.waterUsageLhItem].state) > 0) ? 1 : 0"
tag: circle
slots:
default:
- component: f7-row
config:
dur: "=(items[props.waterUsageLhItem] && items[props.waterUsageLhItem].state ? Math.max(0.7, Math.min(2.3, 1.9 - ((Number.parseFloat(items[props.waterUsageLhItem].state) || 0) / 60))) : 1.8)"
path: M 317 255 Q 317 195 317 148
repeatCount: indefinite
tag: animateMotion
- component: f7-row
config:
cx: "0"
cy: "0"
fill: "#60A5FA"
r: "=(items[props.waterUsageLhItem] && items[props.waterUsageLhItem].state ? Math.max(2, Math.min(4, 2.2 + ((Number.parseFloat(items[props.waterUsageLhItem].state) || 0) / 30))) : 2)"
style:
opacity: "=(items[props.waterUsageLhItem] && items[props.waterUsageLhItem].state && Number.parseFloat(items[props.waterUsageLhItem].state) > 0) ? 0.7 : 0"
tag: circle
slots:
default:
- component: f7-row
config:
begin: -0.6s
dur: "=(items[props.waterUsageLhItem] && items[props.waterUsageLhItem].state ? Math.max(0.6, Math.min(1.8, 1.5 - ((Number.parseFloat(items[props.waterUsageLhItem].state) || 0) / 70))) : 1.4)"
path: M 317 255 Q 317 195 317 148
repeatCount: indefinite
tag: animateMotion
- component: f7-row
config:
cx: "0"
cy: "0"
fill: "#4ADE80"
r: "=(items[props.evPowerItem] && items[props.evPowerItem].state ? Math.max(3, Math.min(7.5, 3 + ((Number.parseFloat(items[props.evPowerItem].state) || 0) / 600))) : 3)"
style:
opacity: "=(items[props.evPowerItem] && items[props.evPowerItem].state && Number.parseFloat(items[props.evPowerItem].state) > 30) ? 1 : 0"
tag: circle
slots:
default:
- component: f7-row
config:
dur: "=(items[props.evPowerItem] && items[props.evPowerItem].state ? Math.max(0.55, Math.min(2.0, 1.7 - ((Number.parseFloat(items[props.evPowerItem].state) || 0) / 2000))) : 1.6)"
path: M 325 123 Q 390 125 512 165
repeatCount: indefinite
tag: animateMotion
- component: f7-row
config:
cx: "0"
cy: "0"
fill: "#4ADE80"
r: "=(items[props.evPowerItem] && items[props.evPowerItem].state ? Math.max(2.2, Math.min(5.5, 2.5 + ((Number.parseFloat(items[props.evPowerItem].state) || 0) / 750))) : 2.2)"
style:
opacity: "=(items[props.evPowerItem] && items[props.evPowerItem].state && Number.parseFloat(items[props.evPowerItem].state) > 30) ? 0.7 : 0"
tag: circle
slots:
default:
- component: f7-row
config:
begin: -0.65s
dur: "=(items[props.evPowerItem] && items[props.evPowerItem].state ? Math.max(0.6, Math.min(1.8, 1.55 - ((Number.parseFloat(items[props.evPowerItem].state) || 0) / 2100))) : 1.45)"
path: M 325 123 Q 390 125 512 165
repeatCount: indefinite
tag: animateMotion
- component: f7-row
config:
cx: "108"
cy: "65"
fill: "=(items[props.solarPowerItem] && Number.parseFloat(items[props.solarPowerItem].state || 0) > 1) ? '#713F12' : '#1e293b'"
r: "32"
stroke: "=(items[props.solarPowerItem] && Number.parseFloat(items[props.solarPowerItem].state || 0) > 1) ? '#EAB308' : '#93c5fd'"
stroke-width: "3"
tag: circle
- component: f7-row
config:
fill: "=(items[props.solarPowerItem] && Number.parseFloat(items[props.solarPowerItem].state || 0) > 1) ? '#EAB308' : '#93c5fd'"
font-size: "30"
font-weight: "700"
innerHTML: "=(items[props.solarPowerItem] && Number.parseFloat(items[props.solarPowerItem].state || 0) > 1) ? '☀️' : '🌙'"
tag: text
text-anchor: middle
x: "108"
y: "73"
- component: f7-row
config:
cx: "108"
cy: "165"
fill: "=(Number.parseFloat((items[props.batteryPercentItem] && items[props.batteryPercentItem].state) || 0) >= 20) ? '#134E4A' : ((Number.parseFloat((items[props.batteryPercentItem] && items[props.batteryPercentItem].state) || 0) >= 10) ? '#78350F' : '#7F1D1D')"
r: "32"
stroke: "=(Number.parseFloat((items[props.batteryPercentItem] && items[props.batteryPercentItem].state) || 0) >= 20) ? '#2DD4BF' : ((Number.parseFloat((items[props.batteryPercentItem] && items[props.batteryPercentItem].state) || 0) >= 10) ? '#F59E0B' : '#EF4444')"
stroke-width: "3"
tag: circle
- component: f7-row
config:
fill: "#ffffff"
font-size: "30"
font-weight: "700"
innerHTML: 🔋
tag: text
text-anchor: middle
x: "108"
y: "173"
- component: f7-row
config:
cx: "317"
cy: "115"
fill: none
opacity: "=(items[props.homePeakTodayItem] && Number.parseFloat(items[props.homePeakTodayItem].state || 0) > 1) ? 0.75 : 0"
r: =(48 + 24 * Math.min(1, Math.max(0, Number.parseFloat((items[props.homePeakTodayItem] && items[props.homePeakTodayItem].state) || 0) / 9000)))
stroke: "#F97316"
stroke-width: "1.5"
tag: circle
- component: f7-row
config:
cx: "317"
cy: "115"
fill: "=(Number.parseFloat((items[props.homePowerItem] && items[props.homePowerItem].state) || 0) < 1500) ? '#22C55E' : ((Number.parseFloat((items[props.homePowerItem] && items[props.homePowerItem].state) || 0) < 3000) ? '#84CC16' : ((Number.parseFloat((items[props.homePowerItem] && items[props.homePowerItem].state) || 0) < 4500) ? '#EAB308' : ((Number.parseFloat((items[props.homePowerItem] && items[props.homePowerItem].state) || 0) < 6000) ? '#F59E0B' : ((Number.parseFloat((items[props.homePowerItem] && items[props.homePowerItem].state) || 0) < 7500) ? '#F97316' : ((Number.parseFloat((items[props.homePowerItem] && items[props.homePowerItem].state) || 0) < 8500) ? '#F43F5E' : '#EF4444')))))"
opacity: "0.16"
r: =(48 + 24 * Math.min(1, Math.max(0, Number.parseFloat((items[props.homePowerItem] && items[props.homePowerItem].state) || 0) / 9000)))
tag: circle
slots:
default:
- component: f7-row
config:
attributeName: opacity
calcMode: spline
dur: =(2.6 - 1.4 * Math.min(1, Math.max(0, Number.parseFloat((items[props.homePowerItem] && items[props.homePowerItem].state) || 0) / 9000))) + 's'
keySplines: 0.4 0 0.2 1; 0.4 0 0.2 1
keyTimes: 0;0.5;1
repeatCount: indefinite
tag: animate
values: 0.08;0.30;0.08
- component: f7-row
config:
attributeName: r
calcMode: spline
dur: =(2.6 - 1.4 * Math.min(1, Math.max(0, Number.parseFloat((items[props.homePowerItem] && items[props.homePowerItem].state) || 0) / 9000))) + 's'
keySplines: 0.4 0 0.2 1; 0.4 0 0.2 1
keyTimes: 0;0.5;1
repeatCount: indefinite
tag: animate
values: =(Math.round(48 + 24 * Math.min(1, Math.max(0, Number.parseFloat((items[props.homePowerItem] && items[props.homePowerItem].state) || 0) / 9000))) + ';' + Math.round(54 + 32 * Math.min(1, Math.max(0, Number.parseFloat((items[props.homePowerItem] && items[props.homePowerItem].state) || 0) / 9000))) + ';' + Math.round(48 + 24 * Math.min(1, Math.max(0, Number.parseFloat((items[props.homePowerItem] && items[props.homePowerItem].state) || 0) / 9000))))
- component: f7-row
config:
cx: "317"
cy: "115"
fill: "=(Number.parseFloat((items[props.homePowerItem] && items[props.homePowerItem].state) || 0) < 1500) ? '#22C55E' : ((Number.parseFloat((items[props.homePowerItem] && items[props.homePowerItem].state) || 0) < 3000) ? '#84CC16' : ((Number.parseFloat((items[props.homePowerItem] && items[props.homePowerItem].state) || 0) < 4500) ? '#EAB308' : ((Number.parseFloat((items[props.homePowerItem] && items[props.homePowerItem].state) || 0) < 6000) ? '#F59E0B' : ((Number.parseFloat((items[props.homePowerItem] && items[props.homePowerItem].state) || 0) < 7500) ? '#F97316' : ((Number.parseFloat((items[props.homePowerItem] && items[props.homePowerItem].state) || 0) < 8500) ? '#F43F5E' : '#EF4444')))))"
opacity: "0.22"
r: =(44 + 16 * Math.min(1, Math.max(0, Number.parseFloat((items[props.homePowerItem] && items[props.homePowerItem].state) || 0) / 9000)))
tag: circle
slots:
default:
- component: f7-row
config:
attributeName: opacity
begin: -0.35s
calcMode: spline
dur: =(2.6 - 1.4 * Math.min(1, Math.max(0, Number.parseFloat((items[props.homePowerItem] && items[props.homePowerItem].state) || 0) / 9000))) + 's'
keySplines: 0.4 0 0.2 1; 0.4 0 0.2 1
keyTimes: 0;0.5;1
repeatCount: indefinite
tag: animate
values: 0.12;0.38;0.12
- component: f7-row
config:
attributeName: r
begin: -0.35s
calcMode: spline
dur: =(2.6 - 1.4 * Math.min(1, Math.max(0, Number.parseFloat((items[props.homePowerItem] && items[props.homePowerItem].state) || 0) / 9000))) + 's'
keySplines: 0.4 0 0.2 1; 0.4 0 0.2 1
keyTimes: 0;0.5;1
repeatCount: indefinite
tag: animate
values: =(Math.round(44 + 16 * Math.min(1, Math.max(0, Number.parseFloat((items[props.homePowerItem] && items[props.homePowerItem].state) || 0) / 9000))) + ';' + Math.round(48 + 22 * Math.min(1, Math.max(0, Number.parseFloat((items[props.homePowerItem] && items[props.homePowerItem].state) || 0) / 9000))) + ';' + Math.round(44 + 16 * Math.min(1, Math.max(0, Number.parseFloat((items[props.homePowerItem] && items[props.homePowerItem].state) || 0) / 9000))))
- component: f7-row
config:
cx: "317"
cy: "115"
fill: "#ee9bed"
r: "40"
stroke: "#d243d2"
stroke-width: "3.5"
tag: circle
- component: f7-row
config:
fill: "#ffffff"
font-size: "40"
font-weight: "700"
innerHTML: 🏠
tag: text
text-anchor: middle
x: "317"
y: "123"
- component: f7-row
config:
cx: "512"
cy: "65"
fill: "#1E3A5F"
r: "32"
stroke: "#60A5FA"
stroke-width: "3"
tag: circle
- component: f7-row
config:
fill: "#ffffff"
font-size: "30"
font-weight: "700"
innerHTML: ⚡
tag: text
text-anchor: middle
x: "512"
y: "72"
- component: f7-row
config:
cx: "512"
cy: "165"
fill: "#4ADE80"
opacity: "=(items[props.evPowerItem] && Number.parseFloat(items[props.evPowerItem].state || 0) > 1) ? (0.16 + 0.16 * Math.min(1, Number.parseFloat(items[props.evPowerItem].state || 0) / 7000)) : 0"
r: "=(items[props.evPowerItem] && Number.parseFloat(items[props.evPowerItem].state || 0) > 1) ? (38 + 16 * Math.min(1, Number.parseFloat(items[props.evPowerItem].state || 0) / 7000)) : 32"
tag: circle
- component: f7-row
config:
cx: "512"
cy: "165"
fill: "#14532D"
r: "32"
stroke: "#4ADE80"
stroke-width: "3"
tag: circle
- component: f7-row
config:
fill: "#ffffff"
font-size: "30"
font-weight: "700"
innerHTML: 🚗
tag: text
text-anchor: middle
x: "512"
y: "172"
- component: f7-row
config:
cx: "317"
cy: "255"
fill: "#1E3A8A"
r: "32"
stroke: "#60A5FA"
stroke-width: "3"
tag: circle
- component: f7-row
config:
fill: "#ffffff"
font-size: "30"
font-weight: "700"
innerHTML: 💧
tag: text
text-anchor: middle
x: "317"
y: "263"
- component: f7-block
config:
style:
background: "#27272a"
border: "1px solid #3f3f46"
borderRadius: 14px
marginTop: 12px
padding: 12px 8px
slots:
default:
- component: f7-row
config:
style:
alignItems: stretch
visible: =device.desktop
slots:
default:
- component: f7-col
config:
style:
textAlign: center
width: "25"
slots:
default:
- component: Label
config:
style:
color: "#FACC15"
fontSize: 20px
fontWeight: "700"
text: "=(items[props.solarTodayKwhItem] && Number.parseFloat(items[props.solarTodayKwhItem].state) == Number.parseFloat(items[props.solarTodayKwhItem].state)) ? Number.parseFloat(items[props.solarTodayKwhItem].state).toFixed(1) + ' kWh' : '—'"
- component: Label
config:
style:
color: "#a1a1aa"
fontSize: 10px
text: PV TODAY
- component: f7-col
config:
style:
borderLeft: "1px solid #3f3f46"
textAlign: center
width: "25"
slots:
default:
- component: Label
config:
style:
color: "#f6b7f6"
fontSize: 20px
fontWeight: "700"
text: "=(items[props.homeTodayKwhItem] && Number.parseFloat(items[props.homeTodayKwhItem].state) == Number.parseFloat(items[props.homeTodayKwhItem].state)) ? Number.parseFloat(items[props.homeTodayKwhItem].state).toFixed(1) + ' kWh' : '0.0 kWh'"
- component: Label
config:
style:
color: "#a1a1aa"
fontSize: 10px
text: HOME TODAY
- component: f7-col
config:
style:
borderLeft: "1px solid #3f3f46"
textAlign: center
width: "25"
slots:
default:
- component: Label
config:
style:
color: "#f87171"
fontSize: 20px
fontWeight: "700"
text: "=(items[props.gridImportTodayKwhItem] && Number.parseFloat(items[props.gridImportTodayKwhItem].state) == Number.parseFloat(items[props.gridImportTodayKwhItem].state)) ? Number.parseFloat(items[props.gridImportTodayKwhItem].state).toFixed(1) + ' kWh' : '—'"
- component: Label
config:
style:
color: "#a1a1aa"
fontSize: 10px
text: GRID TODAY
- component: f7-col
config:
style:
borderLeft: "1px solid #3f3f46"
textAlign: center
width: "25"
slots:
default:
- component: Label
config:
style:
color: "#fdba74"
fontSize: 20px
fontWeight: "700"
text: "=(items[props.oilPriceItem] && items[props.oilPriceItem].state && items[props.oilPriceItem].state != 'NULL' && items[props.oilPriceItem].state != 'UNDEF') ? ('🔥 ' + (items[props.oilPriceItem].displayState || items[props.oilPriceItem].state)) : '🔥 —'"
- component: Label
config:
style:
color: "#a1a1aa"
fontSize: 10px
text: MAZOUT
- component: f7-row
config:
style:
alignItems: stretch
visible: =!device.desktop
slots:
default:
- component: f7-col
config:
style:
textAlign: center
width: "50"
slots:
default:
- component: Label
config:
style:
color: "#FACC15"
fontSize: 18px
fontWeight: "700"
text: "=(items[props.solarTodayKwhItem] && Number.parseFloat(items[props.solarTodayKwhItem].state) == Number.parseFloat(items[props.solarTodayKwhItem].state)) ? Number.parseFloat(items[props.solarTodayKwhItem].state).toFixed(1) + ' kWh' : '—'"
- component: Label
config:
style:
color: "#a1a1aa"
fontSize: 10px
text: PV TODAY
- component: f7-col
config:
style:
borderLeft: "1px solid #3f3f46"
textAlign: center
width: "50"
slots:
default:
- component: Label
config:
style:
color: "#ee9bed"
fontSize: 18px
fontWeight: "700"
text: "=(items[props.homeTodayKwhItem] && Number.parseFloat(items[props.homeTodayKwhItem].state) == Number.parseFloat(items[props.homeTodayKwhItem].state)) ? Number.parseFloat(items[props.homeTodayKwhItem].state).toFixed(1) + ' kWh' : '0.0 kWh'"
- component: Label
config:
style:
color: "#a1a1aa"
fontSize: 10px
text: HOME TODAY
- component: f7-row
config:
style:
alignItems: stretch
marginTop: 8px
visible: =!device.desktop
slots:
default:
- component: f7-col
config:
style:
textAlign: center
width: "50"
slots:
default:
- component: Label
config:
style:
color: "#f87171"
fontSize: 18px
fontWeight: "700"
text: "=(items[props.gridImportTodayKwhItem] && Number.parseFloat(items[props.gridImportTodayKwhItem].state) == Number.parseFloat(items[props.gridImportTodayKwhItem].state)) ? Number.parseFloat(items[props.gridImportTodayKwhItem].state).toFixed(1) + ' kWh' : '—'"
- component: Label
config:
style:
color: "#a1a1aa"
fontSize: 10px
text: GRID TODAY
- component: f7-col
config:
style:
borderLeft: "1px solid #3f3f46"
textAlign: center
width: "50"
slots:
default:
- component: Label
config:
style:
color: "#fdba74"
fontSize: 18px
fontWeight: "700"
text: "=(items[props.oilPriceItem] && items[props.oilPriceItem].state && items[props.oilPriceItem].state != 'NULL' && items[props.oilPriceItem].state != 'UNDEF') ? ('🔥 ' + (items[props.oilPriceItem].displayState || items[props.oilPriceItem].state)) : '🔥 —'"
- component: Label
config:
style:
color: "#a1a1aa"
fontSize: 10px
text: MAZOUT
- component: f7-block
config:
style:
background: "#27272a"
border: "1px solid #3f3f46"
borderRadius: 14px
marginTop: 12px
padding: 14px 12px
slots:
default:
- component: f7-row
config:
style:
alignItems: center
slots:
default:
- component: f7-col
config:
small: "25"
style:
alignItems: center
display: flex
gap: 10px
justifyContent: center
marginBottom: 8px
width: "100"
slots:
default:
- component: f7-icon
config:
innerHTML: 🔋
size: 26
- component: f7-block
slots:
default:
- component: Label
config:
style:
color: "#e4e4e7"
fontSize: 14px
fontWeight: "700"
text: BATTERY
- component: Label
config:
style:
color: "#a1a1aa"
fontSize: 10px
text: STATUS
- component: f7-col
config:
small: "25"
style:
textAlign: center
width: "33"
slots:
default:
- component: Label
config:
style:
color: "#14B8A6"
fontSize: 22px
fontWeight: "700"
text: "=(items[props.batteryPercentItem] && items[props.batteryPercentItem].state) ? Number(items[props.batteryPercentItem].state).toFixed(0) + ' %' : '—'"
- component: Label
config:
style:
color: "#a1a1aa"
fontSize: 11px
text: SOC
- component: f7-col
config:
small: "25"
style:
textAlign: center
width: "33"
slots:
default:
- component: Label
config:
style:
color: "#14B8A6"
fontSize: 22px
fontWeight: "700"
text: "=(items[props.batteryChargeRateItem] && items[props.batteryChargeRateItem].state) ? Number(items[props.batteryChargeRateItem].state).toFixed(0) + ' %' : '—'"
- component: Label
config:
style:
color: "#a1a1aa"
fontSize: 11px
text: Charge Rate
- component: f7-col
config:
small: "25"
style:
textAlign: center
width: "33"
slots:
default:
- component: Label
config:
style:
color: "#14B8A6"
fontSize: 22px
fontWeight: "700"
text: "=(items[props.batteryDischargeRateItem] && items[props.batteryDischargeRateItem].state) ? Number(items[props.batteryDischargeRateItem].state).toFixed(0) + ' %' : '—'"
- component: Label
config:
style:
color: "#a1a1aa"
fontSize: 11px
text: Discharge Rate
- component: f7-block
config:
style:
background: "#27272a"
border: "1px solid #3f3f46"
borderRadius: 14px
marginTop: 12px
padding: 14px 12px
slots:
default:
- component: f7-row
config:
style:
alignItems: center
slots:
default:
- component: f7-col
config:
small: "25"
style:
alignItems: center
display: flex
gap: 10px
justifyContent: center
marginBottom: 8px
width: "100"
slots:
default:
- component: f7-icon
config:
f7: car_fill
size: 26
style:
color: "=(items[props.evConnectedItem] && (items[props.evConnectedItem].state == 'ON' || items[props.evConnectedItem].state == 'true' || items[props.evConnectedItem].state == 'OPEN' || Number.parseFloat(items[props.evConnectedItem].state || 0) > 0)) ? '#22C55E' : '#f87171'"
- component: f7-block
slots:
default:
- component: Label
config:
style:
color: "#e4e4e7"
fontSize: 14px
fontWeight: "700"
text: MEGATRON
- component: Label
config:
style:
color: "#a1a1aa"
fontSize: 10px
text: STATUS
- component: f7-col
config:
small: "25"
style:
textAlign: center
width: "33"
slots:
default:
- component: Label
config:
style:
color: "#4ade80"
fontSize: 22px
fontWeight: "700"
text: "=(items[props.EVBatteryPercentItem] && items[props.EVBatteryPercentItem].state) ? Number(items[props.EVBatteryPercentItem].state).toFixed(0) + ' %' : '—'"
- component: Label
config:
style:
color: "#a1a1aa"
fontSize: 11px
text: SOC
- component: f7-col
config:
small: "25"
style:
textAlign: center
width: "33"
slots:
default:
- component: Label
config:
style:
color: "#4ade80"
fontSize: 22px
fontWeight: "700"
text: "=(items[props.evPowerItem] && items[props.evPowerItem].state ? Number.parseFloat(items[props.evPowerItem].state).toFixed(0) : '0') + ' W'"
- component: Label
config:
style:
color: "#a1a1aa"
fontSize: 11px
text: POWER
- component: f7-col
config:
small: "25"
style:
textAlign: center
width: "33"
slots:
default:
- component: Label
config:
style:
color: "#22C55E"
fontSize: 20px
fontWeight: "700"
text: "=(items[props.evChargeKwhItem] && items[props.evChargeKwhItem].state) ? Number.parseFloat(items[props.evChargeKwhItem].state).toFixed(1) + ' Wh' : '0 kWh'"
- component: Label
config:
style:
color: "#a1a1aa"
fontSize: 10px
text: CHARGE

