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.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 JsonParser parser = new JsonParser();
86 private XiaomiBridgeHandler bridgeHandler;
88 private String itemId;
90 private final void setItemId(String itemId) {
94 private final Logger logger = LoggerFactory.getLogger(XiaomiDeviceBaseHandler.class);
96 public XiaomiDeviceBaseHandler(Thing thing) {
101 public void initialize() {
102 setItemId((String) getConfig().get(ITEM_ID));
103 onlineCheckTask = scheduler.scheduleWithFixedDelay(this::updateThingStatus, 0, ONLINE_TIMEOUT_MILLIS / 2,
104 TimeUnit.MILLISECONDS);
108 public void dispose() {
109 logger.debug("Handler disposes. Unregistering listener");
110 if (getItemId() != null) {
111 if (bridgeHandler != null) {
112 bridgeHandler.unregisterItemListener(this);
113 bridgeHandler = null;
117 if (!onlineCheckTask.isDone()) {
118 onlineCheckTask.cancel(false);
123 public void handleRemoval() {
124 getXiaomiBridgeHandler().writeToBridge(new String[] { REMOVE_DEVICE }, new Object[] { itemId });
125 super.handleRemoval();
129 public void handleCommand(ChannelUID channelUID, Command command) {
130 logger.debug("Device {} on channel {} received command {}", getItemId(), channelUID, command);
131 if (command instanceof RefreshType) {
132 JsonObject message = getXiaomiBridgeHandler().getDeferredMessage(getItemId());
133 if (message != null) {
134 String cmd = message.get("cmd").getAsString();
135 logger.debug("Update Item {} with retented message", getItemId());
136 onItemUpdate(getItemId(), cmd, message);
140 execute(channelUID, command);
144 public void onItemUpdate(String sid, String command, JsonObject message) {
145 if (getItemId() != null && getItemId().equals(sid)) {
146 if (getThing().getStatus() != ThingStatus.ONLINE) {
147 updateStatus(ThingStatus.ONLINE);
149 logger.debug("Item got update: {}", message);
151 JsonObject data = parser.parse(message.get("data").getAsString()).getAsJsonObject();
152 parseCommand(command, data);
153 if (THING_TYPE_BASIC.equals(getThing().getThingTypeUID())) {
154 parseDefault(message);
156 } catch (JsonSyntaxException e) {
157 logger.warn("Unable to parse message as valid JSON: {}", message);
163 public String getItemId() {
167 void parseCommand(String command, JsonObject data) {
173 parseHeartbeat(data);
182 logger.debug("Device {} got unknown command {}", getItemId(), command);
186 void parseReport(JsonObject data) {
187 updateState(CHANNEL_REPORT_MSG, StringType.valueOf(data.toString()));
190 void parseHeartbeat(JsonObject data) {
191 updateState(CHANNEL_HEARTBEAT_MSG, StringType.valueOf(data.toString()));
194 void parseReadAck(JsonObject data) {
195 updateState(CHANNEL_READ_ACK_MSG, StringType.valueOf(data.toString()));
198 void parseWriteAck(JsonObject data) {
199 updateState(CHANNEL_WRITE_ACK_MSG, StringType.valueOf(data.toString()));
202 void parseDefault(JsonObject data) {
203 updateState(CHANNEL_LAST_MSG, StringType.valueOf(data.toString()));
206 void execute(ChannelUID channelUID, Command command) {
207 if (CHANNEL_WRITE_MSG.equals(channelUID.getId())) {
208 if (command instanceof StringType) {
209 getXiaomiBridgeHandler().writeToDevice(itemId, ((StringType) command).toFullString());
211 logger.debug("Command \"{}\" has to be of StringType", command);
214 logger.debug("Received command on read-only channel, thus ignoring it.");
218 private void updateThingStatus() {
219 if (getItemId() != null) {
220 // note: this call implicitly registers our handler as a listener on the bridge, if it's not already
221 if (getXiaomiBridgeHandler() != null) {
222 Bridge bridge = getBridge();
223 ThingStatus bridgeStatus = (bridge == null) ? null : bridge.getStatus();
224 if (bridgeStatus == ThingStatus.ONLINE) {
225 ThingStatus itemStatus = getThing().getStatus();
226 boolean hasItemActivity = getXiaomiBridgeHandler().hasItemActivity(getItemId(),
227 ONLINE_TIMEOUT_MILLIS);
228 ThingStatus newStatus = hasItemActivity ? ThingStatus.ONLINE : ThingStatus.OFFLINE;
230 if (!newStatus.equals(itemStatus)) {
231 updateStatus(newStatus);
234 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
237 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
240 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR);
244 synchronized XiaomiBridgeHandler getXiaomiBridgeHandler() {
245 if (this.bridgeHandler == null) {
246 Bridge bridge = getBridge();
247 if (bridge == null) {
250 ThingHandler handler = bridge.getHandler();
251 if (handler instanceof XiaomiBridgeHandler) {
252 this.bridgeHandler = (XiaomiBridgeHandler) handler;
253 this.bridgeHandler.registerItemListener(this);
258 return this.bridgeHandler;