Compare commits
No commits in common. "main" and "v_2.1" have entirely different histories.
10 changed files with 467 additions and 1849 deletions
1
.github/FUNDING.yml
vendored
1
.github/FUNDING.yml
vendored
|
|
@ -1,3 +1,2 @@
|
||||||
---
|
---
|
||||||
ko_fi: jayjojayson
|
|
||||||
custom: ["https://www.paypal.me/quadFlyerFW"]
|
custom: ["https://www.paypal.me/quadFlyerFW"]
|
||||||
|
|
|
||||||
168
README.md
168
README.md
|
|
@ -9,12 +9,10 @@
|
||||||
|
|
||||||
# Power Flux Card
|
# Power Flux Card
|
||||||
|
|
||||||
The ⚡ Power Flux Card is an advanced, animated energy flow card for Home Assistant. It visualizes the power distribution between Solar, Grid, Battery, and Consumers with beautiful neon effects and diffrent animations.
|
The ⚡ Power Flux Card is an advanced, animated energy flow card for Home Assistant. It visualizes the power distribution between Solar, Grid, Battery, and Consumers with beautiful neon effects and smooth animations.
|
||||||
|
|
||||||
If you like the Card, I would appreciate a Star rating ⭐ from you. 🤗
|
|
||||||
|
|
||||||
<img width="49%" height="auto" alt="power-flux-card" src="https://github.com/jayjojayson/power-flux-card/blob/main/docs/images/power-flux-card-ani.gif" /> <img width="49%" height="auto" alt="power-flux-card" src="https://github.com/jayjojayson/power-flux-card/blob/main/docs/images/power-flux-card.jpg" />
|
<img width="49%" height="auto" alt="power-flux-card" src="https://github.com/jayjojayson/power-flux-card/blob/main/docs/images/power-flux-card-ani.gif" /> <img width="49%" height="auto" alt="power-flux-card" src="https://github.com/jayjojayson/power-flux-card/blob/main/docs/images/power-flux-card.jpg" />
|
||||||
<img width="49%" height="auto" alt="power-flux-card" src="https://github.com/jayjojayson/power-flux-card/blob/main/docs/images/power-flux-card-compact.jpg" /> <img width="49%" height="auto" alt="power-flux-card" src="https://github.com/jayjojayson/power-flux-card/blob/main/docs/images/power-flux-card-compact2.jpg" />
|
<img width="50%" height="auto" alt="power-flux-card" src="https://github.com/jayjojayson/power-flux-card/blob/main/docs/images/power-flux-card-compact.jpg" />
|
||||||
|
|
||||||
### ✨ Features
|
### ✨ Features
|
||||||
|
|
||||||
|
|
@ -26,19 +24,9 @@ If you like the Card, I would appreciate a Star rating ⭐ from you. 🤗
|
||||||
- **Donut Chart**: Optional donut chart around the house icon showing energy mix.
|
- **Donut Chart**: Optional donut chart around the house icon showing energy mix.
|
||||||
- **Comet Tail / Dashed Lines**: Choose your preferred animation style.
|
- **Comet Tail / Dashed Lines**: Choose your preferred animation style.
|
||||||
- **Zoom**: Adjustable scale to fit your dashboard.
|
- **Zoom**: Adjustable scale to fit your dashboard.
|
||||||
- **Custom Colors**: Define custom colors for each source and consumer via the editor.
|
|
||||||
- **Background Color**: Enable a slightly tinted background for the circles in the default view.
|
|
||||||
- **More Info**: Click on any source/consumer for detailed information in a more-info dialog.
|
|
||||||
- **Grid Import/Export**: Supports both separate Import/Export entities or a combined entity with positive/negative values.
|
|
||||||
- **Grid-to-Battery**: Optional direct sensor for Grid-to-Battery flow, bypassing the standard calculation.
|
|
||||||
- **Secondary Sensors**: Optionally display a secondary sensor value in the main circles (e.g., daily yield for Solar, current charge/discharge power for Battery) and consumer bubbles.
|
|
||||||
- **Localization**: Fully translated in English and German.
|
- **Localization**: Fully translated in English and German.
|
||||||
- **Visual Editor**: easy configuration via the Home Assistant UI.
|
- **Visual Editor**: easy configuration via the Home Assistant UI.
|
||||||
|
|
||||||
[](https://www.youtube.com/watch?v=HGFBJJRWGW0)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 🚀 Installation
|
### 🚀 Installation
|
||||||
|
|
||||||
### HACS (Recommended)
|
### HACS (Recommended)
|
||||||
|
|
@ -61,11 +49,9 @@ If you like the Card, I would appreciate a Star rating ⭐ from you. 🤗
|
||||||
1. Download `power-flux-card.js` from the [Releases](../../releases) page.
|
1. Download `power-flux-card.js` from the [Releases](../../releases) page.
|
||||||
2. Upload it to `www/community/power-flux-card/` folder in Home Assistant.
|
2. Upload it to `www/community/power-flux-card/` folder in Home Assistant.
|
||||||
3. Add the resource in your Dashboard configuration:
|
3. Add the resource in your Dashboard configuration:
|
||||||
- URL: `/local/community/power-flux-card/power-flux-card.js`
|
- URL: `/local/power-flux-card.js`
|
||||||
- Type: JavaScript Module
|
- Type: JavaScript Module
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### ⚙️ Configuration
|
### ⚙️ Configuration
|
||||||
|
|
||||||
You can configure the card directly via the visual editor in Home Assistant.
|
You can configure the card directly via the visual editor in Home Assistant.
|
||||||
|
|
@ -84,151 +70,3 @@ 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.
|
- **Donut Chart**: Show the energy mix as a ring around the house.
|
||||||
- **Comet Tail / Dashed Line**: Change the flow animation style.
|
- **Comet Tail / Dashed Line**: Change the flow animation style.
|
||||||
- **Compact View**: Switch to the bar chart layout.
|
- **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).
|
|
||||||
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary> <b>Custom Colors with card_mod and Jinja2 Templates</b></summary>
|
|
||||||
|
|
||||||
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: |
|
|
||||||
:host {
|
|
||||||
{% 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: |
|
|
||||||
:host {
|
|
||||||
{% 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: |
|
|
||||||
:host {
|
|
||||||
{% 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: |
|
|
||||||
:host {
|
|
||||||
{% 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: |
|
|
||||||
:host {
|
|
||||||
{% 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.
|
|
||||||
</details>
|
|
||||||
|
|
|
||||||
963
dist/power-flux-card.js
vendored
963
dist/power-flux-card.js
vendored
File diff suppressed because it is too large
Load diff
|
|
@ -9,12 +9,10 @@
|
||||||
|
|
||||||
# Power Flux Card
|
# Power Flux Card
|
||||||
|
|
||||||
Die ⚡Power Flux Card ist eine erweiterte, animierte Energiefluss-Karte für Home Assistant. Sie visualisiert die Energieverteilung zwischen Solar, Netz, Batterie und Verbrauchern mit wunderschönen Neon-Effekten und verschiedenen Animationen.
|
Die ⚡Power Flux Card ist eine erweiterte, animierte Energiefluss-Karte für Home Assistant. Sie visualisiert die Energieverteilung zwischen Solar, Netz, Batterie und Verbrauchern mit wunderschönen Neon-Effekten und flüssigen Animationen.
|
||||||
|
|
||||||
Wenn euch die custom Card gefällt, würde ich mich sehr über eine Sternebewertung ⭐ freuen. 🤗
|
|
||||||
|
|
||||||
<img width="49%" height="auto" alt="power-flux-card" src="https://github.com/jayjojayson/power-flux-card/blob/main/docs/images/power-flux-card-ani.gif" /> <img width="49%" height="auto" alt="power-flux-card" src="https://github.com/jayjojayson/power-flux-card/blob/main/docs/images/power-flux-card.jpg" />
|
<img width="49%" height="auto" alt="power-flux-card" src="https://github.com/jayjojayson/power-flux-card/blob/main/docs/images/power-flux-card-ani.gif" /> <img width="49%" height="auto" alt="power-flux-card" src="https://github.com/jayjojayson/power-flux-card/blob/main/docs/images/power-flux-card.jpg" />
|
||||||
<img width="49%" height="auto" alt="power-flux-card" src="https://github.com/jayjojayson/power-flux-card/blob/main/docs/images/power-flux-card-compact.jpg" /> <img width="49%" height="auto" alt="power-flux-card" src="https://github.com/jayjojayson/power-flux-card/blob/main/docs/images/power-flux-card-compact2.jpg" />
|
<img width="50%" height="auto" alt="power-flux-card" src="https://github.com/jayjojayson/power-flux-card/blob/main/docs/images/power-flux-card-compact.jpg" />
|
||||||
|
|
||||||
### ✨ Funktionen
|
### ✨ Funktionen
|
||||||
|
|
||||||
|
|
@ -26,21 +24,9 @@ Wenn euch die custom Card gefällt, würde ich mich sehr über eine Sternebewert
|
||||||
- **Donut Chart**: Optionales Donut-Diagramm um das Haus-Icon, das den Energiemix zeigt.
|
- **Donut Chart**: Optionales Donut-Diagramm um das Haus-Icon, das den Energiemix zeigt.
|
||||||
- **Kometenschweif / Gestrichelte Linien**: Wählen Sie Ihren bevorzugten Animationsstil.
|
- **Kometenschweif / Gestrichelte Linien**: Wählen Sie Ihren bevorzugten Animationsstil.
|
||||||
- **Zoom**: Anpassbare Größe für Ihr Dashboard.
|
- **Zoom**: Anpassbare Größe für Ihr Dashboard.
|
||||||
- **Benutzerdefinierte Farben**: Definiere benutzerdefinierte Farben für jede Quelle und jeden Verbraucher über den Editor.
|
|
||||||
- **Hintergrundfarbe**: Aktiviere einen leicht getönten Hintergrund für die Kreise in der Standard-Ansicht.
|
|
||||||
- **Dynamische Animationsgeschwindigkeit**: Partikelgeschwindigkeit und -dichte passen sich dem aktuellen Energiefluss an.
|
|
||||||
- **Weitere Informationen**: Klicke auf eine beliebige Quelle/Verbraucher, um detaillierte Informationen in einem More-Info-Dialog anzuzeigen.
|
|
||||||
- **Netz-Import/Export**: Unterstützt sowohl separate Import/Export-Entitäten als auch eine kombinierte Entität mit positiven/negativen Werten.
|
|
||||||
- **Netz-zu-Batterie**: Optionaler direkter Sensor für den Netz-zu-Batterie-Fluss, der die Standardberechnung umgeht.
|
|
||||||
- **Sekundäre Sensoren**: Optional können sekundäre Sensorwerte in den Hauptkreisen (z.B. Tagesertrag für Solar, aktuelle Lade-/Entladeleistung für Batterie) angezeigt werden.
|
|
||||||
- **Lokalisierung**: Vollständig übersetzt in Deutsch und Englisch.
|
- **Lokalisierung**: Vollständig übersetzt in Deutsch und Englisch.
|
||||||
- **Visueller Editor**: Einfache Konfiguration über die Home Assistant UI.
|
- **Visueller Editor**: Einfache Konfiguration über die Home Assistant UI.
|
||||||
|
|
||||||
[](https://www.youtube.com/watch?v=HGFBJJRWGW0
|
|
||||||
)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 🚀 Installation
|
### 🚀 Installation
|
||||||
|
|
||||||
### HACS (Empfohlen)
|
### HACS (Empfohlen)
|
||||||
|
|
@ -62,12 +48,9 @@ Wenn euch die custom Card gefällt, würde ich mich sehr über eine Sternebewert
|
||||||
1. Lade die Datei `power-flux-card.js` von der [Releases](../../releases)-Seite herunter.
|
1. Lade die Datei `power-flux-card.js` von der [Releases](../../releases)-Seite herunter.
|
||||||
2. Lade sie in Ihren `www/community/power-flux-card/`-Ordner in Home Assistant hoch.
|
2. Lade sie in Ihren `www/community/power-flux-card/`-Ordner in Home Assistant hoch.
|
||||||
3. Füge die Ressource in Ihrer Dashboard-Konfiguration hinzu:
|
3. Füge die Ressource in Ihrer Dashboard-Konfiguration hinzu:
|
||||||
- URL: `/local/community/power-flux-card/power-flux-card.js`
|
- URL: `/local/power-flux-card.js`
|
||||||
- Typ: JavaScript Module
|
- Typ: JavaScript Module
|
||||||
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### ⚙️ Konfiguration
|
### ⚙️ Konfiguration
|
||||||
|
|
||||||
Du kannst die Karte direkt über den visuellen Editor in Home Assistant konfigurieren.
|
Du kannst die Karte direkt über den visuellen Editor in Home Assistant konfigurieren.
|
||||||
|
|
@ -86,151 +69,3 @@ Du kannst die Karte direkt über den visuellen Editor in Home Assistant konfigur
|
||||||
- **Donut Chart**: Zeigt den Energiemix als Ring um das Haus an.
|
- **Donut Chart**: Zeigt den Energiemix als Ring um das Haus an.
|
||||||
- **Kometenschweif / Gestrichelte Linie**: Ändern Sie den Stil der Flussanimation.
|
- **Kometenschweif / Gestrichelte Linie**: Ändern Sie den Stil der Flussanimation.
|
||||||
- **Kompakte Ansicht**: Wechseln Sie zum Balkendiagramm-Layout.
|
- **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).
|
|
||||||
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary> <b>Custom Farben mit card_mod und Jinja2 Templates</b></summary>
|
|
||||||
|
|
||||||
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: |
|
|
||||||
:host {
|
|
||||||
{% 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: |
|
|
||||||
:host {
|
|
||||||
{% 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: |
|
|
||||||
:host {
|
|
||||||
{% 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: |
|
|
||||||
:host {
|
|
||||||
{% 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: |
|
|
||||||
:host {
|
|
||||||
{% 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.
|
|
||||||
</details>
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 12 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 7.1 KiB |
|
|
@ -13,14 +13,14 @@ export default {
|
||||||
"editor.label_toggle": "Label im Kreis anzeigen",
|
"editor.label_toggle": "Label im Kreis anzeigen",
|
||||||
"editor.compact_view": "Kompakte Ansicht (evcc)",
|
"editor.compact_view": "Kompakte Ansicht (evcc)",
|
||||||
"editor.hide_inactive": "Inaktive Röhren ausblenden",
|
"editor.hide_inactive": "Inaktive Röhren ausblenden",
|
||||||
"editor.entity": "Kombinierter Batterie Sensor (W)",
|
"editor.entity": "Entität (Watt)",
|
||||||
"editor.label": "Beschriftung",
|
"editor.label": "Beschriftung",
|
||||||
"editor.icon": "Icon",
|
"editor.icon": "Icon",
|
||||||
"editor.back": "Zurück",
|
"editor.back": "Zurück",
|
||||||
"editor.battery_soc_label": "Ladestand (%)",
|
"editor.battery_soc_label": "Ladestand (%)",
|
||||||
"editor.house_total_title": "🏠 Gesamtverbrauch (optional)",
|
"editor.house_total_title": "🏠 Gesamtverbrauch (optional)",
|
||||||
"editor.house_sensor_label": "Sensor für Hausverbrauch (optional)",
|
"editor.house_sensor_label": "Sensor für Hausverbrauch (optional)",
|
||||||
"editor.house_sensor_hint": "Wird benötigt, damit das Haus-Icon anklickbar ist (more-details). Ansonsten wird der Hausverbrauch berechnet.",
|
"editor.house_sensor_hint": "Wird benötigt, damit das Haus-Icon anklickbar ist (compact view).",
|
||||||
"editor.consumer_1_title": "🚗 Links (Lila)",
|
"editor.consumer_1_title": "🚗 Links (Lila)",
|
||||||
"editor.consumer_2_title": "♨️ Mitte (Orange)",
|
"editor.consumer_2_title": "♨️ Mitte (Orange)",
|
||||||
"editor.consumer_3_title": "🏊 Rechts (Türkis)",
|
"editor.consumer_3_title": "🏊 Rechts (Türkis)",
|
||||||
|
|
@ -29,27 +29,8 @@ export default {
|
||||||
"editor.donut_chart": "Donut Chart (Grid/Haus)",
|
"editor.donut_chart": "Donut Chart (Grid/Haus)",
|
||||||
"editor.comet_tail": "Comet Tail Effect",
|
"editor.comet_tail": "Comet Tail Effect",
|
||||||
"editor.dashed_line": "Dashed Line Effect",
|
"editor.dashed_line": "Dashed Line Effect",
|
||||||
"editor.tinted_background": "Farbiger Hintergrund in Kreisen",
|
|
||||||
"editor.colored_values": "Farbige Textwerte",
|
"editor.colored_values": "Farbige Textwerte",
|
||||||
"editor.hide_consumer_icons": "Icons unten ausblenden",
|
"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 (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": "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: {
|
||||||
"card.label_solar": "Solar",
|
"card.label_solar": "Solar",
|
||||||
|
|
@ -57,7 +38,7 @@ export default {
|
||||||
"card.label_battery": "Batterie",
|
"card.label_battery": "Batterie",
|
||||||
"card.label_house": "Verbrauch",
|
"card.label_house": "Verbrauch",
|
||||||
"card.label_car": "E-Auto",
|
"card.label_car": "E-Auto",
|
||||||
"card.label_heater": "Heizung",
|
"card.label_import": "Import",
|
||||||
"card.label_pool": "Pool",
|
"card.label_export": "Export",
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -13,14 +13,14 @@ export default {
|
||||||
"editor.label_toggle": "Show Label in Bubble",
|
"editor.label_toggle": "Show Label in Bubble",
|
||||||
"editor.compact_view": "Compact View (evcc)",
|
"editor.compact_view": "Compact View (evcc)",
|
||||||
"editor.hide_inactive": "Hide Inactive Pipes",
|
"editor.hide_inactive": "Hide Inactive Pipes",
|
||||||
"editor.entity": "Combined Battery Sensor (W)",
|
"editor.entity": "Entity (Watt)",
|
||||||
"editor.label": "Label",
|
"editor.label": "Label",
|
||||||
"editor.icon": "Icon",
|
"editor.icon": "Icon",
|
||||||
"editor.back": "Back",
|
"editor.back": "Back",
|
||||||
"editor.battery_soc_label": "State of Charge (%)",
|
"editor.battery_soc_label": "State of Charge (%)",
|
||||||
"editor.house_total_title": "🏠 Total Consumption (optional)",
|
"editor.house_total_title": "🏠 Total Consumption (optional)",
|
||||||
"editor.house_sensor_label": "Sensor for House Consumption (optional)",
|
"editor.house_sensor_label": "Sensor for House Consumption (optional)",
|
||||||
"editor.house_sensor_hint": "Required to make the house icon clickable (more-details). Otherwise, the house consumption is calculated.",
|
"editor.house_sensor_hint": "Required to make the house icon clickable (compact view).",
|
||||||
"editor.consumer_1_title": "🚗 Left (Purple)",
|
"editor.consumer_1_title": "🚗 Left (Purple)",
|
||||||
"editor.consumer_2_title": "♨️ Center (Orange)",
|
"editor.consumer_2_title": "♨️ Center (Orange)",
|
||||||
"editor.consumer_3_title": "🏊 Right (Cyan)",
|
"editor.consumer_3_title": "🏊 Right (Cyan)",
|
||||||
|
|
@ -29,27 +29,8 @@ export default {
|
||||||
"editor.donut_chart": "Donut Chart (Grid/House)",
|
"editor.donut_chart": "Donut Chart (Grid/House)",
|
||||||
"editor.comet_tail": "Comet Tail Effect",
|
"editor.comet_tail": "Comet Tail Effect",
|
||||||
"editor.dashed_line": "Dashed Line Effect",
|
"editor.dashed_line": "Dashed Line Effect",
|
||||||
"editor.tinted_background": "Tinted Background in Bubbles",
|
|
||||||
"editor.colored_values": "Colored Text Values",
|
"editor.colored_values": "Colored Text Values",
|
||||||
"editor.hide_consumer_icons": "Hide Consumer Icons",
|
"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 (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.",
|
|
||||||
"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",
|
|
||||||
"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: {
|
||||||
"card.label_solar": "Solar",
|
"card.label_solar": "Solar",
|
||||||
|
|
@ -57,7 +38,7 @@ export default {
|
||||||
"card.label_battery": "Battery",
|
"card.label_battery": "Battery",
|
||||||
"card.label_house": "Consumption",
|
"card.label_house": "Consumption",
|
||||||
"card.label_car": "Car",
|
"card.label_car": "Car",
|
||||||
"card.label_heater": "Heater",
|
"card.label_import": "Import",
|
||||||
"card.label_pool": "Pool",
|
"card.label_export": "Export",
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ class PowerFluxCardEditor extends LitElement {
|
||||||
if (!this._config || !this.hass) return;
|
if (!this._config || !this.hass) return;
|
||||||
|
|
||||||
const target = ev.target;
|
const target = ev.target;
|
||||||
const key = target.configValue;
|
const key = target.configValue || this._currentConfigValue;
|
||||||
|
|
||||||
let value;
|
let value;
|
||||||
if (target.tagName === 'HA-SWITCH') {
|
if (target.tagName === 'HA-SWITCH') {
|
||||||
|
|
@ -65,13 +65,10 @@ class PowerFluxCardEditor extends LitElement {
|
||||||
|
|
||||||
if (key) {
|
if (key) {
|
||||||
const entityKeys = [
|
const entityKeys = [
|
||||||
'solar', 'grid', 'grid_export', 'grid_combined',
|
'solar', 'grid', 'grid_export',
|
||||||
'battery', 'battery_soc', 'grid_to_battery',
|
'battery', 'battery_soc',
|
||||||
'battery_charge', 'battery_discharge',
|
|
||||||
'house',
|
'house',
|
||||||
'consumer_1', 'consumer_2', 'consumer_3',
|
'consumer_1', 'consumer_2', 'consumer_3'
|
||||||
'secondary_solar', 'secondary_grid', 'secondary_battery',
|
|
||||||
'secondary_consumer_1', 'secondary_consumer_2', 'secondary_consumer_3'
|
|
||||||
];
|
];
|
||||||
|
|
||||||
let newConfig = { ...this._config };
|
let newConfig = { ...this._config };
|
||||||
|
|
@ -104,93 +101,6 @@ class PowerFluxCardEditor extends LitElement {
|
||||||
this._subView = null;
|
this._subView = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
_clearEntity(key) {
|
|
||||||
const newConfig = { ...this._config };
|
|
||||||
const currentEntities = newConfig.entities || {};
|
|
||||||
const newEntities = { ...currentEntities, [key]: "" };
|
|
||||||
newConfig.entities = newEntities;
|
|
||||||
this._config = newConfig;
|
|
||||||
fireEvent(this, "config-changed", { config: this._config });
|
|
||||||
}
|
|
||||||
|
|
||||||
_colorChanged(key, ev) {
|
|
||||||
const newConfig = { ...this._config, [key]: ev.target.value };
|
|
||||||
this._config = newConfig;
|
|
||||||
fireEvent(this, "config-changed", { config: this._config });
|
|
||||||
}
|
|
||||||
|
|
||||||
_resetColor(key) {
|
|
||||||
const newConfig = { ...this._config };
|
|
||||||
delete newConfig[key];
|
|
||||||
this._config = newConfig;
|
|
||||||
fireEvent(this, "config-changed", { config: this._config });
|
|
||||||
}
|
|
||||||
|
|
||||||
_renderEntitySelector(entitySelectorSchema, value, configValue, label) {
|
|
||||||
const val = value || "";
|
|
||||||
return html`
|
|
||||||
<div class="entity-picker-wrapper">
|
|
||||||
<ha-selector
|
|
||||||
.hass=${this.hass}
|
|
||||||
.selector=${entitySelectorSchema}
|
|
||||||
.value=${val}
|
|
||||||
.configValue=${configValue}
|
|
||||||
.label=${label}
|
|
||||||
@value-changed=${this._valueChanged}
|
|
||||||
></ha-selector>
|
|
||||||
${val ? html`<ha-icon
|
|
||||||
class="clear-entity-btn"
|
|
||||||
icon="mdi:close-circle"
|
|
||||||
@click=${() => this._clearEntity(configValue)}
|
|
||||||
></ha-icon>` : ''}
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
_renderColorPicker(key, label, defaultColor) {
|
|
||||||
const currentColor = this._config[key] || defaultColor;
|
|
||||||
const hasCustom = !!this._config[key];
|
|
||||||
return html`
|
|
||||||
<div class="color-picker-row">
|
|
||||||
<input type="color"
|
|
||||||
.value=${currentColor}
|
|
||||||
@input=${(e) => this._colorChanged(key, e)}>
|
|
||||||
<span class="color-label">${label}</span>
|
|
||||||
${hasCustom ? html`<ha-icon class="color-reset-btn"
|
|
||||||
icon="mdi:refresh"
|
|
||||||
@click=${() => this._resetColor(key)}></ha-icon>` : ''}
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
_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`
|
|
||||||
<div class="color-picker-quad">
|
|
||||||
${items.map(item => {
|
|
||||||
const color = this._config[item.key] || item.default;
|
|
||||||
const hasCustom = !!this._config[item.key];
|
|
||||||
return html`
|
|
||||||
<div class="color-picker-row">
|
|
||||||
<input type="color"
|
|
||||||
.value=${color}
|
|
||||||
@input=${(e) => this._colorChanged(item.key, e)}>
|
|
||||||
<span class="color-label">${item.label}</span>
|
|
||||||
${hasCustom ? html`<ha-icon class="color-reset-btn"
|
|
||||||
icon="mdi:refresh"
|
|
||||||
@click=${() => this._resetColor(item.key)}></ha-icon>` : ''}
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get styles() {
|
static get styles() {
|
||||||
return css`
|
return css`
|
||||||
.card-config {
|
.card-config {
|
||||||
|
|
@ -269,67 +179,6 @@ class PowerFluxCardEditor extends LitElement {
|
||||||
border-bottom: 1px solid var(--divider-color);
|
border-bottom: 1px solid var(--divider-color);
|
||||||
margin: 10px 0;
|
margin: 10px 0;
|
||||||
}
|
}
|
||||||
.entity-picker-wrapper {
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 4px;
|
|
||||||
}
|
|
||||||
.entity-picker-wrapper ha-selector {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
.clear-entity-btn {
|
|
||||||
--mdc-icon-size: 20px;
|
|
||||||
color: var(--secondary-text-color);
|
|
||||||
cursor: pointer;
|
|
||||||
flex-shrink: 0;
|
|
||||||
margin-top: -12px;
|
|
||||||
}
|
|
||||||
.clear-entity-btn:hover {
|
|
||||||
color: var(--error-color, #db4437);
|
|
||||||
}
|
|
||||||
.color-picker-row {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 12px;
|
|
||||||
padding: 8px 0;
|
|
||||||
}
|
|
||||||
.color-picker-row input[type="color"] {
|
|
||||||
-webkit-appearance: none;
|
|
||||||
border: 2px solid var(--divider-color);
|
|
||||||
border-radius: 50%;
|
|
||||||
width: 30px;
|
|
||||||
height: 30px;
|
|
||||||
padding: 2px;
|
|
||||||
cursor: pointer;
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
.color-picker-row input[type="color"]::-webkit-color-swatch-wrapper {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
.color-picker-row input[type="color"]::-webkit-color-swatch {
|
|
||||||
border: none;
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
.color-label {
|
|
||||||
flex: 1;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
.color-reset-btn {
|
|
||||||
--mdc-icon-size: 20px;
|
|
||||||
color: var(--secondary-text-color);
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.color-reset-btn:hover {
|
|
||||||
color: var(--primary-color);
|
|
||||||
}
|
|
||||||
.color-picker-quad {
|
|
||||||
display: flex;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
.color-picker-quad .color-picker-row {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -344,7 +193,14 @@ class PowerFluxCardEditor extends LitElement {
|
||||||
<h2>${this._localize('editor.solar_section')}</h2>
|
<h2>${this._localize('editor.solar_section')}</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
${this._renderEntitySelector(entitySelectorSchema, entities.solar, 'solar', this._localize('editor.entity'))}
|
<ha-selector
|
||||||
|
.hass=${this.hass}
|
||||||
|
.selector=${entitySelectorSchema}
|
||||||
|
.value=${entities.solar}
|
||||||
|
.configValue=${'solar'}
|
||||||
|
.label=${this._localize('editor.entity')}
|
||||||
|
@value-changed=${this._valueChanged}
|
||||||
|
></ha-selector>
|
||||||
|
|
||||||
<div class="separator"></div>
|
<div class="separator"></div>
|
||||||
|
|
||||||
|
|
@ -366,10 +222,6 @@ class PowerFluxCardEditor extends LitElement {
|
||||||
@value-changed=${this._valueChanged}
|
@value-changed=${this._valueChanged}
|
||||||
></ha-selector>
|
></ha-selector>
|
||||||
|
|
||||||
${this._renderEntitySelector(entitySelectorSchema, entities.secondary_solar || "", 'secondary_solar', this._localize('editor.secondary_sensor'))}
|
|
||||||
|
|
||||||
${this._renderColorPickerQuad('color_solar', 'color_pipe_solar', 'color_text_solar', 'color_icon_solar', '#ffdd00')}
|
|
||||||
|
|
||||||
<div class="separator"></div>
|
<div class="separator"></div>
|
||||||
|
|
||||||
<div class="switch-row">
|
<div class="switch-row">
|
||||||
|
|
@ -401,18 +253,23 @@ class PowerFluxCardEditor extends LitElement {
|
||||||
<h2>${this._localize('editor.grid_section')}</h2>
|
<h2>${this._localize('editor.grid_section')}</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
${this._renderEntitySelector(entitySelectorSchema, entities.grid_combined || "", 'grid_combined', this._localize('editor.grid_combined_sensor'))}
|
<ha-selector
|
||||||
|
.hass=${this.hass}
|
||||||
|
.selector=${entitySelectorSchema}
|
||||||
|
.value=${entities.grid}
|
||||||
|
.configValue=${'grid'}
|
||||||
|
.label=${this._localize('card.label_import') + " (W)"}
|
||||||
|
@value-changed=${this._valueChanged}
|
||||||
|
></ha-selector>
|
||||||
|
|
||||||
<div class="separator"></div>
|
<ha-selector
|
||||||
|
.hass=${this.hass}
|
||||||
<div style="font-size: 0.8em; color: var(--secondary-text-color); margin-top: 4px;">
|
.selector=${entitySelectorSchema}
|
||||||
${this._localize('editor.grid_combined_hint')}
|
.value=${entities.grid_export}
|
||||||
</div>
|
.configValue=${'grid_export'}
|
||||||
|
.label=${this._localize('card.label_export') + " (W, Optional)"}
|
||||||
|
@value-changed=${this._valueChanged}
|
||||||
${this._renderEntitySelector(entitySelectorSchema, entities.grid, 'grid', this._localize('card.label_import') + " (W)")}
|
></ha-selector>
|
||||||
|
|
||||||
${this._renderEntitySelector(entitySelectorSchema, entities.grid_export, 'grid_export', this._localize('card.label_export') + " (W, Optional)")}
|
|
||||||
|
|
||||||
<div class="separator"></div>
|
<div class="separator"></div>
|
||||||
|
|
||||||
|
|
@ -434,12 +291,6 @@ class PowerFluxCardEditor extends LitElement {
|
||||||
@value-changed=${this._valueChanged}
|
@value-changed=${this._valueChanged}
|
||||||
></ha-selector>
|
></ha-selector>
|
||||||
|
|
||||||
${this._renderEntitySelector(entitySelectorSchema, entities.secondary_grid || "", 'secondary_grid', this._localize('editor.secondary_sensor'))}
|
|
||||||
|
|
||||||
${this._renderColorPickerQuad('color_grid', 'color_pipe_grid', 'color_text_grid', 'color_icon_grid', '#3b82f6')}
|
|
||||||
|
|
||||||
${this._renderColorPicker('color_export', this._localize('editor.export_color'), '#ff3333')}
|
|
||||||
|
|
||||||
<div class="separator"></div>
|
<div class="separator"></div>
|
||||||
|
|
||||||
<div class="switch-row">
|
<div class="switch-row">
|
||||||
|
|
@ -471,15 +322,23 @@ class PowerFluxCardEditor extends LitElement {
|
||||||
<h2>${this._localize('editor.battery_section')}</h2>
|
<h2>${this._localize('editor.battery_section')}</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
${this._renderEntitySelector(entitySelectorSchema, entities.battery, 'battery', this._localize('editor.entity'))}
|
<ha-selector
|
||||||
|
.hass=${this.hass}
|
||||||
|
.selector=${entitySelectorSchema}
|
||||||
|
.value=${entities.battery}
|
||||||
|
.configValue=${'battery'}
|
||||||
|
.label=${this._localize('editor.entity')}
|
||||||
|
@value-changed=${this._valueChanged}
|
||||||
|
></ha-selector>
|
||||||
|
|
||||||
<div class="separator"></div>
|
<ha-selector
|
||||||
|
.hass=${this.hass}
|
||||||
<div style="font-size: 0.8em; color: var(--secondary-text-color); margin-top: 4px;">
|
.selector=${entitySelectorSchema}
|
||||||
${this._localize('editor.battery_separate_hint')}
|
.value=${entities.battery_soc}
|
||||||
</div>
|
.configValue=${'battery_soc'}
|
||||||
${this._renderEntitySelector(entitySelectorSchema, entities.battery_charge || "", 'battery_charge', this._localize('editor.battery_charge_sensor'))}
|
.label=${this._localize('editor.battery_soc_label')}
|
||||||
${this._renderEntitySelector(entitySelectorSchema, entities.battery_discharge || "", 'battery_discharge', this._localize('editor.battery_discharge_sensor'))}
|
@value-changed=${this._valueChanged}
|
||||||
|
></ha-selector>
|
||||||
|
|
||||||
<div class="separator"></div>
|
<div class="separator"></div>
|
||||||
|
|
||||||
|
|
@ -503,21 +362,6 @@ class PowerFluxCardEditor extends LitElement {
|
||||||
|
|
||||||
<div class="separator"></div>
|
<div class="separator"></div>
|
||||||
|
|
||||||
${this._renderEntitySelector(entitySelectorSchema, entities.battery_soc, 'battery_soc', this._localize('editor.battery_soc_label'))}
|
|
||||||
|
|
||||||
<div class="separator"></div>
|
|
||||||
|
|
||||||
<div style="font-size: 0.8em; color: var(--secondary-text-color); margin-top: 4px;">
|
|
||||||
${this._localize('editor.grid_to_battery_hint')}
|
|
||||||
</div>
|
|
||||||
${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._renderColorPickerQuad('color_battery', 'color_pipe_battery', 'color_text_battery', 'color_icon_battery', '#00ff88')}
|
|
||||||
|
|
||||||
<div class="separator"></div>
|
|
||||||
|
|
||||||
<div class="switch-row">
|
<div class="switch-row">
|
||||||
<ha-switch
|
<ha-switch
|
||||||
.checked=${this._config.show_label_battery === true}
|
.checked=${this._config.show_label_battery === true}
|
||||||
|
|
@ -558,17 +402,29 @@ class PowerFluxCardEditor extends LitElement {
|
||||||
|
|
||||||
<div class="consumer-group">
|
<div class="consumer-group">
|
||||||
<div class="consumer-title">${this._localize('editor.house_total_title')}</div>
|
<div class="consumer-title">${this._localize('editor.house_total_title')}</div>
|
||||||
${this._renderEntitySelector(entitySelectorSchema, entities.house || "", 'house', this._localize('editor.house_sensor_label'))}
|
<ha-selector
|
||||||
|
.hass=${this.hass}
|
||||||
|
.selector=${entitySelectorSchema}
|
||||||
|
.value=${entities.house || ""}
|
||||||
|
.configValue=${'house'}
|
||||||
|
.label=${this._localize('editor.house_sensor_label')}
|
||||||
|
@value-changed=${this._valueChanged}
|
||||||
|
></ha-selector>
|
||||||
<div style="font-size: 0.8em; color: var(--secondary-text-color); margin-top: 4px;">
|
<div style="font-size: 0.8em; color: var(--secondary-text-color); margin-top: 4px;">
|
||||||
${this._localize('editor.house_sensor_hint')}
|
${this._localize('editor.house_sensor_hint')}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
${this._renderColorPickerQuad('color_house', null, 'color_text_house', 'color_icon_house', '#ff0080')}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="consumer-group">
|
<div class="consumer-group">
|
||||||
<div class="consumer-title" style="color: #a855f7;">${this._localize('editor.consumer_1_title')}</div>
|
<div class="consumer-title" style="color: #a855f7;">${this._localize('editor.consumer_1_title')}</div>
|
||||||
${this._renderEntitySelector(entitySelectorSchema, entities.consumer_1, 'consumer_1', this._localize('editor.entity'))}
|
<ha-selector
|
||||||
|
.hass=${this.hass}
|
||||||
|
.selector=${entitySelectorSchema}
|
||||||
|
.value=${entities.consumer_1}
|
||||||
|
.configValue=${'consumer_1'}
|
||||||
|
.label=${this._localize('editor.entity')}
|
||||||
|
@value-changed=${this._valueChanged}
|
||||||
|
></ha-selector>
|
||||||
|
|
||||||
<ha-selector
|
<ha-selector
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
|
|
@ -587,53 +443,18 @@ class PowerFluxCardEditor extends LitElement {
|
||||||
.label=${this._localize('editor.icon')}
|
.label=${this._localize('editor.icon')}
|
||||||
@value-changed=${this._valueChanged}
|
@value-changed=${this._valueChanged}
|
||||||
></ha-selector>
|
></ha-selector>
|
||||||
|
|
||||||
<div style="display: flex; align-items: center; justify-content: space-between; margin-top: 8px;">
|
|
||||||
<span>${this._localize('editor.invert_consumer_1')}</span>
|
|
||||||
<ha-switch
|
|
||||||
.checked=${this._config.invert_consumer_1 === true}
|
|
||||||
.configValue=${'invert_consumer_1'}
|
|
||||||
@change=${this._valueChanged}
|
|
||||||
></ha-switch>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style="display: flex; align-items: center; justify-content: space-between; margin-top: 8px;">
|
|
||||||
<span>${this._localize('editor.consumer_1_hide_pipe')}</span>
|
|
||||||
<ha-switch
|
|
||||||
.checked=${this._config.consumer_1_hide_pipe === true}
|
|
||||||
.configValue=${'consumer_1_hide_pipe'}
|
|
||||||
@change=${this._valueChanged}
|
|
||||||
></ha-switch>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
${this._config.consumer_1_hide_pipe === true ? html`
|
|
||||||
<ha-selector
|
|
||||||
.hass=${this.hass}
|
|
||||||
.selector=${{ number: { min: 0, max: 2000, step: 10, mode: "slider" } }}
|
|
||||||
.value=${this._config.consumer_1_pipe_threshold !== undefined ? this._config.consumer_1_pipe_threshold : 0}
|
|
||||||
.configValue=${'consumer_1_pipe_threshold'}
|
|
||||||
.label=${this._localize('editor.consumer_pipe_threshold')}
|
|
||||||
@value-changed=${this._valueChanged}
|
|
||||||
></ha-selector>
|
|
||||||
` : ''}
|
|
||||||
|
|
||||||
<div style="display: flex; align-items: center; justify-content: space-between; margin-top: 8px; margin-bottom: 8px;">
|
|
||||||
<span>${this._localize('editor.consumer_unit_kw')}</span>
|
|
||||||
<ha-switch
|
|
||||||
.checked=${this._config.consumer_1_unit_kw === true}
|
|
||||||
.configValue=${'consumer_1_unit_kw'}
|
|
||||||
@change=${this._valueChanged}
|
|
||||||
></ha-switch>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
${this._renderEntitySelector(entitySelectorSchema, entities.secondary_consumer_1 || "", 'secondary_consumer_1', this._localize('editor.secondary_sensor'))}
|
|
||||||
|
|
||||||
${this._renderColorPickerQuad('color_consumer_1', 'color_pipe_consumer_1', 'color_text_consumer_1', 'color_icon_consumer_1', '#a855f7')}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="consumer-group">
|
<div class="consumer-group">
|
||||||
<div class="consumer-title" style="color: #f97316;">${this._localize('editor.consumer_2_title')}</div>
|
<div class="consumer-title" style="color: #f97316;">${this._localize('editor.consumer_2_title')}</div>
|
||||||
${this._renderEntitySelector(entitySelectorSchema, entities.consumer_2, 'consumer_2', this._localize('editor.entity'))}
|
<ha-selector
|
||||||
|
.hass=${this.hass}
|
||||||
|
.selector=${entitySelectorSchema}
|
||||||
|
.value=${entities.consumer_2}
|
||||||
|
.configValue=${'consumer_2'}
|
||||||
|
.label=${this._localize('editor.entity')}
|
||||||
|
@value-changed=${this._valueChanged}
|
||||||
|
></ha-selector>
|
||||||
|
|
||||||
<ha-selector
|
<ha-selector
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
|
|
@ -652,24 +473,18 @@ class PowerFluxCardEditor extends LitElement {
|
||||||
.label=${this._localize('editor.icon')}
|
.label=${this._localize('editor.icon')}
|
||||||
@value-changed=${this._valueChanged}
|
@value-changed=${this._valueChanged}
|
||||||
></ha-selector>
|
></ha-selector>
|
||||||
|
|
||||||
<div style="display: flex; align-items: center; justify-content: space-between; margin-top: 8px; margin-bottom: 8px;">
|
|
||||||
<span>${this._localize('editor.consumer_unit_kw')}</span>
|
|
||||||
<ha-switch
|
|
||||||
.checked=${this._config.consumer_2_unit_kw === true}
|
|
||||||
.configValue=${'consumer_2_unit_kw'}
|
|
||||||
@change=${this._valueChanged}
|
|
||||||
></ha-switch>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
${this._renderEntitySelector(entitySelectorSchema, entities.secondary_consumer_2 || "", 'secondary_consumer_2', this._localize('editor.secondary_sensor'))}
|
|
||||||
|
|
||||||
${this._renderColorPickerQuad('color_consumer_2', 'color_pipe_consumer_2', 'color_text_consumer_2', 'color_icon_consumer_2', '#f97316')}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="consumer-group">
|
<div class="consumer-group">
|
||||||
<div class="consumer-title" style="color: #06b6d4;">${this._localize('editor.consumer_3_title')}</div>
|
<div class="consumer-title" style="color: #06b6d4;">${this._localize('editor.consumer_3_title')}</div>
|
||||||
${this._renderEntitySelector(entitySelectorSchema, entities.consumer_3, 'consumer_3', this._localize('editor.entity'))}
|
<ha-selector
|
||||||
|
.hass=${this.hass}
|
||||||
|
.selector=${entitySelectorSchema}
|
||||||
|
.value=${entities.consumer_3}
|
||||||
|
.configValue=${'consumer_3'}
|
||||||
|
.label=${this._localize('editor.entity')}
|
||||||
|
@value-changed=${this._valueChanged}
|
||||||
|
></ha-selector>
|
||||||
|
|
||||||
<ha-selector
|
<ha-selector
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
|
|
@ -688,19 +503,6 @@ class PowerFluxCardEditor extends LitElement {
|
||||||
.label=${this._localize('editor.icon')}
|
.label=${this._localize('editor.icon')}
|
||||||
@value-changed=${this._valueChanged}
|
@value-changed=${this._valueChanged}
|
||||||
></ha-selector>
|
></ha-selector>
|
||||||
|
|
||||||
<div style="display: flex; align-items: center; justify-content: space-between; margin-top: 8px; margin-bottom: 8px;">
|
|
||||||
<span>${this._localize('editor.consumer_unit_kw')}</span>
|
|
||||||
<ha-switch
|
|
||||||
.checked=${this._config.consumer_3_unit_kw === true}
|
|
||||||
.configValue=${'consumer_3_unit_kw'}
|
|
||||||
@change=${this._valueChanged}
|
|
||||||
></ha-switch>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
${this._renderEntitySelector(entitySelectorSchema, entities.secondary_consumer_3 || "", 'secondary_consumer_3', this._localize('editor.secondary_sensor'))}
|
|
||||||
|
|
||||||
${this._renderColorPickerQuad('color_consumer_3', 'color_pipe_consumer_3', 'color_text_consumer_3', 'color_icon_consumer_3', '#06b6d4')}
|
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
@ -798,15 +600,6 @@ class PowerFluxCardEditor extends LitElement {
|
||||||
<div class="switch-label">${this._localize('editor.dashed_line')}</div>
|
<div class="switch-label">${this._localize('editor.dashed_line')}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="switch-row">
|
|
||||||
<ha-switch
|
|
||||||
.checked=${this._config.show_tinted_background === true}
|
|
||||||
.configValue=${'show_tinted_background'}
|
|
||||||
@change=${this._valueChanged}
|
|
||||||
></ha-switch>
|
|
||||||
<div class="switch-label">${this._localize('editor.tinted_background')}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="switch-row">
|
<div class="switch-row">
|
||||||
<ha-switch
|
<ha-switch
|
||||||
.checked=${this._config.use_colored_values === true}
|
.checked=${this._config.use_colored_values === true}
|
||||||
|
|
@ -834,15 +627,6 @@ class PowerFluxCardEditor extends LitElement {
|
||||||
<div class="switch-label">${this._localize('editor.hide_inactive')}</div>
|
<div class="switch-label">${this._localize('editor.hide_inactive')}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="switch-row">
|
|
||||||
<ha-switch
|
|
||||||
.checked=${this._config.show_consumer_always === true}
|
|
||||||
.configValue=${'show_consumer_always'}
|
|
||||||
@change=${this._valueChanged}
|
|
||||||
></ha-switch>
|
|
||||||
<div class="switch-label">${this._localize('editor.show_consumer_always')}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="switch-row">
|
<div class="switch-row">
|
||||||
<ha-switch
|
<ha-switch
|
||||||
.checked=${this._config.compact_view === true}
|
.checked=${this._config.compact_view === true}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import lang_en from "./lang-en.js";
|
||||||
import lang_de from "./lang-de.js";
|
import lang_de from "./lang-de.js";
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
"%c⚡ Power Flux Card v_2.4 ready",
|
"%c⚡ Power Flux Card v_2.1 ready",
|
||||||
"background: #d19525ff; color: #000; padding: 2px 6px; border-radius: 4px; font-weight: bold;"
|
"background: #d19525ff; color: #000; padding: 2px 6px; border-radius: 4px; font-weight: bold;"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -40,12 +40,6 @@ console.log(
|
||||||
return {
|
return {
|
||||||
zoom: 0.9,
|
zoom: 0.9,
|
||||||
compact_view: false,
|
compact_view: false,
|
||||||
consumer_1_unit_kw: false,
|
|
||||||
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_donut_border: false,
|
||||||
show_neon_glow: true,
|
show_neon_glow: true,
|
||||||
show_comet_tail: false,
|
show_comet_tail: false,
|
||||||
|
|
@ -65,11 +59,8 @@ console.log(
|
||||||
solar: "",
|
solar: "",
|
||||||
grid: "",
|
grid: "",
|
||||||
grid_export: "",
|
grid_export: "",
|
||||||
grid_combined: "",
|
|
||||||
battery: "",
|
battery: "",
|
||||||
battery_soc: "",
|
battery_soc: "",
|
||||||
battery_charge: "",
|
|
||||||
battery_discharge: "",
|
|
||||||
house: "",
|
house: "",
|
||||||
consumer_1: "",
|
consumer_1: "",
|
||||||
consumer_2: "",
|
consumer_2: "",
|
||||||
|
|
@ -106,58 +97,6 @@ console.log(
|
||||||
this._resizeObserver.observe(this);
|
this._resizeObserver.observe(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
updated(changedProps) {
|
|
||||||
super.updated(changedProps);
|
|
||||||
if (changedProps.has('hass') && this.hass) {
|
|
||||||
const isDark = this.hass.themes?.darkMode !== false;
|
|
||||||
if (isDark) {
|
|
||||||
this.removeAttribute('data-theme-light');
|
|
||||||
} else {
|
|
||||||
this.setAttribute('data-theme-light', '');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Apply custom colors from config
|
|
||||||
if (this.config) {
|
|
||||||
const colorMap = {
|
|
||||||
'color_solar': '--neon-yellow',
|
|
||||||
'color_grid': '--neon-blue',
|
|
||||||
'color_battery': '--neon-green',
|
|
||||||
'color_export': '--export-color',
|
|
||||||
'color_consumer_1': '--consumer-1-color',
|
|
||||||
'color_consumer_2': '--consumer-2-color',
|
|
||||||
'color_consumer_3': '--consumer-3-color',
|
|
||||||
'color_pipe_solar': '--pipe-solar-color',
|
|
||||||
'color_pipe_grid': '--pipe-grid-color',
|
|
||||||
'color_pipe_battery': '--pipe-battery-color',
|
|
||||||
'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]) {
|
|
||||||
this.style.setProperty(cssVar, this.config[configKey]);
|
|
||||||
} else {
|
|
||||||
this.style.removeProperty(cssVar);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
disconnectedCallback() {
|
disconnectedCallback() {
|
||||||
super.disconnectedCallback();
|
super.disconnectedCallback();
|
||||||
if (this._resizeObserver) {
|
if (this._resizeObserver) {
|
||||||
|
|
@ -174,45 +113,10 @@ console.log(
|
||||||
--neon-green: #00ff88;
|
--neon-green: #00ff88;
|
||||||
--neon-pink: #ff0080;
|
--neon-pink: #ff0080;
|
||||||
--neon-red: #ff3333;
|
--neon-red: #ff3333;
|
||||||
|
--grid-grey: #9e9e9e;
|
||||||
--export-purple: #a855f7;
|
--export-purple: #a855f7;
|
||||||
--export-color: #ff3333;
|
|
||||||
--consumer-1-color: #a855f7;
|
|
||||||
--consumer-2-color: #f97316;
|
|
||||||
--consumer-3-color: #06b6d4;
|
|
||||||
--pipe-solar-color: var(--neon-yellow);
|
|
||||||
--pipe-grid-color: var(--neon-blue);
|
|
||||||
--pipe-battery-color: var(--neon-green);
|
|
||||||
--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;
|
--flow-dasharray: 0 380;
|
||||||
}
|
}
|
||||||
:host([data-theme-light]) {
|
|
||||||
--neon-yellow: #c8a800;
|
|
||||||
--neon-blue: #2563eb;
|
|
||||||
--neon-green: #059669;
|
|
||||||
--neon-pink: #db2777;
|
|
||||||
--neon-red: #dc2626;
|
|
||||||
--export-purple: #7c3aed;
|
|
||||||
--export-color: #dc2626;
|
|
||||||
--consumer-1-color: #7c3aed;
|
|
||||||
--consumer-2-color: #ea580c;
|
|
||||||
--consumer-3-color: #0891b2;
|
|
||||||
}
|
|
||||||
ha-card {
|
ha-card {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
@ -266,7 +170,7 @@ console.log(
|
||||||
.compact-bar-wrapper {
|
.compact-bar-wrapper {
|
||||||
height: 36px;
|
height: 36px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background: var(--card-background-color, #333);
|
background: #333;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
margin: 4px 0;
|
margin: 4px 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
@ -287,11 +191,17 @@ console.log(
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Source Colors */
|
||||||
|
.src-solar { background: var(--neon-green); color: black; }
|
||||||
|
.src-grid { background: var(--grid-grey); color: black; }
|
||||||
|
.src-battery { background: var(--neon-yellow); color: black; }
|
||||||
|
|
||||||
/* --- STANDARD VIEW STYLES --- */
|
/* --- STANDARD VIEW STYLES --- */
|
||||||
.scale-wrapper {
|
.scale-wrapper {
|
||||||
width: 420px;
|
width: 420px;
|
||||||
transform-origin: top left;
|
transform-origin: top center;
|
||||||
transition: transform 0.1s linear;
|
transition: transform 0.1s linear;
|
||||||
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.absolute-container {
|
.absolute-container {
|
||||||
|
|
@ -311,18 +221,15 @@ console.log(
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
cursor: pointer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.bubble.tinted { background: rgba(255, 255, 255, 0.05); }
|
.bubble.tinted { background: rgba(255, 255, 255, 0.05); }
|
||||||
.bubble.tinted.solar { background: color-mix(in srgb, var(--neon-yellow), transparent 85%); }
|
.bubble.tinted.solar { background: color-mix(in srgb, var(--neon-yellow), transparent 85%); }
|
||||||
.bubble.tinted.grid { background: color-mix(in srgb, var(--neon-blue), transparent 85%); }
|
.bubble.tinted.grid { background: color-mix(in srgb, var(--neon-blue), transparent 85%); }
|
||||||
.bubble.tinted.grid.exporting { background: color-mix(in srgb, var(--export-color), transparent 85%); }
|
|
||||||
.bubble.grid.exporting { border-color: var(--export-color); }
|
|
||||||
.bubble.tinted.battery { background: color-mix(in srgb, var(--neon-green), transparent 85%); }
|
.bubble.tinted.battery { background: color-mix(in srgb, var(--neon-green), transparent 85%); }
|
||||||
.bubble.tinted.c1 { background: color-mix(in srgb, var(--consumer-1-color), transparent 85%); }
|
.bubble.tinted.c1 { background: color-mix(in srgb, #a855f7, transparent 85%); }
|
||||||
.bubble.tinted.c2 { background: color-mix(in srgb, var(--consumer-2-color), transparent 85%); }
|
.bubble.tinted.c2 { background: color-mix(in srgb, #f97316, transparent 85%); }
|
||||||
.bubble.tinted.c3 { background: color-mix(in srgb, var(--consumer-3-color), transparent 85%); }
|
.bubble.tinted.c3 { background: color-mix(in srgb, #06b6d4, transparent 85%); }
|
||||||
|
|
||||||
.bubble.house { border-color: var(--neon-pink); }
|
.bubble.house { border-color: var(--neon-pink); }
|
||||||
.bubble.house.tinted { background: color-mix(in srgb, var(--neon-pink), transparent 85%); }
|
.bubble.house.tinted { background: color-mix(in srgb, var(--neon-pink), transparent 85%); }
|
||||||
|
|
@ -335,34 +242,20 @@ console.log(
|
||||||
-webkit-mask-composite: xor; mask-composite: exclude; z-index: -1; pointer-events: none;
|
-webkit-mask-composite: xor; mask-composite: exclude; z-index: -1; pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bubble.grid.donut { border: none !important; background: transparent; }
|
|
||||||
.bubble.grid.donut.tinted { background: color-mix(in srgb, var(--neon-blue), transparent 85%); }
|
|
||||||
.bubble.grid.donut.tinted.exporting { background: color-mix(in srgb, var(--export-color), transparent 85%); }
|
|
||||||
.bubble.grid.donut::before {
|
|
||||||
content: ""; position: absolute; inset: 0; border-radius: 50%; padding: 4px;
|
|
||||||
background: var(--grid-gradient, var(--neon-blue));
|
|
||||||
-webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
|
|
||||||
-webkit-mask-composite: xor; mask-composite: exclude; z-index: -1; pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-svg, .icon-custom {
|
.icon-svg, .icon-custom {
|
||||||
width: 33px; height: 33px; position: absolute; top: 10px; left: 50%; margin-left: -17px; z-index: 2; display: block;
|
width: 34px; height: 34px; position: absolute; top: 13px; left: 50%; margin-left: -17px; z-index: 2; display: block;
|
||||||
}
|
}
|
||||||
.icon-custom { --mdc-icon-size: 34px; }
|
.icon-custom { --mdc-icon-size: 34px; }
|
||||||
|
|
||||||
.sub {
|
.sub {
|
||||||
font-size: 9px; color: var(--secondary-text-color); text-transform: uppercase; letter-spacing: 0.5px;
|
font-size: 9px; color: var(--secondary-text-color); text-transform: uppercase; letter-spacing: 0.5px;
|
||||||
line-height: 1.1; z-index: 2; position: absolute; top: 46px; left: 0; width: 100%; text-align: center; margin: 0; pointer-events: none;
|
line-height: 1.1; z-index: 2; position: absolute; top: 48px; left: 0; width: 100%; text-align: center; margin: 0; pointer-events: none;
|
||||||
}
|
|
||||||
.sub.secondary-val {
|
|
||||||
text-transform: none; letter-spacing: 0; font-weight: 500; font-size: 10px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.value {
|
.value {
|
||||||
font-weight: bold; font-size: 15px; white-space: nowrap; z-index: 2; transition: color 0.3s ease;
|
font-weight: bold; font-size: 15px; white-space: nowrap; z-index: 2; transition: color 0.3s ease;
|
||||||
line-height: 1.2; position: absolute; bottom: 11px; left: 0; width: 100%; text-align: center; margin: 0;
|
line-height: 1.2; position: absolute; bottom: 11px; left: 0; width: 100%; text-align: center; margin: 0;
|
||||||
}
|
}
|
||||||
.direction-arrow { font-size: 12px; margin-right: 0px; vertical-align: top; }
|
|
||||||
|
|
||||||
@keyframes spin { 100% { transform: rotate(360deg); } }
|
@keyframes spin { 100% { transform: rotate(360deg); } }
|
||||||
.spin-slow { animation: spin 12s linear infinite; transform-origin: center; }
|
.spin-slow { animation: spin 12s linear infinite; transform-origin: center; }
|
||||||
|
|
@ -376,18 +269,17 @@ console.log(
|
||||||
.solar { border-color: var(--neon-yellow); }
|
.solar { border-color: var(--neon-yellow); }
|
||||||
.battery { border-color: var(--neon-green); }
|
.battery { border-color: var(--neon-green); }
|
||||||
.grid { border-color: var(--neon-blue); }
|
.grid { border-color: var(--neon-blue); }
|
||||||
.c1 { border-color: var(--consumer-1-color); }
|
.c1 { border-color: #a855f7; }
|
||||||
.c2 { border-color: var(--consumer-2-color); }
|
.c2 { border-color: #f97316; }
|
||||||
.c3 { border-color: var(--consumer-3-color); }
|
.c3 { border-color: #06b6d4; }
|
||||||
.inactive { border-color: var(--secondary-text-color); }
|
.inactive { border-color: var(--secondary-text-color); }
|
||||||
|
|
||||||
.glow.solar { box-shadow: 0 0 15px color-mix(in srgb, var(--neon-yellow), transparent 60%); }
|
.glow.solar { box-shadow: 0 0 15px rgba(255, 221, 0, 0.4); }
|
||||||
.glow.battery { box-shadow: 0 0 15px color-mix(in srgb, var(--neon-green), transparent 60%); }
|
.glow.battery { box-shadow: 0 0 15px rgba(0, 255, 136, 0.4); }
|
||||||
.glow.grid { box-shadow: 0 0 15px color-mix(in srgb, var(--neon-blue), transparent 60%); }
|
.glow.grid { box-shadow: 0 0 15px rgba(59, 130, 246, 0.4); }
|
||||||
.glow.grid.exporting { box-shadow: 0 0 15px color-mix(in srgb, var(--export-color), transparent 60%); }
|
.glow.c1 { box-shadow: 0 0 15px rgba(168, 85, 247, 0.4); }
|
||||||
.glow.c1 { box-shadow: 0 0 15px color-mix(in srgb, var(--consumer-1-color), transparent 60%); }
|
.glow.c2 { box-shadow: 0 0 15px rgba(249, 115, 22, 0.4); }
|
||||||
.glow.c2 { box-shadow: 0 0 15px color-mix(in srgb, var(--consumer-2-color), transparent 60%); }
|
.glow.c3 { box-shadow: 0 0 15px rgba(6, 182, 212, 0.4); }
|
||||||
.glow.c3 { box-shadow: 0 0 15px color-mix(in srgb, var(--consumer-3-color), transparent 60%); }
|
|
||||||
|
|
||||||
.node-solar { top: 70px; left: 5px; }
|
.node-solar { top: 70px; left: 5px; }
|
||||||
.node-grid { top: 70px; left: 165px; }
|
.node-grid { top: 70px; left: 165px; }
|
||||||
|
|
@ -400,33 +292,33 @@ console.log(
|
||||||
svg { position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 1; pointer-events: none; }
|
svg { position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 1; pointer-events: none; }
|
||||||
|
|
||||||
.bg-path { fill: none; stroke-width: 6; transition: opacity 0.3s ease; }
|
.bg-path { fill: none; stroke-width: 6; transition: opacity 0.3s ease; }
|
||||||
.bg-solar { stroke: var(--pipe-solar-color); }
|
.bg-solar { stroke: var(--neon-yellow); }
|
||||||
.bg-grid { stroke: var(--pipe-grid-color); }
|
.bg-grid { stroke: var(--neon-blue); }
|
||||||
.bg-battery { stroke: var(--pipe-battery-color); }
|
.bg-battery { stroke: var(--neon-green); }
|
||||||
.bg-export { stroke: var(--export-color); }
|
.bg-export { stroke: var(--neon-red); }
|
||||||
.bg-c1 { stroke: var(--pipe-consumer-1-color); }
|
.bg-c1 { stroke: #a855f7; }
|
||||||
.bg-c2 { stroke: var(--pipe-consumer-2-color); }
|
.bg-c2 { stroke: #f97316; }
|
||||||
.bg-c3 { stroke: var(--pipe-consumer-3-color); }
|
.bg-c3 { stroke: #06b6d4; }
|
||||||
|
|
||||||
.flow-line {
|
.flow-line {
|
||||||
fill: none; stroke-width: var(--flow-stroke-width, 8px); stroke-linecap: round; stroke-dasharray: var(--flow-dasharray);
|
fill: none; stroke-width: var(--flow-stroke-width, 8px); stroke-linecap: round; stroke-dasharray: var(--flow-dasharray);
|
||||||
animation: dash linear infinite; opacity: 0; transition: opacity 0.5s;
|
animation: dash linear infinite; opacity: 0; transition: opacity 0.5s;
|
||||||
}
|
}
|
||||||
.flow-solar { stroke: var(--pipe-solar-color); }
|
.flow-solar { stroke: var(--neon-yellow); }
|
||||||
.flow-grid { stroke: var(--pipe-grid-color); }
|
.flow-grid { stroke: var(--neon-blue); }
|
||||||
.flow-battery { stroke: var(--pipe-battery-color); }
|
.flow-battery { stroke: var(--neon-green); }
|
||||||
.flow-export { stroke: var(--export-color); }
|
.flow-export { stroke: var(--neon-red); }
|
||||||
|
|
||||||
@keyframes dash { to { stroke-dashoffset: -1500; } }
|
@keyframes dash { to { stroke-dashoffset: -1500; } }
|
||||||
|
|
||||||
.flow-text {
|
.flow-text {
|
||||||
font-size: 10px; font-weight: bold; text-anchor: middle; fill: #fff; transition: opacity 0.3s ease;
|
font-size: 10px; font-weight: bold; text-anchor: middle; fill: #fff; filter: drop-shadow(0px 1px 2px rgba(0,0,0,0.8)); transition: opacity 0.3s ease;
|
||||||
}
|
}
|
||||||
.flow-text.no-shadow { filter: none; }
|
.flow-text.no-shadow { filter: none; }
|
||||||
.text-solar { fill: var(--pipe-solar-color); }
|
.text-solar { fill: var(--neon-yellow); }
|
||||||
.text-grid { fill: var(--pipe-grid-color); }
|
.text-grid { fill: var(--neon-blue); }
|
||||||
.text-export { fill: var(--export-color); }
|
.text-export { fill: var(--neon-red); }
|
||||||
.text-battery { fill: var(--pipe-battery-color); }
|
.text-battery { fill: var(--neon-green); }
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -434,37 +326,33 @@ console.log(
|
||||||
_renderIcon(type, val = 0, colorOverride = null) {
|
_renderIcon(type, val = 0, colorOverride = null) {
|
||||||
if (type === 'solar') {
|
if (type === 'solar') {
|
||||||
const animate = Math.round(val) > 0 ? 'spin-slow' : '';
|
const animate = Math.round(val) > 0 ? 'spin-slow' : '';
|
||||||
const color = colorOverride || 'var(--icon-solar-color)';
|
const color = colorOverride || 'var(--neon-yellow)';
|
||||||
return html`<svg class="icon-svg ${animate}" viewBox="0 0 24 24" fill="none" stroke="${color}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="5"></circle><line x1="12" y1="1" x2="12" y2="3"></line><line x1="12" y1="21" x2="12" y2="23"></line><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line><line x1="1" y1="12" x2="3" y2="12"></line><line x1="21" y1="12" x2="23" y2="12"></line><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line></svg>`;
|
return html`<svg class="icon-svg ${animate}" viewBox="0 0 24 24" fill="none" stroke="${color}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="5"></circle><line x1="12" y1="1" x2="12" y2="3"></line><line x1="12" y1="21" x2="12" y2="23"></line><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line><line x1="1" y1="12" x2="3" y2="12"></line><line x1="21" y1="12" x2="23" y2="12"></line><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line></svg>`;
|
||||||
}
|
}
|
||||||
if (type === 'grid') {
|
if (type === 'grid') {
|
||||||
const animate = Math.round(val) > 0 ? 'pulse' : '';
|
const animate = Math.round(val) > 0 ? 'pulse' : '';
|
||||||
const color = colorOverride || 'var(--icon-grid-color)';
|
const color = colorOverride || 'var(--neon-blue)';
|
||||||
return html`<svg class="icon-svg" viewBox="0 0 24 24" fill="none" stroke="${color}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2L12 22"></path><path d="M5 8L19 8"></path><path d="M4 14L20 14"></path><path d="M2 22L22 22"></path><circle class="${animate}" cx="12" cy="4" r="4" fill="${color}" stroke="none"></circle></svg>`;
|
return html`<svg class="icon-svg" viewBox="0 0 24 24" fill="none" stroke="${color}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2L12 22"></path><path d="M5 8L19 8"></path><path d="M4 14L20 14"></path><path d="M2 22L22 22"></path><circle class="${animate}" cx="12" cy="4" r="4" fill="${color}" stroke="none"></circle></svg>`;
|
||||||
}
|
}
|
||||||
if (type === 'battery') {
|
if (type === 'battery') {
|
||||||
const soc = Math.min(Math.max(val, 0), 100) / 100;
|
const soc = Math.min(Math.max(val, 0), 100) / 100;
|
||||||
const rectHeight = 14 * soc;
|
const rectHeight = 14 * soc;
|
||||||
const rectY = 18 - rectHeight;
|
const rectY = 18 - rectHeight;
|
||||||
const strokeColor = colorOverride || 'var(--icon-battery-color)';
|
const rectColor = soc > 0.2 ? 'var(--neon-green)' : 'var(--neon-red)';
|
||||||
const rectColor = soc > 0.2 ? strokeColor : 'var(--neon-red)';
|
return html`<svg class="icon-svg" viewBox="0 0 24 24" fill="none" stroke="var(--neon-green)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="6" y="4" width="12" height="16" rx="2" ry="2"></rect><line x1="10" y1="2" x2="14" y2="2"></line><rect x="7" y="${rectY}" width="10" height="${rectHeight}" fill="${rectColor}" stroke="none"></rect></svg>`;
|
||||||
return html`<svg class="icon-svg" viewBox="0 0 24 24" fill="none" stroke="${strokeColor}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="6" y="4" width="12" height="16" rx="2" ry="2"></rect><line x1="10" y1="2" x2="14" y2="2"></line><rect x="7" y="${rectY}" width="10" height="${rectHeight}" fill="${rectColor}" stroke="none"></rect></svg>`;
|
|
||||||
}
|
}
|
||||||
if (type === 'house') {
|
if (type === 'house') {
|
||||||
const strokeColor = colorOverride || 'var(--icon-house-color)';
|
const strokeColor = colorOverride || 'var(--neon-pink)';
|
||||||
return html`<svg class="icon-svg" viewBox="0 0 24 24" fill="none" stroke="${strokeColor}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path><polyline points="9 22 9 12 15 12 15 22"></polyline></svg>`;
|
return html`<svg class="icon-svg" viewBox="0 0 24 24" fill="none" stroke="${strokeColor}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path><polyline points="9 22 9 12 15 12 15 22"></polyline></svg>`;
|
||||||
}
|
}
|
||||||
if (type === 'car') {
|
if (type === 'car') {
|
||||||
const c = colorOverride || 'var(--icon-consumer-1-color)';
|
return html`<svg class="icon-svg" viewBox="0 0 24 24" fill="none" stroke="#a855f7" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M19 17h2c.6 0 1-.4 1-1v-3c0-.9-.7-1.7-1.5-1.9C18.7 10.6 16 10 16 10s-1.3-1.4-2.2-2.3c-.5-.4-1.1-.7-1.8-.7H5c-.6 0-1.1.4-1.4.9l-1.4 2.9A3.7 3.7 0 0 0 2 12v4c0 .6.4 1 1 1h2"></path><circle cx="7" cy="17" r="2"></circle><circle cx="17" cy="17" r="2"></circle><path d="M14 17h-5"></path></svg>`;
|
||||||
return html`<svg class="icon-svg" viewBox="0 0 24 24" fill="none" stroke="${c}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M19 17h2c.6 0 1-.4 1-1v-3c0-.9-.7-1.7-1.5-1.9C18.7 10.6 16 10 16 10s-1.3-1.4-2.2-2.3c-.5-.4-1.1-.7-1.8-.7H5c-.6 0-1.1.4-1.4.9l-1.4 2.9A3.7 3.7 0 0 0 2 12v4c0 .6.4 1 1 1h2"></path><circle cx="7" cy="17" r="2"></circle><circle cx="17" cy="17" r="2"></circle><path d="M14 17h-5"></path></svg>`;
|
|
||||||
}
|
}
|
||||||
if (type === 'heater') {
|
if (type === 'heater') {
|
||||||
const c = colorOverride || 'var(--icon-consumer-2-color)';
|
return html`<svg class="icon-svg" viewBox="0 0 24 24" fill="none" stroke="#f97316" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20a4 4 0 0 0 4-4V8a4 4 0 0 0-8 0v8a4 4 0 0 0 4 4z"></path><path class="float" style="animation-delay: 0s;" d="M8 4c0-1.5 1-2 2-2s2 .5 2 2"></path><path class="float" style="animation-delay: 0.5s;" d="M14 4c0-1.5 1-2 2-2s2 .5 2 2"></path></svg>`;
|
||||||
return html`<svg class="icon-svg" viewBox="0 0 24 24" fill="none" stroke="${c}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20a4 4 0 0 0 4-4V8a4 4 0 0 0-8 0v8a4 4 0 0 0 4 4z"></path><path class="float" style="animation-delay: 0s;" d="M8 4c0-1.5 1-2 2-2s2 .5 2 2"></path><path class="float" style="animation-delay: 0.5s;" d="M14 4c0-1.5 1-2 2-2s2 .5 2 2"></path></svg>`;
|
|
||||||
}
|
}
|
||||||
if (type === 'pool') {
|
if (type === 'pool') {
|
||||||
const c = colorOverride || 'var(--icon-consumer-3-color)';
|
return html`<svg class="icon-svg" viewBox="0 0 24 24" fill="none" stroke="#06b6d4" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M2 12h20"></path><path class="float" d="M2 16c2.5 0 2.5-2 5-2s2.5 2 5 2 2.5-2 5-2 2.5 2 5 2"></path><path d="M12 2v6"></path><path d="M9 5h6"></path></svg>`;
|
||||||
return html`<svg class="icon-svg" viewBox="0 0 24 24" fill="none" stroke="${c}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M2 12h20"></path><path class="float" d="M2 16c2.5 0 2.5-2 5-2s2.5 2 5 2 2.5-2 5-2 2.5 2 5 2"></path><path d="M12 2v6"></path><path d="M9 5h6"></path></svg>`;
|
|
||||||
}
|
}
|
||||||
return html``;
|
return html``;
|
||||||
}
|
}
|
||||||
|
|
@ -477,16 +365,6 @@ console.log(
|
||||||
return Math.round(val) + " W";
|
return Math.round(val) + " W";
|
||||||
}
|
}
|
||||||
|
|
||||||
_getConsumerColor(index) {
|
|
||||||
const style = getComputedStyle(this);
|
|
||||||
return style.getPropertyValue(`--consumer-${index}-color`).trim() || ['#a855f7', '#f97316', '#06b6d4'][index - 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
_getConsumerPipeColor(index) {
|
|
||||||
const style = getComputedStyle(this);
|
|
||||||
return style.getPropertyValue(`--pipe-consumer-${index}-color`).trim() || this._getConsumerColor(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- DOM NODE SVG GENERATOR ---
|
// --- DOM NODE SVG GENERATOR ---
|
||||||
_renderSVGPath(d, color) {
|
_renderSVGPath(d, color) {
|
||||||
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
||||||
|
|
@ -540,32 +418,21 @@ console.log(
|
||||||
const state = this.hass.states[entity];
|
const state = this.hass.states[entity];
|
||||||
return state ? parseFloat(state.state) || 0 : 0;
|
return state ? parseFloat(state.state) || 0 : 0;
|
||||||
};
|
};
|
||||||
const getValKw = (entity, isKw) => {
|
|
||||||
return getVal(entity) * (isKw ? 1000 : 1);
|
|
||||||
};
|
|
||||||
|
|
||||||
const solar = entities.solar ? Math.max(0, getVal(entities.solar)) : 0;
|
const solar = entities.solar ? Math.max(0, getVal(entities.solar)) : 0;
|
||||||
const hasGridCombined = !!(entities.grid_combined && entities.grid_combined !== "");
|
const gridMain = entities.grid ? getVal(entities.grid) : 0;
|
||||||
const gridCombinedVal = hasGridCombined ? getVal(entities.grid_combined) : 0;
|
|
||||||
const gridMain = hasGridCombined ? gridCombinedVal : (entities.grid ? getVal(entities.grid) : 0);
|
|
||||||
const gridExportSensor = entities.grid_export ? getVal(entities.grid_export) : 0;
|
const gridExportSensor = entities.grid_export ? getVal(entities.grid_export) : 0;
|
||||||
let battery = entities.battery ? getVal(entities.battery) : 0;
|
let battery = entities.battery ? getVal(entities.battery) : 0;
|
||||||
if (this.config.invert_battery) {
|
if (this.config.invert_battery) {
|
||||||
battery *= -1;
|
battery *= -1;
|
||||||
}
|
}
|
||||||
let c1Val = entities.consumer_1 ? getValKw(entities.consumer_1, this.config.consumer_1_unit_kw === true) : 0; // EV Value
|
const c1Val = entities.consumer_1 ? getVal(entities.consumer_1) : 0; // EV Value
|
||||||
if (this.config.invert_consumer_1) { c1Val *= -1; }
|
|
||||||
c1Val = Math.abs(c1Val);
|
|
||||||
|
|
||||||
// 2. Logic Calculation
|
// 2. Logic Calculation
|
||||||
let gridImport = 0;
|
let gridImport = 0;
|
||||||
let gridExport = 0;
|
let gridExport = 0;
|
||||||
|
|
||||||
if (hasGridCombined) {
|
if (entities.grid_export && entities.grid_export !== "") {
|
||||||
// COMBINED SENSOR: positive = import, negative = export
|
|
||||||
gridImport = gridCombinedVal > 0 ? gridCombinedVal : 0;
|
|
||||||
gridExport = gridCombinedVal < 0 ? Math.abs(gridCombinedVal) : 0;
|
|
||||||
} else if (entities.grid_export && entities.grid_export !== "") {
|
|
||||||
gridImport = gridMain > 0 ? gridMain : 0;
|
gridImport = gridMain > 0 ? gridMain : 0;
|
||||||
gridExport = Math.abs(gridExportSensor);
|
gridExport = Math.abs(gridExportSensor);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -573,22 +440,13 @@ console.log(
|
||||||
gridExport = gridMain < 0 ? Math.abs(gridMain) : 0;
|
gridExport = gridMain < 0 ? Math.abs(gridMain) : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for separate battery charge/discharge sensors
|
const batteryCharge = battery > 0 ? battery : 0;
|
||||||
const hasBattChargeSensor = !!(entities.battery_charge && entities.battery_charge !== "");
|
const batteryDischarge = battery < 0 ? Math.abs(battery) : 0;
|
||||||
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 solarToBatt = 0;
|
||||||
let gridToBatt = 0;
|
let gridToBatt = 0;
|
||||||
|
|
||||||
if (batteryCharge > 0) {
|
if (batteryCharge > 0) {
|
||||||
const hasGridToBattSensor = !!(entities.grid_to_battery && entities.grid_to_battery !== "");
|
|
||||||
if (hasGridToBattSensor) {
|
|
||||||
gridToBatt = Math.abs(getVal(entities.grid_to_battery));
|
|
||||||
solarToBatt = Math.max(0, batteryCharge - gridToBatt);
|
|
||||||
} else {
|
|
||||||
if (solar >= batteryCharge) {
|
if (solar >= batteryCharge) {
|
||||||
solarToBatt = batteryCharge;
|
solarToBatt = batteryCharge;
|
||||||
gridToBatt = 0;
|
gridToBatt = 0;
|
||||||
|
|
@ -597,7 +455,6 @@ console.log(
|
||||||
gridToBatt = batteryCharge - solar;
|
gridToBatt = batteryCharge - solar;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const solarTotalToCons = Math.max(0, solar - solarToBatt - gridExport);
|
const solarTotalToCons = Math.max(0, solar - solarToBatt - gridExport);
|
||||||
const gridTotalToCons = Math.max(0, gridImport - gridToBatt);
|
const gridTotalToCons = Math.max(0, gridImport - gridToBatt);
|
||||||
|
|
@ -663,18 +520,18 @@ console.log(
|
||||||
currentX += width;
|
currentX += width;
|
||||||
}
|
}
|
||||||
|
|
||||||
addSegment(srcBattery, 'var(--neon-green)', 'battery', 'battery', entities.battery);
|
addSegment(srcBattery, 'var(--neon-yellow)', 'battery', 'battery', entities.battery);
|
||||||
addSegment(srcSolar, 'var(--neon-yellow)', 'solar', 'solar', entities.solar);
|
addSegment(srcSolar, 'var(--neon-green)', 'solar', 'solar', entities.solar);
|
||||||
addSegment(srcGrid, 'var(--neon-blue)', 'grid', 'grid', entities.grid_combined || entities.grid);
|
addSegment(srcGrid, 'var(--grid-grey)', 'grid', 'grid', entities.grid);
|
||||||
|
|
||||||
// --- GENERATE TOP BRACKETS (Based on Bar Segments) ---
|
// --- GENERATE TOP BRACKETS (Based on Bar Segments) ---
|
||||||
const topBrackets = barSegments.map(s => {
|
const topBrackets = barSegments.map(s => {
|
||||||
const path = this._createBracketPath(s.startPx, s.widthPx, 'down');
|
const path = this._createBracketPath(s.startPx, s.widthPx, 'down');
|
||||||
let icon = '';
|
let icon = '';
|
||||||
let iconColor = '';
|
let iconColor = '';
|
||||||
if (s.type === 'solar') { icon = 'mdi:weather-sunny'; iconColor = 'var(--icon-solar-color)'; }
|
if (s.type === 'solar') { icon = 'mdi:weather-sunny'; iconColor = 'var(--neon-green)'; }
|
||||||
if (s.type === 'grid') { icon = 'mdi:transmission-tower'; iconColor = 'var(--icon-grid-color)'; }
|
if (s.type === 'grid') { icon = 'mdi:transmission-tower'; iconColor = 'var(--grid-grey)'; }
|
||||||
if (s.type === 'battery') { icon = 'mdi:battery-high'; iconColor = 'var(--icon-battery-color)'; }
|
if (s.type === 'battery') { icon = 'mdi:battery-high'; iconColor = 'var(--neon-yellow)'; }
|
||||||
|
|
||||||
return { path, width: s.widthPx, center: s.startPx + (s.widthPx / 2), icon, iconColor, val: s.val, entityId: s.entityId };
|
return { path, width: s.widthPx, center: s.startPx + (s.widthPx / 2), icon, iconColor, val: s.val, entityId: s.entityId };
|
||||||
});
|
});
|
||||||
|
|
@ -692,10 +549,10 @@ console.log(
|
||||||
let icon = '';
|
let icon = '';
|
||||||
let iconColor = '';
|
let iconColor = '';
|
||||||
|
|
||||||
if (type === 'house') { icon = 'mdi:home'; iconColor = 'var(--icon-house-color)'; }
|
if (type === 'house') { icon = 'mdi:home'; iconColor = 'var(--primary-text-color)'; }
|
||||||
if (type === 'car') { icon = 'mdi:car-electric'; iconColor = 'var(--icon-consumer-1-color)'; }
|
if (type === 'car') { icon = 'mdi:car-electric'; iconColor = '#a855f7'; }
|
||||||
if (type === 'export') { icon = 'mdi:arrow-right-box'; iconColor = 'var(--export-color)'; }
|
if (type === 'export') { icon = 'mdi:arrow-right-box'; iconColor = 'var(--export-purple)'; }
|
||||||
if (type === 'battery') { icon = 'mdi:battery-charging-high'; iconColor = 'var(--icon-battery-color)'; }
|
if (type === 'battery') { icon = 'mdi:battery-charging-high'; iconColor = 'var(--neon-green)'; }
|
||||||
|
|
||||||
const path = this._createBracketPath(bottomX, width, 'up');
|
const path = this._createBracketPath(bottomX, width, 'up');
|
||||||
bottomBrackets.push({
|
bottomBrackets.push({
|
||||||
|
|
@ -712,7 +569,7 @@ console.log(
|
||||||
|
|
||||||
addBottomBracket(destHouse, 'house', entities.house);
|
addBottomBracket(destHouse, 'house', entities.house);
|
||||||
addBottomBracket(destEV, 'car', entities.consumer_1);
|
addBottomBracket(destEV, 'car', entities.consumer_1);
|
||||||
addBottomBracket(destExport, 'export', entities.grid_combined || entities.grid_export || entities.grid);
|
addBottomBracket(destExport, 'export', entities.grid_export || entities.grid);
|
||||||
addBottomBracket(batteryCharge, 'battery', entities.battery);
|
addBottomBracket(batteryCharge, 'battery', entities.battery);
|
||||||
|
|
||||||
// Note: If there is Battery Charging happening, bottomX will not reach fullWidth.
|
// Note: If there is Battery Charging happening, bottomX will not reach fullWidth.
|
||||||
|
|
@ -738,19 +595,14 @@ console.log(
|
||||||
|
|
||||||
<!-- MAIN BAR -->
|
<!-- MAIN BAR -->
|
||||||
<div class="compact-bar-wrapper">
|
<div class="compact-bar-wrapper">
|
||||||
${barSegments.map(s => {
|
${barSegments.map(s => html`
|
||||||
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`
|
|
||||||
<div class="bar-segment"
|
<div class="bar-segment"
|
||||||
style="width: ${s.widthPct}%; background: ${s.color}; color: ${textColor}; cursor: ${s.entityId ? 'pointer' : 'default'};"
|
style="width: ${s.widthPct}%; background: ${s.color}; color: ${s.color === 'var(--export-purple)' ? 'white' : 'black'}; cursor: ${s.entityId ? 'pointer' : 'default'};"
|
||||||
title="${this._formatPower(s.val)}"
|
title="${this._formatPower(s.val)}"
|
||||||
@click=${() => s.entityId && this._handleClick(s.entityId)}>
|
@click=${() => s.entityId && this._handleClick(s.entityId)}>
|
||||||
${s.widthPx > 35 ? this._formatPower(s.val) : ''}
|
${s.widthPx > 35 ? this._formatPower(s.val) : ''}
|
||||||
</div>
|
</div>
|
||||||
`})}
|
`)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- BOTTOM BRACKETS -->
|
<!-- BOTTOM BRACKETS -->
|
||||||
|
|
@ -799,7 +651,7 @@ console.log(
|
||||||
|
|
||||||
// CUSTOM LABELS
|
// CUSTOM LABELS
|
||||||
const labelSolarText = this.config.solar_label || this._localize('card.label_solar');
|
const labelSolarText = this.config.solar_label || this._localize('card.label_solar');
|
||||||
const labelGridText = this.config.grid_label || this._localize('card.label_grid');
|
const labelGridText = this.config.grid_label || this._localize('card.label_import');
|
||||||
const labelBatteryText = this.config.battery_label || (entities.battery && this.hass.states[entities.battery] && this.hass.states[entities.battery].state > 0 ? '+' : '-') + " " + this._localize('card.label_battery');
|
const labelBatteryText = this.config.battery_label || (entities.battery && this.hass.states[entities.battery] && this.hass.states[entities.battery].state > 0 ? '+' : '-') + " " + this._localize('card.label_battery');
|
||||||
const labelHouseText = this.config.house_label || this._localize('card.label_house');
|
const labelHouseText = this.config.house_label || this._localize('card.label_house');
|
||||||
|
|
||||||
|
|
@ -808,25 +660,9 @@ console.log(
|
||||||
const iconGrid = this.config.grid_icon;
|
const iconGrid = this.config.grid_icon;
|
||||||
const iconBattery = this.config.battery_icon;
|
const iconBattery = this.config.battery_icon;
|
||||||
|
|
||||||
// SECONDARY SENSORS (display only)
|
|
||||||
const hasSecondarySolar = !!(entities.secondary_solar && entities.secondary_solar !== "");
|
|
||||||
const hasSecondaryGrid = !!(entities.secondary_grid && entities.secondary_grid !== "");
|
|
||||||
const hasSecondaryBattery = !!(entities.secondary_battery && entities.secondary_battery !== "");
|
|
||||||
|
|
||||||
const getSecondaryVal = (entity) => {
|
|
||||||
if (!entity) return '';
|
|
||||||
const state = this.hass.states[entity];
|
|
||||||
if (!state) return '';
|
|
||||||
const val = parseFloat(state.state);
|
|
||||||
if (isNaN(val)) return state.state + (state.attributes.unit_of_measurement ? ' ' + state.attributes.unit_of_measurement : '');
|
|
||||||
const unit = state.attributes.unit_of_measurement || '';
|
|
||||||
if (unit === 'W') { return this._formatPower(val); } if (unit === 'Wh') { if (Math.abs(val) >= 1000) return (val / 1000).toFixed(2) + ' kWh'; return Math.round(val) + ' Wh'; } if (unit === 'kWh' || unit === 'kW') { return val.toFixed(1) + ' ' + unit; } if (unit.includes('EUR') || unit.includes('ct') || unit.includes('€')) { return val.toFixed(2) + ' ' + unit; } return val.toFixed(1) + (unit ? ' ' + unit : '');
|
|
||||||
};
|
|
||||||
|
|
||||||
// Determine existence of main entities
|
// Determine existence of main entities
|
||||||
const hasSolar = !!(entities.solar && entities.solar !== "");
|
const hasSolar = !!(entities.solar && entities.solar !== "");
|
||||||
const hasGridCombined = !!(entities.grid_combined && entities.grid_combined !== "");
|
const hasGrid = !!(entities.grid && entities.grid !== "");
|
||||||
const hasGrid = !!(entities.grid && entities.grid !== "") || hasGridCombined;
|
|
||||||
const hasBattery = !!(entities.battery && entities.battery !== "");
|
const hasBattery = !!(entities.battery && entities.battery !== "");
|
||||||
|
|
||||||
const styleSolar = hasSolar ? '' : 'display: none;';
|
const styleSolar = hasSolar ? '' : 'display: none;';
|
||||||
|
|
@ -838,38 +674,26 @@ console.log(
|
||||||
const textClass = showNeonGlow ? 'flow-text' : 'flow-text no-shadow';
|
const textClass = showNeonGlow ? 'flow-text' : 'flow-text no-shadow';
|
||||||
|
|
||||||
// Custom Labels for Consumers
|
// Custom Labels for Consumers
|
||||||
const labelC1 = this.config.consumer_1_label || this._localize('card.label_car');
|
const labelC1 = this.config.consumer_1_label || "E-Auto";
|
||||||
const labelC2 = this.config.consumer_2_label || this._localize('card.label_heater');
|
const labelC2 = this.config.consumer_2_label || "Heizung";
|
||||||
const labelC3 = this.config.consumer_3_label || this._localize('card.label_pool');
|
const labelC3 = this.config.consumer_3_label || "Pool";
|
||||||
|
|
||||||
const getVal = (entity) => {
|
const getVal = (entity) => {
|
||||||
const state = this.hass.states[entity];
|
const state = this.hass.states[entity];
|
||||||
return state ? parseFloat(state.state) || 0 : 0;
|
return state ? parseFloat(state.state) || 0 : 0;
|
||||||
};
|
};
|
||||||
const getValKw = (entity, isKw) => {
|
|
||||||
return getVal(entity) * (isKw ? 1000 : 1);
|
|
||||||
};
|
|
||||||
|
|
||||||
let c1Val = entities.consumer_1 ? getValKw(entities.consumer_1, this.config.consumer_1_unit_kw === true) : 0;
|
const c1Val = entities.consumer_1 ? getVal(entities.consumer_1) : 0;
|
||||||
if (this.config.invert_consumer_1) { c1Val *= -1; }
|
const c2Val = entities.consumer_2 ? getVal(entities.consumer_2) : 0;
|
||||||
c1Val = Math.abs(c1Val);
|
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 alwaysShowConsumer = this.config.show_consumer_always === true;
|
const showC1 = (entities.consumer_1 && Math.round(c1Val) > 0);
|
||||||
const showC1 = (entities.consumer_1 && (alwaysShowConsumer || Math.round(c1Val) > 0));
|
const showC2 = (entities.consumer_2 && Math.round(c2Val) > 0);
|
||||||
const showC2 = (entities.consumer_2 && (alwaysShowConsumer || Math.round(c2Val) > 0));
|
const showC3 = (entities.consumer_3 && Math.round(c3Val) > 0);
|
||||||
const showC3 = (entities.consumer_3 && (alwaysShowConsumer || Math.round(c3Val) > 0));
|
|
||||||
const anyBottomVisible = showC1 || showC2 || showC3;
|
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 solar = hasSolar ? getVal(entities.solar) : 0;
|
||||||
const gridCombinedVal = hasGridCombined ? getVal(entities.grid_combined) : 0;
|
const gridMain = hasGrid ? getVal(entities.grid) : 0;
|
||||||
const gridMain = hasGridCombined ? gridCombinedVal : (hasGrid ? getVal(entities.grid) : 0);
|
|
||||||
const gridExpSensor = (hasGrid && entities.grid_export) ? getVal(entities.grid_export) : 0;
|
const gridExpSensor = (hasGrid && entities.grid_export) ? getVal(entities.grid_export) : 0;
|
||||||
let battery = hasBattery ? getVal(entities.battery) : 0;
|
let battery = hasBattery ? getVal(entities.battery) : 0;
|
||||||
if (this.config.invert_battery) {
|
if (this.config.invert_battery) {
|
||||||
|
|
@ -883,11 +707,7 @@ console.log(
|
||||||
let gridExport = 0;
|
let gridExport = 0;
|
||||||
|
|
||||||
if (hasGrid) {
|
if (hasGrid) {
|
||||||
if (hasGridCombined) {
|
if (entities.grid_export && entities.grid_export !== "") {
|
||||||
// COMBINED SENSOR: positive = import, negative = export
|
|
||||||
gridImport = gridCombinedVal > 0 ? gridCombinedVal : 0;
|
|
||||||
gridExport = gridCombinedVal < 0 ? Math.abs(gridCombinedVal) : 0;
|
|
||||||
} else if (entities.grid_export && entities.grid_export !== "") {
|
|
||||||
gridImport = gridMain > 0 ? gridMain : 0;
|
gridImport = gridMain > 0 ? gridMain : 0;
|
||||||
gridExport = Math.abs(gridExpSensor);
|
gridExport = Math.abs(gridExpSensor);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -896,24 +716,13 @@ console.log(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for separate battery charge/discharge sensors
|
const batteryCharge = battery > 0 ? battery : 0;
|
||||||
const hasBattChargeSensor = !!(entities.battery_charge && entities.battery_charge !== "");
|
const batteryDischarge = battery < 0 ? Math.abs(battery) : 0;
|
||||||
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 solarToBatt = 0;
|
||||||
let gridToBatt = 0;
|
let gridToBatt = 0;
|
||||||
|
|
||||||
if (hasBattery && batteryCharge > 0) {
|
if (hasBattery && batteryCharge > 0) {
|
||||||
const hasGridToBattSensor = !!(entities.grid_to_battery && entities.grid_to_battery !== "");
|
|
||||||
if (hasGridToBattSensor) {
|
|
||||||
// Use dedicated grid-to-battery sensor
|
|
||||||
gridToBatt = Math.abs(getVal(entities.grid_to_battery));
|
|
||||||
solarToBatt = Math.max(0, batteryCharge - gridToBatt);
|
|
||||||
} else {
|
|
||||||
// Calculate: solar prioritized
|
|
||||||
if (solarVal >= batteryCharge) {
|
if (solarVal >= batteryCharge) {
|
||||||
solarToBatt = batteryCharge;
|
solarToBatt = batteryCharge;
|
||||||
gridToBatt = 0;
|
gridToBatt = 0;
|
||||||
|
|
@ -922,15 +731,11 @@ console.log(
|
||||||
gridToBatt = batteryCharge - solarVal;
|
gridToBatt = batteryCharge - solarVal;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const solarToHouse = Math.max(0, solarVal - solarToBatt - gridExport);
|
const solarToHouse = Math.max(0, solarVal - solarToBatt - gridExport);
|
||||||
const gridToHouse = Math.max(0, gridImport - gridToBatt);
|
const gridToHouse = Math.max(0, gridImport - gridToBatt);
|
||||||
const house = solarToHouse + gridToHouse + batteryDischarge;
|
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 isTopArcActive = (solarToBatt > 0);
|
||||||
const topShift = (isTopArcActive || (!hideInactive && hasSolar && hasBattery)) ? 0 : 50;
|
const topShift = (isTopArcActive || (!hideInactive && hasSolar && hasBattery)) ? 0 : 50;
|
||||||
let baseHeight = anyBottomVisible ? 480 : 340;
|
let baseHeight = anyBottomVisible ? 480 : 340;
|
||||||
|
|
@ -946,8 +751,6 @@ console.log(
|
||||||
if (scale > 1.5) scale = 1.5;
|
if (scale > 1.5) scale = 1.5;
|
||||||
|
|
||||||
const finalCardHeightPx = contentHeight * scale;
|
const finalCardHeightPx = contentHeight * scale;
|
||||||
const visualWidth = 420 * scale;
|
|
||||||
const centerMarginLeft = Math.max(0, (availableWidth - visualWidth) / 2);
|
|
||||||
|
|
||||||
let houseGradientVal = '';
|
let houseGradientVal = '';
|
||||||
let houseTextCol = useColoredValues ? 'var(--neon-pink)' : '';
|
let houseTextCol = useColoredValues ? 'var(--neon-pink)' : '';
|
||||||
|
|
@ -974,11 +777,11 @@ console.log(
|
||||||
let stops = [];
|
let stops = [];
|
||||||
let current = 0;
|
let current = 0;
|
||||||
if (pctSolar > 0) { stops.push(`var(--neon-yellow) ${current}% ${current + pctSolar}%`); current += pctSolar; }
|
if (pctSolar > 0) { stops.push(`var(--neon-yellow) ${current}% ${current + pctSolar}%`); current += pctSolar; }
|
||||||
if (pctBatt > 0) { stops.push(`var(--neon-green) ${current}% ${current + pctBatt}%`); current += pctBatt; }
|
|
||||||
if (pctGrid > 0) { stops.push(`var(--neon-blue) ${current}% ${current + pctGrid}%`); current += pctGrid; }
|
if (pctGrid > 0) { stops.push(`var(--neon-blue) ${current}% ${current + pctGrid}%`); current += pctGrid; }
|
||||||
|
if (pctBatt > 0) { stops.push(`var(--neon-green) ${current}% ${current + pctBatt}%`); current += pctBatt; }
|
||||||
if (current < 99.9) { stops.push(`var(--neon-pink) ${current}% 100%`); }
|
if (current < 99.9) { stops.push(`var(--neon-pink) ${current}% 100%`); }
|
||||||
|
|
||||||
houseGradientVal = `conic-gradient(from 330deg, ${stops.join(', ')})`;
|
houseGradientVal = `conic-gradient(${stops.join(', ')})`;
|
||||||
|
|
||||||
if (useColoredValues) {
|
if (useColoredValues) {
|
||||||
const maxVal = Math.max(solarToHouse, gridToHouse, batteryDischarge);
|
const maxVal = Math.max(solarToHouse, gridToHouse, batteryDischarge);
|
||||||
|
|
@ -1009,73 +812,25 @@ console.log(
|
||||||
const houseBubbleStyle = `${showDonut ? `--house-gradient: ${houseGradientVal};` : ''} ${houseTintStyle} ${houseGlowStyle}`;
|
const houseBubbleStyle = `${showDonut ? `--house-gradient: ${houseGradientVal};` : ''} ${houseTintStyle} ${houseGlowStyle}`;
|
||||||
|
|
||||||
const isSolarActive = Math.round(solarVal) > 0;
|
const isSolarActive = Math.round(solarVal) > 0;
|
||||||
const isGridActive = Math.round(gridImport) > 0 || Math.round(gridExport) > 0;
|
const isGridActive = Math.round(gridImport) > 0;
|
||||||
const isGridExporting = Math.round(gridExport) > 0 && Math.round(gridImport) === 0;
|
|
||||||
|
|
||||||
// --- Grid Donut Gradient ---
|
const solarColor = isSolarActive ? 'var(--neon-yellow)' : 'var(--secondary-text-color)';
|
||||||
let gridGradientVal = '';
|
const gridColor = isGridActive ? 'var(--neon-blue)' : 'var(--secondary-text-color)';
|
||||||
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(--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) => {
|
const getAnimStyle = (val) => {
|
||||||
if (val <= 1) return "opacity: 0;";
|
if (val <= 1) return "opacity: 0;";
|
||||||
|
const userMinDuration = 7;
|
||||||
|
const userMaxDuration = 11;
|
||||||
|
const userFactor = 20000;
|
||||||
|
let duration = userFactor / val;
|
||||||
|
duration = Math.max(userMinDuration, Math.min(userMaxDuration, duration));
|
||||||
|
|
||||||
// --- Dynamic speed based on power ---
|
// Adjust speed for dashed line (Factor to slow down: 5x)
|
||||||
// Higher power = faster animation (shorter duration)
|
if (showDashedLine) {
|
||||||
// Range: 2s (very fast, ~5000W+) to 12s (slow, ~50W)
|
duration = duration * 5;
|
||||||
const minDuration = 4;
|
|
||||||
const maxDuration = 12;
|
|
||||||
const factor = 12000;
|
|
||||||
let duration = factor / val;
|
|
||||||
duration = Math.max(minDuration, Math.min(maxDuration, duration));
|
|
||||||
|
|
||||||
// --- Dynamic particle density based on power ---
|
|
||||||
// Higher power = more/denser particles (shorter gap)
|
|
||||||
// Lower power = fewer/sparse particles (longer gap)
|
|
||||||
let dashSize, gapSize;
|
|
||||||
if (showTail) {
|
|
||||||
// Comet tail: vary tail length with power
|
|
||||||
dashSize = Math.round(15 + (val / 200) * 25); // 15-40
|
|
||||||
dashSize = Math.min(dashSize, 40);
|
|
||||||
gapSize = Math.round(380 - (val / 200) * 200); // 380-180
|
|
||||||
gapSize = Math.max(gapSize, 180);
|
|
||||||
} else if (showDashedLine) {
|
|
||||||
// Dashed line: vary dash density
|
|
||||||
dashSize = Math.round(8 + (val / 500) * 10); // 8-18
|
|
||||||
dashSize = Math.min(dashSize, 18);
|
|
||||||
gapSize = Math.round(18 - (val / 1000) * 10); // 18-8
|
|
||||||
gapSize = Math.max(gapSize, 8);
|
|
||||||
duration = duration * 5; // Dashed lines are slower
|
|
||||||
} else {
|
|
||||||
// Default dots: vary dot count/density
|
|
||||||
dashSize = 0; // stays as dots
|
|
||||||
gapSize = Math.round(380 - (val / 200) * 250); // 380-130
|
|
||||||
gapSize = Math.max(gapSize, 130);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const dynamicDash = `${dashSize} ${gapSize}`;
|
return `opacity: 1; animation-duration: ${duration}s;`;
|
||||||
|
|
||||||
return `opacity: 1; animation-duration: ${duration}s; stroke-dasharray: ${dynamicDash};`;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const getPipeStyle = (val) => {
|
const getPipeStyle = (val) => {
|
||||||
|
|
@ -1105,51 +860,38 @@ console.log(
|
||||||
return html`<div class="sub">${text}</div>`;
|
return html`<div class="sub">${text}</div>`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderSecondaryOrLabel = (labelText, showLabel, secondaryEntity, hasSecondary) => {
|
|
||||||
if (hasSecondary) {
|
|
||||||
const secVal = getSecondaryVal(secondaryEntity);
|
|
||||||
return html`<div class="sub secondary-val">${secVal}</div>`;
|
|
||||||
}
|
|
||||||
return renderLabel(labelText, showLabel);
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderMainIcon = (type, val, customIcon, color = null) => {
|
const renderMainIcon = (type, val, customIcon, color = null) => {
|
||||||
if (customIcon) {
|
if (customIcon) {
|
||||||
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);' : ''))));
|
const style = color ? `color: ${color};` : (type === 'solar' ? 'color: var(--neon-yellow);' : (type === 'grid' ? 'color: var(--neon-blue);' : (type === 'battery' ? 'color: var(--neon-green);' : '')));
|
||||||
return html`<ha-icon icon="${customIcon}" class="icon-custom" style="${style}"></ha-icon>`;
|
return html`<ha-icon icon="${customIcon}" class="icon-custom" style="${style}"></ha-icon>`;
|
||||||
}
|
}
|
||||||
return this._renderIcon(type, val, color);
|
return this._renderIcon(type, val, color);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getCustomClass = (icon) => icon ? 'has-custom-icon' : '';
|
||||||
|
|
||||||
const renderConsumer = (isVisible, cssClass, configKey, label, iconType, val, hexColor) => {
|
const renderConsumer = (isVisible, cssClass, configKey, label, iconType, val, hexColor) => {
|
||||||
if (!isVisible) return html``;
|
if (!isVisible) return html``;
|
||||||
|
|
||||||
const customIcon = this.config[`${configKey}_icon`];
|
const customIcon = this.config[`${configKey}_icon`];
|
||||||
let iconContent;
|
let iconContent;
|
||||||
|
|
||||||
const iconColorVar = `var(--icon-${configKey.replace(/_/g, '-')}-color)`;
|
const isCustom = !hideConsumerIcons && !!customIcon;
|
||||||
|
const dynamicClass = isCustom ? 'has-custom-icon' : '';
|
||||||
|
|
||||||
if (hideConsumerIcons) {
|
if (hideConsumerIcons) {
|
||||||
iconContent = html``;
|
iconContent = html``;
|
||||||
} else if (customIcon) {
|
} else if (customIcon) {
|
||||||
iconContent = html`<ha-icon icon="${customIcon}" class="icon-custom" style="color: ${iconColorVar};"></ha-icon>`;
|
iconContent = html`<ha-icon icon="${customIcon}" class="icon-custom" style="color: ${hexColor};"></ha-icon>`;
|
||||||
} else {
|
} else {
|
||||||
iconContent = this._renderIcon(iconType, val);
|
iconContent = this._renderIcon(iconType, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
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`
|
return html`
|
||||||
<div class="bubble ${cssClass} ${cssClass.replace('c', 'node-c')} ${tintClass} ${glowClass}"
|
<div class="bubble ${cssClass} node ${cssClass.replace('c', 'node-c')} ${tintClass} ${dynamicClass} ${glowClass}">
|
||||||
@click=${() => this._handleClick(entities[configKey])}>
|
|
||||||
${iconContent}
|
${iconContent}
|
||||||
${renderSecondaryOrLabel(label, true, secEntity, hasSecondary)}
|
${renderLabel(label, true)}
|
||||||
<div class="value" style="${textStyle}">${this._formatPower(val)}</div>
|
<div class="value" style="${getConsumerColorStyle(hexColor)}">${this._formatPower(val)}</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
};
|
};
|
||||||
|
|
@ -1167,28 +909,21 @@ console.log(
|
||||||
const pathSolarHouse = "M 50 160 Q 50 265 165 265";
|
const pathSolarHouse = "M 50 160 Q 50 265 165 265";
|
||||||
const pathSolarBatt = "M 50 70 Q 210 -20 370 70";
|
const pathSolarBatt = "M 50 70 Q 210 -20 370 70";
|
||||||
const pathGridImport = "M 210 160 L 210 220";
|
const pathGridImport = "M 210 160 L 210 220";
|
||||||
const pathGridExport = "M 95 115 Q 130 145 165 115";
|
const pathGridExport = "M 165 115 Q 130 145 95 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 pathGridToBatt = "M 255 115 Q 290 145 325 115";
|
||||||
const pathBattHouse = "M 370 160 Q 370 265 255 265";
|
const pathBattHouse = "M 370 160 Q 370 265 255 265";
|
||||||
const pathHouseC1 = "M 165 265 Q 50 265 50 370";
|
const pathHouseC1 = "M 165 265 Q 50 265 50 370";
|
||||||
const pathHouseC2 = "M 210 310 L 210 370";
|
const pathHouseC2 = "M 210 310 L 210 370";
|
||||||
const pathHouseC3 = "M 255 265 Q 370 265 370 370";
|
const pathHouseC3 = "M 255 265 Q 370 265 370 370";
|
||||||
|
|
||||||
const houseTextStyle = this.config.color_text_house
|
const houseTextStyle = houseTextCol ? `color: ${houseTextCol};` : '';
|
||||||
? 'color: var(--text-house-color);'
|
|
||||||
: (houseTextCol ? `color: ${houseTextCol};` : '');
|
|
||||||
const dashArrayVal = showTail ? '30 360' : (showDashedLine ? '13 13' : '0 380');
|
const dashArrayVal = showTail ? '30 360' : (showDashedLine ? '13 13' : '0 380');
|
||||||
const strokeWidthVal = showDashedLine ? 4 : 8;
|
const strokeWidthVal = showDashedLine ? 4 : 8;
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<ha-card style="height: ${finalCardHeightPx}px; --flow-dasharray: ${dashArrayVal}; --flow-stroke-width: ${strokeWidthVal}px;">
|
<ha-card style="height: ${finalCardHeightPx}px; --flow-dasharray: ${dashArrayVal}; --flow-stroke-width: ${strokeWidthVal}px;">
|
||||||
|
|
||||||
<div class="scale-wrapper" style="transform: scale(${scale}); margin-left: ${centerMarginLeft}px;">
|
<div class="scale-wrapper" style="transform: scale(${scale});">
|
||||||
|
|
||||||
<div class="absolute-container" style="height: ${baseHeight}px; top: -${topShift}px;">
|
<div class="absolute-container" style="height: ${baseHeight}px; top: -${topShift}px;">
|
||||||
<svg height="${baseHeight}" viewBox="0 0 420 ${baseHeight}" preserveAspectRatio="xMidYMid meet">
|
<svg height="${baseHeight}" viewBox="0 0 420 ${baseHeight}" preserveAspectRatio="xMidYMid meet">
|
||||||
|
|
@ -1197,33 +932,33 @@ console.log(
|
||||||
<path class="bg-path bg-solar" d="${pathSolarBatt}" style="${getPipeStyle(solarToBatt)} ${styleSolarBatt}" />
|
<path class="bg-path bg-solar" d="${pathSolarBatt}" style="${getPipeStyle(solarToBatt)} ${styleSolarBatt}" />
|
||||||
|
|
||||||
<path class="bg-path bg-grid" d="${pathGridImport}" style="${getPipeStyle(gridToHouse)} ${styleGrid}" />
|
<path class="bg-path bg-grid" d="${pathGridImport}" style="${getPipeStyle(gridToHouse)} ${styleGrid}" />
|
||||||
<path class="bg-path bg-export" d="${activeExportPath}" style="${getPipeStyle(gridExport)} ${styleGrid}" />
|
<path class="bg-path bg-export" d="${pathGridExport}" style="${getPipeStyle(gridExport)} ${styleGrid}" />
|
||||||
<path class="bg-path bg-grid" d="${pathGridToBatt}" style="${getPipeStyle(gridToBatt)} ${styleGridBatt}" />
|
<path class="bg-path bg-grid" d="${pathGridToBatt}" style="${getPipeStyle(gridToBatt)} ${styleGridBatt}" />
|
||||||
|
|
||||||
<path class="bg-path bg-battery" d="${pathBattHouse}" style="${getPipeStyle(batteryDischarge)} ${styleBattery}" />
|
<path class="bg-path bg-battery" d="${pathBattHouse}" style="${getPipeStyle(batteryDischarge)} ${styleBattery}" />
|
||||||
|
|
||||||
<path d="${pathHouseC1}" fill="none" stroke="${this._getConsumerPipeColor(1)}" stroke-width="6" style="${getConsumerPipeStyle(c1PipeActive, c1Val)}" />
|
<path d="${pathHouseC1}" fill="none" stroke="#a855f7" stroke-width="6" style="${getConsumerPipeStyle(showC1, c1Val)}" />
|
||||||
<path d="${pathHouseC2}" fill="none" stroke="${this._getConsumerPipeColor(2)}" stroke-width="6" style="${getConsumerPipeStyle(showC2, c2Val)}" />
|
<path d="${pathHouseC2}" fill="none" stroke="#f97316" stroke-width="6" style="${getConsumerPipeStyle(showC2, c2Val)}" />
|
||||||
<path d="${pathHouseC3}" fill="none" stroke="${this._getConsumerPipeColor(3)}" stroke-width="6" style="${getConsumerPipeStyle(showC3, c3Val)}" />
|
<path d="${pathHouseC3}" fill="none" stroke="#06b6d4" stroke-width="6" style="${getConsumerPipeStyle(showC3, c3Val)}" />
|
||||||
|
|
||||||
<path class="flow-line flow-solar" d="${pathSolarHouse}" style="${getAnimStyle(solarToHouse)} ${styleSolar}" />
|
<path class="flow-line flow-solar" d="${pathSolarHouse}" style="${getAnimStyle(solarToHouse)} ${styleSolar}" />
|
||||||
<path class="flow-line flow-solar" d="${pathSolarBatt}" style="${getAnimStyle(solarToBatt)} ${styleSolarBatt}" />
|
<path class="flow-line flow-solar" d="${pathSolarBatt}" style="${getAnimStyle(solarToBatt)} ${styleSolarBatt}" />
|
||||||
|
|
||||||
<path class="flow-line flow-grid" d="${pathGridImport}" style="${getAnimStyle(gridToHouse)} ${styleGrid}" />
|
<path class="flow-line flow-grid" d="${pathGridImport}" style="${getAnimStyle(gridToHouse)} ${styleGrid}" />
|
||||||
<path class="flow-line flow-export" d="${activeExportPath}" style="${getAnimStyle(gridExport)} ${styleGrid}" />
|
<path class="flow-line flow-export" d="${pathGridExport}" style="${getAnimStyle(gridExport)} ${styleGrid}" />
|
||||||
<path class="flow-line flow-grid" d="${pathGridToBatt}" style="${getAnimStyle(gridToBatt)} ${styleGridBatt}" />
|
<path class="flow-line flow-grid" d="${pathGridToBatt}" style="${getAnimStyle(gridToBatt)} ${styleGridBatt}" />
|
||||||
|
|
||||||
<path class="flow-line flow-battery" d="${pathBattHouse}" style="${getAnimStyle(batteryDischarge)} ${styleBattery}" />
|
<path class="flow-line flow-battery" d="${pathBattHouse}" style="${getAnimStyle(batteryDischarge)} ${styleBattery}" />
|
||||||
|
|
||||||
<path class="flow-line" d="${pathHouseC1}" stroke="${this._getConsumerPipeColor(1)}" style="${getConsumerAnimStyle(c1PipeActive, c1Val)}" />
|
<path class="flow-line" d="${pathHouseC1}" stroke="#a855f7" style="${getConsumerAnimStyle(showC1, c1Val)}" />
|
||||||
<path class="flow-line" d="${pathHouseC2}" stroke="${this._getConsumerPipeColor(2)}" style="${getConsumerAnimStyle(showC2, c2Val)}" />
|
<path class="flow-line" d="${pathHouseC2}" stroke="#f97316" style="${getConsumerAnimStyle(showC2, c2Val)}" />
|
||||||
<path class="flow-line" d="${pathHouseC3}" stroke="${this._getConsumerPipeColor(3)}" style="${getConsumerAnimStyle(showC3, c3Val)}" />
|
<path class="flow-line" d="${pathHouseC3}" stroke="#06b6d4" style="${getConsumerAnimStyle(showC3, c3Val)}" />
|
||||||
|
|
||||||
<text x="100" y="235" class="${textClass} text-solar" style="${getTextStyle(solarToHouse, 'solar')} ${styleSolar}">${this._formatPower(solarToHouse)}</text>
|
<text x="100" y="235" class="${textClass} text-solar" style="${getTextStyle(solarToHouse, 'solar')} ${styleSolar}">${this._formatPower(solarToHouse)}</text>
|
||||||
<text x="210" y="45" class="${textClass} text-solar" style="${getTextStyle(solarToBatt, 'solar')} ${styleSolarBatt}">${this._formatPower(solarToBatt)}</text>
|
<text x="210" y="45" class="${textClass} text-solar" style="${getTextStyle(solarToBatt, 'solar')} ${styleSolarBatt}">${this._formatPower(solarToBatt)}</text>
|
||||||
|
|
||||||
<text x="235" y="195" class="${textClass} text-grid" style="${getTextStyle(gridToHouse, 'grid')} ${styleGrid}">${this._formatPower(gridToHouse)}</text>
|
<text x="235" y="195" class="${textClass} text-grid" style="${getTextStyle(gridToHouse, 'grid')} ${styleGrid}">${this._formatPower(gridToHouse)}</text>
|
||||||
<text x="${exportTextX}" y="${exportTextY}" class="${textClass} text-export" style="${getTextStyle(gridExport, 'grid')} ${styleGrid}">${this._formatPower(gridExport)}</text>
|
<text x="130" y="145" class="${textClass} text-export" style="${getTextStyle(gridExport, 'grid')} ${styleGrid}">${this._formatPower(gridExport)}</text>
|
||||||
<text x="290" y="145" class="${textClass} text-grid" style="${getTextStyle(gridToBatt, 'grid')} ${styleGridBatt}">${this._formatPower(gridToBatt)}</text>
|
<text x="290" y="145" class="${textClass} text-grid" style="${getTextStyle(gridToBatt, 'grid')} ${styleGridBatt}">${this._formatPower(gridToBatt)}</text>
|
||||||
|
|
||||||
<text x="320" y="235" class="${textClass} text-battery" style="${getTextStyle(batteryDischarge, 'battery')} ${styleBattery}">${this._formatPower(batteryDischarge)}</text>
|
<text x="320" y="235" class="${textClass} text-battery" style="${getTextStyle(batteryDischarge, 'battery')} ${styleBattery}">${this._formatPower(batteryDischarge)}</text>
|
||||||
|
|
@ -1231,44 +966,36 @@ console.log(
|
||||||
</svg>
|
</svg>
|
||||||
|
|
||||||
${hasSolar ? html`
|
${hasSolar ? html`
|
||||||
<div class="bubble ${isSolarActive ? 'solar' : 'inactive'} node-solar ${tintClass} ${isSolarActive ? glowClass : ''}"
|
<div class="bubble ${isSolarActive ? 'solar' : 'inactive'} node node-solar ${tintClass} ${isSolarActive ? glowClass : ''} ${getCustomClass(iconSolar)}">
|
||||||
@click=${() => this._handleClick(entities.solar)}>
|
|
||||||
${renderMainIcon('solar', solarVal, iconSolar, solarColor)}
|
${renderMainIcon('solar', solarVal, iconSolar, solarColor)}
|
||||||
${renderSecondaryOrLabel(labelSolarText, showLabelSolar, entities.secondary_solar, hasSecondarySolar)}
|
${renderLabel(labelSolarText, showLabelSolar)}
|
||||||
<div class="value" style="${isSolarActive ? (this.config.color_text_solar ? 'color: var(--text-solar-color);' : getColorStyle('--neon-yellow')) : `color: ${solarColor};`}">${this._formatPower(solarVal)}</div>
|
<div class="value" style="${isSolarActive ? getColorStyle('--neon-yellow') : `color: ${solarColor};`}">${this._formatPower(solarVal)}</div>
|
||||||
</div>` : ''}
|
</div>` : ''}
|
||||||
|
|
||||||
${hasGrid ? html`
|
${hasGrid ? html`
|
||||||
<div class="bubble ${isGridActive ? (isGridExporting ? 'grid exporting' : 'grid') : 'inactive'} node-grid ${showDonut && isGridActive ? 'donut' : ''} ${tintClass} ${isGridActive ? glowClass : ''}"
|
<div class="bubble ${isGridActive ? 'grid' : 'inactive'} node node-grid ${tintClass} ${isGridActive ? glowClass : ''} ${getCustomClass(iconGrid)}">
|
||||||
style="${showDonut && isGridActive ? `--grid-gradient: ${gridGradientVal};` : ''}"
|
${renderMainIcon('grid', gridImport, iconGrid, gridColor)}
|
||||||
@click=${() => this._handleClick(entities.grid_combined || entities.grid)}>
|
${renderLabel(labelGridText, showLabelGrid)}
|
||||||
${renderMainIcon('grid', isGridExporting ? gridExport : gridImport, iconGrid, gridIconColor)}
|
<div class="value" style="${isGridActive ? getColorStyle('--neon-blue') : `color: ${gridColor};`}">${this._formatPower(gridImport)}</div>
|
||||||
${renderSecondaryOrLabel(labelGridText, showLabelGrid, entities.secondary_grid, hasSecondaryGrid)}
|
|
||||||
<div class="value" style="color: ${gridTextColor};">
|
|
||||||
${isGridExporting ? html`<span class="direction-arrow">▲</span>` : (isGridActive ? html`<span class="direction-arrow">▼</span>` : '')}
|
|
||||||
${this._formatPower(isGridExporting ? gridExport : gridImport)}
|
|
||||||
</div>
|
|
||||||
</div>` : ''}
|
</div>` : ''}
|
||||||
|
|
||||||
${hasBattery ? html`
|
${hasBattery ? html`
|
||||||
<div class="bubble battery node-battery ${tintClass} ${glowClass}"
|
<div class="bubble battery node node-battery ${tintClass} ${glowClass} ${getCustomClass(iconBattery)}">
|
||||||
@click=${() => this._handleClick(entities.battery)}>
|
|
||||||
${renderMainIcon('battery', battSoc, iconBattery)}
|
${renderMainIcon('battery', battSoc, iconBattery)}
|
||||||
${renderSecondaryOrLabel(labelBatteryText, showLabelBattery, entities.secondary_battery, hasSecondaryBattery)}
|
${renderLabel(labelBatteryText, showLabelBattery)}
|
||||||
<div class="value" style="${this.config.color_text_battery ? 'color: var(--text-battery-color);' : getColorStyle('--neon-green')}">${Math.round(battSoc)}%</div>
|
<div class="value" style="${getColorStyle('--neon-green')}">${Math.round(battSoc)}%</div>
|
||||||
</div>` : ''}
|
</div>` : ''}
|
||||||
|
|
||||||
<div class="bubble house node-house ${showDonut ? 'donut' : ''} ${tintClass}"
|
<div class="bubble house node node-house ${showDonut ? 'donut' : ''} ${tintClass}"
|
||||||
style="${houseBubbleStyle}"
|
style="${houseBubbleStyle}">
|
||||||
@click=${() => 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)}
|
${renderLabel(labelHouseText, showLabelHouse)}
|
||||||
<div class="value" style="${houseTextStyle}">${this._formatPower(houseDisplay)}</div>
|
<div class="value" style="${houseTextStyle}">${this._formatPower(house)}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
${renderConsumer(showC1, 'c1', 'consumer_1', labelC1, 'car', c1Val, this._getConsumerColor(1))}
|
${renderConsumer(showC1, 'c1', 'consumer_1', labelC1, 'car', c1Val, '#a855f7')}
|
||||||
${renderConsumer(showC2, 'c2', 'consumer_2', labelC2, 'heater', c2Val, this._getConsumerColor(2))}
|
${renderConsumer(showC2, 'c2', 'consumer_2', labelC2, 'heater', c2Val, '#f97316')}
|
||||||
${renderConsumer(showC3, 'c3', 'consumer_3', labelC3, 'pool', c3Val, this._getConsumerColor(3))}
|
${renderConsumer(showC3, 'c3', 'consumer_3', labelC3, 'pool', c3Val, '#06b6d4')}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue