2 * Copyright (c) 2010-2022 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 java.util.concurrent.TimeUnit.*;
17 import java.io.IOException;
18 import java.net.URISyntaxException;
19 import java.util.ArrayList;
20 import java.util.Collections;
21 import java.util.List;
22 import java.util.concurrent.ScheduledFuture;
24 import org.eclipse.jdt.annotation.NonNullByDefault;
25 import org.eclipse.jdt.annotation.Nullable;
26 import org.openhab.binding.icloud.internal.ICloudConnection;
27 import org.openhab.binding.icloud.internal.ICloudDeviceInformationListener;
28 import org.openhab.binding.icloud.internal.ICloudDeviceInformationParser;
29 import org.openhab.binding.icloud.internal.configuration.ICloudAccountThingConfiguration;
30 import org.openhab.binding.icloud.internal.json.response.ICloudAccountDataResponse;
31 import org.openhab.binding.icloud.internal.json.response.ICloudDeviceInformation;
32 import org.openhab.core.cache.ExpiringCache;
33 import org.openhab.core.thing.Bridge;
34 import org.openhab.core.thing.ChannelUID;
35 import org.openhab.core.thing.ThingStatus;
36 import org.openhab.core.thing.ThingStatusDetail;
37 import org.openhab.core.thing.binding.BaseBridgeHandler;
38 import org.openhab.core.types.Command;
39 import org.openhab.core.types.RefreshType;
40 import org.osgi.framework.ServiceRegistration;
41 import org.slf4j.Logger;
42 import org.slf4j.LoggerFactory;
44 import com.google.gson.JsonSyntaxException;
47 * Retrieves the data for a given account from iCloud and passes the
48 * information to {@link org.openhab.binding.icloud.internal.discovery.ICloudDeviceDiscovery} and to the
49 * {@link ICloudDeviceHandler}s.
51 * @author Patrik Gfeller - Initial contribution
52 * @author Hans-Jörg Merk - Extended support with initial Contribution
55 public class ICloudAccountBridgeHandler extends BaseBridgeHandler {
57 private final Logger logger = LoggerFactory.getLogger(ICloudAccountBridgeHandler.class);
59 private static final int CACHE_EXPIRY = (int) SECONDS.toMillis(10);
61 private final ICloudDeviceInformationParser deviceInformationParser = new ICloudDeviceInformationParser();
62 private @Nullable ICloudConnection connection;
63 private @Nullable ExpiringCache<String> iCloudDeviceInformationCache;
66 ServiceRegistration<?> service;
68 private final Object synchronizeRefresh = new Object();
70 private List<ICloudDeviceInformationListener> deviceInformationListeners = Collections
71 .synchronizedList(new ArrayList<>());
74 ScheduledFuture<?> refreshJob;
76 public ICloudAccountBridgeHandler(Bridge bridge) {
81 public void handleCommand(ChannelUID channelUID, Command command) {
82 logger.trace("Command '{}' received for channel '{}'", command, channelUID);
84 if (command instanceof RefreshType) {
90 public void initialize() {
91 logger.debug("iCloud bridge handler initializing ...");
92 iCloudDeviceInformationCache = new ExpiringCache<>(CACHE_EXPIRY, () -> {
94 return connection.requestDeviceStatusJSON();
95 } catch (IOException e) {
96 logger.warn("Unable to refresh device data", e);
97 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
103 logger.debug("iCloud bridge initialized.");
107 public void handleRemoval() {
108 super.handleRemoval();
112 public void dispose() {
113 if (refreshJob != null) {
114 refreshJob.cancel(true);
119 public void findMyDevice(String deviceId) throws IOException {
120 if (connection == null) {
121 logger.debug("Can't send Find My Device request, because connection is null!");
124 connection.findMyDevice(deviceId);
127 public void registerListener(ICloudDeviceInformationListener listener) {
128 deviceInformationListeners.add(listener);
131 public void unregisterListener(ICloudDeviceInformationListener listener) {
132 deviceInformationListeners.remove(listener);
135 private void startHandler() {
137 logger.debug("iCloud bridge starting handler ...");
138 ICloudAccountThingConfiguration config = getConfigAs(ICloudAccountThingConfiguration.class);
139 final String localAppleId = config.appleId;
140 final String localPassword = config.password;
141 if (localAppleId != null && localPassword != null) {
142 connection = new ICloudConnection(localAppleId, localPassword);
144 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
145 "Apple ID/Password is not set!");
148 refreshJob = scheduler.scheduleWithFixedDelay(this::refreshData, 0, config.refreshTimeInMinutes, MINUTES);
150 logger.debug("iCloud bridge handler started.");
151 } catch (URISyntaxException e) {
152 logger.debug("Something went wrong while constructing the connection object", e);
153 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
157 public void refreshData() {
158 synchronized (synchronizeRefresh) {
159 logger.debug("iCloud bridge refreshing data ...");
161 String json = iCloudDeviceInformationCache.getValue();
162 logger.trace("json: {}", json);
169 ICloudAccountDataResponse iCloudData = deviceInformationParser.parse(json);
170 if (iCloudData == null) {
173 int statusCode = Integer.parseUnsignedInt(iCloudData.getICloudAccountStatusCode());
174 if (statusCode == 200) {
175 updateStatus(ThingStatus.ONLINE);
176 informDeviceInformationListeners(iCloudData.getICloudDeviceInformationList());
178 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
179 "Status = " + statusCode + ", Response = " + json);
181 logger.debug("iCloud bridge data refresh complete.");
182 } catch (NumberFormatException | JsonSyntaxException e) {
183 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
184 "iCloud response invalid: " + e.getMessage());
189 private void informDeviceInformationListeners(List<ICloudDeviceInformation> deviceInformationList) {
190 this.deviceInformationListeners.forEach(discovery -> discovery.deviceInformationUpdate(deviceInformationList));