]> git.basschouten.com Git - openhab-addons.git/blob
922dfd7f25dc1bd12a03030a8b58f0b4d596b704
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2022 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 java.util.concurrent.TimeUnit.*;
16
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;
23
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;
43
44 import com.google.gson.JsonSyntaxException;
45
46 /**
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.
50  *
51  * @author Patrik Gfeller - Initial contribution
52  * @author Hans-Jörg Merk - Extended support with initial Contribution
53  */
54 @NonNullByDefault
55 public class ICloudAccountBridgeHandler extends BaseBridgeHandler {
56
57     private final Logger logger = LoggerFactory.getLogger(ICloudAccountBridgeHandler.class);
58
59     private static final int CACHE_EXPIRY = (int) SECONDS.toMillis(10);
60
61     private final ICloudDeviceInformationParser deviceInformationParser = new ICloudDeviceInformationParser();
62     private @Nullable ICloudConnection connection;
63     private @Nullable ExpiringCache<String> iCloudDeviceInformationCache;
64
65     @Nullable
66     ServiceRegistration<?> service;
67
68     private final Object synchronizeRefresh = new Object();
69
70     private List<ICloudDeviceInformationListener> deviceInformationListeners = Collections
71             .synchronizedList(new ArrayList<>());
72
73     @Nullable
74     ScheduledFuture<?> refreshJob;
75
76     public ICloudAccountBridgeHandler(Bridge bridge) {
77         super(bridge);
78     }
79
80     @Override
81     public void handleCommand(ChannelUID channelUID, Command command) {
82         logger.trace("Command '{}' received for channel '{}'", command, channelUID);
83
84         if (command instanceof RefreshType) {
85             refreshData();
86         }
87     }
88
89     @Override
90     public void initialize() {
91         logger.debug("iCloud bridge handler initializing ...");
92         iCloudDeviceInformationCache = new ExpiringCache<>(CACHE_EXPIRY, () -> {
93             try {
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());
98                 return null;
99             }
100         });
101
102         startHandler();
103         logger.debug("iCloud bridge initialized.");
104     }
105
106     @Override
107     public void handleRemoval() {
108         super.handleRemoval();
109     }
110
111     @Override
112     public void dispose() {
113         if (refreshJob != null) {
114             refreshJob.cancel(true);
115         }
116         super.dispose();
117     }
118
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!");
122             return;
123         }
124         connection.findMyDevice(deviceId);
125     }
126
127     public void registerListener(ICloudDeviceInformationListener listener) {
128         deviceInformationListeners.add(listener);
129     }
130
131     public void unregisterListener(ICloudDeviceInformationListener listener) {
132         deviceInformationListeners.remove(listener);
133     }
134
135     private void startHandler() {
136         try {
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);
143             } else {
144                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
145                         "Apple ID/Password is not set!");
146                 return;
147             }
148             refreshJob = scheduler.scheduleWithFixedDelay(this::refreshData, 0, config.refreshTimeInMinutes, MINUTES);
149
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());
154         }
155     }
156
157     public void refreshData() {
158         synchronized (synchronizeRefresh) {
159             logger.debug("iCloud bridge refreshing data ...");
160
161             String json = iCloudDeviceInformationCache.getValue();
162             logger.trace("json: {}", json);
163
164             if (json == null) {
165                 return;
166             }
167
168             try {
169                 ICloudAccountDataResponse iCloudData = deviceInformationParser.parse(json);
170                 if (iCloudData == null) {
171                     return;
172                 }
173                 int statusCode = Integer.parseUnsignedInt(iCloudData.getICloudAccountStatusCode());
174                 if (statusCode == 200) {
175                     updateStatus(ThingStatus.ONLINE);
176                     informDeviceInformationListeners(iCloudData.getICloudDeviceInformationList());
177                 } else {
178                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
179                             "Status = " + statusCode + ", Response = " + json);
180                 }
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());
185             }
186         }
187     }
188
189     private void informDeviceInformationListeners(List<ICloudDeviceInformation> deviceInformationList) {
190         this.deviceInformationListeners.forEach(discovery -> discovery.deviceInformationUpdate(deviceInformationList));
191     }
192 }