Skip to content

Commit

Permalink
feat: add new inverted_entities option (see readme) (#43)
Browse files Browse the repository at this point in the history
* feat: add new `inverted_entities` option (see readme)

* disable nested-ternary error and warning

* move away from nested ternaries

* remove linting override
  • Loading branch information
ulic75 authored May 13, 2022
1 parent fa16d3d commit 53200bb
Show file tree
Hide file tree
Showing 4 changed files with 109 additions and 31 deletions.
41 changes: 31 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,15 @@ I recommend looking at the [Example usage section](#example-usage) to understand

#### Card options

| Name | Type | Default | Description |
| -------------- | -------- | :----------: | --------------------------------------------------------------------------------------------------------------------------------------- |
| type | `string` | **required** | `custom:power-flow-card`. |
| entities | `object` | **required** | One or more sensor entities, see [entities object](#entities-object) for additional entity options. |
| kw_decimals | `number` | 1 | Number of decimals rounded to when kilowatts are displayed |
| min_flow_rate | `number` | .75 | Represents the fastest amount of time in seconds for a flow dot to travel from one end to the other, see [flow formula](#flow-formula). |
| max_flow_rate | `number` | 6 | Represents the slowest amount of time in seconds for a flow dot to travel from one end to the other, see [flow formula](#flow-formula). |
| watt_threshold | `number` | 0 | The number of watts to display before converting to and displaying kilowatts. Setting of 0 will always display in kilowatts. |
| Name | Type | Default | Description |
| ----------------- | -------- | :----------: | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| type | `string` | **required** | `custom:power-flow-card`. |
| entities | `object` | **required** | One or more sensor entities, see [entities object](#entities-object) for additional entity options. |
| inverted_entities | `string` | | Comma seperated list of entities that should be inverted (negative for consumption and positive for production). See [example usage](#inverted-entities-example). |
| kw_decimals | `number` | 1 | Number of decimals rounded to when kilowatts are displayed. |
| min_flow_rate | `number` | .75 | Represents the fastest amount of time in seconds for a flow dot to travel from one end to the other, see [flow formula](#flow-formula). |
| max_flow_rate | `number` | 6 | Represents the slowest amount of time in seconds for a flow dot to travel from one end to the other, see [flow formula](#flow-formula). |
| watt_threshold | `number` | 0 | The number of watts to display before converting to and displaying kilowatts. Setting of 0 will always display in kilowatts. |

#### Entities object

Expand All @@ -82,7 +83,9 @@ Can be use with either Grid or Battery configuration. The same `unit_of_measurem

### Example usage

#### Using combined entities for grid and battery that support positive state values for consumption and negative state values for production.
#### Combined Entites Example

Using combined entities for grid, battery and solor that support positive state values for consumption and negative state values for production.

```yaml
type: custom:power-flow-card
Expand All @@ -95,7 +98,25 @@ entities:
max_flow_rate: 10
```

#### Using split entities for grid and battery where each consumption and production entity state has a positive value.
#### Inverted Entities Example

Using combined entites as above but where the battery and grid entities are inverted (negative = consumption and positive = production).

```yaml
type: custom:power-flow-card
title: Power Distribution
entities:
battery: sensor.battery_in_out
battery_charge: sensor.battery_percent
grid: sensor.grid_in_out
solar: sensor.solar_out
inverted_entities: battery, grid
max_flow_rate: 10
```

#### Split Entites Example

Using split entities for grid and battery where each consumption and production entity state has a positive value.

```yaml
type: custom:power-flow-card
Expand Down
1 change: 1 addition & 0 deletions src/power-flow-card-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export interface PowerFlowCardConfig extends LovelaceCardConfig {
};
solar?: string;
};
inverted_entities: string | string[];
kw_decimals: number;
min_flow_rate: number;
max_flow_rate: number;
Expand Down
77 changes: 56 additions & 21 deletions src/power-flow-card.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* eslint no-nested-ternary: warn */
/* eslint-disable no-nested-ternary */
import {
mdiArrowDown,
mdiArrowLeft,
Expand All @@ -18,7 +18,7 @@ import { css, html, LitElement, svg, TemplateResult } from "lit";
import { customElement, property, query, state } from "lit/decorators.js";
import { classMap } from "lit/directives/class-map.js";
import { PowerFlowCardConfig } from "./power-flow-card-config.js";
import { coerceNumber, round } from "./utils.js";
import { coerceNumber, coerceStringArray, round } from "./utils.js";

const CIRCLE_CIRCUMFERENCE = 238.76104;
const KW_DECIMALS = 1;
Expand All @@ -39,6 +39,7 @@ export class PowerFlowCard extends LitElement {
setConfig(config: PowerFlowCardConfig): void {
this._config = {
...config,
inverted_entities: coerceStringArray(config.inverted_entities, ","),
kw_decimals: coerceNumber(config.kw_decimals, KW_DECIMALS),
min_flow_rate: coerceNumber(config.min_flow_rate, MIN_FLOW_RATE),
max_flow_rate: coerceNumber(config.max_flow_rate, MAX_FLOW_RATE),
Expand All @@ -50,6 +51,9 @@ export class PowerFlowCard extends LitElement {
return 3;
}

private entityInverted = (entityName: "battery" | "grid" | "solar") =>
this._config!.inverted_entities.includes(entityName);

private previousDur: { [name: string]: number } = {};

private circleRate = (value: number, total: number): number => {
Expand Down Expand Up @@ -91,35 +95,66 @@ export class PowerFlowCard extends LitElement {
const batteryChargeState = entities.battery_charge?.length
? this.getEntityState(entities.battery_charge)
: null;
const solarState = Math.max(this.getEntityStateWatts(entities.solar), 0);

const solarToGrid = hasReturnToGrid
? typeof entities.grid === "string"
? Math.abs(Math.min(this.getEntityStateWatts(entities.grid), 0))
: this.getEntityStateWatts(entities.grid.production)
: 0;
const solarState = this.entityInverted("solar")
? Math.abs(Math.min(this.getEntityStateWatts(entities.solar), 0))
: Math.max(this.getEntityStateWatts(entities.solar), 0);

let solarToGrid = 0;
if (hasReturnToGrid) {
if (typeof entities.grid === "string") {
if (this.entityInverted("grid"))
solarToGrid = Math.max(this.getEntityStateWatts(entities.grid), 0);
else
solarToGrid = Math.abs(
Math.min(this.getEntityStateWatts(entities.grid), 0)
);
} else solarToGrid = this.getEntityStateWatts(entities.grid.production);
}

const batteryToHome =
typeof entities.battery === "string"
? Math.max(this.getEntityStateWatts(entities.battery), 0)
: this.getEntityStateWatts(entities.battery?.consumption);
let batteryToHome = 0;
if (typeof entities.battery === "string") {
if (this.entityInverted("battery"))
batteryToHome = Math.abs(
Math.min(this.getEntityStateWatts(entities.battery), 0)
);
else
batteryToHome = Math.max(this.getEntityStateWatts(entities.battery), 0);
} else {
batteryToHome = this.getEntityStateWatts(entities.battery?.consumption);
}

const gridToHome =
typeof entities.grid === "string"
? Math.max(this.getEntityStateWatts(entities.grid), 0)
: this.getEntityStateWatts(entities.grid.consumption);
let gridToHome = 0;
if (typeof entities.grid === "string") {
if (this.entityInverted("grid"))
gridToHome = Math.abs(
Math.min(this.getEntityStateWatts(entities.grid), 0)
);
else gridToHome = Math.max(this.getEntityStateWatts(entities.grid), 0);
} else {
gridToHome = this.getEntityStateWatts(entities.grid.consumption);
}

const solarToBattery =
typeof entities.battery === "string"
? Math.abs(Math.min(this.getEntityStateWatts(entities.battery), 0))
: this.getEntityStateWatts(entities.battery?.production);
let solarToBattery = 0;
if (typeof entities.battery === "string") {
if (this.entityInverted("battery"))
solarToBattery = Math.max(
this.getEntityStateWatts(entities.battery),
0
);
else
solarToBattery = Math.abs(
Math.min(this.getEntityStateWatts(entities.battery), 0)
);
} else {
solarToBattery = this.getEntityStateWatts(entities.battery?.production);
}

const solarToHome = solarState - solarToGrid - solarToBattery;

const homeConsumption = batteryToHome + gridToHome + solarToHome;
const totalConsumption = homeConsumption + solarToBattery + solarToGrid;

// eslint-disable-next-line prefer-const
let homeBatteryCircumference: number | undefined;
if (hasBattery)
homeBatteryCircumference =
Expand Down
21 changes: 21 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,27 @@ export function coerceNumber(value: any, fallbackValue = 0) {
return _isNumberValue(value) ? Number(value) : fallbackValue;
}

export function coerceStringArray(
value: any,
separator: string | RegExp = /\s+/
): string[] {
const result: string[] = [];

if (value != null) {
const sourceValues = Array.isArray(value)
? value
: `${value}`.split(separator);
for (const sourceValue of sourceValues) {
const trimmedString = `${sourceValue}`.trim();
if (trimmedString) {
result.push(trimmedString);
}
}
}

return result;
}

export const round = (value: number, decimalPlaces: number): number =>
Number(
`${Math.round(Number(`${value}e${decimalPlaces}`))}e-${decimalPlaces}`
Expand Down

0 comments on commit 53200bb

Please sign in to comment.