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);
112 if (key.contains(FLOW)) {
113 flows.add(new QuantityType<>(value));
115 heights.add(new QuantityType<>(value));
119 referenceHeights = heights.stream().distinct().sorted().collect(Collectors.toList());
120 referenceFlows = flows.stream().distinct().sorted().collect(Collectors.toList());
121 portion = thing.getProperties().get(TRONCON);
124 private Map<String, String> discoverAttributes(StationConfiguration config) {
125 Map<String, String> properties = new HashMap<>();
127 ThingBuilder thingBuilder = editThing();
128 List<Channel> channels = new ArrayList<>(getThing().getChannels());
131 HubEauResponse stationDetails = apiHandler.discoverStations(config.id);
132 stationDetails.stations.stream().findFirst().ifPresent(station -> {
133 PointType stationLocation = new PointType(
134 String.format(Locale.US, "%f,%f", station.latitudeStation, station.longitudeStation));
135 properties.put(LOCATION, stationLocation.toString());
136 PointType serverLocation = locationProvider.getLocation();
137 if (serverLocation != null) {
138 DecimalType distance = serverLocation.distanceFrom(stationLocation);
139 properties.put(DISTANCE, new QuantityType<>(distance, SIUnits.METRE).toString());
141 properties.put(RIVER, station.libelleCoursEau);
143 } catch (VigiCruesException e) {
144 logger.info("Unable to retrieve station location details {} : {}", config.id, e.getMessage());
148 CdStationHydro refineStation = apiHandler.getStationDetails(config.id);
149 if (refineStation.vigilanceCrues.cruesHistoriques == null) {
150 throw new VigiCruesException("No historical data available");
152 refineStation.vigilanceCrues.cruesHistoriques.stream()
153 .forEach(crue -> properties.putAll(crue.getDescription()));
154 String codeTerritoire = refineStation.vigilanceCrues.pereBoitEntVigiCru.cdEntVigiCru;
155 TerEntVigiCru territoire = apiHandler.getTerritoire(codeTerritoire);
156 for (VicANMoinsUn troncon : territoire.vicTerEntVigiCru.vicANMoinsUn) {
157 TronEntVigiCru detail = apiHandler.getTroncon(troncon.vicCdEntVigiCru);
158 if (detail.getStations().anyMatch(s -> config.id.equalsIgnoreCase(s.vicCdEntVigiCru))) {
159 properties.put(TRONCON, troncon.vicCdEntVigiCru);
163 } catch (VigiCruesException e) {
164 logger.info("Historical flooding data are not available {} : {}", config.id, e.getMessage());
165 channels.removeIf(channel -> (channel.getUID().getId().contains(RELATIVE_PREFIX)));
168 if (!properties.containsKey(TRONCON)) {
169 channels.removeIf(channel -> (channel.getUID().getId().contains(ALERT)));
170 channels.removeIf(channel -> (channel.getUID().getId().contains(COMMENT)));
174 OpenDatasoftResponse measures = apiHandler.getMeasures(config.id);
175 measures.getFirstRecord().ifPresent(field -> {
176 if (field.getHeight().isEmpty()) {
177 channels.removeIf(channel -> (channel.getUID().getId().contains(HEIGHT)));
179 if (field.getFlow().isEmpty()) {
180 channels.removeIf(channel -> (channel.getUID().getId().contains(FLOW)));
183 } catch (VigiCruesException e) {
184 logger.warn("Unable to read measurements data {} : {}", config.id, e.getMessage());
187 thingBuilder.withChannels(channels);
188 updateThing(thingBuilder.build());
194 public void dispose() {
195 logger.debug("Disposing the VigiCrues handler.");
197 ScheduledFuture<?> refreshJob = this.refreshJob;
198 if (refreshJob != null) {
199 refreshJob.cancel(true);
201 this.refreshJob = null;
205 public void handleCommand(ChannelUID channelUID, Command command) {
206 if (command instanceof RefreshType) {
211 private void updateAndPublish() {
212 StationConfiguration config = getConfigAs(StationConfiguration.class);
214 OpenDatasoftResponse measures = apiHandler.getMeasures(config.id);
215 measures.getFirstRecord().ifPresent(field -> {
216 field.getHeight().ifPresent(height -> {
217 updateQuantity(HEIGHT, height, SIUnits.METRE);
218 updateRelativeMeasure(RELATIVE_HEIGHT, referenceHeights, height);
220 field.getFlow().ifPresent(flow -> {
221 updateQuantity(FLOW, flow, SmartHomeUnits.CUBICMETRE_PER_SECOND);
222 updateRelativeMeasure(RELATIVE_FLOW, referenceFlows, flow);
224 field.getTimestamp().ifPresent(date -> updateDate(OBSERVATION_TIME, date));
226 String currentPortion = this.portion;
227 if (currentPortion != null) {
228 InfoVigiCru status = apiHandler.getTronconStatus(currentPortion);
229 updateAlert(ALERT, status.vicInfoVigiCru.vicNivInfoVigiCru - 1);
230 updateString(SHORT_COMMENT, status.vicInfoVigiCru.vicSituActuInfoVigiCru);
231 updateString(COMMENT, status.vicInfoVigiCru.vicQualifInfoVigiCru);
233 updateStatus(ThingStatus.ONLINE);
234 } catch (VigiCruesException e) {
235 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
239 private void updateString(String channelId, String value) {
240 if (isLinked(channelId)) {
241 updateState(channelId, new StringType(value));
245 private void updateRelativeMeasure(String channelId, List<QuantityType<?>> reference, double value) {
246 if (reference.size() > 0) {
247 double percent = value / reference.get(0).doubleValue() * 100;
248 updateQuantity(channelId, percent, SmartHomeUnits.PERCENT);
252 private void updateQuantity(String channelId, Double value, Unit<?> unit) {
253 if (isLinked(channelId)) {
254 updateState(channelId, new QuantityType<>(value, unit));
258 public void updateDate(String channelId, ZonedDateTime zonedDateTime) {
259 if (isLinked(channelId)) {
260 updateState(channelId, new DateTimeType(zonedDateTime));
264 public void updateAlert(String channelId, int value) {
265 String channelIcon = channelId + "-icon";
266 if (isLinked(channelId)) {
267 updateState(channelId, new DecimalType(value));
269 if (isLinked(channelIcon)) {
270 byte[] resource = getResource(String.format("picto/crue-%d.svg", value));
271 updateState(channelIcon, resource != null ? new RawType(resource, "image/svg+xml") : UnDefType.UNDEF);
275 public byte @Nullable [] getResource(String iconPath) {
276 try (InputStream stream = VigiCruesHandler.class.getClassLoader().getResourceAsStream(iconPath)) {
277 return stream.readAllBytes();
278 } catch (IOException e) {
279 logger.warn("Unable to load ressource '{}' : {}", iconPath, e.getMessage());