]> git.basschouten.com Git - openhab-addons.git/blob
f4cbc0b7f1ab24250325e9b4919f13a37ad091bc
[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 cloudiness) {
118                         scheduler.submit(() -> {
119                             sagerWeatherCaster.setCloudLevel(cloudiness.intValue());
120                             postNewForecast();
121                         });
122                         break;
123                     }
124                 case CHANNEL_IS_RAINING:
125                     logger.debug("Rain status updated, updating forecast");
126                     if (command instanceof OnOffType isRaining) {
127                         scheduler.submit(() -> {
128                             sagerWeatherCaster.setRaining(isRaining == OnOffType.ON);
129                             postNewForecast();
130                         });
131                     } else {
132                         logger.debug("Channel '{}' accepts Switch commands.", channelUID);
133                     }
134                     break;
135                 case CHANNEL_RAIN_QTTY:
136                     logger.debug("Rain status updated, updating forecast");
137                     if (command instanceof QuantityType quantityCommand) {
138                         updateRain(quantityCommand);
139                     } else if (command instanceof DecimalType decimalCommand) {
140                         updateRain(decimalCommand);
141                     } else {
142                         logger.debug("Channel '{}' accepts Number, Number:(Speed|Length) commands.", channelUID);
143                     }
144                     break;
145                 case CHANNEL_WIND_SPEED:
146                     logger.debug("Updated wind speed, updating forecast");
147                     if (command instanceof DecimalType newValue) {
148                         scheduler.submit(() -> {
149                             sagerWeatherCaster.setBeaufort(newValue.intValue());
150                             postNewForecast();
151                         });
152                     } else {
153                         logger.debug("Channel '{}' only accepts DecimalType commands.", channelUID);
154                     }
155                     break;
156                 case CHANNEL_PRESSURE:
157                     logger.debug("Sea-level pressure updated, updating forecast");
158                     if (command instanceof QuantityType) {
159                         @SuppressWarnings("unchecked")
160                         QuantityType<Pressure> pressQtty = ((QuantityType<Pressure>) command)
161                                 .toUnit(HECTO(SIUnits.PASCAL));
162                         if (pressQtty != null) {
163                             double newPressureValue = pressQtty.doubleValue();
164                             pressureCache.put(newPressureValue);
165                             pressureCache.getAgedValue().ifPresentOrElse(oldPressure -> scheduler.submit(() -> {
166                                 sagerWeatherCaster.setPressure(newPressureValue, oldPressure);
167                                 updateChannelString(GROUP_OUTPUT, CHANNEL_PRESSURETREND,
168                                         String.valueOf(sagerWeatherCaster.getPressureEvolution()));
169                                 postNewForecast();
170                             }), () -> updateChannelString(GROUP_OUTPUT, CHANNEL_FORECAST, FORECAST_PENDING));
171                         }
172                     }
173                     break;
174                 case CHANNEL_TEMPERATURE:
175                     logger.debug("Temperature updated");
176                     if (command instanceof QuantityType) {
177                         @SuppressWarnings("unchecked")
178                         QuantityType<Temperature> tempQtty = ((QuantityType<Temperature>) command)
179                                 .toUnit(SIUnits.CELSIUS);
180                         if (tempQtty != null) {
181                             currentTemp = tempQtty.doubleValue();
182                             temperatureCache.put(currentTemp);
183                             temperatureCache.getAgedValue().ifPresent(oldTemperature -> {
184                                 double delta = currentTemp - oldTemperature;
185                                 String trend = (delta > 3) ? "1"
186                                         : (delta > 0.3) ? "2" : (delta > -0.3) ? "3" : (delta > -3) ? "4" : "5";
187                                 updateChannelString(GROUP_OUTPUT, CHANNEL_TEMPERATURETREND, trend);
188                             });
189                         }
190                     }
191                     break;
192                 case CHANNEL_WIND_ANGLE:
193                     logger.debug("Updated wind direction, updating forecast");
194                     if (command instanceof QuantityType) {
195                         @SuppressWarnings("unchecked")
196                         QuantityType<Angle> angleQtty = (QuantityType<Angle>) command;
197                         int newAngleValue = angleQtty.intValue();
198                         bearingCache.put(newAngleValue);
199                         bearingCache.getAgedValue().ifPresent(oldAngle -> {
200                             scheduler.submit(() -> {
201                                 sagerWeatherCaster.setBearing(newAngleValue, oldAngle);
202                                 updateChannelString(GROUP_OUTPUT, CHANNEL_WINDEVOLUTION,
203                                         String.valueOf(sagerWeatherCaster.getWindEvolution()));
204                                 postNewForecast();
205                             });
206                         });
207                     }
208                     break;
209                 default:
210                     logger.debug("The binding can not handle command: {} on channel: {}", command, channelUID);
211             }
212         }
213     }
214
215     private void updateRain(Number newQtty) {
216         scheduler.submit(() -> {
217             sagerWeatherCaster.setRaining(newQtty.doubleValue() > 0);
218             postNewForecast();
219         });
220     }
221
222     private void postNewForecast() {
223         String newSagerCode = sagerWeatherCaster.getSagerCode();
224         if (!newSagerCode.equals(currentSagerCode)) {
225             logger.debug("Sager prediction changed to {}", newSagerCode);
226             currentSagerCode = newSagerCode;
227             updateChannelTimeStamp(GROUP_OUTPUT, CHANNEL_TIMESTAMP, ZonedDateTime.now());
228             String forecast = sagerWeatherCaster.getForecast();
229             // Sharpens forecast if current temp is below 2 degrees, likely to be flurries rather than shower
230             forecast += SHOWERS.contains(forecast) ? (currentTemp > 2) ? "1" : "2" : "";
231
232             updateChannelString(GROUP_OUTPUT, CHANNEL_FORECAST, forecast);
233             updateChannelString(GROUP_OUTPUT, CHANNEL_WINDFROM, sagerWeatherCaster.getWindDirection());
234             updateChannelString(GROUP_OUTPUT, CHANNEL_WINDTO, sagerWeatherCaster.getWindDirection2());
235             updateChannelString(GROUP_OUTPUT, CHANNEL_VELOCITY, sagerWeatherCaster.getWindVelocity());
236             updateChannelDecimal(GROUP_OUTPUT, CHANNEL_VELOCITY_BEAUFORT, sagerWeatherCaster.getPredictedBeaufort());
237         }
238     }
239
240     private void updateChannelTimeStamp(String group, String channelId, ZonedDateTime zonedDateTime) {
241         ChannelUID id = new ChannelUID(getThing().getUID(), group, channelId);
242         if (isLinked(id)) {
243             updateState(id, new DateTimeType(zonedDateTime));
244         }
245     }
246
247     private void updateChannelString(String group, String channelId, String value) {
248         ChannelUID id = new ChannelUID(getThing().getUID(), group, channelId);
249         if (isLinked(id)) {
250             updateState(id, new StringType(value));
251         }
252     }
253
254     private void updateChannelDecimal(String group, String channelId, int value) {
255         ChannelUID id = new ChannelUID(getThing().getUID(), group, channelId);
256         if (isLinked(id)) {
257             updateState(id, new DecimalType(value));
258         }
259     }
260 }