2 * Copyright (c) 2010-2021 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.api.model;
15 import java.util.Collection;
17 import org.eclipse.jdt.annotation.NonNullByDefault;
18 import org.eclipse.jdt.annotation.Nullable;
19 import org.eclipse.jetty.client.HttpClient;
20 import org.openhab.binding.unifi.internal.api.UniFiException;
21 import org.openhab.binding.unifi.internal.api.UniFiExpiredSessionException;
22 import org.openhab.binding.unifi.internal.api.UniFiNotAuthorizedException;
23 import org.openhab.binding.unifi.internal.api.cache.UniFiClientCache;
24 import org.openhab.binding.unifi.internal.api.cache.UniFiDeviceCache;
25 import org.openhab.binding.unifi.internal.api.cache.UniFiSiteCache;
26 import org.openhab.binding.unifi.internal.api.util.UniFiClientDeserializer;
27 import org.openhab.binding.unifi.internal.api.util.UniFiClientInstanceCreator;
28 import org.openhab.binding.unifi.internal.api.util.UniFiDeviceInstanceCreator;
29 import org.openhab.binding.unifi.internal.api.util.UniFiSiteInstanceCreator;
30 import org.slf4j.Logger;
31 import org.slf4j.LoggerFactory;
33 import com.google.gson.FieldNamingPolicy;
34 import com.google.gson.Gson;
35 import com.google.gson.GsonBuilder;
38 * The {@link UniFiController} is the main communication point with an external instance of the Ubiquiti Networks
39 * Controller Software.
41 * @author Matthew Bowman - Initial contribution
42 * @author Patrik Wimnell - Blocking / Unblocking client support
45 public class UniFiController {
47 private final Logger logger = LoggerFactory.getLogger(UniFiController.class);
49 private UniFiSiteCache sitesCache = new UniFiSiteCache();
51 private UniFiDeviceCache devicesCache = new UniFiDeviceCache();
53 private UniFiClientCache clientsCache = new UniFiClientCache();
55 private UniFiClientCache insightsCache = new UniFiClientCache();
57 private final HttpClient httpClient;
59 private final String host;
61 private final int port;
63 private final String username;
65 private final String password;
67 private final boolean unifios;
69 private String csrfToken;
71 private final Gson gson;
73 public UniFiController(HttpClient httpClient, String host, int port, String username, String password,
75 this.httpClient = httpClient;
78 this.username = username;
79 this.password = password;
80 this.unifios = unifios;
82 UniFiSiteInstanceCreator siteInstanceCreator = new UniFiSiteInstanceCreator(this);
83 UniFiDeviceInstanceCreator deviceInstanceCreator = new UniFiDeviceInstanceCreator(this);
84 UniFiClientInstanceCreator clientInstanceCreator = new UniFiClientInstanceCreator(this);
85 this.gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
86 .registerTypeAdapter(UniFiSite.class, siteInstanceCreator)
87 .registerTypeAdapter(UniFiDevice.class, deviceInstanceCreator)
88 .registerTypeAdapter(UniFiClient.class, new UniFiClientDeserializer())
89 .registerTypeAdapter(UniFiUnknownClient.class, clientInstanceCreator)
90 .registerTypeAdapter(UniFiWiredClient.class, clientInstanceCreator)
91 .registerTypeAdapter(UniFiWirelessClient.class, clientInstanceCreator).create();
96 public void start() throws UniFiException {
104 public void stop() throws UniFiException {
108 public void obtainCsrfToken() throws UniFiException {
111 UniFiControllerRequest<Void> req = newRequest(Void.class);
116 public void login() throws UniFiException {
117 UniFiControllerRequest<Void> req = newRequest(Void.class);
118 req.setPath(unifios ? "/api/auth/login" : "/api/login");
119 req.setBodyParameter("username", username);
120 req.setBodyParameter("password", password);
121 // scurb: Changed strict = false to make blocking feature work
122 req.setBodyParameter("strict", false);
123 req.setBodyParameter("remember", false);
127 public void logout() throws UniFiException {
129 UniFiControllerRequest<Void> req = newRequest(Void.class);
130 req.setPath(unifios ? "/api/auth/logout" : "/logout");
134 public void refresh() throws UniFiException {
135 synchronized (this) {
136 sitesCache = getSites();
137 devicesCache = getDevices();
138 clientsCache = getClients();
139 insightsCache = getInsights();
145 public @Nullable UniFiSite getSite(@Nullable String id) {
146 UniFiSite site = null;
147 if (id != null && !id.isBlank()) {
148 synchronized (this) {
149 site = sitesCache.get(id);
152 logger.debug("Could not find a matching site for id = '{}'", id);
160 public @Nullable UniFiDevice getDevice(@Nullable String id) {
161 UniFiDevice device = null;
162 if (id != null && !id.isBlank()) {
163 synchronized (this) {
164 device = devicesCache.get(id);
166 if (device == null) {
167 logger.debug("Could not find a matching device for id = '{}'", id);
175 public @Nullable UniFiClient getClient(@Nullable String id) {
176 UniFiClient client = null;
177 if (id != null && !id.isBlank()) {
178 synchronized (this) {
179 // mgb: first check active clients and fallback to insights if not found
180 client = clientsCache.get(id);
181 if (client == null) {
182 client = insightsCache.get(id);
185 if (client == null) {
186 logger.debug("Could not find a matching client for id = {}", id);
192 protected void block(UniFiClient client, boolean blocked) throws UniFiException {
193 UniFiControllerRequest<Void> req = newRequest(Void.class);
194 req.setAPIPath("/api/s/" + client.getSite().getName() + "/cmd/stamgr");
195 req.setBodyParameter("cmd", blocked ? "block-sta" : "unblock-sta");
196 req.setBodyParameter("mac", client.getMac());
200 protected void reconnect(UniFiClient client) throws UniFiException {
201 UniFiControllerRequest<Void> req = newRequest(Void.class);
202 req.setAPIPath("/api/s/" + client.getSite().getName() + "/cmd/stamgr");
203 req.setBodyParameter("cmd", "kick-sta");
204 req.setBodyParameter("mac", client.getMac());
210 private <T> UniFiControllerRequest<T> newRequest(Class<T> responseType) {
211 return new UniFiControllerRequest<>(responseType, gson, httpClient, host, port, csrfToken, unifios);
214 private <T> @Nullable T executeRequest(UniFiControllerRequest<T> request) throws UniFiException {
217 result = request.execute();
218 csrfToken = request.getCsrfToken();
219 } catch (UniFiExpiredSessionException e) {
221 result = executeRequest(request);
222 } catch (UniFiNotAuthorizedException e) {
223 logger.warn("Not Authorized! Please make sure your controller credentials have administrator rights");
229 private UniFiSiteCache getSites() throws UniFiException {
230 UniFiControllerRequest<UniFiSite[]> req = newRequest(UniFiSite[].class);
231 req.setAPIPath("/api/self/sites");
232 UniFiSite[] sites = executeRequest(req);
233 UniFiSiteCache cache = new UniFiSiteCache();
235 logger.debug("Found {} UniFi Site(s): {}", sites.length, lazyFormatAsList(sites));
236 for (UniFiSite site : sites) {
243 private UniFiDeviceCache getDevices() throws UniFiException {
244 UniFiDeviceCache cache = new UniFiDeviceCache();
245 Collection<UniFiSite> sites = sitesCache.values();
246 for (UniFiSite site : sites) {
247 cache.putAll(getDevices(site));
252 private UniFiDeviceCache getDevices(UniFiSite site) throws UniFiException {
253 UniFiControllerRequest<UniFiDevice[]> req = newRequest(UniFiDevice[].class);
254 req.setAPIPath("/api/s/" + site.getName() + "/stat/device");
255 UniFiDevice[] devices = executeRequest(req);
256 UniFiDeviceCache cache = new UniFiDeviceCache();
257 if (devices != null) {
258 logger.debug("Found {} UniFi Device(s): {}", devices.length, lazyFormatAsList(devices));
259 for (UniFiDevice device : devices) {
266 private UniFiClientCache getClients() throws UniFiException {
267 UniFiClientCache cache = new UniFiClientCache();
268 Collection<UniFiSite> sites = sitesCache.values();
269 for (UniFiSite site : sites) {
270 cache.putAll(getClients(site));
275 private UniFiClientCache getClients(UniFiSite site) throws UniFiException {
276 UniFiControllerRequest<UniFiClient[]> req = newRequest(UniFiClient[].class);
277 req.setAPIPath("/api/s/" + site.getName() + "/stat/sta");
278 UniFiClient[] clients = executeRequest(req);
279 UniFiClientCache cache = new UniFiClientCache();
280 if (clients != null) {
281 logger.debug("Found {} UniFi Client(s): {}", clients.length, lazyFormatAsList(clients));
282 for (UniFiClient client : clients) {
289 private UniFiClientCache getInsights() throws UniFiException {
290 UniFiClientCache cache = new UniFiClientCache();
291 Collection<UniFiSite> sites = sitesCache.values();
292 for (UniFiSite site : sites) {
293 cache.putAll(getInsights(site));
298 private UniFiClientCache getInsights(UniFiSite site) throws UniFiException {
299 UniFiControllerRequest<UniFiClient[]> req = newRequest(UniFiClient[].class);
300 req.setAPIPath("/api/s/" + site.getName() + "/stat/alluser");
301 req.setQueryParameter("within", 168); // scurb: Changed to 7 days.
302 UniFiClient[] clients = executeRequest(req);
303 UniFiClientCache cache = new UniFiClientCache();
304 if (clients != null) {
305 logger.debug("Found {} UniFi Insights(s): {}", clients.length, lazyFormatAsList(clients));
306 for (UniFiClient client : clients) {
313 private static Object lazyFormatAsList(Object[] arr) {
314 return new Object() {
317 public String toString() {
319 for (Object o : arr) {
320 value += "\n - " + o.toString();