]> git.basschouten.com Git - openhab-addons.git/blob
3a48aaf1977632d31b8805c1cd955d6d07712e87
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 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.api.model;
14
15 import java.util.Collection;
16 import java.util.Map;
17 import java.util.concurrent.ConcurrentHashMap;
18
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;
34
35 import com.google.gson.FieldNamingPolicy;
36 import com.google.gson.Gson;
37 import com.google.gson.GsonBuilder;
38
39 /**
40  * The {@link UniFiController} is the main communication point with an external instance of the Ubiquiti Networks
41  * Controller Software.
42  *
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)
46  */
47 @NonNullByDefault
48 public class UniFiController {
49
50     private final Logger logger = LoggerFactory.getLogger(UniFiController.class);
51
52     private Map<String, String> cidToIdCache = new ConcurrentHashMap<String, String>();
53
54     private UniFiSiteCache sitesCache = new UniFiSiteCache();
55
56     private UniFiDeviceCache devicesCache = new UniFiDeviceCache();
57
58     private UniFiClientCache clientsCache = new UniFiClientCache();
59
60     private UniFiClientCache insightsCache = new UniFiClientCache();
61
62     private final HttpClient httpClient;
63
64     private final String host;
65
66     private final int port;
67
68     private final String username;
69
70     private final String password;
71
72     private final boolean unifios;
73
74     private String csrfToken;
75
76     private final Gson gson;
77
78     public UniFiController(HttpClient httpClient, String host, int port, String username, String password,
79             boolean unifios) {
80         this.httpClient = httpClient;
81         this.host = host;
82         this.port = port;
83         this.username = username;
84         this.password = password;
85         this.unifios = unifios;
86         this.csrfToken = "";
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();
97     }
98
99     // Public API
100
101     public void start() throws UniFiException {
102         if (unifios) {
103             obtainCsrfToken();
104         }
105
106         login();
107     }
108
109     public void stop() throws UniFiException {
110         logout();
111     }
112
113     public void obtainCsrfToken() throws UniFiException {
114         csrfToken = "";
115
116         UniFiControllerRequest<Void> req = newRequest(Void.class);
117         req.setPath("/");
118         executeRequest(req);
119     }
120
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);
129         executeRequest(req);
130     }
131
132     public void logout() throws UniFiException {
133         csrfToken = "";
134         UniFiControllerRequest<Void> req = newRequest(Void.class);
135         req.setPath(unifios ? "/api/auth/logout" : "/logout");
136         executeRequest(req);
137     }
138
139     public void refresh() throws UniFiException {
140         synchronized (this) {
141             sitesCache = getSites();
142             devicesCache = getDevices();
143             clientsCache = getClients();
144             insightsCache = getInsights();
145         }
146     }
147
148     // Site API
149
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);
155             }
156             if (site == null) {
157                 logger.debug("Could not find a matching site for id = '{}'", id);
158             }
159         }
160         return site;
161     }
162
163     // Device API
164
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);
170             }
171             if (device == null) {
172                 logger.debug("Could not find a matching device for id = '{}'", id);
173             }
174         }
175         return device;
176     }
177
178     // Client API
179
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);
190                 }
191             }
192             if (client == null) {
193                 logger.debug("Could not find a matching client for cid = {}", cid);
194             } else {
195                 cidToIdCache.put(cid, client.id);
196             }
197         }
198         return client;
199     }
200
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());
206         executeRequest(req);
207     }
208
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());
214         executeRequest(req);
215     }
216
217     // Internal API
218
219     private <T> UniFiControllerRequest<T> newRequest(Class<T> responseType) {
220         return new UniFiControllerRequest<>(responseType, gson, httpClient, host, port, csrfToken, unifios);
221     }
222
223     private <T> @Nullable T executeRequest(UniFiControllerRequest<T> request) throws UniFiException {
224         T result;
225         try {
226             result = request.execute();
227             csrfToken = request.getCsrfToken();
228         } catch (UniFiExpiredSessionException e) {
229             login();
230             result = executeRequest(request);
231         } catch (UniFiNotAuthorizedException e) {
232             logger.warn("Not Authorized! Please make sure your controller credentials have administrator rights");
233             result = null;
234         }
235         return result;
236     }
237
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();
243         if (sites != null) {
244             logger.debug("Found {} UniFi Site(s): {}", sites.length, lazyFormatAsList(sites));
245             for (UniFiSite site : sites) {
246                 cache.put(site);
247             }
248         }
249         return cache;
250     }
251
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));
257         }
258         return cache;
259     }
260
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) {
269                 cache.put(device);
270             }
271         }
272         return cache;
273     }
274
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));
280         }
281         return cache;
282     }
283
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) {
292                 cache.put(client);
293             }
294         }
295         return cache;
296     }
297
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));
303         }
304         return cache;
305     }
306
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) {
316                 cache.put(client);
317             }
318         }
319         return cache;
320     }
321
322     private static Object lazyFormatAsList(Object[] arr) {
323         return new Object() {
324
325             @Override
326             public String toString() {
327                 String value = "";
328                 for (Object o : arr) {
329                     value += "\n - " + o.toString();
330                 }
331                 return value;
332             }
333         };
334     }
335 }