From 67f6021cd5267a246be4d1e22c902a395603fb4c Mon Sep 17 00:00:00 2001 From: jayjojayson <2683358+jayjojayson@users.noreply.github.com> Date: Sun, 1 Mar 2026 23:38:55 +0000 Subject: [PATCH] power-flux-card Auto-build --- dist/power-flux-card.js | 248 ++++++++++++++++++++++++++++++++-------- 1 file changed, 200 insertions(+), 48 deletions(-) diff --git a/dist/power-flux-card.js b/dist/power-flux-card.js index d39601d..e100d7d 100644 --- a/dist/power-flux-card.js +++ b/dist/power-flux-card.js @@ -25,7 +25,7 @@ const lang_de = { "editor.battery_soc_label": "Ladestand (%)", "editor.house_total_title": "🏠 Gesamtverbrauch (optional)", "editor.house_sensor_label": "Sensor für Hausverbrauch (optional)", - "editor.house_sensor_hint": "Wird benötigt, damit das Haus-Icon anklickbar ist (compact view).", + "editor.house_sensor_hint": "Wird benötigt, damit das Haus-Icon anklickbar ist (more-details). Ansonsten wird der Hausverbrauch berechnet.", "editor.consumer_1_title": "🚗 Links (Lila)", "editor.consumer_2_title": "♨️ Mitte (Orange)", "editor.consumer_3_title": "🏊 Rechts (Türkis)", @@ -42,8 +42,12 @@ const lang_de = { "editor.grid_to_battery_sensor": "Netz-zu-Batterie Sensor (Watt)", "editor.grid_to_battery_hint": "Optional: separater Sensor für den Netz-zu-Batterie Fluss. Wenn leer, wird der Wert automatisch berechnet.", "editor.grid_combined_sensor": "Kombinierter Netz-Sensor (W, Optional)", - "editor.grid_combined_hint": "Ein Sensor für Import UND Export: positiv = Import, negativ = Export. Überschreibt die getrennten Import/Export Sensoren.", - "editor.color_picker": "Farbe anpassen", + "editor.grid_combined_hint": "Ein Sensor für Import UND Export: positiv = Import, negativ = Export. Überschreibt den kombinierten Import/Export Sensor.", + "editor.color_picker": "Kreis Farbe", + "editor.pipe_color": "Rohr Farbe", + "editor.export_color": "Export Farbe", + "editor.consumer_unit_kw": "Sensor meldet in kW", + "editor.show_consumer_always": "Verbraucher bei null Watt anzeigen", }, card: { "card.label_solar": "Solar", @@ -77,7 +81,7 @@ const lang_en = { "editor.battery_soc_label": "State of Charge (%)", "editor.house_total_title": "🏠 Total Consumption (optional)", "editor.house_sensor_label": "Sensor for House Consumption (optional)", - "editor.house_sensor_hint": "Required to make the house icon clickable (compact view).", + "editor.house_sensor_hint": "Required to make the house icon clickable (more-details). Otherwise, the house consumption is calculated.", "editor.consumer_1_title": "🚗 Left (Purple)", "editor.consumer_2_title": "♨️ Center (Orange)", "editor.consumer_3_title": "🏊 Right (Cyan)", @@ -94,8 +98,12 @@ const lang_en = { "editor.grid_to_battery_sensor": "Grid to Battery Sensor (Watt)", "editor.grid_to_battery_hint": "Optional: separate sensor for grid-to-battery flow. If empty, the value is calculated automatically.", "editor.grid_combined_sensor": "Combined Grid Sensor (W, Optional)", - "editor.grid_combined_hint": "Single sensor for import AND export: positive = import, negative = export. Overrides separate import/export sensors.", - "editor.color_picker": "Custom Color", + "editor.grid_combined_hint": "Single sensor for import AND export: positive = import, negative = export. Overrides combined import/export sensor.", + "editor.color_picker": "Bubble Color", + "editor.pipe_color": "Pipe Color", + "editor.export_color": "Export Color", + "editor.consumer_unit_kw": "Sensor reports in kW", + "editor.show_consumer_always": "Show Consumers at zero watts", }, card: { "card.label_solar": "Solar", @@ -278,6 +286,35 @@ class PowerFluxCardEditor extends LitElement { `; } + _renderColorPickerDual(bubbleKey, pipeKey, defaultColor) { + const bubbleColor = this._config[bubbleKey] || defaultColor; + const pipeColor = this._config[pipeKey] || defaultColor; + const hasBubbleCustom = !!this._config[bubbleKey]; + const hasPipeCustom = !!this._config[pipeKey]; + return html` +
+
+ this._colorChanged(bubbleKey, e)}> + ${this._localize('editor.color_picker')} + ${hasBubbleCustom ? html` this._resetColor(bubbleKey)}>` : ''} +
+
+ this._colorChanged(pipeKey, e)}> + ${this._localize('editor.pipe_color')} + ${hasPipeCustom ? html` this._resetColor(pipeKey)}>` : ''} +
+
+ `; + } + static get styles() { return css` .card-config { @@ -410,6 +447,13 @@ class PowerFluxCardEditor extends LitElement { .color-reset-btn:hover { color: var(--primary-color); } + .color-picker-dual { + display: flex; + gap: 8px; + } + .color-picker-dual .color-picker-row { + flex: 1; + } `; } @@ -448,7 +492,7 @@ class PowerFluxCardEditor extends LitElement { ${this._renderEntitySelector(entitySelectorSchema, entities.secondary_solar || "", 'secondary_solar', this._localize('editor.secondary_sensor'))} - ${this._renderColorPicker('color_solar', this._localize('editor.color_picker'), '#ffdd00')} + ${this._renderColorPickerDual('color_solar', 'color_pipe_solar', '#ffdd00')}
@@ -482,11 +526,13 @@ class PowerFluxCardEditor extends LitElement { ${this._renderEntitySelector(entitySelectorSchema, entities.grid_combined || "", 'grid_combined', this._localize('editor.grid_combined_sensor'))} + +
+
${this._localize('editor.grid_combined_hint')}
-
${this._renderEntitySelector(entitySelectorSchema, entities.grid, 'grid', this._localize('card.label_import') + " (W)")} @@ -514,7 +560,9 @@ class PowerFluxCardEditor extends LitElement { ${this._renderEntitySelector(entitySelectorSchema, entities.secondary_grid || "", 'secondary_grid', this._localize('editor.secondary_sensor'))} - ${this._renderColorPicker('color_grid', this._localize('editor.color_picker'), '#3b82f6')} + ${this._renderColorPickerDual('color_grid', 'color_pipe_grid', '#3b82f6')} + + ${this._renderColorPicker('color_export', this._localize('editor.export_color'), '#ff3333')}
@@ -578,7 +626,7 @@ class PowerFluxCardEditor extends LitElement { ${this._renderEntitySelector(entitySelectorSchema, entities.secondary_battery || "", 'secondary_battery', this._localize('editor.secondary_sensor'))} - ${this._renderColorPicker('color_battery', this._localize('editor.color_picker'), '#00ff88')} + ${this._renderColorPickerDual('color_battery', 'color_pipe_battery', '#00ff88')}
@@ -659,9 +707,18 @@ class PowerFluxCardEditor extends LitElement { > +
+ ${this._localize('editor.consumer_unit_kw')} + +
+ ${this._renderEntitySelector(entitySelectorSchema, entities.secondary_consumer_1 || "", 'secondary_consumer_1', this._localize('editor.secondary_sensor'))} - ${this._renderColorPicker('color_consumer_1', this._localize('editor.color_picker'), '#a855f7')} + ${this._renderColorPickerDual('color_consumer_1', 'color_pipe_consumer_1', '#a855f7')}
@@ -686,9 +743,18 @@ class PowerFluxCardEditor extends LitElement { @value-changed=${this._valueChanged} > +
+ ${this._localize('editor.consumer_unit_kw')} + +
+ ${this._renderEntitySelector(entitySelectorSchema, entities.secondary_consumer_2 || "", 'secondary_consumer_2', this._localize('editor.secondary_sensor'))} - ${this._renderColorPicker('color_consumer_2', this._localize('editor.color_picker'), '#f97316')} + ${this._renderColorPickerDual('color_consumer_2', 'color_pipe_consumer_2', '#f97316')}
@@ -713,9 +779,18 @@ class PowerFluxCardEditor extends LitElement { @value-changed=${this._valueChanged} > +
+ ${this._localize('editor.consumer_unit_kw')} + +
+ ${this._renderEntitySelector(entitySelectorSchema, entities.secondary_consumer_3 || "", 'secondary_consumer_3', this._localize('editor.secondary_sensor'))} - ${this._renderColorPicker('color_consumer_3', this._localize('editor.color_picker'), '#06b6d4')} + ${this._renderColorPickerDual('color_consumer_3', 'color_pipe_consumer_3', '#06b6d4')}
`; } @@ -849,6 +924,15 @@ class PowerFluxCardEditor extends LitElement {
${this._localize('editor.hide_inactive')}
+
+ +
${this._localize('editor.show_consumer_always')}
+
+
{ + return getVal(entity) * (isKw ? 1000 : 1); + }; const solar = entities.solar ? Math.max(0, getVal(entities.solar)) : 0; const hasGridCombined = !!(entities.grid_combined && entities.grid_combined !== ""); @@ -1356,7 +1478,7 @@ console.log( if (this.config.invert_battery) { battery *= -1; } - let c1Val = entities.consumer_1 ? getVal(entities.consumer_1) : 0; // EV Value + let c1Val = entities.consumer_1 ? getValKw(entities.consumer_1, this.config.consumer_1_unit_kw === true) : 0; // EV Value if (this.config.invert_consumer_1) { c1Val *= -1; } c1Val = Math.abs(c1Val); @@ -1646,16 +1768,20 @@ console.log( const state = this.hass.states[entity]; return state ? parseFloat(state.state) || 0 : 0; }; + const getValKw = (entity, isKw) => { + return getVal(entity) * (isKw ? 1000 : 1); + }; - let c1Val = entities.consumer_1 ? getVal(entities.consumer_1) : 0; + let c1Val = entities.consumer_1 ? getValKw(entities.consumer_1, this.config.consumer_1_unit_kw === true) : 0; if (this.config.invert_consumer_1) { c1Val *= -1; } c1Val = Math.abs(c1Val); - const c2Val = entities.consumer_2 ? getVal(entities.consumer_2) : 0; - const c3Val = entities.consumer_3 ? getVal(entities.consumer_3) : 0; + const c2Val = entities.consumer_2 ? getValKw(entities.consumer_2, this.config.consumer_2_unit_kw === true) : 0; + const c3Val = entities.consumer_3 ? getValKw(entities.consumer_3, this.config.consumer_3_unit_kw === true) : 0; - const showC1 = (entities.consumer_1 && Math.round(c1Val) > 0); - const showC2 = (entities.consumer_2 && Math.round(c2Val) > 0); - const showC3 = (entities.consumer_3 && Math.round(c3Val) > 0); + const alwaysShowConsumer = this.config.show_consumer_always === true; + const showC1 = (entities.consumer_1 && (alwaysShowConsumer || Math.round(c1Val) > 0)); + const showC2 = (entities.consumer_2 && (alwaysShowConsumer || Math.round(c2Val) > 0)); + const showC3 = (entities.consumer_3 && (alwaysShowConsumer || Math.round(c3Val) > 0)); const anyBottomVisible = showC1 || showC2 || showC3; const solar = hasSolar ? getVal(entities.solar) : 0; @@ -1796,8 +1922,28 @@ console.log( const isGridActive = Math.round(gridImport) > 0 || Math.round(gridExport) > 0; const isGridExporting = Math.round(gridExport) > 0 && Math.round(gridImport) === 0; + // --- Grid Donut Gradient --- + let gridGradientVal = ''; + if (showDonut && hasGrid && isGridActive) { + const gridTotal = gridToHouse + gridToBatt + gridExport; + if (gridTotal > 0) { + const gPctToHouse = (gridToHouse / gridTotal) * 100; + const gPctToBatt = (gridToBatt / gridTotal) * 100; + const gPctExport = (gridExport / gridTotal) * 100; + let gStops = []; + let gCurrent = 0; + if (gPctToHouse > 0) { gStops.push(`var(--neon-blue) ${gCurrent}% ${gCurrent + gPctToHouse}%`); gCurrent += gPctToHouse; } + if (gPctToBatt > 0) { gStops.push(`var(--neon-green) ${gCurrent}% ${gCurrent + gPctToBatt}%`); gCurrent += gPctToBatt; } + if (gPctExport > 0) { gStops.push(`var(--export-color) ${gCurrent}% ${gCurrent + gPctExport}%`); gCurrent += gPctExport; } + if (gCurrent < 99.9) { gStops.push(`var(--neon-blue) ${gCurrent}% 100%`); } + gridGradientVal = `conic-gradient(from 330deg, ${gStops.join(', ')})`; + } else { + gridGradientVal = isGridExporting ? 'var(--export-color)' : 'var(--neon-blue)'; + } + } + const solarColor = isSolarActive ? 'var(--neon-yellow)' : 'var(--secondary-text-color)'; - const gridColor = isGridExporting ? 'var(--neon-red)' : (isGridActive ? 'var(--neon-blue)' : 'var(--secondary-text-color)'); + const gridColor = isGridExporting ? 'var(--export-color)' : (isGridActive ? 'var(--neon-blue)' : 'var(--secondary-text-color)'); const getAnimStyle = (val) => { if (val <= 1) return "opacity: 0;"; @@ -1929,6 +2075,11 @@ console.log( const pathSolarBatt = "M 50 70 Q 210 -20 370 70"; const pathGridImport = "M 210 160 L 210 220"; const pathGridExport = "M 95 115 Q 130 145 165 115"; + const pathHouseExport = "M 210 220 L 210 160"; + const exportFromSolar = solarVal > 1; + const activeExportPath = exportFromSolar ? pathGridExport : pathHouseExport; + const exportTextX = exportFromSolar ? '130' : '185'; + const exportTextY = exportFromSolar ? '145' : '195'; const pathGridToBatt = "M 255 115 Q 290 145 325 115"; const pathBattHouse = "M 370 160 Q 370 265 255 265"; const pathHouseC1 = "M 165 265 Q 50 265 50 370"; @@ -1951,33 +2102,33 @@ console.log( - + - - - + + + - + - - - + + + ${this._formatPower(solarToHouse)} ${this._formatPower(solarToBatt)} ${this._formatPower(gridToHouse)} - ${this._formatPower(gridExport)} + ${this._formatPower(gridExport)} ${this._formatPower(gridToBatt)} ${this._formatPower(batteryDischarge)} @@ -1993,7 +2144,8 @@ console.log(
` : ''} ${hasGrid ? html` -
this._handleClick(entities.grid_combined || entities.grid)}> ${renderMainIcon('grid', isGridExporting ? gridExport : gridImport, iconGrid, gridColor)} ${renderSecondaryOrLabel(labelGridText, showLabelGrid, entities.secondary_grid, hasSecondaryGrid)}