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

Ajout prod compteur linky #10

Merged
merged 12 commits into from
Nov 15, 2023
14 changes: 10 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,18 @@ Pour utiliser cet add-on, il vous faut :

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

- `consumption PRM` : Votre numéro de PRM (14 chiffres).
- `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`
- `production PRM` : Votre numéro de PRM (14 chiffres) pour la production.
- 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_.
- `production token` : Votre token **Conso API**
- `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`

Appliquez les modifications et démarrez / redémarrez l'add-on si ce n'est pas déjà fait

Expand All @@ -69,7 +75,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` que vous avez choisi à l'étape de configuration
- Choisissez la statistique correspondant au `consumption name` et/ou `production name` que vous avez choisi à l'étape de configuration
- Cliquez sur _Enregistrer_ / _Save_

### Bon à savoir
Expand All @@ -82,8 +88,8 @@ 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 créées par **HA Linky**

Revenez sur l'onglet _Configuration_ de l'add-on et changez la valeur de `consumption 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 `production/consumption action` à `reset`, 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` 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 `production/consumption 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 !
11 changes: 10 additions & 1 deletion 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.1.0
version: 1.2.0
slug: linky
init: false
url: https:/bokub/ha-linky
Expand All @@ -17,8 +17,17 @@ options:
consumption token: ''
consumption name: 'Linky consumption'
cddu33 marked this conversation as resolved.
Show resolved Hide resolved
consumption action: sync
production PRM: ''
production token: ''
production name: 'Linky production'
production action: sync

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)
19 changes: 19 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ export type UserConfig = {
name: string;
action: 'sync' | 'reset';
};
production?: {
prm: string;
token: string;
name: string;
action: 'sync' | 'reset';
};
};

