2 * Copyright (c) 2010-2021 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.List;
21 import java.util.Optional;
22 import java.util.concurrent.TimeUnit;
24 import javax.measure.quantity.Angle;
25 import javax.measure.quantity.Pressure;
26 import javax.measure.quantity.Temperature;
28 import org.eclipse.jdt.annotation.NonNullByDefault;
29 import org.openhab.binding.sagercaster.internal.SagerWeatherCaster;
30 import org.openhab.binding.sagercaster.internal.WindDirectionStateDescriptionProvider;
31 import org.openhab.core.library.types.DecimalType;
32 import org.openhab.core.library.types.OnOffType;
33 import org.openhab.core.library.types.QuantityType;
34 import org.openhab.core.library.types.StringType;
35 import org.openhab.core.library.unit.SIUnits;
36 import org.openhab.core.thing.ChannelUID;
37 import org.openhab.core.thing.Thing;
38 import org.openhab.core.thing.ThingStatus;
39 import org.openhab.core.thing.binding.BaseThingHandler;
40 import org.openhab.core.types.Command;
41 import org.openhab.core.types.RefreshType;
42 import org.openhab.core.types.StateOption;
43 import org.slf4j.Logger;
44 import org.slf4j.LoggerFactory;
47 * The {@link SagerCasterHandler} is responsible for handling commands, which are
48 * sent to one of the channels.
50 * @author Gaƫl L'hopital - Initial contribution
53 public class SagerCasterHandler extends BaseThingHandler {
55 private final Logger logger = LoggerFactory.getLogger(SagerCasterHandler.class);
57 private final SagerWeatherCaster sagerWeatherCaster;
59 private final WindDirectionStateDescriptionProvider stateDescriptionProvider;
61 private final ExpiringMap<QuantityType<Pressure>> pressureCache = new ExpiringMap<>();
62 private final ExpiringMap<QuantityType<Temperature>> temperatureCache = new ExpiringMap<>();
63 private final ExpiringMap<QuantityType<Angle>> bearingCache = new ExpiringMap<>();
65 private int currentTemp = 0;
67 public SagerCasterHandler(Thing thing, WindDirectionStateDescriptionProvider stateDescriptionProvider,
68 SagerWeatherCaster sagerWeatherCaster) {
70 this.stateDescriptionProvider = stateDescriptionProvider;
71 this.sagerWeatherCaster = sagerWeatherCaster;
75 public void initialize() {
76 String location = (String) getConfig().get(CONFIG_LOCATION);
77 int observationPeriod = ((BigDecimal) getConfig().get(CONFIG_PERIOD)).intValue();
78 String latitude = location.split(",")[0];
79 sagerWeatherCaster.setLatitude(Double.parseDouble(latitude));
80 long period = TimeUnit.HOURS.toMillis(observationPeriod);
81 pressureCache.setObservationPeriod(period);
82 bearingCache.setObservationPeriod(period);
83 temperatureCache.setObservationPeriod(period);
84 defineWindDirectionStateDescriptions();
85 updateStatus(ThingStatus.ONLINE);
88 private void defineWindDirectionStateDescriptions() {
89 List<StateOption> options = new ArrayList<>();
90 String[] directions = sagerWeatherCaster.getUsedDirections();
91 for (int i = 0; i < directions.length; i++) {
92 int secondDirection = i < directions.length - 1 ? i + 1 : 0;
93 String windDescription = directions[i] + " or " + directions[secondDirection] + " winds";
94 options.add(new StateOption(String.valueOf(i + 1), windDescription));
97 options.add(new StateOption("9", "Shifting / Variable winds"));
98 stateDescriptionProvider.setStateOptions(new ChannelUID(getThing().getUID(), GROUP_OUTPUT, CHANNEL_WINDFROM),
100 stateDescriptionProvider.setStateOptions(new ChannelUID(getThing().getUID(), GROUP_OUTPUT, CHANNEL_WINDTO),
105 public void handleCommand(ChannelUID channelUID, Command command) {
106 if (command instanceof RefreshType) {
109 String id = channelUID.getIdWithoutGroup();
111 case CHANNEL_CLOUDINESS:
112 logger.debug("Octa cloud level changed, updating forecast");
113 if (command instanceof QuantityType) {
114 QuantityType<?> cloudiness = (QuantityType<?>) command;
115 scheduler.submit(() -> {
116 sagerWeatherCaster.setCloudLevel(cloudiness.intValue());
121 case CHANNEL_IS_RAINING:
122 logger.debug("Rain status updated, updating forecast");
123 if (command instanceof OnOffType) {
124 OnOffType isRaining = (OnOffType) command;
125 scheduler.submit(() -> {
126 sagerWeatherCaster.setRaining(isRaining == OnOffType.ON);
130 logger.debug("Channel '{}' can only accept Switch type commands.", channelUID);
133 case CHANNEL_RAIN_QTTY:
134 logger.debug("Rain status updated, updating forecast");
135 if (command instanceof QuantityType) {
136 QuantityType<?> newQtty = (QuantityType<?>) command;
137 scheduler.submit(() -> {
138 sagerWeatherCaster.setRaining(newQtty.doubleValue() > 0);
141 } else if (command instanceof DecimalType) {
142 DecimalType newQtty = (DecimalType) command;
143 scheduler.submit(() -> {
144 sagerWeatherCaster.setRaining(newQtty.doubleValue() > 0);
148 logger.debug("Channel '{}' can accept Number, Number:Speed, Number:Length type commands.",
152 case CHANNEL_WIND_SPEED:
153 logger.debug("Updated wind speed, updating forecast");
154 if (command instanceof DecimalType) {
155 DecimalType newValue = (DecimalType) command;
156 scheduler.submit(() -> {
157 sagerWeatherCaster.setBeaufort(newValue.intValue());
161 logger.debug("Channel '{}' only accepts DecimalType commands.", channelUID);
164 case CHANNEL_PRESSURE:
165 logger.debug("Sea-level pressure updated, updating forecast");
166 if (command instanceof QuantityType) {
167 @SuppressWarnings("unchecked")
168 QuantityType<Pressure> newPressure = ((QuantityType<Pressure>) command)
169 .toUnit(HECTO(SIUnits.PASCAL));
170 if (newPressure != null) {
171 pressureCache.put(newPressure);
172 pressureCache.getAgedValue().ifPresentOrElse(pressure -> scheduler.submit(() -> {
173 sagerWeatherCaster.setPressure(newPressure.doubleValue(), pressure.doubleValue());
174 updateChannelString(GROUP_OUTPUT, CHANNEL_PRESSURETREND,
175 String.valueOf(sagerWeatherCaster.getPressureEvolution()));
177 }), () -> updateChannelString(GROUP_OUTPUT, CHANNEL_FORECAST, FORECAST_PENDING));
181 case CHANNEL_TEMPERATURE:
182 logger.debug("Temperature updated");
183 if (command instanceof QuantityType) {
184 @SuppressWarnings("unchecked")
185 QuantityType<Temperature> newTemperature = ((QuantityType<Temperature>) command)
186 .toUnit(SIUnits.CELSIUS);
187 if (newTemperature != null) {
188 temperatureCache.put(newTemperature);
189 currentTemp = newTemperature.intValue();
190 Optional<QuantityType<Temperature>> agedTemperature = temperatureCache.getAgedValue();
191 agedTemperature.ifPresent(temperature -> {
192 double delta = newTemperature.doubleValue() - temperature.doubleValue();
193 String trend = (delta > 3) ? "1"
194 : (delta > 0.3) ? "2" : (delta > -0.3) ? "3" : (delta > -3) ? "4" : "5";
195 updateChannelString(GROUP_OUTPUT, CHANNEL_TEMPERATURETREND, trend);
200 case CHANNEL_WIND_ANGLE:
201 logger.debug("Updated wind direction, updating forecast");
202 if (command instanceof QuantityType) {
203 @SuppressWarnings("unchecked")
204 QuantityType<Angle> newAngle = (QuantityType<Angle>) command;
205 bearingCache.put(newAngle);
206 Optional<QuantityType<Angle>> agedAngle = bearingCache.getAgedValue();
207 agedAngle.ifPresent(angle -> {
208 scheduler.submit(() -> {
209 sagerWeatherCaster.setBearing(newAngle.intValue(), angle.intValue());
210 updateChannelString(GROUP_OUTPUT, CHANNEL_WINDEVOLUTION,
211 String.valueOf(sagerWeatherCaster.getWindEvolution()));
218 logger.debug("The binding can not handle command: {} on channel: {}", command, channelUID);
223 private void postNewForecast() {
224 String forecast = sagerWeatherCaster.getForecast();
225 // Sharpens forecast if current temp is below 2 degrees, likely to be flurries rather than shower
226 forecast += SHOWERS.contains(forecast) ? (currentTemp > 2) ? "1" : "2" : "";
228 updateChannelString(GROUP_OUTPUT, CHANNEL_FORECAST, forecast);
229 updateChannelString(GROUP_OUTPUT, CHANNEL_WINDFROM, sagerWeatherCaster.getWindDirection());
230 updateChannelString(GROUP_OUTPUT, CHANNEL_WINDTO, sagerWeatherCaster.getWindDirection2());
232 String velocity = sagerWeatherCaster.getWindVelocity();
233 updateChannelString(GROUP_OUTPUT, CHANNEL_VELOCITY, velocity);
234 int predictedBeaufort = sagerWeatherCaster.getBeaufort();
237 predictedBeaufort += 1;
240 predictedBeaufort = 4;
243 predictedBeaufort = 6;
246 predictedBeaufort = 8;
249 predictedBeaufort = 10;
252 predictedBeaufort = 12;
255 predictedBeaufort -= 1;
258 updateChannelDecimal(GROUP_OUTPUT, CHANNEL_VELOCITY_BEAUFORT, predictedBeaufort);
261 private void updateChannelString(String group, String channelId, String value) {
262 ChannelUID id = new ChannelUID(getThing().getUID(), group, channelId);
264 updateState(id, new StringType(value));
268 private void updateChannelDecimal(String group, String channelId, int value) {
269 ChannelUID id = new ChannelUID(getThing().getUID(), group, channelId);
271 updateState(id, new DecimalType(value));