[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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 1129 additions and 85 deletions

View File

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

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