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 java.util.concurrent.ConcurrentHashMap;
19 import org.eclipse.jdt.annotation.NonNullByDefault;
20 import org.eclipse.jdt.annotation.Nullable;
21 import org.eclipse.jetty.client.HttpClient;
22 import org.openhab.binding.unifi.internal.api.UniFiException;
23 import org.openhab.binding.unifi.internal.api.UniFiExpiredSessionException;
24 import org.openhab.binding.unifi.internal.api.UniFiNotAuthorizedException;
25 import org.openhab.binding.unifi.internal.api.cache.UniFiClientCache;
26 import org.openhab.binding.unifi.internal.api.cache.UniFiDeviceCache;
27 import org.openhab.binding.unifi.internal.api.cache.UniFiSiteCache;
28 import org.openhab.binding.unifi.internal.api.util.UniFiClientDeserializer;
29 import org.openhab.binding.unifi.internal.api.util.UniFiClientInstanceCreator;
30 import org.openhab.binding.unifi.internal.api.util.UniFiDeviceInstanceCreator;
31 import org.openhab.binding.unifi.internal.api.util.UniFiSiteInstanceCreator;
32 import org.slf4j.Logger;
33 import org.slf4j.LoggerFactory;
35 import com.google.gson.FieldNamingPolicy;
36 import com.google.gson.Gson;
37 import com.google.gson.GsonBuilder;
40 * The {@link UniFiController} is the main communication point with an external instance of the Ubiquiti Networks
41 * Controller Software.
43 * @author Matthew Bowman - Initial contribution
44 * @author Patrik Wimnell - Blocking / Unblocking client support
45 * @author Jacob Laursen - Fix online/blocked channels (broken by UniFi Controller 5.12.35)
48 public class UniFiController {
50 private final Logger logger = LoggerFactory.getLogger(UniFiController.class);
52 private Map<String, String> cidToIdCache = new ConcurrentHashMap<String, String>();
54 private UniFiSiteCache sitesCache = new UniFiSiteCache();
56 private UniFiDeviceCache devicesCache = new UniFiDeviceCache();
58 private UniFiClientCache clientsCache = new UniFiClientCache();
60 private UniFiClientCache insightsCache = new UniFiClientCache();
62 private final HttpClient httpClient;
64 private final String host;
66 private final int port;
68 private final String username;
70 private final String password;
72 private final boolean unifios;
74 private String csrfToken;
76 private final Gson gson;
78 public UniFiController(HttpClient httpClient, String host, int port, String username, String password,
80 this.httpClient = httpClient;
83 this.username = username;
84 this.password = password;
85 this.unifios = unifios;
87 UniFiSiteInstanceCreator siteInstanceCreator = new UniFiSiteInstanceCreator(this);
88 UniFiDeviceInstanceCreator deviceInstanceCreator = new UniFiDeviceInstanceCreator(this);
89 UniFiClientInstanceCreator clientInstanceCreator = new UniFiClientInstanceCreator(this);
90 this.gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
91 .registerTypeAdapter(UniFiSite.class, siteInstanceCreator)
92 .registerTypeAdapter(UniFiDevice.class, deviceInstanceCreator)
93 .registerTypeAdapter(UniFiClient.class, new UniFiClientDeserializer())
94 .registerTypeAdapter(UniFiUnknownClient.class, clientInstanceCreator)
95 .registerTypeAdapter(UniFiWiredClient.class, clientInstanceCreator)
96 .registerTypeAdapter(UniFiWirelessClient.class, clientInstanceCreator).create();
101 public void start() throws UniFiException {
109 public void stop() throws UniFiException {
113 public void obtainCsrfToken() throws UniFiException {
116 UniFiControllerRequest<Void> req = newRequest(Void.class);
121 public void login() throws UniFiException {
122 UniFiControllerRequest<Void> req = newRequest(Void.class);
123 req.setPath(unifios ? "/api/auth/login" : "/api/login");
124 req.setBodyParameter("username", username);
125 req.setBodyParameter("password", password);
126 // scurb: Changed strict = false to make blocking feature work
127 req.setBodyParameter("strict", false);
128 req.setBodyParameter("remember", false);
132 public void logout() throws UniFiException {
134 UniFiControllerRequest<Void> req = newRequest(Void.class);
135 req.setPath(unifios ? "/api/auth/logout" : "/logout");
139 public void refresh() throws UniFiException {
140 synchronized (this) {
141 sitesCache = getSites();
142 devicesCache = getDevices();
143 clientsCache = getClients();
144 insightsCache = getInsights();
150 public @Nullable UniFiSite getSite(@Nullable String id) {
151 UniFiSite site = null;
152 if (id != null && !id.isBlank()) {
153 synchronized (this) {
154 site = sitesCache.get(id);
157 logger.debug("Could not find a matching site for id = '{}'", id);
165 public @Nullable UniFiDevice getDevice(@Nullable String id) {
166 UniFiDevice device = null;
167 if (id != null && !id.isBlank()) {
168 synchronized (this) {
169 device = devicesCache.get(id);
171 if (device == null) {
172 logger.debug("Could not find a matching device for id = '{}'", id);
180 public @Nullable UniFiClient getClient(@Nullable String cid) {
181 UniFiClient client = null;
182 if (cid != null && !cid.isBlank()) {
183 // Prefer lookups through _id, until initialized use cid.
184 String id = cidToIdCache.get(cid);
185 synchronized (this) {
186 // mgb: first check active clients and fallback to insights if not found
187 client = clientsCache.get(id != null ? id : cid);
188 if (client == null) {
189 client = insightsCache.get(id != null ? id : cid);
192 if (client == null) {
193 logger.debug("Could not find a matching client for cid = {}", cid);
195 cidToIdCache.put(cid, client.id);
201 protected void block(UniFiClient client, boolean blocked) throws UniFiException {
202 UniFiControllerRequest<Void> req = newRequest(Void.class);
203 req.setAPIPath("/api/s/" + client.getSite().getName() + "/cmd/stamgr");
204 req.setBodyParameter("cmd", blocked ? "block-sta" : "unblock-sta");
205 req.setBodyParameter("mac", client.getMac());
209 protected void reconnect(UniFiClient client) throws UniFiException {
210 UniFiControllerRequest<Void> req = newRequest(Void.class);
211 req.setAPIPath("/api/s/" + client.getSite().getName() + "/cmd/stamgr");
212 req.setBodyParameter("cmd", "kick-sta");
213 req.setBodyParameter("mac", client.getMac());
219 private <T> UniFiControllerRequest<T> newRequest(Class<T> responseType) {
220 return new UniFiControllerRequest<>(responseType, gson, httpClient, host, port, csrfToken, unifios);
223 private <T> @Nullable T executeRequest(UniFiControllerRequest<T> request) throws UniFiException {
226 result = request.execute();
227 csrfToken = request.getCsrfToken();
228 } catch (UniFiExpiredSessionException e) {
230 result = executeRequest(request);
231 } catch (UniFiNotAuthorizedException e) {
232 logger.warn("Not Authorized! Please make sure your controller credentials have administrator rights");
238 private UniFiSiteCache getSites() throws UniFiException {
239 UniFiControllerRequest<UniFiSite[]> req = newRequest(UniFiSite[].class);
240 req.setAPIPath("/api/self/sites");
241 UniFiSite[] sites = executeRequest(req);
242 UniFiSiteCache cache = new UniFiSiteCache();
244 logger.debug("Found {} UniFi Site(s): {}", sites.length, lazyFormatAsList(sites));
245 for (UniFiSite site : sites) {
252 private UniFiDeviceCache getDevices() throws UniFiException {
253 UniFiDeviceCache cache = new UniFiDeviceCache();
254 Collection<UniFiSite> sites = sitesCache.values();
255 for (UniFiSite site : sites) {
256 cache.putAll(getDevices(site));
261 private UniFiDeviceCache getDevices(UniFiSite site) throws UniFiException {
262 UniFiControllerRequest<UniFiDevice[]> req = newRequest(UniFiDevice[].class);
263 req.setAPIPath("/api/s/" + site.getName() + "/stat/device");
264 UniFiDevice[] devices = executeRequest(req);
265 UniFiDeviceCache cache = new UniFiDeviceCache();
266 if (devices != null) {
267 logger.debug("Found {} UniFi Device(s): {}", devices.length, lazyFormatAsList(devices));
268 for (UniFiDevice device : devices) {
275 private UniFiClientCache getClients() throws UniFiException {
276 UniFiClientCache cache = new UniFiClientCache();
277 Collection<UniFiSite> sites = sitesCache.values();
278 for (UniFiSite site : sites) {
279 cache.putAll(getClients(site));
284 private UniFiClientCache getClients(UniFiSite site) throws UniFiException {
285 UniFiControllerRequest<UniFiClient[]> req = newRequest(UniFiClient[].class);
286 req.setAPIPath("/api/s/" + site.getName() + "/stat/sta");
287 UniFiClient[] clients = executeRequest(req);
288 UniFiClientCache cache = new UniFiClientCache();
289 if (clients != null) {
290 logger.debug("Found {} UniFi Client(s): {}", clients.length, lazyFormatAsList(clients));
291 for (UniFiClient client : clients) {
298 private UniFiClientCache getInsights() throws UniFiException {
299 UniFiClientCache cache = new UniFiClientCache();
300 Collection<UniFiSite> sites = sitesCache.values();
301 for (UniFiSite site : sites) {
302 cache.putAll(getInsights(site));
307 private UniFiClientCache getInsights(UniFiSite site) throws UniFiException {
308 UniFiControllerRequest<UniFiClient[]> req = newRequest(UniFiClient[].class);
309 req.setAPIPath("/api/s/" + site.getName() + "/stat/alluser");
310 req.setQueryParameter("within", 168); // scurb: Changed to 7 days.
311 UniFiClient[] clients = executeRequest(req);
312 UniFiClientCache cache = new UniFiClientCache();
313 if (clients != null) {
314 logger.debug("Found {} UniFi Insights(s): {}", clients.length, lazyFormatAsList(clients));
315 for (UniFiClient client : clients) {
322 private static Object lazyFormatAsList(Object[] arr) {
323 return new Object() {
326 public String toString() {
328 for (Object o : arr) {
329 value += "\n - " + o.toString();