2 * Copyright (c) 2010-2021 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.broadlinkthermostat.internal.handler;
15 import static org.openhab.binding.broadlinkthermostat.internal.BroadlinkThermostatBindingConstants.*;
17 import java.io.IOException;
18 import java.time.LocalTime;
19 import java.time.ZonedDateTime;
20 import java.util.concurrent.TimeUnit;
22 import org.eclipse.jdt.annotation.NonNullByDefault;
23 import org.eclipse.jdt.annotation.Nullable;
24 import org.openhab.core.cache.ExpiringCache;
25 import org.openhab.core.library.types.DateTimeType;
26 import org.openhab.core.library.types.OnOffType;
27 import org.openhab.core.library.types.QuantityType;
28 import org.openhab.core.library.types.StringType;
29 import org.openhab.core.library.unit.SIUnits;
30 import org.openhab.core.thing.ChannelUID;
31 import org.openhab.core.thing.Thing;
32 import org.openhab.core.thing.ThingStatus;
33 import org.openhab.core.thing.ThingStatusDetail;
34 import org.openhab.core.types.Command;
35 import org.openhab.core.types.RefreshType;
36 import org.slf4j.Logger;
37 import org.slf4j.LoggerFactory;
39 import com.github.mob41.blapi.FloureonDevice;
40 import com.github.mob41.blapi.dev.hysen.AdvancedStatusInfo;
41 import com.github.mob41.blapi.dev.hysen.BaseStatusInfo;
42 import com.github.mob41.blapi.dev.hysen.SensorControl;
43 import com.github.mob41.blapi.mac.Mac;
44 import com.github.mob41.blapi.pkt.cmd.hysen.SetTimeCommand;
47 * The {@link FloureonThermostatHandler} is responsible for handling thermostats labeled as Floureon Thermostat.
49 * @author Florian Mueller - Initial contribution
52 public class FloureonThermostatHandler extends BroadlinkThermostatHandler {
54 private final Logger logger = LoggerFactory.getLogger(FloureonThermostatHandler.class);
55 private @Nullable FloureonDevice floureonDevice;
57 private static final long CACHE_EXPIRY = TimeUnit.SECONDS.toSeconds(3);
58 private final ExpiringCache<AdvancedStatusInfo> advancedStatusInfoExpiringCache = new ExpiringCache<>(CACHE_EXPIRY,
59 this::refreshAdvancedStatus);
62 * Creates a new instance of this class for the {@link FloureonThermostatHandler}.
64 * @param thing the thing that should be handled, not null
66 public FloureonThermostatHandler(Thing thing) {
71 * Initializes a new instance of a {@link FloureonThermostatHandler}.
74 public void initialize() {
76 if (host != null && macAddress != null) {
78 blDevice = new FloureonDevice(host, new Mac(macAddress));
79 this.floureonDevice = (FloureonDevice) blDevice;
80 updateStatus(ThingStatus.ONLINE);
81 } catch (IOException e) {
82 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
83 "Could not find broadlinkthermostat device at host" + host + "with MAC+" + macAddress + ": "
90 public void handleCommand(ChannelUID channelUID, Command command) {
91 logger.debug("Command: {}", command.toFullString());
94 if (command == RefreshType.REFRESH) {
99 switch (channelUID.getIdWithoutGroup()) {
101 handleSetpointCommand(channelUID, command);
104 handlePowerCommand(channelUID, command);
107 handleModeCommand(channelUID, command);
110 handleSensorCommand(channelUID, command);
113 handleRemoteLockCommand(channelUID, command);
116 handleSetTimeCommand(channelUID, command);
119 logger.warn("Channel {} does not support command {}", channelUID, command);
123 private void handlePowerCommand(ChannelUID channelUID, Command command) {
124 FloureonDevice floureonDevice = this.floureonDevice;
125 if (command instanceof OnOffType && floureonDevice != null) {
127 floureonDevice.setPower(command == OnOffType.ON);
128 } catch (Exception e) {
129 logger.warn("Error while setting power of {} to {}: {}", thing.getUID(), command, e.getMessage());
132 logger.warn("Channel {} does not support command {}", channelUID, command);
136 private void handleModeCommand(ChannelUID channelUID, Command command) {
137 FloureonDevice floureonDevice = this.floureonDevice;
138 if (command instanceof StringType && floureonDevice != null) {
140 if (MODE_AUTO.equals(command.toFullString())) {
141 floureonDevice.switchToAuto();
143 floureonDevice.switchToManual();
145 } catch (Exception e) {
146 logger.warn("Error while setting power off {} to {}: {}", thing.getUID(), command, e.getMessage());
149 logger.warn("Channel {} does not support command {}", channelUID, command);
153 private void handleSetpointCommand(ChannelUID channelUID, Command command) {
154 FloureonDevice floureonDevice = this.floureonDevice;
155 if (command instanceof QuantityType && floureonDevice != null) {
157 QuantityType<?> temperatureQuantityType = ((QuantityType<?>) command).toUnit(SIUnits.CELSIUS);
158 if (temperatureQuantityType != null) {
159 floureonDevice.setThermostatTemp(temperatureQuantityType.doubleValue());
161 logger.warn("Could not convert {} to °C", command);
163 } catch (Exception e) {
164 logger.warn("Error while setting setpoint of {} to {}: {}", thing.getUID(), command, e.getMessage());
168 logger.warn("Channel {} does not support command {}", channelUID, command);
172 private void handleSensorCommand(ChannelUID channelUID, Command command) {
173 FloureonDevice floureonDevice = this.floureonDevice;
174 if (command instanceof StringType && floureonDevice != null) {
176 BaseStatusInfo statusInfo = floureonDevice.getBasicStatus();
177 if (SENSOR_INTERNAL.equals(command.toFullString())) {
178 floureonDevice.setMode(statusInfo.getAutoMode(), statusInfo.getLoopMode(), SensorControl.INTERNAL);
179 } else if (SENSOR_EXTERNAL.equals(command.toFullString())) {
180 floureonDevice.setMode(statusInfo.getAutoMode(), statusInfo.getLoopMode(), SensorControl.EXTERNAL);
182 floureonDevice.setMode(statusInfo.getAutoMode(), statusInfo.getLoopMode(),
183 SensorControl.INTERNAL_TEMP_EXTERNAL_LIMIT);
185 } catch (Exception e) {
186 logger.warn("Error while trying to set sensor mode {}: {}", command, e.getMessage());
189 logger.warn("Channel {} does not support command {}", channelUID, command);
193 private void handleRemoteLockCommand(ChannelUID channelUID, Command command) {
194 FloureonDevice floureonDevice = this.floureonDevice;
195 if (command instanceof OnOffType && floureonDevice != null) {
197 floureonDevice.setLock(command == OnOffType.ON);
198 } catch (Exception e) {
199 logger.warn("Error while setting remote lock of {} to {}: {}", thing.getUID(), command, e.getMessage());
202 logger.warn("Channel {} does not support command {}", channelUID, command);
206 private void handleSetTimeCommand(ChannelUID channelUID, Command command) {
207 if (command instanceof DateTimeType) {
208 ZonedDateTime zonedDateTime = ((DateTimeType) command).getZonedDateTime();
210 new SetTimeCommand(tob(zonedDateTime.getHour()), tob(zonedDateTime.getMinute()),
211 tob(zonedDateTime.getSecond()), tob(zonedDateTime.getDayOfWeek().getValue()))
212 .execute(floureonDevice);
213 } catch (Exception e) {
214 logger.warn("Error while setting time of {} to {}: {}", thing.getUID(), command, e.getMessage());
217 logger.warn("Channel {} does not support command {}", channelUID, command);
222 private AdvancedStatusInfo refreshAdvancedStatus() {
223 if (ThingStatus.ONLINE != thing.getStatus()) {
227 FloureonDevice floureonDevice = this.floureonDevice;
228 if (floureonDevice != null) {
230 AdvancedStatusInfo advancedStatusInfo = floureonDevice.getAdvancedStatus();
231 if (advancedStatusInfo == null) {
232 logger.warn("Device {} did not return any data. Trying to reauthenticate...", thing.getUID());
234 advancedStatusInfo = floureonDevice.getAdvancedStatus();
236 if (advancedStatusInfo == null) {
237 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Device not responding.");
240 return advancedStatusInfo;
241 } catch (Exception e) {
242 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
243 "Error while retrieving data for " + thing.getUID() + ": " + e.getMessage());
250 protected void refreshData() {
252 AdvancedStatusInfo advancedStatusInfo = advancedStatusInfoExpiringCache.getValue();
253 if (advancedStatusInfo == null) {
256 logger.trace("Retrieved data from device {}: {}", thing.getUID(), advancedStatusInfo);
257 updateState(ROOM_TEMPERATURE, new QuantityType<>(advancedStatusInfo.getRoomTemp(), SIUnits.CELSIUS));
258 updateState(ROOM_TEMPERATURE_EXTERNAL_SENSOR,
259 new QuantityType<>(advancedStatusInfo.getExternalTemp(), SIUnits.CELSIUS));
260 updateState(SETPOINT, new QuantityType<>(advancedStatusInfo.getThermostatTemp(), SIUnits.CELSIUS));
261 updateState(POWER, OnOffType.from(advancedStatusInfo.getPower()));
262 updateState(MODE, StringType.valueOf(advancedStatusInfo.getAutoMode() ? "auto" : "manual"));
263 updateState(SENSOR, StringType.valueOf(advancedStatusInfo.getSensorControl().name()));
264 updateState(TEMPERATURE_OFFSET, new QuantityType<>(advancedStatusInfo.getDif(), SIUnits.CELSIUS));
265 updateState(ACTIVE, OnOffType.from(advancedStatusInfo.getActive()));
266 updateState(REMOTE_LOCK, OnOffType.from(advancedStatusInfo.getRemoteLock()));
267 updateState(TIME, new DateTimeType(getTimestamp(advancedStatusInfo)));
270 private ZonedDateTime getTimestamp(AdvancedStatusInfo advancedStatusInfo) {
271 ZonedDateTime now = ZonedDateTime.now();
273 LocalTime.of(advancedStatusInfo.getHour(), advancedStatusInfo.getMin(), advancedStatusInfo.getSec()));
276 private static byte tob(int in) {
277 return (byte) (in & 0xff);