- Published on
Energy Overview in Home Assistant with draw.io
- Authors
- Name
- MajorTwip
- @MajorTwip
Overview
Home Assistant has a very useful overview page for energy flows. There is a power overview as a community plugin for Lovelace Card. This is suitable for many setups, but mine is a bit more complex. In particular, I couldn't display my island solar system in addition to the balcony power plant. So I quickly drew a diagram on draw.io.
I then imported this into Home Assistant. For this I use the Floorplan Plugin
Details
draw.io
ID in Element
In order to be able to control the various text fields via HA, they need an id parameter in the element. This is not directly possible with draw.io, as it creates an extremely nested SVG construct during SVG export. More on that later. But at least draw.io has plugins called "plugins/props.js" and "plugins/svgdata.js" which can be activated via Extras --> Plugins. Afterwards, you can change the ID by right-clicking on a symbol --> Edit Data --> Double-click on the ID. In HA we will then address these via "cell-ID_from_draw.io".
<div>
Text fields become full-surface Text fields, or more specifically all rectangular objects from the draw.io library, contain two sub-elements as SVG elements, one of which covers the entire surface. This means that HA Floorplan cannot render the click areas cleanly. (On the entire map, a click leads to displaying the history of the last configured object instead of the actually clicked element). Creating your own SVG with a <rect>
leads to the same problem. So I created an SVG that draws the rectangle as a <path>
.
<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 100 40" xml:space="preserve">
<style>
path.icon {
stroke-width: 1;
stroke: black;
@media (prefers-color-scheme: dark) {stroke: white; }
}
</style>
<path class="icon" d="M 0 0 h 100 v 40 h -100 Z" fill-opacity="0" />
</svg>
Dark Mode
I found the icons at SVG Repo. However, in dark mode they remain black. So I simply inserted the style tag (see above) and then assigned this class to the path.
Home Assistant Floorplan
You can find the necessary information in the Floorplan documentation. The only special thing is the custom function that looks for the lowest element in the extremely nested SVG export from draw.io to set the text there. With the rectangles that are now paths, this would actually no longer be necessary, as all elements now have a <text>
.
type: custom:floorplan-card
views:
- title: Floorplan
path: floorplan
theme: Google Dark Theme
badges: []
cards:
- type: custom:floorplan-card
full_height: true
config:
image: /local/floorplan/Power5.svg
cache: false
console_log_level: info
defaults:
hover_action: hover-info
tap_action: more-info
state_action:
action: call-service
service: floorplan.class_set
service_data: default-${entity.state}
rules:
- element: cell-val_grid_total
entity: sensor.power_mains
state_action:
- service: floorplan.execute
service_data: ${functions.setLowestElementsText(entity, element)};
- entity: sensor.ups_power
elements:
- cell-sw_island_bypass
- cell-sw_island_inv
state_action:
- service: floorplan.style_set
service_data:
element: cell-sw_island_bypass
style: '${(entity.state > 5) ? "opacity:1" : "opacity:0"}'
- service: floorplan.style_set
service_data:
element: cell-sw_island_inv
style: '${(entity.state > 5) ? "opacity:0" : "opacity:1"}'
- entity: binary_sensor.goe_223755_car_0
elements:
- cell-ico_car
- cell-val_car
- cell-line_car
state_action:
- service: floorplan.style_set
service_data:
style: >-
${(entity.state==="plugged in") ? "opacity:1" :
"opacity:0"}
functions: |
>
return {
setLowestElementsText: (entity, element, precision = 0) => {
const valNum = parseFloat(entity.state).toFixed(precision)
const value = valNum + entity.attributes.unit_of_measurement
const leaf = element.querySelector("text")
if(leaf){
leaf.textContent=value;
return;
}
let lowestNode = null;
let maxDepth = -1;
function traverse(node, depth) {
if (depth > maxDepth) {
maxDepth = depth;
lowestNode = node;
}
for (let child of node.children) {
traverse(child, depth + 1);
}
}
traverse(element, 0);
lowestNode.textContent=value;
return;
},
};
type: panel