From a23041830d63406f6c5603913772901a6ecfc9ac Mon Sep 17 00:00:00 2001 From: Paul Orffer Date: Wed, 18 Dec 2024 20:48:21 +0200 Subject: [PATCH] feat: adds `off_colour:` and `remaining_energy_to_shutdown:` closes #577 closes #554 --- docs/configuration.md | 9 ++- package.json | 2 +- src/cards/compact-card.ts | 30 +++++---- src/cards/full-card.ts | 20 +++--- src/defaults.ts | 3 + src/editor.ts | 4 ++ src/index.ts | 104 +++++++++++++++++++++++------- src/localize/languages/ca.json | 2 + src/localize/languages/cs.json | 2 + src/localize/languages/da.json | 2 + src/localize/languages/de.json | 2 + src/localize/languages/en.json | 2 + src/localize/languages/es.json | 2 + src/localize/languages/et.json | 2 + src/localize/languages/fr.json | 2 + src/localize/languages/it.json | 2 + src/localize/languages/nl.json | 2 + src/localize/languages/pt-br.json | 2 + src/localize/languages/ru.json | 2 + src/localize/languages/sk.json | 2 + src/localize/languages/sv.json | 2 + src/localize/languages/uk.json | 2 + src/types.ts | 9 ++- 23 files changed, 163 insertions(+), 48 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index cf0a2a98..b2219a46 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -76,7 +76,8 @@ The `invert_power` attribute can be used to reverse direction if needed by your | show_absolute: | Optional | `false` | set to `true` to display power and current as absolute values. | auto_scale: | Optional | `true` | If set to `true` the card will use the entities `unit_of_measurement` attribute to perform the correct scaling (i,e, power values greater than 999W will be displayed as kW e.g. 1.23kW) and display the correct unit. The number of decimal places can be changed using the `decimal_places` card attribute apart from the daily energy values which are set using the `decimal_places_energy` attribute. | | hide_soc: | Optional | `false` | If set to `true` the current program capacity (soc), or for Goodwe inverters the shutdown soc and offgrid shutdown soc that is shown to the left of the current battery one SOC will be hidden. | -| show_remaining_energy: | Optional | `true` | Set to `true` to display the remaining battery one energy in kWh based on the current SOC. Only visable on the `lite` and `full` cards. | +| show_remaining_energy: | Optional | `true` | Set to `true` to display the remaining battery one energy in kWh based on the current SOC. | +| remaining_energy_to_shutdown: | Optional | `false` | If set to `true` the displayed remaining battery energy will be the available energy to the shutdown SOC and not to 0%. | | navigate: | Optional | | Sets the navigation path when clicking on the battery one image. Can be used to link to other dashboards and views e.g. `/lovelace/1`. | invert_flow: | Optional | `false` | Inverts the animated flow. Expects a positive number for battery charging and a negative number for battery discharging @@ -96,7 +97,8 @@ The `invert_power` attribute can be used to reverse direction if needed by your | show_absolute: | Optional | `false` | set to `true` to display power and current as absolute values. | auto_scale: | Optional | `true` | If set to `true` the card will use the entities `unit_of_measurement` attribute to perform the correct scaling (i,e, power values greater than 999W will be displayed as kW e.g. 1.23kW) and display the correct unit. The number of decimal places can be changed using the `decimal_places` card attribute apart from the daily energy values which are set using the `decimal_places_energy` attribute. | | hide_soc: | Optional | `false` | If set to `true` the current program capacity (soc), or for Goodwe inverters the shutdown soc and offgrid shutdown soc that is shown to the left of the current battery SOC will be hidden. | -| show_remaining_energy: | Optional | `true` | Set to `true` to display the remaining battery two energy in kWh based on the current SOC. Only visable on the `lite` and `full` cards. | +| show_remaining_energy: | Optional | `true` | Set to `true` to display the remaining battery two energy in kWh based on the current SOC. | +| remaining_energy_to_shutdown: | Optional | `false` | If set to `true` the displayed remaining battery energy will be the available energy to the shutdown SOC and not to 0%. | | navigate: | Optional | | Sets the navigation path when clicking on the battery two image. Can be used to link to other dashboards and views e.g. `/lovelace/1`. | invert_flow: | Optional | `false` | Inverts the animated flow. Expects a positive number for battery two charging and a negative number for battery two discharging @@ -131,7 +133,8 @@ These attributes are only needed if `show_solar` is set to `true`. | Attribute | Requirement | Default | Description | |--------------------|-------------|-------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| colour: | Optional | `'#5fb6ad'` | Setss the colour of all the load card objects. Hex codes (`'#66ff00'` etc) or names (`red`, `green`, `blue` etc). | +| colour: | Optional | `'#5fb6ad'` | Sets the colour of all the load card objects. Hex codes (`'#66ff00'` etc) or names (`red`, `green`, `blue` etc). | +| off_colour: | Optional | `grey` | Sets the off colour for the additional essential loads. Set this to `transparent` to hide the load when power is below the `off_threshold` | | dynamic_colour: | Optional | `true` | The essential icon colour will change based on the % contribution of the power source (battery, grid, solar) supplying the load. Set to `false` to disable. | | dynamic_icon: | Optional | `true` | The essential icon will change when there is 100% contribution from a single power source (battery, grid, solar). Set to `false` to disable. | | invert_load: | Optional | `false` | Set to `true` if your sensor provides a negative number when the load is drawing power. | diff --git a/package.json b/package.json index 35d1a81c..fc8d1e40 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sunsynk-power-flow-card", - "version": "6.1.7", + "version": "6.2.0", "description": "A customizable Home Assistant card to emulate the Sunsynk System flow that's displayed on the Inverter screen.", "main": "sunsynk-power-flow-card.js", "scripts": { diff --git a/src/cards/compact-card.ts b/src/cards/compact-card.ts index 094c5567..f8164925 100644 --- a/src/cards/compact-card.ts +++ b/src/cards/compact-card.ts @@ -560,8 +560,10 @@ export const compactCard = (config: sunsynkPowerFlowCardConfig, inverterImg: str class="${!config.entities?.battery_status && !data.compactMode ? 'st3' : 'st3 left-align'}" display="${!config.battery.show_remaining_energy || (data.compactMode && data.batteryCount === 2) ? 'none' : ''}" fill="${data.batteryColour}"> - ${Utils.toNum((data.batteryEnergy * (data.stateBatterySoc.toNum(2) / 100) / 1000), 2)} - ${UnitOfEnergy.KILO_WATT_HOUR} + ${!config.battery.remaining_energy_to_shutdown + ? `${Utils.toNum((data.batteryEnergy * (data.stateBatterySoc.toNum() / 100) / 1000), 2)} ${UnitOfEnergy.KILO_WATT_HOUR}` + : `${Utils.toNum((data.batteryEnergy * ((data.stateBatterySoc?.toNum() - data.batteryOneShutdown) / 100) / 1000), 2)} ${UnitOfEnergy.KILO_WATT_HOUR}` + } - ${Utils.toNum((data.batteryEnergy * (data.stateBatterySoc.toNum(2) / 100) / 1000), 2)} - ${UnitOfEnergy.KILO_WATT_HOUR} + ${!config.battery.remaining_energy_to_shutdown + ? `${Utils.toNum((data.batteryEnergy * (data.stateBatterySoc.toNum() / 100) / 1000), 2)} ${UnitOfEnergy.KILO_WATT_HOUR}` + : `${Utils.toNum((data.batteryEnergy * ((data.stateBatterySoc?.toNum() - data.batteryOneShutdown) / 100) / 1000), 2)} ${UnitOfEnergy.KILO_WATT_HOUR}` + } - ${Utils.toNum((data.battery2Energy * (data.stateBattery2Soc.toNum(2) / 100) / 1000), 2)} - ${UnitOfEnergy.KILO_WATT_HOUR} + ${!config.battery2.remaining_energy_to_shutdown + ? `${Utils.toNum((data.battery2Energy * (data.stateBattery2Soc.toNum() / 100) / 1000), 2)} ${UnitOfEnergy.KILO_WATT_HOUR}` + : `${Utils.toNum((data.battery2Energy * ((data.stateBattery2Soc?.toNum() - data.batteryTwoShutdown) / 100) / 1000), 2)} ${UnitOfEnergy.KILO_WATT_HOUR}` + } - ${Utils.toNum((data.battery2Energy * (data.stateBattery2Soc.toNum(2) / 100) / 1000), 2)} - ${UnitOfEnergy.KILO_WATT_HOUR} + ${!config.battery2.remaining_energy_to_shutdown + ? `${Utils.toNum((data.battery2Energy * (data.stateBattery2Soc.toNum() / 100) / 1000), 2)} ${UnitOfEnergy.KILO_WATT_HOUR}` + : `${Utils.toNum((data.battery2Energy * ((data.stateBattery2Soc?.toNum() - data.batteryTwoShutdown) / 100) / 1000), 2)} ${UnitOfEnergy.KILO_WATT_HOUR}` + } ${config.load?.navigate ? svg` diff --git a/src/cards/full-card.ts b/src/cards/full-card.ts index 3ff0bfdb..4a60f1df 100644 --- a/src/cards/full-card.ts +++ b/src/cards/full-card.ts @@ -658,8 +658,10 @@ export const fullCard = (config: sunsynkPowerFlowCardConfig, inverterImg: string - ${Utils.toNum((data.batteryEnergy * (data.stateBatterySoc?.toNum() / 100) / 1000), 2)} - ${UnitOfEnergy.KILO_WATT_HOUR} + ${!config.battery.remaining_energy_to_shutdown + ? `${Utils.toNum((data.batteryEnergy * (data.stateBatterySoc.toNum() / 100) / 1000), 2)} ${UnitOfEnergy.KILO_WATT_HOUR}` + : `${Utils.toNum((data.batteryEnergy * ((data.stateBatterySoc?.toNum() - data.batteryOneShutdown) / 100) / 1000), 2)} ${UnitOfEnergy.KILO_WATT_HOUR}` + } - ${Utils.toNum((data.battery2Energy * (data.stateBattery2Soc?.toNum() / 100) / 1000), 2)} - ${UnitOfEnergy.KILO_WATT_HOUR} + ${!config.battery2.remaining_energy_to_shutdown + ? `${Utils.toNum((data.battery2Energy * (data.stateBattery2Soc.toNum() / 100) / 1000), 2)} ${UnitOfEnergy.KILO_WATT_HOUR}` + : `${Utils.toNum((data.battery2Energy * ((data.stateBattery2Soc?.toNum() - data.batteryTwoShutdown) / 100) / 1000), 2)} ${UnitOfEnergy.KILO_WATT_HOUR}` + } { if (power === 0) { @@ -1372,6 +1350,80 @@ export class SunsynkPowerFlowCard extends LitElement { viewBoxHeightLite = '408'; } + const offThreshold = Utils.toNum(config.load?.off_threshold, 0); + const offColourTransparent = config.load?.off_colour === 'transparent'; + + // Helper function to check if all given loads are <= offThreshold + const areLoadsBelowThreshold = (...loads) => + loads.every(load => load.toPower(false) <= offThreshold); + + // Initialize colours + let load1Colour = loadColour; + let load2Colour = loadColour; + + // Mapping for Lite and Compact cards + const liteCompactLoads = { + 1: [stateEssentialLoad1], + 2: [stateEssentialLoad1], + 3: [stateEssentialLoad1], + 4: [stateEssentialLoad1, stateEssentialLoad2], + 5: [stateEssentialLoad1, stateEssentialLoad2, stateEssentialLoad5], + 6: [stateEssentialLoad1, stateEssentialLoad2, stateEssentialLoad5], + }; + const liteCompactLoads2 = { + 2: [stateEssentialLoad2], + 3: [stateEssentialLoad2, stateEssentialLoad3], + 4: [stateEssentialLoad3, stateEssentialLoad4], + 5: [stateEssentialLoad3, stateEssentialLoad4], + 6: [stateEssentialLoad3, stateEssentialLoad4, stateEssentialLoad6], + }; + + // Mapping for Full cards + const fullCardLoads = { + 1: [stateEssentialLoad1], + 2: [stateEssentialLoad1, stateEssentialLoad2], + 4: [stateEssentialLoad1, stateEssentialLoad2], + 5: [stateEssentialLoad1, stateEssentialLoad2, stateEssentialLoad5], + 6: [stateEssentialLoad1, stateEssentialLoad2, stateEssentialLoad5], + }; + const fullCardLoads2 = { + 4: [stateEssentialLoad3, stateEssentialLoad4], + 5: [stateEssentialLoad3, stateEssentialLoad4], + 6: [stateEssentialLoad3, stateEssentialLoad4, stateEssentialLoad6], + }; + + // Function to determine load color + const getLoadColour = (essentialLoads, defaultColour) => + areLoadsBelowThreshold(...essentialLoads) ? 'transparent' : defaultColour; + + // Logic for Lite and Compact cards + if (offColourTransparent && (this.isLiteCard || this.isCompactCard)) { + load1Colour = getLoadColour(liteCompactLoads[additionalLoad] || [], load1Colour); + load2Colour = getLoadColour(liteCompactLoads2[additionalLoad] || [], load2Colour); + } + + // Logic for Full cards + if (offColourTransparent && this.isFullCard) { + load1Colour = getLoadColour(fullCardLoads[additionalLoad] || [], load1Colour); + load2Colour = getLoadColour(fullCardLoads2[additionalLoad] || [], load2Colour); + } + + const gridStatusLower = gridStatus.toLowerCase(); + let batteryOneShutdown = batteryShutdown; + let batteryTwoShutdown = batteryShutdown2; + switch (true) { + case ['on', '1', 'on-grid'].includes(gridStatusLower): + batteryOneShutdown = batteryShutdown; + batteryTwoShutdown = batteryShutdown2; + break; + + case ['off', '0', 'off-grid'].includes(gridStatusLower): + batteryOneShutdown = stateShutdownSOCOffGrid.notEmpty() ? shutdownOffGrid : batteryShutdown; + batteryTwoShutdown = stateShutdownSOCOffGrid2.notEmpty() ? shutdownOffGrid2 : batteryShutdown2; + break; + } + + /** * The current structure of this data object is intentional, but it is considered temporary. * There is a need to evaluate the data being passed, as there might be duplication. @@ -1385,6 +1437,8 @@ export class SunsynkPowerFlowCard extends LitElement { cardHeight, cardWidth, loadColour, + load1Colour, + load2Colour, batteryColour, battery2Colour, gridColour, @@ -1459,6 +1513,8 @@ export class SunsynkPowerFlowCard extends LitElement { autarkyEnergy, shutdownOffGrid2, shutdownOffGrid, + batteryOneShutdown, + batteryTwoShutdown, statePV1Current, statePV2Current, statePV3Current, @@ -1688,7 +1744,7 @@ export class SunsynkPowerFlowCard extends LitElement { ? this.colourConvert(this._config.load?.colour) : Math.abs(state) > threshold ? this.colourConvert(this._config.load?.colour) - : 'grey'; + : this.colourConvert(this._config.load?.off_colour) || 'grey'; } setConfig(config) { diff --git a/src/localize/languages/ca.json b/src/localize/languages/ca.json index f1616ee5..b99bc66e 100644 --- a/src/localize/languages/ca.json +++ b/src/localize/languages/ca.json @@ -188,6 +188,8 @@ "label_daily_grid_buy": "Etiqueta Compra Diària Xarxa", "label_daily_grid_sell": "Etiqueta Venta Diària Xarxa", "invert_flow": "Invert Flow", + "remaining_energy_to_shutdown": "Remaining Energy to Shutdown", + "off_colour": "Off Colour", "cat_title": { "title": "Opcions Títol", "general": "Opcions Generals", diff --git a/src/localize/languages/cs.json b/src/localize/languages/cs.json index c11f4aa5..066a5d63 100644 --- a/src/localize/languages/cs.json +++ b/src/localize/languages/cs.json @@ -189,6 +189,8 @@ "label_daily_grid_sell": "Daily Grid Sell Label", "invert_flow": "Invert Flow", "label_daily_load": "Daily Load Label", + "remaining_energy_to_shutdown": "Remaining Energy to Shutdown", + "off_colour": "Off Colour", "cat_title": { "title": "Title Options", "general": "General Options", diff --git a/src/localize/languages/da.json b/src/localize/languages/da.json index 9cf47ff7..7e0316e6 100644 --- a/src/localize/languages/da.json +++ b/src/localize/languages/da.json @@ -189,6 +189,8 @@ "label_daily_grid_sell": "Daily Grid Sell Label", "invert_flow": "Invert Flow", "label_daily_load": "Daily Load Label", + "remaining_energy_to_shutdown": "Remaining Energy to Shutdown", + "off_colour": "Off Colour", "cat_title": { "title": "Titel Options", "general": "Generelle Options", diff --git a/src/localize/languages/de.json b/src/localize/languages/de.json index 69ab781d..c1f3bc25 100644 --- a/src/localize/languages/de.json +++ b/src/localize/languages/de.json @@ -189,6 +189,8 @@ "invert_flow": "Invert Flow", "label_daily_load": "Daily Load Label", "wide": "Wide Screen Layout", + "remaining_energy_to_shutdown": "Remaining Energy to Shutdown", + "off_colour": "Off Colour", "cat_title": { "title": "Title Options", "general": "General Options", diff --git a/src/localize/languages/en.json b/src/localize/languages/en.json index 2c90fff4..0ed718e9 100644 --- a/src/localize/languages/en.json +++ b/src/localize/languages/en.json @@ -189,6 +189,8 @@ "label_daily_grid_sell": "Daily Grid Sell Label", "invert_flow": "Invert Flow", "label_daily_load": "Daily Load Label", + "remaining_energy_to_shutdown": "Remaining Energy to Shutdown", + "off_colour": "Off Colour", "cat_title": { "title": "Title Options", "general": "General Options", diff --git a/src/localize/languages/es.json b/src/localize/languages/es.json index befabf52..4aa04081 100644 --- a/src/localize/languages/es.json +++ b/src/localize/languages/es.json @@ -189,6 +189,8 @@ "invert_flow": "Invert Flow", "label_daily_load": "Daily Load Label", "wide": "Wide Screen Layout", + "remaining_energy_to_shutdown": "Remaining Energy to Shutdown", + "off_colour": "Off Colour", "cat_title": { "title": "Title Options", "general": "General Options", diff --git a/src/localize/languages/et.json b/src/localize/languages/et.json index c0970541..012074cf 100644 --- a/src/localize/languages/et.json +++ b/src/localize/languages/et.json @@ -189,6 +189,8 @@ "invert_flow": "Invert Flow", "label_daily_load": "Daily Load Label", "wide": "Wide Screen Layout", + "remaining_energy_to_shutdown": "Remaining Energy to Shutdown", + "off_colour": "Off Colour", "cat_title": { "title": "Title Options", "general": "General Options", diff --git a/src/localize/languages/fr.json b/src/localize/languages/fr.json index f87fdb5b..d871a81e 100644 --- a/src/localize/languages/fr.json +++ b/src/localize/languages/fr.json @@ -189,6 +189,8 @@ "invert_flow": "Invert Flow", "label_daily_load": "Daily Load Label", "wide": "Wide Screen Layout", + "remaining_energy_to_shutdown": "Remaining Energy to Shutdown", + "off_colour": "Off Colour", "cat_title": { "title": "Title Options", "general": "General Options", diff --git a/src/localize/languages/it.json b/src/localize/languages/it.json index ed838522..3ea0f93e 100644 --- a/src/localize/languages/it.json +++ b/src/localize/languages/it.json @@ -189,6 +189,8 @@ "label_daily_load": "Daily Load Label", "invert_flow": "Invert Flow", "wide": "Wide Screen Layout", + "remaining_energy_to_shutdown": "Remaining Energy to Shutdown", + "off_colour": "Off Colour", "cat_title": { "title": "Title Options", "general": "General Options", diff --git a/src/localize/languages/nl.json b/src/localize/languages/nl.json index e3601604..4b7bd08e 100644 --- a/src/localize/languages/nl.json +++ b/src/localize/languages/nl.json @@ -189,6 +189,8 @@ "invert_flow": "Invert Flow", "label_daily_load": "Daily Load Label", "wide": "Wide Screen Layout", + "remaining_energy_to_shutdown": "Remaining Energy to Shutdown", + "off_colour": "Off Colour", "cat_title": { "title": "Title Options", "general": "General Options", diff --git a/src/localize/languages/pt-br.json b/src/localize/languages/pt-br.json index d3bc6375..da3236a7 100644 --- a/src/localize/languages/pt-br.json +++ b/src/localize/languages/pt-br.json @@ -189,6 +189,8 @@ "invert_flow": "Invert Flow", "label_daily_load": "Daily Load Label", "wide": "Wide Screen Layout", + "remaining_energy_to_shutdown": "Remaining Energy to Shutdown", + "off_colour": "Off Colour", "cat_title": { "title": "Opções de título", "general": "Opções gerais", diff --git a/src/localize/languages/ru.json b/src/localize/languages/ru.json index 07f3cbd1..216a9fd7 100644 --- a/src/localize/languages/ru.json +++ b/src/localize/languages/ru.json @@ -189,6 +189,8 @@ "invert_flow": "Invert Flow", "label_daily_load": "Daily Load Label", "wide": "Wide Screen Layout", + "remaining_energy_to_shutdown": "Remaining Energy to Shutdown", + "off_colour": "Off Colour", "cat_title": { "title": "Параметры Заголовка", "general": "Общие Настройки", diff --git a/src/localize/languages/sk.json b/src/localize/languages/sk.json index 1a38789f..5b158ed8 100644 --- a/src/localize/languages/sk.json +++ b/src/localize/languages/sk.json @@ -189,6 +189,8 @@ "invert_flow": "Invert Flow", "label_daily_load": "Daily Load Label", "wide": "Wide Screen Layout", + "remaining_energy_to_shutdown": "Remaining Energy to Shutdown", + "off_colour": "Off Colour", "cat_title": { "title": "Title Options", "general": "General Options", diff --git a/src/localize/languages/sv.json b/src/localize/languages/sv.json index 7336b453..899c459c 100644 --- a/src/localize/languages/sv.json +++ b/src/localize/languages/sv.json @@ -189,6 +189,8 @@ "invert_flow": "Invert Flow", "label_daily_load": "Daily Load Label", "wide": "Wide Screen Layout", + "remaining_energy_to_shutdown": "Remaining Energy to Shutdown", + "off_colour": "Off Colour", "cat_title": { "title": "Titelalternativ", "general": "Allmänna Alternativ", diff --git a/src/localize/languages/uk.json b/src/localize/languages/uk.json index 0cbb8252..288307b0 100644 --- a/src/localize/languages/uk.json +++ b/src/localize/languages/uk.json @@ -189,6 +189,8 @@ "invert_flow": "Інвертувати Потік", "label_daily_load": "Етикетка Добового Навантаження", "wide": "Wide Screen Layout", + "remaining_energy_to_shutdown": "Remaining Energy to Shutdown", + "off_colour": "Off Colour", "cat_title": { "title": "Параметри Заголовка", "general": "Загальні Параметри", diff --git a/src/types.ts b/src/types.ts index 8453b3ea..b5b9aa8f 100644 --- a/src/types.ts +++ b/src/types.ts @@ -85,6 +85,7 @@ export interface sunsynkPowerFlowCardConfig extends LovelaceCardConfig { show_absolute: boolean; auto_scale: boolean; show_remaining_energy: boolean; + remaining_energy_to_shutdown: boolean; dynamic_colour: boolean; linear_gradient: boolean; animate: boolean; @@ -106,6 +107,7 @@ export interface sunsynkPowerFlowCardConfig extends LovelaceCardConfig { show_absolute: boolean; auto_scale: boolean; show_remaining_energy: boolean; + remaining_energy_to_shutdown: boolean; dynamic_colour: boolean; linear_gradient: boolean; animate: boolean; @@ -137,6 +139,7 @@ export interface sunsynkPowerFlowCardConfig extends LovelaceCardConfig { }; load: { colour: string; + off_colour: string; dynamic_colour: boolean; aux_dynamic_colour: boolean; dynamic_icon: boolean; @@ -345,8 +348,10 @@ export interface DataDto { cardHeight; cardWidth; loadColour; + load1Colour; + load2Colour; batteryColour; - battery2Colour, + battery2Colour; gridColour; isFloating; isFloating2, @@ -397,6 +402,8 @@ export interface DataDto { autarkyEnergy; shutdownOffGrid; shutdownOffGrid2; + batteryOneShutdown; + batteryTwoShutdown; energyCost; inverterCurrent; inverterCurrentL2;