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.sagercaster.internal.handler;
15 import static org.openhab.binding.sagercaster.internal.SagerCasterBindingConstants.*;
16 import static org.openhab.core.library.unit.MetricPrefix.HECTO;
18 import java.math.BigDecimal;
19 import java.util.ArrayList;
20 import java.util.Arrays;
21 import java.util.Collections;
22 import java.util.HashSet;
23 import java.util.List;
24 import java.util.Optional;
26 import java.util.concurrent.TimeUnit;
28 import javax.measure.quantity.Angle;
29 import javax.measure.quantity.Dimensionless;
30 import javax.measure.quantity.Pressure;
31 import javax.measure.quantity.Temperature;
33 import org.eclipse.jdt.annotation.NonNullByDefault;
34 import org.openhab.binding.sagercaster.internal.SagerWeatherCaster;
35 import org.openhab.binding.sagercaster.internal.WindDirectionStateDescriptionProvider;
36 import org.openhab.core.library.types.DecimalType;
37 import org.openhab.core.library.types.OnOffType;
38 import org.openhab.core.library.types.QuantityType;
39 import org.openhab.core.library.types.StringType;
40 import org.openhab.core.library.unit.SIUnits;
41 import org.openhab.core.thing.ChannelUID;
42 import org.openhab.core.thing.Thing;
43 import org.openhab.core.thing.ThingStatus;
44 import org.openhab.core.thing.binding.BaseThingHandler;
45 import org.openhab.core.types.Command;
46 import org.openhab.core.types.RefreshType;
47 import org.openhab.core.types.StateOption;
48 import org.slf4j.Logger;
49 import org.slf4j.LoggerFactory;
52 * The {@link SagerCasterHandler} is responsible for handling commands, which are
53 * sent to one of the channels.
55 * @author Gaƫl L'hopital - Initial contribution
58 public class SagerCasterHandler extends BaseThingHandler {
59 private final static String FORECAST_PENDING = "0";
60 private final static Set<String> SHOWERS = Collections
61 .unmodifiableSet(new HashSet<>(Arrays.asList("G", "K", "L", "R", "S", "T", "U", "W")));
63 private final Logger logger = LoggerFactory.getLogger(SagerCasterHandler.class);
64 private final SagerWeatherCaster sagerWeatherCaster;
65 private final WindDirectionStateDescriptionProvider stateDescriptionProvider;
66 private int currentTemp = 0;
68 private final ExpiringMap<QuantityType<Pressure>> pressureCache = new ExpiringMap<>();
69 private final ExpiringMap<QuantityType<Temperature>> temperatureCache = new ExpiringMap<>();
70 private final ExpiringMap<QuantityType<Angle>> bearingCache = new ExpiringMap<>();
72 public SagerCasterHandler(Thing thing, WindDirectionStateDescriptionProvider stateDescriptionProvider,
73 SagerWeatherCaster sagerWeatherCaster) {
75 this.stateDescriptionProvider = stateDescriptionProvider;
76 this.sagerWeatherCaster = sagerWeatherCaster;
80 public void initialize() {
81 String location = (String) getConfig().get(CONFIG_LOCATION);
82 int observationPeriod = ((BigDecimal) getConfig().get(CONFIG_PERIOD)).intValue();
83 String latitude = location.split(",")[0];
84 sagerWeatherCaster.setLatitude(Double.parseDouble(latitude));
85 long period = TimeUnit.SECONDS.toMillis(observationPeriod);
86 pressureCache.setObservationPeriod(period);
87 bearingCache.setObservationPeriod(period);
88 temperatureCache.setObservationPeriod(period);
89 defineWindDirectionStateDescriptions();
90 updateStatus(ThingStatus.ONLINE);
93 private void defineWindDirectionStateDescriptions() {
94 List<StateOption> options = new ArrayList<>();
95 String[] directions = sagerWeatherCaster.getUsedDirections();
96 for (int i = 0; i < directions.length; i++) {
97 int secondDirection = i < directions.length - 1 ? i + 1 : 0;
98 String windDescription = directions[i] + " or " + directions[secondDirection] + " winds";
99 options.add(new StateOption(String.valueOf(i + 1), windDescription));
102 options.add(new StateOption("9", "Shifting / Variable winds"));
103 stateDescriptionProvider.setStateOptions(new ChannelUID(getThing().getUID(), GROUP_OUTPUT, CHANNEL_WINDFROM),
105 stateDescriptionProvider.setStateOptions(new ChannelUID(getThing().getUID(), GROUP_OUTPUT, CHANNEL_WINDTO),
110 public void handleCommand(ChannelUID channelUID, Command command) {
111 if (command instanceof RefreshType) {
114 String id = channelUID.getIdWithoutGroup();
116 case CHANNEL_CLOUDINESS:
117 logger.debug("Octa cloud level changed, updating forecast");
118 if (command instanceof QuantityType) {
119 @SuppressWarnings("unchecked")
120 QuantityType<Dimensionless> cloudiness = (QuantityType<Dimensionless>) command;
121 scheduler.submit(() -> {
122 sagerWeatherCaster.setCloudLevel(cloudiness.intValue());
127 case CHANNEL_IS_RAINING:
128 logger.debug("Rain status updated, updating forecast");
129 if (command instanceof OnOffType) {
130 OnOffType isRaining = ((OnOffType) command);
131 scheduler.submit(() -> {
132 sagerWeatherCaster.setRaining(isRaining == OnOffType.ON);
136 logger.debug("Channel '{}' can only accept Switch type commands.", channelUID);
139 case CHANNEL_RAIN_QTTY:
140 logger.debug("Rain status updated, updating forecast");
141 if (command instanceof QuantityType) {
142 QuantityType<?> newQtty = ((QuantityType<?>) command);
143 scheduler.submit(() -> {
144 sagerWeatherCaster.setRaining(newQtty.doubleValue() > 0);
147 } else if (command instanceof DecimalType) {
148 DecimalType newQtty = ((DecimalType) command);
149 scheduler.submit(() -> {
150 sagerWeatherCaster.setRaining(newQtty.doubleValue() > 0);
154 logger.debug("Channel '{}' can accept Number, Number:Speed, Number:Length type commands.",
158 case CHANNEL_WIND_SPEED:
159 logger.debug("Updated wind speed, updating forecast");
160 if (command instanceof DecimalType) {
161 DecimalType newValue = (DecimalType) command;
162 scheduler.submit(() -> {
163 sagerWeatherCaster.setBeaufort(newValue.intValue());
167 logger.debug("Channel '{}' only accepts DecimalType commands.", channelUID);
170 case CHANNEL_PRESSURE:
171 logger.debug("Sea-level pressure updated, updating forecast");
172 if (command instanceof QuantityType) {
173 @SuppressWarnings("unchecked")
174 QuantityType<Pressure> newPressure = ((QuantityType<Pressure>) command)
175 .toUnit(HECTO(SIUnits.PASCAL));
176 if (newPressure != null) {
177 pressureCache.put(newPressure);
178 Optional<QuantityType<Pressure>> agedPressure = pressureCache.getAgedValue();
179 if (agedPressure.isPresent()) {
180 scheduler.submit(() -> {
181 sagerWeatherCaster.setPressure(newPressure.doubleValue(),
182 agedPressure.get().doubleValue());
183 updateChannelString(GROUP_OUTPUT, CHANNEL_PRESSURETREND,
184 String.valueOf(sagerWeatherCaster.getPressureEvolution()));
188 updateChannelString(GROUP_OUTPUT, CHANNEL_FORECAST, FORECAST_PENDING);
193 case CHANNEL_TEMPERATURE:
194 logger.debug("Temperature updated");
195 if (command instanceof QuantityType) {
196 @SuppressWarnings("unchecked")
197 QuantityType<Temperature> newTemperature = ((QuantityType<Temperature>) command)
198 .toUnit(SIUnits.CELSIUS);
199 if (newTemperature != null) {
200 temperatureCache.put(newTemperature);
201 currentTemp = newTemperature.intValue();
202 Optional<QuantityType<Temperature>> agedTemperature = temperatureCache.getAgedValue();
203 if (agedTemperature.isPresent()) {
204 double delta = newTemperature.doubleValue() - agedTemperature.get().doubleValue();
205 String trend = (delta > 3) ? "1"
206 : (delta > 0.3) ? "2" : (delta > -0.3) ? "3" : (delta > -3) ? "4" : "5";
207 updateChannelString(GROUP_OUTPUT, CHANNEL_TEMPERATURETREND, trend);
212 case CHANNEL_WIND_ANGLE:
213 logger.debug("Updated wind direction, updating forecast");
214 if (command instanceof QuantityType) {
215 @SuppressWarnings("unchecked")
216 QuantityType<Angle> newAngle = (QuantityType<Angle>) command;
217 bearingCache.put(newAngle);
218 Optional<QuantityType<Angle>> agedAngle = bearingCache.getAgedValue();
219 if (agedAngle.isPresent()) {
220 scheduler.submit(() -> {
221 sagerWeatherCaster.setBearing(newAngle.intValue(), agedAngle.get().intValue());
222 updateChannelString(GROUP_OUTPUT, CHANNEL_WINDEVOLUTION,
223 String.valueOf(sagerWeatherCaster.getWindEvolution()));
230 logger.debug("The binding can not handle command: {} on channel: {}", command, channelUID);
235 private void postNewForecast() {
236 String forecast = sagerWeatherCaster.getForecast();
237 // Sharpens forecast if current temp is below 2 degrees, likely to be flurries rather than shower
238 forecast += SHOWERS.contains(forecast) ? (currentTemp > 2) ? "1" : "2" : "";
240 updateChannelString(GROUP_OUTPUT, CHANNEL_FORECAST, forecast);
241 updateChannelString(GROUP_OUTPUT, CHANNEL_WINDFROM, sagerWeatherCaster.getWindDirection());
242 updateChannelString(GROUP_OUTPUT, CHANNEL_WINDTO, sagerWeatherCaster.getWindDirection2());
244 String velocity = sagerWeatherCaster.getWindVelocity();
245 updateChannelString(GROUP_OUTPUT, CHANNEL_VELOCITY, velocity);
246 int predictedBeaufort = sagerWeatherCaster.getBeaufort();
249 predictedBeaufort += 1;
252 predictedBeaufort = 4;
255 predictedBeaufort = 6;
258 predictedBeaufort = 8;
261 predictedBeaufort = 10;
264 predictedBeaufort = 12;
267 predictedBeaufort -= 1;
270 updateChannelDecimal(GROUP_OUTPUT, CHANNEL_VELOCITY_BEAUFORT, predictedBeaufort);
273 protected void updateChannelString(String group, String channelId, String value) {
274 ChannelUID id = new ChannelUID(getThing().getUID(), group, channelId);
276 updateState(id, new StringType(value));
280 protected void updateChannelDecimal(String group, String channelId, int value) {
281 ChannelUID id = new ChannelUID(getThing().getUID(), group, channelId);
283 updateState(id, new DecimalType(value));