]> git.basschouten.com Git - openhab-addons.git/blob
79065e6ac662b36d666c06df7e94494423c182a2
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.sagercaster.internal.handler;
14
15 import static org.openhab.binding.sagercaster.internal.SagerCasterBindingConstants.*;
16 import static org.openhab.core.library.unit.MetricPrefix.HECTO;
17
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;
23
24 import javax.measure.quantity.Angle;
25 import javax.measure.quantity.Pressure;
26 import javax.measure.quantity.Temperature;
27
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;
45
46 /**
47  * The {@link SagerCasterHandler} is responsible for handling commands, which are
48  * sent to one of the channels.
49  *
50  * @author GaĆ«l L'hopital - Initial contribution
51  */
52 @NonNullByDefault
53 public class SagerCasterHandler extends BaseThingHandler {
54
55     private final Logger logger = LoggerFactory.getLogger(SagerCasterHandler.class);
56
57     private final SagerWeatherCaster sagerWeatherCaster;
58
59     private final WindDirectionStateDescriptionProvider stateDescriptionProvider;
60
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<>();
64
65     private int currentTemp = 0;
66
67     public SagerCasterHandler(Thing thing, WindDirectionStateDescriptionProvider stateDescriptionProvider,
68             SagerWeatherCaster sagerWeatherCaster) {
69         super(thing);
70         this.stateDescriptionProvider = stateDescriptionProvider;
71         this.sagerWeatherCaster = sagerWeatherCaster;
72     }
73
74     @Override
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);
86     }
87
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));
95         }
96
97         options.add(new StateOption("9", "Shifting / Variable winds"));
98         stateDescriptionProvider.setStateOptions(new ChannelUID(getThing().getUID(), GROUP_OUTPUT, CHANNEL_WINDFROM),
99                 options);
100         stateDescriptionProvider.setStateOptions(new ChannelUID(getThing().getUID(), GROUP_OUTPUT, CHANNEL_WINDTO),
101                 options);
102     }
103
104     @Override
105     public void handleCommand(ChannelUID channelUID, Command command) {
106         if (command instanceof RefreshType) {
107             postNewForecast();
108         } else {
109             String id = channelUID.getIdWithoutGroup();
110             switch (id) {
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());
117                             postNewForecast();
118                         });
119                         break;
120                     }
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);
127                             postNewForecast();
128                         });
129                     } else {
130                         logger.debug("Channel '{}' can only accept Switch type commands.", channelUID);
131                     }
132                     break;
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);
139                             postNewForecast();
140                         });
141                     } else if (command instanceof DecimalType) {
142                         DecimalType newQtty = (DecimalType) command;
143                         scheduler.submit(() -> {
144                             sagerWeatherCaster.setRaining(newQtty.doubleValue() > 0);
145                             postNewForecast();
146                         });
147                     } else {
148                         logger.debug("Channel '{}' can accept Number, Number:Speed, Number:Length type commands.",
149                                 channelUID);
150                     }
151                     break;
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());
158                             postNewForecast();
159                         });
160                     } else {
161                         logger.debug("Channel '{}' only accepts DecimalType commands.", channelUID);
162                     }
163                     break;
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()));
176                                 postNewForecast();
177                             }), () -> updateChannelString(GROUP_OUTPUT, CHANNEL_FORECAST, FORECAST_PENDING));
178                         }
179                     }
180                     break;
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);
196                             });
197                         }
198                     }
199                     break;
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()));
212                                 postNewForecast();
213                             });
214                         });
215                     }
216                     break;
217                 default:
218                     logger.debug("The binding can not handle command: {} on channel: {}", command, channelUID);
219             }
220         }
221     }
222
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" : "";
227
228         updateChannelString(GROUP_OUTPUT, CHANNEL_FORECAST, forecast);
229         updateChannelString(GROUP_OUTPUT, CHANNEL_WINDFROM, sagerWeatherCaster.getWindDirection());
230         updateChannelString(GROUP_OUTPUT, CHANNEL_WINDTO, sagerWeatherCaster.getWindDirection2());
231
232         String velocity = sagerWeatherCaster.getWindVelocity();
233         updateChannelString(GROUP_OUTPUT, CHANNEL_VELOCITY, velocity);
234         int predictedBeaufort = sagerWeatherCaster.getBeaufort();
235         switch (velocity) {
236             case "N":
237                 predictedBeaufort += 1;
238                 break;
239             case "F":
240                 predictedBeaufort = 4;
241                 break;
242             case "S":
243                 predictedBeaufort = 6;
244                 break;
245             case "G":
246                 predictedBeaufort = 8;
247                 break;
248             case "W":
249                 predictedBeaufort = 10;
250                 break;
251             case "H":
252                 predictedBeaufort = 12;
253                 break;
254             case "D":
255                 predictedBeaufort -= 1;
256                 break;
257         }
258         updateChannelDecimal(GROUP_OUTPUT, CHANNEL_VELOCITY_BEAUFORT, predictedBeaufort);
259     }
260
261     private void updateChannelString(String group, String channelId, String value) {
262         ChannelUID id = new ChannelUID(getThing().getUID(), group, channelId);
263         if (isLinked(id)) {
264             updateState(id, new StringType(value));
265         }
266     }
267
268     private void updateChannelDecimal(String group, String channelId, int value) {
269         ChannelUID id = new ChannelUID(getThing().getUID(), group, channelId);
270         if (isLinked(id)) {
271             updateState(id, new DecimalType(value));
272         }
273     }
274 }