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