2 * Copyright (c) 2010-2021 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.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;
48 * The {@link UniFiClientThingHandler} is responsible for handling commands and status
49 * updates for UniFi Wireless Devices.
51 * @author Matthew Bowman - Initial contribution
52 * @author Patrik Wimnell - Blocking / Unblocking client support
55 public class UniFiClientThingHandler extends UniFiBaseThingHandler<UniFiClient, UniFiClientThingConfig> {
57 public static boolean supportsThingType(ThingTypeUID thingTypeUID) {
58 return UniFiBindingConstants.THING_TYPE_WIRELESS_CLIENT.equals(thingTypeUID);
61 private final Logger logger = LoggerFactory.getLogger(UniFiClientThingHandler.class);
63 private UniFiClientThingConfig config = new UniFiClientThingConfig();
65 public UniFiClientThingHandler(Thing thing) {
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.");
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
96 protected synchronized @Nullable UniFiClient getEntity(UniFiController controller) {
97 UniFiClient client = controller.getClient(config.getClientID());
99 if (client == null || !belongsToSite(client, config.getSite())) {
105 private State getDefaultState(String channelID, boolean clientHome) {
106 State state = UnDefType.NULL;
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
119 // mgb: uptime should default to 0 seconds
120 state = (clientHome ? UnDefType.NULL : new DecimalType(0)); // skip the update if the client is home
122 case CHANNEL_LAST_SEEN:
123 // mgb: lastSeen should keep the last state no matter what
124 state = UnDefType.NULL;
126 case CHANNEL_RECONNECT:
127 state = OnOffType.OFF;
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());
140 Calendar considerHome = (Calendar) lastSeen.clone();
141 considerHome.add(Calendar.SECOND, config.getConsiderHome());
142 Calendar now = Calendar.getInstance();
143 online = (now.compareTo(considerHome) < 0);
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);
157 // mgb: common wired + wireless client channels
161 state = OnOffType.from(clientHome);
166 if (clientHome && site != null && site.getDescription() != null && !site.getDescription().isBlank()) {
167 state = StringType.valueOf(site.getDescription());
172 case CHANNEL_MAC_ADDRESS:
173 if (clientHome && client.getMac() != null && !client.getMac().isBlank()) {
174 state = StringType.valueOf(client.getMac());
179 case CHANNEL_IP_ADDRESS:
180 if (clientHome && client.getIp() != null && !client.getIp().isBlank()) {
181 state = StringType.valueOf(client.getIp());
187 if (clientHome && client.getUptime() != null) {
188 state = new DecimalType(client.getUptime());
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()));
202 case CHANNEL_BLOCKED:
203 state = OnOffType.from(client.isBlocked());
207 // mgb: additional wired client channels
208 if (client.isWired() && (client instanceof UniFiWiredClient)) {
209 state = getWiredChannelState((UniFiWiredClient) client, clientHome, channelID);
212 // mgb: additional wireless client channels
213 else if (client.isWireless() && (client instanceof UniFiWirelessClient)) {
214 state = getWirelessChannelState((UniFiWirelessClient) client, clientHome, channelID);
218 // mgb: only non null states get updates
219 if (state != UnDefType.NULL) {
220 updateState(channelID, state);
224 private State getWiredChannelState(UniFiWiredClient client, boolean clientHome, String channelID) {
225 State state = UnDefType.NULL;
229 private State getWirelessChannelState(UniFiWirelessClient client, boolean clientHome, String channelID) {
230 State state = UnDefType.NULL;
234 UniFiDevice device = client.getDevice();
235 if (clientHome && device != null && device.getName() != null && !device.getName().isBlank()) {
236 state = StringType.valueOf(device.getName());
242 if (clientHome && client.getEssid() != null && !client.getEssid().isBlank()) {
243 state = StringType.valueOf(client.getEssid());
249 if (clientHome && client.getRssi() != null) {
250 state = new DecimalType(client.getRssi());
255 case CHANNEL_RECONNECT:
256 // nop - read-only channel
263 protected void handleCommand(UniFiClient client, ChannelUID channelUID, Command command) throws UniFiException {
264 String channelID = channelUID.getIdWithoutGroup();
266 case CHANNEL_BLOCKED:
267 handleBlockedCommand(client, channelUID, command);
269 case CHANNEL_RECONNECT:
270 handleReconnectCommand(client, channelUID, command);
273 logger.warn("Ignoring unsupported command = {} for channel = {}", command, channelUID);
277 private void handleBlockedCommand(UniFiClient client, ChannelUID channelUID, Command command)
278 throws UniFiException {
279 if (command instanceof OnOffType) {
280 client.block(command == OnOffType.ON);
282 logger.warn("Ignoring unsupported command = {} for channel = {} - valid commands types are: OnOffType",
283 command, channelUID);
287 private void handleReconnectCommand(UniFiClient client, ChannelUID channelUID, Command command)
288 throws UniFiException {
289 if (command instanceof OnOffType) {
290 if (command == OnOffType.ON) {
292 updateState(channelUID, OnOffType.OFF);
295 logger.warn("Ignoring unsupported command = {} for channel = {} - valid commands types are: OnOffType",
296 command, channelUID);