From 3a4eaae1e95a02a4b0d19824bcbe7e32c9909521 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20L=27hopital?= Date: Mon, 9 Nov 2020 17:41:48 +0100 Subject: [PATCH] [Vigicrues] OH3 enhancements (#8952) * Staging work Saving my work Vigicrues extensions for OH3 First code review * Code review and some corrections * Code review enhancements Changed alert to Number * Code review corrections * Correcting my error * Adressing SAT finding Signed-off-by: clinique --- .../org.openhab.binding.vigicrues/README.md | 33 ++- ...uration.java => StationConfiguration.java} | 6 +- .../internal/VigiCruesBindingConstants.java | 18 +- .../internal/VigiCruesHandlerFactory.java | 32 +-- .../vigicrues/internal/api/ApiHandler.java | 110 +++++++++ .../api/VigiCruesCertificateProvider.java | 44 ++++ .../internal/api/VigiCruesException.java | 37 +++ .../discovery/VigiCruesDiscoveryService.java | 81 +++++++ .../internal/dto/hubeau/HubEauResponse.java | 157 ++++++++++++ .../opendatasoft}/OpenDatasoftResponse.java | 10 +- .../opendatasoft}/Parameters.java | 2 +- .../{json => dto/opendatasoft}/Record.java | 2 +- .../{json => dto/opendatasoft}/Refine.java | 2 +- .../opendatasoft}/VigiCruesFields.java | 2 +- .../dto/vigicrues/CdStationHydro.java | 104 ++++++++ .../internal/dto/vigicrues/InfoVigiCru.java | 56 +++++ .../internal/dto/vigicrues/TerEntVigiCru.java | 62 +++++ .../dto/vigicrues/TronEntVigiCru.java | 80 +++++++ .../internal/dto/vigicrues/VicANMoinsUn.java | 45 ++++ .../internal/handler/VigiCruesHandler.java | 225 ++++++++++++++---- .../resources/OH-INF/thing/thing-types.xml | 52 ++++ .../src/main/resources/picto/crue-0.svg | 1 + .../src/main/resources/picto/crue-1.svg | 1 + .../src/main/resources/picto/crue-2.svg | 1 + .../src/main/resources/picto/crue-3.svg | 1 + .../src/main/resources/vigicrues.cer | 50 ++++ 26 files changed, 1129 insertions(+), 85 deletions(-) rename bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/{VigiCruesConfiguration.java => StationConfiguration.java} (82%) create mode 100644 bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/api/ApiHandler.java create mode 100644 bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/api/VigiCruesCertificateProvider.java create mode 100644 bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/api/VigiCruesException.java create mode 100644 bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/discovery/VigiCruesDiscoveryService.java create mode 100644 bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/dto/hubeau/HubEauResponse.java rename bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/{json => dto/opendatasoft}/OpenDatasoftResponse.java (79%) rename bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/{json => dto/opendatasoft}/Parameters.java (95%) rename bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/{json => dto/opendatasoft}/Record.java (95%) rename bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/{json => dto/opendatasoft}/Refine.java (92%) rename bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/{json => dto/opendatasoft}/VigiCruesFields.java (96%) create mode 100644 bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/dto/vigicrues/CdStationHydro.java create mode 100644 bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/dto/vigicrues/InfoVigiCru.java create mode 100644 bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/dto/vigicrues/TerEntVigiCru.java create mode 100644 bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/dto/vigicrues/TronEntVigiCru.java create mode 100644 bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/dto/vigicrues/VicANMoinsUn.java create mode 100644 bundles/org.openhab.binding.vigicrues/src/main/resources/picto/crue-0.svg create mode 100644 bundles/org.openhab.binding.vigicrues/src/main/resources/picto/crue-1.svg create mode 100644 bundles/org.openhab.binding.vigicrues/src/main/resources/picto/crue-2.svg create mode 100644 bundles/org.openhab.binding.vigicrues/src/main/resources/picto/crue-3.svg create mode 100644 bundles/org.openhab.binding.vigicrues/src/main/resources/vigicrues.cer diff --git a/bundles/org.openhab.binding.vigicrues/README.md b/bundles/org.openhab.binding.vigicrues/README.md index 744569a98..cadece3d3 100644 --- a/bundles/org.openhab.binding.vigicrues/README.md +++ b/bundles/org.openhab.binding.vigicrues/README.md @@ -23,7 +23,10 @@ Of course, you can add multiple Things, e.g. for getting measures for different ## Discovery -This binding does not handle auto-discovery. +You can discover stations based upon the system location. +Select Vigicrues binding and click scan in order to discover new stations. +The first scan will proceed with stations located in a radius of 10 km. +This radius will increase by 10 km at each new scan. ## Binding Configuration @@ -41,13 +44,31 @@ The thing has a few configuration parameters: ## Channels +Once created, at first initialization, the thing will discover its capabilities (available data) using the webservices apis. +Channels will be presented depending upon actual available data. + The VigiCrues information that retrieved are made available with these channels: -| Channel ID | Item Type | Description | -|------------------|---------------------------|-------------------------------| -| observation-time | DateTime | Date and time of measurement | -| flow | Number:VolumetricFlowRate | Volume of water per time unit | -| height | Number:Length | Water height of the river | +| Channel ID | Item Type | Description | +|------------------|---------------------------|------------------------------------------------------------| +| observation-time | DateTime | Date and time of measurement | +| flow | Number:VolumetricFlowRate | Volume of water per time unit | +| height | Number:Length | Water height of the river | +| relative-height | Number:Dimensionless | Current water level toward lowest historical flood | +| relative-flow | Number:Dimensionless | Current water flow tower lowest historical flood | +| alert (*) | Number | Flooding alert level of the portion related to the station | +| alert-icon | Image | Pictogram associated to the alert level | +| short-comment | String | Description of the alert level | +| comment | String | Detailed informations regarding the ongoing event | + +(*) Each alert level is described by a color : + +| Code | Color | Description | +|------|--------|-------------------------------------------| +| 0 | Green | No particular vigilance | +| 1 | Yellow | Be attentive to the flooding situation | +| 2 | Orange | Be "very vigilant" in the concerned areas | +| 3 | Red | Absolute vigilance required | ## Full Example diff --git a/bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/VigiCruesConfiguration.java b/bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/StationConfiguration.java similarity index 82% rename from bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/VigiCruesConfiguration.java rename to bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/StationConfiguration.java index 7148589fe..e83ba6a1a 100644 --- a/bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/VigiCruesConfiguration.java +++ b/bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/StationConfiguration.java @@ -15,13 +15,15 @@ package org.openhab.binding.vigicrues.internal; import org.eclipse.jdt.annotation.NonNullByDefault; /** - * The {@link VigiCruesConfiguration} is the class used to match the + * The {@link StationConfiguration} is the class used to match the * thing configuration. * * @author Gaël L'hopital - Initial contribution */ @NonNullByDefault -public class VigiCruesConfiguration { +public class StationConfiguration { + public static String ID = "id"; + public String id = ""; public int refresh = 30; } diff --git a/bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/VigiCruesBindingConstants.java b/bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/VigiCruesBindingConstants.java index 791a7c7a4..50660cbce 100644 --- a/bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/VigiCruesBindingConstants.java +++ b/bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/VigiCruesBindingConstants.java @@ -28,17 +28,27 @@ import org.openhab.core.thing.ThingTypeUID; public class VigiCruesBindingConstants { public static final String BINDING_ID = "vigicrues"; - public static final String OPENDATASOFT_URL = "https://public.opendatasoft.com/api/records/1.0/search/"; // List of all Thing Type UIDs - public static final ThingTypeUID THING_TYPE_VIGI_CRUES = new ThingTypeUID(BINDING_ID, "station"); + public static final ThingTypeUID THING_TYPE_STATION = new ThingTypeUID(BINDING_ID, "station"); // List of all Channel id's public static final String OBSERVATION_TIME = "observation-time"; public static final String HEIGHT = "height"; public static final String FLOW = "flow"; - + public static final String ALERT = "alert"; public static final String COMMENT = "comment"; + public static final String RELATIVE_PREFIX = "relative"; + public static final String RELATIVE_HEIGHT = RELATIVE_PREFIX + "-" + HEIGHT; + public static final String RELATIVE_FLOW = RELATIVE_PREFIX + "-" + FLOW; + public static final String SHORT_COMMENT = "short-" + COMMENT; - public static final Set SUPPORTED_THING_TYPES_UIDS = Collections.singleton(THING_TYPE_VIGI_CRUES); + // List of properties Labels + public static final String TRONCON = "Tronçon"; + public static final String DISTANCE = "Distance"; + public static final String RIVER = "Cours"; + public static final String LOCATION = "Location"; + public static final String FLOOD = "Crue"; + + public static final Set SUPPORTED_THING_TYPES_UIDS = Collections.singleton(THING_TYPE_STATION); } diff --git a/bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/VigiCruesHandlerFactory.java b/bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/VigiCruesHandlerFactory.java index f37b32681..03fe45c27 100644 --- a/bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/VigiCruesHandlerFactory.java +++ b/bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/VigiCruesHandlerFactory.java @@ -12,14 +12,13 @@ */ package org.openhab.binding.vigicrues.internal; -import static org.openhab.binding.vigicrues.internal.VigiCruesBindingConstants.*; - -import java.time.ZonedDateTime; +import static org.openhab.binding.vigicrues.internal.VigiCruesBindingConstants.SUPPORTED_THING_TYPES_UIDS; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.vigicrues.internal.api.ApiHandler; import org.openhab.binding.vigicrues.internal.handler.VigiCruesHandler; -import org.openhab.core.i18n.TimeZoneProvider; +import org.openhab.core.i18n.LocationProvider; import org.openhab.core.thing.Thing; import org.openhab.core.thing.ThingTypeUID; import org.openhab.core.thing.binding.BaseThingHandlerFactory; @@ -29,10 +28,6 @@ import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Reference; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonDeserializer; - /** * The {@link VigiCruesHandlerFactory} is responsible for creating things and thing * handlers. @@ -42,17 +37,13 @@ import com.google.gson.JsonDeserializer; @Component(service = ThingHandlerFactory.class, configurationPid = "binding.vigicrues") @NonNullByDefault public class VigiCruesHandlerFactory extends BaseThingHandlerFactory { - private final Gson gson; - // Needed for converting UTC time to local time - private final TimeZoneProvider timeZoneProvider; + private final LocationProvider locationProvider; + private final ApiHandler apiHandler; @Activate - public VigiCruesHandlerFactory(@Reference TimeZoneProvider timeZoneProvider) { - this.timeZoneProvider = timeZoneProvider; - this.gson = new GsonBuilder() - .registerTypeAdapter(ZonedDateTime.class, (JsonDeserializer) (json, type, - jsonDeserializationContext) -> ZonedDateTime.parse(json.getAsJsonPrimitive().getAsString())) - .create(); + public VigiCruesHandlerFactory(@Reference ApiHandler apiHandler, @Reference LocationProvider locationProvider) { + this.locationProvider = locationProvider; + this.apiHandler = apiHandler; } @Override @@ -63,11 +54,6 @@ public class VigiCruesHandlerFactory extends BaseThingHandlerFactory { @Override protected @Nullable ThingHandler createHandler(Thing thing) { ThingTypeUID thingTypeUID = thing.getThingTypeUID(); - - if (thingTypeUID.equals(THING_TYPE_VIGI_CRUES)) { - return new VigiCruesHandler(thing, timeZoneProvider, gson); - } - - return null; + return supportsThingType(thingTypeUID) ? new VigiCruesHandler(thing, locationProvider, apiHandler) : null; } } diff --git a/bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/api/ApiHandler.java b/bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/api/ApiHandler.java new file mode 100644 index 000000000..05508eba4 --- /dev/null +++ b/bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/api/ApiHandler.java @@ -0,0 +1,110 @@ +/** + * Copyright (c) 2010-2020 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.vigicrues.internal.api; + +import java.io.IOException; +import java.time.ZonedDateTime; +import java.util.Locale; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.vigicrues.internal.dto.hubeau.HubEauResponse; +import org.openhab.binding.vigicrues.internal.dto.opendatasoft.OpenDatasoftResponse; +import org.openhab.binding.vigicrues.internal.dto.vigicrues.CdStationHydro; +import org.openhab.binding.vigicrues.internal.dto.vigicrues.InfoVigiCru; +import org.openhab.binding.vigicrues.internal.dto.vigicrues.TerEntVigiCru; +import org.openhab.binding.vigicrues.internal.dto.vigicrues.TronEntVigiCru; +import org.openhab.core.i18n.TimeZoneProvider; +import org.openhab.core.io.net.http.HttpUtil; +import org.openhab.core.library.types.PointType; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonSyntaxException; + +/** + * The {@link ApiHandler} is the responsible to call a given + * url and transform the answer in the appropriate dto class + * + * @author Gaël L'hopital - Initial contribution + */ + +@Component(service = ApiHandler.class) +@NonNullByDefault +public class ApiHandler { + private static final int TIMEOUT_MS = 30000; + private final Gson gson; + + @Activate + public ApiHandler(@Reference TimeZoneProvider timeZoneProvider) { + this.gson = new GsonBuilder().registerTypeAdapter(ZonedDateTime.class, + (JsonDeserializer) (json, type, jsonDeserializationContext) -> ZonedDateTime + .parse(json.getAsJsonPrimitive().getAsString()) + .withZoneSameInstant(timeZoneProvider.getTimeZone())) + .create(); + } + + private T execute(String url, Class responseType) throws VigiCruesException { + String jsonResponse = ""; + try { + jsonResponse = HttpUtil.executeUrl("GET", url, TIMEOUT_MS); + return gson.fromJson(jsonResponse, responseType); + } catch (IOException e) { + throw new VigiCruesException(e); + } catch (JsonSyntaxException e) { + throw new VigiCruesException(e); + } + } + + public InfoVigiCru getTronconStatus(String tronconId) throws VigiCruesException { + final String BASE_URL = "https://www.vigicrues.gouv.fr/services/1/InfoVigiCru.jsonld/?TypEntVigiCru=8&CdEntVigiCru=%s"; + return execute(String.format(BASE_URL, tronconId), InfoVigiCru.class); + } + + public TronEntVigiCru getTroncon(String stationId) throws VigiCruesException { + final String BASE_URL = "https://www.vigicrues.gouv.fr/services/1/TronEntVigiCru.jsonld/?TypEntVigiCru=8&CdEntVigiCru=%s"; + return execute(String.format(BASE_URL, stationId), TronEntVigiCru.class); + } + + public TerEntVigiCru getTerritoire(String stationId) throws VigiCruesException { + final String BASE_URL = "https://www.vigicrues.gouv.fr/services/1/TerEntVigiCru.jsonld/?TypEntVigiCru=5&CdEntVigiCru=%s"; + return execute(String.format(BASE_URL, stationId), TerEntVigiCru.class); + } + + public CdStationHydro getStationDetails(String stationId) throws VigiCruesException { + final String BASE_URL = "https://www.vigicrues.gouv.fr/services/station.json/index.php?CdStationHydro=%s"; + return execute(String.format(BASE_URL, stationId), CdStationHydro.class); + } + + public OpenDatasoftResponse getMeasures(String stationId) throws VigiCruesException { + final String BASE_URL = "https://public.opendatasoft.com/api/records/1.0/search/?dataset=vigicrues&sort=timestamp&q=%s"; + return execute(String.format(BASE_URL, stationId), OpenDatasoftResponse.class); + } + + public HubEauResponse discoverStations(PointType location, int range) throws VigiCruesException { + final String BASE_URL = "https://hubeau.eaufrance.fr/api/v1/hydrometrie/referentiel/stations?format=json&size=2000"; + + return execute( + BASE_URL + String.format(Locale.US, "&latitude=%.2f&longitude=%.2f&distance=%d", + location.getLatitude().floatValue(), location.getLongitude().floatValue(), range), + HubEauResponse.class); + } + + public HubEauResponse discoverStations(String stationId) throws VigiCruesException { + final String BASE_URL = "https://hubeau.eaufrance.fr/api/v1/hydrometrie/referentiel/stations?format=json&size=2000"; + return execute(BASE_URL + String.format("&code_station=%s", stationId), HubEauResponse.class); + } +} diff --git a/bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/api/VigiCruesCertificateProvider.java b/bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/api/VigiCruesCertificateProvider.java new file mode 100644 index 000000000..dcc6bcc12 --- /dev/null +++ b/bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/api/VigiCruesCertificateProvider.java @@ -0,0 +1,44 @@ +/** + * Copyright (c) 2010-2020 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.vigicrues.internal.api; + +import java.net.URL; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.io.net.http.TlsCertificateProvider; +import org.osgi.service.component.annotations.Component; + +/** + * Provides a TrustManager for the VigiCrues SSL certificate + * + * @author Gaël L'hopital - Initial Contribution + */ +@Component +@NonNullByDefault +public class VigiCruesCertificateProvider implements TlsCertificateProvider { + + @Override + public String getHostName() { + return "www.vigicrues.gouv.fr"; + } + + @Override + public URL getCertificate() { + URL resource = Thread.currentThread().getContextClassLoader().getResource("vigicrues.cer"); + if (resource != null) { + return resource; + } else { + throw new IllegalStateException("Certifcate resource not found or not accessible"); + } + } +} diff --git a/bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/api/VigiCruesException.java b/bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/api/VigiCruesException.java new file mode 100644 index 000000000..35b0d0f5a --- /dev/null +++ b/bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/api/VigiCruesException.java @@ -0,0 +1,37 @@ +/** + * Copyright (c) 2010-2020 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.vigicrues.internal.api; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * Exception for errors when using the VigiCrues API + * + * @author Gaël L'hopital - Initial contribution + */ +@NonNullByDefault +public class VigiCruesException extends Exception { + private static final long serialVersionUID = -7781683052187130152L; + + public VigiCruesException(Throwable e) { + super(null, e); + } + + public VigiCruesException(String msg, Throwable cause) { + super(msg, cause); + } + + public VigiCruesException(String msg) { + super(msg, null); + } +} diff --git a/bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/discovery/VigiCruesDiscoveryService.java b/bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/discovery/VigiCruesDiscoveryService.java new file mode 100644 index 000000000..c2b94894a --- /dev/null +++ b/bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/discovery/VigiCruesDiscoveryService.java @@ -0,0 +1,81 @@ +/** + * Copyright (c) 2010-2020 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.vigicrues.internal.discovery; + +import static org.openhab.binding.vigicrues.internal.VigiCruesBindingConstants.*; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.vigicrues.internal.StationConfiguration; +import org.openhab.binding.vigicrues.internal.api.ApiHandler; +import org.openhab.binding.vigicrues.internal.api.VigiCruesException; +import org.openhab.binding.vigicrues.internal.dto.hubeau.HubEauResponse; +import org.openhab.core.config.discovery.AbstractDiscoveryService; +import org.openhab.core.config.discovery.DiscoveryResultBuilder; +import org.openhab.core.config.discovery.DiscoveryService; +import org.openhab.core.i18n.LocationProvider; +import org.openhab.core.library.types.PointType; +import org.openhab.core.thing.ThingUID; +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; + +/** + * The {@link VigiCruesDiscoveryService} searches for available + * hydro stations discoverable through API + * + * @author Gaël L'hopital - Initial contribution + */ +@Component(service = DiscoveryService.class, configurationPid = "discovery.vigicrues") +@NonNullByDefault +public class VigiCruesDiscoveryService extends AbstractDiscoveryService { + private static final int SEARCH_TIME = 5; + + private final Logger logger = LoggerFactory.getLogger(VigiCruesDiscoveryService.class); + private final LocationProvider locationProvider; + private final ApiHandler apiHandler; + + private int searchRange = 10; + + @Activate + public VigiCruesDiscoveryService(@Reference ApiHandler apiHandler, @Reference LocationProvider locationProvider) { + super(SUPPORTED_THING_TYPES_UIDS, SEARCH_TIME, false); + this.apiHandler = apiHandler; + this.locationProvider = locationProvider; + } + + @Override + public void startScan() { + PointType location = locationProvider.getLocation(); + if (location != null) { + try { + HubEauResponse response = apiHandler.discoverStations(location, searchRange); + if (response.count > 0) { + response.stations.stream().filter(station -> station.enService).forEach(station -> { + thingDiscovered(DiscoveryResultBuilder + .create(new ThingUID(THING_TYPE_STATION, station.codeStation)) + .withLabel(station.libelleStation).withRepresentationProperty(StationConfiguration.ID) + .withProperty(StationConfiguration.ID, station.codeStation).build()); + }); + } else { + logger.info("No station exists in a neighbourhood of {} km", searchRange); + } + } catch (VigiCruesException e) { + logger.warn("Error discovering nearby hydro stations : {}", e.getMessage()); + } + searchRange += 10; + } + stopScan(); + } +} diff --git a/bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/dto/hubeau/HubEauResponse.java b/bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/dto/hubeau/HubEauResponse.java new file mode 100644 index 000000000..3aea34b38 --- /dev/null +++ b/bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/dto/hubeau/HubEauResponse.java @@ -0,0 +1,157 @@ +package org.openhab.binding.vigicrues.internal.dto.hubeau; + +import java.util.List; + +import com.google.gson.annotations.SerializedName; + +public class HubEauResponse { + public class StationData { + @SerializedName("en_service") + public boolean enService; + + @SerializedName("code_station") + public String codeStation; + + @SerializedName("libelle_station") + public String libelleStation; + + @SerializedName("longitude_station") + public double longitudeStation; + + @SerializedName("latitude_station") + public double latitudeStation; + + @SerializedName("libelle_cours_eau") + public String libelleCoursEau; + /* + * Currently unused, maybe interesting in the future + * + * @SerializedName("code_site") + * public String codeSite; + * + * @SerializedName("libelle_site") + * public String libelleSite; + * + * @SerializedName("type_station") + * public String typeStation; + * + * @SerializedName("coordonnee_x_station") + * public int coordonneeXStation; + * + * @SerializedName("coordonnee_y_station") + * public int coordonneeYStation; + * + * @SerializedName("code_projection") + * public int codeProjection; + * + * @SerializedName("influence_locale_station") + * public int influenceLocaleStation; + * + * @SerializedName("commentaire_station") + * public String commentaireStation; + * + * @SerializedName("altitude_ref_alti_station") + * public double altitudeRefAltiStation; + * + * @SerializedName("code_systeme_alti_site") + * public int codeSystemeAltiSite; + * + * @SerializedName("code_commune_station") + * public String codeCommuneStation; + * + * @SerializedName("libelle_commune") + * public String libelleCommune; + * + * @SerializedName("code_departement") + * public String codeDepartement; + * + * @SerializedName("code_region") + * public String codeRegion; + * + * @SerializedName("libelle_region") + * public String libelleRegion; + * + * @SerializedName("code_cours_eau") + * public String codeCoursEau; + * + * @SerializedName("uri_cours_eau") + * public String uriCoursEau; + * + * @SerializedName("descriptif_station") + * public String descriptifStation; + * + * @SerializedName("date_maj_station") + * public String dateMajStation; + * + * @SerializedName("date_ouverture_station") + * public String dateOuvertureStation; + * + * @SerializedName("date_fermeture_station") + * public String dateFermetureStation; + * + * @SerializedName("commentaire_influence_locale_station") + * public String commentaireInfluenceLocaleStation; + * + * @SerializedName("code_regime_station") + * public int codeRegimeStation; + * + * @SerializedName("qualification_donnees_station") + * public int qualificationDonneesStation; + * + * @SerializedName("code_finalite_station") + * public int codeFinaliteStation; + * + * @SerializedName("type_contexte_loi_stat_station") + * public int typeContexteLoiStatStation; + * + * @SerializedName("type_loi_station") + * public int typeLoiStation; + * + * @SerializedName("code_sandre_reseau_station") + * public List codeSandreReseauStation; + * + * @SerializedName("date_debut_ref_alti_station") + * public String dateDebutRefAltiStation; + * + * @SerializedName("date_activation_ref_alti_station") + * public String dateActivationRefAltiStation; + * + * @SerializedName("date_maj_ref_alti_station") + * public String dateMajRefAltiStation; + * + * @SerializedName("libelle_departement") + * public String libelleDepartement; + * public Geometry geometry; + */ + } + + public int count; + @SerializedName("data") + public List stations; + + /* + * Currently unused, maybe interesting in the future + * public String first; + * public String last; + * public String prev; + * public String next; + * + * @SerializedName("api_version") + * public String apiVersion; + * + * public class Crs { + * public String type; + * public Properties properties; + * } + * + * public class Properties { + * public String name; + * } + * + * public class Geometry { + * public String type; + * public Crs crs; + * public List coordinates; + * } + */ +} diff --git a/bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/json/OpenDatasoftResponse.java b/bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/dto/opendatasoft/OpenDatasoftResponse.java similarity index 79% rename from bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/json/OpenDatasoftResponse.java rename to bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/dto/opendatasoft/OpenDatasoftResponse.java index 0c59ed814..c033892e8 100644 --- a/bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/json/OpenDatasoftResponse.java +++ b/bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/dto/opendatasoft/OpenDatasoftResponse.java @@ -10,8 +10,10 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.vigicrues.internal.json; +package org.openhab.binding.vigicrues.internal.dto.opendatasoft; +import java.util.ArrayList; +import java.util.List; import java.util.Optional; import org.eclipse.jdt.annotation.NonNullByDefault; @@ -30,7 +32,7 @@ public class OpenDatasoftResponse { @SerializedName("nhits") private int nHits; private @Nullable Parameters parameters; - private Record[] records = {}; + private List records = new ArrayList<>(); public int getNHits() { return nHits; @@ -44,7 +46,7 @@ public class OpenDatasoftResponse { return Optional.empty(); } - public Record[] getRecords() { - return records; + public Optional getFirstRecord() { + return records.stream().findFirst().flatMap(Record::getFields); } } diff --git a/bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/json/Parameters.java b/bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/dto/opendatasoft/Parameters.java similarity index 95% rename from bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/json/Parameters.java rename to bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/dto/opendatasoft/Parameters.java index 6ffc2bd43..c5cb5b13b 100644 --- a/bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/json/Parameters.java +++ b/bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/dto/opendatasoft/Parameters.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.vigicrues.internal.json; +package org.openhab.binding.vigicrues.internal.dto.opendatasoft; import java.time.ZoneId; import java.util.Arrays; diff --git a/bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/json/Record.java b/bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/dto/opendatasoft/Record.java similarity index 95% rename from bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/json/Record.java rename to bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/dto/opendatasoft/Record.java index a7a2f8d4b..cd67deed1 100644 --- a/bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/json/Record.java +++ b/bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/dto/opendatasoft/Record.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.vigicrues.internal.json; +package org.openhab.binding.vigicrues.internal.dto.opendatasoft; import java.util.Optional; diff --git a/bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/json/Refine.java b/bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/dto/opendatasoft/Refine.java similarity index 92% rename from bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/json/Refine.java rename to bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/dto/opendatasoft/Refine.java index e98c8d938..145dad5e6 100644 --- a/bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/json/Refine.java +++ b/bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/dto/opendatasoft/Refine.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.vigicrues.internal.json; +package org.openhab.binding.vigicrues.internal.dto.opendatasoft; import org.eclipse.jdt.annotation.NonNullByDefault; diff --git a/bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/json/VigiCruesFields.java b/bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/dto/opendatasoft/VigiCruesFields.java similarity index 96% rename from bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/json/VigiCruesFields.java rename to bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/dto/opendatasoft/VigiCruesFields.java index ec378dbb9..c813515de 100644 --- a/bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/json/VigiCruesFields.java +++ b/bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/dto/opendatasoft/VigiCruesFields.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.vigicrues.internal.json; +package org.openhab.binding.vigicrues.internal.dto.opendatasoft; import java.time.ZonedDateTime; import java.util.Optional; diff --git a/bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/dto/vigicrues/CdStationHydro.java b/bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/dto/vigicrues/CdStationHydro.java new file mode 100644 index 000000000..e0adc428d --- /dev/null +++ b/bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/dto/vigicrues/CdStationHydro.java @@ -0,0 +1,104 @@ +/** + * Copyright (c) 2010-2020 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.vigicrues.internal.dto.vigicrues; + +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import org.openhab.binding.vigicrues.internal.VigiCruesBindingConstants; + +import com.google.gson.annotations.SerializedName; + +/** + * The {@link CdStationHydro} is the Java class used to map the JSON + * response to an vigicrue api endpoint request. + * + * @author Gaël L'hopital - Initial contribution + */ +public class CdStationHydro { + + public class PereBoitEntVigiCru { + @SerializedName("CdEntVigiCru") + public String cdEntVigiCru; + } + + public class CruesHistorique { + @SerializedName("LbUsuel") + public String name; + @SerializedName("ValHauteur") + public double height; + @SerializedName("ValDebit") + public double flow; + + public Map getDescription() { + Map result = new HashMap<>(); + if (height != 0) { + result.put(String.format("%s %s (%s)", VigiCruesBindingConstants.FLOOD, + VigiCruesBindingConstants.HEIGHT, name), String.format(Locale.US, "%.2f m", height)); + } + if (flow != 0) { + result.put(String.format("%s %s (%s)", VigiCruesBindingConstants.FLOOD, VigiCruesBindingConstants.FLOW, + name), String.format(Locale.US, "%.2f m³/s", flow)); + } + return result; + } + } + + public class VigilanceCrues { + @SerializedName("PereBoitEntVigiCru") + public PereBoitEntVigiCru pereBoitEntVigiCru; + @SerializedName("CruesHistoriques") + public List cruesHistoriques; + /* + * Currently unused, maybe interesting in the future + * + * @SerializedName("StationPrevision") + * public String stationPrevision; + * + * @SerializedName("Photo") + * public String photo; + * + * @SerializedName("ZoomInitial") + * public String zoomInitial; + */ + } + + @SerializedName("VigilanceCrues") + public VigilanceCrues vigilanceCrues; + /* + * Currently unused, maybe interesting in the future + * + * @SerializedName("VersionFlux") + * public String versionFlux; + * + * @SerializedName("CdStationHydro") + * public String cdStationHydro; + * + * @SerializedName("LbStationHydro") + * public String lbStationHydro; + * + * @SerializedName("LbCoursEau") + * public String lbCoursEau; + * + * @SerializedName("CdStationHydroAncienRef") + * public String cdStationHydroAncienRef; + * + * @SerializedName("CdCommune") + * public String cdCommune; + * + * @SerializedName("error_msg") + * public String errorMsg; + */ +} diff --git a/bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/dto/vigicrues/InfoVigiCru.java b/bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/dto/vigicrues/InfoVigiCru.java new file mode 100644 index 000000000..0151cc513 --- /dev/null +++ b/bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/dto/vigicrues/InfoVigiCru.java @@ -0,0 +1,56 @@ +/** + * Copyright (c) 2010-2020 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.vigicrues.internal.dto.vigicrues; + +import com.google.gson.annotations.SerializedName; + +/** + * The {@link InfoVigiCru} is the Java class used to map the JSON + * response to an vigicrue api endpoint request. + * + * @author Gaël L'hopital - Initial contribution + */ +public class InfoVigiCru { + public class VicInfoVigiCru { + @SerializedName("vic:NivInfoVigiCru") + public int vicNivInfoVigiCru; + @SerializedName("vic:SituActuInfoVigiCru") + public String vicSituActuInfoVigiCru; + @SerializedName("vic:QualifInfoVigiCru") + public String vicQualifInfoVigiCru; + /* + * Currently unused, maybe interesting in the future + * + * @SerializedName("vic:RefInfoVigiCru") + * private String vicRefInfoVigiCru; + * + * @SerializedName("vic:TypInfoVigiCru") + * private int vicTypInfoVigiCru; + * + * @SerializedName("vic:DtHrInfoVigiCru") + * private String vicDtHrInfoVigiCru; + * + * @SerializedName("vic:DtHrSuivInfoVigiCru") + * private String vicDtHrSuivInfoVigiCru; + * + * @SerializedName("vic:EstNivCalInfoVigiCru") + * private Boolean vicEstNivCalInfoVigiCru; + * + * @SerializedName("vic:StInfoVigiCru") + * private int vicStInfoVigiCru; + */ + } + + @SerializedName("vic:InfoVigiCru") + public VicInfoVigiCru vicInfoVigiCru; +} diff --git a/bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/dto/vigicrues/TerEntVigiCru.java b/bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/dto/vigicrues/TerEntVigiCru.java new file mode 100644 index 000000000..3e6d1d6ca --- /dev/null +++ b/bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/dto/vigicrues/TerEntVigiCru.java @@ -0,0 +1,62 @@ +/** + * Copyright (c) 2010-2020 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.vigicrues.internal.dto.vigicrues; + +import java.util.List; + +import com.google.gson.annotations.SerializedName; + +/** + * The {@link TerEntVigiCru} is the Java class used to map the JSON + * response to an vigicrue api endpoint request. + * + * @author Gaël L'hopital - Initial contribution + */ +public class TerEntVigiCru { + + public class VicTerEntVigiCru { + @SerializedName("vic:aNMoinsUn") + public List vicANMoinsUn; + /* + * Currently unused, maybe interesting in the future + * + * @SerializedName("@id") + * public String id; + * + * @SerializedName("vic:CdEntVigiCru") + * public String vicCdEntVigiCru; + * + * @SerializedName("vic:TypEntVigiCru") + * public String vicTypEntVigiCru; + * + * @SerializedName("vic:LbEntVigiCru") + * public String vicLbEntVigiCru; + * + * @SerializedName("vic:DtHrCreatEntVigiCru") + * public String vicDtHrCreatEntVigiCru; + * + * @SerializedName("vic:DtHrMajEntVigiCru") + * public String vicDtHrMajEntVigiCru; + * + * @SerializedName("vic:StEntVigiCru") + * public String vicStEntVigiCru; + * public int count_aNMoinsUn; + * + * @SerializedName("LinkInfoCru") + * public String linkInfoCru; + */ + } + + @SerializedName("vic:TerEntVigiCru") + public VicTerEntVigiCru vicTerEntVigiCru; +} diff --git a/bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/dto/vigicrues/TronEntVigiCru.java b/bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/dto/vigicrues/TronEntVigiCru.java new file mode 100644 index 000000000..b0383681d --- /dev/null +++ b/bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/dto/vigicrues/TronEntVigiCru.java @@ -0,0 +1,80 @@ +/** + * Copyright (c) 2010-2020 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.vigicrues.internal.dto.vigicrues; + +import java.util.List; +import java.util.stream.Stream; + +import com.google.gson.annotations.SerializedName; + +/** + * The {@link TronEntVigiCru} is the Java class used to map the JSON + * response to an vigicrue api endpoint request. + * + * @author Gaël L'hopital - Initial contribution + */ +public class TronEntVigiCru { + public class VicStaEntVigiCru { + @SerializedName("vic:CdEntVigiCru") + public String vicCdEntVigiCru; + } + + public class VicTronEntVigiCru { + @SerializedName("vic:aNMoinsUn") + public List stations; + /* + * Currently unused, maybe interesting in the future + * + * @SerializedName("@id") + * public String id; + * + * @SerializedName("vic:CdEntVigiCru") + * public String vicCdEntVigiCru; + * + * @SerializedName("vic:TypEntVigiCru") + * public String vicTypEntVigiCru; + * + * @SerializedName("vic:LbEntVigiCru") + * public String vicLbEntVigiCru; + * + * @SerializedName("vic:DtHrCreatEntVigiCru") + * public String vicDtHrCreatEntVigiCru; + * + * @SerializedName("vic:DtHrMajEntVigiCru") + * public String vicDtHrMajEntVigiCru; + * + * @SerializedName("vic:StEntVigiCru") + * public String vicStEntVigiCru; + * + * @SerializedName("count_aNPlusUn") + * public Integer countANPlusUn; + * + * @SerializedName("count_aNMoinsUn") + * public Integer countANMoinsUn; + * + * @SerializedName("LinkInfoCru") + * public String linkInfoCru; + */ + } + + @SerializedName("vic:TronEntVigiCru") + private VicTronEntVigiCru tronTerEntVigiCru; + + public Stream getStations() { + if (tronTerEntVigiCru != null && tronTerEntVigiCru.stations != null) { + return tronTerEntVigiCru.stations.stream(); + } else { + return Stream.empty(); + } + } +} diff --git a/bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/dto/vigicrues/VicANMoinsUn.java b/bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/dto/vigicrues/VicANMoinsUn.java new file mode 100644 index 000000000..afac4cbd8 --- /dev/null +++ b/bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/dto/vigicrues/VicANMoinsUn.java @@ -0,0 +1,45 @@ +/** + * Copyright (c) 2010-2020 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.vigicrues.internal.dto.vigicrues; + +import com.google.gson.annotations.SerializedName; + +/** + * The {@link VicANMoinsUn} is the Java class used to map the JSON + * response to an vigicrue api endpoint request. + * + * @author Gaël L'hopital - Initial contribution + */ +public class VicANMoinsUn { + @SerializedName("vic.CdEntVigiCru") + public String vicCdEntVigiCru; + + /* + * Currently unused, maybe interesting in the future + * + * @SerializedName("@id") + * private String id; + * + * @SerializedName("vic.TypEntVigiCru") + * private String vicTypEntVigiCru; + * + * @SerializedName("vic.LbEntVigiCru") + * private String vicLbEntVigiCru; + * + * @SerializedName("LinkEntity") + * private String linkEntity; + * + * @SerializedName("LinkInfoCru") + * private String linkInfoCru; + */ +} diff --git a/bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/handler/VigiCruesHandler.java b/bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/handler/VigiCruesHandler.java index 1b6ff686a..02909f64f 100644 --- a/bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/handler/VigiCruesHandler.java +++ b/bundles/org.openhab.binding.vigicrues/src/main/java/org/openhab/binding/vigicrues/internal/handler/VigiCruesHandler.java @@ -13,39 +13,55 @@ package org.openhab.binding.vigicrues.internal.handler; import static org.openhab.binding.vigicrues.internal.VigiCruesBindingConstants.*; -import static org.openhab.core.library.unit.SmartHomeUnits.CUBICMETRE_PER_SECOND; -import static tec.uom.se.unit.Units.METRE; import java.io.IOException; -import java.net.MalformedURLException; +import java.io.InputStream; import java.time.ZonedDateTime; -import java.util.Arrays; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; import javax.measure.Unit; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.vigicrues.internal.VigiCruesConfiguration; -import org.openhab.binding.vigicrues.internal.json.OpenDatasoftResponse; -import org.openhab.binding.vigicrues.internal.json.Record; -import org.openhab.core.i18n.TimeZoneProvider; -import org.openhab.core.io.net.http.HttpUtil; +import org.openhab.binding.vigicrues.internal.StationConfiguration; +import org.openhab.binding.vigicrues.internal.api.ApiHandler; +import org.openhab.binding.vigicrues.internal.api.VigiCruesException; +import org.openhab.binding.vigicrues.internal.dto.hubeau.HubEauResponse; +import org.openhab.binding.vigicrues.internal.dto.opendatasoft.OpenDatasoftResponse; +import org.openhab.binding.vigicrues.internal.dto.vigicrues.CdStationHydro; +import org.openhab.binding.vigicrues.internal.dto.vigicrues.InfoVigiCru; +import org.openhab.binding.vigicrues.internal.dto.vigicrues.TerEntVigiCru; +import org.openhab.binding.vigicrues.internal.dto.vigicrues.TronEntVigiCru; +import org.openhab.binding.vigicrues.internal.dto.vigicrues.VicANMoinsUn; +import org.openhab.core.i18n.LocationProvider; import org.openhab.core.library.types.DateTimeType; +import org.openhab.core.library.types.DecimalType; +import org.openhab.core.library.types.PointType; import org.openhab.core.library.types.QuantityType; +import org.openhab.core.library.types.RawType; +import org.openhab.core.library.types.StringType; +import org.openhab.core.library.unit.SIUnits; +import org.openhab.core.library.unit.SmartHomeUnits; +import org.openhab.core.thing.Channel; import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.Thing; import org.openhab.core.thing.ThingStatus; import org.openhab.core.thing.ThingStatusDetail; import org.openhab.core.thing.binding.BaseThingHandler; +import org.openhab.core.thing.binding.builder.ThingBuilder; import org.openhab.core.types.Command; import org.openhab.core.types.RefreshType; +import org.openhab.core.types.UnDefType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.google.gson.Gson; - /** * The {@link VigiCruesHandler} is responsible for querying the API and * updating channels @@ -54,35 +70,124 @@ import com.google.gson.Gson; */ @NonNullByDefault public class VigiCruesHandler extends BaseThingHandler { - private static final String URL = OPENDATASOFT_URL + "?dataset=vigicrues&sort=timestamp&q="; - private static final int TIMEOUT_MS = 30000; private final Logger logger = LoggerFactory.getLogger(VigiCruesHandler.class); + private final LocationProvider locationProvider; - // Time zone provider representing time zone configured in openHAB configuration - private final TimeZoneProvider timeZoneProvider; - private final Gson gson; private @Nullable ScheduledFuture refreshJob; - private @Nullable String queryUrl; + private final ApiHandler apiHandler; - public VigiCruesHandler(Thing thing, TimeZoneProvider timeZoneProvider, Gson gson) { + private List> referenceHeights = new ArrayList<>(); + private List> referenceFlows = new ArrayList<>(); + private @Nullable String portion; + + public VigiCruesHandler(Thing thing, LocationProvider locationProvider, ApiHandler apiHandler) { super(thing); - this.timeZoneProvider = timeZoneProvider; - this.gson = gson; + this.apiHandler = apiHandler; + this.locationProvider = locationProvider; } @Override public void initialize() { logger.debug("Initializing VigiCrues handler."); - VigiCruesConfiguration config = getConfigAs(VigiCruesConfiguration.class); - logger.debug("config station = {}", config.id); + StationConfiguration config = getConfigAs(StationConfiguration.class); logger.debug("config refresh = {} min", config.refresh); updateStatus(ThingStatus.UNKNOWN); - queryUrl = URL + config.id; + + if (thing.getProperties().isEmpty()) { + Map properties = discoverAttributes(config); + updateProperties(properties); + } + getReferences(); refreshJob = scheduler.scheduleWithFixedDelay(this::updateAndPublish, 0, config.refresh, TimeUnit.MINUTES); } + private void getReferences() { + List> heights = new ArrayList<>(); + List> flows = new ArrayList<>(); + thing.getProperties().keySet().stream().filter(key -> key.startsWith(FLOOD)).forEach(key -> { + String value = thing.getProperties().get(key); + if (key.contains(FLOW)) { + flows.add(new QuantityType<>(value)); + } else { + heights.add(new QuantityType<>(value)); + } + }); + referenceHeights = heights.stream().distinct().sorted().collect(Collectors.toList()); + referenceFlows = flows.stream().distinct().sorted().collect(Collectors.toList()); + portion = thing.getProperties().get(TRONCON); + } + + private Map discoverAttributes(StationConfiguration config) { + Map properties = new HashMap<>(); + + ThingBuilder thingBuilder = editThing(); + List channels = new ArrayList<>(getThing().getChannels()); + + try { + HubEauResponse stationDetails = apiHandler.discoverStations(config.id); + stationDetails.stations.stream().findFirst().ifPresent(station -> { + PointType stationLocation = new PointType( + String.format(Locale.US, "%f,%f", station.latitudeStation, station.longitudeStation)); + properties.put(LOCATION, stationLocation.toString()); + PointType serverLocation = locationProvider.getLocation(); + if (serverLocation != null) { + DecimalType distance = serverLocation.distanceFrom(stationLocation); + properties.put(DISTANCE, new QuantityType<>(distance, SIUnits.METRE).toString()); + } + properties.put(RIVER, station.libelleCoursEau); + }); + } catch (VigiCruesException e) { + logger.info("Unable to retrieve station location details {} : {}", config.id, e.getMessage()); + } + + try { + CdStationHydro refineStation = apiHandler.getStationDetails(config.id); + if (refineStation.vigilanceCrues.cruesHistoriques == null) { + throw new VigiCruesException("No historical data available"); + } + refineStation.vigilanceCrues.cruesHistoriques.stream() + .forEach(crue -> properties.putAll(crue.getDescription())); + String codeTerritoire = refineStation.vigilanceCrues.pereBoitEntVigiCru.cdEntVigiCru; + TerEntVigiCru territoire = apiHandler.getTerritoire(codeTerritoire); + for (VicANMoinsUn troncon : territoire.vicTerEntVigiCru.vicANMoinsUn) { + TronEntVigiCru detail = apiHandler.getTroncon(troncon.vicCdEntVigiCru); + if (detail.getStations().anyMatch(s -> config.id.equalsIgnoreCase(s.vicCdEntVigiCru))) { + properties.put(TRONCON, troncon.vicCdEntVigiCru); + break; + } + } + } catch (VigiCruesException e) { + logger.info("Historical flooding data are not available {} : {}", config.id, e.getMessage()); + channels.removeIf(channel -> (channel.getUID().getId().contains(RELATIVE_PREFIX))); + } + + if (!properties.containsKey(TRONCON)) { + channels.removeIf(channel -> (channel.getUID().getId().contains(ALERT))); + channels.removeIf(channel -> (channel.getUID().getId().contains(COMMENT))); + } + + try { + OpenDatasoftResponse measures = apiHandler.getMeasures(config.id); + measures.getFirstRecord().ifPresent(field -> { + if (field.getHeight().isEmpty()) { + channels.removeIf(channel -> (channel.getUID().getId().contains(HEIGHT))); + } + if (field.getFlow().isEmpty()) { + channels.removeIf(channel -> (channel.getUID().getId().contains(FLOW))); + } + }); + } catch (VigiCruesException e) { + logger.warn("Unable to read measurements data {} : {}", config.id, e.getMessage()); + } + + thingBuilder.withChannels(channels); + updateThing(thingBuilder.build()); + + return properties; + } + @Override public void dispose() { logger.debug("Disposing the VigiCrues handler."); @@ -102,26 +207,43 @@ public class VigiCruesHandler extends BaseThingHandler { } private void updateAndPublish() { + StationConfiguration config = getConfigAs(StationConfiguration.class); try { - if (queryUrl != null) { - String response = HttpUtil.executeUrl("GET", queryUrl, TIMEOUT_MS); - updateStatus(ThingStatus.ONLINE); - OpenDatasoftResponse apiResponse = gson.fromJson(response, OpenDatasoftResponse.class); - Arrays.stream(apiResponse.getRecords()).findFirst().flatMap(Record::getFields).ifPresent(field -> { - field.getHeight().ifPresent(height -> updateQuantity(HEIGHT, height, METRE)); - field.getFlow().ifPresent(flow -> updateQuantity(FLOW, flow, CUBICMETRE_PER_SECOND)); - field.getTimestamp().ifPresent(date -> updateDate(OBSERVATION_TIME, date)); + OpenDatasoftResponse measures = apiHandler.getMeasures(config.id); + measures.getFirstRecord().ifPresent(field -> { + field.getHeight().ifPresent(height -> { + updateQuantity(HEIGHT, height, SIUnits.METRE); + updateRelativeMeasure(RELATIVE_HEIGHT, referenceHeights, height); }); - } else { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.DISABLED, - "queryUrl should never be null, but it is !"); + field.getFlow().ifPresent(flow -> { + updateQuantity(FLOW, flow, SmartHomeUnits.CUBICMETRE_PER_SECOND); + updateRelativeMeasure(RELATIVE_FLOW, referenceFlows, flow); + }); + field.getTimestamp().ifPresent(date -> updateDate(OBSERVATION_TIME, date)); + }); + String currentPortion = this.portion; + if (currentPortion != null) { + InfoVigiCru status = apiHandler.getTronconStatus(currentPortion); + updateAlert(ALERT, status.vicInfoVigiCru.vicNivInfoVigiCru - 1); + updateString(SHORT_COMMENT, status.vicInfoVigiCru.vicSituActuInfoVigiCru); + updateString(COMMENT, status.vicInfoVigiCru.vicQualifInfoVigiCru); } - } catch (MalformedURLException e) { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, - String.format("Querying '%s' raised : %s", queryUrl, e.getMessage())); - } catch (IOException e) { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, - String.format("Error opening connection to VigiCrues webservice : {}", e.getMessage())); + updateStatus(ThingStatus.ONLINE); + } catch (VigiCruesException e) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); + } + } + + private void updateString(String channelId, String value) { + if (isLinked(channelId)) { + updateState(channelId, new StringType(value)); + } + } + + private void updateRelativeMeasure(String channelId, List> reference, double value) { + if (reference.size() > 0) { + double percent = value / reference.get(0).doubleValue() * 100; + updateQuantity(channelId, percent, SmartHomeUnits.PERCENT); } } @@ -133,8 +255,27 @@ public class VigiCruesHandler extends BaseThingHandler { public void updateDate(String channelId, ZonedDateTime zonedDateTime) { if (isLinked(channelId)) { - ZonedDateTime localDateTime = zonedDateTime.withZoneSameInstant(timeZoneProvider.getTimeZone()); - updateState(channelId, new DateTimeType(localDateTime)); + updateState(channelId, new DateTimeType(zonedDateTime)); } } + + public void updateAlert(String channelId, int value) { + String channelIcon = channelId + "-icon"; + if (isLinked(channelId)) { + updateState(channelId, new DecimalType(value)); + } + if (isLinked(channelIcon)) { + byte[] resource = getResource(String.format("picto/crue-%d.svg", value)); + updateState(channelIcon, resource != null ? new RawType(resource, "image/svg+xml") : UnDefType.UNDEF); + } + } + + public byte @Nullable [] getResource(String iconPath) { + try (InputStream stream = VigiCruesHandler.class.getClassLoader().getResourceAsStream(iconPath)) { + return stream.readAllBytes(); + } catch (IOException e) { + logger.warn("Unable to load ressource '{}' : {}", iconPath, e.getMessage()); + } + return null; + } } diff --git a/bundles/org.openhab.binding.vigicrues/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.vigicrues/src/main/resources/OH-INF/thing/thing-types.xml index 2e7a13351..11c496cab 100644 --- a/bundles/org.openhab.binding.vigicrues/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.vigicrues/src/main/resources/OH-INF/thing/thing-types.xml @@ -12,10 +12,28 @@ + + + Current height toward historical floods. + + + + Current flow toward historic floods. + + + + + + + + + + id + @@ -32,6 +50,7 @@ Number:VolumetricFlowRate + flow @@ -39,6 +58,7 @@ DateTime Observation date and time + time @@ -48,4 +68,36 @@ Water level in the river + + + Number:Dimensionless + + + + + + Number + + + + + + + + + + + + + String + + + + + + Image + + Pictogramme associé au niveau d'alerte. + + diff --git a/bundles/org.openhab.binding.vigicrues/src/main/resources/picto/crue-0.svg b/bundles/org.openhab.binding.vigicrues/src/main/resources/picto/crue-0.svg new file mode 100644 index 000000000..f0a6fa2e0 --- /dev/null +++ b/bundles/org.openhab.binding.vigicrues/src/main/resources/picto/crue-0.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/bundles/org.openhab.binding.vigicrues/src/main/resources/picto/crue-1.svg b/bundles/org.openhab.binding.vigicrues/src/main/resources/picto/crue-1.svg new file mode 100644 index 000000000..7e3565fdd --- /dev/null +++ b/bundles/org.openhab.binding.vigicrues/src/main/resources/picto/crue-1.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/bundles/org.openhab.binding.vigicrues/src/main/resources/picto/crue-2.svg b/bundles/org.openhab.binding.vigicrues/src/main/resources/picto/crue-2.svg new file mode 100644 index 000000000..3a000b263 --- /dev/null +++ b/bundles/org.openhab.binding.vigicrues/src/main/resources/picto/crue-2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/bundles/org.openhab.binding.vigicrues/src/main/resources/picto/crue-3.svg b/bundles/org.openhab.binding.vigicrues/src/main/resources/picto/crue-3.svg new file mode 100644 index 000000000..5ac6ae245 --- /dev/null +++ b/bundles/org.openhab.binding.vigicrues/src/main/resources/picto/crue-3.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/bundles/org.openhab.binding.vigicrues/src/main/resources/vigicrues.cer b/bundles/org.openhab.binding.vigicrues/src/main/resources/vigicrues.cer new file mode 100644 index 000000000..fba00f355 --- /dev/null +++ b/bundles/org.openhab.binding.vigicrues/src/main/resources/vigicrues.cer @@ -0,0 +1,50 @@ +-----BEGIN CERTIFICATE----- +MIII6DCCBtCgAwIBAgIRAKyivjc1wLX1ahBKQr2wLpwwDQYJKoZIhvcNAQELBQAw +fTELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCURISU1ZT1RJUzEcMBoGA1UECwwTMDAw +MiA0ODE0NjMwODEwMDAzNjEdMBsGA1UEYQwUTlRSRlItNDgxNDYzMDgxMDAwMzYx +HTAbBgNVBAMMFENlcnRpZ25hIFNlcnZpY2VzIENBMB4XDTIwMDkxMDA4NDM0NloX +DTIxMDkxMDA4NDM0NlowgZgxCzAJBgNVBAYTAkZSMRkwFwYDVQQHDBBQQVJJUyBM +QSBERUZFTlNFMSAwHgYDVQQKDBdTRyBNSU4gVFJBTlMgRUNPTCBTT0xJRDEcMBoG +A1UECwwTMDAwMiAxMzAwMTk1NDAwMDAyNTEaMBgGA1UEAwwRdmlnaWNydWVzLmdv +dXYuZnIxEjAQBgNVBAUTCVMxNzQ1ODAxNDCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBAOxiWSS7UVERcuH6PI4Itkm3hNos8wiwmbI1mtHht4a94h9gxzQ1 +U0t/wxYkOgzdQ3VCSNgX27YOEsfJKlqREx3AaSdztPW27mXCgl3+IOF6bm0UooIn +gcl0inpGYJCI4Mb+2xEpmeJt2eEojQgiYQZVJuf2yOoNpCdNJlS/ym3LM1AQVxBm +/Jl24KWbhr7cjPye0773jqckwf3JSFbqVtxinctVnnS+qdwn/hQP3nNBkXXKOnJM +71vD7S4MwIMy3oQHK8oYmTwQpliju8rOIVKu099hiQAcegh5b33gG2RagHaTd6vb +sunW5GBWS4IfhQurQT5pjKnVT4zpF/h4b+kCAwEAAaOCBEUwggRBMAkGA1UdEwQC +MAAwDgYDVR0PAQH/BAQDAgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMGUGA1UdHwRe +MFwwK6ApoCeGJWh0dHA6Ly9jcmwuY2VydGlnbmEuZnIvc2VydmljZXNjYS5jcmww +LaAroCmGJ2h0dHA6Ly9jcmwuZGhpbXlvdGlzLmNvbS9zZXJ2aWNlc2NhLmNybDCB +5AYIKwYBBQUHAQEEgdcwgdQwNgYIKwYBBQUHMAKGKmh0dHA6Ly9hdXRvcml0ZS5j +ZXJ0aWduYS5mci9zZXJ2aWNlc2NhLmRlcjA4BggrBgEFBQcwAoYsaHR0cDovL2F1 +dG9yaXRlLmRoaW15b3Rpcy5jb20vc2VydmljZXNjYS5kZXIwLgYIKwYBBQUHMAGG +Imh0dHA6Ly9zZXJ2aWNlc2NhLm9jc3AuY2VydGlnbmEuZnIwMAYIKwYBBQUHMAGG +JGh0dHA6Ly9zZXJ2aWNlc2NhLm9jc3AuZGhpbXlvdGlzLmNvbTAdBgNVHQ4EFgQU +nWuFMLSW73ZLFxHfrbThfMgS5n0wHwYDVR0jBBgwFoAUrOyGj0s3HLh/FxsZ0K7o +TuM0XBIwMwYDVR0RBCwwKoIRdmlnaWNydWVzLmdvdXYuZnKCFXd3dy52aWdpY3J1 +ZXMuZ291di5mcjBUBgNVHSAETTBLMAgGBmeBDAECAjA/BgsqgXoBgTECBQEBATAw +MC4GCCsGAQUFBwIBFiJodHRwczovL3d3dy5jZXJ0aWduYS5mci9hdXRvcml0ZXMv +MIIB9AYKKwYBBAHWeQIEAgSCAeQEggHgAd4AdQBElGUusO7Or8RAB9io/ijA2uaC +vtjLMbU/0zOWtbaBqAAAAXR3L2RPAAAEAwBGMEQCIDtx/No4sejdX5EvrDXcs4MD +nqYZQymUgF6BEsDI8QBtAiAYOV8IqtH4+C2xMlaWWQSsu0+porprbou01OF7OaPM +QQB2AG9Tdqwx8DEZ2JkApFEV/3cVHBHZAsEAKQaNsgiaN9kTAAABdHcvZRMAAAQD +AEcwRQIgSxFpdehqbsWZavfyc9/qfRp3mZDwxgKWtYWvKWD3jCYCIQDzBkPoaS0R +5NwUKNy5M74Mki6NOJOX3oKpw/lRjKd5ywB1AFzcQ5L+5qtFRLFemtRW5hA3+9X6 +R9yhc5SyXub2xw7KAAABdHcvZ4MAAAQDAEYwRAIgfKfHw+kC5FHov2fo3Igs0Umu +FSmG65zKYv6C5idM+NoCIEddMJWt2e+K6ejFxILKGSzx2pyj5f8J0N0jBwjquQt0 +AHYA9lyUL9F3MCIUVBgIMJRWjuNNExkzv98MLyALzE7xZOMAAAF0dy9oWgAABAMA +RzBFAiEAimauUItb/FhM/KFNrF6pKpq3msq69vf8szYg6HDKq4cCIApAx7DUecse +TeUBpfeJa6618/Yx4HWlbEDjXyToS0LEMA0GCSqGSIb3DQEBCwUAA4ICAQAXkoT/ +QGyvyNx9MlSuVE+CqRYJd2h0UsQjU1tjNWIwT/grodh3M5eYMbPv+mG1Kg8a95lG +PazIJCxhXde77NQF5yXDEY6D7SFqs5s+Ah/oCxmkRNY+ctIrV++lyl24cVrLuMgc +QHxtIDveFEHH7KsqrhcHkzrTG2tWcxD3j9p+mZu1t4xuOT46WfONMOBv9I0O+jVV +2LpR5Qblf6Rvr9a0OopYCeTYT2q91A2XR7hUDxFXBVt+AB3eIOqbRUj0J+1xMjhJ +XhqiaJpGQDf0CvSVE+ej5nafV/+eCS11u9U20B9vBl8JTKGq7lr8jhL3IPOb/EQG +whh6/aYGT6bRh0L68FXRCclVDjFI7sTP0ZKcjGi1oCdCHP7d00xWg8z5fY4lEdkR +6TfpkqBoYF5pnsikoDxX/U52w2+KvU84aOhSf+LkGigDUu+4+FJw4q4hdfevgdVW +gM7vS4LzLTwnaXh+qGipuiWUUz6ublhM6iLn8ff2T82I6ggTHSvRswZfZciRrp4r +m63QWF1dPQdvRBIeuAp772gKnUATGNKDF5XhE8NFvvIiLDFjHsiTGSx8UUzrPRdd +0LXhFz8Dx4ciG4Y6sKnJbp4Lsu/is12n6eAzhId8YZSE0FGLSShKB27cWJ+VyIad +qkouOhMnN18+YFQsMZM6qISdOTz+jQlU+DOMBg== +-----END CERTIFICATE-----