diff --git a/package.json b/package.json index 126e4de..6471f95 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "givtcp-battery-card", - "version": "0.1.9", + "version": "0.2.0", "description": "Lovelace card to display GivTCP battery info", "private": true, "type": "module", diff --git a/src/config-utils.ts b/src/config-utils.ts index cfe6bde..b8ec25a 100644 --- a/src/config-utils.ts +++ b/src/config-utils.ts @@ -17,6 +17,9 @@ import { ICON_STATUS_CHARGING, ICON_STATUS_DISCHARGING, DISPLAY_BATTERY_RATES, + USE_CUSTOM_DOD, + CUSTOM_DOD, + CALCULATE_RESERVE_FROM_DOD, DISPLAY_CUSTOM_DOD_STATS, } from "./constants"; export class ConfigUtils { @@ -40,6 +43,10 @@ export class ConfigUtils { icon_status_charging: ICON_STATUS_CHARGING, icon_status_discharging: ICON_STATUS_DISCHARGING, display_battery_rates: DISPLAY_BATTERY_RATES, + use_custom_dod: USE_CUSTOM_DOD, + custom_dod: CUSTOM_DOD, + calculate_reserve_from_dod: CALCULATE_RESERVE_FROM_DOD, + display_custom_dod_stats: DISPLAY_CUSTOM_DOD_STATS, }; } diff --git a/src/constants.ts b/src/constants.ts index a7f81e1..d7acd1a 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -24,3 +24,18 @@ export const ICON_STATUS_CHARGING = 'mdi:lightning-bolt'; export const ICON_STATUS_DISCHARGING = 'mdi:home-battery'; export const DISPLAY_BATTERY_RATES = true; + +export const USE_CUSTOM_DOD = false; + +export const CUSTOM_DOD = 100.0; + +export const CALCULATE_RESERVE_FROM_DOD = false; + +export const DISPLAY_CUSTOM_DOD_STATS = true; + +export const DISPLAY_UNITS = { + W: "W", + KW: "kW", + WH: "Wh", + KWH: "kWh", +} diff --git a/src/editor.ts b/src/editor.ts index bcaddb7..0d92400 100644 --- a/src/editor.ts +++ b/src/editor.ts @@ -46,7 +46,8 @@ export class GivTCPBatteryCardEditor extends LitElement implements LovelaceCardE selector: { number: { min: 0, - max: 100 + max: 100, + unit_of_measurement: "%", } } }, @@ -65,7 +66,8 @@ export class GivTCPBatteryCardEditor extends LitElement implements LovelaceCardE selector: { number: { min: 0, - max: 100 + max: 100, + unit_of_measurement: "%", } } }, @@ -84,7 +86,8 @@ export class GivTCPBatteryCardEditor extends LitElement implements LovelaceCardE selector: { number: { min: 0, - max: 100 + max: 100, + unit_of_measurement: "%", } } }, @@ -103,7 +106,8 @@ export class GivTCPBatteryCardEditor extends LitElement implements LovelaceCardE selector: { number: { min: 0, - max: 100 + max: 100, + unit_of_measurement: "%", } } }, @@ -133,7 +137,7 @@ export class GivTCPBatteryCardEditor extends LitElement implements LovelaceCardE }, { name: 'display_type', - label: 'Display type (0: Wh | 1: kWh | 2: Dynamic)', + label: 'Display type (0: Wh/W | 1: kWh/kW | 2: Dynamic)', default: defaults.display_type, selector: { number: { @@ -185,6 +189,43 @@ export class GivTCPBatteryCardEditor extends LitElement implements LovelaceCardE boolean: {} } }, + { + name: 'use_custom_dod', + label: 'EXPERIMENTAL! Use custom DoD to override GivTCP battery capacity value.', + default: defaults.use_custom_dod, + selector: { + boolean: {} + } + }, + { + name: 'display_custom_dod_stats', + label: 'EXPERIMENTAL! Display the custom DOD stats', + default: defaults.display_custom_dod_stats, + selector: { + boolean: {} + } + }, + { + name: 'custom_dod', + label: 'EXPERIMENTAL! Custom DoD as percentage to override GivTCP battery capacity value.', + default: defaults.custom_dod, + selector: { + number: { + min: 0, + max: 100, + step: "any", + unit_of_measurement: "%", + } + } + }, + { + name: 'calculate_reserve_from_dod', + label: 'EXPERIMENTAL! Use custom DoD to calculate the battery reserve value', + default: defaults.calculate_reserve_from_dod, + selector: { + boolean: {} + } + }, ]; } diff --git a/src/givtcp-battery-card.ts b/src/givtcp-battery-card.ts index fe30524..b4adb63 100644 --- a/src/givtcp-battery-card.ts +++ b/src/givtcp-battery-card.ts @@ -13,7 +13,10 @@ import { DISPLAY_ABS_POWER, DISPLAY_DP, DISPLAY_TYPE, - DISPLAY_TYPE_OPTIONS, ICON_STATUS_CHARGING, ICON_STATUS_DISCHARGING, ICON_STATUS_IDLE, + DISPLAY_TYPE_OPTIONS, + ICON_STATUS_CHARGING, + ICON_STATUS_DISCHARGING, + ICON_STATUS_IDLE, SOC_THRESH_HIGH, SOC_THRESH_HIGH_COLOUR, SOC_THRESH_LOW, @@ -24,6 +27,11 @@ import { SOC_THRESH_V_HIGH_COLOUR, SOC_THRESH_V_LOW_COLOUR, DISPLAY_BATTERY_RATES, + USE_CUSTOM_DOD, + CUSTOM_DOD, + CALCULATE_RESERVE_FROM_DOD, + DISPLAY_CUSTOM_DOD_STATS, + DISPLAY_UNITS, } from "./constants"; import './components/countdown' @@ -32,7 +40,7 @@ import { styleCss } from './style'; import { version } from '../package.json'; import {ConfigUtils} from "./config-utils"; -import {GivTcpBatteryStats} from "./types"; +import {GivTcpBatteryStats, GivTcpStats} from "./types"; // This puts your card into the UI card picker dialog (window as any).customCards = (window as any).customCards || []; @@ -159,309 +167,225 @@ export class GivTCPBatteryCard extends LitElement implements LovelaceCard { return this.clientHeight > 0 ? Math.ceil(this.clientHeight / 50) : 3; } - setRawValues(): GivTcpBatteryStats { - const rawSocPercentEntity = this._getSocEntity - const rawBatteryPowerEntity = this._getBatteryPowerEntity - const rawSocEnergyEntity = this._getSocKwhEntity - const rawDischargePowerEntity = this._getDischargePowerEntity - const rawChargePowerEntity = this._getChargePowerEntity - const rawBatteryCapacityEntity = this._getBatteryCapacityKwhEntity - const rawBatteryPowerReserveEntity = this._getBatteryPowerReserve - const rawBatteryChargeRate = this._getBatteryChargeRate - const rawBatteryDischargeRate = this._getBatteryDischargeRate - - const socPercent = { - rawState: rawSocPercentEntity.state, - uom: rawSocPercentEntity.attributes?.unit_of_measurement, - value: 0, - display: 0, - displayStr: '', - } - - const batteryPower = { - rawState: rawBatteryPowerEntity.state, - uom: rawBatteryPowerEntity.attributes?.unit_of_measurement, - w: 0, - kW: 0.0, - display: 0, - displayStr: '', - displayUnit: '', - } - - const socEnergy = { - rawState: rawSocEnergyEntity.state, - uom: rawSocEnergyEntity.attributes?.unit_of_measurement, - Wh: 0, - kWh: 0.0, - display: 0, - displayStr: '', - } - - const dischargePower = { - rawState: rawDischargePowerEntity.state, - uom: rawDischargePowerEntity.attributes?.unit_of_measurement, - w: 0, - kW: 0.0, - display: 0, - displayStr: '', - } - - const chargePower = { - rawState: rawChargePowerEntity.state, - uom: rawChargePowerEntity.attributes?.unit_of_measurement, - w: 0, - kW: 0.0, - display: 0, - displayStr: '', - } - - const batteryCapacity = { - rawState: rawBatteryCapacityEntity.state, - uom: rawBatteryCapacityEntity.attributes?.unit_of_measurement, - Wh: 0, - kWh: 0.0, - display: 0, - displayStr: '', - } - - const batteryPowerReservePercent = { - rawState: rawBatteryPowerReserveEntity.state, - uom: rawBatteryPowerReserveEntity.attributes?.unit_of_measurement, - value: 0, - display: 0, - displayStr: '', - } - - const batteryPowerReserveEnergy = { - Wh: 0, - kWh: 0.0, - display: 0, - displayStr: '', - } - - const chargeRate = { - rawState: rawBatteryChargeRate.state, - uom: rawBatteryChargeRate.attributes?.unit_of_measurement, - w: 0, - kW: 0.0, - display: 0, - displayStr: '', - } - - const dischargeRate = { - rawState: rawBatteryDischargeRate.state, - uom: rawBatteryDischargeRate.attributes?.unit_of_measurement, - w: 0, - kW: 0.0, - display: 0, - displayStr: '', + getDp(): number { + let dp = parseInt((this.config.display_dp !== undefined) ? this.config.display_dp : DISPLAY_DP, 10); + if(dp > 3) { + dp = 3; + } + + if(dp < 1) { + dp = 1; } + return dp; + } + getPercentageStats(entity: HassEntity): GivTcpStats { + const s = entity.state + const uom = entity.attributes?.unit_of_measurement; return { - socPercent, - batteryPower, - socEnergy, - dischargePower, - chargePower, - batteryCapacity, - batteryPowerReservePercent, - batteryPowerReserveEnergy, - chargeRate, - dischargeRate, + rawState: s, + uom: uom, + value: parseInt(s, 10), + kValue: 0, + display: parseInt(s, 10), + displayStr: `${parseInt(s, 10)}%`, + displayUnit: "%", } } - calculateStats(): void { + getStandardisedUom(uom: string | undefined): string { + + if(!uom) { + return ""; + } + + switch(uom.toLowerCase()) { + case "w": + return DISPLAY_UNITS.W; + case "wh": + return DISPLAY_UNITS.WH; + case "kw": + return DISPLAY_UNITS.KW; + case "kwh": + return DISPLAY_UNITS.KWH; + default: + return uom; + } + } - const states = this.setRawValues(); + isWorWh(uom: string): boolean { + return (uom === DISPLAY_UNITS.W || uom === DISPLAY_UNITS.WH); + } + + getGivTcpStats(state: string, uomRaw: string | undefined): GivTcpStats { const displayType = (this.config.display_type !== undefined) ? this.config.display_type : DISPLAY_TYPE; const displayAbsPower = (this.config.display_abs_power !== undefined) ? this.config.display_abs_power : DISPLAY_ABS_POWER; - let dp = parseInt((this.config.display_dp !== undefined) ? this.config.display_dp : DISPLAY_DP, 10); + const dp = this.getDp(); - if(dp > 3) { - dp = 3; - } + const uom = this.getStandardisedUom(uomRaw); - if(dp < 1) { - dp = 1; - } + const rawAsNum = this.isWorWh(uom) ? parseInt(state, 10) : parseFloat(state); + const value = this.isWorWh(uom) ? rawAsNum : rawAsNum * 1000; + const kValue = this.isWorWh(uom) ? this.convertToKillo(rawAsNum, 3) : rawAsNum; + const displayK = this.convertToKillo(value, dp); + + const displayUom = this.isWorWh(uom) ? uom : this.getStandardisedUom(`k${uom}`); + const displayKUom = this.isWorWh(uom) ? this.getStandardisedUom(`k${uom}`) : uom; + + let display = 0; + let displayStr = ""; + let displayUnit = ""; - states.socPercent.value = parseInt(states.socPercent.rawState, 10); - states.socPercent.display = parseInt(states.socPercent.rawState, 10); - states.socPercent.displayStr = `${parseInt(states.socPercent.rawState, 10)}%`; - - const batteryPowerReservePercent = parseInt(states.batteryPowerReservePercent.rawState, 10); - states.batteryPowerReservePercent.value = batteryPowerReservePercent; - states.batteryPowerReservePercent.display = batteryPowerReservePercent; - states.batteryPowerReservePercent.displayStr = `${batteryPowerReservePercent}%`; - - const batteryCapacityKwh = parseFloat(states.batteryCapacity.rawState); - const batteryCapacityWh = batteryCapacityKwh * 1000; - states.batteryCapacity.kWh = batteryCapacityKwh; - states.batteryCapacity.Wh = batteryCapacityWh; - - const batteryPowerReserveEnergyWh = Math.round(batteryCapacityWh * (batteryPowerReservePercent / 100)) - const batteryPowerReserveEnergyKWh = this.convertToKillo(batteryPowerReserveEnergyWh, 3); - states.batteryPowerReserveEnergy.Wh = batteryPowerReserveEnergyWh; - states.batteryPowerReserveEnergy.kWh = batteryPowerReserveEnergyKWh; - - const batteryPowerRaw = (states.batteryPower.uom === "W") ? parseInt(states.batteryPower.rawState, 10) : parseFloat(states.batteryPower.rawState); - const batteryPowerW = (states.batteryPower.uom === "W") ? batteryPowerRaw : batteryPowerRaw * 1000; - const batteryPowerKW = (states.batteryPower.uom === "W") ? this.convertToKillo(batteryPowerRaw, 3) : batteryPowerRaw; - states.batteryPower.w = batteryPowerW; - states.batteryPower.kW = batteryPowerKW; - - const chargePowerRaw = (states.chargePower.uom === "W") ? parseInt(states.chargePower.rawState, 10) : parseFloat(states.chargePower.rawState); - const chargePowerW = (states.chargePower.uom === "W") ? chargePowerRaw : chargePowerRaw * 1000; - const chargePowerKW = (states.chargePower.uom === "W") ? this.convertToKillo(chargePowerRaw, 3) : chargePowerRaw; - states.chargePower.w = chargePowerW; - states.chargePower.kW = chargePowerKW; - - const dischargePowerRaw = (states.dischargePower.uom === "W") ? parseInt(states.dischargePower.rawState, 10) : parseFloat(states.dischargePower.rawState); - const dischargePowerW = (states.dischargePower.uom === "W") ? dischargePowerRaw : dischargePowerRaw * 1000; - const dischargePowerKW = (states.dischargePower.uom === "W") ? this.convertToKillo(dischargePowerRaw, 3) : dischargePowerRaw; - states.dischargePower.w = dischargePowerW; - states.dischargePower.kW = dischargePowerKW; - - const socEnergyRaw = (states.socEnergy.uom === "Wh") ? parseInt(states.socEnergy.rawState, 10) : parseFloat(states.socEnergy.rawState); - const socEnergyWh = (states.socEnergy.uom === "Wh") ? socEnergyRaw : socEnergyRaw * 1000; - const socEnergyKWh = (states.socEnergy.uom === "Wh") ? this.convertToKillo(socEnergyRaw, 3) : socEnergyRaw; - states.socEnergy.Wh = socEnergyWh; - states.socEnergy.kWh = socEnergyKWh; - - const chargeRateRaw = (states.chargeRate.uom === "W") ? parseInt(states.chargeRate.rawState, 10) : parseFloat(states.chargeRate.rawState); - const chargeRateW = (states.chargeRate.uom === "W") ? chargeRateRaw : chargeRateRaw * 1000; - const chargeRateKW = (states.chargeRate.uom === "W") ? this.convertToKillo(chargeRateRaw, 3) : chargeRateRaw; - states.chargeRate.w = chargeRateW; - states.chargeRate.kW = chargeRateKW; - - const dischargeRateRaw = (states.dischargeRate.uom === "W") ? parseInt(states.dischargeRate.rawState, 10) : parseFloat(states.dischargeRate.rawState); - const dischargeRateW = (states.dischargeRate.uom === "W") ? dischargeRateRaw : dischargeRateRaw * 1000; - const dischargeRateKW = (states.dischargeRate.uom === "W") ? this.convertToKillo(dischargeRateRaw, 3) : dischargeRateRaw; - states.dischargeRate.w = dischargeRateW; - states.dischargeRate.kW = dischargeRateKW; - - // format displays switch(displayType) { case DISPLAY_TYPE_OPTIONS.WH: default: - states.batteryCapacity.display = batteryCapacityWh; - states.batteryCapacity.displayStr = `${batteryCapacityWh} Wh`; - states.batteryPower.display = (displayAbsPower) ? Math.abs(batteryPowerW) : batteryPowerW; - states.batteryPower.displayStr = `${(displayAbsPower) ? Math.abs(batteryPowerW) : batteryPowerW} W`; - states.chargePower.display = chargePowerW; - states.chargePower.displayStr = `${chargePowerW} W`; - states.dischargePower.display = dischargePowerW; - states.dischargePower.displayStr = `${dischargePowerW} W`; - states.socEnergy.display = socEnergyWh; - states.socEnergy.displayStr = `${socEnergyWh} Wh`; - states.batteryPowerReserveEnergy.display = batteryPowerReserveEnergyWh; - states.batteryPowerReserveEnergy.displayStr = `${batteryPowerReserveEnergyWh} Wh`; - states.batteryPower.displayUnit = 'W'; - states.chargeRate.display = chargeRateW; - states.chargeRate.displayStr = `${chargeRateW} W`; - states.dischargeRate.display = dischargeRateW; - states.dischargeRate.displayStr = `${dischargeRateW} W`; + display = (displayAbsPower) ? Math.abs(value) : value; + displayStr = `${(displayAbsPower) ? Math.abs(value) : value} ${displayUom}`; + displayUnit = displayUom; break; case DISPLAY_TYPE_OPTIONS.KWH: - states.batteryCapacity.display = batteryCapacityKwh; - states.batteryCapacity.displayStr = `${batteryCapacityKwh} kWh`; - states.batteryPower.display = (displayAbsPower) ? this.convertToKillo(Math.abs(batteryPowerW), dp) : this.convertToKillo(batteryPowerW, dp); - states.batteryPower.displayStr = `${(displayAbsPower) ? this.convertToKillo(Math.abs(batteryPowerW), dp) : this.convertToKillo(batteryPowerW, dp)} kW`; - states.chargePower.display = this.convertToKillo(chargePowerW, dp); - states.chargePower.displayStr = `${this.convertToKillo(chargePowerW, dp)} kW`; - states.dischargePower.display = this.convertToKillo(dischargePowerW, dp); - states.dischargePower.displayStr = `${this.convertToKillo(dischargePowerW, dp)} kW`; - states.socEnergy.display = socEnergyKWh; - states.socEnergy.displayStr = `${socEnergyKWh} kWh`; - states.batteryPowerReserveEnergy.display = this.convertToKillo(batteryPowerReserveEnergyWh, dp); - states.batteryPowerReserveEnergy.displayStr = `${this.convertToKillo(batteryPowerReserveEnergyWh, dp)} kWh`; - states.batteryPower.displayUnit = 'kW'; - states.chargeRate.display = this.convertToKillo(chargeRateW, dp); - states.chargeRate.displayStr = `${this.convertToKillo(chargeRateW, dp)} kW`; - states.dischargeRate.display = this.convertToKillo(dischargeRateW, dp); - states.dischargeRate.displayStr = `${this.convertToKillo(dischargeRateW, dp)} kW`; + display = (displayAbsPower) ? Math.abs(displayK) : displayK; + displayStr = `${(displayAbsPower) ? Math.abs(displayK) : displayK} ${displayKUom}`; + displayUnit = displayKUom; break; case DISPLAY_TYPE_OPTIONS.DYNAMIC: - states.batteryCapacity.display = (Math.abs(batteryCapacityWh) >= 1000) ? batteryCapacityKwh : batteryCapacityWh; - states.batteryCapacity.displayStr = (Math.abs(batteryCapacityWh) >= 1000) ? `${batteryCapacityKwh} kWh` : `${batteryCapacityWh} Wh`; - - states.batteryPower.display = (Math.abs(batteryPowerW) >= 1000) ? ((displayAbsPower) ? this.convertToKillo(Math.abs(batteryPowerW), dp) : this.convertToKillo(batteryPowerW, dp)) : ((displayAbsPower) ? Math.abs(batteryPowerW) : batteryPowerW); - states.batteryPower.displayStr = (Math.abs(batteryPowerW) >= 1000) ? `${(displayAbsPower) ? this.convertToKillo(Math.abs(batteryPowerW), dp) : this.convertToKillo(batteryPowerW, dp)} kW` : `${(displayAbsPower) ? Math.abs(batteryPowerW) : batteryPowerW} W`; - states.chargePower.display = (Math.abs(chargePowerW) >= 1000) ? this.convertToKillo(chargePowerW, dp) : chargePowerW; - states.chargePower.displayStr = (Math.abs(chargePowerW) >= 1000) ? `${this.convertToKillo(chargePowerW, dp)} kW` : `${chargePowerW} W`; - states.dischargePower.display = (Math.abs(dischargePowerW) >= 1000) ? this.convertToKillo(dischargePowerW, dp) : dischargePowerW; - states.dischargePower.displayStr = (Math.abs(dischargePowerW) >= 1000) ? `${this.convertToKillo(dischargePowerW, dp)} kW` : `${dischargePowerW} W`; - states.batteryPower.displayUnit = (Math.abs(batteryPowerW) >= 1000) ? 'kW' : 'W'; - - states.batteryPowerReserveEnergy.display = (Math.abs(batteryPowerReserveEnergyWh) >= 1000) ? this.convertToKillo(batteryPowerReserveEnergyWh, dp) : batteryPowerReserveEnergyWh; - states.batteryPowerReserveEnergy.displayStr = (Math.abs(batteryPowerReserveEnergyWh) >= 1000) ? `${this.convertToKillo(batteryPowerReserveEnergyWh, dp)} kWh` : `${batteryPowerReserveEnergyWh} Wh`; - - states.socEnergy.display = (Math.abs(socEnergyWh) >= 1000) ? socEnergyKWh : socEnergyWh; - states.socEnergy.displayStr = (Math.abs(socEnergyWh) >= 1000) ? `${socEnergyKWh} kWh` : `${socEnergyWh} Wh`; - - states.chargeRate.display = (Math.abs(chargeRateW) >= 1000) ? this.convertToKillo(chargeRateW, dp) : chargeRateW; - states.chargeRate.displayStr = (Math.abs(chargeRateW) >= 1000) ? `${this.convertToKillo(chargeRateW, dp)} kW` : `${chargeRateW} W`; - states.dischargeRate.display = (Math.abs(dischargeRateW) >= 1000) ? this.convertToKillo(dischargeRateW, dp) : dischargeRateW; - states.dischargeRate.displayStr = (Math.abs(dischargeRateW) >= 1000) ? `${this.convertToKillo(dischargeRateW, dp)} kW` : `${dischargeRateW} W`; + display = (Math.abs(value) >= 1000) ? ((displayAbsPower) ? Math.abs(displayK) : displayK) : ((displayAbsPower) ? Math.abs(value) : value); + displayStr = (Math.abs(value) >= 1000) ? `${(displayAbsPower) ? Math.abs(displayK) : displayK} ${displayKUom}` : `${(displayAbsPower) ? Math.abs(value) : value} ${displayUom}`; + displayUnit = (Math.abs(value) >= 1000) ? displayKUom : displayUom; break; } - this.calculatedStates = states - + return { + rawState: state, + uom: uom, + value: value, + kValue: kValue, + display: display, + displayStr: displayStr, + displayUnit: displayUnit, + } } - renderReserveAndCapacity(): TemplateResult { - return html` -