export function getUserConfig(): UserConfig {
Expand All @@ -16,6 +22,10 @@ export function getUserConfig(): UserConfig {
'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 {
Expand All @@ -28,6 +38,15 @@ export function getUserConfig(): UserConfig {
action: parsed['consumption action'] === 'reset' ? 'reset' : 'sync',
}
: undefined,
production:
parsed['production PRM'] && parsed['production token']
? {
prm: parsed['production PRM'],
token: parsed['production token'],
name: parsed['production name'] || 'Linky consumption',
action: parsed['production action'] === 'reset' ? 'reset' : 'sync',
}
: undefined,
};
} catch (e) {
throw new Error('Cannot read user configuration: ' + e.toString());
Expand Down
1 change: 1 addition & 0 deletions src/ha.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ export class HomeAssistantClient {

public async saveStatistics(prm: string, name: string, stats: { start: string; state: number; sum: number }[]) {
const statisticId = `${SOURCE}:${prm}`;

await this.sendMessage({
type: 'recorder/import_statistics',
metadata: {
Expand Down
133 changes: 96 additions & 37 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,52 +11,109 @@ async function main() {
const userConfig = getUserConfig();

if (!userConfig.consumption) {
warn('Add-on is not configured properly');
debug('HA Linky stopped');
return;
warn('Add-on consumption is not configured properly');
}

info('Consumption PRM ' + userConfig.consumption.prm + ' found in configuration');
const consumptionClient = new LinkyClient(userConfig.consumption.token, userConfig.consumption.prm);
if (!userConfig.production) {
warn('Add-on production is not configured properly');
}
let consumptionClient;
let productionClient;

const haClient = new HomeAssistantClient();
await haClient.connect();

if (userConfig.consumption.action === 'reset') {
await haClient.purge(userConfig.consumption.prm);
info('Statistics removed successfully!');
haClient.disconnect();
debug('HA Linky stopped');
return;
if (userConfig.consumption) {
info('consumption PRM ' + userConfig.consumption.prm + ' found in configuration');
consumptionClient = new LinkyClient(userConfig.consumption.token, userConfig.consumption.prm);

if (userConfig.consumption.action === 'reset') {
await haClient.purge(userConfig.consumption.prm);
info('Statistics removed successfully for consumption!');
haClient.disconnect();
debug('HA Linky stopped');
return;
}
}
if (userConfig.production) {
info('production PRM ' + userConfig.production.prm + ' found in configuration');
productionClient = new LinkyClient(userConfig.production.token, userConfig.production.prm);

if (userConfig.production && userConfig.production.action === 'reset') {
await haClient.purge(userConfig.production.prm + 'p');
info('Statistics removed successfully for production!');
haClient.disconnect();
debug('HA Linky stopped');
return;
}
}

async function init() {
info(`[${dayjs().format('DD/MM HH:mm')}] New PRM detected, importing as much historical data as possible`);
const energyData = await consumptionClient.getEnergyData(null);
await haClient.saveStatistics(userConfig.consumption.prm, userConfig.consumption.name, energyData);
if (userConfig.consumption) {
const energyData = await consumptionClient.getEnergyData(null, false);
await haClient.saveStatistics(userConfig.consumption.prm, userConfig.consumption.name, energyData);
}
if (userConfig.production) {
const energyData = await productionClient.getEnergyData(null, true);
await haClient.saveStatistics(userConfig.production.prm + 'p', userConfig.production.name, energyData);
}
}
async function sync() {
info(`[${dayjs().format('DD/MM HH:mm')}] Data synchronization started`);
let lastStatisticC = null;
if (userConfig.consumption) {
info(`[${dayjs().format('DD/MM HH:mm')}] Data synchronization started consumption`);
lastStatisticC = await haClient.findLastStatistic(userConfig.consumption.prm);
if (!lastStatisticC) {
warn('Data synchronization failed, no previous statistic found in Home Assistant for consumption');
return;
}
}
let lastStatisticP = null;
if (userConfig.production) {
info(`[${dayjs().format('DD/MM HH:mm')}] Data synchronization started production`);
lastStatisticP = await haClient.findLastStatistic(userConfig.production.prm + 'p');
if (!lastStatisticP) {
warn('Data synchronization failed, no previous statistic found in Home Assistant for production');
return;
}
}

const lastStatistic = await haClient.findLastStatistic(userConfig.consumption.prm);
if (!lastStatistic) {
warn('Data synchronization failed, no previous statistic found in Home Assistant');
return;
let isSyncingNeededC = false;
if (userConfig.consumption) {
isSyncingNeededC = dayjs(lastStatisticC.start).isBefore(dayjs().subtract(2, 'days')) && dayjs().hour() >= 6;
}
let isSyncingNeededP = false;
if (userConfig.production) {
isSyncingNeededP = dayjs(lastStatisticP.start).isBefore(dayjs().subtract(2, 'days')) && dayjs().hour() >= 6;
}

const isSyncingNeeded = dayjs(lastStatistic.start).isBefore(dayjs().subtract(2, 'days')) && dayjs().hour() >= 6;
if (!isSyncingNeeded) {
if (!isSyncingNeededC && !isSyncingNeededP && (userConfig.consumption || userConfig.production)) {
debug('Everything is up to date, nothing to synchronize');
return;
}
const firstDay = dayjs(lastStatistic.start).add(1, 'day');
const energyData = await consumptionClient.getEnergyData(firstDay);
incrementSums(energyData, lastStatistic.sum);
await haClient.saveStatistics(userConfig.consumption.prm, userConfig.consumption.name, energyData);
if (isSyncingNeededC) {
const firstDay = dayjs(lastStatisticC.start).add(1, 'day');
const energyData = await consumptionClient.getEnergyData(firstDay, false);
incrementSums(energyData, lastStatisticC.sum);
await haClient.saveStatistics(userConfig.consumption.prm, userConfig.consumption.name, energyData);
}
if (isSyncingNeededP) {
const firstDay = dayjs(lastStatisticP.start).add(1, 'day');
const energyData = await productionClient.getEnergyData(firstDay, true);
incrementSums(energyData, lastStatisticP.sum);
await haClient.saveStatistics(userConfig.production.prm + 'p', userConfig.production.name, energyData);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Je pense que ce serait plus clean d'ajouter un paramètre "isProd" dans les fonctions de "haClient".

Dans le cas de la production, on pourrait remplacer l'id linky:12345 par linky_production:12345 ou un truc du genre

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

je l'avais créé dans ma première version mais pas vraiment d'intêret

}
}

const isNew = await haClient.isNewPRM(userConfig.consumption.prm);
if (isNew) {
let isNewC = false;
let isNewP = false;
if (userConfig.consumption) {
isNewC = await haClient.isNewPRM(userConfig.consumption.prm);
}
if (userConfig.production) {
isNewP = await haClient.isNewPRM(userConfig.production.prm + 'p');
}
if (isNewP || isNewC) {
cddu33 marked this conversation as resolved.
Show resolved Hide resolved
await init();
} else {
await sync();
Expand All @@ -66,17 +123,19 @@ async function main() {
const randomMinute = Math.floor(Math.random() * 59);
const randomSecond = Math.floor(Math.random() * 59);

info(
`Data synchronization planned every day at ` +
`06:${randomMinute.toString().padStart(2, '0')}:${randomSecond.toString().padStart(2, '0')} and ` +
`09:${randomMinute.toString().padStart(2, '0')}:${randomSecond.toString().padStart(2, '0')}`,
);
if (userConfig.consumption || userConfig.production) {
info(
`Data synchronization planned every day at ` +
`06:${randomMinute.toString().padStart(2, '0')}:${randomSecond.toString().padStart(2, '0')} and ` +
`09:${randomMinute.toString().padStart(2, '0')}:${randomSecond.toString().padStart(2, '0')}`,
);

cron.schedule(`${randomSecond} ${randomMinute} 6,9 * * *`, async () => {
await haClient.connect();
await sync();
haClient.disconnect();
});
cron.schedule(`${randomSecond} ${randomMinute} 6,9 * * *`, async () => {
await haClient.connect();
await sync();
haClient.disconnect();
});
}
}

function incrementSums(data: { sum: number }[], value: number) {
Expand Down
64 changes: 45 additions & 19 deletions src/linky.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ export class LinkyClient {
constructor(token: string, prm: string) {
this.prm = prm;
this.session = new Session(token, prm);
this.session.userAgent = 'ha-linky/1.1.0';
this.session.userAgent = 'ha-linky/1.2.0';
}

public async getEnergyData(firstDay: null | Dayjs): Promise<EnergyDataPoint[]> {
public async getEnergyData(firstDay: null | Dayjs, prod: boolean): Promise<EnergyDataPoint[]> {
const history: LinkyDataPoint[][] = [];
let offset = 0;
let limitReached = false;
Expand All @@ -35,14 +35,27 @@ export class LinkyClient {
}

let to = dayjs().subtract(offset, 'days').format('YYYY-MM-DD');
try {
const loadCurve = await this.session.getLoadCurve(from, to);
history.unshift(LinkyClient.formatLoadCurve(loadCurve));
debug(`Successfully retrieved load curve from ${from} to ${to}`);
offset += interval;
} catch (e) {
debug(`Cannot fetch load curve from ${from} to ${to}, here is the error:`);
warn(e);

if (prod) {
try {
const loadCurve = await this.session.getProductionLoadCurve(from, to);
history.unshift(LinkyClient.formatLoadCurve(loadCurve));
debug(`Successfully retrieved load curve from ${from} to ${to}`);
offset += interval;
} catch (e) {
debug(`Cannot fetch load curve prod from ${from} to ${to}, here is the error:`);
warn(e);
}
} else {
try {
const loadCurve = await this.session.getLoadCurve(from, to);
history.unshift(LinkyClient.formatLoadCurve(loadCurve));
debug(`Successfully retrieved load curve from ${from} to ${to}`);
offset += interval;
Comment on lines +52 to +54
Copy link
Owner

@bokub bokub Nov 13, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dommage de tout dupliquer au lieu de factoriser

Idem pour la fonction sync où tout est écrit en double

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

je ne connais pas assez HA, donc n'hésite pas à modifier

} catch (e) {
debug(`Cannot fetch load curve consum from ${from} to ${to}, here is the error:`);
warn(e);
}
}

for (let loop = 0; loop < 10; loop++) {
Expand All @@ -65,15 +78,28 @@ export class LinkyClient {
limitReached = true;
}

try {
const dailyData = await this.session.getDailyConsumption(from, to);
history.unshift(LinkyClient.formatDailyData(dailyData));
debug(`Successfully retrieved daily data from ${from} to ${to}`);
offset += interval;
} catch (e) {
debug(`Cannot fetch daily data from ${from} to ${to}, here is the error:`);
warn(e);
break;
if (prod) {
try {
const dailyData = await this.session.getDailyProduction(from, to);
history.unshift(LinkyClient.formatDailyData(dailyData));
debug(`Successfully retrieved daily prod data from ${from} to ${to}`);
offset += interval;
} catch (e) {
debug(`Cannot fetch daily production data from ${from} to ${to}, here is the error:`);
warn(e);
break;
}
} else {
try {
const dailyData = await this.session.getDailyConsumption(from, to);
history.unshift(LinkyClient.formatDailyData(dailyData));
debug(`Successfully retrieved daily consum data from ${from} to ${to}`);
offset += interval;
} catch (e) {
debug(`Cannot fetch daily consumption data from ${from} to ${to}, here is the error:`);
warn(e);
break;
}
}
}

Expand Down