]> git.basschouten.com Git - openhab-addons.git/blob
b5ef263970eeedfc702316624fa2210718ed6c96
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 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.handler;
14
15 import static org.openhab.binding.unifi.internal.UniFiBindingConstants.CHANNEL_AP;
16 import static org.openhab.binding.unifi.internal.UniFiBindingConstants.CHANNEL_BLOCKED;
17 import static org.openhab.binding.unifi.internal.UniFiBindingConstants.CHANNEL_CMD;
18 import static org.openhab.binding.unifi.internal.UniFiBindingConstants.CHANNEL_CMD_RECONNECT;
19 import static org.openhab.binding.unifi.internal.UniFiBindingConstants.CHANNEL_ESSID;
20 import static org.openhab.binding.unifi.internal.UniFiBindingConstants.CHANNEL_EXPERIENCE;
21 import static org.openhab.binding.unifi.internal.UniFiBindingConstants.CHANNEL_GUEST;
22 import static org.openhab.binding.unifi.internal.UniFiBindingConstants.CHANNEL_HOSTNAME;
23 import static org.openhab.binding.unifi.internal.UniFiBindingConstants.CHANNEL_IP_ADDRESS;
24 import static org.openhab.binding.unifi.internal.UniFiBindingConstants.CHANNEL_LAST_SEEN;
25 import static org.openhab.binding.unifi.internal.UniFiBindingConstants.CHANNEL_MAC_ADDRESS;
26 import static org.openhab.binding.unifi.internal.UniFiBindingConstants.CHANNEL_NAME;
27 import static org.openhab.binding.unifi.internal.UniFiBindingConstants.CHANNEL_ONLINE;
28 import static org.openhab.binding.unifi.internal.UniFiBindingConstants.CHANNEL_RECONNECT;
29 import static org.openhab.binding.unifi.internal.UniFiBindingConstants.CHANNEL_RSSI;
30 import static org.openhab.binding.unifi.internal.UniFiBindingConstants.CHANNEL_SITE;
31 import static org.openhab.binding.unifi.internal.UniFiBindingConstants.CHANNEL_UPTIME;
32 import static org.openhab.core.thing.ThingStatus.OFFLINE;
33 import static org.openhab.core.thing.ThingStatusDetail.CONFIGURATION_ERROR;
34
35 import java.time.Instant;
36 import java.time.ZoneId;
37 import java.time.ZonedDateTime;
38
39 import org.eclipse.jdt.annotation.NonNullByDefault;
40 import org.eclipse.jdt.annotation.Nullable;
41 import org.openhab.binding.unifi.internal.UniFiClientThingConfig;
42 import org.openhab.binding.unifi.internal.api.UniFiController;
43 import org.openhab.binding.unifi.internal.api.UniFiException;
44 import org.openhab.binding.unifi.internal.api.cache.UniFiControllerCache;
45 import org.openhab.binding.unifi.internal.api.dto.UniFiClient;
46 import org.openhab.binding.unifi.internal.api.dto.UniFiDevice;
47 import org.openhab.binding.unifi.internal.api.dto.UniFiSite;
48 import org.openhab.binding.unifi.internal.api.dto.UniFiWiredClient;
49 import org.openhab.binding.unifi.internal.api.dto.UniFiWirelessClient;
50 import org.openhab.core.library.types.DateTimeType;
51 import org.openhab.core.library.types.OnOffType;
52 import org.openhab.core.library.types.QuantityType;
53 import org.openhab.core.library.types.StringType;
54 import org.openhab.core.library.unit.Units;
55 import org.openhab.core.thing.ChannelUID;
56 import org.openhab.core.thing.Thing;
57 import org.openhab.core.types.Command;
58 import org.openhab.core.types.State;
59 import org.openhab.core.types.UnDefType;
60 import org.slf4j.Logger;
61 import org.slf4j.LoggerFactory;
62
63 /**
64  * The {@link UniFiClientThingHandler} is responsible for handling commands and status
65  * updates for UniFi Wireless Devices.
66  *
67  * @author Matthew Bowman - Initial contribution
68  * @author Patrik Wimnell - Blocking / Unblocking client support
69  */
70 @NonNullByDefault
71 public class UniFiClientThingHandler extends UniFiBaseThingHandler<UniFiClient, UniFiClientThingConfig> {
72
73     private final Logger logger = LoggerFactory.getLogger(UniFiClientThingHandler.class);
74
75     private UniFiClientThingConfig config = new UniFiClientThingConfig();
76
77     public UniFiClientThingHandler(final Thing thing) {
78         super(thing);
79     }
80
81     @Override
82     protected boolean initialize(final UniFiClientThingConfig config) {
83         // mgb: called when the config changes
84         logger.debug("Initializing the UniFi Client Handler with config = {}", config);
85         if (!config.isValid()) {
86             updateStatus(OFFLINE, CONFIGURATION_ERROR, "@text/error.thing.client.offline.configuration_error");
87             return false;
88         }
89         this.config = config;
90         return true;
91     }
92
93     private static boolean belongsToSite(final UniFiClient client, final String siteName) {
94         boolean result = true; // mgb: assume true = proof by contradiction
95         if (!siteName.isEmpty()) {
96             final UniFiSite site = client.getSite();
97             // mgb: if the 'site' can't be found or the name doesn't match...
98             if (site == null || !site.matchesName(siteName)) {
99                 // mgb: ... then the client doesn't belong to this thing's configured 'site' and we 'filter' it
100                 result = false;
101             }
102         }
103         return result;
104     }
105
106     @Override
107     protected @Nullable UniFiClient getEntity(final UniFiControllerCache cache) {
108         final UniFiClient client = cache.getClient(config.getClientID());
109         // mgb: short circuit
110         if (client == null || !belongsToSite(client, config.getSite())) {
111             return null;
112         }
113         return client;
114     }
115
116     @Override
117     protected State getDefaultState(final String channelID) {
118         final State state;
119         switch (channelID) {
120             case CHANNEL_SITE:
121             case CHANNEL_AP:
122             case CHANNEL_ESSID:
123             case CHANNEL_RSSI:
124             case CHANNEL_MAC_ADDRESS:
125             case CHANNEL_IP_ADDRESS:
126             case CHANNEL_BLOCKED:
127                 state = UnDefType.UNDEF;
128                 break;
129             case CHANNEL_UPTIME:
130                 // mgb: uptime should default to 0 seconds
131                 state = new QuantityType<>(0, Units.SECOND);
132                 break;
133             case CHANNEL_EXPERIENCE:
134                 // mgb: uptime + experience should default to 0
135                 state = new QuantityType<>(0, Units.PERCENT);
136                 break;
137             case CHANNEL_LAST_SEEN:
138                 // mgb: lastSeen should keep the last state no matter what
139                 state = UnDefType.NULL;
140                 break;
141             case CHANNEL_RECONNECT:
142                 state = OnOffType.OFF;
143                 break;
144             default:
145                 state = UnDefType.NULL;
146                 break;
147         }
148         return state;
149     }
150
151     private synchronized boolean isClientHome(final UniFiClient client) {
152         final boolean online;
153
154         final Instant lastSeen = client.getLastSeen();
155         if (lastSeen == null) {
156             online = false;
157             logger.warn("Could not determine if client is online: cid = {}, lastSeen = null", config.getClientID());
158         } else {
159             final Instant considerHomeExpiry = lastSeen.plusSeconds(config.getConsiderHome());
160             online = Instant.now().isBefore(considerHomeExpiry);
161         }
162         return online;
163     }
164
165     @Override
166     protected State getChannelState(final UniFiClient client, final String channelId) {
167         final boolean clientHome = isClientHome(client);
168         final UniFiDevice device = client.getDevice();
169         final UniFiSite site = device == null ? null : device.getSite();
170         State state = getDefaultState(channelId);
171
172         switch (channelId) {
173             // mgb: common wired + wireless client channels
174
175             // :online
176             case CHANNEL_ONLINE:
177                 state = OnOffType.from(clientHome);
178                 break;
179
180             // :name
181             case CHANNEL_NAME:
182                 if (client.getName() != null) {
183                     state = StringType.valueOf(client.getName());
184                 }
185                 break;
186
187             // :hostname
188             case CHANNEL_HOSTNAME:
189                 if (client.getHostname() != null) {
190                     state = StringType.valueOf(client.getHostname());
191                 }
192                 break;
193             // :site
194             case CHANNEL_SITE:
195                 if (site != null && site.getDescription() != null && !site.getDescription().isBlank()) {
196                     state = StringType.valueOf(site.getDescription());
197                 }
198                 break;
199
200             // :macAddress
201             case CHANNEL_MAC_ADDRESS:
202                 if (client.getMac() != null && !client.getMac().isBlank()) {
203                     state = StringType.valueOf(client.getMac());
204                 }
205                 break;
206
207             // :ipAddress
208             case CHANNEL_IP_ADDRESS:
209                 if (client.getIp() != null && !client.getIp().isBlank()) {
210                     state = StringType.valueOf(client.getIp());
211                 }
212                 break;
213
214             // :uptime
215             case CHANNEL_UPTIME:
216                 if (client.getUptime() != null) {
217                     state = new QuantityType<>(client.getUptime(), Units.SECOND);
218                 }
219                 break;
220
221             // :lastSeen
222             case CHANNEL_LAST_SEEN:
223                 // mgb: we don't check clientOnline as lastSeen is also included in the Insights data
224                 if (client.getLastSeen() != null) {
225                     state = new DateTimeType(ZonedDateTime.ofInstant(client.getLastSeen(), ZoneId.systemDefault()));
226                 }
227                 break;
228
229             // :blocked
230             case CHANNEL_BLOCKED:
231                 state = OnOffType.from(client.isBlocked());
232                 break;
233
234             // :guest
235             case CHANNEL_GUEST:
236                 state = OnOffType.from(client.isGuest());
237                 break;
238
239             // :experience
240             case CHANNEL_EXPERIENCE:
241                 if (client.getExperience() != null) {
242                     state = new QuantityType<>(client.getExperience(), Units.PERCENT);
243                 }
244                 break;
245
246             default:
247                 // mgb: additional wired client channels
248                 if (client.isWired() && (client instanceof UniFiWiredClient wiredClient)) {
249                     state = getWiredChannelState(wiredClient, channelId, state);
250                 }
251
252                 // mgb: additional wireless client channels
253                 else if (client.isWireless() && (client instanceof UniFiWirelessClient wirelessClient)) {
254                     state = getWirelessChannelState(wirelessClient, channelId, state);
255                 }
256                 break;
257         }
258         return state;
259     }
260
261     private State getWiredChannelState(final UniFiWiredClient client, final String channelId,
262             final State defaultState) {
263         return defaultState;
264     }
265
266     private State getWirelessChannelState(final UniFiWirelessClient client, final String channelId,
267             final State defaultState) {
268         State state = defaultState;
269         switch (channelId) {
270             // :ap
271             case CHANNEL_AP:
272                 final UniFiDevice device = client.getDevice();
273                 if (device != null && device.getName() != null && !device.getName().isBlank()) {
274                     state = StringType.valueOf(device.getName());
275                 }
276                 break;
277
278             // :essid
279             case CHANNEL_ESSID:
280                 if (client.getEssid() != null && !client.getEssid().isBlank()) {
281                     state = StringType.valueOf(client.getEssid());
282                 }
283                 break;
284
285             // :rssi
286             case CHANNEL_RSSI:
287                 if (client.getRssi() != null) {
288                     state = new QuantityType<>(client.getRssi(), Units.DECIBEL_MILLIWATTS);
289                 }
290                 break;
291
292             // :reconnect
293             case CHANNEL_RECONNECT:
294                 // nop - trigger channel so it's always OFF by default
295                 state = OnOffType.OFF;
296                 break;
297         }
298         return state;
299     }
300
301     @Override
302     protected boolean handleCommand(final UniFiController controller, final UniFiClient client,
303             final ChannelUID channelUID, final Command command) throws UniFiException {
304         final String channelID = channelUID.getIdWithoutGroup();
305         switch (channelID) {
306             case CHANNEL_BLOCKED:
307                 return handleBlockedCommand(controller, client, channelUID, command);
308             case CHANNEL_CMD:
309                 return handleReconnectCommand(controller, client, channelUID, command);
310             case CHANNEL_RECONNECT:
311                 return handleReconnectSwitch(controller, client, channelUID, command);
312             default:
313                 return false;
314         }
315     }
316
317     private boolean handleBlockedCommand(final UniFiController controller, final UniFiClient client,
318             final ChannelUID channelUID, final Command command) throws UniFiException {
319         if (command instanceof OnOffType) {
320             controller.block(client, command == OnOffType.ON);
321             refresh();
322             return true;
323         }
324         return false;
325     }
326
327     private boolean handleReconnectCommand(final UniFiController controller, final UniFiClient client,
328             final ChannelUID channelUID, final Command command) throws UniFiException {
329         if (command instanceof StringType && CHANNEL_CMD_RECONNECT.equalsIgnoreCase(command.toFullString())) {
330             controller.reconnect(client);
331             return true;
332         } else {
333             logger.info("Unknown command '{}' given to wireless client thing '{}': client {}", command,
334                     getThing().getUID(), client);
335             return false;
336         }
337     }
338
339     private boolean handleReconnectSwitch(final UniFiController controller, final UniFiClient client,
340             final ChannelUID channelUID, final Command command) throws UniFiException {
341         if (command instanceof OnOffType && command == OnOffType.ON) {
342             controller.reconnect(client);
343             updateState(channelUID, OnOffType.OFF);
344             refresh();
345             return true;
346         }
347         return false;
348     }
349 }