[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 <gael@lhopital.org>
This commit is contained in:
Gaël L'hopital
2020-11-09 17:41:48 +01:00
committed by GitHub
parent 918408eac1
commit 3a4eaae1e9
26 changed files with 1129 additions and 85 deletions

View File

@@ -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;
}

View File

@@ -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<ThingTypeUID> 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<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.singleton(THING_TYPE_STATION);
}

View File

@@ -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<ZonedDateTime>) (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;
}
}

View File

@@ -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<ZonedDateTime>) (json, type, jsonDeserializationContext) -> ZonedDateTime
.parse(json.getAsJsonPrimitive().getAsString())
.withZoneSameInstant(timeZoneProvider.getTimeZone()))
.create();
}
private <T> T execute(String url, Class<T> 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);
}
}

View File

@@ -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");
}
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}

View File

@@ -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<String> 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<StationData> 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<Double> coordinates;
* }
*/
}

View File

@@ -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<Record> 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<VigiCruesFields> getFirstRecord() {
return records.stream().findFirst().flatMap(Record::getFields);
}
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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<String, String> getDescription() {
Map<String, String> 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<CruesHistorique> 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;
*/
}

View File

@@ -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;
}

View File

@@ -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> 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;
}

View File

@@ -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<VicStaEntVigiCru> 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<VicStaEntVigiCru> getStations() {
if (tronTerEntVigiCru != null && tronTerEntVigiCru.stations != null) {
return tronTerEntVigiCru.stations.stream();
} else {
return Stream.empty();
}
}
}

View File

@@ -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;
*/
}

View File

@@ -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<QuantityType<?>> referenceHeights = new ArrayList<>();
private List<QuantityType<?>> 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<String, String> properties = discoverAttributes(config);
updateProperties(properties);
}
getReferences();
refreshJob = scheduler.scheduleWithFixedDelay(this::updateAndPublish, 0, config.refresh, TimeUnit.MINUTES);
}
private void getReferences() {
List<QuantityType<?>> heights = new ArrayList<>();
List<QuantityType<?>> 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<String, String> discoverAttributes(StationConfiguration config) {
Map<String, String> properties = new HashMap<>();
ThingBuilder thingBuilder = editThing();
List<Channel> 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<QuantityType<?>> 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;
}
}

View File

