2 * Copyright (c) 2010-2024 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.icloud.internal.handler;
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.*;
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;
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;
51 * Handles updates of an iCloud device Thing.
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
60 public class ICloudDeviceHandler extends BaseThingHandler implements ICloudDeviceInformationListener {
61 private final Logger logger = LoggerFactory.getLogger(ICloudDeviceHandler.class);
63 private @Nullable String deviceId;
65 private @Nullable ICloudDeviceInformation deviceInformationRecord;
67 public ICloudDeviceHandler(Thing thing) {
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) {
79 updateStatus(OFFLINE, COMMUNICATION_ERROR, "Reported offline by iCloud webservice");
82 updateState(BATTERY_STATUS, new StringType(deviceInformationRecord.getBatteryStatus()));
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));
90 if (deviceInformationRecord.getLocation() != null) {
91 updateLocationRelatedStates(deviceInformationRecord);
94 updateStatus(OFFLINE, CONFIGURATION_ERROR, "The device is not included in the current account");
99 public void initialize() {
100 ICloudDeviceThingConfiguration configuration = getConfigAs(ICloudDeviceThingConfiguration.class);
101 this.deviceId = configuration.deviceId;
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);
112 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
115 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED,
116 "Bridge handler is not configured");
119 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Bridge is not configured");
124 public void handleCommand(ChannelUID channelUID, Command command) {
125 this.logger.trace("Command '{}' received for channel '{}'", command, channelUID);
127 Bridge bridge = getBridge();
128 if (bridge == null) {
129 this.logger.debug("No bridge found, ignoring command");
133 ICloudAccountBridgeHandler bridgeHandler = (ICloudAccountBridgeHandler) bridge.getHandler();
134 if (bridgeHandler == null) {
135 this.logger.debug("No bridge handler found, ignoring command");
139 String channelId = channelUID.getId();
140 if (channelId.equals(FIND_MY_PHONE)) {
141 if (command == OnOffType.ON) {
144 if (deviceInformationRecord == null) {
146 .debug("Can't send Find My Device request, because deviceInformationRecord is null!");
149 if (deviceInformationRecord.getId() == null) {
151 "Can't send Find My Device request, because deviceInformationRecord.getId() is null!");
154 bridgeHandler.findMyDevice(deviceInformationRecord.getId());
155 } catch (IOException | InterruptedException e) {
156 this.logger.warn("Unable to execute find my device request", e);
158 updateState(FIND_MY_PHONE, OnOffType.OFF);
162 if (command instanceof RefreshType) {
163 bridgeHandler.refreshData();
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);
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());
185 PointType location = new PointType(latitude, longitude, altitude);
187 updateState(LOCATION, location);
188 updateState(LOCATION_ACCURACY, accuracy);
189 updateState(LOCATION_LASTUPDATE, getLastLocationUpdateDateTimeState(deviceInformationRecord));
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();
201 logger.debug("Current data element: [id = {}]", currentId);
203 if (currentId != null && currentId.equals(deviceId)) {
204 return deviceInformationRecord;
208 logger.debug("Unable to find device data.");
212 private State getLastLocationUpdateDateTimeState(ICloudDeviceInformation deviceInformationRecord) {
213 State dateTime = UnDefType.UNDEF;
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);