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