diff --git a/README.md b/README.md
index 66a0584..22fb0cb 100644
--- a/README.md
+++ b/README.md
@@ -84,3 +84,151 @@ You can configure the card directly via the visual editor in Home Assistant.
- **Donut Chart**: Show the energy mix as a ring around the house.
- **Comet Tail / Dashed Line**: Change the flow animation style.
- **Compact View**: Switch to the bar chart layout.
+- **Color Options**: Define custom colors for each source and consumer.
+- **Grid Import/Export**: Configure separate or combined entities.
+- **Grid-to-Battery**: Optional direct sensor for Grid-to-Battery flow.
+- **Separate Battery Sensors**: Optional separate sensors for battery charge and discharge.
+- **Secondary Sensors**: Display alternative values in the main circles (e.g., daily yield, current charge power).
+
+
+
+ Custom Colors with card_mod and Jinja2 Templates
+
+With the [card_mod](https://github.com/thomasloven/lovelace-card-mod) integration, you can dynamically override the CSS variables of the Power Flux Card using Jinja2 templates. This allows you to change colors based on sensor values — e.g., green solar icon during production, grey when idle.
+
+### Available CSS Variables
+
+| Variable | Description |
+|---|---|
+| `--neon-yellow` | Bubble color Solar |
+| `--neon-blue` | Bubble color Grid |
+| `--neon-green` | Bubble color Battery |
+| `--neon-pink` | Bubble color House |
+| `--pipe-solar-color` | Pipe color Solar |
+| `--pipe-grid-color` | Pipe color Grid |
+| `--pipe-battery-color` | Pipe color Battery |
+| `--icon-solar-color` | Icon color Solar |
+| `--icon-grid-color` | Icon color Grid |
+| `--icon-battery-color` | Icon color Battery |
+| `--icon-house-color` | Icon color House |
+| `--icon-consumer-1-color` | Icon color Consumer 1 |
+| `--text-solar-color` | Text color Solar |
+| `--text-grid-color` | Text color Grid |
+| `--text-battery-color` | Text color Battery |
+| `--text-house-color` | Text color House |
+| `--text-consumer-1-color` | Text color Consumer 1 |
+| `--consumer-1-color` | Bubble color Consumer 1 |
+| `--consumer-2-color` | Bubble color Consumer 2 |
+| `--consumer-3-color` | Bubble color Consumer 3 |
+| `--export-color` | Color for Export |
+
+### Example 1: Solar Icon — green during production, grey when idle
+
+```yaml
+type: custom:power-flux-card
+entities:
+ solar: sensor.solar_power
+ grid: sensor.grid_power
+ battery: sensor.battery_power
+ battery_soc: sensor.battery_soc
+card_mod:
+ style: |
+ power-flux-card {
+ {% if states('sensor.solar_power') | float > 0 %}
+ --icon-solar-color: #00ff88;
+ {% else %}
+ --icon-solar-color: #9e9e9e;
+ {% endif %}
+ }
+```
+
+### Example 2: Grid text color — red on export, blue on import
+
+```yaml
+type: custom:power-flux-card
+entities:
+ solar: sensor.solar_power
+ grid_combined: sensor.grid_power_combined
+ battery: sensor.battery_power
+ battery_soc: sensor.battery_soc
+card_mod:
+ style: |
+ power-flux-card {
+ {% if states('sensor.grid_power_combined') | float < 0 %}
+ --text-grid-color: #ff3333;
+ {% else %}
+ --text-grid-color: #3b82f6;
+ {% endif %}
+ }
+```
+
+### Example 3: Battery bubble — color based on State of Charge (SoC)
+
+```yaml
+type: custom:power-flux-card
+entities:
+ solar: sensor.solar_power
+ grid: sensor.grid_power
+ battery: sensor.battery_power
+ battery_soc: sensor.battery_soc
+card_mod:
+ style: |
+ power-flux-card {
+ {% set soc = states('sensor.battery_soc') | float %}
+ {% if soc > 80 %}
+ --neon-green: #00ff88;
+ {% elif soc > 30 %}
+ --neon-green: #f59e0b;
+ {% else %}
+ --neon-green: #ff3333;
+ {% endif %}
+ }
+```
+
+### Example 4: Consumer 1 pipe — visible only at high power, otherwise transparent
+
+```yaml
+type: custom:power-flux-card
+entities:
+ solar: sensor.solar_power
+ grid: sensor.grid_power
+ battery: sensor.battery_power
+ battery_soc: sensor.battery_soc
+ consumer_1: sensor.wallbox_power
+card_mod:
+ style: |
+ power-flux-card {
+ {% if states('sensor.wallbox_power') | float > 500 %}
+ --pipe-consumer-1-color: #a855f7;
+ --icon-consumer-1-color: #a855f7;
+ {% else %}
+ --pipe-consumer-1-color: rgba(168, 85, 247, 0.2);
+ --icon-consumer-1-color: #9e9e9e;
+ {% endif %}
+ }
+```
+
+### Example 5: Multiple colors at once — night mode (everything dimmed when Solar = 0)
+
+```yaml
+type: custom:power-flux-card
+entities:
+ solar: sensor.solar_power
+ grid: sensor.grid_power
+ battery: sensor.battery_power
+ battery_soc: sensor.battery_soc
+ consumer_1: sensor.wallbox_power
+card_mod:
+ style: |
+ power-flux-card {
+ {% if states('sensor.solar_power') | float == 0 %}
+ --icon-solar-color: #555555;
+ --text-solar-color: #777777;
+ --neon-yellow: #666633;
+ --pipe-solar-color: #444422;
+ {% endif %}
+ }
+```
+
+> **Note:** card_mod must be installed separately via HACS. Templates are evaluated on every state update, so colors change in real time.
+
diff --git a/docs/README-de.md b/docs/README-de.md
index 3e90900..6523845 100644
--- a/docs/README-de.md
+++ b/docs/README-de.md
@@ -86,3 +86,151 @@ Du kannst die Karte direkt über den visuellen Editor in Home Assistant konfigur
- **Donut Chart**: Zeigt den Energiemix als Ring um das Haus an.
- **Kometenschweif / Gestrichelte Linie**: Ändern Sie den Stil der Flussanimation.
- **Kompakte Ansicht**: Wechseln Sie zum Balkendiagramm-Layout.
+- **Farboptionen**: Definieren Sie benutzerdefinierte Farben für jede Quelle und Verbraucher.
+- **Netz-Import/Export**: Konfigurieren Sie separate oder kombinierte Entitäten.
+- **Netz-zu-Batterie**: Optionaler direkter Sensor für den Netz-zu-Batterie-Fluss.
+- **Batterie getrennte Sensoren**: Optional separate Sensoren für Batterie-Ladung und -Entladung.
+- **Sekundäre Sensoren**: Zeigen Sie alternative Werte in den Hauptkreisen an (z.B. Tagesertrag, aktuelle Ladeleistung).
+
+
+
+ Custom Farben mit card_mod und Jinja2 Templates
+
+Mit der [card_mod](https://github.com/thomasloven/lovelace-card-mod) Integration können die CSS-Variablen der Power Flux Card dynamisch per Jinja2-Templates überschrieben werden. So lassen sich Farben abhängig von Sensorwerten ändern — z.B. Solar-Icon grün bei Produktion, grau bei Stillstand.
+
+### Verfügbare CSS-Variablen
+
+| Variable | Beschreibung |
+|---|---|
+| `--neon-yellow` | Bubble-Farbe Solar |
+| `--neon-blue` | Bubble-Farbe Grid |
+| `--neon-green` | Bubble-Farbe Batterie |
+| `--neon-pink` | Bubble-Farbe Haus |
+| `--pipe-solar-color` | Pipe-Farbe Solar |
+| `--pipe-grid-color` | Pipe-Farbe Grid |
+| `--pipe-battery-color` | Pipe-Farbe Batterie |
+| `--icon-solar-color` | Icon-Farbe Solar |
+| `--icon-grid-color` | Icon-Farbe Grid |
+| `--icon-battery-color` | Icon-Farbe Batterie |
+| `--icon-house-color` | Icon-Farbe Haus |
+| `--icon-consumer-1-color` | Icon-Farbe Consumer 1 |
+| `--text-solar-color` | Text-Farbe Solar |
+| `--text-grid-color` | Text-Farbe Grid |
+| `--text-battery-color` | Text-Farbe Batterie |
+| `--text-house-color` | Text-Farbe Haus |
+| `--text-consumer-1-color` | Text-Farbe Consumer 1 |
+| `--consumer-1-color` | Bubble-Farbe Consumer 1 |
+| `--consumer-2-color` | Bubble-Farbe Consumer 2 |
+| `--consumer-3-color` | Bubble-Farbe Consumer 3 |
+| `--export-color` | Farbe für Export |
+
+### Beispiel 1: Solar-Icon — grün bei Produktion, grau bei Stillstand
+
+```yaml
+type: custom:power-flux-card
+entities:
+ solar: sensor.solar_power
+ grid: sensor.grid_power
+ battery: sensor.battery_power
+ battery_soc: sensor.battery_soc
+card_mod:
+ style: |
+ power-flux-card {
+ {% if states('sensor.solar_power') | float > 0 %}
+ --icon-solar-color: #00ff88;
+ {% else %}
+ --icon-solar-color: #9e9e9e;
+ {% endif %}
+ }
+```
+
+### Beispiel 2: Grid-Textfarbe — rot bei Export, blau bei Import
+
+```yaml
+type: custom:power-flux-card
+entities:
+ solar: sensor.solar_power
+ grid_combined: sensor.grid_power_combined
+ battery: sensor.battery_power
+ battery_soc: sensor.battery_soc
+card_mod:
+ style: |
+ power-flux-card {
+ {% if states('sensor.grid_power_combined') | float < 0 %}
+ --text-grid-color: #ff3333;
+ {% else %}
+ --text-grid-color: #3b82f6;
+ {% endif %}
+ }
+```
+
+### Beispiel 3: Batterie-Bubble — Farbe nach Ladestand (SoC)
+
+```yaml
+type: custom:power-flux-card
+entities:
+ solar: sensor.solar_power
+ grid: sensor.grid_power
+ battery: sensor.battery_power
+ battery_soc: sensor.battery_soc
+card_mod:
+ style: |
+ power-flux-card {
+ {% set soc = states('sensor.battery_soc') | float %}
+ {% if soc > 80 %}
+ --neon-green: #00ff88;
+ {% elif soc > 30 %}
+ --neon-green: #f59e0b;
+ {% else %}
+ --neon-green: #ff3333;
+ {% endif %}
+ }
+```
+
+### Beispiel 4: Consumer-1-Pipe — sichtbar nur bei hoher Leistung, sonst transparent
+
+```yaml
+type: custom:power-flux-card
+entities:
+ solar: sensor.solar_power
+ grid: sensor.grid_power
+ battery: sensor.battery_power
+ battery_soc: sensor.battery_soc
+ consumer_1: sensor.wallbox_power
+card_mod:
+ style: |
+ power-flux-card {
+ {% if states('sensor.wallbox_power') | float > 500 %}
+ --pipe-consumer-1-color: #a855f7;
+ --icon-consumer-1-color: #a855f7;
+ {% else %}
+ --pipe-consumer-1-color: rgba(168, 85, 247, 0.2);
+ --icon-consumer-1-color: #9e9e9e;
+ {% endif %}
+ }
+```
+
+### Beispiel 5: Mehrere Farben gleichzeitig — Nachtmodus (alles gedimmt wenn Solar = 0)
+
+```yaml
+type: custom:power-flux-card
+entities:
+ solar: sensor.solar_power
+ grid: sensor.grid_power
+ battery: sensor.battery_power
+ battery_soc: sensor.battery_soc
+ consumer_1: sensor.wallbox_power
+card_mod:
+ style: |
+ power-flux-card {
+ {% if states('sensor.solar_power') | float == 0 %}
+ --icon-solar-color: #555555;
+ --text-solar-color: #777777;
+ --neon-yellow: #666633;
+ --pipe-solar-color: #444422;
+ {% endif %}
+ }
+```
+
+> **Hinweis:** card_mod muss separat über HACS installiert werden. Die Templates werden bei jedem State-Update ausgewertet, die Farben ändern sich also in Echtzeit.
+
\ No newline at end of file
diff --git a/src/lang-de.js b/src/lang-de.js
index 4401713..8e7845b 100644
--- a/src/lang-de.js
+++ b/src/lang-de.js
@@ -13,7 +13,7 @@ export default {
"editor.label_toggle": "Label im Kreis anzeigen",
"editor.compact_view": "Kompakte Ansicht (evcc)",
"editor.hide_inactive": "Inaktive Röhren ausblenden",
- "editor.entity": "Entität (Watt)",
+ "editor.entity": "Kombinierter Batterie Sensor (W)",
"editor.label": "Beschriftung",
"editor.icon": "Icon",
"editor.back": "Zurück",
@@ -34,15 +34,22 @@ export default {
"editor.hide_consumer_icons": "Icons unten ausblenden",
"editor.invert_consumer_1": "Sensorwert invertieren (+/-)",
"editor.secondary_sensor": "Zweiter Sensor (nur Anzeige)",
- "editor.grid_to_battery_sensor": "Netz-zu-Batterie Sensor (Watt)",
+ "editor.grid_to_battery_sensor": "Netz-zu-Batterie Sensor (W, Optional)",
"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 den kombinierten Import/Export Sensor.",
- "editor.color_picker": "Kreis Farbe",
- "editor.pipe_color": "Rohr Farbe",
- "editor.export_color": "Export Farbe",
+ "editor.color_picker": "Bubble",
+ "editor.pipe_color": "Pipe",
+ "editor.export_color": "Export",
"editor.consumer_unit_kw": "Sensor meldet in kW",
"editor.show_consumer_always": "Verbraucher bei null Watt anzeigen",
+ "editor.battery_charge_sensor": "Batterie-Ladung Sensor (W, Optional)",
+ "editor.battery_discharge_sensor": "Batterie-Entladung Sensor (W, Optional)",
+ "editor.battery_separate_hint": "Optional: Separate Sensoren für Laden/Entladen. Überschreiben den Hauptsensor für die Berechnung.",
+ "editor.consumer_1_hide_pipe": "Pipe bei geringer Leistung ausblenden",
+ "editor.consumer_pipe_threshold": "Pipe-Schwellenwert (Watt)",
+ "editor.text_color": "Text",
+ "editor.icon_color": "Icon",
},
card: {
"card.label_solar": "Solar",
@@ -50,7 +57,7 @@ export default {
"card.label_battery": "Batterie",
"card.label_house": "Verbrauch",
"card.label_car": "E-Auto",
- "card.label_import": "Import",
- "card.label_export": "Export",
+ "card.label_heater": "Heizung",
+ "card.label_pool": "Pool",
}
};
diff --git a/src/lang-en.js b/src/lang-en.js
index fa9b379..46ae1ca 100644
--- a/src/lang-en.js
+++ b/src/lang-en.js
@@ -13,7 +13,7 @@ export default {
"editor.label_toggle": "Show Label in Bubble",
"editor.compact_view": "Compact View (evcc)",
"editor.hide_inactive": "Hide Inactive Pipes",
- "editor.entity": "Entity (Watt)",
+ "editor.entity": "Combined Battery Sensor (W)",
"editor.label": "Label",
"editor.icon": "Icon",
"editor.back": "Back",
@@ -34,7 +34,7 @@ export default {
"editor.hide_consumer_icons": "Hide Consumer Icons",
"editor.invert_consumer_1": "Invert Sensor Value (+/-)",
"editor.secondary_sensor": "Secondary Sensor (display only)",
- "editor.grid_to_battery_sensor": "Grid to Battery Sensor (Watt)",
+ "editor.grid_to_battery_sensor": "Grid to Battery Sensor (W, optional)",
"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 combined import/export sensor.",
@@ -43,6 +43,13 @@ export default {
"editor.export_color": "Export Color",
"editor.consumer_unit_kw": "Sensor reports in kW",
"editor.show_consumer_always": "Show Consumers at zero watts",
+ "editor.battery_charge_sensor": "Battery Charge Sensor (W, Optional)",
+ "editor.battery_discharge_sensor": "Battery Discharge Sensor (W, Optional)",
+ "editor.battery_separate_hint": "Optional: Separate sensors for charge/discharge. Override the main sensor for calculations.",
+ "editor.consumer_1_hide_pipe": "Hide pipe at low power",
+ "editor.consumer_pipe_threshold": "Pipe Threshold (Watts)",
+ "editor.text_color": "Text Color",
+ "editor.icon_color": "Icon Color",
},
card: {
"card.label_solar": "Solar",
@@ -50,7 +57,7 @@ export default {
"card.label_battery": "Battery",
"card.label_house": "Consumption",
"card.label_car": "Car",
- "card.label_import": "Import",
- "card.label_export": "Export",
+ "card.label_heater": "Heater",
+ "card.label_pool": "Pool",
}
};
diff --git a/src/power-flux-card-editor.js b/src/power-flux-card-editor.js
index 15c68c7..7702902 100644
--- a/src/power-flux-card-editor.js
+++ b/src/power-flux-card-editor.js
@@ -48,7 +48,7 @@ class PowerFluxCardEditor extends LitElement {
if (!this._config || !this.hass) return;
const target = ev.target;
- const key = target.configValue || this._currentConfigValue;
+ const key = target.configValue;
let value;
if (target.tagName === 'HA-SWITCH') {
@@ -67,6 +67,7 @@ class PowerFluxCardEditor extends LitElement {
const entityKeys = [
'solar', 'grid', 'grid_export', 'grid_combined',
'battery', 'battery_soc', 'grid_to_battery',
+ 'battery_charge', 'battery_discharge',
'house',
'consumer_1', 'consumer_2', 'consumer_3',
'secondary_solar', 'secondary_grid', 'secondary_battery',
@@ -162,31 +163,30 @@ 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];
+ _renderColorPickerQuad(bubbleKey, pipeKey, textKey, iconKey, defaultColor) {
+ const items = [
+ { key: bubbleKey, label: this._localize('editor.color_picker'), default: defaultColor },
+ ];
+ if (pipeKey) items.push({ key: pipeKey, label: this._localize('editor.pipe_color'), default: defaultColor });
+ items.push({ key: textKey, label: this._localize('editor.text_color'), default: defaultColor });
+ items.push({ key: iconKey, label: this._localize('editor.icon_color'), default: defaultColor });
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)}>` : ''}
-
+
+ ${items.map(item => {
+ const color = this._config[item.key] || item.default;
+ const hasCustom = !!this._config[item.key];
+ return html`
+
+ this._colorChanged(item.key, e)}>
+ ${item.label}
+ ${hasCustom ? html` this._resetColor(item.key)}>` : ''}
+
+ `;
+ })}
`;
}
@@ -298,8 +298,8 @@ class PowerFluxCardEditor extends LitElement {
-webkit-appearance: none;
border: 2px solid var(--divider-color);
border-radius: 50%;
- width: 36px;
- height: 36px;
+ width: 30px;
+ height: 30px;
padding: 2px;
cursor: pointer;
background: transparent;
@@ -323,11 +323,11 @@ class PowerFluxCardEditor extends LitElement {
.color-reset-btn:hover {
color: var(--primary-color);
}
- .color-picker-dual {
+ .color-picker-quad {
display: flex;
gap: 8px;
}
- .color-picker-dual .color-picker-row {
+ .color-picker-quad .color-picker-row {
flex: 1;
}
`;
@@ -368,7 +368,7 @@ class PowerFluxCardEditor extends LitElement {
${this._renderEntitySelector(entitySelectorSchema, entities.secondary_solar || "", 'secondary_solar', this._localize('editor.secondary_sensor'))}
- ${this._renderColorPickerDual('color_solar', 'color_pipe_solar', '#ffdd00')}
+ ${this._renderColorPickerQuad('color_solar', 'color_pipe_solar', 'color_text_solar', 'color_icon_solar', '#ffdd00')}
@@ -436,7 +436,7 @@ class PowerFluxCardEditor extends LitElement {
${this._renderEntitySelector(entitySelectorSchema, entities.secondary_grid || "", 'secondary_grid', this._localize('editor.secondary_sensor'))}
- ${this._renderColorPickerDual('color_grid', 'color_pipe_grid', '#3b82f6')}
+ ${this._renderColorPickerQuad('color_grid', 'color_pipe_grid', 'color_text_grid', 'color_icon_grid', '#3b82f6')}
${this._renderColorPicker('color_export', this._localize('editor.export_color'), '#ff3333')}
@@ -473,13 +473,14 @@ class PowerFluxCardEditor extends LitElement {
${this._renderEntitySelector(entitySelectorSchema, entities.battery, 'battery', this._localize('editor.entity'))}
- ${this._renderEntitySelector(entitySelectorSchema, entities.battery_soc, 'battery_soc', this._localize('editor.battery_soc_label'))}
+
- ${this._renderEntitySelector(entitySelectorSchema, entities.grid_to_battery || "", 'grid_to_battery', this._localize('editor.grid_to_battery_sensor'))}
- ${this._localize('editor.grid_to_battery_hint')}
+ ${this._localize('editor.battery_separate_hint')}
-
+ ${this._renderEntitySelector(entitySelectorSchema, entities.battery_charge || "", 'battery_charge', this._localize('editor.battery_charge_sensor'))}
+ ${this._renderEntitySelector(entitySelectorSchema, entities.battery_discharge || "", 'battery_discharge', this._localize('editor.battery_discharge_sensor'))}
+
+
+
+ ${this._renderEntitySelector(entitySelectorSchema, entities.battery_soc, 'battery_soc', this._localize('editor.battery_soc_label'))}
+
+
+
+
+ ${this._localize('editor.grid_to_battery_hint')}
+
+ ${this._renderEntitySelector(entitySelectorSchema, entities.grid_to_battery || "", 'grid_to_battery', this._localize('editor.grid_to_battery_sensor'))}
+
${this._renderEntitySelector(entitySelectorSchema, entities.secondary_battery || "", 'secondary_battery', this._localize('editor.secondary_sensor'))}
- ${this._renderColorPickerDual('color_battery', 'color_pipe_battery', '#00ff88')}
+ ${this._renderColorPickerQuad('color_battery', 'color_pipe_battery', 'color_text_battery', 'color_icon_battery', '#00ff88')}
@@ -550,6 +562,8 @@ class PowerFluxCardEditor extends LitElement {
${this._localize('editor.house_sensor_hint')}
+
+ ${this._renderColorPickerQuad('color_house', null, 'color_text_house', 'color_icon_house', '#ff0080')}
@@ -583,6 +597,26 @@ class PowerFluxCardEditor extends LitElement {
>
+
+ ${this._localize('editor.consumer_1_hide_pipe')}
+
+
+
+ ${this._config.consumer_1_hide_pipe === true ? html`
+
+ ` : ''}
+
${this._localize('editor.consumer_unit_kw')}
@@ -630,7 +664,7 @@ class PowerFluxCardEditor extends LitElement {
${this._renderEntitySelector(entitySelectorSchema, entities.secondary_consumer_2 || "", 'secondary_consumer_2', this._localize('editor.secondary_sensor'))}
- ${this._renderColorPickerDual('color_consumer_2', 'color_pipe_consumer_2', '#f97316')}
+ ${this._renderColorPickerQuad('color_consumer_2', 'color_pipe_consumer_2', 'color_text_consumer_2', 'color_icon_consumer_2', '#f97316')}
@@ -666,7 +700,7 @@ class PowerFluxCardEditor extends LitElement {
${this._renderEntitySelector(entitySelectorSchema, entities.secondary_consumer_3 || "", 'secondary_consumer_3', this._localize('editor.secondary_sensor'))}
- ${this._renderColorPickerDual('color_consumer_3', 'color_pipe_consumer_3', '#06b6d4')}
+ ${this._renderColorPickerQuad('color_consumer_3', 'color_pipe_consumer_3', 'color_text_consumer_3', 'color_icon_consumer_3', '#06b6d4')}
`;
}
diff --git a/src/power-flux-card.js b/src/power-flux-card.js
index 14d5dcd..4db2e67 100644
--- a/src/power-flux-card.js
+++ b/src/power-flux-card.js
@@ -3,7 +3,7 @@ import lang_en from "./lang-en.js";
import lang_de from "./lang-de.js";
console.log(
- "%c⚡ Power Flux Card v_2.1 ready",
+ "%c⚡ Power Flux Card v_2.4 ready",
"background: #d19525ff; color: #000; padding: 2px 6px; border-radius: 4px; font-weight: bold;"
);
@@ -44,6 +44,8 @@ console.log(
consumer_2_unit_kw: false,
consumer_3_unit_kw: false,
show_consumer_always: false,
+ consumer_1_hide_pipe: false,
+ consumer_1_pipe_threshold: 0,
show_donut_border: false,
show_neon_glow: true,
show_comet_tail: false,
@@ -66,6 +68,8 @@ console.log(
grid_combined: "",
battery: "",
battery_soc: "",
+ battery_charge: "",
+ battery_discharge: "",
house: "",
consumer_1: "",
consumer_2: "",
@@ -128,6 +132,21 @@ console.log(
'color_pipe_consumer_1': '--pipe-consumer-1-color',
'color_pipe_consumer_2': '--pipe-consumer-2-color',
'color_pipe_consumer_3': '--pipe-consumer-3-color',
+ 'color_house': '--neon-pink',
+ 'color_icon_solar': '--icon-solar-color',
+ 'color_icon_grid': '--icon-grid-color',
+ 'color_icon_battery': '--icon-battery-color',
+ 'color_icon_house': '--icon-house-color',
+ 'color_icon_consumer_1': '--icon-consumer-1-color',
+ 'color_icon_consumer_2': '--icon-consumer-2-color',
+ 'color_icon_consumer_3': '--icon-consumer-3-color',
+ 'color_text_solar': '--text-solar-color',
+ 'color_text_grid': '--text-grid-color',
+ 'color_text_battery': '--text-battery-color',
+ 'color_text_house': '--text-house-color',
+ 'color_text_consumer_1': '--text-consumer-1-color',
+ 'color_text_consumer_2': '--text-consumer-2-color',
+ 'color_text_consumer_3': '--text-consumer-3-color',
};
for (const [configKey, cssVar] of Object.entries(colorMap)) {
if (this.config[configKey]) {
@@ -155,7 +174,6 @@ console.log(
--neon-green: #00ff88;
--neon-pink: #ff0080;
--neon-red: #ff3333;
- --grid-grey: #9e9e9e;
--export-purple: #a855f7;
--export-color: #ff3333;
--consumer-1-color: #a855f7;
@@ -167,6 +185,20 @@ console.log(
--pipe-consumer-1-color: var(--consumer-1-color);
--pipe-consumer-2-color: var(--consumer-2-color);
--pipe-consumer-3-color: var(--consumer-3-color);
+ --icon-solar-color: var(--neon-yellow);
+ --icon-grid-color: var(--neon-blue);
+ --icon-battery-color: var(--neon-green);
+ --icon-house-color: var(--neon-pink);
+ --icon-consumer-1-color: var(--consumer-1-color);
+ --icon-consumer-2-color: var(--consumer-2-color);
+ --icon-consumer-3-color: var(--consumer-3-color);
+ --text-solar-color: var(--neon-yellow);
+ --text-grid-color: var(--neon-blue);
+ --text-battery-color: var(--neon-green);
+ --text-house-color: var(--neon-pink);
+ --text-consumer-1-color: var(--consumer-1-color);
+ --text-consumer-2-color: var(--consumer-2-color);
+ --text-consumer-3-color: var(--consumer-3-color);
--flow-dasharray: 0 380;
}
:host([data-theme-light]) {
@@ -175,7 +207,6 @@ console.log(
--neon-green: #059669;
--neon-pink: #db2777;
--neon-red: #dc2626;
- --grid-grey: #6b7280;
--export-purple: #7c3aed;
--export-color: #dc2626;
--consumer-1-color: #7c3aed;
@@ -256,11 +287,6 @@ console.log(
overflow: hidden;
}
- /* Source Colors */
- .src-solar { background: var(--neon-yellow); color: black; }
- .src-grid { background: var(--neon-blue); color: black; }
- .src-battery { background: var(--neon-green); color: black; }
-
/* --- STANDARD VIEW STYLES --- */
.scale-wrapper {
width: 420px;
@@ -394,7 +420,7 @@ console.log(
@keyframes dash { to { stroke-dashoffset: -1500; } }
.flow-text {
- font-size: 10px; font-weight: bold; text-anchor: middle; fill: #fff; filter: transition: opacity 0.3s ease;
+ font-size: 10px; font-weight: bold; text-anchor: middle; fill: #fff; transition: opacity 0.3s ease;
}
.flow-text.no-shadow { filter: none; }
.text-solar { fill: var(--pipe-solar-color); }
@@ -408,35 +434,36 @@ console.log(
_renderIcon(type, val = 0, colorOverride = null) {
if (type === 'solar') {
const animate = Math.round(val) > 0 ? 'spin-slow' : '';
- const color = colorOverride || 'var(--neon-yellow)';
+ const color = colorOverride || 'var(--icon-solar-color)';
return html``;
}
if (type === 'grid') {
const animate = Math.round(val) > 0 ? 'pulse' : '';
- const color = colorOverride || 'var(--neon-blue)';
+ const color = colorOverride || 'var(--icon-grid-color)';
return html``;
}
if (type === 'battery') {
const soc = Math.min(Math.max(val, 0), 100) / 100;
const rectHeight = 14 * soc;
const rectY = 18 - rectHeight;
- const rectColor = soc > 0.2 ? 'var(--neon-green)' : 'var(--neon-red)';
- return html``;
+ const strokeColor = colorOverride || 'var(--icon-battery-color)';
+ const rectColor = soc > 0.2 ? strokeColor : 'var(--neon-red)';
+ return html``;
}
if (type === 'house') {
- const strokeColor = colorOverride || 'var(--neon-pink)';
+ const strokeColor = colorOverride || 'var(--icon-house-color)';
return html``;
}
if (type === 'car') {
- const c = colorOverride || 'var(--consumer-1-color)';
+ const c = colorOverride || 'var(--icon-consumer-1-color)';
return html``;
}
if (type === 'heater') {
- const c = colorOverride || 'var(--consumer-2-color)';
+ const c = colorOverride || 'var(--icon-consumer-2-color)';
return html``;
}
if (type === 'pool') {
- const c = colorOverride || 'var(--consumer-3-color)';
+ const c = colorOverride || 'var(--icon-consumer-3-color)';
return html``;
}
return html``;
@@ -546,8 +573,12 @@ console.log(
gridExport = gridMain < 0 ? Math.abs(gridMain) : 0;
}
- const batteryCharge = battery > 0 ? battery : 0;
- const batteryDischarge = battery < 0 ? Math.abs(battery) : 0;
+ // Check for separate battery charge/discharge sensors
+ const hasBattChargeSensor = !!(entities.battery_charge && entities.battery_charge !== "");
+ const hasBattDischargeSensor = !!(entities.battery_discharge && entities.battery_discharge !== "");
+
+ const batteryCharge = hasBattChargeSensor ? Math.abs(getVal(entities.battery_charge)) : (battery > 0 ? battery : 0);
+ const batteryDischarge = hasBattDischargeSensor ? Math.abs(getVal(entities.battery_discharge)) : (battery < 0 ? Math.abs(battery) : 0);
let solarToBatt = 0;
let gridToBatt = 0;
@@ -641,9 +672,9 @@ console.log(
const path = this._createBracketPath(s.startPx, s.widthPx, 'down');
let icon = '';
let iconColor = '';
- if (s.type === 'solar') { icon = 'mdi:weather-sunny'; iconColor = 'var(--neon-yellow)'; }
- if (s.type === 'grid') { icon = 'mdi:transmission-tower'; iconColor = 'var(--neon-blue)'; }
- if (s.type === 'battery') { icon = 'mdi:battery-high'; iconColor = 'var(--neon-green)'; }
+ if (s.type === 'solar') { icon = 'mdi:weather-sunny'; iconColor = 'var(--icon-solar-color)'; }
+ if (s.type === 'grid') { icon = 'mdi:transmission-tower'; iconColor = 'var(--icon-grid-color)'; }
+ if (s.type === 'battery') { icon = 'mdi:battery-high'; iconColor = 'var(--icon-battery-color)'; }
return { path, width: s.widthPx, center: s.startPx + (s.widthPx / 2), icon, iconColor, val: s.val, entityId: s.entityId };
});
@@ -661,10 +692,10 @@ console.log(
let icon = '';
let iconColor = '';
- if (type === 'house') { icon = 'mdi:home'; iconColor = 'var(--primary-text-color)'; }
- if (type === 'car') { icon = 'mdi:car-electric'; iconColor = this._getConsumerColor(1); }
- if (type === 'export') { icon = 'mdi:arrow-right-box'; iconColor = 'var(--export-purple)'; }
- if (type === 'battery') { icon = 'mdi:battery-charging-high'; iconColor = 'var(--neon-green)'; }
+ if (type === 'house') { icon = 'mdi:home'; iconColor = 'var(--icon-house-color)'; }
+ if (type === 'car') { icon = 'mdi:car-electric'; iconColor = 'var(--icon-consumer-1-color)'; }
+ if (type === 'export') { icon = 'mdi:arrow-right-box'; iconColor = 'var(--export-color)'; }
+ if (type === 'battery') { icon = 'mdi:battery-charging-high'; iconColor = 'var(--icon-battery-color)'; }
const path = this._createBracketPath(bottomX, width, 'up');
bottomBrackets.push({
@@ -707,14 +738,19 @@ console.log(
- ${barSegments.map(s => html`
+ ${barSegments.map(s => {
+ const textColor = s.type === 'solar' && this.config.color_text_solar ? 'var(--text-solar-color)'
+ : s.type === 'grid' && this.config.color_text_grid ? 'var(--text-grid-color)'
+ : s.type === 'battery' && this.config.color_text_battery ? 'var(--text-battery-color)'
+ : (s.color === 'var(--export-purple)' ? 'white' : 'black');
+ return html`
s.entityId && this._handleClick(s.entityId)}>
${s.widthPx > 35 ? this._formatPower(s.val) : ''}
- `)}
+ `})}
@@ -808,9 +844,9 @@ console.log(
const textClass = showNeonGlow ? 'flow-text' : 'flow-text no-shadow';
// Custom Labels for Consumers
- const labelC1 = this.config.consumer_1_label || "E-Auto";
- const labelC2 = this.config.consumer_2_label || "Heizung";
- const labelC3 = this.config.consumer_3_label || "Pool";
+ const labelC1 = this.config.consumer_1_label || this._localize('card.label_car');
+ const labelC2 = this.config.consumer_2_label || this._localize('card.label_heater');
+ const labelC3 = this.config.consumer_3_label || this._localize('card.label_pool');
const getVal = (entity) => {
const state = this.hass.states[entity];
@@ -832,6 +868,11 @@ console.log(
const showC3 = (entities.consumer_3 && (alwaysShowConsumer || Math.round(c3Val) > 0));
const anyBottomVisible = showC1 || showC2 || showC3;
+ // Consumer 1 pipe threshold
+ const hideC1Pipe = this.config.consumer_1_hide_pipe === true;
+ const c1PipeThreshold = this.config.consumer_1_pipe_threshold || 0;
+ const c1PipeActive = showC1 && (!hideC1Pipe || c1Val >= c1PipeThreshold);
+
const solar = hasSolar ? getVal(entities.solar) : 0;
const gridCombinedVal = hasGridCombined ? getVal(entities.grid_combined) : 0;
const gridMain = hasGridCombined ? gridCombinedVal : (hasGrid ? getVal(entities.grid) : 0);
@@ -861,8 +902,12 @@ console.log(
}
}
- const batteryCharge = battery > 0 ? battery : 0;
- const batteryDischarge = battery < 0 ? Math.abs(battery) : 0;
+ // Check for separate battery charge/discharge sensors
+ const hasBattChargeSensor = !!(entities.battery_charge && entities.battery_charge !== "");
+ const hasBattDischargeSensor = !!(entities.battery_discharge && entities.battery_discharge !== "");
+
+ const batteryCharge = hasBattChargeSensor ? Math.abs(getVal(entities.battery_charge)) : (battery > 0 ? battery : 0);
+ const batteryDischarge = hasBattDischargeSensor ? Math.abs(getVal(entities.battery_discharge)) : (battery < 0 ? Math.abs(battery) : 0);
let solarToBatt = 0;
let gridToBatt = 0;
@@ -889,6 +934,9 @@ console.log(
const gridToHouse = Math.max(0, gridImport - gridToBatt);
const house = solarToHouse + gridToHouse + batteryDischarge;
+ // Use house entity for display if defined, otherwise use calculated value
+ const houseDisplay = (entities.house && entities.house !== "") ? getVal(entities.house) : house;
+
const isTopArcActive = (solarToBatt > 0);
const topShift = (isTopArcActive || (!hideInactive && hasSolar && hasBattery)) ? 0 : 50;
let baseHeight = anyBottomVisible ? 480 : 340;
@@ -990,8 +1038,10 @@ console.log(
}
}
- const solarColor = isSolarActive ? 'var(--neon-yellow)' : 'var(--secondary-text-color)';
+ const solarColor = isSolarActive ? 'var(--icon-solar-color)' : 'var(--secondary-text-color)';
const gridColor = isGridExporting ? 'var(--export-color)' : (isGridActive ? 'var(--neon-blue)' : 'var(--secondary-text-color)');
+ const gridIconColor = (isGridActive && this.config.color_icon_grid) ? 'var(--icon-grid-color)' : gridColor;
+ const gridTextColor = (isGridActive && this.config.color_text_grid) ? 'var(--text-grid-color)' : gridColor;
const getAnimStyle = (val) => {
if (val <= 1) return "opacity: 0;";
@@ -1071,27 +1121,24 @@ console.log(
const renderMainIcon = (type, val, customIcon, color = null) => {
if (customIcon) {
- const style = color ? `color: ${color};` : (type === 'solar' ? 'color: var(--neon-yellow);' : (type === 'grid' ? 'color: var(--neon-blue);' : (type === 'battery' ? 'color: var(--neon-green);' : '')));
+ const style = color ? `color: ${color};` : (type === 'solar' ? 'color: var(--icon-solar-color);' : (type === 'grid' ? 'color: var(--icon-grid-color);' : (type === 'battery' ? 'color: var(--icon-battery-color);' : (type === 'house' ? 'color: var(--icon-house-color);' : ''))));
return html``;
}
return this._renderIcon(type, val, color);
};
- const getCustomClass = (icon) => icon ? 'has-custom-icon' : '';
-
const renderConsumer = (isVisible, cssClass, configKey, label, iconType, val, hexColor) => {
if (!isVisible) return html``;
const customIcon = this.config[`${configKey}_icon`];
let iconContent;
- const isCustom = !hideConsumerIcons && !!customIcon;
- const dynamicClass = isCustom ? 'has-custom-icon' : '';
+ const iconColorVar = `var(--icon-${configKey.replace(/_/g, '-')}-color)`;
if (hideConsumerIcons) {
iconContent = html``;
} else if (customIcon) {
- iconContent = html``;
+ iconContent = html``;
} else {
iconContent = this._renderIcon(iconType, val);
}
@@ -1099,12 +1146,16 @@ console.log(
const secEntity = entities[`secondary_${configKey}`];
const hasSecondary = !!(secEntity && secEntity !== "");
+ const textStyle = this.config[`color_text_${configKey}`]
+ ? `color: var(--text-${configKey.replace(/_/g, '-')}-color);`
+ : getConsumerColorStyle(hexColor);
+
return html`
- this._handleClick(entities[configKey])}>
${iconContent}
${renderSecondaryOrLabel(label, true, secEntity, hasSecondary)}
-
${this._formatPower(val)}
+
${this._formatPower(val)}
`;
};
@@ -1134,7 +1185,9 @@ console.log(
const pathHouseC2 = "M 210 310 L 210 370";
const pathHouseC3 = "M 255 265 Q 370 265 370 370";
- const houseTextStyle = houseTextCol ? `color: ${houseTextCol};` : '';
+ const houseTextStyle = this.config.color_text_house
+ ? 'color: var(--text-house-color);'
+ : (houseTextCol ? `color: ${houseTextCol};` : '');
const dashArrayVal = showTail ? '30 360' : (showDashedLine ? '13 13' : '0 380');
const strokeWidthVal = showDashedLine ? 4 : 8;
@@ -1155,7 +1208,7 @@ console.log(
-
+
@@ -1168,7 +1221,7 @@ console.log(
-
+
@@ -1184,39 +1237,39 @@ console.log(
${hasSolar ? html`
- this._handleClick(entities.solar)}>
${renderMainIcon('solar', solarVal, iconSolar, solarColor)}
${renderSecondaryOrLabel(labelSolarText, showLabelSolar, entities.secondary_solar, hasSecondarySolar)}
-
${this._formatPower(solarVal)}
+
${this._formatPower(solarVal)}
` : ''}
${hasGrid ? html`
- this._handleClick(entities.grid_combined || entities.grid)}>
- ${renderMainIcon('grid', isGridExporting ? gridExport : gridImport, iconGrid, gridColor)}
+ ${renderMainIcon('grid', isGridExporting ? gridExport : gridImport, iconGrid, gridIconColor)}
${renderSecondaryOrLabel(labelGridText, showLabelGrid, entities.secondary_grid, hasSecondaryGrid)}
-
+
${isGridExporting ? html`▲` : (isGridActive ? html`▼` : '')}
${this._formatPower(isGridExporting ? gridExport : gridImport)}
` : ''}
${hasBattery ? html`
-
this._handleClick(entities.battery)}>
${renderMainIcon('battery', battSoc, iconBattery)}
${renderSecondaryOrLabel(labelBatteryText, showLabelBattery, entities.secondary_battery, hasSecondaryBattery)}
-
${Math.round(battSoc)}%
+
${Math.round(battSoc)}%
` : ''}
-
this._handleClick(entities.house)}>
- ${renderMainIcon('house', 0, null, houseDominantColor)}
+ ${renderMainIcon('house', 0, null, this.config.color_icon_house ? 'var(--icon-house-color)' : houseDominantColor)}
${renderLabel(labelHouseText, showLabelHouse)}
-
${this._formatPower(house)}
+
${this._formatPower(houseDisplay)}
${renderConsumer(showC1, 'c1', 'consumer_1', labelC1, 'car', c1Val, this._getConsumerColor(1))}