2 * Copyright (c) 2010-2024 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.ojelectronics.internal;
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;
23 import java.util.Objects;
24 import java.util.function.Consumer;
26 import javax.measure.quantity.Temperature;
28 import org.eclipse.jdt.annotation.NonNullByDefault;
29 import org.eclipse.jdt.annotation.Nullable;
30 import org.openhab.binding.ojelectronics.internal.config.OJElectronicsThermostatConfiguration;
31 import org.openhab.binding.ojelectronics.internal.models.thermostat.ThermostatModel;
32 import org.openhab.binding.ojelectronics.internal.models.thermostat.ThermostatRealTimeValuesModel;
33 import org.openhab.core.i18n.TimeZoneProvider;
34 import org.openhab.core.library.types.DateTimeType;
35 import org.openhab.core.library.types.DecimalType;
36 import org.openhab.core.library.types.OpenClosedType;
37 import org.openhab.core.library.types.QuantityType;
38 import org.openhab.core.library.types.StringType;
39 import org.openhab.core.library.unit.SIUnits;
40 import org.openhab.core.thing.Bridge;
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.thing.binding.BridgeHandler;
46 import org.openhab.core.types.Command;
47 import org.openhab.core.types.RefreshType;
48 import org.slf4j.Logger;
49 import org.slf4j.LoggerFactory;
52 * The {@link ThermostatHandler} is responsible for handling commands, which are
53 * sent to one of the channels.
55 * @author Christian Kittel - Initial contribution
58 public class ThermostatHandler extends BaseThingHandler {
60 private static final Map<Integer, String> REGULATION_MODES = createRegulationMap();
61 private static final Map<String, Integer> REVERSE_REGULATION_MODES = createRegulationReverseMap();
63 private final String serialNumber;
64 private final Logger logger = LoggerFactory.getLogger(ThermostatHandler.class);
65 private final Map<String, Consumer<ThermostatModel>> channelRefreshActions = createChannelRefreshActionMap();
66 private final Map<String, Consumer<ThermostatRealTimeValuesModel>> channelRealTimeRefreshActions = createRealTimeChannelRefreshActionMap();
67 private final Map<String, Consumer<Command>> updateThermostatValueActions = createUpdateThermostatValueActionMap();
68 private final TimeZoneProvider timeZoneProvider;
70 private LinkedList<AbstractMap.SimpleImmutableEntry<String, Command>> updatedValues = new LinkedList<>();
71 private @Nullable ThermostatModel currentThermostat;
74 * Creates a new instance of {@link ThermostatHandler}
77 * @param timeZoneProvider Time zone
79 public ThermostatHandler(Thing thing, TimeZoneProvider timeZoneProvider) {
81 serialNumber = getConfigAs(OJElectronicsThermostatConfiguration.class).serialNumber;
82 this.timeZoneProvider = timeZoneProvider;
86 * Gets the thing's serial number.
88 * @return serial number
90 public String getSerialNumber() {
95 * Handles commands to this thing.
98 public void handleCommand(ChannelUID channelUID, Command command) {
99 if (command instanceof RefreshType) {
100 final ThermostatModel thermostat = currentThermostat;
101 if (thermostat != null && channelRefreshActions.containsKey(channelUID.getId())) {
102 final @Nullable Consumer<ThermostatModel> consumer = channelRefreshActions.get(channelUID.getId());
103 if (consumer != null) {
104 consumer.accept(thermostat);
108 synchronized (this) {
109 updatedValues.add(new AbstractMap.SimpleImmutableEntry<String, Command>(channelUID.getId(), command));
111 BridgeHandler bridgeHandler = Objects.requireNonNull(getBridge()).getHandler();
112 if (bridgeHandler != null) {
113 ((OJCloudHandler) (bridgeHandler)).updateThinksChannelValuesToCloud();
115 currentThermostat = null;
116 updateStatus(ThingStatus.OFFLINE);
123 * Initializes the thing handler.
126 public void initialize() {
128 Bridge bridge = getBridge();
129 if (bridge != null && bridge.getStatus() == ThingStatus.ONLINE && currentThermostat == null) {
131 OJCloudHandler bridgeHandler = (OJCloudHandler) (bridge.getHandler());
132 if (bridgeHandler != null) {
133 bridgeHandler.reInitialize();
139 * Sets the values after refreshing the thermostats values
141 * @param thermostat thermostat values
143 public void handleThermostatRefresh(ThermostatModel thermostat) {
144 if (currentThermostat == null) {
145 updateStatus(ThingStatus.ONLINE);
147 currentThermostat = thermostat;
148 channelRefreshActions.forEach((channelUID, action) -> action.accept(thermostat));
152 * Sets the values after refreshing the thermostats values
154 * @param thermostat thermostat values
156 public void handleThermostatRefresh(ThermostatRealTimeValuesModel thermostat) {
157 final ThermostatModel currentThermostat = this.currentThermostat;
158 if (currentThermostat != null) {
159 currentThermostat.heating = thermostat.heating;
160 currentThermostat.floorTemperature = thermostat.floorTemperature;
161 currentThermostat.action = thermostat.action;
162 currentThermostat.online = thermostat.online;
163 currentThermostat.roomTemperature = thermostat.roomTemperature;
164 channelRealTimeRefreshActions.forEach((channelUID, action) -> action.accept(thermostat));
169 * Gets a {@link ThermostatModel} with changed values or null if nothing has changed
171 * @return The changed {@link ThermostatModel}
173 public @Nullable ThermostatModel tryHandleAndGetUpdatedThermostat() {
174 final LinkedList<SimpleImmutableEntry<String, Command>> updatedValues = this.updatedValues;
175 if (updatedValues.isEmpty()) {
178 this.updatedValues = new LinkedList<>();
179 updatedValues.forEach(item -> {
180 if (updateThermostatValueActions.containsKey(item.getKey())) {
181 final @Nullable Consumer<Command> consumer = updateThermostatValueActions.get(item.getKey());
182 if (consumer != null) {
183 consumer.accept(item.getValue());
187 return currentThermostat;
190 private ThermostatModel getCurrentThermostat() {
191 return Objects.requireNonNull(currentThermostat);
194 private void updateManualSetpoint(ThermostatModel thermostat) {
195 updateState(BindingConstants.CHANNEL_OWD5_MANUALSETPOINT,
196 new QuantityType<Temperature>(thermostat.manualModeSetpoint / (double) 100, SIUnits.CELSIUS));
199 private void updateManualSetpoint(Command command) {
200 if (command instanceof QuantityType<?> quantityCommand) {
201 getCurrentThermostat().manualModeSetpoint = (int) (quantityCommand.floatValue() * 100);
203 logger.warn("Unable to set value {}", command);
207 private void updateBoostEndTime(ThermostatModel thermostat) {
208 updateState(BindingConstants.CHANNEL_OWD5_BOOSTENDTIME, new DateTimeType(
209 ZonedDateTime.ofInstant(thermostat.boostEndTime.toInstant(), timeZoneProvider.getTimeZone())));
212 private void updateBoostEndTime(Command command) {
213 if (command instanceof DateTimeType dateTimeCommand) {
214 getCurrentThermostat().boostEndTime = Date.from(dateTimeCommand.getZonedDateTime().toInstant());
216 logger.warn("Unable to set value {}", command);
220 private void updateComfortEndTime(ThermostatModel thermostat) {
221 updateState(BindingConstants.CHANNEL_OWD5_COMFORTENDTIME, new DateTimeType(
222 ZonedDateTime.ofInstant(thermostat.comfortEndTime.toInstant(), timeZoneProvider.getTimeZone())));
225 private void updateComfortEndTime(Command command) {
226 if (command instanceof DateTimeType dateTimeCommand) {
227 getCurrentThermostat().comfortEndTime = Objects
228 .requireNonNull(Date.from(dateTimeCommand.getZonedDateTime().toInstant()));
230 logger.warn("Unable to set value {}", command);
234 private void updateComfortSetpoint(ThermostatModel thermostat) {
235 updateState(BindingConstants.CHANNEL_OWD5_COMFORTSETPOINT,
236 new QuantityType<Temperature>(thermostat.comfortSetpoint / (double) 100, SIUnits.CELSIUS));
239 private void updateComfortSetpoint(Command command) {
240 if (command instanceof QuantityType<?> quantityCommand) {
241 getCurrentThermostat().comfortSetpoint = (int) (quantityCommand.floatValue() * 100);
243 logger.warn("Unable to set value {}", command);
247 private void updateRegulationMode(ThermostatModel thermostat) {
248 updateState(BindingConstants.CHANNEL_OWD5_REGULATIONMODE,
249 StringType.valueOf(getRegulationMode(thermostat.regulationMode)));
252 private void updateRegulationMode(Command command) {
253 if (command instanceof StringType && (REVERSE_REGULATION_MODES.containsKey(command.toString().toLowerCase()))) {
254 final @Nullable Integer mode = REVERSE_REGULATION_MODES.get(command.toString().toLowerCase());
256 getCurrentThermostat().regulationMode = mode;
259 logger.warn("Unable to set value {}", command);
263 private void updateThermostatName(ThermostatModel thermostat) {
264 updateState(BindingConstants.CHANNEL_OWD5_THERMOSTATNAME, StringType.valueOf(thermostat.thermostatName));
267 private void updateFloorTemperature(ThermostatModel thermostat) {
268 updateState(BindingConstants.CHANNEL_OWD5_FLOORTEMPERATURE,
269 new QuantityType<Temperature>(thermostat.floorTemperature / (double) 100, SIUnits.CELSIUS));
272 private void updateFloorTemperature(ThermostatRealTimeValuesModel thermostatRealTimeValues) {
273 updateState(BindingConstants.CHANNEL_OWD5_FLOORTEMPERATURE, new QuantityType<Temperature>(
274 thermostatRealTimeValues.floorTemperature / (double) 100, SIUnits.CELSIUS));
277 private void updateRoomTemperature(ThermostatModel thermostat) {
278 updateState(BindingConstants.CHANNEL_OWD5_ROOMTEMPERATURE,
279 new QuantityType<Temperature>(thermostat.roomTemperature / (double) 100, SIUnits.CELSIUS));
282 private void updateRoomTemperature(ThermostatRealTimeValuesModel thermostatRealTimeValues) {
283 updateState(BindingConstants.CHANNEL_OWD5_ROOMTEMPERATURE, new QuantityType<Temperature>(
284 thermostatRealTimeValues.roomTemperature / (double) 100, SIUnits.CELSIUS));
287 private void updateHeating(ThermostatModel thermostat) {
288 updateState(BindingConstants.CHANNEL_OWD5_HEATING,
289 thermostat.heating ? OpenClosedType.OPEN : OpenClosedType.CLOSED);
292 private void updateHeating(ThermostatRealTimeValuesModel thermostatRealTimeValues) {
293 updateState(BindingConstants.CHANNEL_OWD5_HEATING,
294 thermostatRealTimeValues.heating ? OpenClosedType.OPEN : OpenClosedType.CLOSED);
297 private void updateOnline(ThermostatModel thermostat) {
298 updateState(BindingConstants.CHANNEL_OWD5_ONLINE,
299 thermostat.online ? OpenClosedType.OPEN : OpenClosedType.CLOSED);
302 private void updateOnline(ThermostatRealTimeValuesModel thermostatRealTimeValues) {
303 updateState(BindingConstants.CHANNEL_OWD5_ONLINE,
304 thermostatRealTimeValues.online ? OpenClosedType.OPEN : OpenClosedType.CLOSED);
307 private void updateGroupId(ThermostatModel thermostat) {
308 updateState(BindingConstants.CHANNEL_OWD5_GROUPID, new DecimalType(thermostat.groupId));
311 private void updateGroupName(ThermostatModel thermostat) {
312 updateState(BindingConstants.CHANNEL_OWD5_GROUPNAME, StringType.valueOf(thermostat.groupName));
315 private void updateVacationEnabled(ThermostatModel thermostat) {
316 updateState(BindingConstants.CHANNEL_OWD5_VACATIONENABLED,
317 thermostat.online ? OpenClosedType.OPEN : OpenClosedType.CLOSED);
320 private void updateVacationBeginDay(ThermostatModel thermostat) {
321 updateState(BindingConstants.CHANNEL_OWD5_VACATIONBEGINDAY,
323 ZonedDateTime.ofInstant(thermostat.vacationBeginDay.toInstant(), timeZoneProvider.getTimeZone())
324 .truncatedTo(ChronoUnit.DAYS)));
327 private void updateVacationBeginDay(Command command) {
328 if (command instanceof DateTimeType dateTimeCommand) {
329 getCurrentThermostat().vacationBeginDay = Date
330 .from(dateTimeCommand.getZonedDateTime().toInstant().truncatedTo(ChronoUnit.DAYS));
332 logger.warn("Unable to set value {}", command);
336 private void updateVacationEndDay(ThermostatModel thermostat) {
337 updateState(BindingConstants.CHANNEL_OWD5_VACATIONENDDAY,
339 ZonedDateTime.ofInstant(thermostat.vacationEndDay.toInstant(), timeZoneProvider.getTimeZone())
340 .truncatedTo(ChronoUnit.DAYS)));
343 private void updateVacationEndDay(Command command) {
344 if (command instanceof DateTimeType dateTimeCommand) {
345 getCurrentThermostat().vacationEndDay = Date
346 .from(dateTimeCommand.getZonedDateTime().toInstant().truncatedTo(ChronoUnit.DAYS));
348 logger.warn("Unable to set value {}", command);
352 private @Nullable String getRegulationMode(int regulationMode) {
353 return REGULATION_MODES.get(regulationMode);
356 private static Map<Integer, String> createRegulationMap() {
357 HashMap<Integer, String> map = new HashMap<>();
359 map.put(2, "comfort");
360 map.put(3, "manual");
361 map.put(4, "vacation");
362 map.put(6, "frostProtection");
368 private static Map<String, Integer> createRegulationReverseMap() {
369 HashMap<String, Integer> map = new HashMap<>();
371 map.put("comfort", 2);
372 map.put("manual", 3);
373 map.put("vacation", 4);
374 map.put("frostprotection", 6);
380 private Map<String, Consumer<ThermostatModel>> createChannelRefreshActionMap() {
381 HashMap<String, Consumer<ThermostatModel>> map = new HashMap<>();
382 map.put(BindingConstants.CHANNEL_OWD5_GROUPNAME, this::updateGroupName);
383 map.put(BindingConstants.CHANNEL_OWD5_GROUPID, this::updateGroupId);
384 map.put(BindingConstants.CHANNEL_OWD5_ONLINE, this::updateOnline);
385 map.put(BindingConstants.CHANNEL_OWD5_HEATING, this::updateHeating);
386 map.put(BindingConstants.CHANNEL_OWD5_ROOMTEMPERATURE, this::updateRoomTemperature);
387 map.put(BindingConstants.CHANNEL_OWD5_FLOORTEMPERATURE, this::updateFloorTemperature);
388 map.put(BindingConstants.CHANNEL_OWD5_THERMOSTATNAME, this::updateThermostatName);
389 map.put(BindingConstants.CHANNEL_OWD5_REGULATIONMODE, this::updateRegulationMode);
390 map.put(BindingConstants.CHANNEL_OWD5_COMFORTSETPOINT, this::updateComfortSetpoint);
391 map.put(BindingConstants.CHANNEL_OWD5_COMFORTENDTIME, this::updateComfortEndTime);
392 map.put(BindingConstants.CHANNEL_OWD5_BOOSTENDTIME, this::updateBoostEndTime);
393 map.put(BindingConstants.CHANNEL_OWD5_MANUALSETPOINT, this::updateManualSetpoint);
394 map.put(BindingConstants.CHANNEL_OWD5_VACATIONENABLED, this::updateVacationEnabled);
395 map.put(BindingConstants.CHANNEL_OWD5_VACATIONBEGINDAY, this::updateVacationBeginDay);
396 map.put(BindingConstants.CHANNEL_OWD5_VACATIONENDDAY, this::updateVacationEndDay);
400 private Map<String, Consumer<ThermostatRealTimeValuesModel>> createRealTimeChannelRefreshActionMap() {
401 HashMap<String, Consumer<ThermostatRealTimeValuesModel>> map = new HashMap<>();
402 map.put(BindingConstants.CHANNEL_OWD5_ONLINE, this::updateOnline);
403 map.put(BindingConstants.CHANNEL_OWD5_HEATING, this::updateHeating);
404 map.put(BindingConstants.CHANNEL_OWD5_ROOMTEMPERATURE, this::updateRoomTemperature);
405 map.put(BindingConstants.CHANNEL_OWD5_FLOORTEMPERATURE, this::updateFloorTemperature);
409 private Map<String, Consumer<Command>> createUpdateThermostatValueActionMap() {
410 HashMap<String, Consumer<Command>> map = new HashMap<>();
411 map.put(BindingConstants.CHANNEL_OWD5_REGULATIONMODE, this::updateRegulationMode);
412 map.put(BindingConstants.CHANNEL_OWD5_MANUALSETPOINT, this::updateManualSetpoint);
413 map.put(BindingConstants.CHANNEL_OWD5_BOOSTENDTIME, this::updateBoostEndTime);
414 map.put(BindingConstants.CHANNEL_OWD5_COMFORTENDTIME, this::updateComfortEndTime);
415 map.put(BindingConstants.CHANNEL_OWD5_COMFORTSETPOINT, this::updateComfortSetpoint);
416 map.put(BindingConstants.CHANNEL_OWD5_VACATIONBEGINDAY, this::updateVacationBeginDay);
417 map.put(BindingConstants.CHANNEL_OWD5_VACATIONENDDAY, this::updateVacationEndDay);