]> git.basschouten.com Git - openhab-addons.git/blob
05b934ed3f43258d23de58490c8949bd3c0f261b
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 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.time.ZonedDateTime;
20 import java.util.ArrayList;
21 import java.util.List;
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.WindDirectionStateDescriptionProvider;
30 import org.openhab.binding.sagercaster.internal.caster.SagerWeatherCaster;
31 import org.openhab.core.library.types.DateTimeType;
32 import org.openhab.core.library.types.DecimalType;
33 import org.openhab.core.library.types.OnOffType;
34 import org.openhab.core.library.types.QuantityType;
35 import org.openhab.core.library.types.StringType;
36 import org.openhab.core.library.unit.SIUnits;
37 import org.openhab.core.thing.ChannelUID;
38 import org.openhab.core.thing.Thing;
39 import org.openhab.core.thing.ThingStatus;
40 import org.openhab.core.thing.ThingUID;
41 import org.openhab.core.thing.binding.BaseThingHandler;
42 import org.openhab.core.types.Command;
43 import org.openhab.core.types.RefreshType;
44 import org.openhab.core.types.StateOption;
45 import org.slf4j.Logger;
46 import org.slf4j.LoggerFactory;
47
48 /**
49  * The {@link SagerCasterHandler} is responsible for handling commands, which are
50  * sent to one of the channels.
51  *
52  * @author GaĆ«l L'hopital - Initial contribution
53  */
54 @NonNullByDefault
55 public class SagerCasterHandler extends BaseThingHandler {
56
57     private final Logger logger = LoggerFactory.getLogger(SagerCasterHandler.class);
58
59     private final SagerWeatherCaster sagerWeatherCaster;
60
61     private final WindDirectionStateDescriptionProvider stateDescriptionProvider;
62
63     private final ExpiringMap<Double> pressureCache = new ExpiringMap<>();
64     private final ExpiringMap<Double> temperatureCache = new ExpiringMap<>();
65     private final ExpiringMap<Integer> bearingCache = new ExpiringMap<>();
66
67     private double currentTemp = 0;
68     private String currentSagerCode = SagerWeatherCaster.UNDEF;
69
70     public SagerCasterHandler(Thing thing, WindDirectionStateDescriptionProvider stateDescriptionProvider,
71             SagerWeatherCaster sagerWeatherCaster) {
72         super(thing);
73         this.stateDescriptionProvider = stateDescriptionProvider;
74         this.sagerWeatherCaster = sagerWeatherCaster;
75     }
76
77     @Override
78     public void initialize() {
79         int observationPeriod = ((BigDecimal) getConfig().get(CONFIG_PERIOD)).intValue();
80         long period = TimeUnit.HOURS.toMillis(observationPeriod);
81         pressureCache.setObservationPeriod(period);
82         bearingCache.setObservationPeriod(period);
83         temperatureCache.setObservationPeriod(period);
84
85         String location = (String) getConfig().get(CONFIG_LOCATION);
86         String latitude = location.split(",")[0];
87         sagerWeatherCaster.setLatitude(Double.parseDouble(latitude));
88         defineWindDirectionStateDescriptions();
89
90         updateStatus(ThingStatus.ONLINE);
91     }
92
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));
100         }
101
102         options.add(new StateOption("9", "Shifting / Variable winds"));
103         ThingUID thingUID = getThing().getUID();
104         stateDescriptionProvider.setStateOptions(new ChannelUID(thingUID, GROUP_OUTPUT, CHANNEL_WINDFROM), options);
105         stateDescriptionProvider.setStateOptions(new ChannelUID(thingUID, GROUP_OUTPUT, CHANNEL_WINDTO), options);
106     }
107
108     @Override
109     public void handleCommand(ChannelUID channelUID, Command command) {
110         if (command instanceof RefreshType) {
111             postNewForecast();
112         } else {
113             String id = channelUID.getIdWithoutGroup();
114             switch (id) {
115                 case CHANNEL_CLOUDINESS:
116                     logger.debug("Cloud level changed, updating forecast");
117                     if (command instanceof QuantityType) {
118                         QuantityType<?> cloudiness = (QuantityType<?>) command;
119                         scheduler.submit(() -> {
120                             sagerWeatherCaster.setCloudLevel(cloudiness.intValue());
121                             postNewForecast();
122                         });
123                         break;
124                     }
125                 case CHANNEL_IS_RAINING:
126                     logger.debug("Rain status updated, updating forecast");
127                     if (command instanceof OnOffType) {
128                         OnOffType isRaining = (OnOffType) command;
129                         scheduler.submit(() -> {
130                             sagerWeatherCaster.setRaining(isRaining == OnOffType.ON);
131                             postNewForecast();
132                         });
133                     } else {
134                         logger.debug("Channel '{}' accepts Switch commands.", channelUID);
135                     }
136                     break;
137                 case CHANNEL_RAIN_QTTY:
138                     logger.debug("Rain status updated, updating forecast");
139                     if (command instanceof QuantityType) {
140                         updateRain((QuantityType<?>) command);
141                     } else if (command instanceof DecimalType) {
142                         updateRain((DecimalType) command);
143                     } else {
144                         logger.debug("Channel '{}' accepts Number, Number:(Speed|Length) commands.", channelUID);
145                     }
146                     break;
147                 case CHANNEL_WIND_SPEED:
148                     logger.debug("Updated wind speed, updating forecast");
149                     if (command instanceof DecimalType) {
150                         DecimalType newValue = (DecimalType) command;
151                         scheduler.submit(() -> {
152                             sagerWeatherCaster.setBeaufort(newValue.intValue());
153                             postNewForecast();
154                         });
155                     } else {
156                         logger.debug("Channel '{}' only accepts DecimalType commands.", channelUID);
157                     }
158                     break;
159                 case CHANNEL_PRESSURE:
160                     logger.debug("Sea-level pressure updated, updating forecast");
161                     if (command instanceof QuantityType) {
162                         @SuppressWarnings("unchecked")
163                         QuantityType<Pressure> pressQtty = ((QuantityType<Pressure>) command)
164                                 .toUnit(HECTO(SIUnits.PASCAL));
165                         if (pressQtty != null) {
166                             double newPressureValue = pressQtty.doubleValue();
167                             pressureCache.put(newPressureValue);
168                             pressureCache.getAgedValue().ifPresentOrElse(oldPressure -> scheduler.submit(() -> {
169                                 sagerWeatherCaster.setPressure(newPressureValue, oldPressure);
170                                 updateChannelString(GROUP_OUTPUT, CHANNEL_PRESSURETREND,
171                                         String.valueOf(sagerWeatherCaster.getPressureEvolution()));
172                                 postNewForecast();
173                             }), () -> updateChannelString(GROUP_OUTPUT, CHANNEL_FORECAST, FORECAST_PENDING));
174                         }
175                     }
176                     break;
177                 case CHANNEL_TEMPERATURE:
178                     logger.debug("Temperature updated");
179                     if (command instanceof QuantityType) {
180                         @SuppressWarnings("unchecked")
181                         QuantityType<Temperature> tempQtty = ((QuantityType<Temperature>) command)
182                                 .toUnit(SIUnits.CELSIUS);
183                         if (tempQtty != null) {
184                             currentTemp = tempQtty.doubleValue();
185                             temperatureCache.put(currentTemp);
186                             temperatureCache.getAgedValue().ifPresent(oldTemperature -> {
187                                 double delta = currentTemp - oldTemperature;
188                                 String trend = (delta > 3) ? "1"
189                                         : (delta > 0.3) ? "2" : (delta > -0.3) ? "3" : (delta > -3) ? "4" : "5";
190                                 updateChannelString(GROUP_OUTPUT, CHANNEL_TEMPERATURETREND, trend);
191                             });
192                         }
193                     }
194                     break;
195                 case CHANNEL_WIND_ANGLE:
196                     logger.debug("Updated wind direction, updating forecast");
197                     if (command instanceof QuantityType) {
198                         @SuppressWarnings("unchecked")
199                         QuantityType<Angle> angleQtty = (QuantityType<Angle>) command;
200                         int newAngleValue = angleQtty.intValue();
201                         bearingCache.put(newAngleValue);
202                         bearingCache.getAgedValue().ifPresent(oldAngle -> {
203                             scheduler.submit(() -> {
204                                 sagerWeatherCaster.setBearing(newAngleValue, oldAngle);
205                                 updateChannelString(GROUP_OUTPUT, CHANNEL_WINDEVOLUTION,
206                                         String.valueOf(sagerWeatherCaster.getWindEvolution()));
207                                 postNewForecast();
208                             });
209                         });
210                     }
211                     break;
212                 default:
213                     logger.debug("The binding can not handle command: {} on channel: {}", command, channelUID);
214             }
215         }
216     }
217
218     private void updateRain(Number newQtty) {
219         scheduler.submit(() -> {
220             sagerWeatherCaster.setRaining(newQtty.doubleValue() > 0);
221             postNewForecast();
222         });
223     }
224
225     private void postNewForecast() {
226         String newSagerCode = sagerWeatherCaster.getSagerCode();
227         if (!newSagerCode.equals(currentSagerCode)) {
228             logger.debug("Sager prediction changed to {}", newSagerCode);
229             currentSagerCode = newSagerCode;
230             updateChannelTimeStamp(GROUP_OUTPUT, CHANNEL_TIMESTAMP, ZonedDateTime.now());
231             String forecast = sagerWeatherCaster.getForecast();
232             // Sharpens forecast if current temp is below 2 degrees, likely to be flurries rather than shower
233             forecast += SHOWERS.contains(forecast) ? (currentTemp > 2) ? "1" : "2" : "";
234
235             updateChannelString(GROUP_OUTPUT, CHANNEL_FORECAST, forecast);
236             updateChannelString(GROUP_OUTPUT, CHANNEL_WINDFROM, sagerWeatherCaster.getWindDirection());
237             updateChannelString(GROUP_OUTPUT, CHANNEL_WINDTO, sagerWeatherCaster.getWindDirection2());
238             updateChannelString(GROUP_OUTPUT, CHANNEL_VELOCITY, sagerWeatherCaster.getWindVelocity());
239             updateChannelDecimal(GROUP_OUTPUT, CHANNEL_VELOCITY_BEAUFORT, sagerWeatherCaster.getPredictedBeaufort());
240         }
241     }
242
243     private void updateChannelTimeStamp(String group, String channelId, ZonedDateTime zonedDateTime) {
244         ChannelUID id = new ChannelUID(getThing().getUID(), group, channelId);
245         if (isLinked(id)) {
246             updateState(id, new DateTimeType(zonedDateTime));
247         }
248     }
249
250     private void updateChannelString(String group, String channelId, String value) {
251         ChannelUID id = new ChannelUID(getThing().getUID(), group, channelId);
252         if (isLinked(id)) {
253             updateState(id, new StringType(value));
254         }
255     }
256
257     private void updateChannelDecimal(String group, String channelId, int value) {
258         ChannelUID id = new ChannelUID(getThing().getUID(), group, channelId);
259         if (isLinked(id)) {
260             updateState(id, new DecimalType(value));
261         }
262     }
263 }