]> git.basschouten.com Git - openhab-addons.git/commitdiff
[Linky] Some enhancements and corrections on the linky binding (#8871)
authorGaël L'hopital <gael@lhopital.org>
Tue, 10 Nov 2020 17:08:02 +0000 (18:08 +0100)
committerGitHub <noreply@github.com>
Tue, 10 Nov 2020 17:08:02 +0000 (09:08 -0800)
* Some enhancements and corrections on the linky binding
spotless apply
Adressing code review comments
* Adressing potential NPR.
* Code review findings correction
* Pleasing SAT checks

Signed-off-by: clinique <gael@lhopital.org>
bundles/org.openhab.binding.linky/README.md
bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/LinkyHandlerFactory.java
bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/api/EnedisHttpApi.java
bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/api/ExpiringDayCache.java
bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/dto/AuthData.java
bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/handler/LinkyHandler.java
bundles/org.openhab.binding.linky/src/main/resources/OH-INF/thing/thing-types.xml

index 47b4c7ec3d201072059d2ac42660f2917c34c3b6..535b9c49e2e39557498737b0aae49209ec1d7d42 100644 (file)
@@ -40,7 +40,8 @@ Instructions given for Firefox :
 4. Clic on "Suivant".
 5. In the login page, prefilled with your mail address, enter your Enedis account password and click on "Connexion à Espace Client Enedis".
 6. You will be directed to your Enedis account environment. Get back to previous page in you browser.
-7. Open the developper tool window (F12) and select "Stockage" tab. In the "Cookies" entry, select "https://mon-compte-enedis.fr". You should see an entry named "internalAuthId", copy this value in your Openhab configuration.
+7. Disconnect from your Enedis account
+8. Repeat steps 1, 2. You should arrive directly on step 5, then open the developer tool window (F12) and select "Stockage" tab. In the "Cookies" entry, select "https://mon-compte-enedis.fr". You'll find an entry named "internalAuthId", copy this value in your Openhab configuration.
 
 ## Channels
 
index 59c7fbaf434a0528031f4767f12d302966b77596..9eae071ea0d3edb7748b0e5898c51d6decf351fd 100644 (file)
@@ -28,9 +28,12 @@ import org.openhab.core.thing.ThingTypeUID;
 import org.openhab.core.thing.binding.BaseThingHandlerFactory;
 import org.openhab.core.thing.binding.ThingHandler;
 import org.openhab.core.thing.binding.ThingHandlerFactory;
+import org.osgi.service.component.ComponentContext;
 import org.osgi.service.component.annotations.Activate;
 import org.osgi.service.component.annotations.Component;
 import org.osgi.service.component.annotations.Reference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import com.google.gson.Gson;
 import com.google.gson.GsonBuilder;
@@ -44,6 +47,8 @@ import com.google.gson.JsonDeserializer;
 @NonNullByDefault
 @Component(service = ThingHandlerFactory.class, configurationPid = "binding.linky")
 public class LinkyHandlerFactory extends BaseThingHandlerFactory {
+    private final Logger logger = LoggerFactory.getLogger(LinkyHandlerFactory.class);
+
     private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss.SSSX");
     private final LocaleProvider localeProvider;
     private final Gson gson;
@@ -53,11 +58,33 @@ public class LinkyHandlerFactory extends BaseThingHandlerFactory {
     public LinkyHandlerFactory(final @Reference LocaleProvider localeProvider,
             final @Reference HttpClientFactory httpClientFactory) {
         this.localeProvider = localeProvider;
-        this.httpClient = httpClientFactory.createHttpClient(LinkyBindingConstants.BINDING_ID);
         this.gson = new GsonBuilder().registerTypeAdapter(ZonedDateTime.class,
                 (JsonDeserializer<ZonedDateTime>) (json, type, jsonDeserializationContext) -> ZonedDateTime
                         .parse(json.getAsJsonPrimitive().getAsString(), formatter))
                 .create();
+        this.httpClient = httpClientFactory.createHttpClient(LinkyBindingConstants.BINDING_ID);
+    }
+
+    @Override
+    protected void activate(ComponentContext componentContext) {
+        super.activate(componentContext);
+        httpClient.getSslContextFactory().setExcludeCipherSuites(new String[0]);
+        httpClient.setFollowRedirects(false);
+        try {
+            httpClient.start();
+        } catch (Exception e) {
+            logger.warn("Unable to start Jetty HttpClient {}", e.getMessage());
+        }
+    }
+
+    @Override
+    protected void deactivate(ComponentContext componentContext) {
+        super.deactivate(componentContext);
+        try {
+            httpClient.stop();
+        } catch (Exception e) {
+            logger.warn("Unable to stop Jetty HttpClient {}", e.getMessage());
+        }
     }
 
     @Override
@@ -69,10 +96,6 @@ public class LinkyHandlerFactory extends BaseThingHandlerFactory {
     protected @Nullable ThingHandler createHandler(Thing thing) {
         ThingTypeUID thingTypeUID = thing.getThingTypeUID();
 
-        if (supportsThingType(thingTypeUID)) {
-            return new LinkyHandler(thing, localeProvider, gson, httpClient);
-        }
-
-        return null;
+        return supportsThingType(thingTypeUID) ? new LinkyHandler(thing, localeProvider, gson, httpClient) : null;
     }
 }
index 01fd10031e6b24c9863e6aaa0414dcad5d09dca7..d44ece03d497bb2732d952358dbe64552c3a395f 100644 (file)
@@ -73,17 +73,6 @@ public class EnedisHttpApi {
     }
 
     public void initialize() throws LinkyException {
-        httpClient.getSslContextFactory().setExcludeCipherSuites(new String[0]);
-        httpClient.setFollowRedirects(false);
-        try {
-            httpClient.start();
-        } catch (Exception e) {
-            throw new LinkyException("Unable to start Jetty HttpClient", e);
-        }
-        connect();
-    }
-
-    private void connect() throws LinkyException {
         addCookie(LinkyConfiguration.INTERNAL_AUTH_ID, config.internalAuthId);
 
         logger.debug("Starting login process for user : {}", config.username);
@@ -185,12 +174,7 @@ public class EnedisHttpApi {
     }
 
     public void dispose() throws LinkyException {
-        try {
-            disconnect();
-            httpClient.stop();
-        } catch (Exception e) {
-            throw new LinkyException("Error stopping Jetty client", e);
-        }
+        disconnect();
     }
 
     private void addCookie(String key, String value) {
index 3d2720a43db73d52601f62fd8f310db7675ec8f2..7441f46a0dabffdbe86554afcc99ab7d5c0254fe 100644 (file)
@@ -15,6 +15,7 @@ package org.openhab.binding.linky.internal.api;
 import java.time.LocalDateTime;
 import java.time.format.DateTimeFormatter;
 import java.time.temporal.ChronoUnit;
+import java.util.Optional;
 import java.util.function.Supplier;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
@@ -64,7 +65,7 @@ public class ExpiringDayCache<V> {
     /**
      * Returns the value - possibly from the cache, if it is still valid.
      */
-    public synchronized @Nullable V getValue() {
+    public synchronized Optional<V> getValue() {
         @Nullable
         V cachedValue = value;
         if (cachedValue == null || isExpired()) {
@@ -73,7 +74,7 @@ public class ExpiringDayCache<V> {
         } else {
             logger.debug("getValue from cache \"{}\" is returning a cached value", name);
         }
-        return cachedValue;
+        return Optional.ofNullable(cachedValue);
     }
 
     /**
index 6e20453ccb9c8f7e3681ec697528c0eb644ec632..4802c900da0c09e81cf28cab67adb9bd1033c8d4 100644 (file)
@@ -15,6 +15,7 @@ package org.openhab.binding.linky.internal.dto;
 import java.util.ArrayList;
 import java.util.List;
 
+import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
 
 /**
@@ -22,12 +23,12 @@ import org.eclipse.jdt.annotation.Nullable;
  *
  * @author Gaël L'hopital - Initial contribution
  */
-
+@NonNullByDefault
 public class AuthData {
     public class AuthDataCallBack {
         public class NameValuePair {
-            public String name;
-            public Object value;
+            public @Nullable String name;
+            public @Nullable Object value;
 
             public @Nullable String valueAsString() {
                 if (value instanceof String) {
@@ -37,15 +38,15 @@ public class AuthData {
             }
         }
 
-        public String type;
+        public @Nullable String type;
 
         public List<NameValuePair> output = new ArrayList<>();
         public List<NameValuePair> input = new ArrayList<>();
     }
 
-    public String authId;
-    public String template;
-    public String stage;
-    public String header;
+    public @Nullable String authId;
+    public @Nullable String template;
+    public @Nullable String stage;
+    public @Nullable String header;
     public List<AuthDataCallBack> callbacks = new ArrayList<>();
 }
index a4e19d0dd5999bf3e1cf4977f694f7a28c590a67..6a0e147a399eaa97171be9be96ce3be37e8cdd42 100644 (file)
@@ -70,12 +70,12 @@ public class LinkyHandler extends BaseThingHandler {
 
     private final HttpClient httpClient;
     private final Gson gson;
+    private final WeekFields weekFields;
 
     private @Nullable ScheduledFuture<?> refreshJob;
     private @Nullable EnedisHttpApi enedisApi;
-    private final WeekFields weekFields;
 
-    private final ExpiringDayCache<Consumption> cachedDaylyData;
+    private final ExpiringDayCache<Consumption> cachedDailyData;
     private final ExpiringDayCache<Consumption> cachedPowerData;
     private final ExpiringDayCache<Consumption> cachedMonthlyData;
     private final ExpiringDayCache<Consumption> cachedYearlyData;
@@ -87,12 +87,11 @@ public class LinkyHandler extends BaseThingHandler {
         super(thing);
         this.gson = gson;
         this.httpClient = httpClient;
-
         this.weekFields = WeekFields.of(localeProvider.getLocale());
 
-        this.cachedDaylyData = new ExpiringDayCache<>("daily cache", REFRESH_FIRST_HOUR_OF_DAY, () -> {
+        this.cachedDailyData = new ExpiringDayCache<>("daily cache", REFRESH_FIRST_HOUR_OF_DAY, () -> {
             LocalDate today = LocalDate.now();
-            return getConsumptionData(today.minusDays(13), today);
+            return getConsumptionData(today.minusDays(15), today);
         });
 
         this.cachedPowerData = new ExpiringDayCache<>("power cache", REFRESH_FIRST_HOUR_OF_DAY, () -> {
@@ -120,31 +119,37 @@ public class LinkyHandler extends BaseThingHandler {
         LinkyConfiguration config = getConfigAs(LinkyConfiguration.class);
         enedisApi = new EnedisHttpApi(config, gson, httpClient);
 
-        try {
-            enedisApi.initialize();
-            updateStatus(ThingStatus.ONLINE);
-
-            if (thing.getProperties().isEmpty()) {
-                Map<String, String> properties = discoverAttributes();
-                updateProperties(properties);
-            }
-
-            prmId = thing.getProperties().get(PRM_ID);
-            userId = thing.getProperties().get(USER_ID);
+        scheduler.submit(() -> {
+            try {
+                EnedisHttpApi api = this.enedisApi;
+                if (api != null) {
+                    api.initialize();
+                    updateStatus(ThingStatus.ONLINE);
+
+                    if (thing.getProperties().isEmpty()) {
+                        Map<String, String> properties = discoverAttributes();
+                        updateProperties(properties);
+                    }
 
-            final LocalDateTime now = LocalDateTime.now();
-            final LocalDateTime nextDayFirstTimeUpdate = now.plusDays(1).withHour(REFRESH_FIRST_HOUR_OF_DAY)
-                    .truncatedTo(ChronoUnit.HOURS);
+                    prmId = thing.getProperties().get(PRM_ID);
+                    userId = thing.getProperties().get(USER_ID);
 
-            updateData();
+                    final LocalDateTime now = LocalDateTime.now();
+                    final LocalDateTime nextDayFirstTimeUpdate = now.plusDays(1).withHour(REFRESH_FIRST_HOUR_OF_DAY)
+                            .truncatedTo(ChronoUnit.HOURS);
 
-            refreshJob = scheduler.scheduleWithFixedDelay(this::updateData,
-                    ChronoUnit.MINUTES.between(now, nextDayFirstTimeUpdate) % REFRESH_INTERVAL_IN_MIN + 1,
-                    REFRESH_INTERVAL_IN_MIN, TimeUnit.MINUTES);
+                    updateData();
 
-        } catch (LinkyException e) {
-            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
-        }
+                    refreshJob = scheduler.scheduleWithFixedDelay(this::updateData,
+                            ChronoUnit.MINUTES.between(now, nextDayFirstTimeUpdate) % REFRESH_INTERVAL_IN_MIN + 1,
+                            REFRESH_INTERVAL_IN_MIN, TimeUnit.MINUTES);
+                } else {
+                    throw new LinkyException("Enedis Api is not initialized");
+                }
+            } catch (LinkyException e) {
+                updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
+            }
+        });
     }
 
     private Map<String, String> discoverAttributes() throws LinkyException {
@@ -167,17 +172,17 @@ public class LinkyHandler extends BaseThingHandler {
     private void updateData() {
         updatePowerData();
         updateDailyData();
+        updateWeeklyData();
         updateMonthlyData();
         updateYearlyData();
     }
 
     private synchronized void updatePowerData() {
         if (isLinked(PEAK_POWER) || isLinked(PEAK_TIMESTAMP)) {
-            Consumption result = cachedPowerData.getValue();
-            if (result != null) {
-                updateVAChannel(PEAK_POWER, result.aggregats.days.datas.get(0));
-                updateState(PEAK_TIMESTAMP, new DateTimeType(result.aggregats.days.periodes.get(0).dateDebut));
-            }
+            cachedPowerData.getValue().ifPresent(values -> {
+                updateVAChannel(PEAK_POWER, values.aggregats.days.datas.get(0));
+                updateState(PEAK_TIMESTAMP, new DateTimeType(values.aggregats.days.periodes.get(0).dateDebut));
+            });
         }
     }
 
@@ -185,23 +190,19 @@ public class LinkyHandler extends BaseThingHandler {
      * Request new dayly/weekly data and updates channels
      */
     private synchronized void updateDailyData() {
-        if (isLinked(YESTERDAY) || isLinked(LAST_WEEK) || isLinked(THIS_WEEK)) {
-            Consumption result = cachedDaylyData.getValue();
-            if (result != null) {
-                Aggregate days = result.aggregats.days;
-
+        if (isLinked(YESTERDAY) || isLinked(THIS_WEEK)) {
+            cachedDailyData.getValue().ifPresent(values -> {
+                Aggregate days = values.aggregats.days;
                 int maxValue = days.periodes.size() - 1;
                 int thisWeekNumber = days.periodes.get(maxValue).dateDebut.get(weekFields.weekOfWeekBasedYear());
                 double yesterday = days.datas.get(maxValue);
-                double lastWeek = 0.0;
-                double thisWeek = 0.0;
+                double thisWeek = 0.00;
 
                 for (int i = maxValue; i >= 0; i--) {
                     int weekNumber = days.periodes.get(i).dateDebut.get(weekFields.weekOfWeekBasedYear());
                     if (weekNumber == thisWeekNumber) {
-                        thisWeek += days.datas.get(i);
-                    } else if (weekNumber == thisWeekNumber - 1) {
-                        lastWeek += days.datas.get(i);
+                        Double value = days.datas.get(i);
+                        thisWeek += !value.isNaN() ? value : 0;
                     } else {
                         break;
                     }
@@ -209,8 +210,21 @@ public class LinkyHandler extends BaseThingHandler {
 
                 updateKwhChannel(YESTERDAY, yesterday);
                 updateKwhChannel(THIS_WEEK, thisWeek);
-                updateKwhChannel(LAST_WEEK, lastWeek);
-            }
+            });
+        }
+    }
+
+    /**
+     * Request new weekly data and updates channels
+     */
+    private synchronized void updateWeeklyData() {
+        if (isLinked(LAST_WEEK)) {
+            cachedDailyData.getValue().ifPresent(values -> {
+                Aggregate weeks = values.aggregats.weeks;
+                if (weeks.datas.size() > 1) {
+                    updateKwhChannel(LAST_WEEK, weeks.datas.get(1));
+                }
+            });
         }
     }
 
@@ -219,16 +233,15 @@ public class LinkyHandler extends BaseThingHandler {
      */
     private synchronized void updateMonthlyData() {
         if (isLinked(LAST_MONTH) || isLinked(THIS_MONTH)) {
-            Consumption result = cachedMonthlyData.getValue();
-            if (result != null) {
-                Aggregate months = result.aggregats.months;
-                if (months.datas.size() < 2) {
-                    logger.debug("Received data array too small (required size is 2): {}", months);
-                    return;
-                }
+            cachedMonthlyData.getValue().ifPresent(values -> {
+                Aggregate months = values.aggregats.months;
                 updateKwhChannel(LAST_MONTH, months.datas.get(0));
-                updateKwhChannel(THIS_MONTH, months.datas.get(1));
-            }
+                if (months.datas.size() > 1) {
+                    updateKwhChannel(THIS_MONTH, months.datas.get(1));
+                } else {
+                    updateKwhChannel(THIS_MONTH, Double.NaN);
+                }
+            });
         }
     }
 
@@ -237,26 +250,28 @@ public class LinkyHandler extends BaseThingHandler {
      */
     private synchronized void updateYearlyData() {
         if (isLinked(LAST_YEAR) || isLinked(THIS_YEAR)) {
-            Consumption result = cachedYearlyData.getValue();
-            if (result != null) {
-                Aggregate years = result.aggregats.years;
+            cachedYearlyData.getValue().ifPresent(values -> {
+                Aggregate years = values.aggregats.years;
                 updateKwhChannel(LAST_YEAR, years.datas.get(0));
-                updateKwhChannel(THIS_YEAR, years.datas.get(1));
-            }
+                if (years.datas.size() > 1) {
+                    updateKwhChannel(THIS_YEAR, years.datas.get(1));
+                } else {
+                    updateKwhChannel(THIS_YEAR, Double.NaN);
+                }
+            });
         }
     }
 
     private void updateKwhChannel(String channelId, double consumption) {
         logger.debug("Update channel {} with {}", channelId, consumption);
-        updateState(channelId,
-                !Double.isNaN(consumption) ? new QuantityType<>(consumption, SmartHomeUnits.KILOWATT_HOUR)
-                        : UnDefType.UNDEF);
+        updateState(channelId, Double.isNaN(consumption) ? UnDefType.UNDEF
+                : new QuantityType<>(consumption, SmartHomeUnits.KILOWATT_HOUR));
     }
 
     private void updateVAChannel(String channelId, double power) {
         logger.debug("Update channel {} with {}", channelId, power);
         updateState(channelId,
-                !Double.isNaN(power) ? new QuantityType<>(power, SmartHomeUnits.VOLT_AMPERE) : UnDefType.UNDEF);
+                Double.isNaN(power) ? UnDefType.UNDEF : new QuantityType<>(power, SmartHomeUnits.VOLT_AMPERE));
     }
 
     /**
index 30fde724fba96972900288575d94d81ae1552c34..5aa9e7fcde39d2b4aaf47941313f1016b54bd7ec 100644 (file)
                        <channel id="yesterday" typeId="consumption">
                                <label>Yesterday Consumption</label>
                        </channel>
-                       <channel id="power" typeId="power"/>
-                       <channel id="timestamp" typeId="timestamp"/>
+                       <channel id="power" typeId="power">
+                               <label>Maximum power usage yesterday</label>
+                       </channel>
+                       <channel id="timestamp" typeId="timestamp">
+                               <label>Peak Timestamp</label>
+                               <description>Maximum power usage timestamp</description>
+                       </channel>
                </channels>
        </channel-group-type>
 
@@ -55,7 +60,7 @@
                                <label>This Week Consumption</label>
                        </channel>
                        <channel id="lastWeek" typeId="consumption">
-                               <label>Maximum power usage yesterday</label>
+                               <label>Last Week Consumption</label>
                        </channel>
                </channels>
        </channel-group-type>
                <item-type>Number:Energy</item-type>
                <label>Total Consumption</label>
                <description>Consumption at given time interval</description>
-               <state readOnly="true" pattern="%.3f %unit%"></state>
+               <category>energy</category>
+               <state readOnly="true" pattern="%.3f %unit%"/>
        </channel-type>
 
        <channel-type id="power">
                <item-type>Number:Power</item-type>
                <label>Peak Power</label>
                <description>Maximum power usage yesterday</description>
-               <state readOnly="true" pattern="%.3f %unit%"></state>
+               <state readOnly="true" pattern="%.3f %unit%"/>
        </channel-type>
 
        <channel-type id="timestamp">
                <item-type>DateTime</item-type>
-               <label>Peak Timestamp</label>
-               <description>Maximum power usage timestamp</description>
-               <state readOnly="true">
-               </state>
+               <label>Timestamp</label>
+               <category>time</category>
+               <state readOnly="true"/>
        </channel-type>
 </thing:thing-descriptions>