2 * Copyright (c) 2010-2023 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.mihome.internal.handler;
15 import static org.openhab.binding.mihome.internal.XiaomiGatewayBindingConstants.*;
16 import static org.openhab.core.library.unit.MetricPrefix.*;
18 import java.util.Arrays;
19 import java.util.HashSet;
21 import java.util.concurrent.ScheduledFuture;
22 import java.util.concurrent.TimeUnit;
24 import javax.measure.Unit;
25 import javax.measure.quantity.Angle;
26 import javax.measure.quantity.Dimensionless;
27 import javax.measure.quantity.Pressure;
28 import javax.measure.quantity.Temperature;
29 import javax.measure.quantity.Time;
31 import org.openhab.binding.mihome.internal.XiaomiItemUpdateListener;
32 import org.openhab.core.library.types.StringType;
33 import org.openhab.core.library.unit.SIUnits;
34 import org.openhab.core.library.unit.Units;
35 import org.openhab.core.thing.Bridge;
36 import org.openhab.core.thing.ChannelUID;
37 import org.openhab.core.thing.Thing;
38 import org.openhab.core.thing.ThingStatus;
39 import org.openhab.core.thing.ThingStatusDetail;
40 import org.openhab.core.thing.ThingTypeUID;
41 import org.openhab.core.thing.binding.BaseThingHandler;
42 import org.openhab.core.thing.binding.ThingHandler;
43 import org.openhab.core.types.Command;
44 import org.openhab.core.types.RefreshType;
45 import org.slf4j.Logger;
46 import org.slf4j.LoggerFactory;
48 import com.google.gson.JsonObject;
49 import com.google.gson.JsonParser;
50 import com.google.gson.JsonSyntaxException;
53 * The {@link XiaomiDeviceBaseHandler} is responsible for handling commands, which are
54 * sent to one of the channels.
56 * @author Patrick Boos - Initial contribution
57 * @author Kuba Wolanin - Added voltage and low battery report
58 * @author Dieter Schmidt - Added cube rotation, heartbeat and voltage handling, configurable window and motion delay,
61 * @author Daniel Walters - Added Aqara Door/Window sensor and Aqara temperature, humidity and pressure sensor
63 public class XiaomiDeviceBaseHandler extends BaseThingHandler implements XiaomiItemUpdateListener {
65 public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = new HashSet<>(Arrays.asList(THING_TYPE_GATEWAY,
66 THING_TYPE_SENSOR_HT, THING_TYPE_SENSOR_AQARA_WEATHER_V1, THING_TYPE_SENSOR_MOTION,
67 THING_TYPE_SENSOR_AQARA_MOTION, THING_TYPE_SENSOR_SWITCH, THING_TYPE_SENSOR_AQARA_SWITCH,
68 THING_TYPE_SENSOR_MAGNET, THING_TYPE_SENSOR_AQARA_LOCK, THING_TYPE_SENSOR_AQARA_MAGNET,
69 THING_TYPE_SENSOR_CUBE, THING_TYPE_SENSOR_AQARA_VIBRATION, THING_TYPE_SENSOR_AQARA1,
70 THING_TYPE_SENSOR_AQARA2, THING_TYPE_SENSOR_GAS, THING_TYPE_SENSOR_SMOKE, THING_TYPE_SENSOR_WATER,
71 THING_TYPE_ACTOR_AQARA1, THING_TYPE_ACTOR_AQARA2, THING_TYPE_ACTOR_PLUG, THING_TYPE_ACTOR_AQARA_ZERO1,
72 THING_TYPE_ACTOR_AQARA_ZERO2, THING_TYPE_ACTOR_CURTAIN, THING_TYPE_BASIC));
74 protected static final Unit<Temperature> TEMPERATURE_UNIT = SIUnits.CELSIUS;
75 protected static final Unit<Pressure> PRESSURE_UNIT = KILO(SIUnits.PASCAL);
76 protected static final Unit<Dimensionless> PERCENT_UNIT = Units.PERCENT;
77 protected static final Unit<Angle> ANGLE_UNIT = Units.DEGREE_ANGLE;
78 protected static final Unit<Time> TIME_UNIT = MILLI(Units.SECOND);
80 private static final String REMOVE_DEVICE = "remove_device";
81 private static final long ONLINE_TIMEOUT_MILLIS = TimeUnit.HOURS.toMillis(2);
82 private ScheduledFuture<?> onlineCheckTask;
84 private XiaomiBridgeHandler bridgeHandler;
86 private String itemId;
88 private final void setItemId(String itemId) {
92 private final Logger logger = LoggerFactory.getLogger(XiaomiDeviceBaseHandler.class);
94 public XiaomiDeviceBaseHandler(Thing thing) {
99 public void initialize() {
100 setItemId((String) getConfig().get(ITEM_ID));
101 onlineCheckTask = scheduler.scheduleWithFixedDelay(this::updateThingStatus, 0, ONLINE_TIMEOUT_MILLIS / 2,
102 TimeUnit.MILLISECONDS);
106 public void dispose() {
107 logger.debug("Handler disposes. Unregistering listener");
108 if (getItemId() != null) {
109 if (bridgeHandler != null) {
110 bridgeHandler.unregisterItemListener(this);
111 bridgeHandler = null;
115 if (!onlineCheckTask.isDone()) {
116 onlineCheckTask.cancel(false);
121 public void handleRemoval() {
122 getXiaomiBridgeHandler().writeToBridge(new String[] { REMOVE_DEVICE }, new Object[] { itemId });
123 super.handleRemoval();
127 public void handleCommand(ChannelUID channelUID, Command command) {
128 logger.debug("Device {} on channel {} received command {}", getItemId(), channelUID, command);
129 if (command instanceof RefreshType) {
130 JsonObject message = getXiaomiBridgeHandler().getDeferredMessage(getItemId());
131 if (message != null) {
132 String cmd = message.get("cmd").getAsString();
133 logger.debug("Update Item {} with retented message", getItemId());
134 onItemUpdate(getItemId(), cmd, message);
138 execute(channelUID, command);
142 public void onItemUpdate(String sid, String command, JsonObject message) {
143 if (getItemId() != null && getItemId().equals(sid)) {
144 if (getThing().getStatus() != ThingStatus.ONLINE) {
145 updateStatus(ThingStatus.ONLINE);
147 logger.debug("Item got update: {}", message);
149 JsonObject data = JsonParser.parseString(message.get("data").getAsString()).getAsJsonObject();
150 parseCommand(command, data);
151 if (THING_TYPE_BASIC.equals(getThing().getThingTypeUID())) {
152 parseDefault(message);
154 } catch (JsonSyntaxException e) {
155 logger.warn("Unable to parse message as valid JSON: {}", message);
161 public String getItemId() {
165 void parseCommand(String command, JsonObject data) {
171 parseHeartbeat(data);
180 logger.debug("Device {} got unknown command {}", getItemId(), command);
184 void parseReport(JsonObject data) {
185 updateState(CHANNEL_REPORT_MSG, StringType.valueOf(data.toString()));
188 void parseHeartbeat(JsonObject data) {
189 updateState(CHANNEL_HEARTBEAT_MSG, StringType.valueOf(data.toString()));
192 void parseReadAck(JsonObject data) {
193 updateState(CHANNEL_READ_ACK_MSG, StringType.valueOf(data.toString()));
196 void parseWriteAck(JsonObject data) {
197 updateState(CHANNEL_WRITE_ACK_MSG, StringType.valueOf(data.toString()));
200 void parseDefault(JsonObject data) {
201 updateState(CHANNEL_LAST_MSG, StringType.valueOf(data.toString()));
204 void execute(ChannelUID channelUID, Command command) {
205 if (CHANNEL_WRITE_MSG.equals(channelUID.getId())) {
206 if (command instanceof StringType) {
207 getXiaomiBridgeHandler().writeToDevice(itemId, ((StringType) command).toFullString());
209 logger.debug("Command \"{}\" has to be of StringType", command);
212 logger.debug("Received command on read-only channel, thus ignoring it.");
216 private void updateThingStatus() {
217 if (getItemId() != null) {
218 // note: this call implicitly registers our handler as a listener on the bridge, if it's not already
219 if (getXiaomiBridgeHandler() != null) {
220 Bridge bridge = getBridge();
221 ThingStatus bridgeStatus = (bridge == null) ? null : bridge.getStatus();
222 if (bridgeStatus == ThingStatus.ONLINE) {
223 ThingStatus itemStatus = getThing().getStatus();
224 boolean hasItemActivity = getXiaomiBridgeHandler().hasItemActivity(getItemId(),
225 ONLINE_TIMEOUT_MILLIS);
226 ThingStatus newStatus = hasItemActivity ? ThingStatus.ONLINE : ThingStatus.OFFLINE;
228 if (!newStatus.equals(itemStatus)) {
229 updateStatus(newStatus);
232 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
235 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
238 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR);
242 synchronized XiaomiBridgeHandler getXiaomiBridgeHandler() {
243 if (this.bridgeHandler == null) {
244 Bridge bridge = getBridge();
245 if (bridge == null) {
248 ThingHandler handler = bridge.getHandler();
249 if (handler instanceof XiaomiBridgeHandler) {
250 this.bridgeHandler = (XiaomiBridgeHandler) handler;
251 this.bridgeHandler.registerItemListener(this);
256 return this.bridgeHandler;