Skip to content

Commit

Permalink
Migrate configuration format to allow multiple meters
Browse files Browse the repository at this point in the history
  • Loading branch information
bokub committed Feb 22, 2024
1 parent adcbf2e commit 3b65288
Show file tree
Hide file tree
Showing 10 changed files with 248 additions and 115 deletions.
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v18.15.0
v20.11.1
72 changes: 47 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,23 +39,36 @@ Pour utiliser cet add-on, il vous faut :

## Configuration

Une fois l'add-on installé, rendez-vous dans l'onglet _Configuration_ et remplissez les champs vides
Une fois l'add-on installé, rendez-vous dans l'onglet _Configuration_.

### Si vous voulez récupérer votre consommation
La configuration YAML de base comporte 2 compteurs :

- `consumption PRM` : Votre numéro de PRM (14 chiffres) pour la consommation.
- Si vous ne le connaissez pas, entrez votre token sur [la page exemples](https://conso.boris.sh/exemples) de Conso API et le PRM s'affichera dans le champ _PRM_
- Vous pouvez également le trouver sur votre compteur en appuyant sur la touche **+** jusqu’à lire la valeur du _numéro de PRM_.
- `consumption token` : Votre token **Conso API**
- `consumption name` : Choisissez le nom qui sera affiché dans les tableaux de bord d'énergie. Vous pourrez le changer plus tard si vous le souhaitez.
- `consumption action` : Laissez la valeur par défaut: `sync`
- Le premier pour mesurer la **consommation** d'énergie
- Le deuxième pour mesurer la **production** d'énergie

Vous pouvez effacer les lignes correspondant à la production si vous ne produisez pas d'électricité, ou rajouter d'autres compteurs si vous en avez besoin.

### Si vous produisez de l'électricité et voulez récupérer votre production
Pour chaque compteur, remplissez les champs suivants :

- `production PRM` : Votre numéro de PRM (14 chiffres) pour la production. Selon les cas, il peut être identique ou différent de celui qui gère la consommation.
- `production token` : Votre token **Conso API** correspondant au PRM de production
- `production name` : Choisissez le nom qui sera affiché dans les tableaux de bord d'énergie. Vous pourrez le changer plus tard si vous le souhaitez.
- `production action` : Laissez la valeur par défaut: `sync`
- `prm` : Votre numéro de PRM (14 chiffres).
- Si vous ne le connaissez pas, entrez votre token sur [la page exemples](https://conso.boris.sh/exemples) de Conso API et le PRM s'affichera dans le champ _PRM_
- Vous pouvez également le trouver sur votre compteur en appuyant sur la touche **+** jusqu’à lire la valeur du _numéro de PRM_.
- Selon les cas, le PRM de consommation peut être identique ou différent de celui qui gère la production.
- Les 14 chiffres du PRM doivent être saisis entre guillemets `"`, comme dans l'exemple ci-dessous
- `token` : Votre token **Conso API**
- `name` : Choisissez le nom qui sera affiché dans les tableaux de bord d'énergie. Vous pourrez le changer plus tard si vous le souhaitez.
- `action` : Laissez la valeur par défaut: `sync`
- `production` : Choisissez `true` si le compteur gère la production d'énergie, `false` sinon

> Exemple de configuration pour un seul compteur, en consommation :
```yaml
- prm: '18435095771264'
token: abcdef.ghijklmnopqrs.tuvwxyz
name: Consommation Linky
action: sync
production: false
```
Appliquez les modifications et démarrez / redémarrez l'add-on si ce n'est pas déjà fait
Expand All @@ -78,7 +91,7 @@ Pour visualiser les données de **HA Linky** dans vos tableaux de bord d'énergi
- Cliquez [ici](https://my.home-assistant.io/redirect/config_energy/), ou ouvrez le menu _Paramètres_ / _Settings_, puis _Tableaux de bord_ / _Dashboards_, puis _Énergie_ / _Energy_
- Dans la section _Réseau électrique_ / _Electricity grid_, cliquez sur _Ajouter une consommation_ / _Add consumption_
- Choisissez la statistique correspondant au `consumption name` ou `production name` que vous avez choisi à l'étape de configuration
- Choisissez la statistique correspondant au `name` que vous avez choisi à l'étape de configuration
- Cliquez sur _Enregistrer_ / _Save_

### Bon à savoir
Expand All @@ -91,11 +104,11 @@ Pour visualiser les données de **HA Linky** dans vos tableaux de bord d'énergi

En cas de problème, il est toujours possible d'effacer toutes les données de consommation ou de production créées par **HA Linky**

Revenez sur l'onglet _Configuration_ de l'add-on et changez la valeur de `consumption action` (ou `production action`) à `reset`, puis appliquez les modifications et redémarrez l'add-on.
Revenez sur l'onglet _Configuration_ de l'add-on et changez la valeur de `action` à `reset` sur le compteur qui vous intéresse, puis appliquez les modifications et redémarrez l'add-on.

Ouvrez ensuite l'onglet _Journal_ / _Log_ pour vérifier que la remise à zéro s'est bien déroulée.

Au prochain démarrage, si `consumption action` (ou `production action`) est repassé à `sync`, **HA Linky** réimportera à nouveau toutes vos données. Cette manipulation peut surcharger le serveur de **Conso API**, ne l'utilisez donc que si nécessaire pour ne pas risquer un ban !
Au prochain démarrage, si `action` est repassé à `sync`, **HA Linky** réimportera à nouveau toutes vos données. Cette manipulation peut surcharger le serveur de **Conso API**, ne l'utilisez donc que si nécessaire pour ne pas risquer un ban !

## Installation standalone

Expand All @@ -104,7 +117,8 @@ Si votre installation de Home Assistant ne vous permet pas d'accéder au systèm
> [!NOTE]
> Cette méthode n'est pas recommandée et fait office de solution de **dépannage** pour les personnes ne voulant pas utiliser HAOS et son système d'add-ons.
>
> Ne me demandez pas d'aide si vous ne savez pas utiliser Docker, ou si votre cluster Kubernetes ne fonctionne pas. Vous avez choisi d'installer Home Assistant avec une méthode "avancée", essayez de vous débrouiller par vous-même !
> Vous avez choisi d'installer Home Assistant avec une méthode "avancée", vous devriez donc maitriser vos outils.
> Je ne fournis **pas d'aide** concernant l'utilisation de Docker, Kubernetes, ou tout système autre que HAOS.

### Setup

Expand All @@ -118,14 +132,22 @@ Créez ensuite un fichier nommé `options.json`, au format suivant, puis suivez

```json
{
"consumption PRM": "",
"consumption token": "",
"consumption name": "Linky consumption",
"consumption action": "sync",
"production PRM": "",
"production token": "",
"production name": "Linky production",
"production action": "sync"
"meters": [
{
"prm": "",
"token": "",
"name": "Linky consumption",
"action": "sync",
"production": false
},
{
"prm": "",
"token": "",
"name": "Linky production",
"action": "sync",
"production": true
}
]
}
```

Expand Down
37 changes: 19 additions & 18 deletions config.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: Linky
description: Sync Energy dashboards with your Linky smart meter
version: 1.2.0
version: 1.3.0
slug: linky
init: false
url: https:/bokub/ha-linky
Expand All @@ -12,22 +12,23 @@ arch:
- armv7
- i386
homeassistant_api: true
hassio_api: true
options:
consumption PRM: ''
consumption token: ''
consumption name: 'Linky consumption'
consumption action: sync
production PRM: ''
production token: ''
production name: 'Linky production'
production action: sync

meters:
- prm: ''
token: ''
name: 'Linky consumption'
action: 'sync'
production: false
- prm: ''
token: ''
name: 'Linky production'
action: 'sync'
production: true
schema:
consumption PRM: str?
consumption token: str?
consumption name: str
consumption action: list(sync|reset)
production PRM: str?
production token: str?
production name: str
production action: list(sync|reset)
meters:
- prm: str?
token: str?
name: str?
action: list(sync|reset)
production: bool?
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "ha-linky",
"private": true,
"version": "1.2.0",
"version": "1.3.0",
"type": "module",
"scripts": {
"build": "tsc",
Expand Down
66 changes: 66 additions & 0 deletions run.sh
Original file line number Diff line number Diff line change
@@ -1,3 +1,69 @@
#!/usr/bin/with-contenv bashio

CONFIG=$(bashio::addon.options)

# === Migrate 1.2.0 config ===

consumption_prm=$(echo "$CONFIG" | jq -r '."consumption PRM" // empty')
if [ ! -z "$consumption_prm" ]; then
bashio::addon.option 'meters[0].prm' "$consumption_prm"
echo -e "\e[33mMoved consumption PRM to meters configuration\e[0m"
fi

consumption_token=$(echo "$CONFIG" | jq -r '."consumption token" // empty')
if [ ! -z "$consumption_token" ]; then
bashio::addon.option 'meters[0].token' "$consumption_token"
echo -e "\e[33mMoved consumption token to meters configuration\e[0m"
fi

consumption_name=$(echo "$CONFIG" | jq -r '."consumption name" // empty')
if [ ! -z "$consumption_name" ]; then
bashio::addon.option 'meters[0].name' "$consumption_name"
echo -e "\e[33mMoved consumption name to meters configuration\e[0m"
fi

consumption_action=$(echo "$CONFIG" | jq -r '."consumption action" // empty')
if [ ! -z "$consumption_action" ]; then
bashio::addon.option 'meters[0].action' "$consumption_action"
echo -e "\e[33mMoved consumption action to meters configuration\e[0m"
fi

production_prm=$(echo "$CONFIG" | jq -r '."production PRM" // empty')
if [ ! -z "$production_prm" ]; then
bashio::addon.option 'meters[1].prm' "$production_prm"
echo -e "\e[33mMoved production PRM to meters configuration\e[0m"
fi

production_token=$(echo "$CONFIG" | jq -r '."production token" // empty')
if [ ! -z "$production_token" ]; then
bashio::addon.option 'meters[1].token' "$production_token"
echo -e "\e[33mMoved production token to meters configuration\e[0m"
fi

production_name=$(echo "$CONFIG" | jq -r '."production name" // empty')
if [ ! -z "$production_name" ]; then
bashio::addon.option 'meters[1].name' "$production_name"
echo -e "\e[33mMoved production name to meters configuration\e[0m"
fi

production_action=$(echo "$CONFIG" | jq -r '."production action" // empty')
if [ ! -z "$production_action" ]; then
bashio::addon.option 'meters[1].action' "$production_action"
echo -e "\e[33mMoved production action to meters configuration\e[0m"
fi

if [ ! -z "$consumption_prm" ] || [ ! -z "$consumption_token" ] || [ ! -z "$consumption_name" ] || [ ! -z "$consumption_action" ] || [ ! -z "$production_prm" ] || [ ! -z "$production_token" ] || [ ! -z "$production_name" ] || [ ! -z "$production_action" ]; then
bashio::addon.option '"consumption PRM"'
bashio::addon.option '"consumption token"'
bashio::addon.option '"consumption name"'
bashio::addon.option '"consumption action"'
bashio::addon.option '"production PRM"'
bashio::addon.option '"production token"'
bashio::addon.option '"production name"'
bashio::addon.option '"production action"'
echo -e "\e[33mRestarting add-on\e[0m"
bashio::addon.restart
fi

# Run HA Linky
node --experimental-modules dist/index.js
40 changes: 40 additions & 0 deletions src/config.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { expect, it, vi, describe, beforeEach } from 'vitest';
import { getUserConfig } from './config.js';
import { readFileSync } from 'node:fs';

vi.mock('node:fs', () => ({
readFileSync: vi.fn(),
}));

describe('getUserConfig', () => {
beforeEach(() => {
vi.mocked(readFileSync).mockReset();
});

it('Parses basic config correctly', () => {
vi.mocked(readFileSync).mockReturnValue(`{
"meters": [
{ "prm": "123", "token": "ccc", "name": "Conso", "action": "sync" },
{ "prm": "123", "token": "ppp", "name": "Prod", "action": "reset", "production": true }
]
}`);
expect(getUserConfig()).toEqual({
meters: [
{ action: 'sync', name: 'Conso', prm: '123', production: false, token: 'ccc' },
{ action: 'reset', name: 'Prod', prm: '123', production: true, token: 'ppp' },
],
});
});

it('Throws if a PRM is configured multiple times', () => {
vi.mocked(readFileSync).mockReturnValue(`{
"meters": [
{ "prm": "123", "token": "ccc", "name": "Conso", "action": "sync" },
{ "prm": "123", "token": "ppp", "name": "Prod", "action": "reset", "production": true },
{ "prm": "123", "token": "ddd", "name": "Clone", "action": "sync", "production": false }
]
}`);

expect(() => getUserConfig()).toThrowError('PRM 123 is configured multiple times in consumption mode');
});
});
79 changes: 40 additions & 39 deletions src/config.ts
Original file line number Diff line number Diff line change
@@ -1,54 +1,55 @@
import { readFileSync } from 'fs';
import { readFileSync } from 'node:fs';

export type MeterConfig = {
prm: string;
token: string;
name: string;
action: 'sync' | 'reset';
isProduction: boolean;
production: boolean;
};

export type UserConfig = {
consumption?: MeterConfig;
production?: MeterConfig;
};
export type UserConfig = { meters: MeterConfig[] };

export function getUserConfig(): UserConfig {
let parsed: { meters?: any[] } = {};

try {
const parsed: {
'consumption PRM'?: string;
'consumption token'?: string;
'consumption name'?: string;
'consumption action'?: string;
'production PRM'?: string;
'production token'?: string;
'production name'?: string;
'production action'?: string;
} = JSON.parse(readFileSync('/data/options.json', 'utf8'));

return {
consumption:
parsed['consumption PRM'] && parsed['consumption token']
? {
prm: parsed['consumption PRM'],
token: parsed['consumption token'],
name: parsed['consumption name'] || 'Linky consumption',
action: parsed['consumption action'] === 'reset' ? 'reset' : 'sync',
isProduction: false,
}
: undefined,
production:
parsed['production PRM'] && parsed['production token']
? {
prm: parsed['production PRM'],
token: parsed['production token'],
name: parsed['production name'] || 'Linky production',
action: parsed['production action'] === 'reset' ? 'reset' : 'sync',
isProduction: true,
}
: undefined,
};
parsed = JSON.parse(readFileSync('/data/options.json', 'utf8'));
} catch (e) {
throw new Error('Cannot read user configuration: ' + e.toString());
}

const result: UserConfig = { meters: [] };

if (parsed.meters && Array.isArray(parsed.meters) && parsed.meters.length > 0) {
for (const meter of parsed.meters) {
if (meter.prm && meter.token) {
result.meters.push({
prm: meter.prm.toString(),
token: meter.token,
name: meter.name || 'Linky',
action: meter.action === 'reset' ? 'reset' : 'sync',
production: meter.production === true,
});
}
}
}

for (const m in result.meters) {
for (const n in result.meters) {
if (
m !== n &&
result.meters[m].prm === result.meters[n].prm &&
result.meters[m].production === result.meters[n].production
) {
throw new Error(
`PRM ${result.meters[m].prm} is configured multiple times in ${
result.meters[m].production ? 'production' : 'consumption'
} mode`,
);
}
}
}

return result;
}
Loading

0 comments on commit 3b65288

Please sign in to comment.