]> git.basschouten.com Git - openhab-addons.git/blob
139e031fb9d11f41866a861ef9351c215b6db840
[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         if (thing.getStatus() == INITIALIZING) {
73             logger.debug("Initializing the UniFi Client Handler with config = {}", config);
74             if (!config.isValid()) {
75                 updateStatus(OFFLINE, CONFIGURATION_ERROR,
76                         "You must define a MAC address, IP address, hostname or alias for this thing.");
77                 return;
78             }
79             this.config = config;
80             updateStatus(ONLINE);
81         }
82     }
83
84     private static boolean belongsToSite(UniFiClient client, String siteName) {
85         boolean result = true; // mgb: assume true = proof by contradiction
86         if (!siteName.isEmpty()) {
87             UniFiSite site = client.getSite();
88             // mgb: if the 'site' can't be found or the name doesn't match...
89             if (site == null || !site.matchesName(siteName)) {
90                 // mgb: ... then the client doesn't belong to this thing's configured 'site' and we 'filter' it
91                 result = false;
92             }
93         }
94         return result;
95     }
96
97     @Override
98     protected synchronized @Nullable UniFiClient getEntity(UniFiController controller) {
99         UniFiClient client = controller.getClient(config.getClientID());
100         // mgb: short circuit
101         if (client == null || !belongsToSite(client, config.getSite())) {
102             return null;
103         }
104         return client;
105     }
106
107     private State getDefaultState(String channelID, boolean clientHome) {
108         State state = UnDefType.NULL;
109         switch (channelID) {
110             case CHANNEL_ONLINE:
111             case CHANNEL_SITE:
112             case CHANNEL_AP:
113             case CHANNEL_ESSID:
114             case CHANNEL_RSSI:
115             case CHANNEL_MAC_ADDRESS:
116             case CHANNEL_IP_ADDRESS:
117             case CHANNEL_BLOCKED:
118                 state = (clientHome ? UnDefType.NULL : UnDefType.UNDEF); // skip the update if the client is home
119                 break;
120             case CHANNEL_UPTIME:
121                 // mgb: uptime should default to 0 seconds
122                 state = (clientHome ? UnDefType.NULL : new DecimalType(0)); // skip the update if the client is home
123                 break;
124             case CHANNEL_LAST_SEEN:
125                 // mgb: lastSeen should keep the last state no matter what
126                 state = UnDefType.NULL;
127                 break;
128             case CHANNEL_RECONNECT:
129                 state = OnOffType.OFF;
130                 break;
131         }
132         return state;
133     }
134
135     private synchronized boolean isClientHome(UniFiClient client) {
136         boolean online = false;
137         if (client != null) {
138             Calendar lastSeen = client.getLastSeen();
139             if (lastSeen == null) {
140                 logger.warn("Could not determine if client is online: cid = {}, lastSeen = null", config.getClientID());
141             } else {
142                 Calendar considerHome = (Calendar) lastSeen.clone();
143                 considerHome.add(Calendar.SECOND, config.getConsiderHome());
144                 Calendar now = Calendar.getInstance();
145                 online = (now.compareTo(considerHome) < 0);
146             }
147         }
148         return online;
149     }
150
151     @Override
152     protected void refreshChannel(UniFiClient client, ChannelUID channelUID) {
153         boolean clientHome = isClientHome(client);
154         UniFiDevice device = client.getDevice();
155         UniFiSite site = (device == null ? null : device.getSite());
156         String channelID = channelUID.getIdWithoutGroup();
157         State state = getDefaultState(channelID, clientHome);
158         switch (channelID) {
159             // mgb: common wired + wireless client channels
160
161             // :online
162             case CHANNEL_ONLINE:
163                 state = OnOffType.from(clientHome);
164                 break;
165
166             // :site
167             case CHANNEL_SITE:
168                 if (clientHome && site != null && site.getDescription() != null && !site.getDescription().isBlank()) {
169                     state = StringType.valueOf(site.getDescription());
170                 }
171                 break;
172
173             // :macAddress
174             case CHANNEL_MAC_ADDRESS:
175                 if (clientHome && client.getMac() != null && !client.getMac().isBlank()) {
176                     state = StringType.valueOf(client.getMac());
177                 }
178                 break;
179
180             // :ipAddress
181             case CHANNEL_IP_ADDRESS:
182                 if (clientHome && client.getIp() != null && !client.getIp().isBlank()) {
183                     state = StringType.valueOf(client.getIp());
184                 }
185                 break;
186
187             // :uptime
188             case CHANNEL_UPTIME:
189                 if (clientHome && client.getUptime() != null) {
190                     state = new DecimalType(client.getUptime());
191                 }
192                 break;
193
194             // :lastSeen
195             case CHANNEL_LAST_SEEN:
196                 // mgb: we don't check clientOnline as lastSeen is also included in the Insights data
197                 if (client.getLastSeen() != null) {
198                     state = new DateTimeType(
199                             ZonedDateTime.ofInstant(client.getLastSeen().toInstant(), ZoneId.systemDefault()));
200                 }
201                 break;
202
203             // :blocked
204             case CHANNEL_BLOCKED:
205                 state = OnOffType.from(client.isBlocked());
206                 break;
207
208             default:
209                 // mgb: additional wired client channels
210                 if (client.isWired() && (client instanceof UniFiWiredClient)) {
211                     state = getWiredChannelState((UniFiWiredClient) client, clientHome, channelID);
212                 }
213
214                 // mgb: additional wireless client channels
215                 else if (client.isWireless() && (client instanceof UniFiWirelessClient)) {
216                     state = getWirelessChannelState((UniFiWirelessClient) client, clientHome, channelID);
217                 }
218                 break;
219         }
220         // mgb: only non null states get updates
221         if (state != UnDefType.NULL) {
222             updateState(channelID, state);
223         }
224     }
225
226     private State getWiredChannelState(UniFiWiredClient client, boolean clientHome, String channelID) {
227         State state = UnDefType.NULL;
228         return state;
229     }
230
231     private State getWirelessChannelState(UniFiWirelessClient client, boolean clientHome, String channelID) {
232         State state = UnDefType.NULL;
233         switch (channelID) {
234             // :ap
235             case CHANNEL_AP:
236                 UniFiDevice device = client.getDevice();
237                 if (clientHome && device != null && device.getName() != null && !device.getName().isBlank()) {
238                     state = StringType.valueOf(device.getName());
239                 }
240                 break;
241
242             // :essid
243             case CHANNEL_ESSID:
244                 if (clientHome && client.getEssid() != null && !client.getEssid().isBlank()) {
245                     state = StringType.valueOf(client.getEssid());
246                 }
247                 break;
248
249             // :rssi
250             case CHANNEL_RSSI:
251                 if (clientHome && client.getRssi() != null) {
252                     state = new DecimalType(client.getRssi());
253                 }
254                 break;
255
256             // :reconnect
257             case CHANNEL_RECONNECT:
258                 // nop - read-only channel
259                 break;
260         }
261         return state;
262     }
263
264     @Override
265     protected void handleCommand(UniFiClient client, ChannelUID channelUID, Command command) throws UniFiException {
266         String channelID = channelUID.getIdWithoutGroup();
267         switch (channelID) {
268             case CHANNEL_BLOCKED:
269                 handleBlockedCommand(client, channelUID, command);
270                 break;
271             case CHANNEL_RECONNECT:
272                 handleReconnectCommand(client, channelUID, command);
273                 break;
274             default:
275                 logger.warn("Ignoring unsupported command = {} for channel = {}", command, channelUID);
276         }
277     }
278
279     private void handleBlockedCommand(UniFiClient client, ChannelUID channelUID, Command command)
280             throws UniFiException {
281         if (command instanceof OnOffType) {
282             client.block(command == OnOffType.ON);
283         } else {
284             logger.warn("Ignoring unsupported command = {} for channel = {} - valid commands types are: OnOffType",
285                     command, channelUID);
286         }
287     }
288
289     private void handleReconnectCommand(UniFiClient client, ChannelUID channelUID, Command command)
290             throws UniFiException {
291         if (command instanceof OnOffType) {
292             if (command == OnOffType.ON) {
293                 client.reconnect();
294                 updateState(channelUID, OnOffType.OFF);
295             }
296         } else {
297             logger.warn("Ignoring unsupported command = {} for channel = {} - valid commands types are: OnOffType",
298                     command, channelUID);
299         }
300     }
301 }