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.unifi.internal.handler;
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.*;
19 import java.util.concurrent.ScheduledFuture;
20 import java.util.concurrent.TimeUnit;
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;
46 * The {@link UniFiControllerThingHandler} is responsible for handling commands and status
47 * updates for the UniFi Controller.
49 * @author Matthew Bowman - Initial contribution
52 public class UniFiControllerThingHandler extends BaseBridgeHandler {
54 public static boolean supportsThingType(ThingTypeUID thingTypeUID) {
55 return UniFiBindingConstants.THING_TYPE_CONTROLLER.equals(thingTypeUID);
58 private static final String STATUS_DESCRIPTION_COMMUNICATION_ERROR = "Error communicating with the UniFi controller";
60 private static final String STATUS_DESCRIPTION_SSL_ERROR = "Error establishing an SSL connection with the UniFi controller";
62 private static final String STATUS_DESCRIPTION_INVALID_CREDENTIALS = "Invalid username and/or password - please double-check your configuration";
64 private static final String STATUS_DESCRIPTION_INVALID_HOSTNAME = "Invalid hostname - please double-check your configuration";
66 private final Logger logger = LoggerFactory.getLogger(UniFiControllerThingHandler.class);
68 private UniFiControllerThingConfig config = new UniFiControllerThingConfig();
70 private @Nullable volatile UniFiController controller; /* mgb: volatile because accessed from multiple threads */
72 private @Nullable ScheduledFuture<?> refreshJob;
74 private final HttpClient httpClient;
76 public UniFiControllerThingHandler(Bridge bridge, HttpClient httpClient) {
78 this.httpClient = httpClient;
84 public void initialize() {
85 // mgb: called when the config changes
87 config = getConfig().as(UniFiControllerThingConfig.class);
88 logger.debug("Initializing the UniFi Controller Handler with config = {}", config);
90 controller = new UniFiController(httpClient, config.getHost(), config.getPort(), config.getUsername(),
91 config.getPassword(), config.isUniFiOS());
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());
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) {
115 // mgb: update the status only if it's changed
116 ThingStatusInfo statusInfo = ThingStatusInfoBuilder.create(status, statusDetail).withDescription(description)
118 if (!statusInfo.equals(getThing().getStatusInfo())) {
119 super.updateStatus(status, statusDetail, description);
124 public void dispose() {
126 if (controller != null) {
129 } catch (UniFiException e) {
130 // mgb: nop as we're in dispose
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);
142 public @Nullable UniFiController getController() {
146 public int getRefreshInterval() {
147 return config.getRefresh();
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);
161 private void cancelRefreshJob() {
162 synchronized (this) {
163 if (refreshJob != null) {
164 logger.debug("Cancelling refresh job");
165 refreshJob.cancel(true);
173 logger.trace("Executing refresh job");
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(),
183 updateStatus(OFFLINE, COMMUNICATION_ERROR, e.getMessage());
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();