Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Separate entities #1

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.vscode/
89 changes: 52 additions & 37 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# LuxPower Distribution Card

<!-- [![hacs_badge](https://img.shields.io/badge/HACS-Default-41BDF5.svg?style=flat-square)](https://github.com/hacs/integration) -->
[![hacs_badge](https://img.shields.io/badge/HACS-Custom-41BDF5.svg)](https://github.com/hacs/integration)
[![hacs_badge](https://img.shields.io/badge/HACS-Default-41BDF5.svg?style=flat-square)](https://github.com/hacs/integration)
![GitHub release (latest by date)](https://img.shields.io/github/v/release/DanteWinters/lux-power-distribution-card?style=flat-square)
![Github stars](https://img.shields.io/github/stars/DanteWinters/lux-power-distribution-card?style=flat-square)
![Github issues](https://img.shields.io/github/issues/DanteWinters/lux-power-distribution-card?style=flat-square)
Expand All @@ -10,32 +9,21 @@ A simple power distribution card of an inverter and battery system, for [Home As

<img src="https://raw.githubusercontent.com/DanteWinters/lux-power-distribution-card/main/docs/images/full-card-allocated-power.png" width="450" />

# Installation
## Installation

## HACS
### HACS

There is a PR to add this card to the HACS defaults, but for now this card can be added by adding the URL as a custom repository source in HACS:
```
DanteWinters/lux-power-distribution-card
```

## Manual install

1. Download the four JavaScript files (`lux-power-distribution-card.js`, `config-entity-functions.js`, `html-functions.js` and `constants.js`) from the [latest release](https://github.com/DanteWinters/lux-power-distribution-card/releases/latest) and copy it into your `config/www` directory.
This card has been added to the list of default HACS frontend elements. Search for the name on HACS, and download from there to install.

2. Add the resource reference:
1. Visit the Resources page in your Home Assistant instance [![Open your Home Assistant instance and show your dashboard resources.](https://my.home-assistant.io/badges/lovelace_resources.svg)](https://my.home-assistant.io/redirect/lovelace_resources/)
2. Add `lux-power-distribution-card.js` as a JavaScript Module.
## Adding the card to the dashboard

### Configuration

# Adding the card to the dashboard

## Configuration
The following is a list of configs for the card:

**NOTE:** Please refer to the example config below for clarification on how the entities should be added.

### Required configurations
#### Required configurations

| Name | Type | Description |
|---|:---:|---|
Expand All @@ -45,7 +33,7 @@ The following is a list of configs for the card:
| home_consumption | entities | Output power of the inverter to your home. |
| grid_flow | entities | Power flowing to and from grid. Negative flow is import from grid, and positive flow is export to grid. |

### Optional configurations
#### Optional configurations

| Name | Type | Description |
|---|---|---|
Expand All @@ -60,26 +48,27 @@ The following is a list of configs for the card:
| inverter_alias | list of strings | This is used when there is more than 1 inverter. This will be the names used in the dropdown list. This or the lux dongle list is required. |
| refresh_button | string | The location of the refresh button. Can be 'left', 'right' or 'both'. See below for more information. **NOTE:** the refresh button will only show if the *lux_dongle* is added. |

### Sub-configs that are not a list of entities or values
#### Sub-configs that are not a list of entities or values

| Parent | Name | Type | Description |
|---|---|---|---|
| grid_indicator | hue | bool | If this is set to true and the grid voltage drops to 0, the grid image will become dimmer. (Requires a grid voltage entity.) |
| grid_indicator | dot | bool | If this is set to true and the grid voltage drops to 0, a red indicator will be added next to the grid voltage text. (Requires a grid voltage entity.) |
| update_time | show_last_update | bool | If the update time entity has a timestamp attribute, it can be used to show how long since the last update. |
| status_codes | no_grid_is_warning | bool | Some status codes (64, 136 and 192) are shown when grid is not available. If this value is true, these codes will show up as a warning on the status. If the value is false, these values will show up as normal. |
| parallel | average_voltage | bool | When using multiple inverters, there is a default created item on the list of inverters called "Parallel" that averages all the values from the different inverters. If *average_voltage* is true, the battery and grid voltages will be averaged and shown on the Parallel setting. Otherwise it will not show the voltages there. |
| parallel | parallel_first | bool | When using multiple inverters, there is a default created item on the list of inverters called "Parallel" that averages all the values from the different inverters. If *parallel_first* is true, the "Parallel" option will be shown first of the list, otherwise it will be last. |

# LuxpowerTek integration

The LuxpowerTek integration is hosted in a private repository by [Guy Wells](https://github.com/guybw)
### Example Configuration

## Configuration

If you have the Luxpower integration, you can use the following code directly (except for the energy_allocations, and change the dongle number):
If you have the LuxpowerTek integration, you can use the following code directly (except for the energy_allocations, and change the dongle number):

```yaml
type: custom:lux-power-distribution-card
inverter_count: 1
parallel:
average_voltage: true
parallel_first: true
battery_soc:
entities:
- sensor.lux_battery
Expand Down Expand Up @@ -127,6 +116,10 @@ energy_allocations:
- sensor.power_plug_4
```

## LuxpowerTek integration

The LuxpowerTek integration is hosted in a private repository by [Guy Wells](https://github.com/guybw).

## Refresh and the Dongle serial number

This refresh only works for the LuxPowerTek integration referenced above. The service name and function call format are hard-coded.
Expand All @@ -138,25 +131,25 @@ The location of the refresh button can be set with the *refresh_button_location*
- both (Displayed on both sides, as described on the above two points.)
- none (Removes the refresh button.)

# Energy Allocations Entities
## Energy Allocations Entities

The *energy_allocations* entities can be any entity that measures power. It will sum the values together and display on the card. The idea is to use this to track how much of the home's power usage is known.

# Grid indicators
## Grid indicators

Below are 2 pictures of the grid image. The first is the grid in a normal state, and the second is the grid image with both indicators active.

| Normal Grid | No Grid Input |
|---|---|
| <img src="https://raw.githubusercontent.com/DanteWinters/lux-power-distribution-card/main/docs/images/grid-normal.png" /> | <img src="https://raw.githubusercontent.com/DanteWinters/lux-power-distribution-card/main/docs/images/grid-no-ac.png" /> |

# Gallery
## Gallery

| The card with only required entities | The card with all required and optional entities | The card using all the LuxPower integration options and entities |
|---|---|---|
| <img src="https://raw.githubusercontent.com/DanteWinters/lux-power-distribution-card/main/docs/images/base-card.png" /> | <img src="https://raw.githubusercontent.com/DanteWinters/lux-power-distribution-card/main/docs/images/base-card-with-extras.png" /> | <img src="https://raw.githubusercontent.com/DanteWinters/lux-power-distribution-card/main/docs/images/full-card.png" /> |

# Interactive Card
## Interactive Card

The four entity images on the card can be clicked to display the history of the connected entity.

Expand All @@ -165,17 +158,39 @@ The four entity images on the card can be clicked to display the history of the
- Home Image: Home consumption entity's history.
- Grid Image: Grid flow entity's history.

# Parallel inverters
## Parallel inverters

With v1.0.0, support for parallel inverters have arrived! In order to use parallel inverters, simply indicate the number of inverters you are using in the config, and add the additional inverter's entities under their corresponding headers. Take note of the *inverter_alias* and *lux_dongle* config values when using parallel inverters.

### Known issues
- Currently there is no option to mix the values, but that is on the roadmap.
- The refresh button only works on the first dongle value added.
The status bar for the parallel inverters works as follows:

- If both inverters have normal status, it will display a normal status.
- If only one inverter has an non-normal status, the inverter alias will be displayed along with the non-normal status.
- If all the inverters have the same error (i.e. 'no-grid'), it will display this error on the parallel page.
- If there are multiple different error, the status will display as 'multiple errors' and you will need to go to the specific inverter to see the error.

## Known issues

### Card not loading issue

With this card, there has been multiple instances of the card not loading. From my experience, the best way to fix this is to clear the cache and it should load. I can give instructions for both Android mobile devices and the browser.

#### Android

1. Find *Home Assistant* in the list of apps in settings.
2. This step may differ depending on the Android device. Find anything that indicates data used or storage.
3. When there, find the option to clear all the data (cache and storage). Clearing this will log you out of the app and you'll need to log in again.
4. Card should then show up. If it doesn't, please log a bug.

#### Web browser

1. With the page open, open the developer console on the browser. Usually it's *F12*.
2. Click on the refresh button.
3. Rick-click on the refresh button to open a menu.
4. Choose the option *Empty Cache and Hard Reload*, or the option closest to this description.

# Developer's note
## Developer's note

Although the card is functional and even has a few nice features, the development of it was done with a lot of inexperience. From my side, I do not have JavaScript or HTML experience other than this card. For this reason, there may be many ways I implemented things that aren't optimal or safe. If you are knowledgeable in and willing to look through the code, and advice and help will be much appreciated.

In addition, I currently only have 1 inverter. So the tests for the parallel inverters were done purely in a testing environment and may have some bugs.
In addition, I currently only have 1 inverter. So the tests for the parallel inverters were done purely in a testing environment and may have some bugs.
88 changes: 81 additions & 7 deletions config-entity-functions.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import * as constants from "./constants.js";
export function buildConfig(config) {
let new_config = constants.base_config;

new_config.header = config.header;

// Check inverter count
let inverter_count = parseInt(config.inverter_count);
if (isNaN(inverter_count) || inverter_count <= 0) {
Expand Down Expand Up @@ -87,6 +89,18 @@ export function buildConfig(config) {
}
}

// Check parallel settings
if (config.parallel) {
if (config.parallel.average_voltage) {
new_config.parallel.average_voltage = true;
}
if (config.parallel.parallel_first) {
new_config.parallel.parallel_first = true;
} else {
new_config.parallel.parallel_first = false;
}
}

validateConfig(new_config);

return new_config;
Expand Down Expand Up @@ -140,23 +154,84 @@ function importConfigValues(config, new_config, inverter_count, object_name) {
}
}

export function getEntity(config, hass, config_entity, index) {
const entityConfig = config[config_entity].entities[index];
if (typeof entityConfig === "string") {
try {
return hass.states[entityConfig];
} catch (error) {
throw new Error(`Invalid entity: ${entityConfig}`);
}
}

if (typeof entityConfig.consumption === "string" && typeof entityConfig.production === "string") {
const consumptionValue = parseInt(getEntitiesStateValue(hass.states[entityConfig.consumption]));
const productionValue = parseInt(getEntitiesStateValue(hass.states[entityConfig.production]));

if (typeof consumptionValue === "number" && consumptionValue > 0) {3
try {
return hass.states[entityConfig.consumption];
} catch (error) {
throw new Error(`Invalid entity: ${entityConfig.consumption}`);
}
}
try {
return hass.states[entityConfig.production];
} catch (error) {
throw new Error(`Invalid entity: ${entityConfig.production}`);
}
}
}

export function getEntitiesState(config, hass, config_entity, index) {
const entity = hass.states[config[config_entity].entities[index]];

const entity = getEntity(config, hass, config_entity, index);
let value = getEntitiesStateValue(entity);

const entityConfig = config[config_entity].entities[index];
if (typeof entityConfig !== "string" && typeof entityConfig.consumption === "string" && typeof entityConfig.production === "string") {
const consumptionValue = parseInt(getEntitiesStateValue(hass.states[entityConfig.consumption]));
const productionValue = parseInt(getEntitiesStateValue(hass.states[entityConfig.production]));

if (typeof consumptionValue === "number" && consumptionValue > 0) {
value *= -1;
}
}

return value;
}

function getEntitiesStateValue(entity) {
if (entity.state) {
if (entity.state === "unavailable" || entity.state === "unknown") {
return "-";
} else if (isNaN(entity.state)) {
return entity.state;
} else {
return entity.state;
}
}
return "-";
}

export function getEntitiesNumState(config, hass, config_entity, index, is_int = true, is_avg = false) {
let value = 0;
if (index == -1) {
for (let i = 0; i < config.inverter_count; i++) {
value += parseFloat(getEntitiesState(config, hass, config_entity, i));
}
if (is_avg) {
value = value / config.inverter_count;
}
} else {
value = parseFloat(getEntitiesState(config, hass, config_entity, index));
}
if (is_int) {
return parseInt(value);
}
return Math.round(value * 100) / 100;
}

export function getEntitiesAttribute(config, hass, config_entity, attribute_name, index) {
const entity = hass.states[config[config_entity].entities[index]];
const entity = getEntity(config, hass, config_entity, index);

if (entity.attributes && entity.attributes[attribute_name]) {
return entity.attributes[attribute_name];
Expand All @@ -166,13 +241,12 @@ export function getEntitiesAttribute(config, hass, config_entity, attribute_name
}

export function getEntitiesUnit(config, hass, config_entity, index) {
const entity = hass.states[config[config_entity].entities[index]];
const entity = getEntity(config, hass, config_entity, index);

if (entity.state) {
if (isNaN(entity.state)) return "-";
else return entity.attributes.unit_of_measurement ?? "";
}
return "";
}

export function getStatusMessage(status_code, show_no_grid_as_warning) {
Expand Down Expand Up @@ -244,5 +318,5 @@ export function getStatusMessage(status_code, show_no_grid_as_warning) {
break;
}

return `Status: ${message} ${indicator}`;
return `${message} ${indicator}`;
}
9 changes: 7 additions & 2 deletions constants.js

Large diffs are not rendered by default.

Loading