diff --git a/src/lang-de.js b/src/lang-de.js
index f7220c7..4401713 100644
--- a/src/lang-de.js
+++ b/src/lang-de.js
@@ -20,7 +20,7 @@ export default {
"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)",
@@ -37,8 +37,12 @@ export default {
"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",
diff --git a/src/lang-en.js b/src/lang-en.js
index f955cb9..fa9b379 100644
--- a/src/lang-en.js
+++ b/src/lang-en.js
@@ -20,7 +20,7 @@ export default {
"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)",
@@ -37,8 +37,12 @@ export default {
"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",
diff --git a/src/power-flux-card-editor.js b/src/power-flux-card-editor.js
index bf1177a..15c68c7 100644
--- a/src/power-flux-card-editor.js
+++ b/src/power-flux-card-editor.js
@@ -162,6 +162,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`
+
+ `;
+ }
+
static get styles() {
return css`
.card-config {
@@ -294,6 +323,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;
+ }
`;
}
@@ -332,7 +368,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')}
@@ -366,11 +402,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)")}
@@ -398,7 +436,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')}
@@ -462,7 +502,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')}
@@ -543,9 +583,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')}
@@ -570,9 +619,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')}
@@ -597,9 +655,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')}
`;
}
@@ -733,6 +800,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 !== "");
@@ -488,7 +526,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);
@@ -778,16 +816,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;
@@ -928,8 +970,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;";
@@ -1061,6 +1123,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";
@@ -1083,33 +1150,33 @@ console.log(
-
+
-
-
-
+
+
+
-
+
-
-
-
+
+
+
${this._formatPower(solarToHouse)}
${this._formatPower(solarToBatt)}
${this._formatPower(gridToHouse)}
- ${this._formatPower(gridExport)}
+ ${this._formatPower(gridExport)}
${this._formatPower(gridToBatt)}
${this._formatPower(batteryDischarge)}
@@ -1125,7 +1192,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)}