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 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.");
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
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())) {
107 private State getDefaultState(String channelID, boolean clientHome) {
108 State state = UnDefType.NULL;
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
121 // mgb: uptime should default to 0 seconds
122 state = (clientHome ? UnDefType.NULL : new DecimalType(0)); // skip the update if the client is home
124 case CHANNEL_LAST_SEEN:
125 // mgb: lastSeen should keep the last state no matter what
126 state = UnDefType.NULL;
128 case CHANNEL_RECONNECT:
129 state = OnOffType.OFF;
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());
142 Calendar considerHome = (Calendar) lastSeen.clone();
143 considerHome.add(Calendar.SECOND, config.getConsiderHome());
144 Calendar now = Calendar.getInstance();
145 online = (now.compareTo(considerHome) < 0);
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);
159 // mgb: common wired + wireless client channels
163 state = OnOffType.from(clientHome);
168 if (clientHome && site != null && site.getDescription() != null && !site.getDescription().isBlank()) {
169 state = StringType.valueOf(site.getDescription());
174 case CHANNEL_MAC_ADDRESS:
175 if (clientHome && client.getMac() != null && !client.getMac().isBlank()) {
176 state = StringType.valueOf(client.getMac());
181 case CHANNEL_IP_ADDRESS:
182 if (clientHome && client.getIp() != null && !client.getIp().isBlank()) {
183 state = StringType.valueOf(client.getIp());
189 if (clientHome && client.getUptime() != null) {
190 state = new DecimalType(client.getUptime());
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()));
204 case CHANNEL_BLOCKED:
205 state = OnOffType.from(client.isBlocked());
209 // mgb: additional wired client channels
210 if (client.isWired() && (client instanceof UniFiWiredClient)) {
211 state = getWiredChannelState((UniFiWiredClient) client, clientHome, channelID);
214 // mgb: additional wireless client channels
215 else if (client.isWireless() && (client instanceof UniFiWirelessClient)) {
216 state = getWirelessChannelState((UniFiWirelessClient) client, clientHome, channelID);
220 // mgb: only non null states get updates
221 if (state != UnDefType.NULL) {
222 updateState(channelID, state);
226 private State getWiredChannelState(UniFiWiredClient client, boolean clientHome, String channelID) {
227 State state = UnDefType.NULL;
231 private State getWirelessChannelState(UniFiWirelessClient client, boolean clientHome, String channelID) {
232 State state = UnDefType.NULL;
236 UniFiDevice device = client.getDevice();
237 if (clientHome && device != null && device.getName() != null && !device.getName().isBlank()) {
238 state = StringType.valueOf(device.getName());
244 if (clientHome && client.getEssid() != null && !client.getEssid().isBlank()) {
245 state = StringType.valueOf(client.getEssid());
251 if (clientHome && client.getRssi() != null) {
252 state = new DecimalType(client.getRssi());
257 case CHANNEL_RECONNECT:
258 // nop - read-only channel
265 protected void handleCommand(UniFiClient client, ChannelUID channelUID, Command command) throws UniFiException {
266 String channelID = channelUID.getIdWithoutGroup();
268 case CHANNEL_BLOCKED:
269 handleBlockedCommand(client, channelUID, command);
271 case CHANNEL_RECONNECT:
272 handleReconnectCommand(client, channelUID, command);
275 logger.warn("Ignoring unsupported command = {} for channel = {}", command, channelUID);
279 private void handleBlockedCommand(UniFiClient client, ChannelUID channelUID, Command command)
280 throws UniFiException {
281 if (command instanceof OnOffType) {
282 client.block(command == OnOffType.ON);
284 logger.warn("Ignoring unsupported command = {} for channel = {} - valid commands types are: OnOffType",
285 command, channelUID);
289 private void handleReconnectCommand(UniFiClient client, ChannelUID channelUID, Command command)
290 throws UniFiException {
291 if (command instanceof OnOffType) {
292 if (command == OnOffType.ON) {
294 updateState(channelUID, OnOffType.OFF);
297 logger.warn("Ignoring unsupported command = {} for channel = {} - valid commands types are: OnOffType",
298 command, channelUID);