]> git.basschouten.com Git - openhab-addons.git/blob
a5c5015a3577421840088bfd90988768c88d8d5a
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2020 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.Arrays;
21 import java.util.Collections;
22 import java.util.HashSet;
23 import java.util.List;
24 import java.util.Optional;
25 import java.util.Set;
26 import java.util.concurrent.TimeUnit;
27
28 import javax.measure.quantity.Angle;
29 import javax.measure.quantity.Dimensionless;
30 import javax.measure.quantity.Pressure;
31 import javax.measure.quantity.Temperature;
32
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;
50
51 /**
52  * The {@link SagerCasterHandler} is responsible for handling commands, which are
53  * sent to one of the channels.
54  *
55  * @author GaĆ«l L'hopital - Initial contribution
56  */
57 @NonNullByDefault
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")));
62
63     private final Logger logger = LoggerFactory.getLogger(SagerCasterHandler.class);
64     private final SagerWeatherCaster sagerWeatherCaster;
65     private final WindDirectionStateDescriptionProvider stateDescriptionProvider;
66     private int currentTemp = 0;
67
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<>();
71
72     public SagerCasterHandler(Thing thing, WindDirectionStateDescriptionProvider stateDescriptionProvider,
73             SagerWeatherCaster sagerWeatherCaster) {
74         super(thing);
75         this.stateDescriptionProvider = stateDescriptionProvider;
76         this.sagerWeatherCaster = sagerWeatherCaster;
77     }
78
79     @Override
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);
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         stateDescriptionProvider.setStateOptions(new ChannelUID(getThing().getUID(), GROUP_OUTPUT, CHANNEL_WINDFROM),
104                 options);
105         stateDescriptionProvider.setStateOptions(new ChannelUID(getThing().getUID(), GROUP_OUTPUT, CHANNEL_WINDTO),
106                 options);
107     }
108
109     @Override
110     public void handleCommand(ChannelUID channelUID, Command command) {
111         if (command instanceof RefreshType) {
112             postNewForecast();
113         } else {
114             String id = channelUID.getIdWithoutGroup();
115             switch (id) {
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());
123                             postNewForecast();
124                         });
125                         break;
126                     }
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);
133                             postNewForecast();
134                         });
135                     } else {
136                         logger.debug("Channel '{}' can only accept Switch type commands.", channelUID);
137                     }
138                     break;
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);
145                             postNewForecast();
146                         });
147                     } else if (command instanceof DecimalType) {
148                         DecimalType newQtty = ((DecimalType) command);
149                         scheduler.submit(() -> {
150                             sagerWeatherCaster.setRaining(newQtty.doubleValue() > 0);
151                             postNewForecast();
152                         });
153                     } else {
154                         logger.debug("Channel '{}' can accept Number, Number:Speed, Number:Length type commands.",
155                                 channelUID);
156                     }
157                     break;
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());
164                             postNewForecast();
165                         });
166                     } else {
167                         logger.debug("Channel '{}' only accepts DecimalType commands.", channelUID);
168                     }
169                     break;
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()));
185                                     postNewForecast();
186                                 });
187                             } else {
188                                 updateChannelString(GROUP_OUTPUT, CHANNEL_FORECAST, FORECAST_PENDING);
189                             }
190                         }
191                     }
192                     break;
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);
208                             }
209                         }
210                     }
211                     break;
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()));
224                                 postNewForecast();
225                             });
226                         }
227                     }
228                     break;
229                 default:
230                     logger.debug("The binding can not handle command: {} on channel: {}", command, channelUID);
231             }
232         }
233     }
234
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" : "";
239
240         updateChannelString(GROUP_OUTPUT, CHANNEL_FORECAST, forecast);
241         updateChannelString(GROUP_OUTPUT, CHANNEL_WINDFROM, sagerWeatherCaster.getWindDirection());
242         updateChannelString(GROUP_OUTPUT, CHANNEL_WINDTO, sagerWeatherCaster.getWindDirection2());
243
244         String velocity = sagerWeatherCaster.getWindVelocity();
245         updateChannelString(GROUP_OUTPUT, CHANNEL_VELOCITY, velocity);
246         int predictedBeaufort = sagerWeatherCaster.getBeaufort();
247         switch (velocity) {
248             case "N":
249                 predictedBeaufort += 1;
250                 break;
251             case "F":
252                 predictedBeaufort = 4;
253                 break;
254             case "S":
255                 predictedBeaufort = 6;
256                 break;
257             case "G":
258                 predictedBeaufort = 8;
259                 break;
260             case "W":
261                 predictedBeaufort = 10;
262                 break;
263             case "H":
264                 predictedBeaufort = 12;
265                 break;
266             case "D":
267                 predictedBeaufort -= 1;
268                 break;
269         }
270         updateChannelDecimal(GROUP_OUTPUT, CHANNEL_VELOCITY_BEAUFORT, predictedBeaufort);
271     }
272
273     protected void updateChannelString(String group, String channelId, String value) {
274         ChannelUID id = new ChannelUID(getThing().getUID(), group, channelId);
275         if (isLinked(id)) {
276             updateState(id, new StringType(value));
277         }
278     }
279
280     protected void updateChannelDecimal(String group, String channelId, int value) {
281         ChannelUID id = new ChannelUID(getThing().getUID(), group, channelId);
282         if (isLinked(id)) {
283             updateState(id, new DecimalType(value));
284         }
285     }
286 }