diff --git a/bundles/org.openhab.binding.linky/README.md b/bundles/org.openhab.binding.linky/README.md index 269541263c29c..e4f87f45390ef 100644 --- a/bundles/org.openhab.binding.linky/README.md +++ b/bundles/org.openhab.binding.linky/README.md @@ -17,9 +17,9 @@ Step are: ``` java Bridge linky:enedis:local "EnedisWebBridge" [ - username="laurent@clae.net", - password="Mnbo32tyu123!", - internalAuthId="eyJhbGciOiJBMTI4S1ciLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0.u_mxXO7_d4I5bLvJzGtc2MARvpkYv0iM0EsO6a24k-tW9493_Myxwg.LVlfephhGTiCBxii8bRIkA.GOf9Ea8PTGshvkfjl62b6w.hSH97IkmBcEAz2udU-FqQg"] { + username="myUserName@myDomain.com", + password="MyPassword", + internalAuthId="zeJhbGciOiJBMTIdaqzerZiLCJlbmMiOiJBMTIcwxdsq..."] { } ``` @@ -27,7 +27,7 @@ Step are: 1. Link your old thing to the new created bridge thing: ```java - Thing linky:linky:linkremotemelody "Linky Melody" (linky:enedis:local) + Thing linky:linky:linkremotexxxx "Linky xxxx" (linky:enedis:local) ``` 1. Start using the new channels added by the enhanced binding.. @@ -251,6 +251,37 @@ The retrieved information is available in multiple groups. | contactMail | The usage point Contact Mail | | contactPhone | The usage point Contact Phone | +#### Dynamic Thing Channels + +The binding (as of openHAB 5.1.0) supports reading consumption indexes from the Enedis website. +This makes it possible to view consumption for different tariffs such as *heures pleines / heures creuses* or *tempo*. + +To handle this, binding will create a new set of channels for daily, weekly, monthly, and yearly groups. + +You will have two different sets of indexes: + +- **Raw consumption indexes:** + These are the default indexes returned by Enedis. The naming uses base indexes, so there is no direct way to know which tariff each index corresponds to. + Channels will be named as follows: + + + consumptionSupplierIdx0, consumptionSupplierIdx1, ..., consumptionSupplierIdx9 + consumptionDistributorIdx0, consumptionDistributorIdx1, ..., consumptionDistributorIdx3 + + In France, the distributor is most often Enedis — they are responsible for distributing electricity on your network. + The supplier is the commercial company with which you have a contract (EDF, TotalEnergies, etc.). This is where your specific supplier tariff is defined. + +- **Named consumption indexes:** +To make things simpler, the binding also exposes tariff-named channels. +For example: + + daily#heuresPleines, daily#heuresCreuses, daily#bleuHeuresCreuses, + daily#bleuHeuresPleines, daily#redHeuresCreuses, ... + +⚠️ **Warning:** +Dynamic channels and indexes are currently only supported with the **EnedisWebBridge**. +Support for other bridges will be introduced later, once Enedis provides an API to access this data. + ### Full Example #### Remote Enedis Web Connection @@ -269,11 +300,27 @@ Number:Energy ConsoMoisEnCours "Conso ce mois [%.0f %unit%]" { channel= Number:Energy ConsoMoisDernier "Conso mois dernier [%.0f %unit%]" { channel="linky:linky:linkyremotexxxx:monthly#lastMonth" } Number:Energy ConsoAnneeEnCours "Conso cette année [%.0f %unit%]" { channel="linky:linky:linkyremotexxxx:yearly#thisYear" } Number:Energy ConsoAnneeDerniere "Conso année dernière [%.0f %unit%]" { channel="linky:linky:linkyremotexxxx:yearly#lastYear" } + +Number:Energy ConsoDay "Linky Conso Day -x Histo [%d]" { channel="linky:linky:linkyremotexxxx:daily#consumption" } +Number:Energy ConsoMonth "Linky Conso Month -x Histo [%d]" { channel="linky:linky:linkyremotexxxx:monthly#consumption" } + +Number:Energy ConsoMonthHeuresPleines "Linky Conso Month Heures Pleines -x Histo [%d]" { channel="linky:linky:linkyremotexxxx:monthly#heuresPleines" } +Number:Energy ConsoMonthHeuresCreuses "Linky Conso Month Heures Creuses -x Histo [%d]" { channel="linky:linky:linkyremotexxxx:monthly#heuresCreuses" } + +Number:Energy ConsoMonthHeuresCreusesBlanc "Linky Conso Month Heures Creuses Bleue -x Histo [%d]" { channel="linky:linky:linkyremotexxxx:monthly#blancHeuresCreuses" } +Number:Energy ConsoMonthHeuresCreusesBleue "Linky Conso Month Heures Creuses Blanc -x Histo [%d]" { channel="linky:linky:linkyremotexxxx:monthly#bleueHeuresCreuses" } +Number:Energy ConsoMonthHeuresCreusesRouge "Linky Conso Month Heures Rouge -x Histo [%d]" { channel="linky:linky:linkyremotexxxx:monthly#rougeHeuresCreuses" } + +Number:Energy ConsoMonthHeuresPleinesBlanc "Linky Conso Month Heures Pleines Blanc -x Histo [%d]" { channel="linky:linky:linkyremotexxxx:monthly#blancHeuresPleines" } +Number:Energy ConsoMonthHeuresPleinesBleue "Linky Conso Month Heures Pleines Bleue -x Histo [%d]" { channel="linky:linky:linkyremotexxxx:monthly#bleueHeuresPleines" } +Number:Energy ConsoMonthHeuresPleinesRouge "Linky Conso Month Heures Pleines Rouge -x Histo [%d]" { channel="linky:linky:linkyremotexxxx:monthly#rougeHeuresPleines" } + +Number Linky_Tempo "Linky Tempo Day [%s]" channel="linky:tempo-calendar:local:tempo-calendar#tempo-info-timeseries" } ``` ### Displaying Information Graph -Using the timeseries channel, you will be able to easily create a calendar graph to display the Tempo calendar. +Using the timeseries channel and the binding version in openHAB 5.1.0, you will be able to easily create a chart to show the consumption graph. To do this, you need to enable a timeseries persistence framework. Graph definitions will look like this: @@ -284,7 +331,7 @@ Sample code: ```java config: future: false - label: Linky Melody Conso Journalière + label: Conso Day order: "110" period: 2W sidebar: true @@ -310,7 +357,7 @@ slots: areaStyle: opacity: 0.2 gridIndex: 0 - item: Linky_Melody_Daily_Conso_Day + item: ConsoDay label: formatter: =v=>Number.parseFloat(v.data[1]).toFixed(2) + " Kwh" position: inside @@ -354,6 +401,239 @@ slots: nameLocation: center ``` +### Displaying Information Graph / New version with tariff + +Using the timeseries channel and new version of the addons, you will be able to easily create a chart to show the consumption graph with tariff differentiation. + +To do this, you need to enable a timeseries persistence framework. +Graph definitions will look like this: + +![TempoGraph](doc/GraphConsoWithTarif.png) + +Sample code: + +```java +config: + future: false + label: ConsoMonth + order: "9999999" + period: Y + sidebar: true +slots: + dataZoom: + - component: oh-chart-datazoom + config: + type: inside + grid: + - component: oh-chart-grid + config: + containLabel: true + includeLabels: true + show: true + legend: + - component: oh-chart-legend + config: + bottom: 3 + orient: horizontal + show: true + type: scroll + series: + - component: oh-time-series + config: + barGap: -100% + gridIndex: 0 + item: ConsoMonth + label: + formatter: =v=>Number.parseFloat(v.data[1]).toFixed(2) + " Kwh" + position: top + show: true + name: Consumption + noBoundary: true + noItemState: true + service: inmemory + type: bar + xAxisIndex: 0 + yAxisIndex: 0 + - component: oh-time-series + config: + color: "#1010ff" + gridIndex: 0 + item: ConsoMonthHeuresPleinesBleue + label: + formatter: =v=>v.data[1]!="0"?Number.parseFloat(v.data[1]).toFixed(2) + " + Kwh":'' + position: inside + show: true + name: Bleue HP + noBoundary: true + noItemState: true + service: inmemory + stack: total + type: bar + xAxisIndex: 0 + yAxisIndex: 0 + - component: oh-time-series + config: + color: "#f0f0f0" + emphasis: + disabled: true + gridIndex: 0 + item: ConsoMonthHeuresPleinesBlanc + label: + formatter: =v=>v.data[1]!="0"?Number.parseFloat(v.data[1]).toFixed(2) + " + Kwh":'' + position: inside + show: true + name: Blanc HP + noBoundary: true + noItemState: true + service: inmemory + stack: total + type: bar + xAxisIndex: 0 + yAxisIndex: 0 + - component: oh-time-series + config: + color: "#ff7070" + emphasis: + disabled: true + gridIndex: 0 + item: ConsoMonthHeuresCreusesRouge + label: + formatter: =v=>v.data[1]!="0"?Number.parseFloat(v.data[1]).toFixed(2) + " + Kwh":'' + position: inside + show: true + name: Rouge HC + noBoundary: true + noItemState: true + service: inmemory + stack: total + type: bar + xAxisIndex: 0 + yAxisIndex: 0 + - component: oh-time-series + config: + color: "#d0d0d0" + emphasis: + disabled: true + gridIndex: 0 + item: ConsoMonthHeuresCreusesBlanc + label: + formatter: =v=>v.data[1]!="0"?Number.parseFloat(v.data[1]).toFixed(2) + " + Kwh":'' + position: inside + show: true + name: Blanc HC + noBoundary: true + noItemState: true + service: inmemory + stack: total + type: bar + xAxisIndex: 0 + yAxisIndex: 0 + - component: oh-time-series + config: + color: "#7070ff" + emphasis: + disabled: true + gridIndex: 0 + item: ConsoMonthHeuresCreusesBleue + label: + formatter: =v=>v.data[1]!="0"?Number.parseFloat(v.data[1]).toFixed(2) + " + Kwh":'' + position: inside + show: true + name: Bleue HC + noBoundary: true + noItemState: true + service: inmemory + stack: total + type: bar + xAxisIndex: 0 + yAxisIndex: 0 + - component: oh-time-series + config: + color: "#ff1010" + emphasis: + disabled: true + gridIndex: 0 + item: ConsoMonthHeuresPleinesRouge + label: + formatter: =v=>v.data[1]!="0"?Number.parseFloat(v.data[1]).toFixed(2) + " + Kwh":'' + position: inside + show: true + name: Rouge HP + noBoundary: true + noItemState: true + service: inmemory + stack: total + type: bar + xAxisIndex: 0 + yAxisIndex: 0 + - component: oh-time-series + config: + color: "#00ff00" + emphasis: + disabled: true + gridIndex: 0 + item: ConsoMonthHeuresPleines + label: + formatter: =v=>v.data[1]!="0"?Number.parseFloat(v.data[1]).toFixed(2) + " + Kwh":'' + position: inside + show: true + name: HP + noBoundary: true + noItemState: true + service: inmemory + stack: total + type: bar + xAxisIndex: 0 + yAxisIndex: 0 + - component: oh-time-series + config: + color: "#80ff80" + emphasis: + disabled: true + gridIndex: 0 + item: ConsoMonthHeuresCreuses + label: + formatter: =v=>v.data[1]!="0"?Number.parseFloat(v.data[1]).toFixed(2) + " + Kwh":'' + position: inside + show: true + name: HC + noBoundary: true + noItemState: true + service: inmemory + stack: total + type: bar + xAxisIndex: 0 + yAxisIndex: 0 + tooltip: + - component: oh-chart-tooltip + config: + confine: true + orient: vertical + show: true + smartFormatter: true + visualMap: [] + xAxis: + - component: oh-time-axis + config: + gridIndex: 0 + nameLocation: center + splitNumber: 10 + yAxis: + - component: oh-value-axis + config: + gridIndex: 0 + name: kWh + nameLocation: center +``` + ## Getting Tempo Calendar Information ### Tempo Thing Channels @@ -422,7 +702,7 @@ slots: aggregationFunction: average calendarIndex: 0 coordinateSystem: calendar - item: Linky_Melody_Tempo + item: Linky_Tempo label: formatter: =v=> JSON.stringify(v.data[0]).substring(1,11) show: true diff --git a/bundles/org.openhab.binding.linky/doc/GraphConsoWithTarif.png b/bundles/org.openhab.binding.linky/doc/GraphConsoWithTarif.png new file mode 100644 index 0000000000000..1b044d3a47c6f Binary files /dev/null and b/bundles/org.openhab.binding.linky/doc/GraphConsoWithTarif.png differ diff --git a/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/api/EnedisHttpApi.java b/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/api/EnedisHttpApi.java index cc226eca53be7..2a4a351cd2422 100644 --- a/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/api/EnedisHttpApi.java +++ b/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/api/EnedisHttpApi.java @@ -259,7 +259,7 @@ public Contact getContact(ThingLinkyRemoteHandler handler, String prmId) throws } private MeterReading getMeasures(ThingLinkyRemoteHandler handler, String apiUrl, String mps, String prmId, - String segment, LocalDate from, LocalDate to) throws LinkyException { + String segment, LocalDate from, LocalDate to, boolean useIndex) throws LinkyException { String dtStart = from.format(linkyBridgeHandler.getApiDateFormat()); String dtEnd = to.format(linkyBridgeHandler.getApiDateFormat()); @@ -270,23 +270,28 @@ private MeterReading getMeasures(ThingLinkyRemoteHandler handler, String apiUrl, } else { String url = String.format(apiUrl, mps, prmId, segment, dtStart, dtEnd); ConsumptionReport consomptionReport = getData(handler, url, ConsumptionReport.class); - return MeterReading.convertFromComsumptionReport(consomptionReport); + return MeterReading.convertFromComsumptionReport(consomptionReport, useIndex); } } public MeterReading getEnergyData(ThingLinkyRemoteHandler handler, String mps, String prmId, String segment, LocalDate from, LocalDate to) throws LinkyException { - return getMeasures(handler, linkyBridgeHandler.getDailyConsumptionUrl(), mps, prmId, segment, from, to); + return getMeasures(handler, linkyBridgeHandler.getDailyConsumptionUrl(), mps, prmId, segment, from, to, false); + } + + public MeterReading getEnergyIndex(ThingLinkyRemoteHandler handler, String mps, String prmId, String segment, + LocalDate from, LocalDate to) throws LinkyException { + return getMeasures(handler, linkyBridgeHandler.getDailyIndexUrl(), mps, prmId, segment, from, to, true); } public MeterReading getLoadCurveData(ThingLinkyRemoteHandler handler, String mps, String prmId, String segment, LocalDate from, LocalDate to) throws LinkyException { - return getMeasures(handler, linkyBridgeHandler.getLoadCurveUrl(), mps, prmId, segment, from, to); + return getMeasures(handler, linkyBridgeHandler.getLoadCurveUrl(), mps, prmId, segment, from, to, false); } public MeterReading getPowerData(ThingLinkyRemoteHandler handler, String mps, String prmId, String segment, LocalDate from, LocalDate to) throws LinkyException { - return getMeasures(handler, linkyBridgeHandler.getMaxPowerUrl(), mps, prmId, segment, from, to); + return getMeasures(handler, linkyBridgeHandler.getMaxPowerUrl(), mps, prmId, segment, from, to, false); } public ResponseTempo getTempoData(ThingBaseRemoteHandler handler, LocalDate from, LocalDate to) diff --git a/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/constants/LinkyBindingConstants.java b/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/constants/LinkyBindingConstants.java index bccd2b5d1d3fe..c2e9b2832f2c9 100644 --- a/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/constants/LinkyBindingConstants.java +++ b/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/constants/LinkyBindingConstants.java @@ -57,6 +57,8 @@ public class LinkyBindingConstants { public static final String LINKY_TEMPO_CALENDAR_GROUP = "tempo-calendar"; public static final String LINKY_REMOTE_LOAD_CURVE_GROUP = "load-curve"; + public static final String CHANNEL_TYPE_CONSUMPTION = "consumption"; + // List of all Channel id's public static final String CHANNEL_CONSUMPTION = "consumption"; public static final String CHANNEL_MAX_POWER = "max-power"; diff --git a/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/dto/Calendrier.java b/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/dto/Calendrier.java new file mode 100644 index 0000000000000..91861b6bf28a7 --- /dev/null +++ b/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/dto/Calendrier.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2010-2025 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.linky.internal.dto; + +/** + * The {@link Calendrier} holds informations about the available energy calendar + * + * @author Laurent Arnal - Initial contribution + */ + +public class Calendrier { + public String idCalendrier; + public String libelleCalendrier; +} diff --git a/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/dto/ClassesTemporelles.java b/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/dto/ClassesTemporelles.java new file mode 100644 index 0000000000000..c9e245af12865 --- /dev/null +++ b/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/dto/ClassesTemporelles.java @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2010-2025 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.linky.internal.dto; + +/** + * The {@link ClassesTemporelles} holds informations about energy consumption + * + * @author Gaël L'hopital - Initial contribution + * @author Laurent Arnal - Rewrite addon to use official dataconect API + */ + +public class ClassesTemporelles { + public String libelle; + public Double valeur = 0.0; +} diff --git a/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/dto/ConsumptionReport.java b/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/dto/ConsumptionReport.java index 1034872825294..f17b3bd5a43df 100644 --- a/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/dto/ConsumptionReport.java +++ b/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/dto/ConsumptionReport.java @@ -31,6 +31,11 @@ public class Data { public LocalDateTime dateDebut; public LocalDateTime dateFin; public Double valeur; + @SerializedName("classesTemporellesFournisseur") + public ClassesTemporelles[] classesTemporellesSupplier; + @SerializedName("classesTemporellesDistributeur") + public ClassesTemporelles[] classesTemporellesDistributor; + public Calendrier[] calendrier; } public class Aggregate { diff --git a/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/dto/IndexInfo.java b/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/dto/IndexInfo.java new file mode 100644 index 0000000000000..879c29d3d91b4 --- /dev/null +++ b/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/dto/IndexInfo.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2010-2025 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.linky.internal.dto; + +/** + * The {@link IndexInfo} contains data for a given index + * + * @author Laurent Arnal - Initial contribution + */ + +public class IndexInfo { + public double[] value; + public String[] label; +} diff --git a/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/dto/IndexMode.java b/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/dto/IndexMode.java new file mode 100644 index 0000000000000..51ddbdb1b9813 --- /dev/null +++ b/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/dto/IndexMode.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2010-2025 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.linky.internal.dto; + +/** + * The {@link IndexMode} represents the index type : Supplier or Distributor + * + * @author Laurent Arnal - Initial contribution + */ + +public enum IndexMode { + NONE(-1, 0, "None"), + SUPPLIER(0, 10, "Supplier"), + DISTRIBUTOR(1, 4, "Distributor"); + + private final int idx; + private final int size; + private final String label; + + IndexMode(int idx, int size, String label) { + this.idx = idx; + this.size = size; + this.label = label; + } + + public int getIdx() { + return idx; + } + + public int getSize() { + return size; + } + + public String getLabel() { + return label; + } +} diff --git a/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/dto/IntervalReading.java b/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/dto/IntervalReading.java index 31bc99aba9157..248843c3fc491 100644 --- a/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/dto/IntervalReading.java +++ b/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/dto/IntervalReading.java @@ -23,5 +23,17 @@ public class IntervalReading { public Double value = 0.0; + public IndexInfo[] indexInfo; public LocalDateTime date; + + public void initIndexInfo() { + indexInfo = new IndexInfo[2]; + indexInfo[0] = new IndexInfo(); + indexInfo[1] = new IndexInfo(); + + indexInfo[0].label = new String[10]; + indexInfo[0].value = new double[10]; + indexInfo[1].label = new String[4]; + indexInfo[1].value = new double[4]; + } } diff --git a/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/dto/MeterReading.java b/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/dto/MeterReading.java index 92d849e777e6f..04a8b72a464c9 100644 --- a/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/dto/MeterReading.java +++ b/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/dto/MeterReading.java @@ -44,32 +44,125 @@ public class MeterReading { public IntervalReading[] monthValue; public IntervalReading[] yearValue; - public static MeterReading convertFromComsumptionReport(ConsumptionReport comsumptionReport) { + public static MeterReading convertFromComsumptionReport(ConsumptionReport comsumptionReport, boolean useIndex) { MeterReading result = new MeterReading(); result.readingType = new ReadingType(); if (comsumptionReport.consumptions.aggregats != null) { if (comsumptionReport.consumptions.aggregats.days != null) { - result.baseValue = fromAgregat(comsumptionReport.consumptions.aggregats.days); + result.baseValue = fromAgregat(comsumptionReport.consumptions.aggregats.days, useIndex); } else if (comsumptionReport.consumptions.aggregats.heure != null) { - result.baseValue = fromAgregat(comsumptionReport.consumptions.aggregats.heure); + result.baseValue = fromAgregat(comsumptionReport.consumptions.aggregats.heure, useIndex); } } return result; } - public static IntervalReading[] fromAgregat(ConsumptionReport.Aggregate agregat) { + /** + * This method will get data from old ConsumptionReport.Aggregate that is the format use by the Web API. + * And will result to IntervalReading[] that is the format of the new Enedis API + * + * @param agregat + * @param useIndex : tell if we are reading value from raw consumption or from index value + * @return IntervalReading[] : the data structure of new API + */ + public static IntervalReading[] fromAgregat(ConsumptionReport.Aggregate agregat, boolean useIndex) { int size = agregat.datas.size(); - IntervalReading[] result = new IntervalReading[size]; + IntervalReading[] result = null; - for (int i = 0; i < size; i++) { - Data dataObj = agregat.datas.get(i); - result[i] = new IntervalReading(); - result[i].value = Double.valueOf(dataObj.valeur); - result[i].date = dataObj.dateDebut; + // For some unknown reason, index API don't return the index for day N-1. + // So array length is cut off 1 + if (useIndex) { + result = new IntervalReading[size - 1]; + } else { + result = new IntervalReading[size]; + } + + if (!useIndex) { + for (int i = 0; i < size; i++) { + Data dataObj = agregat.datas.get(i); + result[i] = new IntervalReading(); + result[i].value = dataObj.valeur; + result[i].date = dataObj.dateDebut; + } + } else { + double lastVal = 0.0; + double[] lastValueSupplier = new double[10]; + double[] lastValueDistributor = new double[4]; + String lastCalendrierSupplier = ""; + String lastCalendrierDistributor = ""; + + for (int idx = 0; idx < size; idx++) { + Data dataObj = agregat.datas.get(idx); + double value = dataObj.valeur; + String calendrierDistributor = ""; + String calendrierSupplier = ""; + + if (dataObj.calendrier == null && idx > 0) { + dataObj.calendrier = agregat.datas.get(idx - 1).calendrier; + } + + if (dataObj.calendrier.length >= 1 && dataObj.calendrier[0] != null) { + calendrierDistributor = dataObj.calendrier[0].idCalendrier; + } + if (dataObj.calendrier.length >= 2 && dataObj.calendrier[1] != null) { + calendrierSupplier = dataObj.calendrier[1].idCalendrier; + } + + if (idx > 0) { + result[idx - 1] = new IntervalReading(); + result[idx - 1].value = value - lastVal; + // The index in on nextDay N, but index difference give consumption for day N-1 + result[idx - 1].date = dataObj.dateDebut.minusDays(1); + result[idx - 1].initIndexInfo(); + + if (dataObj.classesTemporellesSupplier == null) { + dataObj.classesTemporellesSupplier = agregat.datas.get(idx - 1).classesTemporellesSupplier; + } + + if (dataObj.classesTemporellesDistributor == null) { + dataObj.classesTemporellesDistributor = agregat.datas + .get(idx - 1).classesTemporellesDistributor; + } + } + + initIndexValue(IndexMode.SUPPLIER, dataObj.classesTemporellesSupplier, result, idx, calendrierSupplier, + lastCalendrierSupplier, lastValueSupplier); + + initIndexValue(IndexMode.DISTRIBUTOR, dataObj.classesTemporellesDistributor, result, idx, + calendrierDistributor, lastCalendrierDistributor, lastValueDistributor); + + lastVal = value; + lastCalendrierDistributor = calendrierDistributor; + lastCalendrierSupplier = calendrierSupplier; + } } return result; } + + public static void initIndexValue(IndexMode indexMode, ClassesTemporelles[] classTp, IntervalReading[] ir, int idx, + String calendrier, String lastCalendrier, double[] lastValue) { + if (classTp != null) { + for (int idxClTp = 0; idxClTp < classTp.length; idxClTp++) { + ClassesTemporelles ct = classTp[idxClTp]; + + if (idx > 0) { + // We check if calendar are the same that previous iteration + // If not, we are not able to reconciliate index, and so set index value to 0 ! + + if (calendrier.equals(lastCalendrier)) { + ir[idx - 1].indexInfo[indexMode.getIdx()].value[idxClTp] = (ct.valeur - lastValue[idxClTp]); + } else { + ir[idx - 1].indexInfo[indexMode.getIdx()].value[idxClTp] = 0; + } + + ir[idx - 1].indexInfo[indexMode.getIdx()].label[idxClTp] = ct.libelle; + } + + lastValue[idxClTp] = ct.valeur; + } + } + } } diff --git a/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/factory/LinkyHandlerFactory.java b/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/factory/LinkyHandlerFactory.java index e8a0555b21de5..d582a1cdfba5a 100644 --- a/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/factory/LinkyHandlerFactory.java +++ b/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/factory/LinkyHandlerFactory.java @@ -33,6 +33,7 @@ import org.openhab.core.auth.client.oauth2.OAuthFactory; import org.openhab.core.i18n.LocaleProvider; import org.openhab.core.i18n.TimeZoneProvider; +import org.openhab.core.i18n.TranslationProvider; import org.openhab.core.io.net.http.HttpClientFactory; import org.openhab.core.thing.Bridge; import org.openhab.core.thing.Thing; @@ -70,6 +71,7 @@ public class LinkyHandlerFactory extends BaseThingHandlerFactory { private final HttpService httpService; private final ComponentContext componentContext; private final TimeZoneProvider timeZoneProvider; + private final TranslationProvider translationProvider; private final Gson gson = new GsonBuilder() .registerTypeAdapter(ZonedDateTime.class, @@ -96,13 +98,15 @@ public class LinkyHandlerFactory extends BaseThingHandlerFactory { public LinkyHandlerFactory(final @Reference LocaleProvider localeProvider, final @Reference HttpClientFactory httpClientFactory, final @Reference OAuthFactory oAuthFactory, final @Reference HttpService httpService, ComponentContext componentContext, - final @Reference TimeZoneProvider timeZoneProvider) { + final @Reference TimeZoneProvider timeZoneProvider, + final @Reference TranslationProvider translationProvider) { this.localeProvider = localeProvider; this.timeZoneProvider = timeZoneProvider; this.httpClientFactory = httpClientFactory; this.oAuthFactory = oAuthFactory; this.httpService = httpService; this.componentContext = componentContext; + this.translationProvider = translationProvider; } @Override @@ -130,7 +134,8 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) { this.httpClientFactory, this.oAuthFactory, this.httpService, componentContext, gson); return handler; } else if (THING_TYPE_LINKY.equals(thing.getThingTypeUID())) { - ThingLinkyRemoteHandler handler = new ThingLinkyRemoteHandler(thing, localeProvider, timeZoneProvider); + ThingLinkyRemoteHandler handler = new ThingLinkyRemoteHandler(thing, localeProvider, timeZoneProvider, + translationProvider); return handler; } else if (THING_TYPE_TEMPO_CALENDAR.equals(thing.getThingTypeUID())) { ThingHandler handler = new ThingTempoCalendarHandler(thing); diff --git a/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/handler/BridgeRemoteBaseHandler.java b/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/handler/BridgeRemoteBaseHandler.java index 4ff0bf0595631..0df3a1abed6a9 100644 --- a/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/handler/BridgeRemoteBaseHandler.java +++ b/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/handler/BridgeRemoteBaseHandler.java @@ -178,6 +178,8 @@ public void handleCommand(ChannelUID channelUID, Command command) { public abstract String getDailyConsumptionUrl(); + public abstract String getDailyIndexUrl(); + public abstract String getMaxPowerUrl(); public abstract String getLoadCurveUrl(); diff --git a/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/handler/BridgeRemoteEnedisHandler.java b/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/handler/BridgeRemoteEnedisHandler.java index acb014d60d420..87671a5a5f38d 100644 --- a/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/handler/BridgeRemoteEnedisHandler.java +++ b/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/handler/BridgeRemoteEnedisHandler.java @@ -51,6 +51,8 @@ public class BridgeRemoteEnedisHandler extends BridgeRemoteApiHandler { private static final String ADDRESS_URL = "customers_upa/v5/usage_points/addresses?usage_point_id=%s"; private static final String MEASURE_DAILY_CONSUMPTION_URL = "metering_data_dc/v5/daily_consumption?usage_point_id=%s&start=%s&end=%s"; + private static final String MEASURE_DAILY_INDEX_URL = MEASURE_DAILY_CONSUMPTION_URL; + private static final String MEASURE_MAX_POWER_URL = "metering_data_dcmp/v5/daily_consumption_max_power?usage_point_id=%s&start=%s&end=%s"; private static final String LOAD_CURVE_CONSUMPTION_URL = "metering_data_clc/v5/consumption_load_curve?usage_point_id=%s&start=%s&end=%s"; @@ -179,6 +181,11 @@ public String getDailyConsumptionUrl() { return getBaseUrl() + MEASURE_DAILY_CONSUMPTION_URL; } + @Override + public String getDailyIndexUrl() { + return getBaseUrl() + MEASURE_DAILY_INDEX_URL; + } + @Override public String getMaxPowerUrl() { return getBaseUrl() + MEASURE_MAX_POWER_URL; diff --git a/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/handler/BridgeRemoteEnedisWebHandler.java b/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/handler/BridgeRemoteEnedisWebHandler.java index eb4a526b3fd8d..3d711be5eeea4 100644 --- a/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/handler/BridgeRemoteEnedisWebHandler.java +++ b/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/handler/BridgeRemoteEnedisWebHandler.java @@ -73,6 +73,9 @@ public class BridgeRemoteEnedisWebHandler extends BridgeRemoteBaseHandler { private static final String MEASURE_DAILY_CONSUMPTION_URL = PRM_INFO_BASE_URL + "%s/prms/%s/donnees-energetiques?mesuresTypeCode=ENERGIE&mesuresCorrigees=false&typeDonnees=CONS&segments=%s"; + private static final String MEASURE_DAILY_INDEX_URL = PRM_INFO_BASE_URL + + "%s/prms/%s/donnees-energetiques?mesuresTypeCode=INDEX&mesuresCorrigees=false&typeDonnees=CONS&segments=%s"; + private static final String MEASURE_MAX_POWER_URL = PRM_INFO_BASE_URL + "%s/prms/%s/donnees-energetiques?mesuresTypeCode=PMAX&mesuresCorrigees=false&typeDonnees=CONS&segments=%s"; @@ -146,6 +149,11 @@ public String getDailyConsumptionUrl() { return MEASURE_DAILY_CONSUMPTION_URL; } + @Override + public String getDailyIndexUrl() { + return MEASURE_DAILY_INDEX_URL; + } + @Override public String getMaxPowerUrl() { return MEASURE_MAX_POWER_URL; diff --git a/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/handler/BridgeRemoteMyElectricalDataHandler.java b/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/handler/BridgeRemoteMyElectricalDataHandler.java index f27dd895e4bd5..ac37fca9803fc 100644 --- a/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/handler/BridgeRemoteMyElectricalDataHandler.java +++ b/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/handler/BridgeRemoteMyElectricalDataHandler.java @@ -49,6 +49,8 @@ public class BridgeRemoteMyElectricalDataHandler extends BridgeRemoteApiHandler private static final String CONTACT_URL = BASE_URL + "contact/%s/cache/"; private static final String ADDRESS_URL = BASE_URL + "addresses/%s/cache/"; private static final String MEASURE_DAILY_CONSUMPTION_URL = BASE_URL + "daily_consumption/%s/start/%s/end/%s/cache"; + private static final String MEASURE_DAILY_INDEX_URL = MEASURE_DAILY_CONSUMPTION_URL; + private static final String MEASURE_MAX_POWER_URL = BASE_URL + "daily_consumption_max_power/%s/start/%s/end/%s/cache"; private static final String LOAD_CURVE_CONSUMPTION_URL = BASE_URL @@ -192,6 +194,11 @@ public String getDailyConsumptionUrl() { return MEASURE_DAILY_CONSUMPTION_URL; } + @Override + public String getDailyIndexUrl() { + return MEASURE_DAILY_INDEX_URL; + } + @Override public String getMaxPowerUrl() { return MEASURE_MAX_POWER_URL; diff --git a/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/handler/ThingLinkyRemoteHandler.java b/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/handler/ThingLinkyRemoteHandler.java index 4f1bc093639ae..d23464815d833 100644 --- a/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/handler/ThingLinkyRemoteHandler.java +++ b/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/handler/ThingLinkyRemoteHandler.java @@ -14,6 +14,7 @@ import static org.openhab.binding.linky.internal.constants.LinkyBindingConstants.*; +import java.text.Normalizer; import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; @@ -22,6 +23,7 @@ import java.time.temporal.ChronoUnit; import java.time.temporal.WeekFields; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Locale; import java.util.Map; @@ -37,9 +39,12 @@ import org.openhab.binding.linky.internal.api.EnedisHttpApi; import org.openhab.binding.linky.internal.api.ExpiringDayCache; import org.openhab.binding.linky.internal.config.LinkyThingRemoteConfiguration; +import org.openhab.binding.linky.internal.constants.LinkyBindingConstants; import org.openhab.binding.linky.internal.dto.Contact; import org.openhab.binding.linky.internal.dto.Contract; import org.openhab.binding.linky.internal.dto.Identity; +import org.openhab.binding.linky.internal.dto.IndexInfo; +import org.openhab.binding.linky.internal.dto.IndexMode; import org.openhab.binding.linky.internal.dto.IntervalReading; import org.openhab.binding.linky.internal.dto.MetaData; import org.openhab.binding.linky.internal.dto.MeterReading; @@ -51,21 +56,27 @@ import org.openhab.core.config.core.Configuration; import org.openhab.core.i18n.LocaleProvider; import org.openhab.core.i18n.TimeZoneProvider; +import org.openhab.core.i18n.TranslationProvider; import org.openhab.core.library.types.DateTimeType; import org.openhab.core.library.types.QuantityType; import org.openhab.core.library.unit.MetricPrefix; import org.openhab.core.library.unit.Units; import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.Channel; import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.Thing; import org.openhab.core.thing.ThingStatus; import org.openhab.core.thing.ThingStatusDetail; +import org.openhab.core.thing.binding.builder.ChannelBuilder; +import org.openhab.core.thing.type.ChannelTypeUID; import org.openhab.core.types.Command; import org.openhab.core.types.RefreshType; import org.openhab.core.types.State; import org.openhab.core.types.TimeSeries; import org.openhab.core.types.TimeSeries.Policy; import org.openhab.core.types.UnDefType; +import org.osgi.framework.Bundle; +import org.osgi.framework.FrameworkUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -83,12 +94,15 @@ public class ThingLinkyRemoteHandler extends ThingBaseRemoteHandler { private static final int REFRESH_HOUR_OF_DAY = 1; private static final int REFRESH_MINUTE_OF_DAY = RANDOM_NUMBERS.nextInt(60); private static final int REFRESH_INTERVAL_IN_MIN = 120; + private static final int NUMBER_OF_DATA_DAY = 1095; private final TimeZoneProvider timeZoneProvider; + private final TranslationProvider translationProvider; private final Logger logger = LoggerFactory.getLogger(ThingLinkyRemoteHandler.class); private final ExpiringDayCache metaData; private final ExpiringDayCache dailyConsumption; + private final ExpiringDayCache dailyIndex; private final ExpiringDayCache dailyConsumptionMaxPower; private final ExpiringDayCache loadCurveConsumption; @@ -108,10 +122,12 @@ private enum Target { ALL } - public ThingLinkyRemoteHandler(Thing thing, LocaleProvider localeProvider, TimeZoneProvider timeZoneProvider) { + public ThingLinkyRemoteHandler(Thing thing, LocaleProvider localeProvider, TimeZoneProvider timeZoneProvider, + TranslationProvider translationProvider) { super(thing); this.timeZoneProvider = timeZoneProvider; + this.translationProvider = translationProvider; this.metaData = new ExpiringDayCache<>("metaData", REFRESH_HOUR_OF_DAY, REFRESH_MINUTE_OF_DAY, () -> { MetaData metaData = getMetaData(); @@ -121,7 +137,7 @@ public ThingLinkyRemoteHandler(Thing thing, LocaleProvider localeProvider, TimeZ this.dailyConsumption = new ExpiringDayCache<>("dailyConsumption", REFRESH_HOUR_OF_DAY, REFRESH_MINUTE_OF_DAY, () -> { LocalDate today = LocalDate.now(); - MeterReading meterReading = getConsumptionData(today.minusDays(1095), today); + MeterReading meterReading = getConsumptionData(today.minusDays(NUMBER_OF_DATA_DAY), today); meterReading = getMeterReadingAfterChecks(meterReading); if (meterReading != null) { logData(meterReading.baseValue, "Day", DateTimeFormatter.ISO_LOCAL_DATE, Target.ALL); @@ -130,16 +146,26 @@ public ThingLinkyRemoteHandler(Thing thing, LocaleProvider localeProvider, TimeZ return meterReading; }); - // We request data for yesterday and the day before yesterday - // even if the data for the day before yesterday - // This is only a workaround to an API bug that will return INTERNAL_SERVER_ERROR rather - // than the expected data with a NaN value when the data for yesterday is not yet available. - // By requesting two days, the API is not failing and you get the expected NaN value for yesterday + this.dailyIndex = new ExpiringDayCache<>("dailyIndex", REFRESH_HOUR_OF_DAY, REFRESH_MINUTE_OF_DAY, () -> { + LocalDate today = LocalDate.now(); + MeterReading meterReading = getConsumptionIndex(today.minusDays(NUMBER_OF_DATA_DAY), today); + meterReading = getMeterReadingAfterChecks(meterReading); + if (meterReading != null) { + logData(meterReading.baseValue, "Day", DateTimeFormatter.ISO_LOCAL_DATE, Target.ALL); + logData(meterReading.weekValue, "Week", DateTimeFormatter.ISO_LOCAL_DATE_TIME, Target.ALL); + } + return meterReading; + }); + + // We request data for yesterday and the day before yesterday. + // This is a workaround for an API bug: if the data for yesterday is not yet available, + // the API returns INTERNAL_SERVER_ERROR instead of the expected NaN value. + // By requesting both days, the API does not fail, and you get the expected NaN for yesterday // when the data is not yet available. this.dailyConsumptionMaxPower = new ExpiringDayCache<>("dailyConsumptionMaxPower", REFRESH_HOUR_OF_DAY, REFRESH_MINUTE_OF_DAY, () -> { LocalDate today = LocalDate.now(); - MeterReading meterReading = getPowerData(today.minusDays(1095), today); + MeterReading meterReading = getPowerData(today.minusDays(NUMBER_OF_DATA_DAY), today); meterReading = getMeterReadingAfterChecks(meterReading); if (meterReading != null) { logData(meterReading.baseValue, "Day (peak)", DateTimeFormatter.ISO_LOCAL_DATE, Target.ALL); @@ -355,7 +381,7 @@ private void addProps(Map props, String key, @Nullable String va } /** - * Request new data and updates channels + * Requests new data and updates the channels. */ private synchronized void updateData() { // If one of the cache is expired, force also a metaData refresh to prevent 500 error from Enedis servers ! @@ -376,6 +402,7 @@ private synchronized void updateData() { } updateEnergyData(); + updateEnergyIndex(); updatePowerData(); updateLoadCurveData(); } @@ -400,16 +427,16 @@ private synchronized void updatePowerData() { updateState(LINKY_REMOTE_DAILY_GROUP, CHANNEL_PEAK_POWER_TS_DAY_MINUS_3, new DateTimeType(values.baseValue[dSize - 3].date.atZone(zoneId))); - updateTimeSeries(LINKY_REMOTE_DAILY_GROUP, CHANNEL_MAX_POWER, values.baseValue, + updateTimeSeries(LINKY_REMOTE_DAILY_GROUP, CHANNEL_MAX_POWER, values.baseValue, -1, MetricPrefix.KILO(Units.VOLT_AMPERE)); - updateTimeSeries(LINKY_REMOTE_WEEKLY_GROUP, CHANNEL_MAX_POWER, values.weekValue, + updateTimeSeries(LINKY_REMOTE_WEEKLY_GROUP, CHANNEL_MAX_POWER, values.weekValue, -1, MetricPrefix.KILO(Units.VOLT_AMPERE)); - updateTimeSeries(LINKY_REMOTE_MONTHLY_GROUP, CHANNEL_MAX_POWER, values.monthValue, + updateTimeSeries(LINKY_REMOTE_MONTHLY_GROUP, CHANNEL_MAX_POWER, values.monthValue, -1, MetricPrefix.KILO(Units.VOLT_AMPERE)); - updateTimeSeries(LINKY_REMOTE_YEARLY_GROUP, CHANNEL_MAX_POWER, values.yearValue, + updateTimeSeries(LINKY_REMOTE_YEARLY_GROUP, CHANNEL_MAX_POWER, values.yearValue, -1, MetricPrefix.KILO(Units.VOLT_AMPERE)); }, () -> { updateKwhChannel(LINKY_REMOTE_DAILY_GROUP, CHANNEL_PEAK_POWER_DAY_MINUS_1, Double.NaN); @@ -425,7 +452,7 @@ private synchronized void updatePowerData() { } /** - * Request new daily/weekly data and updates channels + * Requests new daily or weekly data and updates the corresponding channels. */ private synchronized void updateEnergyData() { dailyConsumption.getValue().ifPresentOrElse(values -> { @@ -464,10 +491,11 @@ private synchronized void updateEnergyData() { values.yearValue[idxCurrentYear - 2].value); } - updateTimeSeries(LINKY_REMOTE_DAILY_GROUP, CHANNEL_CONSUMPTION, values.baseValue, Units.KILOWATT_HOUR); - updateTimeSeries(LINKY_REMOTE_WEEKLY_GROUP, CHANNEL_CONSUMPTION, values.weekValue, Units.KILOWATT_HOUR); - updateTimeSeries(LINKY_REMOTE_MONTHLY_GROUP, CHANNEL_CONSUMPTION, values.monthValue, Units.KILOWATT_HOUR); - updateTimeSeries(LINKY_REMOTE_YEARLY_GROUP, CHANNEL_CONSUMPTION, values.yearValue, Units.KILOWATT_HOUR); + updateTimeSeries(LINKY_REMOTE_DAILY_GROUP, CHANNEL_CONSUMPTION, values.baseValue, -1, Units.KILOWATT_HOUR); + updateTimeSeries(LINKY_REMOTE_WEEKLY_GROUP, CHANNEL_CONSUMPTION, values.weekValue, -1, Units.KILOWATT_HOUR); + updateTimeSeries(LINKY_REMOTE_MONTHLY_GROUP, CHANNEL_CONSUMPTION, values.monthValue, -1, + Units.KILOWATT_HOUR); + updateTimeSeries(LINKY_REMOTE_YEARLY_GROUP, CHANNEL_CONSUMPTION, values.yearValue, -1, Units.KILOWATT_HOUR); }, () -> { updateKwhChannel(LINKY_REMOTE_DAILY_GROUP, CHANNEL_DAY_MINUS_1, Double.NaN); updateKwhChannel(LINKY_REMOTE_DAILY_GROUP, CHANNEL_DAY_MINUS_2, Double.NaN); @@ -488,12 +516,286 @@ private synchronized void updateEnergyData() { } /** - * Request new loadCurve data and updates channels + * This method removes specific localized characters (such as 'é', 'ê', 'â') + * to produce a correctly formatted UID from a localized item label. + * + * @return the label without invalid characters + */ + private static String sanetizeId(String label) { + String result = label; + + if (!Normalizer.isNormalized(label, Normalizer.Form.NFKD)) { + result = Normalizer.normalize(label, Normalizer.Form.NFKD); + result = result.replaceAll("\\p{M}", ""); + } + + result = result.replaceAll("[^a-zA-Z0-9_]", ""); + if (!result.isEmpty()) { + result = result.substring(0, 1).toLowerCase() + result.substring(1); + } + + return result; + } + + /** + * This method creates a new channel for a consumption index. + * + * @param channels : the resulting list of channels + * @param channelTypeUid : the uid of the ChannelType associated to this channel + * @param channelGroup : the group associated to this channel + * @param channelName : the name of the channel + * @param channelLabel : the label of the channel + * @param channelDesc : the description of the channel + */ + private void addChannel(List channels, ChannelTypeUID chanTypeUid, String channelGroup, String channelName, + String channelLabel, @Nullable String channelDesc) { + ChannelUID channelUid = new ChannelUID(this.getThing().getUID(), channelGroup, channelName); + + if (getThing().getChannel(channelUid) != null) { + return; + } + + ChannelBuilder builder = ChannelBuilder.create(channelUid).withType(chanTypeUid).withLabel(channelLabel); + + if (channelDesc != null) { + builder = builder.withDescription(channelDesc); + } + + Channel channel = builder.build(); + + if (channels.contains(channel)) { + return; + } + + channels.add(channel); + } + + /** + * This method creates dynamic channels in the following formats: + * consumptionSupplierIdx0, consumptionSupplierIdx1, ..., consumptionSupplierIdx9 + * consumptionDistributorIdx0, consumptionDistributorIdx1, ..., consumptionDistributorIdx3 + * + * @param channels the resulting list of channels + * @param channelTypeUid : the uid of the ChannelType associated to this channel + * @param indexMode indicates whether this is Supplier or Distributor index mode + */ + private void addDynamicChannelByIdx(List channels, ChannelTypeUID chanTypeUid, IndexMode indexMode) { + String channelPrefix = CHANNEL_CONSUMPTION + indexMode.getLabel() + "Idx"; + + for (int idx = 0; idx < indexMode.getSize(); idx++) { + String channelName = channelPrefix + idx; + String channelLabel = " Consumption " + idx; + Bundle bundle = FrameworkUtil.getBundle(this.getClass()); + + String channelDesc = translationProvider.getText(bundle, "consumptionindex.description", "", null) + " \"" + + indexMode.getLabel() + " Consumption " + idx + "\""; + + addChannel(channels, chanTypeUid, LINKY_REMOTE_DAILY_GROUP, channelName, channelLabel, channelDesc); + addChannel(channels, chanTypeUid, LINKY_REMOTE_WEEKLY_GROUP, channelName, channelLabel, channelDesc); + addChannel(channels, chanTypeUid, LINKY_REMOTE_MONTHLY_GROUP, channelName, channelLabel, channelDesc); + addChannel(channels, chanTypeUid, LINKY_REMOTE_YEARLY_GROUP, channelName, channelLabel, channelDesc); + } + } + + /** + * This method creates dynamic channels labeled by tariff name: + * heuresPleines, heuresCreuses, bleuHeuresCreuses, bleuHeuresPleines, ... + * + * @param channels the resulting list of channels + * @param channelTypeUid : the uid of the ChannelType associated to this channel + * @param values : the dataset from enedis. + * @param indexMode indicates whether this is Supplier or Distributor index mode + */ + private void addDynamicChannelByLabel(List channels, ChannelTypeUID chanTypeUid, MeterReading values, + IndexMode indexMode) { + + if (indexMode.getIdx() < 0) { + logger.error( + "We only support indexMode values of Supplier or Distributor. Your incoming data seems corrupted—please check! !"); + return; + } + + for (IntervalReading ir : values.baseValue) { + String[] labels = ir.indexInfo[indexMode.getIdx()].label; + + for (String st : labels) { + if (st == null) { + continue; + } + + String channelName = sanetizeId(st); + String channelLabel = st; + Bundle bundle = FrameworkUtil.getBundle(this.getClass()); + + String channelDesc = translationProvider.getText(bundle, "consumptionindex.description", "", null) + + " \"" + st + "\""; + + addChannel(channels, chanTypeUid, LINKY_REMOTE_DAILY_GROUP, channelName, channelLabel, channelDesc); + addChannel(channels, chanTypeUid, LINKY_REMOTE_WEEKLY_GROUP, channelName, channelLabel, channelDesc); + addChannel(channels, chanTypeUid, LINKY_REMOTE_MONTHLY_GROUP, channelName, channelLabel, channelDesc); + addChannel(channels, chanTypeUid, LINKY_REMOTE_YEARLY_GROUP, channelName, channelLabel, channelDesc); + } + } + } + + /** + * This method dynamically creates new channels at runtime when we read datasets from Enedis. + * We do this because we want to expose an index for each tariff. + * For the Tempo tariff for example, you will have 6 different channels. + * + * But this is not the only available tariff, so listing all possible channels in a resource file will not work. + * It's much easier to create them by looking at the available tariffs in the customer dataset. + * + * There will be two sets of channels: + * - Enedis channels: + * Supplier0 to Supplier9: will expose original Enedis Supplier indexes. + * Distributor0 to Distributor3: will expose original Enedis Distributor indexes. + * + * - Named channels: + * Will enable having more meaningful channel names. For example: + * - For “Heures Pleines / Heures Creuses” tariff: channel names will be heuresPleines / heuresCreuses. + * - For Tempo tariff: channel names will be + * bleuHeuresCreuses / bleuHeuresPleines / blancHeuresCreuses / blancHeuresPleines / rougeHeuresCreuses / + * rougeHeuresPleines. + * + * @param values : the dataset from enedis. + */ + private void handleDynamicChannel(MeterReading values) { + ChannelTypeUID chanTypeUid = new ChannelTypeUID(LinkyBindingConstants.BINDING_ID, + LinkyBindingConstants.CHANNEL_TYPE_CONSUMPTION); + List channels = new ArrayList(); + + addDynamicChannelByLabel(channels, chanTypeUid, values, IndexMode.SUPPLIER); + addDynamicChannelByLabel(channels, chanTypeUid, values, IndexMode.DISTRIBUTOR); + + addDynamicChannelByIdx(channels, chanTypeUid, IndexMode.SUPPLIER); + addDynamicChannelByIdx(channels, chanTypeUid, IndexMode.DISTRIBUTOR); + + // If we have channel change, update the thing + if (!channels.isEmpty()) { + Thing thing = this.getThing(); + + for (Channel chan : thing.getChannels()) { + channels.add(chan); + } + + updateThing(editThing().withChannels(channels).build()); + } + } + + /** + * This method takes the full dataset and returns a list of subdatasets, + * split on tariff changes. + * This can happen if, in your subscription, you ask your supplier to change the tariff. + * For example, moving from the Heures Pleines / Heures Creuses tariff to the Tempo tariff. + * + * We perform this split because we want to expose tariffs on dedicated named channels, + * making it easier to display them on a chart. + * + * @param irs the incoming data in the form of a single IntervalReading + * @param indexMode indicates whether this is Supplier or Distributor index mode + * @return a list of subdatasets split on tariff change + */ + private List splitOnTariffBound(IntervalReading[] irs, IndexMode indexMode) { + List result = new ArrayList(); + String currentTarif = ""; + int lastIdx = 0; + + if (indexMode.getIdx() < 0) { + logger.error( + "We only support indexMode values of Supplier or Distributor. Your incoming data seems corrupted—please check! !"); + return result; + } + + for (int idx = 0; idx < irs.length; idx++) { + IntervalReading ir = irs[idx]; + if (ir != null) { + String tarif = String.join("#", ir.indexInfo[indexMode.getIdx()].label); + + if ((!tarif.equals(currentTarif) && !currentTarif.isEmpty()) || (idx == irs.length - 1)) { + IntervalReading[] subArray; + if (idx == irs.length - 1) { + subArray = Arrays.copyOfRange(irs, lastIdx, idx + 1); + } else { + subArray = Arrays.copyOfRange(irs, lastIdx, idx); + } + result.add(subArray); + lastIdx = idx; + } + + currentTarif = tarif; + } + } + return result; + } + + /** + * The updateEnergyIndex method updates time series for a given energy index. + * Two time series are updated for each energy index: + * - The index-based time series. + * - The tariff-labeled base time series. + * + * @param irs the incoming data in the form of a single IntervalReading + * @param groupName the group name associate to this channel + * @param indexMode indicates whether this is Supplier or Distributor index mode + */ + private void updateEnergyIndex(IntervalReading[] irs, String groupName, IndexMode indexMode) { + List lirs = splitOnTariffBound(irs, indexMode); + + if (indexMode.getIdx() < 0) { + logger.error( + "We only support indexMode values of Supplier or Distributor. Your incoming data seems corrupted—please check! !"); + return; + } + + int size = indexMode.getSize(); + String channelPrefix = CHANNEL_CONSUMPTION + indexMode.getLabel() + "Idx"; + + for (int idx = 0; idx < size; idx++) { + updateTimeSeries(groupName, channelPrefix + idx, irs, idx, Units.KILOWATT_HOUR, indexMode); + + for (IntervalReading[] ir : lirs) { + String[] label = ir[0].indexInfo[indexMode.getIdx()].label; + + if (label == null || label[idx] == null) { + continue; + } + + updateTimeSeries(groupName, sanetizeId(label[idx]), ir, idx, Units.KILOWATT_HOUR, indexMode); + } + } + } + + private void updateEnergyIndex(IntervalReading[] irs, String groupName) { + updateEnergyIndex(irs, groupName, IndexMode.SUPPLIER); + updateEnergyIndex(irs, groupName, IndexMode.DISTRIBUTOR); + } + + /** + * Requests new daily or weekly data and updates the channels. + */ + private synchronized void updateEnergyIndex() { + if (!(getBridge() instanceof BridgeRemoteEnedisWebHandler)) { + return; + } + dailyIndex.getValue().ifPresentOrElse(values -> { + handleDynamicChannel(values); + + updateEnergyIndex(values.baseValue, LINKY_REMOTE_DAILY_GROUP); + updateEnergyIndex(values.weekValue, LINKY_REMOTE_WEEKLY_GROUP); + updateEnergyIndex(values.monthValue, LINKY_REMOTE_MONTHLY_GROUP); + updateEnergyIndex(values.yearValue, LINKY_REMOTE_YEARLY_GROUP); + }, () -> { + }); + } + + /** + * Requests new load curve data and updates the channels. */ private synchronized void updateLoadCurveData() { if (isLinked(LINKY_REMOTE_LOAD_CURVE_GROUP, CHANNEL_POWER)) { loadCurveConsumption.getValue().ifPresentOrElse(values -> { - updateTimeSeries(LINKY_REMOTE_LOAD_CURVE_GROUP, CHANNEL_POWER, values.baseValue, + updateTimeSeries(LINKY_REMOTE_LOAD_CURVE_GROUP, CHANNEL_POWER, values.baseValue, -1, MetricPrefix.KILO(Units.VOLT_AMPERE)); }, () -> { }); @@ -501,23 +803,41 @@ private synchronized void updateLoadCurveData() { } private synchronized > void updateTimeSeries(String groupId, String channelId, - IntervalReading[] iv, Unit unit) { + IntervalReading[] iv, int idx, Unit unit) { + updateTimeSeries(groupId, channelId, iv, idx, unit, IndexMode.NONE); + } + + private synchronized > void updateTimeSeries(String groupId, String channelId, + IntervalReading[] iv, int idx, Unit unit, IndexMode indexMode) { TimeSeries timeSeries = new TimeSeries(Policy.REPLACE); for (int i = 0; i < iv.length; i++) { try { + if (iv[i] == null) { + continue; + } if (iv[i].date == null) { continue; } Instant timestamp = iv[i].date.atZone(zoneId).toInstant(); - if (Double.isNaN(iv[i].value)) { - continue; + if (indexMode == IndexMode.NONE) { + if (Double.isNaN(iv[i].value)) { + continue; + } + + timeSeries.add(timestamp, new QuantityType<>(iv[i].value, unit)); + } else { + int indexIdx = indexMode.getIdx(); + + if (iv[i].indexInfo[indexIdx].label[idx] != null + && !Double.isNaN(iv[i].indexInfo[indexIdx].value[idx])) { + timeSeries.add(timestamp, new QuantityType<>(iv[i].indexInfo[indexIdx].value[idx], unit)); + } } - timeSeries.add(timestamp, new QuantityType<>(iv[i].value, unit)); } catch (Exception ex) { - logger.error("error occurs durring updatePowerTimeSeries for {} : {}", config.prmId, ex.getMessage(), + logger.error("error occurs during updatePowerTimeSeries for {} : {}", config.prmId, ex.getMessage(), ex); } } @@ -544,15 +864,14 @@ protected void sendTimeSeries(String groupId, String channelID, TimeSeries timeS } /** - * Produce a report of all daily values between two dates + * Produces a report of all daily values between two dates. * * @param startDay the start day of the report * @param endDay the end day of the report - * @param separator the separator to be used betwwen the date and the value + * @param separator the separator to be used between the date and the value * - * @return the report as a list of string + * @return the report as a list of strings */ - public synchronized List reportValues(LocalDate startDay, LocalDate endDay, @Nullable String separator) { return buildReport(startDay, endDay, separator); } @@ -619,6 +938,23 @@ private List buildReport(LocalDate startDay, LocalDate endDay, @Nullable return null; } + private @Nullable MeterReading getConsumptionIndex(LocalDate from, LocalDate to) { + logger.debug("getConsumptionIndex for {} from {} to {}", config.prmId, + from.format(DateTimeFormatter.ISO_LOCAL_DATE), to.format(DateTimeFormatter.ISO_LOCAL_DATE)); + + EnedisHttpApi api = this.enedisApi; + if (api != null) { + try { + return api.getEnergyIndex(this, this.userId, config.prmId, segment, from, to); + } catch (LinkyException e) { + logger.debug("Exception when getting consumption data for {} : {}", config.prmId, e.getMessage(), e); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, e.getMessage()); + } + } + + return null; + } + private @Nullable MeterReading getLoadCurveConsumption(LocalDate from, LocalDate to) { logger.debug("getLoadCurveConsumption for {} from {} to {}", config.prmId, from.format(DateTimeFormatter.ISO_LOCAL_DATE), to.format(DateTimeFormatter.ISO_LOCAL_DATE)); @@ -680,6 +1016,101 @@ public void handleCommand(ChannelUID channelUID, Command command) { } } + /** + * This method initializes the IndexInfo data structure for a given IntervalReading. + * + * @param ir the IntervalReading to initialize + */ + private void initIntervalReadingTarif(IntervalReading ir) { + if (ir.indexInfo == null) { + ir.indexInfo = new IndexInfo[2]; + ir.indexInfo[0] = new IndexInfo(); + ir.indexInfo[1] = new IndexInfo(); + + ir.indexInfo[0].value = new double[10]; + ir.indexInfo[0].label = new String[10]; + + ir.indexInfo[1].value = new double[4]; + ir.indexInfo[1].label = new String[4]; + } + } + + /** + * This method sums the daily index value into the respective week, month, and year indexes. + * This is done for Supplier or Distributor indexes according to the specified indexMode. + * + * @param indexMode the index mode: Supplier or Distributor + * @param meterReading the incoming meter reading data + * @param ir the incoming IntervalReading + * @param idxWeek : the index of the Week we wants to sum + * @param weeksNum : the maximum index for the Week + * @param idxMonth : the index of the Month we wants to sum + * @param monthsNum : the maximum index for the Month + * @param idxYear : the index of the Year we wants to sum + * @param yearsNum : the maximum index for the Year + */ + public void sumIndex(IndexMode indexMode, MeterReading meterReading, IntervalReading ir, int idxWeek, int weeksNum, + int idxMonth, int monthsNum, int idxYear, int yearsNum) { + int indexIdx = indexMode.getIdx(); + int size = indexMode.getSize(); + + if (ir.indexInfo[indexIdx].value != null) { + for (int idxIndex = 0; idxIndex < size; idxIndex++) { + double valIndex = ir.indexInfo[indexIdx].value[idxIndex]; + String label = ir.indexInfo[indexIdx].label[idxIndex]; + + // Sums day to week + if (idxWeek < weeksNum) { + meterReading.weekValue[idxWeek].indexInfo[indexIdx].value[idxIndex] += valIndex; + meterReading.weekValue[idxWeek].indexInfo[indexIdx].label[idxIndex] = label; + } + + // Sums day to month + if (idxMonth < monthsNum) { + meterReading.monthValue[idxMonth].indexInfo[indexIdx].value[idxIndex] += valIndex; + meterReading.monthValue[idxMonth].indexInfo[indexIdx].label[idxIndex] = label; + } + + // Sums day to year + if (idxYear < yearsNum) { + meterReading.yearValue[idxYear].indexInfo[indexIdx].value[idxIndex] += valIndex; + meterReading.yearValue[idxYear].indexInfo[indexIdx].label[idxIndex] = label; + } + } + } + } + + private void checkData(@Nullable MeterReading meterReading) throws LinkyException { + if (meterReading != null) { + if (meterReading.baseValue.length == 0) { + throw new LinkyException("Invalid meterReading data: no day period"); + } + } else { + throw new LinkyException("Invalid meterReading == null"); + } + } + + private boolean isDataLastDayAvailable(@Nullable MeterReading meterReading) { + if (meterReading != null) { + IntervalReading[] iv = meterReading.baseValue; + + logData(iv, "Last day", DateTimeFormatter.ISO_LOCAL_DATE, Target.LAST); + return iv != null && iv.length != 0 && iv[iv.length - 1] != null && !iv[iv.length - 1].value.isNaN(); + } + + return false; + } + + /** + * This method performs basic checks on a dataset from Enedis + * and calculates the weekly, monthly, and yearly aggregates. + * + * When data comes from Enedis, it is available only day by day. + * To get values for a week, month, or year, we need to sum the daily data. + * + * @param meterReading the incoming data to check + * @return the resulting data after checks + */ public @Nullable MeterReading getMeterReadingAfterChecks(@Nullable MeterReading meterReading) { try { checkData(meterReading); @@ -739,12 +1170,16 @@ public void handleCommand(ChannelUID channelUID, Command command) { int idxWeek = (idxYear * 52) + dtWeek - baseWeek; int month = dt.getMonthValue(); + // Sums day to week if (idxWeek < weeksNum) { meterReading.weekValue[idxWeek].value += value; + if (meterReading.weekValue[idxWeek].date == null) { meterReading.weekValue[idxWeek].date = dt; } } + + // Sums day to month if (idxMonth < monthsNum) { meterReading.monthValue[idxMonth].value += value; if (meterReading.monthValue[idxMonth].date == null) { @@ -752,38 +1187,29 @@ public void handleCommand(ChannelUID channelUID, Command command) { } } + // Sums day to year if (idxYear < yearsNum) { meterReading.yearValue[idxYear].value += value; if (meterReading.yearValue[idxYear].date == null) { meterReading.yearValue[idxYear].date = LocalDateTime.of(dt.getYear(), 1, 1, 0, 0); } } - } - } - } - return meterReading; - } + if (ir.indexInfo != null) { + initIntervalReadingTarif(meterReading.weekValue[idxWeek]); + initIntervalReadingTarif(meterReading.monthValue[idxMonth]); + initIntervalReadingTarif(meterReading.yearValue[idxYear]); - private void checkData(@Nullable MeterReading meterReading) throws LinkyException { - if (meterReading != null) { - if (meterReading.baseValue.length == 0) { - throw new LinkyException("Invalid meterReading data: no day period"); + sumIndex(IndexMode.SUPPLIER, meterReading, ir, idxWeek, weeksNum, idxMonth, monthsNum, idxYear, + yearsNum); + sumIndex(IndexMode.DISTRIBUTOR, meterReading, ir, idxWeek, weeksNum, idxMonth, monthsNum, + idxYear, yearsNum); + } + } } - } else { - throw new LinkyException("Invalid meterReading == null"); } - } - private boolean isDataLastDayAvailable(@Nullable MeterReading meterReading) { - if (meterReading != null) { - IntervalReading[] iv = meterReading.baseValue; - - logData(iv, "Last day", DateTimeFormatter.ISO_LOCAL_DATE, Target.LAST); - return iv.length != 0 && !iv[iv.length - 1].value.isNaN(); - } - - return false; + return meterReading; } private void logData(IntervalReading[] ivArray, String title, DateTimeFormatter dateTimeFormatter, Target target) { diff --git a/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/utils/DoubleTypeAdapter.java b/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/utils/DoubleTypeAdapter.java index c3c927be81cf5..22ba90c6fcf70 100644 --- a/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/utils/DoubleTypeAdapter.java +++ b/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/utils/DoubleTypeAdapter.java @@ -44,6 +44,9 @@ public class DoubleTypeAdapter extends TypeAdapter { return Double.NaN; } String stringValue = reader.nextString(); + if (stringValue != null) { + stringValue = stringValue.replace(',', '.'); + } try { Double value = Double.valueOf(stringValue); return value; diff --git a/bundles/org.openhab.binding.linky/src/main/resources/OH-INF/i18n/linky.properties b/bundles/org.openhab.binding.linky/src/main/resources/OH-INF/i18n/linky.properties index 0da0c67a429ff..c916bd0a298bb 100644 --- a/bundles/org.openhab.binding.linky/src/main/resources/OH-INF/i18n/linky.properties +++ b/bundles/org.openhab.binding.linky/src/main/resources/OH-INF/i18n/linky.properties @@ -114,3 +114,5 @@ channel-type.linky.tempo-value.state.option.0 = Blue channel-type.linky.tempo-value.state.option.1 = White channel-type.linky.tempo-value.state.option.2 = Red channel-type.linky.timestamp.label = Timestamp + +consumptionindex.description = Consumption at given time interval for index diff --git a/bundles/org.openhab.binding.linky/src/main/resources/OH-INF/i18n/linky_fr.properties b/bundles/org.openhab.binding.linky/src/main/resources/OH-INF/i18n/linky_fr.properties index a62d52db76e83..25f83cf5f3102 100644 --- a/bundles/org.openhab.binding.linky/src/main/resources/OH-INF/i18n/linky_fr.properties +++ b/bundles/org.openhab.binding.linky/src/main/resources/OH-INF/i18n/linky_fr.properties @@ -37,6 +37,7 @@ channel-group-type.linky.yearly.channel.thisYear.label = Consommation Année Act channel-type.linky.consumption.label = Consommation Totale channel-type.linky.consumption.description = Consommation pour un intervalle de temps donné + channel-type.linky.power.label = Pic Consommation Hier channel-type.linky.power.description = Pic maximum de consommation d'énergie hier channel-type.linky.timestamp.label = Horodatage @@ -44,3 +45,5 @@ channel-type.linky.timestamp.label = Horodatage # Thing status descriptions offline.config-error-mandatory-settings = Le nom d'utilisateur, le mot de passe et l'ID d'authentification sont obligatoires. + +consumptionindex.description = Consommation pour un intervalle de temps donné pour l'index diff --git a/bundles/org.openhab.binding.linky/src/main/resources/OH-INF/thing/group-linky-remote-daily.xml b/bundles/org.openhab.binding.linky/src/main/resources/OH-INF/thing/group-linky-remote-daily.xml index 8b9d09f8b0ed7..a3fd9822b6590 100644 --- a/bundles/org.openhab.binding.linky/src/main/resources/OH-INF/thing/group-linky-remote-daily.xml +++ b/bundles/org.openhab.binding.linky/src/main/resources/OH-INF/thing/group-linky-remote-daily.xml @@ -11,52 +11,42 @@ The energy consumption for previous day - The energy consumption for day -2 - The energy consumption for day -3 - The energy consumption - Maximum power usage value - Maximum power usage value for Yesterday - Maximum power usage timestamp for Yesterday - Maximum power usage value for Day-2 - Maximum power usage timestamp for Day-2 - Maximum power usage value for Day-3 - Maximum power usage timestamp for Day-3 diff --git a/bundles/org.openhab.binding.linky/src/main/resources/OH-INF/thing/group-linky-remote-load-curve.xml b/bundles/org.openhab.binding.linky/src/main/resources/OH-INF/thing/group-linky-remote-load-curve.xml index 4f2dcc1a80b66..9f6bf4441909c 100644 --- a/bundles/org.openhab.binding.linky/src/main/resources/OH-INF/thing/group-linky-remote-load-curve.xml +++ b/bundles/org.openhab.binding.linky/src/main/resources/OH-INF/thing/group-linky-remote-load-curve.xml @@ -12,5 +12,4 @@ - diff --git a/bundles/org.openhab.binding.linky/src/main/resources/OH-INF/thing/group-linky-remote-monthly.xml b/bundles/org.openhab.binding.linky/src/main/resources/OH-INF/thing/group-linky-remote-monthly.xml index 3b6695f8d70e3..243182c811de1 100644 --- a/bundles/org.openhab.binding.linky/src/main/resources/OH-INF/thing/group-linky-remote-monthly.xml +++ b/bundles/org.openhab.binding.linky/src/main/resources/OH-INF/thing/group-linky-remote-monthly.xml @@ -11,22 +11,18 @@ The energy consumption for the current Month - The energy consumption for the previous Month - The energy consumption for the Month -2 - The energy consumption - Maximum power usage value diff --git a/bundles/org.openhab.binding.linky/src/test/java/org/openhab/binding/linky/internal/handler/ThingLinkyRemoteHandlerTest.java b/bundles/org.openhab.binding.linky/src/test/java/org/openhab/binding/linky/internal/handler/ThingLinkyRemoteHandlerTest.java index 3933b20b4050c..a14461e00788b 100644 --- a/bundles/org.openhab.binding.linky/src/test/java/org/openhab/binding/linky/internal/handler/ThingLinkyRemoteHandlerTest.java +++ b/bundles/org.openhab.binding.linky/src/test/java/org/openhab/binding/linky/internal/handler/ThingLinkyRemoteHandlerTest.java @@ -28,6 +28,7 @@ import org.openhab.binding.linky.internal.dto.MeterReading; import org.openhab.core.i18n.LocaleProvider; import org.openhab.core.i18n.TimeZoneProvider; +import org.openhab.core.i18n.TranslationProvider; import org.openhab.core.thing.Thing; /** @@ -49,6 +50,9 @@ public class ThingLinkyRemoteHandlerTest { @Mock TimeZoneProvider tzProvider; + @Mock + TranslationProvider translationProvider; + @Mock LinkyThingRemoteConfiguration config; @@ -71,7 +75,7 @@ public void setUp() { @Test public void testBase() { - handler = new ThingLinkyRemoteHandler(thing, localProvider, tzProvider); + handler = new ThingLinkyRemoteHandler(thing, localProvider, tzProvider, translationProvider); MeterReading mr = getMeterReadingAfterChecks(handler, null); assertEquals(mr, null); @@ -79,7 +83,7 @@ public void testBase() { @Test public void testValidRange1() { - handler = new ThingLinkyRemoteHandler(thing, localProvider, tzProvider); + handler = new ThingLinkyRemoteHandler(thing, localProvider, tzProvider, translationProvider); MeterReading mr = new MeterReading(); mr.baseValue = new IntervalReading[75]; @@ -129,7 +133,7 @@ public void testValidRange1() { @Test public void testValidRange2() { - handler = new ThingLinkyRemoteHandler(thing, localProvider, tzProvider); + handler = new ThingLinkyRemoteHandler(thing, localProvider, tzProvider, translationProvider); MeterReading mr = new MeterReading(); mr.baseValue = new IntervalReading[128]; @@ -195,7 +199,7 @@ public void testValidRange2() { @Test public void testValidRange3() { - handler = new ThingLinkyRemoteHandler(thing, localProvider, tzProvider); + handler = new ThingLinkyRemoteHandler(thing, localProvider, tzProvider, translationProvider); MeterReading mr = new MeterReading(); mr.baseValue = new IntervalReading[716]; @@ -280,7 +284,7 @@ public void testValidRange3() { @Test public void testValidRange4() { - handler = new ThingLinkyRemoteHandler(thing, localProvider, tzProvider); + handler = new ThingLinkyRemoteHandler(thing, localProvider, tzProvider, translationProvider); MeterReading mr = new MeterReading(); mr.baseValue = new IntervalReading[35];