]> git.basschouten.com Git - openhab-addons.git/blob
26e261ec545465370e07b6468c4b8ace578382ac
[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.broadlinkthermostat.internal.handler;
14
15 import static org.openhab.binding.broadlinkthermostat.internal.BroadlinkBindingConstants.*;
16
17 import java.io.IOException;
18 import java.time.LocalTime;
19 import java.time.ZonedDateTime;
20 import java.util.concurrent.ScheduledFuture;
21 import java.util.concurrent.TimeUnit;
22
23 import org.eclipse.jdt.annotation.NonNullByDefault;
24 import org.eclipse.jdt.annotation.Nullable;
25 import org.openhab.core.cache.ExpiringCache;
26 import org.openhab.core.library.types.DateTimeType;
27 import org.openhab.core.library.types.OnOffType;
28 import org.openhab.core.library.types.QuantityType;
29 import org.openhab.core.library.types.StringType;
30 import org.openhab.core.library.unit.SIUnits;
31 import org.openhab.core.thing.ChannelUID;
32 import org.openhab.core.thing.Thing;
33 import org.openhab.core.thing.ThingStatus;
34 import org.openhab.core.thing.ThingStatusDetail;
35 import org.openhab.core.types.Command;
36 import org.openhab.core.types.RefreshType;
37 import org.slf4j.Logger;
38 import org.slf4j.LoggerFactory;
39
40 import com.github.mob41.blapi.FloureonDevice;
41 import com.github.mob41.blapi.dev.hysen.AdvancedStatusInfo;
42 import com.github.mob41.blapi.dev.hysen.BaseStatusInfo;
43 import com.github.mob41.blapi.dev.hysen.SensorControl;
44 import com.github.mob41.blapi.mac.Mac;
45 import com.github.mob41.blapi.pkt.cmd.hysen.SetTimeCommand;
46
47 /**
48  * The {@link FloureonThermostatHandler} is responsible for handling thermostats labeled as Floureon Thermostat.
49  *
50  * @author Florian Mueller - Initial contribution
51  */
52 @NonNullByDefault
53 public class FloureonThermostatHandler extends BroadlinkBaseHandler {
54
55     private final Logger logger = LoggerFactory.getLogger(FloureonThermostatHandler.class);
56     private @Nullable FloureonDevice floureonDevice;
57
58     private static final long CACHE_EXPIRY = TimeUnit.SECONDS.toSeconds(3);
59     private final ExpiringCache<AdvancedStatusInfo> advancedStatusInfoExpiringCache = new ExpiringCache<>(CACHE_EXPIRY,
60             this::refreshAdvancedStatus);
61
62     private @Nullable ScheduledFuture<?> scanJob;
63
64     /**
65      * Creates a new instance of this class for the {@link FloureonThermostatHandler}.
66      *
67      * @param thing the thing that should be handled, not null
68      */
69     public FloureonThermostatHandler(Thing thing) {
70         super(thing);
71     }
72
73     /**
74      * Initializes a new instance of a {@link FloureonThermostatHandler}.
75      */
76     @Override
77     public void initialize() {
78         super.initialize();
79         // schedule a new scan every minute
80         scanJob = scheduler.scheduleWithFixedDelay(this::refreshData, 0, 1, TimeUnit.MINUTES);
81         if (!host.isBlank() && !macAddress.isBlank()) {
82             try {
83                 blDevice = new FloureonDevice(host, new Mac(macAddress));
84                 this.floureonDevice = (FloureonDevice) blDevice;
85                 updateStatus(ThingStatus.ONLINE);
86             } catch (IOException e) {
87                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
88                         "Could not find broadlink device at host " + host + " with MAC " + macAddress + ": "
89                                 + e.getMessage());
90             }
91         } else {
92             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Missing device configuration");
93         }
94     }
95
96     @Override
97     public void handleCommand(ChannelUID channelUID, Command command) {
98         logger.debug("Command: {}", command.toFullString());
99         authenticate(false);
100
101         if (command == RefreshType.REFRESH) {
102             refreshData();
103             return;
104         }
105
106         switch (channelUID.getIdWithoutGroup()) {
107             case SETPOINT:
108                 handleSetpointCommand(channelUID, command);
109                 break;
110             case POWER:
111                 handlePowerCommand(channelUID, command);
112                 break;
113             case MODE:
114                 handleModeCommand(channelUID, command);
115                 break;
116             case SENSOR:
117                 handleSensorCommand(channelUID, command);
118                 break;
119             case REMOTE_LOCK:
120                 handleRemoteLockCommand(channelUID, command);
121                 break;
122             case TIME:
123                 handleSetTimeCommand(channelUID, command);
124                 break;
125             default:
126                 logger.warn("Channel {} does not support command {}", channelUID, command);
127         }
128     }
129
130     private void handlePowerCommand(ChannelUID channelUID, Command command) {
131         FloureonDevice floureonDevice = this.floureonDevice;
132         if (command instanceof OnOffType && floureonDevice != null) {
133             try {
134                 floureonDevice.setPower(command == OnOffType.ON);
135             } catch (Exception e) {
136                 logger.warn("Error while setting power of {} to {}: {}", thing.getUID(), command, e.getMessage());
137             }
138         } else {
139             logger.warn("Channel {} does not support command {}", channelUID, command);
140         }
141     }
142
143     private void handleModeCommand(ChannelUID channelUID, Command command) {
144         FloureonDevice floureonDevice = this.floureonDevice;
145         if (command instanceof StringType && floureonDevice != null) {
146             try {
147                 if (MODE_AUTO.equals(command.toFullString())) {
148                     floureonDevice.switchToAuto();
149                 } else {
150                     floureonDevice.switchToManual();
151                 }
152             } catch (Exception e) {
153                 logger.warn("Error while setting power off {} to {}: {}", thing.getUID(), command, e.getMessage());
154             }
155         } else {
156             logger.warn("Channel {} does not support command {}", channelUID, command);
157         }
158     }
159
160     private void handleSetpointCommand(ChannelUID channelUID, Command command) {
161         FloureonDevice floureonDevice = this.floureonDevice;
162         if (command instanceof QuantityType && floureonDevice != null) {
163             try {
164                 QuantityType<?> temperatureQuantityType = ((QuantityType<?>) command).toUnit(SIUnits.CELSIUS);
165                 if (temperatureQuantityType != null) {
166                     floureonDevice.setThermostatTemp(temperatureQuantityType.doubleValue());
167                 } else {
168                     logger.warn("Could not convert {} to °C", command);
169                 }
170             } catch (Exception e) {
171                 logger.warn("Error while setting setpoint of {} to {}: {}", thing.getUID(), command, e.getMessage());
172             }
173
174         } else {
175             logger.warn("Channel {} does not support command {}", channelUID, command);
176         }
177     }
178
179     private void handleSensorCommand(ChannelUID channelUID, Command command) {
180         FloureonDevice floureonDevice = this.floureonDevice;
181         if (command instanceof StringType && floureonDevice != null) {
182             try {
183                 BaseStatusInfo statusInfo = floureonDevice.getBasicStatus();
184                 if (SENSOR_INTERNAL.equals(command.toFullString())) {
185                     floureonDevice.setMode(statusInfo.getAutoMode(), statusInfo.getLoopMode(), SensorControl.INTERNAL);
186                 } else if (SENSOR_EXTERNAL.equals(command.toFullString())) {
187                     floureonDevice.setMode(statusInfo.getAutoMode(), statusInfo.getLoopMode(), SensorControl.EXTERNAL);
188                 } else {
189                     floureonDevice.setMode(statusInfo.getAutoMode(), statusInfo.getLoopMode(),
190                             SensorControl.INTERNAL_TEMP_EXTERNAL_LIMIT);
191                 }
192             } catch (Exception e) {
193                 logger.warn("Error while trying to set sensor mode {}: {}", command, e.getMessage());
194             }
195         } else {
196             logger.warn("Channel {} does not support command {}", channelUID, command);
197         }
198     }
199
200     private void handleRemoteLockCommand(ChannelUID channelUID, Command command) {
201         FloureonDevice floureonDevice = this.floureonDevice;
202         if (command instanceof OnOffType && floureonDevice != null) {
203             try {
204                 floureonDevice.setLock(command == OnOffType.ON);
205             } catch (Exception e) {
206                 logger.warn("Error while setting remote lock of {} to {}: {}", thing.getUID(), command, e.getMessage());
207             }
208         } else {
209             logger.warn("Channel {} does not support command {}", channelUID, command);
210         }
211     }
212
213     private void handleSetTimeCommand(ChannelUID channelUID, Command command) {
214         if (command instanceof DateTimeType) {
215             ZonedDateTime zonedDateTime = ((DateTimeType) command).getZonedDateTime();
216             try {
217                 new SetTimeCommand(tob(zonedDateTime.getHour()), tob(zonedDateTime.getMinute()),
218                         tob(zonedDateTime.getSecond()), tob(zonedDateTime.getDayOfWeek().getValue()))
219                         .execute(floureonDevice);
220             } catch (Exception e) {
221                 logger.warn("Error while setting time of {} to {}: {}", thing.getUID(), command, e.getMessage());
222             }
223         } else {
224             logger.warn("Channel {} does not support command {}", channelUID, command);
225         }
226     }
227
228     @Nullable
229     private AdvancedStatusInfo refreshAdvancedStatus() {
230         if (ThingStatus.ONLINE != thing.getStatus()) {
231             return null;
232         }
233
234         FloureonDevice floureonDevice = this.floureonDevice;
235         if (floureonDevice != null) {
236             try {
237                 AdvancedStatusInfo advancedStatusInfo = floureonDevice.getAdvancedStatus();
238                 if (advancedStatusInfo == null) {
239                     logger.warn("Device {} did not return any data. Trying to reauthenticate...", thing.getUID());
240                     authenticate(true);
241                     advancedStatusInfo = floureonDevice.getAdvancedStatus();
242                 }
243                 if (advancedStatusInfo == null) {
244                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Device not responding.");
245                     return null;
246                 }
247                 return advancedStatusInfo;
248             } catch (Exception e) {
249                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
250                         "Error while retrieving data for " + thing.getUID() + ":  " + e.getMessage());
251             }
252         }
253         return null;
254     }
255
256     @Override
257     public void dispose() {
258         ScheduledFuture<?> currentScanJob = scanJob;
259         if (currentScanJob != null) {
260             currentScanJob.cancel(true);
261         }
262     }
263
264     protected void refreshData() {
265
266         AdvancedStatusInfo advancedStatusInfo = advancedStatusInfoExpiringCache.getValue();
267         if (advancedStatusInfo == null) {
268             return;
269         }
270         logger.trace("Retrieved data from device {}: {}", thing.getUID(), advancedStatusInfo);
271         updateState(ROOM_TEMPERATURE, new QuantityType<>(advancedStatusInfo.getRoomTemp(), SIUnits.CELSIUS));
272         updateState(ROOM_TEMPERATURE_EXTERNAL_SENSOR,
273                 new QuantityType<>(advancedStatusInfo.getExternalTemp(), SIUnits.CELSIUS));
274         updateState(SETPOINT, new QuantityType<>(advancedStatusInfo.getThermostatTemp(), SIUnits.CELSIUS));
275         updateState(POWER, OnOffType.from(advancedStatusInfo.getPower()));
276         updateState(MODE, StringType.valueOf(advancedStatusInfo.getAutoMode() ? "auto" : "manual"));
277         updateState(SENSOR, StringType.valueOf(advancedStatusInfo.getSensorControl().name()));
278         updateState(TEMPERATURE_OFFSET, new QuantityType<>(advancedStatusInfo.getDif(), SIUnits.CELSIUS));
279         updateState(ACTIVE, OnOffType.from(advancedStatusInfo.getActive()));
280         updateState(REMOTE_LOCK, OnOffType.from(advancedStatusInfo.getRemoteLock()));
281         updateState(TIME, new DateTimeType(getTimestamp(advancedStatusInfo)));
282     }
283
284     private ZonedDateTime getTimestamp(AdvancedStatusInfo advancedStatusInfo) {
285         ZonedDateTime now = ZonedDateTime.now();
286         return now.with(
287                 LocalTime.of(advancedStatusInfo.getHour(), advancedStatusInfo.getMin(), advancedStatusInfo.getSec()));
288     }
289
290     private static byte tob(int in) {
291         return (byte) (in & 0xff);
292     }
293 }