From 3d5a45fa5cb69d56a32db18482d649bec03b8083 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20L=27hopital?= Date: Sat, 9 Nov 2024 20:44:02 +0100 Subject: [PATCH] [linky] Yet another website underlaying API modification (#17538) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Yet another website underlaying API modification * Correction for current and previous week, month, year * Added unitHing * Switch peek power to kVA * Adding new cookie and user agent Signed-off-by: Gaël L'hopital --- bundles/org.openhab.binding.linky/README.md | 2 +- bundles/org.openhab.binding.linky/pom.xml | 4 + .../linky/internal/LinkyException.java | 4 +- .../linky/internal/LinkyHandlerFactory.java | 2 + .../linky/internal/api/EnedisHttpApi.java | 148 ++++++++++-------- .../binding/linky/internal/dto/PrmDetail.java | 80 ++++++++++ .../binding/linky/internal/dto/PrmInfo.java | 28 +--- .../linky/internal/handler/LinkyHandler.java | 95 ++++++----- .../resources/OH-INF/thing/thing-types.xml | 4 +- 9 files changed, 223 insertions(+), 144 deletions(-) create mode 100644 bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/dto/PrmDetail.java diff --git a/bundles/org.openhab.binding.linky/README.md b/bundles/org.openhab.binding.linky/README.md index 637f687c24c..1f7aa92d1ee 100644 --- a/bundles/org.openhab.binding.linky/README.md +++ b/bundles/org.openhab.binding.linky/README.md @@ -83,7 +83,7 @@ In case you are running openHAB inside Docker, the binding will work only if you ### Thing ```java -Thing linky:linky:local "Compteur Linky" [ username="example@domaine.fr", password="******" ] +Thing linky:linky:local "Compteur Linky" [ username="example@domaine.fr", password="******", internalAuthId="******" ] ``` ### Items diff --git a/bundles/org.openhab.binding.linky/pom.xml b/bundles/org.openhab.binding.linky/pom.xml index b81f3453608..0324aa2c584 100644 --- a/bundles/org.openhab.binding.linky/pom.xml +++ b/bundles/org.openhab.binding.linky/pom.xml @@ -12,6 +12,10 @@ openHAB Add-ons :: Bundles :: Linky Binding + + javax.annotation.meta;resolution:=optional + + org.jsoup diff --git a/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/LinkyException.java b/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/LinkyException.java index fe03e31b98d..728078d5bc2 100644 --- a/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/LinkyException.java +++ b/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/LinkyException.java @@ -36,10 +36,10 @@ public class LinkyException extends Exception { } public LinkyException(String message, Object... params) { - this(String.format(message, params)); + this(message.formatted(params)); } public LinkyException(Exception e, String message, Object... params) { - this(e, String.format(message, params)); + this(e, message.formatted(params)); } } diff --git a/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/LinkyHandlerFactory.java b/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/LinkyHandlerFactory.java index 2981b6f3125..315ed268c12 100644 --- a/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/LinkyHandlerFactory.java +++ b/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/LinkyHandlerFactory.java @@ -56,6 +56,7 @@ import com.google.gson.JsonDeserializer; public class LinkyHandlerFactory extends BaseThingHandlerFactory { private static final DateTimeFormatter LINKY_FORMATTER = DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss.SSSX"); private static final int REQUEST_BUFFER_SIZE = 8000; + private static final int RESPONSE_BUFFER_SIZE = 200000; private final Logger logger = LoggerFactory.getLogger(LinkyHandlerFactory.class); private final Gson gson = new GsonBuilder().registerTypeAdapter(ZonedDateTime.class, @@ -83,6 +84,7 @@ public class LinkyHandlerFactory extends BaseThingHandlerFactory { this.httpClient = httpClientFactory.createHttpClient(LinkyBindingConstants.BINDING_ID, sslContextFactory); httpClient.setFollowRedirects(false); httpClient.setRequestBufferSize(REQUEST_BUFFER_SIZE); + httpClient.setResponseBufferSize(RESPONSE_BUFFER_SIZE); } @Override 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 9d7c7d4fc3e..ea08dca0a6b 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 @@ -16,18 +16,26 @@ import java.net.HttpCookie; import java.net.URI; import java.time.LocalDate; import java.time.format.DateTimeFormatter; +import java.util.HashMap; +import java.util.List; import java.util.Objects; +import java.util.Optional; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeoutException; import java.util.regex.Matcher; import java.util.regex.Pattern; +import javax.ws.rs.core.MediaType; + import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.util.FormContentProvider; import org.eclipse.jetty.client.util.StringContentProvider; import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.http.HttpMethod; +import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.util.Fields; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; @@ -38,6 +46,7 @@ import org.openhab.binding.linky.internal.dto.AuthData; import org.openhab.binding.linky.internal.dto.AuthResult; import org.openhab.binding.linky.internal.dto.ConsumptionReport; import org.openhab.binding.linky.internal.dto.ConsumptionReport.Consumption; +import org.openhab.binding.linky.internal.dto.PrmDetail; import org.openhab.binding.linky.internal.dto.PrmInfo; import org.openhab.binding.linky.internal.dto.UserInfo; import org.slf4j.Logger; @@ -59,9 +68,10 @@ public class EnedisHttpApi { private static final String URL_MON_COMPTE = "https://mon-compte" + ENEDIS_DOMAIN; private static final String URL_COMPTE_PART = URL_MON_COMPTE.replace("compte", "compte-particulier"); private static final String URL_ENEDIS_AUTHENTICATE = URL_APPS_LINCS + "/authenticate?target=" + URL_COMPTE_PART; + private static final String USER_INFO_CONTRACT_URL = URL_APPS_LINCS + "/mon-compte-client/api/private/v1/userinfos"; private static final String USER_INFO_URL = URL_APPS_LINCS + "/userinfos"; private static final String PRM_INFO_BASE_URL = URL_APPS_LINCS + "/mes-mesures/api/private/v1/personnes/"; - private static final String PRM_INFO_URL = PRM_INFO_BASE_URL + "null/prms"; + private static final String PRM_INFO_URL = URL_APPS_LINCS + "/mes-prms/api/private/v2/personnes/%s/prms"; private static final String MEASURE_URL = PRM_INFO_BASE_URL + "%s/prms/%s/donnees-%s?dateDebut=%s&dateFin=%s&mesuretypecode=CONS"; private static final URI COOKIE_URI = URI.create(URL_COMPTE_PART); @@ -81,22 +91,22 @@ public class EnedisHttpApi { } public void initialize() throws LinkyException { - logger.debug("Starting login process for user : {}", config.username); + logger.debug("Starting login process for user: {}", config.username); try { addCookie(LinkyConfiguration.INTERNAL_AUTH_ID, config.internalAuthId); - logger.debug("Step 1 : getting authentification"); - String data = getData(URL_ENEDIS_AUTHENTICATE); + logger.debug("Step 1: getting authentification"); + String data = getContent(URL_ENEDIS_AUTHENTICATE); logger.debug("Reception request SAML"); Document htmlDocument = Jsoup.parse(data); Element el = htmlDocument.select("form").first(); Element samlInput = el.select("input[name=SAMLRequest]").first(); - logger.debug("Step 2 : send SSO SAMLRequest"); + logger.debug("Step 2: send SSO SAMLRequest"); ContentResponse result = httpClient.POST(el.attr("action")) .content(getFormContent("SAMLRequest", samlInput.attr("value"))).send(); - if (result.getStatus() != 302) { + if (result.getStatus() != HttpStatus.FOUND_302) { throw new LinkyException("Connection failed step 2"); } @@ -112,11 +122,11 @@ public class EnedisHttpApi { + reqId + "%26index%3Dnull%26acsURL%3D" + URL_APPS_LINCS + "/saml/SSO%26spEntityID%3DSP-ODW-PROD%26binding%3Durn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST&AMAuthCookie="; - logger.debug("Step 3 : auth1 - retrieve the template, thanks to cookie internalAuthId user is already set"); + logger.debug("Step 3: auth1 - retrieve the template, thanks to cookie internalAuthId user is already set"); result = httpClient.POST(authenticateUrl).header("X-NoSession", "true").header("X-Password", "anonymous") .header("X-Requested-With", "XMLHttpRequest").header("X-Username", "anonymous").send(); - if (result.getStatus() != 200) { - throw new LinkyException("Connection failed step 3 - auth1 : %s", result.getContentAsString()); + if (result.getStatus() != HttpStatus.OK_200) { + throw new LinkyException("Connection failed step 3 - auth1: %s", result.getContentAsString()); } AuthData authData = gson.fromJson(result.getContentAsString(), AuthData.class); @@ -128,13 +138,13 @@ public class EnedisHttpApi { } authData.callbacks.get(1).input.get(0).value = config.password; - logger.debug("Step 4 : auth2 - send the auth data"); - result = httpClient.POST(authenticateUrl).header(HttpHeader.CONTENT_TYPE, "application/json") + logger.debug("Step 4: auth2 - send the auth data"); + result = httpClient.POST(authenticateUrl).header(HttpHeader.CONTENT_TYPE, MediaType.APPLICATION_JSON) .header("X-NoSession", "true").header("X-Password", "anonymous") .header("X-Requested-With", "XMLHttpRequest").header("X-Username", "anonymous") .content(new StringContentProvider(gson.toJson(authData))).send(); - if (result.getStatus() != 200) { - throw new LinkyException("Connection failed step 3 - auth2 : %s", result.getContentAsString()); + if (result.getStatus() != HttpStatus.OK_200) { + throw new LinkyException("Connection failed step 3 - auth2: %s", result.getContentAsString()); } AuthResult authResult = gson.fromJson(result.getContentAsString(), AuthResult.class); @@ -145,18 +155,40 @@ public class EnedisHttpApi { logger.debug("Add the tokenId cookie"); addCookie("enedisExt", authResult.tokenId); - logger.debug("Step 5 : retrieve the SAMLresponse"); - data = getData(URL_MON_COMPTE + "/" + authResult.successUrl); + logger.debug("Step 5: retrieve the SAMLresponse"); + data = getContent(URL_MON_COMPTE + "/" + authResult.successUrl); htmlDocument = Jsoup.parse(data); el = htmlDocument.select("form").first(); samlInput = el.select("input[name=SAMLResponse]").first(); - logger.debug("Step 6 : post the SAMLresponse to finish the authentication"); + logger.debug("Step 6: post the SAMLresponse to finish the authentication"); result = httpClient.POST(el.attr("action")).content(getFormContent("SAMLResponse", samlInput.attr("value"))) .send(); - if (result.getStatus() != 302) { + if (result.getStatus() != HttpStatus.FOUND_302) { throw new LinkyException("Connection failed step 6"); } + + logger.debug("Step 7: retrieve cookieKey"); + result = httpClient.GET(USER_INFO_CONTRACT_URL); + + @SuppressWarnings("unchecked") + HashMap hashRes = gson.fromJson(result.getContentAsString(), HashMap.class); + + String cookieKey; + if (hashRes != null && hashRes.containsKey("cnAlex")) { + cookieKey = "personne_for_" + hashRes.get("cnAlex"); + } else { + throw new LinkyException("Connection failed step 7, missing cookieKey"); + } + + List lCookie = httpClient.getCookieStore().getCookies(); + Optional cookie = lCookie.stream().filter(it -> it.getName().contains(cookieKey)).findFirst(); + + String cookieVal = cookie.map(HttpCookie::getValue) + .orElseThrow(() -> new LinkyException("Connection failed step 7, missing cookieVal")); + + addCookie(cookieKey, cookieVal); + connected = true; } catch (InterruptedException | TimeoutException | ExecutionException | JsonSyntaxException e) { throw new LinkyException(e, "Error opening connection with Enedis webservice"); @@ -203,76 +235,64 @@ public class EnedisHttpApi { return new FormContentProvider(fields); } - private String getData(String url) throws LinkyException { + private String getContent(String url) throws LinkyException { try { - ContentResponse result = httpClient.GET(url); - if (result.getStatus() != 200) { - throw new LinkyException("Error requesting '%s' : %s", url, result.getContentAsString()); + Request request = httpClient.newRequest(url) + .agent("Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0"); + request = request.method(HttpMethod.GET); + ContentResponse result = request.send(); + if (result.getStatus() != HttpStatus.OK_200) { + throw new LinkyException("Error requesting '%s': %s", url, result.getContentAsString()); } - return result.getContentAsString(); + String content = result.getContentAsString(); + logger.trace("getContent returned {}", content); + return content; } catch (InterruptedException | ExecutionException | TimeoutException e) { - throw new LinkyException(e, "Error getting url : '%s'", url); + throw new LinkyException(e, "Error getting url: '%s'", url); } } - public PrmInfo getPrmInfo() throws LinkyException { + private T getData(String url, Class clazz) throws LinkyException { if (!connected) { initialize(); } - String data = getData(PRM_INFO_URL); + String data = getContent(url); if (data.isEmpty()) { - throw new LinkyException("Requesting '%s' returned an empty response", PRM_INFO_URL); + throw new LinkyException("Requesting '%s' returned an empty response", url); } try { - PrmInfo[] prms = gson.fromJson(data, PrmInfo[].class); - if (prms == null || prms.length < 1) { - throw new LinkyException("Invalid prms data received"); - } - return prms[0]; + return Objects.requireNonNull(gson.fromJson(data, clazz)); } catch (JsonSyntaxException e) { - logger.debug("invalid JSON response not matching PrmInfo[].class: {}", data); - throw new LinkyException(e, "Requesting '%s' returned an invalid JSON response", PRM_INFO_URL); + logger.debug("Invalid JSON response not matching {}: {}", clazz.getName(), data); + throw new LinkyException(e, "Requesting '%s' returned an invalid JSON response", url); } } + public PrmInfo getPrmInfo(String internId) throws LinkyException { + String url = PRM_INFO_URL.formatted(internId); + PrmInfo[] prms = getData(url, PrmInfo[].class); + if (prms.length < 1) { + throw new LinkyException("Invalid prms data received"); + } + return prms[0]; + } + + public PrmDetail getPrmDetails(String internId, String prmId) throws LinkyException { + String url = PRM_INFO_URL.formatted(internId) + "/" + prmId + + "?embed=SITALI&embed=SITCOM&embed=SITCON&embed=SYNCON"; + return getData(url, PrmDetail.class); + } + public UserInfo getUserInfo() throws LinkyException { - if (!connected) { - initialize(); - } - String data = getData(USER_INFO_URL); - if (data.isEmpty()) { - throw new LinkyException("Requesting '%s' returned an empty response", USER_INFO_URL); - } - try { - return Objects.requireNonNull(gson.fromJson(data, UserInfo.class)); - } catch (JsonSyntaxException e) { - logger.debug("invalid JSON response not matching UserInfo.class: {}", data); - throw new LinkyException(e, "Requesting '%s' returned an invalid JSON response", USER_INFO_URL); - } + return getData(USER_INFO_URL, UserInfo.class); } private Consumption getMeasures(String userId, String prmId, LocalDate from, LocalDate to, String request) throws LinkyException { String url = String.format(MEASURE_URL, userId, prmId, request, from.format(API_DATE_FORMAT), to.format(API_DATE_FORMAT)); - if (!connected) { - initialize(); - } - String data = getData(url); - if (data.isEmpty()) { - throw new LinkyException("Requesting '%s' returned an empty response", url); - } - logger.trace("getData returned {}", data); - try { - ConsumptionReport report = gson.fromJson(data, ConsumptionReport.class); - if (report == null) { - throw new LinkyException("No report data received"); - } - return report.firstLevel.consumptions; - } catch (JsonSyntaxException e) { - logger.debug("invalid JSON response not matching ConsumptionReport.class: {}", data); - throw new LinkyException(e, "Requesting '%s' returned an invalid JSON response", url); - } + ConsumptionReport report = getData(url, ConsumptionReport.class); + return report.firstLevel.consumptions; } public Consumption getEnergyData(String userId, String prmId, LocalDate from, LocalDate to) throws LinkyException { diff --git a/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/dto/PrmDetail.java b/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/dto/PrmDetail.java new file mode 100644 index 00000000000..76f5aa56297 --- /dev/null +++ b/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/dto/PrmDetail.java @@ -0,0 +1,80 @@ +/** + * Copyright (c) 2010-2024 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; + +import java.util.ArrayList; + +/** + * The {@link PrmDetail} holds detailed informations about prm configuration + * + * @author Gaël L'hopital - Initial contribution + */ +public class PrmDetail { + public record Adresse(String ligne2, String ligne3, String ligne4, String ligne5, String ligne6) { + } + + public record DicEntry(String code, String libelle) { + } + + public record Measure(String unite, String valeur) { + } + + public record AlimentationPrincipale(Object puissanceRaccordementInjection, + Measure puissanceRaccordementSoutirage) { + } + + public record Compteur(boolean accessibilite, boolean ticActivee, boolean ticStandard) { + } + + public record Contrat(DicEntry typeContrat, String referenceContrat) { + } + + public record Disjoncteur(DicEntry calibre) { + } + + public record DispositifComptage(DicEntry typeComptage) { + } + + public record GrilleFournisseur(DicEntry calendrier, Object classeTemporelle) { + } + + public record InformationsContractuelles(Contrat contrat, DicEntry etatContractuel, SiContractuel siContractuel) { + } + + public record SiContractuel(DicEntry application) { + } + + public record SituationAlimentationDto(AlimentationPrincipale alimentationPrincipale) { + } + + public record SituationComptageDto(ArrayList compteurs, Disjoncteur disjoncteur, + DispositifComptage dispositifComptage) { + } + + public record SituationContractuelleDto(InformationsContractuelles informationsContractuelles, + StructureTarifaire structureTarifaire, String fournisseur, DicEntry segment) { + } + + public record StructureTarifaire(Measure puissanceSouscrite, GrilleFournisseur grilleFournisseur) { + } + + public record SyntheseContractuelleDto(DicEntry niveauOuvertureServices) { + } + + public Adresse adresse; + public String segment; + public SyntheseContractuelleDto syntheseContractuelleDto; + public SituationContractuelleDto[] situationContractuelleDtos; + public SituationAlimentationDto situationAlimentationDto; + public SituationComptageDto situationComptageDto; +} diff --git a/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/dto/PrmInfo.java b/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/dto/PrmInfo.java index f652b57cc12..b03f577de79 100644 --- a/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/dto/PrmInfo.java +++ b/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/dto/PrmInfo.java @@ -13,35 +13,11 @@ package org.openhab.binding.linky.internal.dto; /** - * The {@link UserInfo} holds informations about energy delivery point + * The {@link UserInfo} holds ids of existing Prms * * @author Gaël L'hopital - Initial contribution */ public class PrmInfo { - public class Adresse { - public Object adresseLigneUn; - public String adresseLigneDeux; - public Object adresseLigneTrois; - public String adresseLigneQuatre; - public Object adresseLigneCinq; - public String adresseLigneSix; - public String adresseLigneSept; - } - - public String prmId; - public String dateFinRole; - public String segment; - public Adresse adresse; - public String typeCompteur; - public String niveauOuvertureServices; - public String communiquant; - public long dateSoutirage; - public String dateInjection; - public int departement; - public int puissanceSouscrite; - public String codeCalendrier; - public String codeTitulaire; - public boolean collecteActivee; - public boolean multiTitulaire; + public String idPrm; } diff --git a/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/handler/LinkyHandler.java b/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/handler/LinkyHandler.java index 97b9a97ded5..09b2b38112d 100644 --- a/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/handler/LinkyHandler.java +++ b/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/handler/LinkyHandler.java @@ -35,10 +35,13 @@ import org.openhab.binding.linky.internal.api.EnedisHttpApi; import org.openhab.binding.linky.internal.api.ExpiringDayCache; import org.openhab.binding.linky.internal.dto.ConsumptionReport.Aggregate; import org.openhab.binding.linky.internal.dto.ConsumptionReport.Consumption; +import org.openhab.binding.linky.internal.dto.PrmDetail; import org.openhab.binding.linky.internal.dto.PrmInfo; +import org.openhab.binding.linky.internal.dto.UserInfo; import org.openhab.core.i18n.LocaleProvider; 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.ChannelUID; import org.openhab.core.thing.Thing; @@ -157,9 +160,13 @@ public class LinkyHandler extends BaseThingHandler { updateStatus(ThingStatus.ONLINE); if (thing.getProperties().isEmpty()) { - PrmInfo prmInfo = api.getPrmInfo(); - updateProperties(Map.of(USER_ID, api.getUserInfo().userProperties.internId, PUISSANCE, - prmInfo.puissanceSouscrite + " kVA", PRM_ID, prmInfo.prmId)); + UserInfo userInfo = api.getUserInfo(); + PrmInfo prmInfo = api.getPrmInfo(userInfo.userProperties.internId); + PrmDetail details = api.getPrmDetails(userInfo.userProperties.internId, prmInfo.idPrm); + updateProperties(Map.of(USER_ID, userInfo.userProperties.internId, PUISSANCE, + details.situationContractuelleDtos[0].structureTarifaire().puissanceSouscrite().valeur() + + " kVA", + PRM_ID, prmInfo.idPrm)); } prmId = thing.getProperties().get(PRM_ID); @@ -204,7 +211,7 @@ public class LinkyHandler extends BaseThingHandler { if (isLinked(PEAK_POWER) || isLinked(PEAK_TIMESTAMP)) { cachedPowerData.getValue().ifPresentOrElse(values -> { Aggregate days = values.aggregats.days; - updateVAChannel(PEAK_POWER, days.datas.get(days.datas.size() - 1)); + updatekVAChannel(PEAK_POWER, days.datas.get(days.datas.size() - 1)); updateState(PEAK_TIMESTAMP, new DateTimeType(days.periodes.get(days.datas.size() - 1).dateDebut)); }, () -> { updateKwhChannel(PEAK_POWER, Double.NaN); @@ -213,6 +220,19 @@ public class LinkyHandler extends BaseThingHandler { } } + private void setCurrentAndPrevious(Aggregate periods, String currentChannel, String previousChannel) { + double currentValue = 0.0; + double previousValue = 0.0; + if (!periods.datas.isEmpty()) { + currentValue = periods.datas.get(periods.datas.size() - 1); + if (periods.datas.size() > 1) { + previousValue = periods.datas.get(periods.datas.size() - 2); + } + } + updateKwhChannel(currentChannel, currentValue); + updateKwhChannel(previousChannel, previousValue); + } + /** * Request new dayly/weekly data and updates channels */ @@ -221,17 +241,7 @@ public class LinkyHandler extends BaseThingHandler { cachedDailyData.getValue().ifPresentOrElse(values -> { Aggregate days = values.aggregats.days; updateKwhChannel(YESTERDAY, days.datas.get(days.datas.size() - 1)); - int idxLast = days.periodes.get(days.periodes.size() - 1).dateDebut.get(weekFields.dayOfWeek()) == 7 ? 2 - : 1; - Aggregate weeks = values.aggregats.weeks; - if (weeks.datas.size() > idxLast) { - updateKwhChannel(LAST_WEEK, weeks.datas.get(idxLast)); - } - if (weeks.datas.size() > (idxLast + 1)) { - updateKwhChannel(THIS_WEEK, weeks.datas.get(idxLast + 1)); - } else { - updateKwhChannel(THIS_WEEK, 0.0); - } + setCurrentAndPrevious(values.aggregats.weeks, THIS_WEEK, LAST_WEEK); }, () -> { updateKwhChannel(YESTERDAY, Double.NaN); if (ZonedDateTime.now().get(weekFields.dayOfWeek()) == 1) { @@ -249,22 +259,15 @@ public class LinkyHandler extends BaseThingHandler { */ private synchronized void updateMonthlyData() { if (isLinked(LAST_MONTH) || isLinked(THIS_MONTH)) { - cachedMonthlyData.getValue().ifPresentOrElse(values -> { - Aggregate months = values.aggregats.months; - updateKwhChannel(LAST_MONTH, months.datas.get(0)); - if (months.datas.size() > 1) { - updateKwhChannel(THIS_MONTH, months.datas.get(1)); - } else { - updateKwhChannel(THIS_MONTH, 0.0); - } - }, () -> { - if (ZonedDateTime.now().getDayOfMonth() == 1) { - updateKwhChannel(THIS_MONTH, 0.0); - updateKwhChannel(LAST_MONTH, Double.NaN); - } else { - updateKwhChannel(THIS_MONTH, Double.NaN); - } - }); + cachedMonthlyData.getValue().ifPresentOrElse( + values -> setCurrentAndPrevious(values.aggregats.months, THIS_MONTH, LAST_MONTH), () -> { + if (ZonedDateTime.now().getDayOfMonth() == 1) { + updateKwhChannel(THIS_MONTH, 0.0); + updateKwhChannel(LAST_MONTH, Double.NaN); + } else { + updateKwhChannel(THIS_MONTH, Double.NaN); + } + }); } } @@ -273,22 +276,15 @@ public class LinkyHandler extends BaseThingHandler { */ private synchronized void updateYearlyData() { if (isLinked(LAST_YEAR) || isLinked(THIS_YEAR)) { - cachedYearlyData.getValue().ifPresentOrElse(values -> { - Aggregate years = values.aggregats.years; - updateKwhChannel(LAST_YEAR, years.datas.get(0)); - if (years.datas.size() > 1) { - updateKwhChannel(THIS_YEAR, years.datas.get(1)); - } else { - updateKwhChannel(THIS_YEAR, 0.0); - } - }, () -> { - if (ZonedDateTime.now().getDayOfYear() == 1) { - updateKwhChannel(THIS_YEAR, 0.0); - updateKwhChannel(LAST_YEAR, Double.NaN); - } else { - updateKwhChannel(THIS_YEAR, Double.NaN); - } - }); + cachedYearlyData.getValue().ifPresentOrElse( + values -> setCurrentAndPrevious(values.aggregats.years, THIS_YEAR, LAST_YEAR), () -> { + if (ZonedDateTime.now().getDayOfYear() == 1) { + updateKwhChannel(THIS_YEAR, 0.0); + updateKwhChannel(LAST_YEAR, Double.NaN); + } else { + updateKwhChannel(THIS_YEAR, Double.NaN); + } + }); } } @@ -298,9 +294,10 @@ public class LinkyHandler extends BaseThingHandler { Double.isNaN(consumption) ? UnDefType.UNDEF : new QuantityType<>(consumption, Units.KILOWATT_HOUR)); } - private void updateVAChannel(String channelId, double power) { + private void updatekVAChannel(String channelId, double power) { logger.debug("Update channel {} with {}", channelId, power); - updateState(channelId, Double.isNaN(power) ? UnDefType.UNDEF : new QuantityType<>(power, Units.VOLT_AMPERE)); + updateState(channelId, Double.isNaN(power) ? UnDefType.UNDEF + : new QuantityType<>(power, MetricPrefix.KILO(Units.VOLT_AMPERE))); } /** diff --git a/bundles/org.openhab.binding.linky/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.linky/src/main/resources/OH-INF/thing/thing-types.xml index cfacffdabc0..fbcdbb509e3 100644 --- a/bundles/org.openhab.binding.linky/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.linky/src/main/resources/OH-INF/thing/thing-types.xml @@ -96,10 +96,10 @@ - Number:Power + Number:Power Maximum power usage yesterday - +