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