]> git.basschouten.com Git - openhab-addons.git/blob
2ca01de22d437c75510ed6445ef8ba3821102d05
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2024 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.ojelectronics.internal;
14
15 import java.time.ZonedDateTime;
16 import java.time.temporal.ChronoUnit;
17 import java.util.AbstractMap;
18 import java.util.AbstractMap.SimpleImmutableEntry;
19 import java.util.Date;
20 import java.util.HashMap;
21 import java.util.LinkedList;
22 import java.util.Map;
23 import java.util.Objects;
24 import java.util.function.Consumer;
25
26 import org.eclipse.jdt.annotation.NonNullByDefault;
27 import org.eclipse.jdt.annotation.Nullable;
28 import org.openhab.binding.ojelectronics.internal.config.OJElectronicsThermostatConfiguration;
29 import org.openhab.binding.ojelectronics.internal.models.thermostat.ThermostatModel;
30 import org.openhab.binding.ojelectronics.internal.models.thermostat.ThermostatRealTimeValuesModel;
31 import org.openhab.core.i18n.TimeZoneProvider;
32 import org.openhab.core.library.types.DateTimeType;
33 import org.openhab.core.library.types.DecimalType;
34 import org.openhab.core.library.types.OpenClosedType;
35 import org.openhab.core.library.types.QuantityType;
36 import org.openhab.core.library.types.StringType;
37 import org.openhab.core.library.unit.SIUnits;
38 import org.openhab.core.thing.Bridge;
39 import org.openhab.core.thing.ChannelUID;
40 import org.openhab.core.thing.Thing;
41 import org.openhab.core.thing.ThingStatus;
42 import org.openhab.core.thing.binding.BaseThingHandler;
43 import org.openhab.core.thing.binding.BridgeHandler;
44 import org.openhab.core.types.Command;
45 import org.openhab.core.types.RefreshType;
46 import org.slf4j.Logger;
47 import org.slf4j.LoggerFactory;
48
49 /**
50  * The {@link ThermostatHandler} is responsible for handling commands, which are
51  * sent to one of the channels.
52  *
53  * @author Christian Kittel - Initial contribution
54  */
55 @NonNullByDefault
56 public class ThermostatHandler extends BaseThingHandler {
57
58     private static final Map<Integer, String> REGULATION_MODES = createRegulationMap();
59     private static final Map<String, Integer> REVERSE_REGULATION_MODES = createRegulationReverseMap();
60
61     private final String serialNumber;
62     private final Logger logger = LoggerFactory.getLogger(ThermostatHandler.class);
63     private final Map<String, Consumer<ThermostatModel>> channelRefreshActions = createChannelRefreshActionMap();
64     private final Map<String, Consumer<ThermostatRealTimeValuesModel>> channelRealTimeRefreshActions = createRealTimeChannelRefreshActionMap();
65     private final Map<String, Consumer<Command>> updateThermostatValueActions = createUpdateThermostatValueActionMap();
66     private final TimeZoneProvider timeZoneProvider;
67
68     private LinkedList<AbstractMap.SimpleImmutableEntry<String, Command>> updatedValues = new LinkedList<>();
69     private @Nullable ThermostatModel currentThermostat;
70
71     /**
72      * Creates a new instance of {@link ThermostatHandler}
73      *
74      * @param thing Thing
75      * @param timeZoneProvider Time zone
76      */
77     public ThermostatHandler(Thing thing, TimeZoneProvider timeZoneProvider) {
78         super(thing);
79         serialNumber = getConfigAs(OJElectronicsThermostatConfiguration.class).serialNumber;
80         this.timeZoneProvider = timeZoneProvider;
81     }
82
83     /**
84      * Gets the thing's serial number.
85      *
86      * @return serial number
87      */
88     public String getSerialNumber() {
89         return serialNumber;
90     }
91
92     /**
93      * Handles commands to this thing.
94      */
95     @Override
96     public void handleCommand(ChannelUID channelUID, Command command) {
97         if (command instanceof RefreshType) {
98             final ThermostatModel thermostat = currentThermostat;
99             if (thermostat != null && channelRefreshActions.containsKey(channelUID.getId())) {
100                 final @Nullable Consumer<ThermostatModel> consumer = channelRefreshActions.get(channelUID.getId());
101                 if (consumer != null) {
102                     consumer.accept(thermostat);
103                 }
104             }
105         } else {
106             synchronized (this) {
107                 updatedValues.add(new AbstractMap.SimpleImmutableEntry<>(channelUID.getId(), command));
108
109                 BridgeHandler bridgeHandler = Objects.requireNonNull(getBridge()).getHandler();
110                 if (bridgeHandler != null) {
111                     ((OJCloudHandler) (bridgeHandler)).updateThinksChannelValuesToCloud();
112                 } else {
113                     currentThermostat = null;
114                     updateStatus(ThingStatus.OFFLINE);
115                 }
116             }
117         }
118     }
119
120     /**
121      * Initializes the thing handler.
122      */
123     @Override
124     public void initialize() {
125         @Nullable
126         Bridge bridge = getBridge();
127         if (bridge != null && bridge.getStatus() == ThingStatus.ONLINE && currentThermostat == null) {
128             @Nullable
129             OJCloudHandler bridgeHandler = (OJCloudHandler) (bridge.getHandler());
130             if (bridgeHandler != null) {
131                 bridgeHandler.reInitialize();
132             }
133         }
134     }
135
136     /**
137      * Sets the values after refreshing the thermostats values
138      *
139      * @param thermostat thermostat values
140      */
141     public void handleThermostatRefresh(ThermostatModel thermostat) {
142         if (currentThermostat == null) {
143             updateStatus(ThingStatus.ONLINE);
144         }
145         currentThermostat = thermostat;
146         channelRefreshActions.forEach((channelUID, action) -> action.accept(thermostat));
147     }
148
149     /**
150      * Sets the values after refreshing the thermostats values
151      *
152      * @param thermostat thermostat values
153      */
154     public void handleThermostatRefresh(ThermostatRealTimeValuesModel thermostat) {
155         final ThermostatModel currentThermostat = this.currentThermostat;
156         if (currentThermostat != null) {
157             currentThermostat.heating = thermostat.heating;
158             currentThermostat.floorTemperature = thermostat.floorTemperature;
159             currentThermostat.action = thermostat.action;
160             currentThermostat.online = thermostat.online;
161             currentThermostat.roomTemperature = thermostat.roomTemperature;
162             channelRealTimeRefreshActions.forEach((channelUID, action) -> action.accept(thermostat));
163         }
164     }
165
166     /**
167      * Gets a {@link ThermostatModel} with changed values or null if nothing has changed
168      *
169      * @return The changed {@link ThermostatModel}
170      */
171     public @Nullable ThermostatModel tryHandleAndGetUpdatedThermostat() {
172         final LinkedList<SimpleImmutableEntry<String, Command>> updatedValues = this.updatedValues;
173         if (updatedValues.isEmpty()) {
174             return null;
175         }
176         this.updatedValues = new LinkedList<>();
177         updatedValues.forEach(item -> {
178             if (updateThermostatValueActions.containsKey(item.getKey())) {
179                 final @Nullable Consumer<Command> consumer = updateThermostatValueActions.get(item.getKey());
180                 if (consumer != null) {
181                     consumer.accept(item.getValue());
182                 }
183             }
184         });
185         return currentThermostat;
186     }
187
188     private ThermostatModel getCurrentThermostat() {
189         return Objects.requireNonNull(currentThermostat);
190     }
191
192     private void updateManualSetpoint(ThermostatModel thermostat) {
193         updateState(BindingConstants.CHANNEL_OWD5_MANUALSETPOINT,
194                 new QuantityType<>(thermostat.manualModeSetpoint / (double) 100, SIUnits.CELSIUS));
195     }
196
197     private void updateManualSetpoint(Command command) {
198         if (command instanceof QuantityType<?> quantityCommand) {
199             getCurrentThermostat().manualModeSetpoint = (int) (quantityCommand.floatValue() * 100);
200         } else {
201             logger.warn("Unable to set value {}", command);
202         }
203     }
204
205     private void updateBoostEndTime(ThermostatModel thermostat) {
206         updateState(BindingConstants.CHANNEL_OWD5_BOOSTENDTIME, new DateTimeType(
207                 ZonedDateTime.ofInstant(thermostat.boostEndTime.toInstant(), timeZoneProvider.getTimeZone())));
208     }
209
210     private void updateBoostEndTime(Command command) {
211         if (command instanceof DateTimeType dateTimeCommand) {
212             getCurrentThermostat().boostEndTime = Date.from(dateTimeCommand.getZonedDateTime().toInstant());
213         } else {
214             logger.warn("Unable to set value {}", command);
215         }
216     }
217
218     private void updateComfortEndTime(ThermostatModel thermostat) {
219         updateState(BindingConstants.CHANNEL_OWD5_COMFORTENDTIME, new DateTimeType(
220                 ZonedDateTime.ofInstant(thermostat.comfortEndTime.toInstant(), timeZoneProvider.getTimeZone())));
221     }
222
223     private void updateComfortEndTime(Command command) {
224         if (command instanceof DateTimeType dateTimeCommand) {
225             getCurrentThermostat().comfortEndTime = Objects
226                     .requireNonNull(Date.from(dateTimeCommand.getZonedDateTime().toInstant()));
227         } else {
228             logger.warn("Unable to set value {}", command);
229         }
230     }
231
232     private void updateComfortSetpoint(ThermostatModel thermostat) {
233         updateState(BindingConstants.CHANNEL_OWD5_COMFORTSETPOINT,
234                 new QuantityType<>(thermostat.comfortSetpoint / (double) 100, SIUnits.CELSIUS));
235     }
236
237     private void updateComfortSetpoint(Command command) {
238         if (command instanceof QuantityType<?> quantityCommand) {
239             getCurrentThermostat().comfortSetpoint = (int) (quantityCommand.floatValue() * 100);
240         } else {
241             logger.warn("Unable to set value {}", command);
242         }
243     }
244
245     private void updateRegulationMode(ThermostatModel thermostat) {
246         updateState(BindingConstants.CHANNEL_OWD5_REGULATIONMODE,
247                 StringType.valueOf(getRegulationMode(thermostat.regulationMode)));
248     }
249
250     private void updateRegulationMode(Command command) {
251         if (command instanceof StringType && (REVERSE_REGULATION_MODES.containsKey(command.toString().toLowerCase()))) {
252             final @Nullable Integer mode = REVERSE_REGULATION_MODES.get(command.toString().toLowerCase());
253             if (mode != null) {
254                 getCurrentThermostat().regulationMode = mode;
255             }
256         } else {
257             logger.warn("Unable to set value {}", command);
258         }
259     }
260
261     private void updateThermostatName(ThermostatModel thermostat) {
262         updateState(BindingConstants.CHANNEL_OWD5_THERMOSTATNAME, StringType.valueOf(thermostat.thermostatName));
263     }
264
265     private void updateFloorTemperature(ThermostatModel thermostat) {
266         updateState(BindingConstants.CHANNEL_OWD5_FLOORTEMPERATURE,
267                 new QuantityType<>(thermostat.floorTemperature / (double) 100, SIUnits.CELSIUS));
268     }
269
270     private void updateFloorTemperature(ThermostatRealTimeValuesModel thermostatRealTimeValues) {
271         updateState(BindingConstants.CHANNEL_OWD5_FLOORTEMPERATURE,
272                 new QuantityType<>(thermostatRealTimeValues.floorTemperature / (double) 100, SIUnits.CELSIUS));
273     }
274
275     private void updateRoomTemperature(ThermostatModel thermostat) {
276         updateState(BindingConstants.CHANNEL_OWD5_ROOMTEMPERATURE,
277                 new QuantityType<>(thermostat.roomTemperature / (double) 100, SIUnits.CELSIUS));
278     }
279
280     private void updateRoomTemperature(ThermostatRealTimeValuesModel thermostatRealTimeValues) {
281         updateState(BindingConstants.CHANNEL_OWD5_ROOMTEMPERATURE,
282                 new QuantityType<>(thermostatRealTimeValues.roomTemperature / (double) 100, SIUnits.CELSIUS));
283     }
284
285     private void updateHeating(ThermostatModel thermostat) {
286         updateState(BindingConstants.CHANNEL_OWD5_HEATING,
287                 thermostat.heating ? OpenClosedType.OPEN : OpenClosedType.CLOSED);
288     }
289
290     private void updateHeating(ThermostatRealTimeValuesModel thermostatRealTimeValues) {
291         updateState(BindingConstants.CHANNEL_OWD5_HEATING,
292                 thermostatRealTimeValues.heating ? OpenClosedType.OPEN : OpenClosedType.CLOSED);
293     }
294
295     private void updateOnline(ThermostatModel thermostat) {
296         updateState(BindingConstants.CHANNEL_OWD5_ONLINE,
297                 thermostat.online ? OpenClosedType.OPEN : OpenClosedType.CLOSED);
298     }
299
300     private void updateOnline(ThermostatRealTimeValuesModel thermostatRealTimeValues) {
301         updateState(BindingConstants.CHANNEL_OWD5_ONLINE,
302                 thermostatRealTimeValues.online ? OpenClosedType.OPEN : OpenClosedType.CLOSED);
303     }
304
305     private void updateGroupId(ThermostatModel thermostat) {
306         updateState(BindingConstants.CHANNEL_OWD5_GROUPID, new DecimalType(thermostat.groupId));
307     }
308
309     private void updateGroupName(ThermostatModel thermostat) {
310         updateState(BindingConstants.CHANNEL_OWD5_GROUPNAME, StringType.valueOf(thermostat.groupName));
311     }
312
313     private void updateVacationEnabled(ThermostatModel thermostat) {
314         updateState(BindingConstants.CHANNEL_OWD5_VACATIONENABLED,
315                 thermostat.online ? OpenClosedType.OPEN : OpenClosedType.CLOSED);
316     }
317
318     private void updateVacationBeginDay(ThermostatModel thermostat) {
319         updateState(BindingConstants.CHANNEL_OWD5_VACATIONBEGINDAY,
320                 new DateTimeType(
321                         ZonedDateTime.ofInstant(thermostat.vacationBeginDay.toInstant(), timeZoneProvider.getTimeZone())
322                                 .truncatedTo(ChronoUnit.DAYS)));
323     }
324
325     private void updateVacationBeginDay(Command command) {
326         if (command instanceof DateTimeType dateTimeCommand) {
327             getCurrentThermostat().vacationBeginDay = Date
328                     .from(dateTimeCommand.getZonedDateTime().toInstant().truncatedTo(ChronoUnit.DAYS));
329         } else {
330             logger.warn("Unable to set value {}", command);
331         }
332     }
333
334     private void updateVacationEndDay(ThermostatModel thermostat) {
335         updateState(BindingConstants.CHANNEL_OWD5_VACATIONENDDAY,
336                 new DateTimeType(
337                         ZonedDateTime.ofInstant(thermostat.vacationEndDay.toInstant(), timeZoneProvider.getTimeZone())
338                                 .truncatedTo(ChronoUnit.DAYS)));
339     }
340
341     private void updateVacationEndDay(Command command) {
342         if (command instanceof DateTimeType dateTimeCommand) {
343             getCurrentThermostat().vacationEndDay = Date
344                     .from(dateTimeCommand.getZonedDateTime().toInstant().truncatedTo(ChronoUnit.DAYS));
345         } else {
346             logger.warn("Unable to set value {}", command);
347         }
348     }
349
350     private @Nullable String getRegulationMode(int regulationMode) {
351         return REGULATION_MODES.get(regulationMode);
352     }
353
354     private static Map<Integer, String> createRegulationMap() {
355         HashMap<Integer, String> map = new HashMap<>();
356         map.put(1, "auto");
357         map.put(2, "comfort");
358         map.put(3, "manual");
359         map.put(4, "vacation");
360         map.put(6, "frostProtection");
361         map.put(8, "boost");
362         map.put(9, "eco");
363         return map;
364     }
365
366     private static Map<String, Integer> createRegulationReverseMap() {
367         HashMap<String, Integer> map = new HashMap<>();
368         map.put("auto", 1);
369         map.put("comfort", 2);
370         map.put("manual", 3);
371         map.put("vacation", 4);
372         map.put("frostprotection", 6);
373         map.put("boost", 8);
374         map.put("eco", 9);
375         return map;
376     }
377
378     private Map<String, Consumer<ThermostatModel>> createChannelRefreshActionMap() {
379         HashMap<String, Consumer<ThermostatModel>> map = new HashMap<>();
380         map.put(BindingConstants.CHANNEL_OWD5_GROUPNAME, this::updateGroupName);
381         map.put(BindingConstants.CHANNEL_OWD5_GROUPID, this::updateGroupId);
382         map.put(BindingConstants.CHANNEL_OWD5_ONLINE, this::updateOnline);
383         map.put(BindingConstants.CHANNEL_OWD5_HEATING, this::updateHeating);
384         map.put(BindingConstants.CHANNEL_OWD5_ROOMTEMPERATURE, this::updateRoomTemperature);
385         map.put(BindingConstants.CHANNEL_OWD5_FLOORTEMPERATURE, this::updateFloorTemperature);
386         map.put(BindingConstants.CHANNEL_OWD5_THERMOSTATNAME, this::updateThermostatName);
387         map.put(BindingConstants.CHANNEL_OWD5_REGULATIONMODE, this::updateRegulationMode);
388         map.put(BindingConstants.CHANNEL_OWD5_COMFORTSETPOINT, this::updateComfortSetpoint);
389         map.put(BindingConstants.CHANNEL_OWD5_COMFORTENDTIME, this::updateComfortEndTime);
390         map.put(BindingConstants.CHANNEL_OWD5_BOOSTENDTIME, this::updateBoostEndTime);
391         map.put(BindingConstants.CHANNEL_OWD5_MANUALSETPOINT, this::updateManualSetpoint);
392         map.put(BindingConstants.CHANNEL_OWD5_VACATIONENABLED, this::updateVacationEnabled);
393         map.put(BindingConstants.CHANNEL_OWD5_VACATIONBEGINDAY, this::updateVacationBeginDay);
394         map.put(BindingConstants.CHANNEL_OWD5_VACATIONENDDAY, this::updateVacationEndDay);
395         return map;
396     }
397
398     private Map<String, Consumer<ThermostatRealTimeValuesModel>> createRealTimeChannelRefreshActionMap() {
399         HashMap<String, Consumer<ThermostatRealTimeValuesModel>> map = new HashMap<>();
400         map.put(BindingConstants.CHANNEL_OWD5_ONLINE, this::updateOnline);
401         map.put(BindingConstants.CHANNEL_OWD5_HEATING, this::updateHeating);
402         map.put(BindingConstants.CHANNEL_OWD5_ROOMTEMPERATURE, this::updateRoomTemperature);
403         map.put(BindingConstants.CHANNEL_OWD5_FLOORTEMPERATURE, this::updateFloorTemperature);
404         return map;
405     }
406
407     private Map<String, Consumer<Command>> createUpdateThermostatValueActionMap() {
408         HashMap<String, Consumer<Command>> map = new HashMap<>();
409         map.put(BindingConstants.CHANNEL_OWD5_REGULATIONMODE, this::updateRegulationMode);
410         map.put(BindingConstants.CHANNEL_OWD5_MANUALSETPOINT, this::updateManualSetpoint);
411         map.put(BindingConstants.CHANNEL_OWD5_BOOSTENDTIME, this::updateBoostEndTime);
412         map.put(BindingConstants.CHANNEL_OWD5_COMFORTENDTIME, this::updateComfortEndTime);
413         map.put(BindingConstants.CHANNEL_OWD5_COMFORTSETPOINT, this::updateComfortSetpoint);
414         map.put(BindingConstants.CHANNEL_OWD5_VACATIONBEGINDAY, this::updateVacationBeginDay);
415         map.put(BindingConstants.CHANNEL_OWD5_VACATIONENDDAY, this::updateVacationEndDay);
416         return map;
417     }
418 }