]> git.basschouten.com Git - openhab-addons.git/blob
56376136baabe65a7c5dc7754e9b5773ca8bb5ae
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 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.unifi.internal.handler;
14
15 import static org.openhab.core.thing.ThingStatus.OFFLINE;
16 import static org.openhab.core.thing.ThingStatus.ONLINE;
17 import static org.openhab.core.thing.ThingStatusDetail.*;
18
19 import java.util.concurrent.ScheduledFuture;
20 import java.util.concurrent.TimeUnit;
21
22 import org.eclipse.jdt.annotation.NonNullByDefault;
23 import org.eclipse.jdt.annotation.Nullable;
24 import org.eclipse.jetty.client.HttpClient;
25 import org.openhab.binding.unifi.internal.UniFiBindingConstants;
26 import org.openhab.binding.unifi.internal.UniFiControllerThingConfig;
27 import org.openhab.binding.unifi.internal.api.UniFiCommunicationException;
28 import org.openhab.binding.unifi.internal.api.UniFiException;
29 import org.openhab.binding.unifi.internal.api.UniFiInvalidCredentialsException;
30 import org.openhab.binding.unifi.internal.api.UniFiInvalidHostException;
31 import org.openhab.binding.unifi.internal.api.UniFiSSLException;
32 import org.openhab.binding.unifi.internal.api.model.UniFiController;
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.ThingStatusInfo;
38 import org.openhab.core.thing.ThingTypeUID;
39 import org.openhab.core.thing.binding.BaseBridgeHandler;
40 import org.openhab.core.thing.binding.builder.ThingStatusInfoBuilder;
41 import org.openhab.core.types.Command;
42 import org.slf4j.Logger;
43 import org.slf4j.LoggerFactory;
44
45 /**
46  * The {@link UniFiControllerThingHandler} is responsible for handling commands and status
47  * updates for the UniFi Controller.
48  *
49  * @author Matthew Bowman - Initial contribution
50  */
51 @NonNullByDefault
52 public class UniFiControllerThingHandler extends BaseBridgeHandler {
53
54     public static boolean supportsThingType(ThingTypeUID thingTypeUID) {
55         return UniFiBindingConstants.THING_TYPE_CONTROLLER.equals(thingTypeUID);
56     }
57
58     private static final String STATUS_DESCRIPTION_COMMUNICATION_ERROR = "Error communicating with the UniFi controller";
59
60     private static final String STATUS_DESCRIPTION_SSL_ERROR = "Error establishing an SSL connection with the UniFi controller";
61
62     private static final String STATUS_DESCRIPTION_INVALID_CREDENTIALS = "Invalid username and/or password - please double-check your configuration";
63
64     private static final String STATUS_DESCRIPTION_INVALID_HOSTNAME = "Invalid hostname - please double-check your configuration";
65
66     private final Logger logger = LoggerFactory.getLogger(UniFiControllerThingHandler.class);
67
68     private UniFiControllerThingConfig config = new UniFiControllerThingConfig();
69
70     private @Nullable volatile UniFiController controller; /* mgb: volatile because accessed from multiple threads */
71
72     private @Nullable ScheduledFuture<?> refreshJob;
73
74     private final HttpClient httpClient;
75
76     public UniFiControllerThingHandler(Bridge bridge, HttpClient httpClient) {
77         super(bridge);
78         this.httpClient = httpClient;
79     }
80
81     // Public API
82
83     @Override
84     public void initialize() {
85         // mgb: called when the config changes
86         cancelRefreshJob();
87         config = getConfig().as(UniFiControllerThingConfig.class);
88         logger.debug("Initializing the UniFi Controller Handler with config = {}", config);
89         try {
90             controller = new UniFiController(httpClient, config.getHost(), config.getPort(), config.getUsername(),
91                     config.getPassword(), config.isUniFiOS());
92             controller.start();
93             updateStatus(ONLINE);
94         } catch (UniFiInvalidHostException e) {
95             updateStatus(OFFLINE, CONFIGURATION_ERROR, STATUS_DESCRIPTION_INVALID_HOSTNAME);
96         } catch (UniFiCommunicationException e) {
97             updateStatus(OFFLINE, COMMUNICATION_ERROR, STATUS_DESCRIPTION_COMMUNICATION_ERROR);
98         } catch (UniFiSSLException e) {
99             updateStatus(OFFLINE, CONFIGURATION_ERROR, STATUS_DESCRIPTION_SSL_ERROR);
100         } catch (UniFiInvalidCredentialsException e) {
101             updateStatus(OFFLINE, CONFIGURATION_ERROR, STATUS_DESCRIPTION_INVALID_CREDENTIALS);
102         } catch (UniFiException e) {
103             logger.error("Unknown error while configuring the UniFi Controller", e);
104             updateStatus(OFFLINE, CONFIGURATION_ERROR, e.getMessage());
105         }
106     }
107
108     @Override
109     protected void updateStatus(ThingStatus status, ThingStatusDetail statusDetail, @Nullable String description) {
110         if (status == ONLINE || (status == OFFLINE && statusDetail == COMMUNICATION_ERROR)) {
111             scheduleRefreshJob();
112         } else if (status == OFFLINE && statusDetail == CONFIGURATION_ERROR) {
113             cancelRefreshJob();
114         }
115         // mgb: update the status only if it's changed
116         ThingStatusInfo statusInfo = ThingStatusInfoBuilder.create(status, statusDetail).withDescription(description)
117                 .build();
118         if (!statusInfo.equals(getThing().getStatusInfo())) {
119             super.updateStatus(status, statusDetail, description);
120         }
121     }
122
123     @Override
124     public void dispose() {
125         cancelRefreshJob();
126         if (controller != null) {
127             try {
128                 controller.stop();
129             } catch (UniFiException e) {
130                 // mgb: nop as we're in dispose
131             }
132             controller = null;
133         }
134     }
135
136     @Override
137     public void handleCommand(ChannelUID channelUID, Command command) {
138         // nop - read-only binding
139         logger.warn("Ignoring command = {} for channel = {} - the UniFi binding is read-only!", command, channelUID);
140     }
141
142     public @Nullable UniFiController getController() {
143         return controller;
144     }
145
146     public int getRefreshInterval() {
147         return config.getRefresh();
148     }
149
150     // Private API
151
152     private void scheduleRefreshJob() {
153         synchronized (this) {
154             if (refreshJob == null) {
155                 logger.debug("Scheduling refresh job every {}s", config.getRefresh());
156                 refreshJob = scheduler.scheduleWithFixedDelay(this::run, 0, config.getRefresh(), TimeUnit.SECONDS);
157             }
158         }
159     }
160
161     private void cancelRefreshJob() {
162         synchronized (this) {
163             if (refreshJob != null) {
164                 logger.debug("Cancelling refresh job");
165                 refreshJob.cancel(true);
166                 refreshJob = null;
167             }
168         }
169     }
170
171     private void run() {
172         try {
173             logger.trace("Executing refresh job");
174             refresh();
175             updateStatus(ONLINE);
176         } catch (UniFiCommunicationException e) {
177             updateStatus(OFFLINE, COMMUNICATION_ERROR, STATUS_DESCRIPTION_COMMUNICATION_ERROR);
178         } catch (UniFiInvalidCredentialsException e) {
179             updateStatus(OFFLINE, CONFIGURATION_ERROR, STATUS_DESCRIPTION_INVALID_CREDENTIALS);
180         } catch (Exception e) {
181             logger.warn("Unhandled exception while refreshing the UniFi Controller {} - {}", getThing().getUID(),
182                     e.getMessage());
183             updateStatus(OFFLINE, COMMUNICATION_ERROR, e.getMessage());
184         }
185     }
186
187     private void refresh() throws UniFiException {
188         if (controller != null) {
189             logger.debug("Refreshing the UniFi Controller {}", getThing().getUID());
190             controller.refresh();
191             // mgb: then refresh all the client things
192             getThing().getThings().forEach((thing) -> {
193                 if (thing.getHandler() instanceof UniFiBaseThingHandler) {
194                     ((UniFiBaseThingHandler) thing.getHandler()).refresh();
195                 }
196             });
197         }
198     }
199 }