2 * Copyright (c) 2010-2023 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.ThingStatus.UNKNOWN;
18 import static org.openhab.core.thing.ThingStatusDetail.COMMUNICATION_ERROR;
19 import static org.openhab.core.thing.ThingStatusDetail.CONFIGURATION_ERROR;
21 import java.util.Collection;
22 import java.util.List;
23 import java.util.concurrent.ScheduledFuture;
24 import java.util.concurrent.TimeUnit;
26 import org.eclipse.jdt.annotation.NonNullByDefault;
27 import org.eclipse.jdt.annotation.Nullable;
28 import org.eclipse.jetty.client.HttpClient;
29 import org.openhab.binding.unifi.internal.UniFiControllerThingConfig;
30 import org.openhab.binding.unifi.internal.api.UniFiCommunicationException;
31 import org.openhab.binding.unifi.internal.api.UniFiController;
32 import org.openhab.binding.unifi.internal.api.UniFiException;
33 import org.openhab.binding.unifi.internal.api.UniFiInvalidCredentialsException;
34 import org.openhab.binding.unifi.internal.api.UniFiInvalidHostException;
35 import org.openhab.binding.unifi.internal.api.UniFiSSLException;
36 import org.openhab.core.thing.Bridge;
37 import org.openhab.core.thing.ChannelUID;
38 import org.openhab.core.thing.ThingStatus;
39 import org.openhab.core.thing.ThingStatusDetail;
40 import org.openhab.core.thing.ThingStatusInfo;
41 import org.openhab.core.thing.binding.BaseBridgeHandler;
42 import org.openhab.core.thing.binding.ThingHandler;
43 import org.openhab.core.thing.binding.ThingHandlerService;
44 import org.openhab.core.thing.binding.builder.ThingStatusInfoBuilder;
45 import org.openhab.core.types.Command;
46 import org.slf4j.Logger;
47 import org.slf4j.LoggerFactory;
50 * The {@link UniFiControllerThingHandler} is responsible for handling commands and status
51 * updates for the UniFi Controller.
53 * @author Matthew Bowman - Initial contribution
56 public class UniFiControllerThingHandler extends BaseBridgeHandler {
58 private static final String STATUS_DESCRIPTION_COMMUNICATION_ERROR = "@text/error.bridge.offline.communication_error";
59 private static final String STATUS_DESCRIPTION_SSL_ERROR = "@text/error.bridge.offline.ssl_error";
60 private static final String STATUS_DESCRIPTION_INVALID_CREDENTIALS = "@text/error.bridge.offline.invalid_credentials";
61 private static final String STATUS_DESCRIPTION_INVALID_HOSTNAME = "@text/error.bridge.offline.invalid_hostname";
63 private final Logger logger = LoggerFactory.getLogger(UniFiControllerThingHandler.class);
65 private UniFiControllerThingConfig config = new UniFiControllerThingConfig();
67 private @Nullable volatile UniFiController controller; /* mgb: volatile because accessed from multiple threads */
69 private @Nullable ScheduledFuture<?> refreshJob;
71 private final HttpClient httpClient;
73 public UniFiControllerThingHandler(final Bridge bridge, final HttpClient httpClient) {
75 this.httpClient = httpClient;
81 public Collection<Class<? extends ThingHandlerService>> getServices() {
82 return List.of(UniFiThingDiscoveryService.class);
86 public void initialize() {
87 config = getConfigAs(UniFiControllerThingConfig.class);
88 logger.debug("Initializing the UniFi Controller Handler with config = {}", config);
89 final UniFiController uc = new UniFiController(httpClient, config.getHost(), config.getPort(),
90 config.getUsername(), config.getPassword(), config.isUniFiOS());
93 updateStatus(UNKNOWN);
94 scheduler.schedule(() -> start(uc), 10, TimeUnit.MILLISECONDS);
98 protected void updateStatus(final ThingStatus status, final ThingStatusDetail statusDetail,
99 @Nullable final String description) {
100 // mgb: update the status only if it's changed
101 final ThingStatusInfo statusInfo = ThingStatusInfoBuilder.create(status, statusDetail)
102 .withDescription(description).build();
103 if (!statusInfo.equals(getThing().getStatusInfo())) {
104 super.updateStatus(status, statusDetail, description);
109 public void dispose() {
111 final UniFiController controller = this.controller;
113 if (controller != null) {
116 } catch (final UniFiException e) {
117 // mgb: nop as we're in dispose
119 this.controller = null;
124 public void handleCommand(final ChannelUID channelUID, final Command command) {
125 // nop - read-only binding
126 logger.warn("Ignoring command = {} for channel = {} - the UniFi binding is read-only!", command, channelUID);
129 public @Nullable UniFiController getController() {
135 private void start(final UniFiController uc) {
136 boolean startRefresh = false;
140 } catch (final UniFiCommunicationException e) {
141 updateStatus(OFFLINE, COMMUNICATION_ERROR, STATUS_DESCRIPTION_COMMUNICATION_ERROR);
143 } catch (final UniFiInvalidHostException e) {
144 updateStatus(OFFLINE, CONFIGURATION_ERROR, STATUS_DESCRIPTION_INVALID_HOSTNAME);
145 } catch (final UniFiSSLException e) {
146 updateStatus(OFFLINE, CONFIGURATION_ERROR, STATUS_DESCRIPTION_SSL_ERROR);
147 } catch (final UniFiInvalidCredentialsException e) {
148 updateStatus(OFFLINE, CONFIGURATION_ERROR, STATUS_DESCRIPTION_INVALID_CREDENTIALS);
149 } catch (final UniFiException e) {
150 logger.debug("Unknown error while configuring the UniFi Controller", e);
151 updateStatus(OFFLINE, CONFIGURATION_ERROR, e.getMessage());
154 logger.debug("Scheduling refresh job every {}s", config.getRefresh());
155 refreshJob = scheduler.scheduleWithFixedDelay(this::run, 0, config.getRefresh(), TimeUnit.SECONDS);
159 private void cancelRefreshJob() {
160 synchronized (this) {
161 final ScheduledFuture<?> rj = refreshJob;
164 logger.debug("Cancelling refresh job");
173 logger.trace("Executing refresh job");
175 updateStatus(ONLINE);
176 } catch (final UniFiCommunicationException e) {
177 updateStatus(OFFLINE, COMMUNICATION_ERROR, STATUS_DESCRIPTION_COMMUNICATION_ERROR);
178 } catch (final UniFiInvalidCredentialsException e) {
179 updateStatus(OFFLINE, CONFIGURATION_ERROR, STATUS_DESCRIPTION_INVALID_CREDENTIALS);
180 } catch (final RuntimeException | UniFiException e) {
181 logger.debug("Unhandled exception while refreshing the UniFi Controller {}", getThing().getUID(), e);
182 updateStatus(OFFLINE, COMMUNICATION_ERROR, e.getMessage());
186 private void refresh() throws UniFiException {
187 final UniFiController uc = controller;
190 logger.debug("Refreshing the UniFi Controller {}", getThing().getUID());
192 // mgb: then refresh all the client things
193 getThing().getThings().forEach((thing) -> {
194 final ThingHandler handler = thing.getHandler();
196 if (handler instanceof UniFiBaseThingHandler) {
197 ((UniFiBaseThingHandler<?, ?>) handler).refresh();