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