Skip to content

Commit

Permalink
Merge pull request #309 from Pho3niX90/feature/dynamic_hiding
Browse files Browse the repository at this point in the history
Improve custom entity handling and add more unit handling
  • Loading branch information
slipx06 authored Mar 17, 2024
2 parents 1f896a1 + 55b01d5 commit eb078d7
Show file tree
Hide file tree
Showing 10 changed files with 596 additions and 987 deletions.
22 changes: 11 additions & 11 deletions dist/sunsynk-power-flow-card.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "sunsynk-power-flow-card",
"version": "4.24.0",
"version": "4.24.1",
"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": {
Expand Down
584 changes: 196 additions & 388 deletions src/cards/compact-card.ts

Large diffs are not rendered by default.

678 changes: 223 additions & 455 deletions src/cards/full-card.ts

Large diffs are not rendered by default.

64 changes: 64 additions & 0 deletions src/const.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,67 @@ export const enum SensorDeviceClass {
TEMPERATURE = "temperature",
VOLTAGE = "voltage"
}

export const enum UnitOfPower {
WATT = "W",
KILO_WATT = "kW",
MEGA_WATT = "MW",
BTU_PER_HOUR = "BTU/h",
}

export const enum UnitOfEnergy {
GIGA_JOULE = "GJ",
KILO_WATT_HOUR = "kWh",
MEGA_JOULE = "MJ",
MEGA_WATT_HOUR = "MWh",
WATT_HOUR = "Wh",
}

export const enum UnitOfElectricalCurrent {
MILLIAMPERE = "mA",
AMPERE = "A"
}

export const enum UnitOfElectricPotential {
MILLIVOLT = "mV",
VOLT = "V"
}

export type UnitOfEnergyOrPower = UnitOfEnergy | UnitOfPower;

type ConversionRule = {
threshold: number;
divisor: number;
targetUnit: UnitOfEnergyOrPower;
decimal?: number;
};

export const unitOfEnergyConversionRules: Record<UnitOfEnergyOrPower, ConversionRule[]> = {
[UnitOfEnergy.WATT_HOUR]: [{threshold: 1e6, divisor: 1e6, targetUnit: UnitOfEnergy.MEGA_WATT_HOUR}, {
threshold: 1e3,
divisor: 1e3,
targetUnit: UnitOfEnergy.KILO_WATT_HOUR,
decimal: 1
}],
[UnitOfEnergy.KILO_WATT_HOUR]: [{
threshold: 1e3,
divisor: 1e3,
targetUnit: UnitOfEnergy.MEGA_WATT_HOUR,
decimal: 2
}],
[UnitOfEnergy.MEGA_WATT_HOUR]: [],
[UnitOfEnergy.GIGA_JOULE]: [{threshold: 1e3, divisor: 1e3, targetUnit: UnitOfEnergy.MEGA_JOULE}],
[UnitOfEnergy.MEGA_JOULE]: [],
[UnitOfPower.WATT]: [{threshold: 1e6, divisor: 1e6, targetUnit: UnitOfPower.MEGA_WATT}, {
threshold: 1e3,
divisor: 1e3,
targetUnit: UnitOfPower.KILO_WATT,
}],
[UnitOfPower.KILO_WATT]: [{
threshold: 1e3,
divisor: 1e3,
targetUnit: UnitOfPower.MEGA_WATT,
}],
[UnitOfPower.MEGA_WATT]: [],
[UnitOfPower.BTU_PER_HOUR]: [],
};
6 changes: 6 additions & 0 deletions src/helpers/icons.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export const icons = {
ess: 'm15 13l-4 4v-3H2v-2h9V9l4 4M5 20v-4h2v2h10v-7.81l-5-4.5L7.21 10H4.22L12 3l10 9h-3v8H5Z',
essBat: 'M15 9h1V7.5h4V9h1c.55 0 1 .45 1 1v11c0 .55-.45 1-1 1h-6c-.55 0-1-.45-1-1V10c0-.55.45-1 1-1m1 2v3h4v-3h-4m-4-5.31l-5 4.5V18h5v2H5v-8H2l10-9l2.78 2.5H14v1.67l-.24.1L12 5.69Z',
essGrid: 'M5 20v-8H2l10-9l10 9h-3v8zm7-14.31l-5 4.5V18h10v-7.81zM11.5 18v-4H9l3.5-7v4H15z',
essPv: 'M11.6 3.45zM18.25 19.6v-7.6h2.85L11.6 3.45 2.1 12h2.85v7.6zM11.6 6.015l4.75 4.275V17.7H6.85v-7.41zM6.58 2.8v1.42L8 3.508zm-.4 2.4L5.2 6.184l1.5.5zM2.8 6.58 3.508 8l.712-1.42zM6 2.8H2.8v3.2c.228.068.468.1.708.1 1.432.004 2.596-1.16 2.6-2.6-.004-.236-.04-.472-.108-.7M12.5 3.844l2.25 2.026.5-.5-2.24-2.04zM17.71 8.53 18.2 8.04 15.76 5.84 15.26 6.34ZM20.52 11.09l.48-.49-2.31-2.14-.5.5z M18.1299 5.1169 17.318 4.6482l2.4492-1.6171-.75 1.299.8119.4687-2.4492 1.6171z'
}
87 changes: 20 additions & 67 deletions src/helpers/utils.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import {UnitOfEnergy, UnitOfPower, UnitOfEnergyOrPower, unitOfEnergyConversionRules} from '../const';

export class Utils {
static toNum(val: string | number, decimals: number = -1, invert: boolean = false): number {
let numberValue = Number(val);
Expand All @@ -22,85 +24,36 @@ export class Utils {
}
}

static convertValueNew(value: string | number, unit: string = '', decimal: number = 2) {

decimal = Number.isNaN(decimal) ? 2 : decimal;
let numberValue = Number(value);
if (Number.isNaN(numberValue)) {
return 0;
}

const units = ['W', 'kW', 'MW', 'Wh', 'kWh', 'MWh'];
static convertValueNew(value: string | number, unit: UnitOfEnergyOrPower | string = '', decimal: number = 2) {
decimal = isNaN(decimal) ? 2 : decimal;
const numberValue = Number(value);
if (isNaN(numberValue)) return 0;

// Find the index of the unit in the units array
const unitIndex = units.findIndex(u => u.toLowerCase() === unit.toLowerCase());
const rules = unitOfEnergyConversionRules[unit];
if (!rules) return `${Math.round(numberValue)} ${unit}`;

//console.log(`Input: ${value} ${unit}`);
//console.log(`Unit Index: ${unitIndex}`);
if (unit === UnitOfPower.WATT && Math.abs(numberValue) < 1000) {
return `${Math.round(numberValue)} ${unit}`;
};

// Check if the unit is in the allowed units array
if (unitIndex !== -1) {
// Perform the conversion based on the unit index
switch (unitIndex) {
case 0: // W
if (Math.abs(numberValue) >= 1e6) {
return `${(numberValue / 1e6).toFixed(decimal)} ${units[2]}`;
} else if (Math.abs(numberValue) >= 1e3) {
return `${(numberValue / 1e3).toFixed(decimal)} ${units[1]}`;
} else {
return `${Math.round(numberValue)} ${units[unitIndex]}`;
}
case 1: // kW
if (Math.abs(numberValue) >= 1e3) {
return `${(numberValue / 1e3).toFixed(decimal)} ${units[2]}`;
} else if (Math.abs(numberValue) < 1) {
return `${Math.round(numberValue * 1000)} ${units[0]}`;
} else {
return `${numberValue.toFixed(decimal)} ${units[unitIndex]}`;
}
case 2: // MW
if (Math.abs(numberValue) < 1) {
return `${(numberValue * 1000).toFixed(decimal)} ${units[1]}`;
} else {
return `${numberValue.toFixed(decimal)} ${units[unitIndex]}`;
}
case 3: // Wh
if (Math.abs(numberValue) >= 1e6) {
return `${(numberValue / 1e6).toFixed(1)} ${units[5]}`;
} else if (Math.abs(numberValue) >= 1e3) {
return `${(numberValue / 1e3).toFixed(1)} ${units[4]}`;
} else {
return `${numberValue.toFixed(1)} ${units[unitIndex]}`;
}
case 4: // kWh
if (Math.abs(numberValue) >= 1e3) {
return `${(numberValue / 1e3).toFixed(2)} ${units[5]}`;
//} else if (Math.abs(numberValue) < 1) {
// return `${(numberValue * 1000).toFixed(1)} ${units[3]}`;
} else {
return `${numberValue.toFixed(1)} ${units[unitIndex]}`;
}
case 5: // MWh
if (Math.abs(numberValue) < 1) {
return `${(numberValue * 1000).toFixed(1)} ${units[4]}`;
} else {
return `${numberValue.toFixed(1)} ${units[unitIndex]}`;
}
default:
return `${Math.round(numberValue)} ${unit}`;
for (const rule of rules) {
if (Math.abs(numberValue) >= rule.threshold) {
const convertedValue = (numberValue / rule.divisor).toFixed(rule.decimal || decimal);
return `${convertedValue} ${rule.targetUnit}`;
}
} else {
// If the unit is not in the allowed units, return the numeric value without unit
return Math.round(numberValue);
}

return `${numberValue.toFixed(decimal)} ${unit}`;
}

static handlePopup(e, entityId) {
if(!entityId)
return;
this._handleClick(e, {action: 'more-info'}, entityId);
}

private static _handleClick(event, actionConfig, entityId) {
if(!entityId || !event)
if (!entityId || !event)
return;
event.stopPropagation();
let e;
Expand Down
24 changes: 9 additions & 15 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {globalData} from './helpers/globals';
import {InverterFactory} from './inverters/inverter-factory';
import {BatteryIconManager} from './helpers/battery-icon-manager';
import {convertToCustomEntity, CustomEntity} from './inverters/dto/custom-entity';
import {icons} from './helpers/icons';

console.groupCollapsed(
`%c ⚡ SUNSYNK-POWER-FLOW-CARD %c ${localize('common.version')}: ${CARD_VERSION} `,
Expand Down Expand Up @@ -878,28 +879,23 @@ export class SunsynkPowerFlowCard extends LitElement {

//console.log(`${pvPercentageBat} % PV to charge battery, ${gridPercentageBat} % Grid to charge battery`);

const essBat = 'M15 9h1V7.5h4V9h1c.55 0 1 .45 1 1v11c0 .55-.45 1-1 1h-6c-.55 0-1-.45-1-1V10c0-.55.45-1 1-1m1 2v3h4v-3h-4m-4-5.31l-5 4.5V18h5v2H5v-8H2l10-9l2.78 2.5H14v1.67l-.24.1L12 5.69Z';
const essGrid = 'M5 20v-8H2l10-9l10 9h-3v8zm7-14.31l-5 4.5V18h10v-7.81zM11.5 18v-4H9l3.5-7v4H15z';
const essPv = 'M11.6 3.45zM18.25 19.6v-7.6h2.85L11.6 3.45 2.1 12h2.85v7.6zM11.6 6.015l4.75 4.275V17.7H6.85v-7.41zM6.58 2.8v1.42L8 3.508zm-.4 2.4L5.2 6.184l1.5.5zM2.8 6.58 3.508 8l.712-1.42zM6 2.8H2.8v3.2c.228.068.468.1.708.1 1.432.004 2.596-1.16 2.6-2.6-.004-.236-.04-.472-.108-.7M12.5 3.844l2.25 2.026.5-.5-2.24-2.04zM17.71 8.53 18.2 8.04 15.76 5.84 15.26 6.34ZM20.52 11.09l.48-.49-2.31-2.14-.5.5z M18.1299 5.1169 17.318 4.6482l2.4492-1.6171-.75 1.299.8119.4687-2.4492 1.6171z';
const ess = 'm15 13l-4 4v-3H2v-2h9V9l4 4M5 20v-4h2v2h10v-7.81l-5-4.5L7.21 10H4.22L12 3l10 9h-3v8H5Z';

let essIcon: string;
let essIconSize: number;
switch (true) {
case pvPercentageRaw >= 100 && batteryPercentageRaw <= 5 && (totalGridPower - nonessentialPower) < 50 && config.load.dynamic_icon:
essIcon = essPv;
essIcon = icons.essPv;
essIconSize = 1;
break;
case batteryPercentageRaw >= 100 && pvPercentageRaw <= 5 && (totalGridPower - nonessentialPower) < 50 && config.load.dynamic_icon:
essIcon = essBat;
essIcon = icons.essBat;
essIconSize = 0;
break;
case pvPercentageRaw < 5 && batteryPercentageRaw < 5 && config.load.dynamic_icon:
essIcon = essGrid;
essIcon = icons.essGrid;
essIconSize = 0;
break;
default:
essIcon = ess;
essIcon = icons.ess;
essIconSize = 0;
break;
}
Expand Down Expand Up @@ -1093,13 +1089,11 @@ export class SunsynkPowerFlowCard extends LitElement {
let entityString;

const props = String(entity).split(".");
const ent = props.length > 0 ? props[0] : null;
const prop = props.length > 1 ? props[1] : null;

if (ent && prop) {
entityString = this._config[ent][prop]
} else if (ent) {
entityString = this._config.entities[ent]
if (props.length > 1) {
entityString = this._config[props[0]][props[1]]
} else if (props.length > 0) {
entityString = this._config.entities[props[0]]
}

const state = entityString ? this.hass.states[entityString] : undefined;
Expand Down
23 changes: 19 additions & 4 deletions src/inverters/dto/custom-entity.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {HassEntity} from 'home-assistant-js-websocket/dist/types';
import {Utils} from '../../helpers/utils';
import {globalData} from '../../helpers/globals';
import {UnitOfElectricalCurrent, UnitOfEnergy, UnitOfPower} from '../../const';

/**
* CustomEntity interface represents a custom entity in Home Assistant.
Expand All @@ -19,7 +19,7 @@ export interface CustomEntity extends HassEntity {
toString(): string;

/**
* Checks that the state is not null or undefined
* Checks that the state is not null, undefined or unknown
*/
isValid(): boolean;

Expand All @@ -35,19 +35,34 @@ export interface CustomEntity extends HassEntity {
* @param invert
*/
toPower(invert?: boolean): number;

/**
* Auto converts the state to watts/kilowatts, with the suffix
* @param invert
* @param decimals
* @param scale
*/
toPowerString(scale?: boolean, decimals?: number, invert?: boolean): string;

getUOM(): UnitOfPower | UnitOfEnergy | UnitOfElectricalCurrent
}

// Function to convert HassEntity to CustomEntity
export function convertToCustomEntity(entity: any): CustomEntity {
return {
...entity,
toNum: (decimals?: number, invert?: boolean) => Utils.toNum(entity?.state, decimals, invert),
isValid: () => entity?.state !== null && entity.state !== undefined || false,
isValid: () => entity?.state !== null && entity.state !== undefined && entity.state !== 'unknown' || false,
notEmpty: () => entity?.state !== '' || false,
isNaN: () => Number.isNaN(entity?.state) || true,
toPower: (invert?: boolean) => (entity.attributes?.unit_of_measurement || '').toLowerCase() === 'kw'
? Utils.toNum(((entity?.state || '0') * 1000), 0, invert)
: Utils.toNum((entity?.state || '0'), 0, invert) || 0,
toString: () => entity?.state?.toString() || ''
toPowerString: (scale?: boolean, decimals?: number, invert?: boolean) =>
scale ?
Utils.convertValueNew(entity?.state, entity?.attributes?.unit_of_measurement, decimals || 0) :
`${Utils.toNum(entity?.state, 0, invert)} ${entity?.attributes?.unit_of_measurement || ''}`,
toString: () => entity?.state?.toString() || '',
getUOM: () => entity?.attributes?.unit_of_measurement || ''
}
}
Loading

0 comments on commit eb078d7

Please sign in to comment.