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