]> git.basschouten.com Git - openhab-addons.git/blob
da8dce148e1490e1d120894f4b38c0d63e3733c2
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 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.ThingStatus.UNKNOWN;
18 import static org.openhab.core.thing.ThingStatusDetail.COMMUNICATION_ERROR;
19 import static org.openhab.core.thing.ThingStatusDetail.CONFIGURATION_ERROR;
20
21 import java.util.Collection;
22 import java.util.List;
23 import java.util.concurrent.ScheduledFuture;
24 import java.util.concurrent.TimeUnit;
25
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;
48
49 /**
50  * The {@link UniFiControllerThingHandler} is responsible for handling commands and status
51  * updates for the UniFi Controller.
52  *
53  * @author Matthew Bowman - Initial contribution
54  */
55 @NonNullByDefault
56 public class UniFiControllerThingHandler extends BaseBridgeHandler {
57
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";
62     private static final String I18N_STATUS_WITH_ARGUMENTS = "%s [\"%s\"]";
63
64     private final Logger logger = LoggerFactory.getLogger(UniFiControllerThingHandler.class);
65
66     private UniFiControllerThingConfig config = new UniFiControllerThingConfig();
67
68     private @Nullable volatile UniFiController controller; /* mgb: volatile because accessed from multiple threads */
69
70     private @Nullable ScheduledFuture<?> refreshJob;
71
72     private final HttpClient httpClient;
73
74     public UniFiControllerThingHandler(final Bridge bridge, final HttpClient httpClient) {
75         super(bridge);
76         this.httpClient = httpClient;
77     }
78
79     // Public API
80
81     @Override
82     public Collection<Class<? extends ThingHandlerService>> getServices() {
83         return List.of(UniFiThingDiscoveryService.class);
84     }
85
86     @Override
87     public void initialize() {
88         config = getConfigAs(UniFiControllerThingConfig.class);
89         logger.debug("Initializing the UniFi Controller Handler with config = {}", config);
90         final UniFiController uc = new UniFiController(httpClient, config.getHost(), config.getPort(),
91                 config.getUsername(), config.getPassword(), config.isUniFiOS());
92
93         controller = uc;
94         updateStatus(UNKNOWN);
95         scheduler.schedule(() -> start(uc), 10, TimeUnit.MILLISECONDS);
96     }
97
98     @Override
99     protected void updateStatus(final ThingStatus status, final ThingStatusDetail statusDetail,
100             @Nullable final String description) {
101         // mgb: update the status only if it's changed
102         final ThingStatusInfo statusInfo = ThingStatusInfoBuilder.create(status, statusDetail)
103                 .withDescription(description).build();
104         if (!statusInfo.equals(getThing().getStatusInfo())) {
105             super.updateStatus(status, statusDetail, description);
106         }
107     }
108
109     @Override
110     public void dispose() {
111         cancelRefreshJob();
112         final UniFiController controller = this.controller;
113
114         if (controller != null) {
115             try {
116                 controller.stop();
117             } catch (final UniFiException e) {
118                 // mgb: nop as we're in dispose
119             }
120             this.controller = null;
121         }
122     }
123
124     @Override
125     public void handleCommand(final ChannelUID channelUID, final Command command) {
126         // nop - read-only binding
127         logger.warn("Ignoring command = {} for channel = {} - the UniFi binding is read-only!", command, channelUID);
128     }
129
130     public @Nullable UniFiController getController() {
131         return controller;
132     }
133
134     // Private API
135
136     private void start(final UniFiController uc) {
137         boolean startRefresh = false;
138         try {
139             uc.start();
140             startRefresh = true;
141         } catch (final UniFiCommunicationException e) {
142             updateStatusOffline(COMMUNICATION_ERROR, STATUS_DESCRIPTION_COMMUNICATION_ERROR, e.getMessage());
143             startRefresh = true;
144         } catch (final UniFiInvalidHostException e) {
145             updateStatusOffline(CONFIGURATION_ERROR, STATUS_DESCRIPTION_INVALID_HOSTNAME, e.getMessage());
146         } catch (final UniFiSSLException e) {
147             updateStatusOffline(CONFIGURATION_ERROR, STATUS_DESCRIPTION_SSL_ERROR, e.getMessage());
148         } catch (final UniFiInvalidCredentialsException e) {
149             updateStatusOffline(CONFIGURATION_ERROR, STATUS_DESCRIPTION_INVALID_CREDENTIALS, e.getMessage());
150         } catch (final UniFiException e) {
151             logger.debug("Unknown error while configuring the UniFi Controller", e);
152             updateStatus(OFFLINE, CONFIGURATION_ERROR, e.getMessage());
153         }
154         if (startRefresh) {
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     private void cancelRefreshJob() {
161         synchronized (this) {
162             final ScheduledFuture<?> rj = refreshJob;
163
164             if (rj != null) {
165                 logger.debug("Cancelling refresh job");
166                 rj.cancel(true);
167                 refreshJob = null;
168             }
169         }
170     }
171
172     private void run() {
173         try {
174             logger.trace("Executing refresh job");
175             refresh();
176             updateStatus(ONLINE);
177         } catch (final UniFiCommunicationException e) {
178             updateStatusOffline(COMMUNICATION_ERROR, STATUS_DESCRIPTION_COMMUNICATION_ERROR, e.getMessage());
179         } catch (final UniFiInvalidCredentialsException e) {
180             updateStatusOffline(CONFIGURATION_ERROR, STATUS_DESCRIPTION_INVALID_CREDENTIALS, e.getMessage());
181         } catch (final RuntimeException | UniFiException e) {
182             logger.debug("Unhandled exception while refreshing the UniFi Controller {}", getThing().getUID(), e);
183             updateStatus(OFFLINE, COMMUNICATION_ERROR, e.getMessage());
184         }
185     }
186
187     private void updateStatusOffline(final ThingStatusDetail thingStatusDetail, final String i18nKey,
188             final @Nullable String argument) {
189         updateStatus(OFFLINE, thingStatusDetail, String.format(I18N_STATUS_WITH_ARGUMENTS, i18nKey, argument));
190     }
191
192     private void refresh() throws UniFiException {
193         final UniFiController uc = controller;
194
195         if (uc != null) {
196             logger.debug("Refreshing the UniFi Controller {}", getThing().getUID());
197             uc.refresh();
198             // mgb: then refresh all the client things
199             getThing().getThings().forEach((thing) -> {
200                 final ThingHandler handler = thing.getHandler();
201
202                 if (handler instanceof UniFiBaseThingHandler) {
203                     ((UniFiBaseThingHandler<?, ?>) handler).refresh();
204                 }
205             });
206         }
207     }
208 }