2 * Copyright (c) 2010-2020 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.unifi.internal.handler;
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;
19 import java.time.ZoneId;
20 import java.time.ZonedDateTime;
21 import java.util.Calendar;
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;
49 * The {@link UniFiClientThingHandler} is responsible for handling commands and status
50 * updates for UniFi Wireless Devices.
52 * @author Matthew Bowman - Initial contribution
53 * @author Patrik Wimnell - Blocking / Unblocking client support
56 public class UniFiClientThingHandler extends UniFiBaseThingHandler<UniFiClient, UniFiClientThingConfig> {
58 public static boolean supportsThingType(ThingTypeUID thingTypeUID) {
59 return UniFiBindingConstants.THING_TYPE_WIRELESS_CLIENT.equals(thingTypeUID);
62 private final Logger logger = LoggerFactory.getLogger(UniFiClientThingHandler.class);
64 private UniFiClientThingConfig config = new UniFiClientThingConfig();
66 public UniFiClientThingHandler(Thing thing) {
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.");
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
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())) {
108 private State getDefaultState(String channelID, boolean clientHome) {
109 State state = UnDefType.NULL;
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
122 // mgb: uptime should default to 0 seconds
123 state = (clientHome ? UnDefType.NULL : new DecimalType(0)); // skip the update if the client is home
125 case CHANNEL_LAST_SEEN:
126 // mgb: lastSeen should keep the last state no matter what
127 state = UnDefType.NULL;
129 case CHANNEL_RECONNECT:
130 state = OnOffType.OFF;
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());
143 Calendar considerHome = (Calendar) lastSeen.clone();
144 considerHome.add(Calendar.SECOND, config.getConsiderHome());
145 Calendar now = Calendar.getInstance();
146 online = (now.compareTo(considerHome) < 0);
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);
160 // mgb: common wired + wireless client channels
164 state = OnOffType.from(clientHome);
169 if (clientHome && site != null && StringUtils.isNotBlank(site.getDescription())) {
170 state = StringType.valueOf(site.getDescription());
175 case CHANNEL_MAC_ADDRESS:
176 if (clientHome && StringUtils.isNotBlank(client.getMac())) {
177 state = StringType.valueOf(client.getMac());
182 case CHANNEL_IP_ADDRESS:
183 if (clientHome && StringUtils.isNotBlank(client.getIp())) {
184 state = StringType.valueOf(client.getIp());
190 if (clientHome && client.getUptime() != null) {
191 state = new DecimalType(client.getUptime());
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()));
205 case CHANNEL_BLOCKED:
206 state = OnOffType.from(client.isBlocked());
210 // mgb: additional wired client channels
211 if (client.isWired() && (client instanceof UniFiWiredClient)) {
212 state = getWiredChannelState((UniFiWiredClient) client, clientHome, channelID);
215 // mgb: additional wireless client channels
216 else if (client.isWireless() && (client instanceof UniFiWirelessClient)) {
217 state = getWirelessChannelState((UniFiWirelessClient) client, clientHome, channelID);
221 // mgb: only non null states get updates
222 if (state != UnDefType.NULL) {
223 updateState(channelID, state);
227 private State getWiredChannelState(UniFiWiredClient client, boolean clientHome, String channelID) {
228 State state = UnDefType.NULL;
232 private State getWirelessChannelState(UniFiWirelessClient client, boolean clientHome, String channelID) {
233 State state = UnDefType.NULL;
237 UniFiDevice device = client.getDevice();
238 if (clientHome && device != null && StringUtils.isNotBlank(device.getName())) {
239 state = StringType.valueOf(device.getName());
245 if (clientHome && StringUtils.isNotBlank(client.getEssid())) {
246 state = StringType.valueOf(client.getEssid());
252 if (clientHome && client.getRssi() != null) {
253 state = new DecimalType(client.getRssi());
258 case CHANNEL_RECONNECT:
259 // nop - read-only channel
266 protected void handleCommand(UniFiClient client, ChannelUID channelUID, Command command) throws UniFiException {
267 String channelID = channelUID.getIdWithoutGroup();
269 case CHANNEL_BLOCKED:
270 handleBlockedCommand(client, channelUID, command);
272 case CHANNEL_RECONNECT:
273 handleReconnectCommand(client, channelUID, command);
276 logger.warn("Ignoring unsupported command = {} for channel = {}", command, channelUID);
280 private void handleBlockedCommand(UniFiClient client, ChannelUID channelUID, Command command)
281 throws UniFiException {
282 if (command instanceof OnOffType) {
283 client.block(command == OnOffType.ON);
285 logger.warn("Ignoring unsupported command = {} for channel = {} - valid commands types are: OnOffType",
286 command, channelUID);
290 private void handleReconnectCommand(UniFiClient client, ChannelUID channelUID, Command command)
291 throws UniFiException {
292 if (command instanceof OnOffType) {
293 if (command == OnOffType.ON) {
295 updateState(channelUID, OnOffType.OFF);
298 logger.warn("Ignoring unsupported command = {} for channel = {} - valid commands types are: OnOffType",
299 command, channelUID);