]> git.basschouten.com Git - openhab-addons.git/blob
358a9ade30c0b37663bfa1304da7b4d717a82ce7
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2024 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.icloud.internal.handler;
14
15 import static org.openhab.binding.icloud.internal.ICloudBindingConstants.*;
16 import static org.openhab.core.thing.ThingStatus.OFFLINE;
17 import static org.openhab.core.thing.ThingStatus.ONLINE;
18 import static org.openhab.core.thing.ThingStatusDetail.*;
19
20 import java.io.IOException;
21 import java.time.ZoneId;
22 import java.time.ZonedDateTime;
23 import java.util.Date;
24 import java.util.List;
25
26 import org.eclipse.jdt.annotation.NonNullByDefault;
27 import org.eclipse.jdt.annotation.Nullable;
28 import org.openhab.binding.icloud.internal.ICloudDeviceInformationListener;
29 import org.openhab.binding.icloud.internal.configuration.ICloudDeviceThingConfiguration;
30 import org.openhab.binding.icloud.internal.handler.dto.json.response.ICloudDeviceInformation;
31 import org.openhab.core.library.types.DateTimeType;
32 import org.openhab.core.library.types.DecimalType;
33 import org.openhab.core.library.types.OnOffType;
34 import org.openhab.core.library.types.PointType;
35 import org.openhab.core.library.types.StringType;
36 import org.openhab.core.thing.Bridge;
37 import org.openhab.core.thing.ChannelUID;
38 import org.openhab.core.thing.Thing;
39 import org.openhab.core.thing.ThingStatus;
40 import org.openhab.core.thing.ThingStatusDetail;
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.openhab.core.types.State;
46 import org.openhab.core.types.UnDefType;
47 import org.slf4j.Logger;
48 import org.slf4j.LoggerFactory;
49
50 /**
51  * Handles updates of an iCloud device Thing.
52  *
53  * @author Patrik Gfeller - Initial contribution
54  * @author Hans-Jörg Merk - Helped with testing and feedback
55  * @author Gaël L'hopital - Added low battery
56  * @author Simon Spielmann - Rework for new iCloud API
57  *
58  */
59 @NonNullByDefault
60 public class ICloudDeviceHandler extends BaseThingHandler implements ICloudDeviceInformationListener {
61     private final Logger logger = LoggerFactory.getLogger(ICloudDeviceHandler.class);
62
63     private @Nullable String deviceId;
64
65     private @Nullable ICloudDeviceInformation deviceInformationRecord;
66
67     public ICloudDeviceHandler(Thing thing) {
68         super(thing);
69     }
70
71     @Override
72     public void deviceInformationUpdate(List<ICloudDeviceInformation> deviceInformationList) {
73         ICloudDeviceInformation deviceInformationRecord = getDeviceInformationRecord(deviceInformationList);
74         if (deviceInformationRecord != null) {
75             this.deviceInformationRecord = deviceInformationRecord;
76             if (deviceInformationRecord.getDeviceStatus() == 200) {
77                 updateStatus(ONLINE);
78             } else {
79                 updateStatus(OFFLINE, COMMUNICATION_ERROR, "Reported offline by iCloud webservice");
80             }
81
82             updateState(BATTERY_STATUS, new StringType(deviceInformationRecord.getBatteryStatus()));
83
84             Double batteryLevel = deviceInformationRecord.getBatteryLevel();
85             if (batteryLevel != Double.NaN) {
86                 updateState(BATTERY_LEVEL, new DecimalType(deviceInformationRecord.getBatteryLevel() * 100));
87                 updateState(LOW_BATTERY, OnOffType.from(batteryLevel < 0.2));
88             }
89
90             if (deviceInformationRecord.getLocation() != null) {
91                 updateLocationRelatedStates(deviceInformationRecord);
92             }
93         } else {
94             updateStatus(OFFLINE, CONFIGURATION_ERROR, "The device is not included in the current account");
95         }
96     }
97
98     @Override
99     public void initialize() {
100         ICloudDeviceThingConfiguration configuration = getConfigAs(ICloudDeviceThingConfiguration.class);
101         this.deviceId = configuration.deviceId;
102
103         Bridge bridge = getBridge();
104         if (bridge != null) {
105             ICloudAccountBridgeHandler handler = (ICloudAccountBridgeHandler) bridge.getHandler();
106             if (handler != null) {
107                 handler.registerListener(this);
108                 if (bridge.getStatus() == ThingStatus.ONLINE) {
109                     handler.refreshData();
110                     updateStatus(ThingStatus.ONLINE);
111                 } else {
112                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
113                 }
114             } else {
115                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED,
116                         "Bridge handler is not configured");
117             }
118         } else {
119             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Bridge is not configured");
120         }
121     }
122
123     @Override
124     public void handleCommand(ChannelUID channelUID, Command command) {
125         this.logger.trace("Command '{}' received for channel '{}'", command, channelUID);
126
127         Bridge bridge = getBridge();
128         if (bridge == null) {
129             this.logger.debug("No bridge found, ignoring command");
130             return;
131         }
132
133         ICloudAccountBridgeHandler bridgeHandler = (ICloudAccountBridgeHandler) bridge.getHandler();
134         if (bridgeHandler == null) {
135             this.logger.debug("No bridge handler found, ignoring command");
136             return;
137         }
138
139         String channelId = channelUID.getId();
140         if (channelId.equals(FIND_MY_PHONE)) {
141             if (command == OnOffType.ON) {
142                 try {
143
144                     if (deviceInformationRecord == null) {
145                         this.logger
146                                 .debug("Can't send Find My Device request, because deviceInformationRecord is null!");
147                         return;
148                     }
149                     if (deviceInformationRecord.getId() == null) {
150                         this.logger.debug(
151                                 "Can't send Find My Device request, because deviceInformationRecord.getId() is null!");
152                         return;
153                     }
154                     bridgeHandler.findMyDevice(deviceInformationRecord.getId());
155                 } catch (IOException | InterruptedException e) {
156                     this.logger.warn("Unable to execute find my device request", e);
157                 }
158                 updateState(FIND_MY_PHONE, OnOffType.OFF);
159             }
160         }
161
162         if (command instanceof RefreshType) {
163             bridgeHandler.refreshData();
164         }
165     }
166
167     @Override
168     public void dispose() {
169         Bridge bridge = getBridge();
170         if (bridge != null) {
171             ThingHandler bridgeHandler = bridge.getHandler();
172             if (bridgeHandler instanceof ICloudAccountBridgeHandler iCloudAccountBridgeHandler) {
173                 iCloudAccountBridgeHandler.unregisterListener(this);
174             }
175         }
176         super.dispose();
177     }
178
179     private void updateLocationRelatedStates(ICloudDeviceInformation deviceInformationRecord) {
180         DecimalType latitude = new DecimalType(deviceInformationRecord.getLocation().getLatitude());
181         DecimalType longitude = new DecimalType(deviceInformationRecord.getLocation().getLongitude());
182         DecimalType altitude = new DecimalType(deviceInformationRecord.getLocation().getAltitude());
183         DecimalType accuracy = new DecimalType(deviceInformationRecord.getLocation().getHorizontalAccuracy());
184
185         PointType location = new PointType(latitude, longitude, altitude);
186
187         updateState(LOCATION, location);
188         updateState(LOCATION_ACCURACY, accuracy);
189         updateState(LOCATION_LASTUPDATE, getLastLocationUpdateDateTimeState(deviceInformationRecord));
190     }
191
192     private @Nullable ICloudDeviceInformation getDeviceInformationRecord(
193             List<ICloudDeviceInformation> deviceInformationList) {
194         this.logger.debug("Device: [{}]", this.deviceId);
195         for (ICloudDeviceInformation deviceInformationRecord : deviceInformationList) {
196             String currentId = deviceInformationRecord.getDeviceDiscoveryId();
197             if (currentId == null || currentId.isBlank()) {
198                 logger.debug("deviceDiscoveryId is empty, using device name for identification.");
199                 currentId = deviceInformationRecord.getName();
200             }
201             logger.debug("Current data element: [id = {}]", currentId);
202
203             if (currentId != null && currentId.equals(deviceId)) {
204                 return deviceInformationRecord;
205             }
206         }
207
208         logger.debug("Unable to find device data.");
209         return null;
210     }
211
212     private State getLastLocationUpdateDateTimeState(ICloudDeviceInformation deviceInformationRecord) {
213         State dateTime = UnDefType.UNDEF;
214
215         if (deviceInformationRecord.getLocation().getTimeStamp() > 0) {
216             Date date = new Date(deviceInformationRecord.getLocation().getTimeStamp());
217             ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
218             dateTime = new DateTimeType(zonedDateTime);
219         }
220
221         return dateTime;
222     }
223 }