]> git.basschouten.com Git - openhab-addons.git/commitdiff
[ecowatt] Initial contribution (#13404)
authorlolodomo <lg.hc@free.fr>
Sun, 18 Sep 2022 15:52:18 +0000 (17:52 +0200)
committerGitHub <noreply@github.com>
Sun, 18 Sep 2022 15:52:18 +0000 (17:52 +0200)
* [ecowatt] Initial contribution

This binding uses the Ecowatt API to expose clear signals to adopt the right gestures and to ensure a good supply of electricity for all in France.

Close #13351

Signed-off-by: Laurent Garnier <lg.hc@free.fr>
* Update bundles/org.openhab.binding.ecowatt/src/main/resources/OH-INF/thing/thing-types.xml

Signed-off-by: Fabian Wolter <github@fabian-wolter.de>
Signed-off-by: Laurent Garnier <lg.hc@free.fr>
Signed-off-by: Fabian Wolter <github@fabian-wolter.de>
Co-authored-by: Fabian Wolter <github@fabian-wolter.de>
19 files changed:
CODEOWNERS
bom/openhab-addons/pom.xml
bundles/org.openhab.binding.ecowatt/NOTICE [new file with mode: 0644]
bundles/org.openhab.binding.ecowatt/README.md [new file with mode: 0644]
bundles/org.openhab.binding.ecowatt/pom.xml [new file with mode: 0644]
bundles/org.openhab.binding.ecowatt/src/main/feature/feature.xml [new file with mode: 0644]
bundles/org.openhab.binding.ecowatt/src/main/java/org/openhab/binding/ecowatt/internal/EcowattBindingConstants.java [new file with mode: 0644]
bundles/org.openhab.binding.ecowatt/src/main/java/org/openhab/binding/ecowatt/internal/EcowattHandlerFactory.java [new file with mode: 0644]
bundles/org.openhab.binding.ecowatt/src/main/java/org/openhab/binding/ecowatt/internal/configuration/EcowattConfiguration.java [new file with mode: 0644]
bundles/org.openhab.binding.ecowatt/src/main/java/org/openhab/binding/ecowatt/internal/exception/EcowattApiLimitException.java [new file with mode: 0644]
bundles/org.openhab.binding.ecowatt/src/main/java/org/openhab/binding/ecowatt/internal/handler/EcowattHandler.java [new file with mode: 0644]
bundles/org.openhab.binding.ecowatt/src/main/java/org/openhab/binding/ecowatt/internal/restapi/EcowattApiResponse.java [new file with mode: 0644]
bundles/org.openhab.binding.ecowatt/src/main/java/org/openhab/binding/ecowatt/internal/restapi/EcowattDaySignals.java [new file with mode: 0644]
bundles/org.openhab.binding.ecowatt/src/main/java/org/openhab/binding/ecowatt/internal/restapi/EcowattHourSignal.java [new file with mode: 0644]
bundles/org.openhab.binding.ecowatt/src/main/java/org/openhab/binding/ecowatt/internal/restapi/EcowattRestApi.java [new file with mode: 0644]
bundles/org.openhab.binding.ecowatt/src/main/resources/OH-INF/binding/binding.xml [new file with mode: 0644]
bundles/org.openhab.binding.ecowatt/src/main/resources/OH-INF/i18n/ecowatt.properties [new file with mode: 0644]
bundles/org.openhab.binding.ecowatt/src/main/resources/OH-INF/thing/thing-types.xml [new file with mode: 0644]
bundles/pom.xml

index 316b63416709654af1dcee3d2ab97d6d1ab8b068..86feb8041de8c69371607d04702a82b874e83892 100644 (file)
@@ -79,6 +79,7 @@
 /bundles/org.openhab.binding.dwdunwetter/ @limdul79
 /bundles/org.openhab.binding.ecobee/ @mhilbush
 /bundles/org.openhab.binding.ecotouch/ @sibbi77
+/bundles/org.openhab.binding.ecowatt/ @lolodomo
 /bundles/org.openhab.binding.ekey/ @hmerk
 /bundles/org.openhab.binding.electroluxair/ @jannegpriv
 /bundles/org.openhab.binding.elerotransmitterstick/ @vbier
index fbadb8d51429a78bb597a95d1a8e66e7571f027d..1b76eaccb384af8ff454f2dbfdfcd3102558bb10 100644 (file)
       <artifactId>org.openhab.binding.ecotouch</artifactId>
       <version>${project.version}</version>
     </dependency>
+    <dependency>
+      <groupId>org.openhab.addons.bundles</groupId>
+      <artifactId>org.openhab.binding.ecowatt</artifactId>
+      <version>${project.version}</version>
+    </dependency>
     <dependency>
       <groupId>org.openhab.addons.bundles</groupId>
       <artifactId>org.openhab.binding.ekey</artifactId>
diff --git a/bundles/org.openhab.binding.ecowatt/NOTICE b/bundles/org.openhab.binding.ecowatt/NOTICE
new file mode 100644 (file)
index 0000000..38d625e
--- /dev/null
@@ -0,0 +1,13 @@
+This content is produced and maintained by the openHAB project.
+
+* Project home: https://www.openhab.org
+
+== Declared Project Licenses
+
+This program and the accompanying materials are made available under the terms
+of the Eclipse Public License 2.0 which is available at
+https://www.eclipse.org/legal/epl-2.0/.
+
+== Source Code
+
+https://github.com/openhab/openhab-addons
diff --git a/bundles/org.openhab.binding.ecowatt/README.md b/bundles/org.openhab.binding.ecowatt/README.md
new file mode 100644 (file)
index 0000000..6567b7c
--- /dev/null
@@ -0,0 +1,71 @@
+# Ecowatt Binding
+
+This binding uses the Ecowatt API to expose clear signals to adopt the right gestures and to ensure a good supply of electricity for all in France.
+
+You can find more information about Ecowatt on this [site](https://www.monecowatt.fr).
+
+## Supported Things
+
+This binding supports only one thing type: `signals`.
+
+## Discovery
+
+Discovery is not supported.
+You have to add the thing manually.
+
+## Prerequisites before configuration
+
+You must create an account and an application on the RTE portal to obtain the OAuth2 credentials required to access the API.
+
+1. Open this [page](https://data.rte-france.com/catalog/-/api/consumption/Ecowatt/v4.0), find the "Ecowatt" tile and click on the "Abonnez-vous à l'API" button.
+2. Create an account by following the instructions (you will receive an email to validate your new account).
+3. Once logged in, create an application by entering a name (for example "openHAB Integration"), choosing "Web Server" as type, entering any description of your choice and finally clicking on the "Valider" button.
+4. You will then see your application details, in particular the "ID client" and "ID Secret" information which you will need later to set up your binding thing.
+
+## Binding Configuration
+
+There are no overall binding configuration settings that need to be set.
+All settings are through thing configuration parameters.
+
+## Thing Configuration
+
+| Name      | Type    | Description                                                           | Required |
+|-----------|---------|-----------------------------------------------------------------------|----------|
+| idClient  | text    | ID client provided with the application you created in the RTE portal | yes      |
+| idSecret  | text    | ID secret provided with the application you created in the RTE portal | yes      |
+
+## Channels
+
+All channels are read-only.
+
+| Channel           | Type   | Description                                                      |
+|-------------------|--------|------------------------------------------------------------------|
+| todaySignal       | Number | The signal relating to the forecast consumption level for today. Values are 1 for normal consumption, 2 for strained electrical system and 3 for very strained electrical system. |
+| tomorrowSignal    | Number | The signal relating to the forecast consumption level for tomorrow. Values are 1 for normal consumption, 2 for strained electrical system and 3 for very strained electrical system. |
+| currentHourSignal | Number | The signal relating to the forecast consumption level for the current hour. Values are 1 for normal consumption, 2 for strained electrical system and 3 for very strained electrical system. |
+
+## Full Example
+
+example.things:
+
+```
+Thing ecowatt:signals:signals "Ecowatt Signals" [ idClient="xxxxx", idSecret="yyyyy"]
+```
+
+example.items:
+
+```
+Number TodaySignal "Today [%s]" { channel="ecowatt:signals:signals:todaySignal" }
+Number TomorrowSignal "Tomorrow [%s]" { channel="ecowatt:signals:signals:tomorrowSignal" }
+Number CurrentHourSignal "Current hour [%s]" { channel="ecowatt:signals:signals:currentHourSignal" }
+```
+
+example.sitemap:
+
+```
+    Frame label="Ecowatt" {
+        Default item=TodaySignal
+        Default item=TomorrowSignal
+        Default item=CurrentHourSignal
+    }
+```
diff --git a/bundles/org.openhab.binding.ecowatt/pom.xml b/bundles/org.openhab.binding.ecowatt/pom.xml
new file mode 100644 (file)
index 0000000..6b0fff0
--- /dev/null
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.openhab.addons.bundles</groupId>
+    <artifactId>org.openhab.addons.reactor.bundles</artifactId>
+    <version>3.4.0-SNAPSHOT</version>
+  </parent>
+
+  <artifactId>org.openhab.binding.ecowatt</artifactId>
+
+  <name>openHAB Add-ons :: Bundles :: Ecowatt Binding</name>
+
+</project>
diff --git a/bundles/org.openhab.binding.ecowatt/src/main/feature/feature.xml b/bundles/org.openhab.binding.ecowatt/src/main/feature/feature.xml
new file mode 100644 (file)
index 0000000..7f18af5
--- /dev/null
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<features name="org.openhab.binding.ecowatt-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
+       <repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
+
+       <feature name="openhab-binding-ecowatt" description="Ecowatt Binding" version="${project.version}">
+               <feature>openhab-runtime-base</feature>
+               <bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.ecowatt/${project.version}</bundle>
+       </feature>
+</features>
diff --git a/bundles/org.openhab.binding.ecowatt/src/main/java/org/openhab/binding/ecowatt/internal/EcowattBindingConstants.java b/bundles/org.openhab.binding.ecowatt/src/main/java/org/openhab/binding/ecowatt/internal/EcowattBindingConstants.java
new file mode 100644 (file)
index 0000000..91f35da
--- /dev/null
@@ -0,0 +1,36 @@
+/**
+ * Copyright (c) 2010-2022 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.ecowatt.internal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.core.thing.ThingTypeUID;
+
+/**
+ * The {@link EcowattBindingConstants} class defines common constants, which are
+ * used across the whole binding.
+ *
+ * @author Laurent Garnier - Initial contribution
+ */
+@NonNullByDefault
+public class EcowattBindingConstants {
+
+    private static final String BINDING_ID = "ecowatt";
+
+    // List of all Thing Type UIDs
+    public static final ThingTypeUID THING_TYPE_SIGNALS = new ThingTypeUID(BINDING_ID, "signals");
+
+    // List of all Channel ids
+    public static final String CHANNEL_TODAY_SIGNAL = "todaySignal";
+    public static final String CHANNEL_TOMORROW_SIGNAL = "tomorrowSignal";
+    public static final String CHANNEL_CURRENT_HOUR_SIGNAL = "currentHourSignal";
+}
diff --git a/bundles/org.openhab.binding.ecowatt/src/main/java/org/openhab/binding/ecowatt/internal/EcowattHandlerFactory.java b/bundles/org.openhab.binding.ecowatt/src/main/java/org/openhab/binding/ecowatt/internal/EcowattHandlerFactory.java
new file mode 100644 (file)
index 0000000..b290e59
--- /dev/null
@@ -0,0 +1,77 @@
+/**
+ * Copyright (c) 2010-2022 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.ecowatt.internal;
+
+import static org.openhab.binding.ecowatt.internal.EcowattBindingConstants.THING_TYPE_SIGNALS;
+
+import java.util.Set;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.jetty.client.HttpClient;
+import org.openhab.binding.ecowatt.internal.handler.EcowattHandler;
+import org.openhab.core.auth.client.oauth2.OAuthFactory;
+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.Thing;
+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.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+
+/**
+ * The {@link EcowattHandlerFactory} is responsible for creating things and thing
+ * handlers.
+ *
+ * @author Laurent Garnier - Initial contribution
+ */
+@NonNullByDefault
+@Component(configurationPid = "binding.ecowatt", service = ThingHandlerFactory.class)
+public class EcowattHandlerFactory extends BaseThingHandlerFactory {
+
+    private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_SIGNALS);
+
+    private final OAuthFactory oAuthFactory;
+    private final HttpClient httpClient;
+    private final TranslationProvider i18nProvider;
+    private final TimeZoneProvider timeZoneProvider;
+
+    @Activate
+    public EcowattHandlerFactory(@Reference OAuthFactory oAuthFactory, @Reference HttpClientFactory httpClientFactory,
+            final @Reference TranslationProvider i18nProvider, final @Reference TimeZoneProvider timeZoneProvider) {
+        this.oAuthFactory = oAuthFactory;
+        this.httpClient = httpClientFactory.getCommonHttpClient();
+        this.i18nProvider = i18nProvider;
+        this.timeZoneProvider = timeZoneProvider;
+    }
+
+    @Override
+    public boolean supportsThingType(ThingTypeUID thingTypeUID) {
+        return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
+    }
+
+    @Override
+    protected @Nullable ThingHandler createHandler(Thing thing) {
+        ThingTypeUID thingTypeUID = thing.getThingTypeUID();
+
+        if (THING_TYPE_SIGNALS.equals(thingTypeUID)) {
+            return new EcowattHandler(thing, oAuthFactory, httpClient, i18nProvider, timeZoneProvider);
+        }
+
+        return null;
+    }
+}
diff --git a/bundles/org.openhab.binding.ecowatt/src/main/java/org/openhab/binding/ecowatt/internal/configuration/EcowattConfiguration.java b/bundles/org.openhab.binding.ecowatt/src/main/java/org/openhab/binding/ecowatt/internal/configuration/EcowattConfiguration.java
new file mode 100644 (file)
index 0000000..903160a
--- /dev/null
@@ -0,0 +1,27 @@
+/**
+ * Copyright (c) 2010-2022 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.ecowatt.internal.configuration;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * The {@link EcowattConfiguration} class contains fields mapping thing configuration parameters.
+ *
+ * @author Laurent Garnier - Initial contribution
+ */
+@NonNullByDefault
+public class EcowattConfiguration {
+
+    public String idClient = "";
+    public String idSecret = "";
+}
diff --git a/bundles/org.openhab.binding.ecowatt/src/main/java/org/openhab/binding/ecowatt/internal/exception/EcowattApiLimitException.java b/bundles/org.openhab.binding.ecowatt/src/main/java/org/openhab/binding/ecowatt/internal/exception/EcowattApiLimitException.java
new file mode 100644 (file)
index 0000000..171e513
--- /dev/null
@@ -0,0 +1,37 @@
+/**
+ * Copyright (c) 2010-2022 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.ecowatt.internal.exception;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.core.i18n.CommunicationException;
+
+/**
+ * An exception used when the API limit is reached
+ *
+ * @author Laurent Garnier - Initial contribution
+ */
+@NonNullByDefault
+public class EcowattApiLimitException extends CommunicationException {
+    private static final long serialVersionUID = 1L;
+    private int retryAfter;
+
+    public EcowattApiLimitException(int retryAfter, String message, @Nullable Object @Nullable... msgParams) {
+        super(message, msgParams);
+        this.retryAfter = retryAfter;
+    }
+
+    public int getRetryAfter() {
+        return retryAfter;
+    }
+}
diff --git a/bundles/org.openhab.binding.ecowatt/src/main/java/org/openhab/binding/ecowatt/internal/handler/EcowattHandler.java b/bundles/org.openhab.binding.ecowatt/src/main/java/org/openhab/binding/ecowatt/internal/handler/EcowattHandler.java
new file mode 100644 (file)
index 0000000..d6530b9
--- /dev/null
@@ -0,0 +1,245 @@
+/**
+ * Copyright (c) 2010-2022 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.ecowatt.internal.handler;
+
+import static org.openhab.binding.ecowatt.internal.EcowattBindingConstants.*;
+
+import java.time.Duration;
+import java.time.LocalDateTime;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.temporal.ChronoUnit;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.jetty.client.HttpClient;
+import org.openhab.binding.ecowatt.internal.configuration.EcowattConfiguration;
+import org.openhab.binding.ecowatt.internal.exception.EcowattApiLimitException;
+import org.openhab.binding.ecowatt.internal.restapi.EcowattApiResponse;
+import org.openhab.binding.ecowatt.internal.restapi.EcowattDaySignals;
+import org.openhab.binding.ecowatt.internal.restapi.EcowattRestApi;
+import org.openhab.core.auth.client.oauth2.OAuthFactory;
+import org.openhab.core.cache.ExpiringCache;
+import org.openhab.core.i18n.CommunicationException;
+import org.openhab.core.i18n.TimeZoneProvider;
+import org.openhab.core.i18n.TranslationProvider;
+import org.openhab.core.library.types.DecimalType;
+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.BaseThingHandler;
+import org.openhab.core.types.Command;
+import org.openhab.core.types.RefreshType;
+import org.openhab.core.types.State;
+import org.openhab.core.types.UnDefType;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.FrameworkUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link EcowattHandler} is responsible for updating the state of the channels
+ *
+ * @author Laurent Garnier - Initial contribution
+ */
+@NonNullByDefault
+public class EcowattHandler extends BaseThingHandler {
+
+    private final Logger logger = LoggerFactory.getLogger(EcowattHandler.class);
+
+    private final OAuthFactory oAuthFactory;
+    private final HttpClient httpClient;
+    private final TranslationProvider i18nProvider;
+    private final TimeZoneProvider timeZoneProvider;
+    private final Bundle bundle;
+
+    private @Nullable EcowattRestApi api;
+    private ExpiringCache<EcowattApiResponse> cachedApiResponse = new ExpiringCache<>(Duration.ofHours(4),
+            this::getApiResponse); // cache the API response during 4 hours
+
+    private @Nullable ScheduledFuture<?> updateJob;
+
+    public EcowattHandler(Thing thing, OAuthFactory oAuthFactory, HttpClient httpClient,
+            TranslationProvider i18nProvider, TimeZoneProvider timeZoneProvider) {
+        super(thing);
+        this.oAuthFactory = oAuthFactory;
+        this.httpClient = httpClient;
+        this.i18nProvider = i18nProvider;
+        this.timeZoneProvider = timeZoneProvider;
+        this.bundle = FrameworkUtil.getBundle(this.getClass());
+    }
+
+    @Override
+    public void handleCommand(ChannelUID channelUID, Command command) {
+        if (command == RefreshType.REFRESH) {
+            updateChannel(channelUID.getId());
+        }
+    }
+
+    @Override
+    public void initialize() {
+        EcowattConfiguration config = getConfigAs(EcowattConfiguration.class);
+
+        final String idClient = config.idClient;
+        final String idSecret = config.idSecret;
+
+        if (idClient.isBlank() || idSecret.isBlank()) {
+            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
+                    "@text/offline.config-error-unset-parameters");
+        } else {
+            api = new EcowattRestApi(oAuthFactory, httpClient, thing.getUID().getAsString(), idClient, idSecret);
+            updateStatus(ThingStatus.UNKNOWN);
+            scheduleNextUpdate(0, true);
+        }
+    }
+
+    @Override
+    public void dispose() {
+        stopScheduledJob();
+        EcowattRestApi localApi = api;
+        if (localApi != null) {
+            localApi.dispose();
+            api = null;
+        }
+    }
+
+    /**
+     * Schedule the next update of channels.
+     *
+     * After this update is run, a new update will be rescheduled, either just after the API is reachable again or at
+     * the beginning of the following hour.
+     *
+     * @param delayInSeconds the delay in seconds before running the next update
+     * @param retryIfApiLimitReached true if a retry is expected when the update fails due to reached API limit
+     */
+    private void scheduleNextUpdate(long delayInSeconds, boolean retryIfApiLimitReached) {
+        logger.debug("scheduleNextUpdate delay={}s retryIfLimitReached={}", delayInSeconds, retryIfApiLimitReached);
+        updateJob = scheduler.schedule(() -> {
+            int retryDelay = updateChannels(retryIfApiLimitReached);
+            long delayNextUpdate;
+            if (retryDelay > 0) {
+                // Schedule a new update just after the API is reachable again
+                logger.debug("retryDelay {}", retryDelay);
+                delayNextUpdate = retryDelay;
+            } else {
+                // Schedule a new update at the beginning of the following hour
+                final LocalDateTime now = LocalDateTime.now();
+                final LocalDateTime beginningNextHour = now.plusHours(1).truncatedTo(ChronoUnit.HOURS);
+                delayNextUpdate = ChronoUnit.SECONDS.between(now, beginningNextHour);
+            }
+            // Add 3s of additional delay for security...
+            delayNextUpdate += 3;
+            scheduleNextUpdate(delayNextUpdate, retryDelay == 0);
+        }, delayInSeconds, TimeUnit.SECONDS);
+    }
+
+    private void stopScheduledJob() {
+        ScheduledFuture<?> job = updateJob;
+        if (job != null) {
+            job.cancel(true);
+            updateJob = null;
+        }
+    }
+
+    private EcowattApiResponse getApiResponse() {
+        EcowattRestApi localApi = api;
+        if (localApi == null) {
+            return new EcowattApiResponse();
+        }
+
+        EcowattApiResponse response;
+        try {
+            response = localApi.getSignals();
+        } catch (CommunicationException e) {
+            Throwable cause = e.getCause();
+            if (cause != null) {
+                logger.warn("{}: {}", e.getMessage(bundle, i18nProvider), cause.getMessage());
+            } else {
+                logger.warn("{}", e.getMessage(bundle, i18nProvider));
+            }
+            response = new EcowattApiResponse(e);
+        }
+        return response;
+    }
+
+    private int updateChannels(boolean retryIfApiLimitReached) {
+        return updateChannel(null, retryIfApiLimitReached);
+    }
+
+    private void updateChannel(String channelId) {
+        updateChannel(channelId, false);
+    }
+
+    private synchronized int updateChannel(@Nullable String channelId, boolean retryIfApiLimitReached) {
+        logger.debug("updateChannel channelId={}, retryIfApiLimitReached={}", channelId, retryIfApiLimitReached);
+        int retryDelay = 0;
+        EcowattApiResponse response = cachedApiResponse.getValue();
+        if (response == null || !response.succeeded()) {
+            CommunicationException exception = response == null ? null : response.getException();
+            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
+                    exception == null ? null : exception.getRawMessage());
+
+            // Invalidate the cache to be sure the next request will trigger the API
+            cachedApiResponse.invalidateValue();
+
+            if (retryIfApiLimitReached && exception instanceof EcowattApiLimitException
+                    && ((EcowattApiLimitException) exception).getRetryAfter() > 0) {
+                // Will retry when the API is available again (just after the limit expired)
+                retryDelay = ((EcowattApiLimitException) exception).getRetryAfter();
+            }
+        } else {
+            updateStatus(ThingStatus.ONLINE);
+        }
+
+        ZonedDateTime now = ZonedDateTime.now(timeZoneProvider.getTimeZone());
+        logger.debug("now {}", now.format(DateTimeFormatter.ISO_ZONED_DATE_TIME));
+        if ((channelId == null || CHANNEL_TODAY_SIGNAL.equals(channelId)) && isLinked(CHANNEL_TODAY_SIGNAL)) {
+            updateState(CHANNEL_TODAY_SIGNAL, getDaySignalState(response, now));
+        }
+        if ((channelId == null || CHANNEL_TOMORROW_SIGNAL.equals(channelId)) && isLinked(CHANNEL_TOMORROW_SIGNAL)) {
+            updateState(CHANNEL_TOMORROW_SIGNAL, getDaySignalState(response, now.plusDays(1)));
+        }
+        if ((channelId == null || CHANNEL_CURRENT_HOUR_SIGNAL.equals(channelId))
+                && isLinked(CHANNEL_CURRENT_HOUR_SIGNAL)) {
+            updateState(CHANNEL_CURRENT_HOUR_SIGNAL, getHourSignalState(response, now));
+        }
+
+        return retryDelay;
+    }
+
+    private State getDaySignalState(@Nullable EcowattApiResponse response, ZonedDateTime dateTime) {
+        EcowattDaySignals signals = response == null ? null : response.getDaySignals(dateTime);
+        return signals != null && signals.getDaySignal() >= 1 && signals.getDaySignal() <= 3
+                ? new DecimalType(signals.getDaySignal())
+                : UnDefType.UNDEF;
+    }
+
+    private State getHourSignalState(@Nullable EcowattApiResponse response, ZonedDateTime dateTime) {
+        EcowattDaySignals signals = response == null ? null : response.getDaySignals(dateTime);
+        ZonedDateTime day = signals == null ? null : signals.getDay();
+        if (signals != null && day != null) {
+            // Move the current time to the same offset as the data returned by the API to get and use the right current
+            // hour index in these data
+            int hour = dateTime.withZoneSameInstant(day.getZone()).getHour();
+            int value = signals.getHourSignal(hour);
+            logger.debug("hour {} value {}", hour, value);
+            if (value >= 1 && value <= 3) {
+                return new DecimalType(value);
+            }
+        }
+        return UnDefType.UNDEF;
+    }
+}
diff --git a/bundles/org.openhab.binding.ecowatt/src/main/java/org/openhab/binding/ecowatt/internal/restapi/EcowattApiResponse.java b/bundles/org.openhab.binding.ecowatt/src/main/java/org/openhab/binding/ecowatt/internal/restapi/EcowattApiResponse.java
new file mode 100644 (file)
index 0000000..b146e9a
--- /dev/null
@@ -0,0 +1,93 @@
+/**
+ * Copyright (c) 2010-2022 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.ecowatt.internal.restapi;
+
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.temporal.ChronoUnit;
+import java.util.List;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.core.i18n.CommunicationException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link EcowattApiResponse} class contains fields mapping the response to the Ecowatt API request /signals.
+ *
+ * It also includes an exception field to be set in case the API request fails.
+ *
+ * @author Laurent Garnier - Initial contribution
+ */
+@NonNullByDefault
+public class EcowattApiResponse {
+    private final Logger logger = LoggerFactory.getLogger(EcowattApiResponse.class);
+
+    public @Nullable List<EcowattDaySignals> signals;
+    private @Nullable CommunicationException exception;
+
+    public EcowattApiResponse() {
+        this.signals = null;
+        this.exception = null;
+    }
+
+    public EcowattApiResponse(@Nullable List<EcowattDaySignals> signals) {
+        this.signals = signals;
+        this.exception = null;
+    }
+
+    public EcowattApiResponse(CommunicationException exception) {
+        this.signals = null;
+        this.exception = exception;
+    }
+
+    /**
+     * Search the data for the day of the given date and time
+     *
+     * @param dateTime a date and time
+     * @return the data for the searched day or null if no data is found for this day
+     */
+    public @Nullable EcowattDaySignals getDaySignals(ZonedDateTime dateTime) {
+        List<EcowattDaySignals> localSignals = signals;
+        if (localSignals != null) {
+            for (EcowattDaySignals daySignals : localSignals) {
+                ZonedDateTime zdt = daySignals.getDay();
+                if (zdt != null) {
+                    // Adjust date/times to the same offset/zone
+                    ZonedDateTime dateTime2 = dateTime.withZoneSameInstant(zdt.getZone());
+                    logger.trace("zdt {} offset {} - dateTime2 {} offset {}",
+                            zdt.format(DateTimeFormatter.ISO_ZONED_DATE_TIME), zdt.getOffset(),
+                            dateTime2.format(DateTimeFormatter.ISO_ZONED_DATE_TIME), dateTime2.getOffset());
+                    // Check if the two date/times are in the same day
+                    if (zdt.truncatedTo(ChronoUnit.DAYS).toInstant()
+                            .equals(dateTime2.truncatedTo(ChronoUnit.DAYS).toInstant())) {
+                        logger.debug("getDaySignals for {} returns signal {} : {} ( {} )",
+                                dateTime.format(DateTimeFormatter.ISO_ZONED_DATE_TIME), daySignals.getDaySignal(),
+                                daySignals.getDayMessage(), zdt.format(DateTimeFormatter.ISO_ZONED_DATE_TIME));
+                        return daySignals;
+                    }
+                }
+            }
+        }
+        return null;
+    }
+
+    public boolean succeeded() {
+        return signals != null;
+    }
+
+    public @Nullable CommunicationException getException() {
+        return exception;
+    }
+}
diff --git a/bundles/org.openhab.binding.ecowatt/src/main/java/org/openhab/binding/ecowatt/internal/restapi/EcowattDaySignals.java b/bundles/org.openhab.binding.ecowatt/src/main/java/org/openhab/binding/ecowatt/internal/restapi/EcowattDaySignals.java
new file mode 100644 (file)
index 0000000..009a383
--- /dev/null
@@ -0,0 +1,63 @@
+/**
+ * Copyright (c) 2010-2022 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.ecowatt.internal.restapi;
+
+import java.time.ZonedDateTime;
+import java.util.List;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * The {@link EcowattDaySignals} class contains fields mapping the content of each value of JSON table "signals" inside
+ * the API response
+ *
+ * @author Laurent Garnier - Initial contribution
+ */
+@NonNullByDefault
+public class EcowattDaySignals {
+    @SerializedName("GenerationFichier")
+    public @Nullable ZonedDateTime fileTimestamp;
+    @SerializedName("jour")
+    public @Nullable ZonedDateTime day;
+    @SerializedName("dvalue")
+    public int value;
+    public @Nullable String message;
+    public @Nullable List<EcowattHourSignal> values;
+
+    public @Nullable ZonedDateTime getDay() {
+        return day;
+    }
+
+    public int getDaySignal() {
+        return value;
+    }
+
+    public @Nullable String getDayMessage() {
+        return message;
+    }
+
+    public int getHourSignal(int hour) {
+        List<EcowattHourSignal> localValues = values;
+        if (localValues != null) {
+            for (EcowattHourSignal hourSignal : localValues) {
+                if (hourSignal.hour == hour) {
+                    return hourSignal.value;
+                }
+            }
+        }
+        return 0;
+    }
+}
diff --git a/bundles/org.openhab.binding.ecowatt/src/main/java/org/openhab/binding/ecowatt/internal/restapi/EcowattHourSignal.java b/bundles/org.openhab.binding.ecowatt/src/main/java/org/openhab/binding/ecowatt/internal/restapi/EcowattHourSignal.java
new file mode 100644 (file)
index 0000000..bc23c3c
--- /dev/null
@@ -0,0 +1,31 @@
+/**
+ * Copyright (c) 2010-2022 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.ecowatt.internal.restapi;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * The {@link EcowattHourSignal} class contains fields mapping the content of each value of JSON table "values" inside
+ * the API response
+ *
+ * @author Laurent Garnier - Initial contribution
+ */
+@NonNullByDefault
+public class EcowattHourSignal {
+    @SerializedName("pas")
+    public int hour = -1;
+    @SerializedName("hvalue")
+    public int value;
+}
diff --git a/bundles/org.openhab.binding.ecowatt/src/main/java/org/openhab/binding/ecowatt/internal/restapi/EcowattRestApi.java b/bundles/org.openhab.binding.ecowatt/src/main/java/org/openhab/binding/ecowatt/internal/restapi/EcowattRestApi.java
new file mode 100644 (file)
index 0000000..084274a
--- /dev/null
@@ -0,0 +1,141 @@
+/**
+ * Copyright (c) 2010-2022 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.ecowatt.internal.restapi;
+
+import java.io.IOException;
+import java.time.Instant;
+import java.time.OffsetDateTime;
+import java.time.ZonedDateTime;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+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.http.HttpHeader;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpStatus;
+import org.openhab.binding.ecowatt.internal.exception.EcowattApiLimitException;
+import org.openhab.core.auth.client.oauth2.AccessTokenResponse;
+import org.openhab.core.auth.client.oauth2.OAuthClientService;
+import org.openhab.core.auth.client.oauth2.OAuthException;
+import org.openhab.core.auth.client.oauth2.OAuthFactory;
+import org.openhab.core.auth.client.oauth2.OAuthResponseException;
+import org.openhab.core.i18n.CommunicationException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonSyntaxException;
+
+/**
+ * The {@link EcowattRestApi} is responsible for handling all communication with the Ecowatt REST API
+ *
+ * @author Laurent Garnier - Initial contribution
+ */
+@NonNullByDefault
+public class EcowattRestApi {
+
+    private static final String ECOWATT_API_TOKEN_URL = "https://digital.iservices.rte-france.com/token/oauth/";
+    private static final String ECOWATT_API_GET_SIGNALS_URL = "https://digital.iservices.rte-france.com/open_api/ecowatt/v4/signals";
+
+    private final Logger logger = LoggerFactory.getLogger(EcowattRestApi.class);
+
+    private final OAuthFactory oAuthFactory;
+    private final HttpClient httpClient;
+    private final Gson gson;
+    private OAuthClientService authService;
+    private String authServiceHandle;
+
+    public EcowattRestApi(OAuthFactory oAuthFactory, HttpClient httpClient, String authServiceHandle, String idClient,
+            String idSecret) {
+        this.oAuthFactory = oAuthFactory;
+        this.httpClient = httpClient;
+        GsonBuilder gsonBuilder = new GsonBuilder();
+        gson = gsonBuilder.registerTypeAdapter(ZonedDateTime.class,
+                (JsonDeserializer<ZonedDateTime>) (json, type, jsonDeserializationContext) -> OffsetDateTime
+                        .parse(json.getAsJsonPrimitive().getAsString()).toZonedDateTime())
+                .create();
+        this.authService = oAuthFactory.createOAuthClientService(authServiceHandle, ECOWATT_API_TOKEN_URL, null,
+                idClient, idSecret, null, true);
+        this.authServiceHandle = authServiceHandle;
+    }
+
+    public EcowattApiResponse getSignals() throws CommunicationException, EcowattApiLimitException {
+        logger.debug("API request signals");
+        String token = authenticate().getAccessToken();
+
+        final Request request = httpClient.newRequest(ECOWATT_API_GET_SIGNALS_URL).method(HttpMethod.GET)
+                .header(HttpHeader.AUTHORIZATION, "Bearer " + token).timeout(10, TimeUnit.SECONDS);
+
+        ContentResponse response;
+        try {
+            response = request.send();
+        } catch (TimeoutException | ExecutionException e) {
+            throw new CommunicationException("@text/exception.api-request-failed", e);
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            throw new CommunicationException("@text/exception.api-request-failed", e);
+        }
+
+        int statusCode = response.getStatus();
+
+        if (statusCode == HttpStatus.TOO_MANY_REQUESTS_429) {
+            int retryAfter = -1;
+            if (response.getHeaders().contains(HttpHeader.RETRY_AFTER)) {
+                try {
+                    retryAfter = Integer.parseInt(response.getHeaders().get(HttpHeader.RETRY_AFTER));
+                } catch (NumberFormatException e) {
+                }
+            }
+            throw new EcowattApiLimitException(retryAfter, "@text/exception.api-limit-reached");
+        } else if (statusCode != HttpStatus.OK_200) {
+            throw new CommunicationException("@text/exception.api-request-failed-params", statusCode,
+                    response.getContentAsString());
+        }
+
+        try {
+            EcowattApiResponse deserializedResp = gson.fromJson(response.getContentAsString(),
+                    EcowattApiResponse.class);
+            if (deserializedResp == null) {
+                throw new CommunicationException("@text/exception.empty-api-response");
+            }
+            return deserializedResp;
+        } catch (JsonSyntaxException e) {
+            throw new CommunicationException("@text/exception.parsing-api-response-failed", e);
+        }
+    }
+
+    private AccessTokenResponse authenticate() throws CommunicationException {
+        try {
+            AccessTokenResponse result = authService.getAccessTokenResponse();
+            if (result == null || result.isExpired(Instant.now(), 120)) {
+                logger.debug("Authentication required");
+                result = authService.getAccessTokenByClientCredentials(null);
+            }
+            logger.debug("Token {} of type {} created on {} expiring after {} seconds", result.getAccessToken(),
+                    result.getTokenType(), result.getCreatedOn(), result.getExpiresIn());
+            return result;
+        } catch (OAuthException | IOException | OAuthResponseException e) {
+            throw new CommunicationException("@text/exception.authentication-failed", e);
+        }
+    }
+
+    public void dispose() {
+        oAuthFactory.ungetOAuthService(authServiceHandle);
+    }
+}
diff --git a/bundles/org.openhab.binding.ecowatt/src/main/resources/OH-INF/binding/binding.xml b/bundles/org.openhab.binding.ecowatt/src/main/resources/OH-INF/binding/binding.xml
new file mode 100644 (file)
index 0000000..1b07d82
--- /dev/null
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<binding:binding id="ecowatt" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
+       xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
+
+       <name>Ecowatt Binding</name>
+       <description>This binding uses the Ecowatt API to expose clear signals to adopt the right gestures and to ensure a good
+               supply of electricity for all in Frances.</description>
+
+</binding:binding>
diff --git a/bundles/org.openhab.binding.ecowatt/src/main/resources/OH-INF/i18n/ecowatt.properties b/bundles/org.openhab.binding.ecowatt/src/main/resources/OH-INF/i18n/ecowatt.properties
new file mode 100644 (file)
index 0000000..742223a
--- /dev/null
@@ -0,0 +1,43 @@
+# binding
+
+binding.ecowatt.name = Ecowatt Binding
+binding.ecowatt.description = This binding uses the Ecowatt API to expose clear signals to adopt the right gestures and to ensure a good supply of electricity for all in Frances.
+
+# thing types
+
+thing-type.ecowatt.signals.label = Electricity Forecast
+thing-type.ecowatt.signals.description = The French electricity consumption forecasts
+thing-type.ecowatt.signals.channel.currentHourSignal.label = Current Hour Signal
+thing-type.ecowatt.signals.channel.currentHourSignal.description = The signal relating to the forecast consumption level for the current hour. Values are 1 for normal consumption, 2 for strained electrical system and 3 for very strained electrical system.
+thing-type.ecowatt.signals.channel.todaySignal.label = Today Signal
+thing-type.ecowatt.signals.channel.todaySignal.description = The signal relating to the forecast consumption level for today. Values are 1 for normal consumption, 2 for strained electrical system and 3 for very strained electrical system.
+thing-type.ecowatt.signals.channel.tomorrowSignal.label = Tomorrow Signal
+thing-type.ecowatt.signals.channel.tomorrowSignal.description = The signal relating to the forecast consumption level for tomorrow. Values are 1 for normal consumption, 2 for strained electrical system and 3 for very strained electrical system.
+
+# thing types config
+
+thing-type.config.ecowatt.signals.idClient.label = ID Client
+thing-type.config.ecowatt.signals.idClient.description = ID client provided with the application you created in the RTE portal.
+thing-type.config.ecowatt.signals.idSecret.label = ID Secret
+thing-type.config.ecowatt.signals.idSecret.description = ID secret provided with the application you created in the RTE portal.
+
+# channel types
+
+channel-type.ecowatt.signal.label = Consumption Signal
+channel-type.ecowatt.signal.description = The signal relating to the forecast consumption level. Values are 1 for normal consumption, 2 for strained electrical system and 3 for very strained electrical system.
+channel-type.ecowatt.signal.state.option.1 = Green (normal consumption)
+channel-type.ecowatt.signal.state.option.2 = Orange (strained electrical system)
+channel-type.ecowatt.signal.state.option.3 = Red (very strained electrical system)
+
+# thing status descriptions
+
+offline.config-error-unset-parameters = Id client and/or id secret configuration parameters is not set
+
+# exceptions
+
+exception.authentication-failed = Authentication to the API failed
+exception.api-request-failed = REST API request failed
+exception.api-request-failed-params = REST API request failed: statusCode={0}, message={1}
+exception.empty-api-response = API response is empty
+exception.parsing-api-response-failed = Parsing of the API response failed
+exception.api-limit-reached = API limit reached; will retry later
diff --git a/bundles/org.openhab.binding.ecowatt/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.ecowatt/src/main/resources/OH-INF/thing/thing-types.xml
new file mode 100644 (file)
index 0000000..43da259
--- /dev/null
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="ecowatt"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
+       xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
+
+       <thing-type id="signals">
+               <label>Electricity Forecast</label>
+               <description>The French electricity consumption forecasts</description>
+
+               <channels>
+                       <channel id="todaySignal" typeId="signal">
+                               <label>Today Signal</label>
+                               <description>The signal relating to the forecast consumption level for today. Values are 1 for normal consumption, 2
+                                       for strained electrical system and 3 for very strained electrical system.</description>
+                       </channel>
+                       <channel id="tomorrowSignal" typeId="signal">
+                               <label>Tomorrow Signal</label>
+                               <description>The signal relating to the forecast consumption level for tomorrow. Values are 1 for normal
+                                       consumption, 2 for strained electrical system and 3 for very strained electrical system.</description>
+                       </channel>
+                       <channel id="currentHourSignal" typeId="signal">
+                               <label>Current Hour Signal</label>
+                               <description>The signal relating to the forecast consumption level for the current hour. Values are 1 for normal
+                                       consumption, 2 for strained electrical system and 3 for very strained electrical system.</description>
+                       </channel>
+               </channels>
+
+               <config-description>
+                       <parameter name="idClient" type="text" required="true">
+                               <label>ID Client</label>
+                               <description>ID client provided with the application you created in the RTE portal.</description>
+                       </parameter>
+                       <parameter name="idSecret" type="text" required="true">
+                               <context>password</context>
+                               <label>ID Secret</label>
+                               <description>ID secret provided with the application you created in the RTE portal.</description>
+                       </parameter>
+               </config-description>
+       </thing-type>
+
+       <channel-type id="signal">
+               <item-type>Number</item-type>
+               <label>Consumption Signal</label>
+               <description>The signal relating to the forecast consumption level. Values are 1 for normal consumption, 2 for
+                       strained electrical system and 3 for very strained electrical system.</description>
+               <state readOnly="true">
+                       <options>
+                               <option value="1">Green (normal consumption)</option>
+                               <option value="2">Orange (strained electrical system)</option>
+                               <option value="3">Red (very strained electrical system)</option>
+                       </options>
+               </state>
+       </channel-type>
+</thing:thing-descriptions>
index 99ec72c728c4ddf1b64422806a11be8a81c733b6..6f121148bab7d0f91591e9e0d86e69dbe7e77a89 100644 (file)
     <module>org.openhab.binding.easee</module>
     <module>org.openhab.binding.ecobee</module>
     <module>org.openhab.binding.ecotouch</module>
+    <module>org.openhab.binding.ecowatt</module>
     <module>org.openhab.binding.ekey</module>
     <module>org.openhab.binding.electroluxair</module>
     <module>org.openhab.binding.elerotransmitterstick</module>