]> git.basschouten.com Git - openhab-addons.git/blob
361f542b76a44f6ad087285f11f45e7485eabcf6
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2020 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.mihome.internal.handler;
14
15 import static org.openhab.binding.mihome.internal.XiaomiGatewayBindingConstants.*;
16 import static org.openhab.core.library.unit.MetricPrefix.*;
17
18 import java.util.Arrays;
19 import java.util.HashSet;
20 import java.util.Set;
21 import java.util.concurrent.ScheduledFuture;
22 import java.util.concurrent.TimeUnit;
23
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;
30
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;
47
48 import com.google.gson.JsonObject;
49 import com.google.gson.JsonParser;
50 import com.google.gson.JsonSyntaxException;
51
52 /**
53  * The {@link XiaomiDeviceBaseHandler} is responsible for handling commands, which are
54  * sent to one of the channels.
55  *
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,
59  *         Aqara
60  *         switches
61  * @author Daniel Walters - Added Aqara Door/Window sensor and Aqara temperature, humidity and pressure sensor
62  */
63 public class XiaomiDeviceBaseHandler extends BaseThingHandler implements XiaomiItemUpdateListener {
64
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));
73
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);
79
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;
83
84     private JsonParser parser = new JsonParser();
85
86     private XiaomiBridgeHandler bridgeHandler;
87
88     private String itemId;
89
90     private final void setItemId(String itemId) {
91         this.itemId = itemId;
92     }
93
94     private final Logger logger = LoggerFactory.getLogger(XiaomiDeviceBaseHandler.class);
95
96     public XiaomiDeviceBaseHandler(Thing thing) {
97         super(thing);
98     }
99
100     @Override
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);
105     }
106
107     @Override
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;
114             }
115             setItemId(null);
116         }
117         if (!onlineCheckTask.isDone()) {
118             onlineCheckTask.cancel(false);
119         }
120     }
121
122     @Override
123     public void handleRemoval() {
124         getXiaomiBridgeHandler().writeToBridge(new String[] { REMOVE_DEVICE }, new Object[] { itemId });
125         super.handleRemoval();
126     }
127
128     @Override
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);
137             }
138             return;
139         }
140         execute(channelUID, command);
141     }
142
143     @Override
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);
148             }
149             logger.debug("Item got update: {}", message);
150             try {
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);
155                 }
156             } catch (JsonSyntaxException e) {
157                 logger.warn("Unable to parse message as valid JSON: {}", message);
158             }
159         }
160     }
161
162     @Override
163     public String getItemId() {
164         return itemId;
165     }
166
167     void parseCommand(String command, JsonObject data) {
168         switch (command) {
169             case "report":
170                 parseReport(data);
171                 break;
172             case "heartbeat":
173                 parseHeartbeat(data);
174                 break;
175             case "read_ack":
176                 parseReadAck(data);
177                 break;
178             case "write_ack":
179                 parseWriteAck(data);
180                 break;
181             default:
182                 logger.debug("Device {} got unknown command {}", getItemId(), command);
183         }
184     }
185
186     void parseReport(JsonObject data) {
187         updateState(CHANNEL_REPORT_MSG, StringType.valueOf(data.toString()));
188     }
189
190     void parseHeartbeat(JsonObject data) {
191         updateState(CHANNEL_HEARTBEAT_MSG, StringType.valueOf(data.toString()));
192     }
193
194     void parseReadAck(JsonObject data) {
195         updateState(CHANNEL_READ_ACK_MSG, StringType.valueOf(data.toString()));
196     }
197
198     void parseWriteAck(JsonObject data) {
199         updateState(CHANNEL_WRITE_ACK_MSG, StringType.valueOf(data.toString()));
200     }
201
202     void parseDefault(JsonObject data) {
203         updateState(CHANNEL_LAST_MSG, StringType.valueOf(data.toString()));
204     }
205
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());
210             } else {
211                 logger.debug("Command \"{}\" has to be of StringType", command);
212             }
213         } else {
214             logger.debug("Received command on read-only channel, thus ignoring it.");
215         }
216     }
217
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;
229
230                     if (!newStatus.equals(itemStatus)) {
231                         updateStatus(newStatus);
232                     }
233                 } else {
234                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
235                 }
236             } else {
237                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
238             }
239         } else {
240             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR);
241         }
242     }
243
244     synchronized XiaomiBridgeHandler getXiaomiBridgeHandler() {
245         if (this.bridgeHandler == null) {
246             Bridge bridge = getBridge();
247             if (bridge == null) {
248                 return null;
249             }
250             ThingHandler handler = bridge.getHandler();
251             if (handler instanceof XiaomiBridgeHandler) {
252                 this.bridgeHandler = (XiaomiBridgeHandler) handler;
253                 this.bridgeHandler.registerItemListener(this);
254             } else {
255                 return null;
256             }
257         }
258         return this.bridgeHandler;
259     }
260 }