2 * Copyright (c) 2010-2020 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.vigicrues.internal.handler;
15 import static org.openhab.binding.vigicrues.internal.VigiCruesBindingConstants.*;
17 import java.io.IOException;
18 import java.io.InputStream;
19 import java.time.ZonedDateTime;
20 import java.util.ArrayList;
21 import java.util.HashMap;
22 import java.util.List;
23 import java.util.Locale;
25 import java.util.concurrent.ScheduledFuture;
26 import java.util.concurrent.TimeUnit;
27 import java.util.stream.Collectors;
29 import javax.measure.Unit;
31 import org.eclipse.jdt.annotation.NonNullByDefault;
32 import org.eclipse.jdt.annotation.Nullable;
33 import org.openhab.binding.vigicrues.internal.StationConfiguration;
34 import org.openhab.binding.vigicrues.internal.api.ApiHandler;
35 import org.openhab.binding.vigicrues.internal.api.VigiCruesException;
36 import org.openhab.binding.vigicrues.internal.dto.hubeau.HubEauResponse;
37 import org.openhab.binding.vigicrues.internal.dto.opendatasoft.OpenDatasoftResponse;
38 import org.openhab.binding.vigicrues.internal.dto.vigicrues.CdStationHydro;
39 import org.openhab.binding.vigicrues.internal.dto.vigicrues.InfoVigiCru;
40 import org.openhab.binding.vigicrues.internal.dto.vigicrues.TerEntVigiCru;
41 import org.openhab.binding.vigicrues.internal.dto.vigicrues.TronEntVigiCru;
42 import org.openhab.binding.vigicrues.internal.dto.vigicrues.VicANMoinsUn;
43 import org.openhab.core.i18n.LocationProvider;
44 import org.openhab.core.library.types.DateTimeType;
45 import org.openhab.core.library.types.DecimalType;
46 import org.openhab.core.library.types.PointType;
47 import org.openhab.core.library.types.QuantityType;
48 import org.openhab.core.library.types.RawType;
49 import org.openhab.core.library.types.StringType;
50 import org.openhab.core.library.unit.SIUnits;
51 import org.openhab.core.library.unit.SmartHomeUnits;
52 import org.openhab.core.thing.Channel;
53 import org.openhab.core.thing.ChannelUID;
54 import org.openhab.core.thing.Thing;
55 import org.openhab.core.thing.ThingStatus;
56 import org.openhab.core.thing.ThingStatusDetail;
57 import org.openhab.core.thing.binding.BaseThingHandler;
58 import org.openhab.core.thing.binding.builder.ThingBuilder;
59 import org.openhab.core.types.Command;
60 import org.openhab.core.types.RefreshType;
61 import org.openhab.core.types.UnDefType;
62 import org.slf4j.Logger;
63 import org.slf4j.LoggerFactory;
66 * The {@link VigiCruesHandler} is responsible for querying the API and
69 * @author Gaƫl L'hopital - Initial contribution
72 public class VigiCruesHandler extends BaseThingHandler {
73 private final Logger logger = LoggerFactory.getLogger(VigiCruesHandler.class);
74 private final LocationProvider locationProvider;
76 private @Nullable ScheduledFuture<?> refreshJob;
77 private final ApiHandler apiHandler;
79 private List<QuantityType<?>> referenceHeights = new ArrayList<>();
80 private List<QuantityType<?>> referenceFlows = new ArrayList<>();
81 private @Nullable String portion;
83 public VigiCruesHandler(Thing thing, LocationProvider locationProvider, ApiHandler apiHandler) {
85 this.apiHandler = apiHandler;
86 this.locationProvider = locationProvider;
90 public void initialize() {
91 logger.debug("Initializing VigiCrues handler.");
93 StationConfiguration config = getConfigAs(StationConfiguration.class);
94 logger.debug("config refresh = {} min", config.refresh);
96 updateStatus(ThingStatus.UNKNOWN);
98 if (thing.getProperties().isEmpty()) {
99 Map<String, String> properties = discoverAttributes(config);
100 updateProperties(properties);
103 refreshJob = scheduler.scheduleWithFixedDelay(this::updateAndPublish, 0, config.refresh, TimeUnit.MINUTES);
106 private void getReferences() {
107 List<QuantityType<?>> heights = new ArrayList<>();
108 List<QuantityType<?>> flows = new ArrayList<>();
109 thing.getProperties().keySet().stream().filter(key -> key.startsWith(FLOOD)).forEach(key -> {
110 String value = thing.getProperties().get(key);
111 if (key.contains(FLOW)) {
112 flows.add(new QuantityType<>(value));
114 heights.add(new QuantityType<>(value));
117 referenceHeights = heights.stream().distinct().sorted().collect(Collectors.toList());
118 referenceFlows = flows.stream().distinct().sorted().collect(Collectors.toList());
119 portion = thing.getProperties().get(TRONCON);
122 private Map<String, String> discoverAttributes(StationConfiguration config) {
123 Map<String, String> properties = new HashMap<>();
125 ThingBuilder thingBuilder = editThing();
126 List<Channel> channels = new ArrayList<>(getThing().getChannels());
129 HubEauResponse stationDetails = apiHandler.discoverStations(config.id);
130 stationDetails.stations.stream().findFirst().ifPresent(station -> {
131 PointType stationLocation = new PointType(
132 String.format(Locale.US, "%f,%f", station.latitudeStation, station.longitudeStation));
133 properties.put(LOCATION, stationLocation.toString());
134 PointType serverLocation = locationProvider.getLocation();
135 if (serverLocation != null) {
136 DecimalType distance = serverLocation.distanceFrom(stationLocation);
137 properties.put(DISTANCE, new QuantityType<>(distance, SIUnits.METRE).toString());
139 properties.put(RIVER, station.libelleCoursEau);
141 } catch (VigiCruesException e) {
142 logger.info("Unable to retrieve station location details {} : {}", config.id, e.getMessage());
146 CdStationHydro refineStation = apiHandler.getStationDetails(config.id);
147 if (refineStation.vigilanceCrues.cruesHistoriques == null) {
148 throw new VigiCruesException("No historical data available");
150 refineStation.vigilanceCrues.cruesHistoriques.stream()
151 .forEach(crue -> properties.putAll(crue.getDescription()));
152 String codeTerritoire = refineStation.vigilanceCrues.pereBoitEntVigiCru.cdEntVigiCru;
153 TerEntVigiCru territoire = apiHandler.getTerritoire(codeTerritoire);
154 for (VicANMoinsUn troncon : territoire.vicTerEntVigiCru.vicANMoinsUn) {
155 TronEntVigiCru detail = apiHandler.getTroncon(troncon.vicCdEntVigiCru);
156 if (detail.getStations().anyMatch(s -> config.id.equalsIgnoreCase(s.vicCdEntVigiCru))) {
157 properties.put(TRONCON, troncon.vicCdEntVigiCru);
161 } catch (VigiCruesException e) {
162 logger.info("Historical flooding data are not available {} : {}", config.id, e.getMessage());
163 channels.removeIf(channel -> (channel.getUID().getId().contains(RELATIVE_PREFIX)));
166 if (!properties.containsKey(TRONCON)) {
167 channels.removeIf(channel -> (channel.getUID().getId().contains(ALERT)));
168 channels.removeIf(channel -> (channel.getUID().getId().contains(COMMENT)));
172 OpenDatasoftResponse measures = apiHandler.getMeasures(config.id);
173 measures.getFirstRecord().ifPresent(field -> {
174 if (field.getHeight().isEmpty()) {
175 channels.removeIf(channel -> (channel.getUID().getId().contains(HEIGHT)));
177 if (field.getFlow().isEmpty()) {
178 channels.removeIf(channel -> (channel.getUID().getId().contains(FLOW)));
181 } catch (VigiCruesException e) {
182 logger.warn("Unable to read measurements data {} : {}", config.id, e.getMessage());
185 thingBuilder.withChannels(channels);
186 updateThing(thingBuilder.build());
192 public void dispose() {
193 logger.debug("Disposing the VigiCrues handler.");
195 ScheduledFuture<?> refreshJob = this.refreshJob;
196 if (refreshJob != null) {
197 refreshJob.cancel(true);
199 this.refreshJob = null;
203 public void handleCommand(ChannelUID channelUID, Command command) {
204 if (command instanceof RefreshType) {
209 private void updateAndPublish() {
210 StationConfiguration config = getConfigAs(StationConfiguration.class);
212 OpenDatasoftResponse measures = apiHandler.getMeasures(config.id);
213 measures.getFirstRecord().ifPresent(field -> {
214 field.getHeight().ifPresent(height -> {
215 updateQuantity(HEIGHT, height, SIUnits.METRE);
216 updateRelativeMeasure(RELATIVE_HEIGHT, referenceHeights, height);
218 field.getFlow().ifPresent(flow -> {
219 updateQuantity(FLOW, flow, SmartHomeUnits.CUBICMETRE_PER_SECOND);
220 updateRelativeMeasure(RELATIVE_FLOW, referenceFlows, flow);
222 field.getTimestamp().ifPresent(date -> updateDate(OBSERVATION_TIME, date));
224 String currentPortion = this.portion;
225 if (currentPortion != null) {
226 InfoVigiCru status = apiHandler.getTronconStatus(currentPortion);
227 updateAlert(ALERT, status.vicInfoVigiCru.vicNivInfoVigiCru - 1);
228 updateString(SHORT_COMMENT, status.vicInfoVigiCru.vicSituActuInfoVigiCru);
229 updateString(COMMENT, status.vicInfoVigiCru.vicQualifInfoVigiCru);
231 updateStatus(ThingStatus.ONLINE);
232 } catch (VigiCruesException e) {
233 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
237 private void updateString(String channelId, String value) {
238 if (isLinked(channelId)) {
239 updateState(channelId, new StringType(value));
243 private void updateRelativeMeasure(String channelId, List<QuantityType<?>> reference, double value) {
244 if (reference.size() > 0) {
245 double percent = value / reference.get(0).doubleValue() * 100;
246 updateQuantity(channelId, percent, SmartHomeUnits.PERCENT);
250 private void updateQuantity(String channelId, Double value, Unit<?> unit) {
251 if (isLinked(channelId)) {
252 updateState(channelId, new QuantityType<>(value, unit));
256 public void updateDate(String channelId, ZonedDateTime zonedDateTime) {
257 if (isLinked(channelId)) {
258 updateState(channelId, new DateTimeType(zonedDateTime));
262 public void updateAlert(String channelId, int value) {
263 String channelIcon = channelId + "-icon";
264 if (isLinked(channelId)) {
265 updateState(channelId, new DecimalType(value));
267 if (isLinked(channelIcon)) {
268 byte[] resource = getResource(String.format("picto/crue-%d.svg", value));
269 updateState(channelIcon, resource != null ? new RawType(resource, "image/svg+xml") : UnDefType.UNDEF);
273 public byte @Nullable [] getResource(String iconPath) {
274 try (InputStream stream = VigiCruesHandler.class.getClassLoader().getResourceAsStream(iconPath)) {
275 return stream.readAllBytes();
276 } catch (IOException e) {
277 logger.warn("Unable to load ressource '{}' : {}", iconPath, e.getMessage());