]> git.basschouten.com Git - openhab-addons.git/blob
05b7c69d962a4a8a59254f2c99b7f3b6296b35af
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2024 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;
14
15 import java.util.Collection;
16 import java.util.List;
17
18 import org.eclipse.jdt.annotation.NonNullByDefault;
19 import org.eclipse.jdt.annotation.Nullable;
20 import org.eclipse.jetty.client.HttpClient;
21 import org.eclipse.jetty.http.HttpMethod;
22 import org.openhab.binding.unifi.internal.api.cache.UniFiControllerCache;
23 import org.openhab.binding.unifi.internal.api.dto.UnfiPortOverrideJsonObject;
24 import org.openhab.binding.unifi.internal.api.dto.UniFiClient;
25 import org.openhab.binding.unifi.internal.api.dto.UniFiDevice;
26 import org.openhab.binding.unifi.internal.api.dto.UniFiSite;
27 import org.openhab.binding.unifi.internal.api.dto.UniFiSwitchPorts;
28 import org.openhab.binding.unifi.internal.api.dto.UniFiUnknownClient;
29 import org.openhab.binding.unifi.internal.api.dto.UniFiVoucher;
30 import org.openhab.binding.unifi.internal.api.dto.UniFiWiredClient;
31 import org.openhab.binding.unifi.internal.api.dto.UniFiWirelessClient;
32 import org.openhab.binding.unifi.internal.api.dto.UniFiWlan;
33 import org.openhab.binding.unifi.internal.api.util.UnfiPortOverrideJsonElementDeserializer;
34 import org.openhab.binding.unifi.internal.api.util.UniFiClientDeserializer;
35 import org.openhab.binding.unifi.internal.api.util.UniFiClientInstanceCreator;
36 import org.openhab.binding.unifi.internal.api.util.UniFiDeviceInstanceCreator;
37 import org.openhab.binding.unifi.internal.api.util.UniFiSiteInstanceCreator;
38 import org.openhab.binding.unifi.internal.api.util.UniFiVoucherInstanceCreator;
39 import org.openhab.binding.unifi.internal.api.util.UniFiWlanInstanceCreator;
40 import org.slf4j.Logger;
41 import org.slf4j.LoggerFactory;
42
43 import com.google.gson.FieldNamingPolicy;
44 import com.google.gson.Gson;
45 import com.google.gson.GsonBuilder;
46 import com.google.gson.JsonObject;
47
48 /**
49  * The {@link UniFiController} is the main communication point with an external instance of the Ubiquiti Networks
50  * Controller Software.
51  *
52  * @author Matthew Bowman - Initial contribution
53  * @author Patrik Wimnell - Blocking / Unblocking client support
54  * @author Jacob Laursen - Fix online/blocked channels (broken by UniFi Controller 5.12.35)
55  * @author Hilbrand Bouwkamp - Added POEPort support, moved generic cache related code to cache object
56  * @author Mark Herwege - Added guest vouchers
57  */
58 @NonNullByDefault
59 public class UniFiController {
60
61     private static final int INSIGHT_WITHIN_HOURS = 7 * 24; // scurb: Changed to 7 days.
62
63     private final Logger logger = LoggerFactory.getLogger(UniFiController.class);
64
65     private final HttpClient httpClient;
66     private final UniFiControllerCache cache = new UniFiControllerCache();
67
68     private final String host;
69     private final int port;
70     private final String username;
71     private final String password;
72     private final boolean unifios;
73     private final Gson gson;
74     private final Gson poeGson;
75
76     private String csrfToken;
77
78     public UniFiController(final HttpClient httpClient, final String host, final int port, final String username,
79             final String password, final 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         final UniFiSiteInstanceCreator siteInstanceCreator = new UniFiSiteInstanceCreator(cache);
88         final UniFiWlanInstanceCreator wlanInstanceCreator = new UniFiWlanInstanceCreator(cache);
89         final UniFiDeviceInstanceCreator deviceInstanceCreator = new UniFiDeviceInstanceCreator(cache);
90         final UniFiClientInstanceCreator clientInstanceCreator = new UniFiClientInstanceCreator(cache);
91         final UniFiVoucherInstanceCreator voucherInstanceCreator = new UniFiVoucherInstanceCreator(cache);
92         this.gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
93                 .registerTypeAdapter(UniFiSite.class, siteInstanceCreator)
94                 .registerTypeAdapter(UniFiWlan.class, wlanInstanceCreator)
95                 .registerTypeAdapter(UniFiDevice.class, deviceInstanceCreator)
96                 .registerTypeAdapter(UniFiClient.class, new UniFiClientDeserializer())
97                 .registerTypeAdapter(UniFiUnknownClient.class, clientInstanceCreator)
98                 .registerTypeAdapter(UniFiWiredClient.class, clientInstanceCreator)
99                 .registerTypeAdapter(UniFiWirelessClient.class, clientInstanceCreator)
100                 .registerTypeAdapter(UniFiVoucher.class, voucherInstanceCreator).create();
101         this.poeGson = new GsonBuilder()
102                 .registerTypeAdapter(UnfiPortOverrideJsonObject.class, new UnfiPortOverrideJsonElementDeserializer())
103                 .create();
104     }
105
106     // Public API
107
108     public void start() throws UniFiException {
109         if (unifios) {
110             obtainCsrfToken();
111         }
112
113         login();
114     }
115
116     public void stop() throws UniFiException {
117         logout();
118     }
119
120     public void obtainCsrfToken() throws UniFiException {
121         csrfToken = "";
122
123         final UniFiControllerRequest<Void> req = newRequest(Void.class, HttpMethod.GET, gson);
124         req.setPath("/");
125         executeRequest(req);
126     }
127
128     public void login() throws UniFiException {
129         final UniFiControllerRequest<Void> req = newRequest(Void.class, HttpMethod.POST, gson);
130         req.setPath(unifios ? "/api/auth/login" : "/api/login");
131         req.setBodyParameter("username", username);
132         req.setBodyParameter("password", password);
133         // scurb: Changed strict = false to make blocking feature work
134         req.setBodyParameter("strict", false);
135         req.setBodyParameter("remember", false);
136         executeRequest(req, true);
137     }
138
139     public void logout() throws UniFiException {
140         csrfToken = "";
141         final UniFiControllerRequest<Void> req = newRequest(Void.class, HttpMethod.POST, gson);
142         req.setPath(unifios ? "/api/auth/logout" : "/logout");
143         executeRequest(req);
144     }
145
146     public void refresh() throws UniFiException {
147         synchronized (this) {
148             cache.clear();
149             final Collection<UniFiSite> sites = refreshSites();
150             refreshWlans(sites);
151             refreshDevices(sites);
152             refreshClients(sites);
153             refreshInsights(sites);
154             refreshVouchers(sites);
155         }
156     }
157
158     public UniFiControllerCache getCache() {
159         return cache;
160     }
161
162     public @Nullable UniFiSwitchPorts getSwitchPorts(@Nullable final String deviceId) {
163         return cache.getSwitchPorts(deviceId);
164     }
165
166     public void block(final UniFiClient client, final boolean blocked) throws UniFiException {
167         final UniFiControllerRequest<Void> req = newRequest(Void.class, HttpMethod.POST, gson);
168         req.setAPIPath(String.format("/api/s/%s/cmd/stamgr", client.getSite().getName()));
169         req.setBodyParameter("cmd", blocked ? "block-sta" : "unblock-sta");
170         req.setBodyParameter("mac", client.getMac());
171         executeRequest(req);
172         refresh();
173     }
174
175     public void reconnect(final UniFiClient client) throws UniFiException {
176         final UniFiControllerRequest<Void> req = newRequest(Void.class, HttpMethod.POST, gson);
177         req.setAPIPath(String.format("/api/s/%s/cmd/stamgr", client.getSite().getName()));
178         req.setBodyParameter("cmd", "kick-sta");
179         req.setBodyParameter("mac", client.getMac());
180         executeRequest(req);
181         refresh();
182     }
183
184     public boolean poeMode(final UniFiDevice device, final List<JsonObject> data) throws UniFiException {
185         // Safety check to make sure no empty data is send to avoid corrupting override data on the device.
186         if (data.isEmpty() || data.stream().anyMatch(p -> p.entrySet().isEmpty())) {
187             logger.info("Not overriding port for '{}', because port data contains empty JSON: {}", device.getName(),
188                     poeGson.toJson(data));
189             return false;
190         } else {
191             final UniFiControllerRequest<Void> req = newRequest(Void.class, HttpMethod.PUT, poeGson);
192             req.setAPIPath(String.format("/api/s/%s/rest/device/%s", device.getSite().getName(), device.getId()));
193             req.setBodyParameter("port_overrides", data);
194             executeRequest(req);
195             return true;
196         }
197     }
198
199     public void poePowerCycle(final UniFiDevice device, final Integer portIdx) throws UniFiException {
200         final UniFiControllerRequest<Void> req = newRequest(Void.class, HttpMethod.POST, gson);
201         req.setAPIPath(String.format("/api/s/%s/cmd/devmgr", device.getSite().getName()));
202         req.setBodyParameter("cmd", "power-cycle");
203         req.setBodyParameter("mac", device.getMac());
204         req.setBodyParameter("port_idx", portIdx);
205         executeRequest(req);
206         refresh();
207     }
208
209     public void enableWifi(final UniFiWlan wlan, final boolean enable) throws UniFiException {
210         final UniFiControllerRequest<Void> req = newRequest(Void.class, HttpMethod.PUT, poeGson);
211         req.setAPIPath(String.format("/api/s/%s/rest/wlanconf/%s", wlan.getSite().getName(), wlan.getId()));
212         req.setBodyParameter("_id", wlan.getId());
213         req.setBodyParameter("enabled", enable ? "true" : "false");
214         executeRequest(req);
215         refresh();
216     }
217
218     public void generateVouchers(final UniFiSite site, final int count, final int expiration, final int users,
219             @Nullable Integer upLimit, @Nullable Integer downLimit, @Nullable Integer dataQuota) throws UniFiException {
220         final UniFiControllerRequest<Void> req = newRequest(Void.class, HttpMethod.POST, gson);
221         req.setAPIPath(String.format("/api/s/%s/cmd/hotspot", site.getName()));
222         req.setBodyParameter("cmd", "create-voucher");
223         req.setBodyParameter("expire", expiration);
224         req.setBodyParameter("n", count);
225         req.setBodyParameter("quota", users);
226         if (upLimit != null) {
227             req.setBodyParameter("up", upLimit);
228         }
229         if (downLimit != null) {
230             req.setBodyParameter("down", downLimit);
231         }
232         if (dataQuota != null) {
233             req.setBodyParameter("bytes", dataQuota);
234         }
235         executeRequest(req);
236         refresh();
237     }
238
239     public void revokeVouchers(final UniFiSite site, final List<UniFiVoucher> vouchers) throws UniFiException {
240         for (UniFiVoucher voucher : vouchers) {
241             final UniFiControllerRequest<Void> req = newRequest(Void.class, HttpMethod.POST, gson);
242             req.setAPIPath(String.format("/api/s/%s/cmd/hotspot", site.getName()));
243             req.setBodyParameter("cmd", "delete-voucher");
244             req.setBodyParameter("_id", voucher.getId());
245             executeRequest(req);
246         }
247         refresh();
248     }
249
250     // Internal API
251
252     private <T> UniFiControllerRequest<T> newRequest(final Class<T> responseType, final HttpMethod method,
253             final Gson gson) {
254         return new UniFiControllerRequest<>(responseType, gson, httpClient, method, host, port, csrfToken, unifios);
255     }
256
257     private <T> @Nullable T executeRequest(final UniFiControllerRequest<T> request) throws UniFiException {
258         return executeRequest(request, false);
259     }
260
261     private <T> @Nullable T executeRequest(final UniFiControllerRequest<T> request, final boolean fromLogin)
262             throws UniFiException {
263         T result;
264         try {
265             result = (T) request.execute();
266             csrfToken = request.getCsrfToken();
267         } catch (final UniFiExpiredSessionException e) {
268             if (fromLogin) {
269                 // if this exception is thrown from a login attempt something is wrong, because the login should init
270                 // the session.
271                 throw new UniFiCommunicationException(e);
272             } else {
273                 login();
274                 result = (T) executeRequest(request);
275             }
276         } catch (final UniFiNotAuthorizedException e) {
277             logger.warn("Not Authorized! Please make sure your controller credentials have administrator rights");
278             result = (T) null;
279         }
280         return result;
281     }
282
283     private List<UniFiSite> refreshSites() throws UniFiException {
284         final UniFiControllerRequest<UniFiSite[]> req = newRequest(UniFiSite[].class, HttpMethod.GET, gson);
285         req.setAPIPath("/api/self/sites");
286         return cache.setSites(executeRequest(req));
287     }
288
289     private void refreshWlans(final Collection<UniFiSite> sites) throws UniFiException {
290         for (final UniFiSite site : sites) {
291             cache.putWlans(getWlans(site));
292         }
293     }
294
295     private UniFiWlan @Nullable [] getWlans(final UniFiSite site) throws UniFiException {
296         final UniFiControllerRequest<UniFiWlan[]> req = newRequest(UniFiWlan[].class, HttpMethod.GET, gson);
297         req.setAPIPath(String.format("/api/s/%s/rest/wlanconf", site.getName()));
298         return executeRequest(req);
299     }
300
301     private void refreshDevices(final Collection<UniFiSite> sites) throws UniFiException {
302         for (final UniFiSite site : sites) {
303             cache.putDevices(getDevices(site));
304         }
305     }
306
307     private UniFiDevice @Nullable [] getDevices(final UniFiSite site) throws UniFiException {
308         final UniFiControllerRequest<UniFiDevice[]> req = newRequest(UniFiDevice[].class, HttpMethod.GET, gson);
309         req.setAPIPath(String.format("/api/s/%s/stat/device", site.getName()));
310         return executeRequest(req);
311     }
312
313     private void refreshClients(final Collection<UniFiSite> sites) throws UniFiException {
314         for (final UniFiSite site : sites) {
315             cache.putClients(getClients(site));
316         }
317     }
318
319     private UniFiClient @Nullable [] getClients(final UniFiSite site) throws UniFiException {
320         final UniFiControllerRequest<UniFiClient[]> req = newRequest(UniFiClient[].class, HttpMethod.GET, gson);
321         req.setAPIPath(String.format("/api/s/%s/stat/sta", site.getName()));
322         return executeRequest(req);
323     }
324
325     private void refreshVouchers(final Collection<UniFiSite> sites) throws UniFiException {
326         for (final UniFiSite site : sites) {
327             cache.putVouchers(getVouchers(site));
328         }
329     }
330
331     private UniFiVoucher @Nullable [] getVouchers(final UniFiSite site) throws UniFiException {
332         final UniFiControllerRequest<UniFiVoucher[]> req = newRequest(UniFiVoucher[].class, HttpMethod.GET, gson);
333         req.setAPIPath(String.format("/api/s/%s/stat/voucher", site.getName()));
334         return executeRequest(req);
335     }
336
337     private void refreshInsights(final Collection<UniFiSite> sites) throws UniFiException {
338         for (final UniFiSite site : sites) {
339             cache.putInsights(getInsights(site));
340         }
341     }
342
343     private UniFiClient @Nullable [] getInsights(final UniFiSite site) throws UniFiException {
344         final UniFiControllerRequest<UniFiClient[]> req = newRequest(UniFiClient[].class, HttpMethod.GET, gson);
345         req.setAPIPath(String.format("/api/s/%s/stat/alluser", site.getName()));
346         req.setQueryParameter("within", INSIGHT_WITHIN_HOURS);
347         return executeRequest(req);
348     }
349 }