Skip to content

Commit 2dfd0ba

Browse files
committed
feat: integrate upstream PRs from flixlix/power-flow-card-plus
Integrated 10 open pull requests from the upstream repository: **Priority 1 - Critical Fixes:** - PR flixlix#882: Fix Spanish translation (Inicio → Casa) - PR flixlix#879: Fix secondary template bug with >4 individual entities - PR flixlix#878: Fix battery charging attribution (prioritize Solar over Grid) - PR flixlix#803 + flixlix#806: Fix individual consumption calculation (only count visible entities) and grid color at zero state - PR flixlix#804: Fix battery-grid flow animation speed - PR flixlix#801: Better null/undefined handling in displayValue **Priority 2 - Features & i18n:** - PR flixlix#875: Add HACS badge to README - PR flixlix#877: Add Ukrainian translation (ua/uk) - PR flixlix#845: Add Hindi translation (hi/hi-IN) All changes tested and verified with successful build.
1 parent 29aa466 commit 2dfd0ba

File tree

9 files changed

+200
-16
lines changed

9 files changed

+200
-16
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ In case you want to watch a tutorial instead of reading through this very long r
6060

6161
### HACS (recommended)
6262

63+
[![Open your Home Assistant instance and open a repository inside the Home Assistant Community Store.](https://my.home-assistant.io/badges/hacs_repository.svg)](https://my.home-assistant.io/redirect/hacs_repository/?owner=flixlix&repository=power-flow-card-plus&category=Dashboard)
64+
6365
This card is directly available through [HACS](https://hacs.xyz). To install HACS, follow these [instructions](https://hacs.xyz/docs/setup/prerequisites).
6466
After having HACS installed, simply search for "Power Flow Card Plus" and download it using the UI 🙂
6567

src/components/grid.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ export const gridElement = (
4141
${(entities.grid?.display_state === "two_way" ||
4242
entities.grid?.display_state === undefined ||
4343
(entities.grid?.display_state === "one_way_no_zero" && (grid.state.toGrid ?? 0) > 0) ||
44-
(entities.grid?.display_state === "one_way" && (grid.state.fromGrid === null || grid.state.fromGrid === 0) && grid.state.toGrid !== 0)) &&
44+
(entities.grid?.display_state === "one_way" && ((grid.state.fromGrid ?? 0) == 0))) &&
4545
grid.state.toGrid !== null &&
4646
!grid.powerOutage.isOutage
4747
? html`<span
@@ -70,7 +70,7 @@ export const gridElement = (
7070
${((entities.grid?.display_state === "two_way" ||
7171
entities.grid?.display_state === undefined ||
7272
(entities.grid?.display_state === "one_way_no_zero" && grid.state.fromGrid > 0) ||
73-
(entities.grid?.display_state === "one_way" && (grid.state.toGrid === null || grid.state.toGrid === 0))) &&
73+
(entities.grid?.display_state === "one_way" && (grid.state.toGrid ?? 0) == 0 && grid.state.fromGrid > 0)) &&
7474
grid.state.fromGrid !== null &&
7575
!grid.powerOutage.isOutage) ||
7676
(grid.powerOutage.isOutage && !!grid.powerOutage.entityGenerator)

src/localize/languages/es.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
"solar": "Solar",
1717
"battery": "Batería",
1818
"fossil_fuel_percentage": "Combustible Fósil",
19-
"home": "Inicio",
19+
"home": "Casa",
2020
"individual": "Individual",
2121
"accept_negative": "Aceptar valores negativos",
2222
"advanced": "Opciones Avanzadas",

src/localize/languages/hi-IN.json

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
{
2+
"card": {
3+
"label": {
4+
"car": "कार",
5+
"motorbike": "मोटरसाइकिल"
6+
}
7+
},
8+
"editor": {
9+
"entity_generator": "जनरेटर",
10+
"combined": "संयुक्त इकाई (सकारात्मक और नकारात्मक मानों के साथ)",
11+
"separated": "अलग-अलग इकाइयाँ (उपभोग और उत्पादन के लिए)",
12+
"custom_colors": "कस्टम रंग",
13+
"secondary_info": "द्वितीयक जानकारी",
14+
"power_outage": "बिजली गुल",
15+
"grid": "ग्रिड",
16+
"solar": "सौर ऊर्जा",
17+
"battery": "बैटरी",
18+
"fossil_fuel_percentage": "जीवाश्म ईंधन",
19+
"home": "घर",
20+
"individual": "व्यक्तिगत",
21+
"accept_negative": "नकारात्मक स्वीकार करें",
22+
"advanced": "उन्नत विकल्प",
23+
"decimals": "दशमलव",
24+
"consumption": "उपभोग",
25+
"production": "उत्पादन",
26+
"color_icon": "आइकन का रंग",
27+
"color_circle": "सर्कल का रंग",
28+
"color_value": "मान का रंग",
29+
"color_state_of_charge_value": "रंग",
30+
"display_zero": "शून्य दिखाएँ",
31+
"display_zero_tolerance": "शून्य सहिष्णुता दिखाएँ",
32+
"display_state": "स्थिति दिखाएँ",
33+
"display_zero_state": "शून्य स्थिति दिखाएँ",
34+
"invert_state": "स्थिति उलटें",
35+
"template": "टेम्पलेट",
36+
"unit_of_measurement": "माप की इकाई",
37+
"unit_white_space": "यूनिट स्पेस",
38+
"label_alert": "अलर्ट के लिए लेबल",
39+
"icon_alert": "अलर्ट के लिए आइकन",
40+
"state_alert": "अलर्ट की स्थिति",
41+
"state_of_charge": "चार्ज की स्थिति",
42+
"state_of_charge_unit_white_space": "स्पेस",
43+
"state_of_charge_unit": "इकाई",
44+
"state_of_charge_decimals": "दशमलव",
45+
"state_of_charge_icon": "आइकन",
46+
"show_state_of_charge": "चार्ज की स्थिति दिखाएँ",
47+
"state_type": "स्थिति प्रकार",
48+
"subtract_individual": "व्यक्तिगत घटाएँ",
49+
"override_state": "स्थिति ओवरराइड करें",
50+
"calculate_flow_rate": "प्रवाह दर की गणना करें",
51+
"inverted_animation": "एनिमेशन उलटें",
52+
"show_direction": "दिशा दिखाएँ",
53+
"circle_animation": "सर्कल एनिमेशन",
54+
"color": "रंग",
55+
"dashboard_link": "डैशबोर्ड लिंक",
56+
"dashboard_link_label": "डैशबोर्ड लिंक नाम",
57+
"w_decimals": "वाट दशमलव",
58+
"kw_decimals": "किलोवाट दशमलव",
59+
"max_flow_rate": "अधिकतम प्रवाह दर",
60+
"min_flow_rate": "न्यूनतम प्रवाह दर",
61+
"max_expected_power": "अधिकतम अपेक्षित शक्ति",
62+
"min_expected_power": "न्यूनतम अपेक्षित शक्ति",
63+
"watt_threshold": "वाट से किलोवाट सीमा",
64+
"display_zero_lines": "शून्य रेखाएँ दिखाएँ",
65+
"clickable_entities": "क्लिक करने योग्य इकाइयाँ",
66+
"disable_dots": "डॉट्स अक्षम करें",
67+
"use_new_flow_rate_model": "नया प्रवाह दर मॉडल",
68+
"use_metadata": "मेटाडेटा का उपयोग करें",
69+
"mode": "मोड",
70+
"show": "दिखाएँ",
71+
"hide": "छुपाएँ",
72+
"custom": "कस्टम",
73+
"grey_out": "धूसर करें",
74+
"transparency": "पारदर्शिता",
75+
"grey_color": "धूसर रंग",
76+
"tap_action": "टैप क्रिया",
77+
"navigation_path": "नेविगेशन पथ",
78+
"sort_individual_devices": "व्यक्तिगत डिवाइस क्रमबद्ध करें"
79+
}
80+
}

src/localize/languages/ua.json

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
{
2+
"card": {
3+
"label": {
4+
"car": "Автомобіль",
5+
"motorbike": "Мотоцикл"
6+
}
7+
},
8+
"editor": {
9+
"entity_generator": "Генератор сутностей",
10+
"combined": "Об'єднана сутність (одна сутність із позитивними та негативними значеннями)",
11+
"separated": "Розділені сутності (окрема для споживання та окрема для виробництва)",
12+
"custom_colors": "Власні кольори",
13+
"secondary_info": "Додаткова інформація",
14+
"power_outage": "Відключення живлення",
15+
"grid": "Мережа",
16+
"solar": "Сонячна енергія",
17+
"battery": "Акумулятор",
18+
"fossil_fuel_percentage": "Частка викопного палива",
19+
"home": "Дім",
20+
"individual": "Окремо",
21+
"accept_negative": "Дозволити від'ємні значення",
22+
"advanced": "Розширені параметри",
23+
"decimals": "Кількість десяткових знаків",
24+
"consumption": "Споживання",
25+
"production": "Виробництво",
26+
"color_icon": "Колір іконки",
27+
"color_circle": "Колір кола",
28+
"color_value": "Колір значення",
29+
"color_state_of_charge_value": "Колір заряду",
30+
"display_zero": "Показувати нульові значення",
31+
"display_zero_tolerance": "Допуск для нульових значень",
32+
"display_state": "Показувати стан",
33+
"display_zero_state": "Показувати стан при нулі",
34+
"invert_state": "Інвертувати стан",
35+
"template": "Шаблон",
36+
"unit_of_measurement": "Одиниця виміру",
37+
"unit_white_space": "Пробіл між числом і одиницею",
38+
"label_alert": "Мітка сповіщення",
39+
"icon_alert": "Іконка сповіщення",
40+
"state_alert": "Стан сповіщення",
41+
"state_of_charge": "Рівень заряду",
42+
"state_of_charge_unit_white_space": "Пробіл одиниці виміру заряду",
43+
"state_of_charge_unit": "Одиниця виміру заряду",
44+
"state_of_charge_decimals": "Десяткові знаки заряду",
45+
"state_of_charge_icon": "Іконка заряду",
46+
"show_state_of_charge": "Показувати рівень заряду",
47+
"state_type": "Тип стану",
48+
"subtract_individual": "Віднімати індивідуальні значення",
49+
"override_state": "Перевизначити стан",
50+
"calculate_flow_rate": "Розраховувати швидкість потоку",
51+
"inverted_animation": "Інвертована анімація",
52+
"show_direction": "Показувати напрямок",
53+
"circle_animation": "Анімація кола",
54+
"color": "Колір",
55+
"dashboard_link": "Посилання на дашборд",
56+
"dashboard_link_label": "Назва посилання на дашборд",
57+
"w_decimals": "Десяткові знаки для ватів",
58+
"kw_decimals": "Десяткові знаки для кіловатів",
59+
"max_flow_rate": "Максимальна швидкість потоку",
60+
"min_flow_rate": "Мінімальна швидкість потоку",
61+
"max_expected_power": "Максимальна очікувана потужність",
62+
"min_expected_power": "Мінімальна очікувана потужність",
63+
"watt_threshold": "Поріг переходу ват → кіловат",
64+
"display_zero_lines": "Показувати лінії з нульовим значенням",
65+
"clickable_entities": "Клікабельні сутності",
66+
"disable_dots": "Вимкнути точки",
67+
"use_new_flow_rate_model": "Новий алгоритм швидкості потоку",
68+
"use_metadata": "Використовувати метадані",
69+
"mode": "Режим",
70+
"show": "Показати",
71+
"hide": "Приховати",
72+
"custom": "Користувацький",
73+
"grey_out": "Затемнити неактивні",
74+
"transparency": "Прозорість",
75+
"grey_color": "Колір затемнення",
76+
"tap_action": "Дія при натисканні",
77+
"navigation_path": "Шлях переходу",
78+
"sort_individual_devices": "Сортувати окремі пристрої"
79+
}
80+
}

src/localize/localize.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import * as fi from "./languages/fi.json";
1313
import * as pl from "./languages/pl.json";
1414
import * as sk from "./languages/sk.json";
1515
import * as sv from "./languages/sv.json";
16+
import * as ua from "./languages/ua.json";
17+
import * as hi from "./languages/hi-IN.json";
1618

1719
const languages: Record<string, unknown> = {
1820
cs,
@@ -30,6 +32,10 @@ const languages: Record<string, unknown> = {
3032
pl,
3133
sk,
3234
sv,
35+
ua,
36+
uk: ua,
37+
hi,
38+
hi_IN: hi,
3339
};
3440

3541
const defaultLang = "en";

src/power-flow-card-plus.ts

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -354,10 +354,24 @@ export class PowerFlowCardPlus extends LitElement {
354354
}
355355
}
356356
solar.state.toHome = 0;
357-
} else if (solar.state.toHome !== null && solar.state.toHome > 0) {
358-
grid.state.toBattery = 0;
357+
359358
} else if (battery.state.toBattery && battery.state.toBattery > 0) {
360-
grid.state.toBattery = battery.state.toBattery;
359+
// Allocate PV to the battery first; only the remainder can be Grid → Battery.
360+
const pvTotal = Math.max(solar.state.total ?? 0, 0);
361+
const battCharge = Math.max(battery.state.toBattery ?? 0, 0);
362+
const gridExport = Math.max(grid.state.toGrid ?? 0, 0); // 0 if you don't have export
363+
364+
// PV → Battery portion (capped by battery charge)
365+
const pvToBattery = Math.min(pvTotal, battCharge);
366+
367+
// Residual charge must come from the grid
368+
grid.state.toBattery = Math.max(battCharge - pvToBattery, 0);
369+
370+
// PV left for Home after reserving PV→Battery (and any export)
371+
solar.state.toHome = Math.max(pvTotal - pvToBattery - gridExport, 0);
372+
373+
// Explicit PV→Battery for rendering
374+
solar.state.toBattery = pvToBattery;
361375
}
362376
grid.state.toBattery = (grid.state.toBattery ?? 0) > largestGridBatteryTolerance ? grid.state.toBattery : 0;
363377

@@ -402,9 +416,10 @@ export class PowerFlowCardPlus extends LitElement {
402416
nonFossil.state.power = grid.state.toHome * nonFossilFuelDecimal;
403417
}
404418

405-
// Calculate Total Consumptions
406-
const totalIndividualConsumption = individualObjs?.reduce((a, b) => a + (b.state || 0), 0) || 0;
419+
// Calculate Individual Consumption, ignore not shown objects
420+
const totalIndividualConsumption = individualObjs?.reduce((a, b) => a + (b.has ? b.state || 0 : 0), 0) || 0;
407421

422+
// Calculate Total Consumptions
408423
const totalHomeConsumption = Math.max(grid.state.toHome + (solar.state.toHome ?? 0) + (battery.state.toHome ?? 0), 0);
409424

410425
// Calculate Circumferences
@@ -463,7 +478,7 @@ export class PowerFlowCardPlus extends LitElement {
463478

464479
// Compute durations
465480
const newDur: NewDur = {
466-
batteryGrid: computeFlowRate(this._config, grid.state.toBattery ?? battery.state.toGrid ?? 0, totalLines),
481+
batteryGrid: computeFlowRate(this._config, Math.max(grid.state.toBattery ?? 0, battery.state.toGrid ?? 0, 0), totalLines),
467482
batteryToHome: computeFlowRate(this._config, battery.state.toHome ?? 0, totalLines),
468483
gridToHome: computeFlowRate(this._config, grid.state.toHome, totalLines),
469484
solarToBattery: computeFlowRate(this._config, solar.state.toBattery ?? 0, totalLines),
@@ -516,15 +531,14 @@ export class PowerFlowCardPlus extends LitElement {
516531
});
517532
};
518533

519-
const individualKeys = ["left-top", "left-bottom", "right-top", "right-bottom"];
520534
// Templates
521535
const templatesObj: TemplatesObj = {
522536
gridSecondary: this._templateResults.gridSecondary?.result,
523537
solarSecondary: this._templateResults.solarSecondary?.result,
524538
homeSecondary: this._templateResults.homeSecondary?.result,
525539

526540
nonFossilFuelSecondary: this._templateResults.nonFossilFuelSecondary?.result,
527-
individual: individualObjs?.map((_, index) => this._templateResults[`${individualKeys[index]}Secondary`]?.result) || [],
541+
individual: individualObjs?.map((_, index) => this._templateResults[`individual${index}Secondary`]?.result) || [],
528542
};
529543

530544
// Styles
@@ -689,9 +703,8 @@ export class PowerFlowCardPlus extends LitElement {
689703
for (const [key, value] of Object.entries(templatesObj)) {
690704
if (value) {
691705
if (Array.isArray(value)) {
692-
const individualKeys = ["left-top", "left-bottom", "right-top", "right-bottom"];
693706
value.forEach((template, index) => {
694-
if (template) this._tryConnect(template, `${individualKeys[index]}Secondary`);
707+
if (template) this._tryConnect(template, `individual${index}Secondary`);
695708
});
696709
} else {
697710
this._tryConnect(value, key);

src/style/all.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export const allDynamicStyles = (
3535
: grid.color.icon_type === "production"
3636
? "var(--energy-grid-return-color)"
3737
: grid.color.icon_type === true
38-
? (grid.state.fromGrid ?? 0) >= (grid.state.toGrid ?? 0)
38+
? (grid.state.fromGrid ?? 0) > (grid.state.toGrid ?? 0)
3939
? "var(--energy-grid-consumption-color)"
4040
: "var(--energy-grid-return-color)"
4141
: "var(--primary-text-color)"
@@ -48,7 +48,7 @@ export const allDynamicStyles = (
4848
: grid.color.circle_type === "production"
4949
? "var(--energy-grid-return-color)"
5050
: grid.color.circle_type === true
51-
? (grid.state.fromGrid ?? 0) >= (grid.state.toGrid ?? 0)
51+
? (grid.state.fromGrid ?? 0) > (grid.state.toGrid ?? 0)
5252
? "var(--energy-grid-consumption-color)"
5353
: "var(--energy-grid-return-color)"
5454
: "var(--energy-grid-consumption-color)"

src/utils/displayValue.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,10 @@ export const displayValue = (
2828
watt_threshold?: number;
2929
}
3030
): string => {
31-
if (value === null) return "0";
31+
const space = unitWhiteSpace === false ? "" : " ";
32+
if (value === null || value === undefined || value === "") {
33+
return `0${space}${unit ?? "W"}`;
34+
}
3235

3336
if (!isNumberValue(value)) return value.toString();
3437

0 commit comments

Comments
 (0)