@@ -12,10 +12,28 @@
<channels>
<channel id="height" typeId="height"/>
<channel id="relative-height" typeId="gauge">
<label>Relative Height</label>
<description>Current height toward historical floods.</description>
</channel>
<channel id="flow" typeId="flow"/>
<channel id="relative-flow" typeId="gauge">
<label>Relative Flow</label>
<description>Current flow toward historic floods.</description>
</channel>
<channel id="alert" typeId="alert-level"/>
<channel id="alert-icon" typeId="alert-icon"/>
<channel id="short-comment" typeId="comment">
<label>Info Qualification</label>
</channel>
<channel id="comment" typeId="comment">
<label>Situation</label>
</channel>
<channel id="observation-time" typeId="observation-time"/>
</channels>
<representation-property>id</representation-property>
<config-description>
<parameter name="id" type="text" required="true">
<label>Identifiant</label>
@@ -32,6 +50,7 @@
<channel-type id="flow">
<item-type>Number:VolumetricFlowRate</item-type>
<label>Current Flow</label>
<category>flow</category>
<state readOnly="true" pattern="%.2f %unit%"/>
</channel-type>
@@ -39,6 +58,7 @@
<item-type>DateTime</item-type>
<label>Observation Time</label>
<description>Observation date and time</description>
<category>time</category>
<state readOnly="true"/>
</channel-type>
@@ -48,4 +68,36 @@
<description>Water level in the river</description>
<state readOnly="true" pattern="%.2f %unit%"/>
</channel-type>
<channel-type id="gauge">
<item-type>Number:Dimensionless</item-type>
<label>Relative Measure</label>
<state readOnly="true" pattern="%.2f %unit%"/>
</channel-type>
<channel-type id="alert-level">
<item-type>Number</item-type>
<label>Alerte</label>
<state readOnly="true">
<options>
<option value="0">Vert</option>
<option value="1">Jaune</option>
<option value="2">Orange</option>
<option value="3">Rouge</option>
</options>
</state>
</channel-type>
<channel-type id="comment">
<item-type>String</item-type>
<label>Commentaire</label>
<state readOnly="true" pattern="%s"/>
</channel-type>
<channel-type id="alert-icon">
<item-type>Image</item-type>
<label>Pictogramme</label>
<description>Pictogramme associé au niveau d'alerte.</description>
<state readOnly="true"/>
</channel-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 78.271 84.645"><g id="indice-vert"><path d="M47.594 24.86c0 .233-.197.944-.59 2.123a480.704 480.704 0 0 1-2.656 7.733c-.432 1.22-.689 2.027-.77 2.421a89.557 89.557 0 0 1-1.414 4.134 133.507 133.507 0 0 0-1.533 4.367 82.549 82.549 0 0 1-1.42 4.311 678.77 678.77 0 0 0-1.533 4.307c-.316.869-.65 1.828-1.004 2.891a54.563 54.563 0 0 1-1.004 2.775 481.208 481.208 0 0 1-2.125 5.963 97.792 97.792 0 0 0-2.006 6.08c-.475 1.42-.943 2.5-1.418 3.246-.473.75-1.004 1.277-1.594 1.596-.592.313-1.281.49-2.064.531-.791.039-1.656.057-2.602.057-1.258 0-2.301-.039-3.125-.117-.828-.08-1.535-.371-2.127-.885-.59-.512-1.141-1.279-1.654-2.303-.51-1.021-1.043-2.479-1.596-4.369a710.439 710.439 0 0 0-7.375-21.898A1676.56 1676.56 0 0 1 .369 25.92a1.32 1.32 0 0 1-.119-.589v-.471c0-.787.193-1.379.592-1.773a2.828 2.828 0 0 1 1.416-.77 8.273 8.273 0 0 1 1.713-.175H7.69a4.96 4.96 0 0 1 2.008.415c.627.274 1.197.747 1.709 1.415.514.668.924 1.636 1.24 2.892.158.554.414 1.379.768 2.479.356 1.105.648 2.087.885 2.952a114.139 114.139 0 0 1 1.773 5.611 410.58 410.58 0 0 0 1.887 6.312c.315.871.631 1.873.947 3.014.31 1.141.666 2.264 1.06 3.365.078.234.217.725.412 1.477.197.746.473 1.514.826 2.301.356.785.75 1.494 1.182 2.121.432.633.967.947 1.596.947.551 0 1.059-.314 1.533-.947.473-.627.885-1.314 1.24-2.064.353-.744.629-1.455.826-2.123.195-.666.297-1.041.297-1.123.234-.863.51-1.828.824-2.891.313-1.063.592-2.025.826-2.893a71.07 71.07 0 0 0 .828-2.361c.313-.946.59-1.85.824-2.716.633-1.811 1.262-3.7 1.893-5.666a170.943 170.943 0 0 0 1.889-6.378c.391-1.493.783-2.654 1.178-3.483.395-.825.865-1.433 1.416-1.829.553-.393 1.223-.628 2.008-.706a29.256 29.256 0 0 1 2.832-.119h1.479c.59 0 1.16.077 1.711.235a3.584 3.584 0 0 1 1.418.768c.391.355.589.926.589 1.713z" fill="#28d761" stroke="#504d49" stroke-width=".5"/><path d="M78.271 77.531c0 3.926-9.189 7.113-20.523 7.113-11.336 0-20.523-3.188-20.523-7.113 0-3.928 9.187-7.109 20.523-7.109 11.334 0 20.523 3.182 20.523 7.109z" fill="#7c9fbb"/><ellipse cx="62.657" cy="78.139" rx="9.595" ry="2.639" fill="#63839b"/><path d="M62.656 16.214c0 .048-.053.154-.15.313a.903.903 0 0 1-.459.354c-.545.246-1.17.382-1.885.407-.715.025-1.445.036-2.189.036-.547 0-1.121-.007-1.732-.018a8.751 8.751 0 0 1-1.732-.203 1.903 1.903 0 0 1-.865-.425c-.238-.212-.355-.366-.355-.464V1.072c0-.05.049-.148.152-.293.102-.15.254-.276.457-.372.543-.248 1.189-.375 1.934-.388C56.578.004 57.293 0 57.973 0c.473 0 1.033.004 1.68.019.645.013 1.273.079 1.883.203.342.072.613.208.816.407.207.198.305.345.305.443v15.142zM62.656 36.682c0 .051-.053.157-.15.316a.89.89 0 0 1-.459.35c-.545.249-1.17.385-1.885.41-.715.022-1.445.035-2.189.035-.547 0-1.121-.005-1.732-.017a8.679 8.679 0 0 1-1.732-.202 1.869 1.869 0 0 1-.865-.428c-.238-.21-.355-.364-.355-.464v-15.14c0-.049.049-.147.152-.295a1.16 1.16 0 0 1 .457-.371c.543-.246 1.189-.375 1.934-.389a102.378 102.378 0 0 1 3.823 0c.645.014 1.273.08 1.883.205.342.073.613.209.816.407.207.195.305.346.305.443v15.14zM62.656 57.152c0 .047-.053.154-.15.314a.905.905 0 0 1-.459.352c-.545.246-1.17.383-1.885.408a74.944 74.944 0 0 1-3.921.017 8.603 8.603 0 0 1-1.732-.205 1.89 1.89 0 0 1-.865-.422c-.238-.213-.355-.367-.355-.465V42.013c0-.05.049-.15.152-.296.102-.15.254-.273.457-.373.543-.247 1.189-.376 1.934-.39.748-.012 1.463-.018 2.143-.018.473 0 1.033.006 1.68.018.645.014 1.273.082 1.883.205.342.072.613.208.816.407.207.198.305.345.305.446v15.14z" fill="#504d49"/><path d="M62.656 77.389c0 .051-.053.156-.15.314a.897.897 0 0 1-.459.357c-.545.244-1.17.379-1.885.404a67.95 67.95 0 0 1-3.921.019 8.751 8.751 0 0 1-1.732-.203 1.878 1.878 0 0 1-.865-.428c-.238-.211-.355-.363-.355-.465V62.252c0-.049.049-.148.152-.297.102-.15.254-.273.457-.371.543-.246 1.189-.375 1.934-.389.748-.012 1.463-.018 2.143-.018.473 0 1.033.006 1.68.018.645.014 1.273.08 1.883.205.342.072.613.209.816.404.207.199.305.348.305.447v15.138z" fill="#28d761" stroke="#504d49" stroke-width=".5"/></g></svg>

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 78.271 84.645"><g id="indice-jaune"><path d="M47.594 24.86c0 .233-.197.944-.59 2.123a480.704 480.704 0 0 1-2.656 7.733c-.432 1.22-.689 2.027-.77 2.421a89.557 89.557 0 0 1-1.414 4.134 133.507 133.507 0 0 0-1.533 4.367 81.616 81.616 0 0 1-1.42 4.311 831.628 831.628 0 0 0-1.533 4.307c-.316.869-.649 1.828-1.004 2.891s-.692 1.992-1.004 2.775a481.208 481.208 0 0 1-2.125 5.963 98.462 98.462 0 0 0-2.006 6.08c-.475 1.42-.943 2.5-1.418 3.246-.473.75-1.004 1.277-1.594 1.596-.592.313-1.281.49-2.064.531-.791.039-1.656.057-2.602.057-1.258 0-2.301-.039-3.125-.117-.828-.08-1.535-.371-2.127-.885-.59-.512-1.141-1.279-1.653-2.303-.511-1.021-1.044-2.479-1.596-4.369a707.767 707.767 0 0 0-7.376-21.898A1691.504 1691.504 0 0 1 .369 25.92a1.32 1.32 0 0 1-.119-.589v-.471c0-.787.194-1.379.592-1.773a2.828 2.828 0 0 1 1.416-.77 8.273 8.273 0 0 1 1.713-.175H7.69a4.96 4.96 0 0 1 2.008.415c.627.274 1.197.747 1.71 1.415.513.668.923 1.636 1.239 2.892.158.554.414 1.379.769 2.479a72.92 72.92 0 0 1 .884 2.952 114.043 114.043 0 0 1 1.774 5.611c.63 2.164 1.258 4.271 1.888 6.312.313.871.63 1.873.946 3.014a45.059 45.059 0 0 0 1.062 3.365c.077.234.216.725.412 1.477.196.746.472 1.514.826 2.301.354.785.75 1.494 1.182 2.121.431.633.966.947 1.596.947.551 0 1.059-.314 1.533-.947.472-.627.884-1.314 1.239-2.064.354-.744.629-1.455.826-2.123.196-.666.297-1.041.297-1.123.234-.863.51-1.828.824-2.891.313-1.063.592-2.025.826-2.893.237-.629.513-1.416.829-2.361.311-.946.589-1.85.824-2.716a242.12 242.12 0 0 0 1.893-5.666 172.94 172.94 0 0 0 1.888-6.378c.391-1.493.783-2.654 1.179-3.483.394-.825.865-1.433 1.416-1.829.552-.393 1.222-.628 2.008-.706a29.22 29.22 0 0 1 2.831-.119h1.479c.59 0 1.16.077 1.711.235a3.577 3.577 0 0 1 1.418.768c.389.355.587.926.587 1.713z" fill="#ff0" stroke="#504d49" stroke-width=".5"/><path d="M78.271 77.531c0 3.926-9.189 7.113-20.523 7.113-11.336 0-20.523-3.188-20.523-7.113 0-3.928 9.188-7.109 20.523-7.109 11.334 0 20.523 3.182 20.523 7.109z" fill="#7c9fbb"/><ellipse cx="62.657" cy="78.139" rx="9.595" ry="2.639" fill="#63839b"/><path d="M62.656 16.214c0 .048-.053.154-.15.313a.903.903 0 0 1-.459.354c-.545.246-1.17.382-1.885.407-.715.025-1.445.036-2.19.036-.547 0-1.121-.007-1.732-.018a8.751 8.751 0 0 1-1.732-.203 1.903 1.903 0 0 1-.865-.425c-.238-.212-.355-.366-.355-.464V1.072c0-.05.049-.148.152-.293.102-.15.254-.276.457-.372.543-.248 1.189-.375 1.934-.388C56.578.004 57.293 0 57.973 0c.473 0 1.033.004 1.68.019.645.013 1.273.079 1.883.203.342.072.613.208.816.407.207.198.305.345.305.443v15.142zM62.656 36.682c0 .051-.053.157-.15.316a.89.89 0 0 1-.459.35c-.545.249-1.17.385-1.885.41-.715.022-1.445.035-2.19.035-.547 0-1.121-.005-1.732-.017a8.679 8.679 0 0 1-1.732-.202 1.869 1.869 0 0 1-.865-.428c-.238-.21-.355-.364-.355-.464v-15.14c0-.049.049-.147.152-.295a1.16 1.16 0 0 1 .457-.371c.543-.246 1.189-.375 1.934-.389a102.378 102.378 0 0 1 3.822 0c.645.014 1.273.08 1.883.205.342.073.613.209.816.407.207.195.305.346.305.443v15.14z" fill="#504d49"/><path d="M62.656 57.152c0 .047-.053.154-.15.314a.905.905 0 0 1-.459.352c-.545.246-1.17.383-1.885.408a74.944 74.944 0 0 1-3.922.017 8.603 8.603 0 0 1-1.732-.205 1.89 1.89 0 0 1-.865-.422c-.238-.213-.355-.367-.355-.465V42.013c0-.05.049-.15.152-.296.102-.15.254-.273.457-.373.543-.247 1.189-.376 1.934-.39.748-.012 1.463-.018 2.142-.018.473 0 1.033.006 1.68.018.645.014 1.273.082 1.883.205.342.072.613.208.816.407.207.198.305.345.305.446v15.14z" fill="#ff0" stroke="#504d49" stroke-width=".5"/><path d="M62.656 77.389c0 .051-.053.156-.15.314a.897.897 0 0 1-.459.357c-.545.244-1.17.379-1.885.404a67.96 67.96 0 0 1-3.922.019 8.751 8.751 0 0 1-1.732-.203 1.878 1.878 0 0 1-.865-.428c-.238-.211-.355-.363-.355-.465V62.252c0-.049.049-.148.152-.297.102-.15.254-.273.457-.371.543-.246 1.189-.375 1.934-.389.748-.012 1.463-.018 2.142-.018.473 0 1.033.006 1.68.018.645.014 1.273.08 1.883.205.342.072.613.209.816.404.207.199.305.348.305.447v15.138z" fill="#504d49"/></g></svg>

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 78.272 84.645"><g id="indice-orange"><path d="M47.594 24.86c0 .233-.197.944-.59 2.123a480.704 480.704 0 0 1-2.656 7.733c-.432 1.22-.69 2.027-.77 2.421a88.378 88.378 0 0 1-1.414 4.134 133.507 133.507 0 0 0-1.533 4.367 82.222 82.222 0 0 1-1.42 4.31 747.465 747.465 0 0 0-1.533 4.307c-.317.869-.65 1.828-1.004 2.891a51.805 51.805 0 0 1-1.004 2.775 465.383 465.383 0 0 1-2.125 5.963 97.53 97.53 0 0 0-2.005 6.08c-.475 1.42-.944 2.5-1.418 3.246-.472.75-1.003 1.277-1.593 1.596-.592.313-1.281.49-2.065.531-.791.039-1.656.057-2.602.057-1.258 0-2.3-.039-3.124-.117-.829-.08-1.536-.371-2.127-.885-.589-.512-1.141-1.279-1.653-2.303-.51-1.021-1.045-2.479-1.596-4.369a706.87 706.87 0 0 0-7.376-21.898A1642.262 1642.262 0 0 1 .37 25.92a1.306 1.306 0 0 1-.12-.589v-.471c0-.787.194-1.379.592-1.773a2.828 2.828 0 0 1 1.416-.77 8.266 8.266 0 0 1 1.713-.175h3.718c.707 0 1.377.139 2.008.414.627.274 1.198.747 1.71 1.415.513.668.923 1.636 1.239 2.892.158.554.415 1.379.769 2.479.354 1.104.647 2.087.884 2.952a113.068 113.068 0 0 1 1.773 5.611 413.78 413.78 0 0 0 1.888 6.312c.314.871.63 1.873.946 3.014a45.059 45.059 0 0 0 1.062 3.365c.077.234.216.725.412 1.476.196.746.472 1.514.826 2.301.355.785.75 1.494 1.182 2.121.431.633.965.947 1.596.947.551 0 1.059-.314 1.533-.947.472-.627.884-1.314 1.239-2.064.354-.744.63-1.455.826-2.123.197-.666.297-1.041.297-1.123.235-.863.51-1.828.824-2.891.314-1.063.592-2.025.827-2.893.237-.629.513-1.416.829-2.361.312-.946.589-1.85.824-2.716.632-1.811 1.26-3.7 1.893-5.666a172.94 172.94 0 0 0 1.888-6.378c.39-1.493.783-2.654 1.178-3.483.393-.825.865-1.433 1.416-1.829.551-.393 1.222-.628 2.008-.707a29.256 29.256 0 0 1 2.832-.119h1.479c.59 0 1.16.077 1.711.235a3.574 3.574 0 0 1 1.418.769c.391.357.588.928.588 1.715z" fill="#fa0" stroke="#504d49" stroke-width=".5"/><path d="M78.272 77.531c0 3.926-9.189 7.113-20.523 7.113-11.336 0-20.524-3.188-20.524-7.113 0-3.928 9.188-7.109 20.524-7.109 11.333 0 20.523 3.182 20.523 7.109z" fill="#7c9fbb"/><ellipse cx="62.658" cy="78.139" rx="9.595" ry="2.639" fill="#63839b"/><path d="M62.657 16.214c0 .048-.053.154-.15.313a.903.903 0 0 1-.459.354c-.545.246-1.17.382-1.885.407-.715.025-1.445.036-2.189.036a96.9 96.9 0 0 1-1.733-.018 8.76 8.76 0 0 1-1.732-.203 1.903 1.903 0 0 1-.865-.425c-.238-.212-.355-.366-.355-.464V1.072c0-.05.049-.148.152-.293.102-.15.254-.276.457-.372.543-.248 1.189-.375 1.934-.388C56.579.004 57.293 0 57.973 0c.473 0 1.033.004 1.68.019.645.013 1.274.079 1.883.203.342.072.613.208.816.407.207.198.305.345.305.443v15.142z" fill="#504d49"/><path d="M62.657 36.682c0 .051-.053.157-.15.316a.89.89 0 0 1-.459.35c-.545.249-1.17.385-1.885.41-.715.023-1.445.035-2.189.035-.547 0-1.121-.005-1.733-.017a8.687 8.687 0 0 1-1.732-.202 1.869 1.869 0 0 1-.865-.428c-.238-.21-.355-.364-.355-.464v-15.14c0-.049.049-.147.152-.295a1.15 1.15 0 0 1 .457-.371c.543-.246 1.189-.375 1.934-.389a102.378 102.378 0 0 1 3.823 0c.645.014 1.274.08 1.883.205.342.073.613.209.816.407.207.195.305.346.305.443v15.14z" fill="#fa0" stroke="#504d49" stroke-width=".5"/><path d="M62.657 57.152c0 .047-.053.154-.15.314a.905.905 0 0 1-.459.352c-.545.246-1.17.383-1.885.408a75.085 75.085 0 0 1-3.922.017 8.611 8.611 0 0 1-1.732-.205 1.89 1.89 0 0 1-.865-.422c-.238-.213-.355-.367-.355-.465V42.013c0-.05.049-.15.152-.296.102-.15.254-.273.457-.373.543-.247 1.189-.376 1.934-.39.748-.012 1.463-.018 2.143-.018.473 0 1.033.006 1.68.018.645.014 1.274.082 1.883.205.342.072.613.208.816.407.207.198.305.345.305.446v15.14zM62.657 77.389c0 .051-.053.156-.15.314a.897.897 0 0 1-.459.357c-.545.244-1.17.379-1.885.404a65.179 65.179 0 0 1-3.922.019 8.76 8.76 0 0 1-1.732-.203 1.878 1.878 0 0 1-.865-.428c-.238-.211-.355-.363-.355-.465V62.252c0-.049.049-.148.152-.297.102-.15.254-.273.457-.371.543-.246 1.189-.375 1.934-.389.748-.012 1.463-.018 2.143-.018.473 0 1.033.006 1.68.018.645.014 1.274.08 1.883.205.342.072.613.209.816.404.207.199.305.348.305.447v15.138z" fill="#504d49"/></g></svg>

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 78.272 84.895"><g id="indice-rouge"><path d="M47.594 25.11c0 .233-.196.944-.589 2.123a576.842 576.842 0 0 1-1.299 3.838 365.141 365.141 0 0 1-1.358 3.895c-.432 1.22-.689 2.027-.77 2.421a90.678 90.678 0 0 1-1.413 4.134 131.995 131.995 0 0 0-1.533 4.367 82.954 82.954 0 0 1-1.421 4.312 747.465 747.465 0 0 0-1.533 4.307c-.316.869-.649 1.828-1.004 2.891a52.449 52.449 0 0 1-1.004 2.775 476.542 476.542 0 0 1-2.125 5.963 97.53 97.53 0 0 0-2.005 6.08c-.475 1.419-.944 2.499-1.418 3.246-.472.75-1.003 1.277-1.593 1.596-.592.313-1.281.489-2.065.53-.79.04-1.656.058-2.602.058-1.258 0-2.3-.039-3.124-.117-.829-.08-1.536-.372-2.127-.885-.589-.513-1.141-1.279-1.653-2.303-.51-1.022-1.044-2.479-1.596-4.369a706.134 706.134 0 0 0-7.377-21.9A1671.14 1671.14 0 0 1 .371 26.171a1.303 1.303 0 0 1-.12-.589v-.471c0-.787.194-1.379.592-1.773a2.828 2.828 0 0 1 1.416-.77 8.266 8.266 0 0 1 1.713-.175H7.69a4.96 4.96 0 0 1 2.008.415c.627.274 1.198.747 1.71 1.415.513.668.923 1.636 1.239 2.892.158.554.415 1.379.769 2.479.354 1.104.647 2.087.884 2.952a113.068 113.068 0 0 1 1.773 5.611 413.96 413.96 0 0 0 1.888 6.313c.314.87.63 1.873.946 3.014a44.882 44.882 0 0 0 1.061 3.364c.077.235.216.726.412 1.478.196.746.472 1.513.826 2.3.355.786.75 1.495 1.182 2.122.431.633.965.947 1.596.947.551 0 1.059-.314 1.533-.947.472-.627.884-1.314 1.239-2.064.354-.745.63-1.455.826-2.124.197-.666.297-1.041.297-1.122.235-.864.51-1.828.824-2.891.314-1.062.592-2.025.827-2.893.237-.63.513-1.416.829-2.362.312-.946.589-1.85.824-2.716.632-1.811 1.26-3.7 1.893-5.666a172.94 172.94 0 0 0 1.888-6.378c.39-1.493.783-2.654 1.178-3.483.393-.825.865-1.433 1.416-1.829.551-.393 1.222-.628 2.008-.707a29.242 29.242 0 0 1 2.832-.119h1.479c.589 0 1.159.077 1.71.235a3.59 3.59 0 0 1 1.419.769c.391.354.588.925.588 1.712z" fill="red" stroke="#504d49" stroke-width=".5"/><path d="M78.272 77.782c0 3.926-9.19 7.113-20.524 7.113-11.335 0-20.523-3.188-20.523-7.113 0-3.928 9.188-7.109 20.523-7.109 11.334-.001 20.524 3.181 20.524 7.109z" fill="#7c9fbb"/><ellipse cx="62.658" cy="78.389" rx="9.594" ry="2.639" fill="#63839b"/><path d="M62.656 16.464c0 .048-.053.154-.15.313a.896.896 0 0 1-.458.354c-.546.246-1.171.382-1.886.407-.714.025-1.444.036-2.19.036-.547 0-1.121-.007-1.732-.018a8.76 8.76 0 0 1-1.732-.203 1.9 1.9 0 0 1-.864-.425c-.239-.212-.356-.366-.356-.464V1.322c0-.05.05-.148.152-.293.102-.15.254-.276.458-.372.542-.248 1.188-.375 1.933-.388.748-.015 1.463-.019 2.143-.019.473 0 1.033.004 1.68.019.644.013 1.274.079 1.883.203.343.072.613.208.816.407.207.198.305.345.305.443v15.142z" fill="red" stroke="#504d49" stroke-width=".5"/><path d="M62.656 36.932c0 .051-.053.157-.15.316a.884.884 0 0 1-.458.35c-.546.249-1.171.385-1.886.41a71.04 71.04 0 0 1-2.19.035c-.547 0-1.121-.005-1.732-.017a8.687 8.687 0 0 1-1.732-.202 1.865 1.865 0 0 1-.864-.428c-.239-.21-.356-.364-.356-.464v-15.14c0-.049.05-.147.152-.295s.254-.271.458-.371c.542-.246 1.188-.375 1.933-.389a102.378 102.378 0 0 1 3.823 0c.644.014 1.274.08 1.883.205.343.073.613.209.816.407.207.195.305.346.305.443v15.14zM62.656 57.403c0 .047-.053.154-.15.313a.894.894 0 0 1-.458.353c-.546.246-1.171.383-1.886.408a71.04 71.04 0 0 1-3.922.017 8.611 8.611 0 0 1-1.732-.205 1.886 1.886 0 0 1-.864-.422c-.239-.213-.356-.367-.356-.465v-15.14c0-.05.05-.15.152-.296.102-.15.254-.273.458-.373.542-.247 1.188-.376 1.933-.39.748-.012 1.463-.018 2.143-.018.473 0 1.033.006 1.68.018.644.014 1.274.082 1.883.205.343.072.613.208.816.407.207.198.305.345.305.446v15.142zM62.656 77.639c0 .051-.053.156-.15.314a.9.9 0 0 1-.458.357c-.546.244-1.171.378-1.886.404a67.857 67.857 0 0 1-3.922.019 8.76 8.76 0 0 1-1.732-.203 1.884 1.884 0 0 1-.864-.428c-.239-.211-.356-.363-.356-.465V62.502c0-.05.05-.148.152-.297.102-.15.254-.273.458-.371.542-.246 1.188-.375 1.933-.389a93.232 93.232 0 0 1 3.823 0c.644.014 1.274.08 1.883.204.343.072.613.209.816.405.207.199.305.348.305.447v15.138z" fill="#504d49"/></g></svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@@ -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-----