diff --git a/bundles/org.openhab.binding.unifi/README.md b/bundles/org.openhab.binding.unifi/README.md index 2edd316713c..3135eeed7ab 100644 --- a/bundles/org.openhab.binding.unifi/README.md +++ b/bundles/org.openhab.binding.unifi/README.md @@ -5,23 +5,26 @@ This binding integrates with [Ubiquiti UniFi Networks](https://www.ubnt.com/prod ## Supported Things -* `controller` - An instance of the UniFi controller software +* `controller` - An instance of the UniFi controller software +* `site` - A site thing with connection statistics +* `wlan` - A wireless network thing. Control Wi-Fi network and easy access to access. * `wirelessClient` - Any wireless client connected to a UniFi wireless network - +* `wiredClient` - A wired client connected to the UniFi network +* `poePort` - A PoE (Power over Ethernet) port on a UniFi switch ## Discovery -Discovery is currently not supported. - +The binding supports discovery of things connected to a UniFi controller (Bridge). +To discover things start the discovery process manually. ## Binding Configuration - + The binding has no configuration options, all configuration is done at the Bridge and Thing levels. - ## Bridge Configuration -You need at least one UniFi Controller (Bridge) for this binding to work. It requires a network accessible instance of the [Ubiquiti Networks Controller Software](https://www.ubnt.com/download/unifi). +You need at least one UniFi Controller (Bridge) for this binding to work. +It requires a network accessible instance of the [Ubiquiti Networks Controller Software](https://www.ubnt.com/download/unifi). The following table describes the Bridge configuration parameters: @@ -36,9 +39,28 @@ The following table describes the Bridge configuration parameters: ## Thing Configuration -You must define a UniFi Controller (Bridge) before defining UniFi Clients (Things) for this binding to work. +You must define a UniFi Controller (Bridge) before defining UniFi Things for this binding to work. + +### `site` + +The following table describes the `site` configuration parameters: + +| Parameter | Description | Config | Default | +| ------------ | -------------------------------------------------------------|--------- | ------- | +| sid | The name, description or id of the site | Required | - | + +### `wlan` + +The following table describes the `wlan` configuration parameters: + +| Parameter | Description | Config | Default | +| ------------ | -------------------------------------------------------------|--------- | ------- | +| wid | The name or id of the WLAN | Required | - | + +### `wirelessClient` & `wiredClient` + +The following table describes the `wirelessClient` & `wiredClient` configuration parameters: -The following table describes the Thing configuration parameters: | Parameter | Description | Config | Default | | ------------ | -------------------------------------------------------------|--------- | ------- | @@ -56,8 +78,10 @@ The `cid` parameter is a universal "client identifier". It accepts the following 1. IP address 1. Hostname (as show by the controller) 1. Alias (as defined by you in the controller UI) [lowest priority] - -The priority essentially means the binding attempts to lookup by MAC address, then by IP address, then by hostname and finally by alias. Once it finds a matching client, it short circuits and stops searching. Most of the time, you will simply use the MAC address. + +The priority essentially means the binding attempts to lookup by MAC address, then by IP address, then by hostname and finally by alias. +Once it finds a matching client, it short circuits and stops searching. +Most of the time, you will simply use the MAC address. ##### `site` @@ -69,37 +93,124 @@ Additionally, you may use friendly site names as they appear in the controller U ##### `considerHome` -The `considerHome` parameter allows you to control how quickly the binding marks a client as away. For example, using the default of `180` (seconds), the binding will report a client away as soon as `lastSeen` + `180` (seconds) < `now` +The `considerHome` parameter allows you to control how quickly the binding marks a client as away. +For example, using the default of `180` (seconds), the binding will report a client away as soon as `lastSeen` + `180` (seconds) < `now`. + +### `poePort` + +The following table describes the `poePort` configuration parameters: + + +| Parameter | Description | Config | +|------------|-----------------------------------------------------------|----------| +| portNumber | The port number as reported by the switch (starts with 1) | Required | +| macAddress | The MAC address of the switch device the port is part of | Required | ## Channels -The Wireless Client information that is retrieved is available as these channels: +### `site` + +The `site` information that is retrieved is available as these channels: + +| Channel ID | Item Type | Description | Permissions | +|-----------------|-----------|--------------------------------------|-------------| +| totalClients | Number | Total number of clients connected | Read | +| wirelessClients | Number | Number of wireless clients connected | Read | +| wiredClients | Number | Number of wired clients connected | Read | +| guestClients | Number | Number of guest clients connected | Read | + +### `wlan` + +The `wlan` information that is retrieved is available as these channels: + +| Channel ID | Item Type | Description | Permissions | +|-----------------|-----------|---------------------------------------------------------------------------------|-------------| +| enable | Switch | Enable status of the WLAN | Read, Write | +| wirelessClients | Number | Number of wireless clients connected | Read | +| guestClients | Number | Number of guest clients connected | Read | +| essid | String | Wireless Network (ESSID) | Read | +| site | String | UniFi Site the client is associated with | Read | +| security | String | Security protocol of the Wi-Fi network | Read | +| wlanBand | String | Wireless LAN band of the Wi-Fi network | Read | +| wpaEnc | String | WPA Encoding of the Wi-Fi network | Read | +| wpaMode | String | WPA Mode of the Wi-Fi network | Read | +| passphrase | String | Passphrase of the Wi-Fi network | Read | +| qrcodeEncoding | String | MECARD like encoding to generate a QR Code for easy access to the Wi-Fi network | Read | + +::: warning Attention +If you link an item to the `passphrase` or `qrcodeEncoding` channel your Wi-Fi password will be exposed in openHAB. +The password will also be visible in openHAB event log. +::: + +The `qrcodeEncoding` channel can be used to easily create a QR Code to access, for example, a guest network. +It contains a MECARD like representation of the access. +This is the notation used in QR Codes that can be scanned by mobile phones. + +### `wirelessClient` + +The `wirelessClient` information that is retrieved is available as these channels: + +| Channel ID | Item Type | Description | Permissions | +|------------|----------------------|----------------------------------------------------------------------|-------------| +| online | Switch | Online status of the client | Read | +| site | String | Site name (from the controller web UI) the client is associated with | Read | +| macAddress | String | MAC address of the client | Read | +| ipAddress | String | IP address of the client | Read | +| guest | Switch | On if this is a guest client | Read | +| ap | String | Access point (AP) the client is connected to | Read | +| essid | String | Network name (ESSID) the client is connected to | Read | +| rssi | Number | Received signal strength indicator (RSSI) of the client | Read | +| uptime | Number | Uptime of the client (in seconds) | Read | +| lastSeen | DateTime | Date and Time the client was last seen | Read | +| experience | Number:Dimensionless | Overall health indication of the client (in percentage) | Read | +| blocked | Switch | Blocked status of the client | Read, Write | +| cmd | String | Command channel: `reconnect` to force the client to reconnect | Write | +| reconnect | Switch | Force the client to reconnect | Write | -| Channel ID | Item Type | Description | Permissions | -|------------|-----------|--------------------------------------------------------------------- | ----------- | -| online | Switch | Online status of the client | Read | -| site | String | Site name (from the controller web UI) the client is associated with | Read | -| macAddress | String | MAC address of the client | Read | -| ipAddress | String | IP address of the client | Read | -| ap | String | Access point (AP) the client is connected to | Read | -| essid | String | Network name (ESSID) the client is connected to | Read | -| rssi | Number | Received signal strength indicator (RSSI) of the client | Read | -| uptime | Number | Uptime of the wireless client (in seconds) | Read | -| lastSeen | DateTime | Date and Time the wireless client was last seen | Read | -| blocked | Switch | Blocked status of the client | Read, Write | -| reconnect | Switch | Force the client to be reconnect | Write | _Note: All channels with the Write permission require administrator credentials as defined in the controller._ +### `wiredClient` + +The `wiredClient` information that is retrieved is available as these channels: + +| Channel ID | Item Type | Description | Permissions | +|------------|----------------------|----------------------------------------------------------------------|-------------| +| online | Switch | Online status of the client | Read | +| site | String | Site name (from the controller web UI) the client is associated with | Read | +| macAddress | String | MAC address of the client | Read | +| ipAddress | String | IP address of the client | Read | +| uptime | Number | Uptime of the client (in seconds) | Read | +| lastSeen | DateTime | Date and Time the client was last seen | Read | +| experience | Number:Dimensionless | Overall health indication of the client (in percentage) | Read | +| blocked | Switch | Blocked status of the client | Read, Write | + ##### `blocked` The `blocked` channel allows you to block / unblock a client via the controller. ##### `reconnect` -The `reconnect` channel allows you to force a client to reconnect. Sending `ON` to this channel will trigger a reconnect via the controller. +The `reconnect` channel allows you to force a client to reconnect. +Sending `ON` to this channel will trigger a reconnect via the controller. +### `poePort` + +The `poePort` information that is retrieved is available as these channels: + +| Channel ID | Item Type | Description | Permissions | +|------------|--------------------------|----------------------------------------------------|-------------| +| online | Switch | Online status of the port | Read | +| mode | Selection | Select the PoE mode: off, auto, 24v or passthrough | Read, Write | +| enable | Switch | Enable Power over Ethernet | Read, Write | +| cmd | String | Command channel: `power-cycle`: Power Cycle port | Write | +| power | Number:Power | Power consumption of the port in Watt | Read | +| voltage | Number:ElectricPotential | Voltage of the port in Volt | Read | +| current | Number:ElectricCurrent | Current used by the port in mA | Read | + +The `enable` switch channel has a configuration parameter `mode` which is the value used to switch PoE on when the channel is switched to ON. +The default mode value is `auto`. ## Full Example @@ -126,7 +237,7 @@ String MatthewsPhoneAP "Matthew's iPhone: AP [%s]" String MatthewsPhoneESSID "Matthew's iPhone: ESSID [%s]" { channel="unifi:wirelessClient:home:matthewsPhone:essid" } Number MatthewsPhoneRSSI "Matthew's iPhone: RSSI [%d]" { channel="unifi:wirelessClient:home:matthewsPhone:rssi" } Number MatthewsPhoneUptime "Matthew's iPhone: Uptime [%d]" { channel="unifi:wirelessClient:home:matthewsPhone:uptime" } -DateTime MatthewsPhoneLastSeen "Matthew's iPhone: Last Seen [%1$tH:%1$tM:%1$tS]" { channel="unifi:wirelessClient:home:matthewsPhone:lastSeen" } +DateTime MatthewsPhoneLastSeen "Matthew's iPhone: Last Seen [%1$tH:%1$tM:%1$tS]" { channel="unifi:wirelessClient:home:matthewsPhone:lastSeen" } Switch MatthewsPhoneBlocked "Matthew's iPhone: Blocked" { channel="unifi:wirelessClient:home:matthewsPhone:blocked" } Switch MatthewsPhoneReconnect "Matthew's iPhone: Reconnect" { channel="unifi:wirelessClient:home:matthewsPhone:reconnect" } ``` diff --git a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/UniFiBindingConstants.java b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/UniFiBindingConstants.java index ed7ac82a856..5f994d3502e 100644 --- a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/UniFiBindingConstants.java +++ b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/UniFiBindingConstants.java @@ -12,6 +12,9 @@ */ package org.openhab.binding.unifi.internal; +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.core.thing.ThingTypeUID; /** @@ -20,15 +23,38 @@ import org.openhab.core.thing.ThingTypeUID; * * @author Matthew Bowman - Initial contribution * @author Patrik Wimnell - Blocking / Unblocking client support + * @author Hilbrand Bouwkamp - Added poePort */ -public class UniFiBindingConstants { +@NonNullByDefault +public final class UniFiBindingConstants { public static final String BINDING_ID = "unifi"; // List of all Thing Types public static final ThingTypeUID THING_TYPE_CONTROLLER = new ThingTypeUID(BINDING_ID, "controller"); + public static final ThingTypeUID THING_TYPE_SITE = new ThingTypeUID(BINDING_ID, "site"); + public static final ThingTypeUID THING_TYPE_WLAN = new ThingTypeUID(BINDING_ID, "wlan"); public static final ThingTypeUID THING_TYPE_WIRED_CLIENT = new ThingTypeUID(BINDING_ID, "wiredClient"); public static final ThingTypeUID THING_TYPE_WIRELESS_CLIENT = new ThingTypeUID(BINDING_ID, "wirelessClient"); + public static final ThingTypeUID THING_TYPE_POE_PORT = new ThingTypeUID(BINDING_ID, "poePort"); + public static final Set ALL_THING_TYPE_SUPPORTED = Set.of(THING_TYPE_CONTROLLER, THING_TYPE_SITE, + THING_TYPE_WLAN, THING_TYPE_WIRED_CLIENT, THING_TYPE_WIRELESS_CLIENT, THING_TYPE_POE_PORT); + public static final Set THING_TYPE_SUPPORTED = Set.of(THING_TYPE_SITE, THING_TYPE_WLAN, + THING_TYPE_WIRED_CLIENT, THING_TYPE_WIRELESS_CLIENT, THING_TYPE_POE_PORT); + + // List of site channels + public static final String CHANNEL_TOTAL_CLIENTS = "totalClients"; + public static final String CHANNEL_WIRELESS_CLIENTS = "wirelessClients"; + public static final String CHANNEL_WIRED_CLIENTS = "wiredClients"; + public static final String CHANNEL_GUEST_CLIENTS = "guestClients"; + + // List of wlan channels + public static final String CHANNEL_SECURITY = "security"; + public static final String CHANNEL_WLANBAND = "wlanBand"; + public static final String CHANNEL_WPAENC = "wpaEnc"; + public static final String CHANNEL_WPAMODE = "wpaMode"; + public static final String CHANNEL_PASSPHRASE = "passphrase"; + public static final String CHANNEL_QRCODE_ENCODING = "qrcodeEncoding"; // List of common wired + wireless client channels public static final String CHANNEL_ONLINE = "online"; @@ -37,17 +63,31 @@ public class UniFiBindingConstants { public static final String CHANNEL_IP_ADDRESS = "ipAddress"; public static final String CHANNEL_UPTIME = "uptime"; public static final String CHANNEL_LAST_SEEN = "lastSeen"; + public static final String CHANNEL_GUEST = "guest"; public static final String CHANNEL_BLOCKED = "blocked"; public static final String CHANNEL_RECONNECT = "reconnect"; - - // List of additional wired client channels - // ..coming soon.. + public static final String CHANNEL_CMD = "cmd"; + public static final String CHANNEL_CMD_RECONNECT = "reconnect"; + public static final String CHANNEL_EXPERIENCE = "experience"; // List of additional wireless client channels public static final String CHANNEL_AP = "ap"; public static final String CHANNEL_ESSID = "essid"; public static final String CHANNEL_RSSI = "rssi"; + // List of switch port channels + public static final String CHANNEL_ENABLE = "enable"; + public static final String CHANNEL_ENABLE_PARAMETER_MODE = "mode"; + public static final String CHANNEL_ENABLE_PARAMETER_MODE_OFF = "off"; + public static final String CHANNEL_ENABLE_PARAMETER_MODE_AUTO = "auto"; + public static final String CHANNEL_PORT_POE_MODE = "mode"; + public static final String CHANNEL_PORT_POE_CMD = "cmd"; + public static final String CHANNEL_PORT_POE_CMD_POWER_CYCLE = "powercycle"; + public static final String CHANNEL_PORT_POE_ENABLE = "enable"; + public static final String CHANNEL_PORT_POE_POWER = "power"; + public static final String CHANNEL_PORT_POE_VOLTAGE = "voltage"; + public static final String CHANNEL_PORT_POE_CURRENT = "current"; + // List of all Parameters public static final String PARAMETER_HOST = "host"; public static final String PARAMETER_PORT = "port"; @@ -56,4 +96,13 @@ public class UniFiBindingConstants { public static final String PARAMETER_UNIFIOS = "unifios"; public static final String PARAMETER_SITE = "site"; public static final String PARAMETER_CID = "cid"; + public static final String PARAMETER_SID = "sid"; + public static final String PARAMETER_WID = "wid"; + public static final String PARAMETER_PORT_NUMBER = "portNumber"; + public static final String PARAMETER_MAC_ADDRESS = "macAddress"; + public static final String PARAMETER_WIFI_NAME = "wifi"; + + private UniFiBindingConstants() { + // Constants class + } } diff --git a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/UniFiClientThingConfig.java b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/UniFiClientThingConfig.java index 7eecdcf823f..1ea54d467d6 100644 --- a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/UniFiClientThingConfig.java +++ b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/UniFiClientThingConfig.java @@ -12,6 +12,7 @@ */ package org.openhab.binding.unifi.internal; +import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.unifi.internal.handler.UniFiClientThingHandler; /** @@ -20,6 +21,8 @@ import org.openhab.binding.unifi.internal.handler.UniFiClientThingHandler; * * @author Matthew Bowman - Initial contribution */ +@NonNullByDefault +@SuppressWarnings("unused") public class UniFiClientThingConfig { private String cid = ""; @@ -32,24 +35,33 @@ public class UniFiClientThingConfig { return cid; } + private void setCid(final String cid) { + // method to avoid ide auto format mark the field as final + this.cid = cid; + } + public String getSite() { return site; } + private void setSite(final String site) { + // method to avoid ide auto format mark the field as final + this.site = site; + } + public int getConsiderHome() { return considerHome; } - public UniFiClientThingConfig tidy() { - cid = cid.trim().toLowerCase(); - site = site.trim().toLowerCase(); - return this; - } - public boolean isValid() { return !cid.isBlank(); } + private void setConsiderHome(final int considerHome) { + // method to avoid ide auto format mark the field as final + this.considerHome = considerHome; + } + @Override public String toString() { return String.format("UniFiClientConfig{cid: '%s', site: '%s', considerHome: %d}", cid, site, considerHome); diff --git a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/UniFiControllerThingConfig.java b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/UniFiControllerThingConfig.java index ad5a136a199..d1ba889bf86 100644 --- a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/UniFiControllerThingConfig.java +++ b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/UniFiControllerThingConfig.java @@ -12,6 +12,7 @@ */ package org.openhab.binding.unifi.internal; +import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.unifi.internal.handler.UniFiControllerThingHandler; /** @@ -20,6 +21,8 @@ import org.openhab.binding.unifi.internal.handler.UniFiControllerThingHandler; * * @author Matthew Bowman - Initial contribution */ +@NonNullByDefault +@SuppressWarnings("unused") public class UniFiControllerThingConfig { private String host = "unifi"; @@ -38,26 +41,56 @@ public class UniFiControllerThingConfig { return host; } + private void setHost(final String host) { + // method to avoid ide auto format mark the field as final + this.host = host; + } + public int getPort() { return port; } + private void setPort(final int port) { + // method to avoid ide auto format mark the field as final + this.port = port; + } + public String getUsername() { return username; } + private void setUsername(final String username) { + // method to avoid ide auto format mark the field as final + this.username = username; + } + public String getPassword() { return password; } + private void setPassword(final String password) { + // method to avoid ide auto format mark the field as final + this.password = password; + } + public int getRefresh() { return refresh; } + private void setRefresh(final int refresh) { + // method to avoid ide auto format mark the field as final + this.refresh = refresh; + } + public boolean isUniFiOS() { return unifios; } + private void setUnifiOS(final boolean unifios) { + // method to avoid ide auto format mark the field as final + this.unifios = unifios; + } + public boolean isValid() { return !host.isBlank() && !username.isBlank() && !password.isBlank(); } diff --git a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/UniFiPoePortThingConfig.java b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/UniFiPoePortThingConfig.java new file mode 100644 index 00000000000..d631daee887 --- /dev/null +++ b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/UniFiPoePortThingConfig.java @@ -0,0 +1,47 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.unifi.internal; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link UniFiPoeThingConfig} encapsulates all the configuration options for an instance of the + * {@link UniFiPoePortThingHandler}. + * + * @author Hilbrand Bouwkamp - Initial contribution + */ +@NonNullByDefault +@SuppressWarnings("unused") +public class UniFiPoePortThingConfig { + + private int portNumber; + + private String macAddress = ""; + + public int getPortNumber() { + return portNumber; + } + + public String getMacAddress() { + return macAddress; + } + + private void setMacAddress(final String macAddress) { + // method to avoid ide auto format mark the field as final + this.macAddress = macAddress; + } + + public boolean isValid() { + return !macAddress.isBlank(); + } +} diff --git a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/UniFiSiteThingConfig.java b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/UniFiSiteThingConfig.java new file mode 100644 index 00000000000..24c34282dab --- /dev/null +++ b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/UniFiSiteThingConfig.java @@ -0,0 +1,47 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.unifi.internal; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.unifi.internal.handler.UniFiSiteThingHandler; + +/** + * The {@link UniFiSiteThingConfig} encapsulates all the configuration options for an instance of the + * {@link UniFiSiteThingHandler}. + * + * @author Matthew Bowman - Initial contribution + */ +@NonNullByDefault +@SuppressWarnings("unused") +public class UniFiSiteThingConfig { + + private String sid = ""; + + public String getSiteID() { + return sid; + } + + private void setSiteID(final String sid) { + // method to avoid ide auto format mark the field as final + this.sid = sid; + } + + public boolean isValid() { + return !sid.isBlank(); + } + + @Override + public String toString() { + return String.format("UniFiSiteThingConfig{sid: '%s'}", sid); + } +} diff --git a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/UniFiThingHandlerFactory.java b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/UniFiThingHandlerFactory.java index b8ed6312442..020efec6dc4 100644 --- a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/UniFiThingHandlerFactory.java +++ b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/UniFiThingHandlerFactory.java @@ -12,12 +12,23 @@ */ package org.openhab.binding.unifi.internal; +import static org.openhab.binding.unifi.internal.UniFiBindingConstants.ALL_THING_TYPE_SUPPORTED; +import static org.openhab.binding.unifi.internal.UniFiBindingConstants.THING_TYPE_CONTROLLER; +import static org.openhab.binding.unifi.internal.UniFiBindingConstants.THING_TYPE_POE_PORT; +import static org.openhab.binding.unifi.internal.UniFiBindingConstants.THING_TYPE_SITE; +import static org.openhab.binding.unifi.internal.UniFiBindingConstants.THING_TYPE_WIRED_CLIENT; +import static org.openhab.binding.unifi.internal.UniFiBindingConstants.THING_TYPE_WIRELESS_CLIENT; +import static org.openhab.binding.unifi.internal.UniFiBindingConstants.THING_TYPE_WLAN; + import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.util.ssl.SslContextFactory; import org.openhab.binding.unifi.internal.handler.UniFiClientThingHandler; import org.openhab.binding.unifi.internal.handler.UniFiControllerThingHandler; +import org.openhab.binding.unifi.internal.handler.UniFiPoePortThingHandler; +import org.openhab.binding.unifi.internal.handler.UniFiSiteThingHandler; +import org.openhab.binding.unifi.internal.handler.UniFiWlanThingHandler; import org.openhab.core.io.net.http.HttpClientFactory; import org.openhab.core.io.net.http.HttpClientInitializationException; import org.openhab.core.thing.Bridge; @@ -26,6 +37,7 @@ import org.openhab.core.thing.ThingTypeUID; import org.openhab.core.thing.binding.BaseThingHandlerFactory; import org.openhab.core.thing.binding.ThingHandler; import org.openhab.core.thing.binding.ThingHandlerFactory; +import org.osgi.service.component.ComponentContext; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Reference; @@ -49,24 +61,40 @@ public class UniFiThingHandlerFactory extends BaseThingHandlerFactory { httpClient = new HttpClient(new SslContextFactory.Client(true)); try { httpClient.start(); - } catch (Exception e) { + } catch (final Exception e) { throw new HttpClientInitializationException("Could not start HttpClient", e); } } @Override - public boolean supportsThingType(ThingTypeUID thingTypeUID) { - return UniFiControllerThingHandler.supportsThingType(thingTypeUID) - || UniFiClientThingHandler.supportsThingType(thingTypeUID); + protected void deactivate(final ComponentContext componentContext) { + try { + httpClient.stop(); + } catch (final Exception e) { + // Eat http client stop exception. + } finally { + super.deactivate(componentContext); + } } @Override - protected @Nullable ThingHandler createHandler(Thing thing) { - ThingTypeUID thingTypeUID = thing.getThingTypeUID(); - if (UniFiControllerThingHandler.supportsThingType(thingTypeUID)) { + public boolean supportsThingType(final ThingTypeUID thingTypeUID) { + return ALL_THING_TYPE_SUPPORTED.contains(thingTypeUID); + } + + @Override + protected @Nullable ThingHandler createHandler(final Thing thing) { + final ThingTypeUID thingTypeUID = thing.getThingTypeUID(); + if (THING_TYPE_CONTROLLER.equals(thingTypeUID)) { return new UniFiControllerThingHandler((Bridge) thing, httpClient); - } else if (UniFiClientThingHandler.supportsThingType(thingTypeUID)) { + } else if (THING_TYPE_SITE.equals(thingTypeUID)) { + return new UniFiSiteThingHandler(thing); + } else if (THING_TYPE_WLAN.equals(thingTypeUID)) { + return new UniFiWlanThingHandler(thing); + } else if (THING_TYPE_WIRELESS_CLIENT.equals(thingTypeUID) || THING_TYPE_WIRED_CLIENT.equals(thingTypeUID)) { return new UniFiClientThingHandler(thing); + } else if (THING_TYPE_POE_PORT.equals(thingTypeUID)) { + return new UniFiPoePortThingHandler(thing); } return null; } diff --git a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/UniFiWlanThingConfig.java b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/UniFiWlanThingConfig.java new file mode 100644 index 00000000000..8725067a183 --- /dev/null +++ b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/UniFiWlanThingConfig.java @@ -0,0 +1,47 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.unifi.internal; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.unifi.internal.handler.UniFiWlanThingHandler; + +/** + * The {@link UniFiWlanThingConfig} encapsulates all the configuration options for an instance of the + * {@link UniFiWlanThingHandler}. + * + * @author Hilbrand Bouwkamp - Initial contribution + */ +@NonNullByDefault +@SuppressWarnings("unused") +public class UniFiWlanThingConfig { + + private String wid = ""; + + public String getWlanId() { + return wid; + } + + private void setWlanId(final String wid) { + // method to avoid auto format mark the field as final + this.wid = wid; + } + + public boolean isValid() { + return !wid.isBlank(); + } + + @Override + public String toString() { + return String.format("UniFiWlanThingConfig{wid: '%s'}", wid); + } +} diff --git a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/UniFiCommunicationException.java b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/UniFiCommunicationException.java index 992fbdf6c4a..693590d6863 100644 --- a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/UniFiCommunicationException.java +++ b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/UniFiCommunicationException.java @@ -12,16 +12,19 @@ */ package org.openhab.binding.unifi.internal.api; +import org.eclipse.jdt.annotation.NonNullByDefault; + /** * The {@link UniFiCommunicationException} signals there was a problem communicating with the controller. * * @author Matthew Bowman - Initial contribution */ +@NonNullByDefault public class UniFiCommunicationException extends UniFiException { private static final long serialVersionUID = -7261308872245069364L; - public UniFiCommunicationException(Throwable cause) { + public UniFiCommunicationException(final Throwable cause) { super(cause); } } diff --git a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/UniFiController.java b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/UniFiController.java new file mode 100644 index 00000000000..d0c6678a0b6 --- /dev/null +++ b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/UniFiController.java @@ -0,0 +1,290 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.unifi.internal.api; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.http.HttpMethod; +import org.openhab.binding.unifi.internal.api.cache.UniFiControllerCache; +import org.openhab.binding.unifi.internal.api.dto.UnfiPortOverride; +import org.openhab.binding.unifi.internal.api.dto.UniFiClient; +import org.openhab.binding.unifi.internal.api.dto.UniFiDevice; +import org.openhab.binding.unifi.internal.api.dto.UniFiPortTable; +import org.openhab.binding.unifi.internal.api.dto.UniFiSite; +import org.openhab.binding.unifi.internal.api.dto.UniFiUnknownClient; +import org.openhab.binding.unifi.internal.api.dto.UniFiWiredClient; +import org.openhab.binding.unifi.internal.api.dto.UniFiWirelessClient; +import org.openhab.binding.unifi.internal.api.dto.UniFiWlan; +import org.openhab.binding.unifi.internal.api.util.UniFiClientDeserializer; +import org.openhab.binding.unifi.internal.api.util.UniFiClientInstanceCreator; +import org.openhab.binding.unifi.internal.api.util.UniFiDeviceInstanceCreator; +import org.openhab.binding.unifi.internal.api.util.UniFiSiteInstanceCreator; +import org.openhab.binding.unifi.internal.api.util.UniFiWlanInstanceCreator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.gson.FieldNamingPolicy; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +/** + * The {@link UniFiController} is the main communication point with an external instance of the Ubiquiti Networks + * Controller Software. + * + * @author Matthew Bowman - Initial contribution + * @author Patrik Wimnell - Blocking / Unblocking client support + * @author Jacob Laursen - Fix online/blocked channels (broken by UniFi Controller 5.12.35) + * @author Hilbrand Bouwkamp - Added POEPort support, moved generic cache related code to cache object + */ +@NonNullByDefault +public class UniFiController { + + private static final int INSIGHT_WITHIN_HOURS = 7 * 24; // scurb: Changed to 7 days. + + private final Logger logger = LoggerFactory.getLogger(UniFiController.class); + + private final HttpClient httpClient; + private final UniFiControllerCache cache = new UniFiControllerCache(); + + private final String host; + private final int port; + private final String username; + private final String password; + private final boolean unifios; + private final Gson gson; + private final Gson poeGson; + + private String csrfToken; + + public UniFiController(final HttpClient httpClient, final String host, final int port, final String username, + final String password, final boolean unifios) { + this.httpClient = httpClient; + this.host = host; + this.port = port; + this.username = username; + this.password = password; + this.unifios = unifios; + this.csrfToken = ""; + final UniFiSiteInstanceCreator siteInstanceCreator = new UniFiSiteInstanceCreator(cache); + final UniFiWlanInstanceCreator wlanInstanceCreator = new UniFiWlanInstanceCreator(cache); + final UniFiDeviceInstanceCreator deviceInstanceCreator = new UniFiDeviceInstanceCreator(cache); + final UniFiClientInstanceCreator clientInstanceCreator = new UniFiClientInstanceCreator(cache); + this.gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) + .registerTypeAdapter(UniFiSite.class, siteInstanceCreator) + .registerTypeAdapter(UniFiWlan.class, wlanInstanceCreator) + .registerTypeAdapter(UniFiDevice.class, deviceInstanceCreator) + .registerTypeAdapter(UniFiClient.class, new UniFiClientDeserializer()) + .registerTypeAdapter(UniFiUnknownClient.class, clientInstanceCreator) + .registerTypeAdapter(UniFiWiredClient.class, clientInstanceCreator) + .registerTypeAdapter(UniFiWirelessClient.class, clientInstanceCreator).create(); + this.poeGson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) + .excludeFieldsWithoutExposeAnnotation().create(); + } + + // Public API + + public void start() throws UniFiException { + if (unifios) { + obtainCsrfToken(); + } + + login(); + } + + public void stop() throws UniFiException { + logout(); + } + + public void obtainCsrfToken() throws UniFiException { + csrfToken = ""; + + final UniFiControllerRequest req = newRequest(Void.class, HttpMethod.GET, gson); + req.setPath("/"); + executeRequest(req); + } + + public void login() throws UniFiException { + final UniFiControllerRequest req = newRequest(Void.class, HttpMethod.POST, gson); + req.setPath(unifios ? "/api/auth/login" : "/api/login"); + req.setBodyParameter("username", username); + req.setBodyParameter("password", password); + // scurb: Changed strict = false to make blocking feature work + req.setBodyParameter("strict", false); + req.setBodyParameter("remember", false); + executeRequest(req, true); + } + + public void logout() throws UniFiException { + csrfToken = ""; + final UniFiControllerRequest req = newRequest(Void.class, HttpMethod.GET, gson); + req.setPath(unifios ? "/api/auth/logout" : "/logout"); + executeRequest(req); + } + + public void refresh() throws UniFiException { + synchronized (this) { + cache.clear(); + final Collection sites = refreshSites(); + refreshWlans(sites); + refreshDevices(sites); + refreshClients(sites); + refreshInsights(sites); + } + } + + public UniFiControllerCache getCache() { + return cache; + } + + public @Nullable Map getSwitchPorts(@Nullable final String deviceId) { + return cache.getSwitchPorts(deviceId); + } + + public void block(final UniFiClient client, final boolean blocked) throws UniFiException { + final UniFiControllerRequest req = newRequest(Void.class, HttpMethod.POST, gson); + req.setAPIPath(String.format("/api/s/%s/cmd/stamgr", client.getSite().getName())); + req.setBodyParameter("cmd", blocked ? "block-sta" : "unblock-sta"); + req.setBodyParameter("mac", client.getMac()); + executeRequest(req); + refresh(); + } + + public void reconnect(final UniFiClient client) throws UniFiException { + final UniFiControllerRequest req = newRequest(Void.class, HttpMethod.POST, gson); + req.setAPIPath(String.format("/api/s/%s/cmd/stamgr", client.getSite().getName())); + req.setBodyParameter("cmd", "kick-sta"); + req.setBodyParameter("mac", client.getMac()); + executeRequest(req); + refresh(); + } + + public void poeMode(final UniFiDevice device, final Map data) throws UniFiException { + final UniFiControllerRequest req = newRequest(Void.class, HttpMethod.PUT, poeGson); + req.setAPIPath(String.format("/api/s/%s/rest/device/%s", device.getSite().getName(), device.getId())); + req.setBodyParameter("port_overrides", data.values()); + executeRequest(req); + refresh(); + } + + public void poePowerCycle(final UniFiDevice device, final Integer portIdx) throws UniFiException { + final UniFiControllerRequest req = newRequest(Void.class, HttpMethod.POST, gson); + req.setAPIPath(String.format("/api/s/%s/cmd/devmgr", device.getSite().getName())); + req.setBodyParameter("cmd", "power-cycle"); + req.setBodyParameter("mac", device.getMac()); + req.setBodyParameter("port_idx", portIdx); + executeRequest(req); + refresh(); + } + + public void enableWifi(final UniFiWlan wlan, final boolean enable) throws UniFiException { + final UniFiControllerRequest req = newRequest(Void.class, HttpMethod.PUT, poeGson); + req.setAPIPath(String.format("/api/s/%s/rest/wlanconf/%s", wlan.getSite().getName(), wlan.getId())); + req.setBodyParameter("_id", wlan.getId()); + req.setBodyParameter("enabled", enable ? "true" : "false"); + executeRequest(req); + refresh(); + } + + // Internal API + + private UniFiControllerRequest newRequest(final Class responseType, final HttpMethod method, + final Gson gson) { + return new UniFiControllerRequest<>(responseType, gson, httpClient, method, host, port, csrfToken, unifios); + } + + private @Nullable T executeRequest(final UniFiControllerRequest request) throws UniFiException { + return executeRequest(request, false); + } + + private @Nullable T executeRequest(final UniFiControllerRequest request, final boolean fromLogin) + throws UniFiException { + T result; + try { + result = request.execute(); + csrfToken = request.getCsrfToken(); + } catch (final UniFiExpiredSessionException e) { + if (fromLogin) { + // if this exception is thrown from a login attempt something is wrong, because the login should init + // the session. + throw new UniFiCommunicationException(e); + } else { + login(); + result = executeRequest(request); + } + } catch (final UniFiNotAuthorizedException e) { + logger.warn("Not Authorized! Please make sure your controller credentials have administrator rights"); + result = null; + } + return result; + } + + private List refreshSites() throws UniFiException { + final UniFiControllerRequest req = newRequest(UniFiSite[].class, HttpMethod.GET, gson); + req.setAPIPath("/api/self/sites"); + return cache.setSites(executeRequest(req)); + } + + private void refreshWlans(final Collection sites) throws UniFiException { + for (final UniFiSite site : sites) { + cache.putWlans(getWlans(site)); + } + } + + private UniFiWlan @Nullable [] getWlans(final UniFiSite site) throws UniFiException { + final UniFiControllerRequest req = newRequest(UniFiWlan[].class, HttpMethod.GET, gson); + req.setAPIPath(String.format("/api/s/%s/rest/wlanconf", site.getName())); + return executeRequest(req); + } + + private void refreshDevices(final Collection sites) throws UniFiException { + for (final UniFiSite site : sites) { + cache.putDevices(getDevices(site)); + } + } + + private UniFiDevice @Nullable [] getDevices(final UniFiSite site) throws UniFiException { + final UniFiControllerRequest req = newRequest(UniFiDevice[].class, HttpMethod.GET, gson); + req.setAPIPath(String.format("/api/s/%s/stat/device", site.getName())); + return executeRequest(req); + } + + private void refreshClients(final Collection sites) throws UniFiException { + for (final UniFiSite site : sites) { + cache.putClients(getClients(site)); + } + } + + private UniFiClient @Nullable [] getClients(final UniFiSite site) throws UniFiException { + final UniFiControllerRequest req = newRequest(UniFiClient[].class, HttpMethod.GET, gson); + req.setAPIPath(String.format("/api/s/%s/stat/sta", site.getName())); + return executeRequest(req); + } + + private void refreshInsights(final Collection sites) throws UniFiException { + for (final UniFiSite site : sites) { + cache.putInsights(getInsights(site)); + } + } + + private UniFiClient @Nullable [] getInsights(final UniFiSite site) throws UniFiException { + final UniFiControllerRequest req = newRequest(UniFiClient[].class, HttpMethod.GET, gson); + req.setAPIPath(String.format("/api/s/%s/stat/alluser", site.getName())); + req.setQueryParameter("within", INSIGHT_WITHIN_HOURS); + return executeRequest(req); + } +} diff --git a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/model/UniFiControllerRequest.java b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/UniFiControllerRequest.java similarity index 61% rename from bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/model/UniFiControllerRequest.java rename to bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/UniFiControllerRequest.java index b5185fef8ed..4acf1e0b464 100644 --- a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/model/UniFiControllerRequest.java +++ b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/UniFiControllerRequest.java @@ -10,8 +10,11 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.unifi.internal.api.model; +package org.openhab.binding.unifi.internal.api; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; import java.net.ConnectException; import java.net.UnknownHostException; import java.nio.charset.StandardCharsets; @@ -28,32 +31,23 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.HttpResponseException; -import org.eclipse.jetty.client.api.ContentProvider; import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.api.Request; +import org.eclipse.jetty.client.api.Response; +import org.eclipse.jetty.client.util.InputStreamResponseListener; import org.eclipse.jetty.client.util.StringContentProvider; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpScheme; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpURI; import org.eclipse.jetty.http.MimeTypes; -import org.openhab.binding.unifi.internal.api.UniFiCommunicationException; -import org.openhab.binding.unifi.internal.api.UniFiException; -import org.openhab.binding.unifi.internal.api.UniFiExpiredSessionException; -import org.openhab.binding.unifi.internal.api.UniFiInvalidCredentialsException; -import org.openhab.binding.unifi.internal.api.UniFiInvalidHostException; -import org.openhab.binding.unifi.internal.api.UniFiNotAuthorizedException; -import org.openhab.binding.unifi.internal.api.UniFiSSLException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.gson.Gson; import com.google.gson.GsonBuilder; -import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; -import com.google.gson.JsonPrimitive; -import com.google.gson.JsonSyntaxException; /** * The {@link UniFiControllerRequest} encapsulates a request sent by the {@link UniFiController}. @@ -63,9 +57,9 @@ import com.google.gson.JsonSyntaxException; * @param The response type expected as a result of the request's execution */ @NonNullByDefault -public class UniFiControllerRequest { +class UniFiControllerRequest { - private static final String CONTENT_TYPE_APPLICATION_JSON = MimeTypes.Type.APPLICATION_JSON.asString(); + private static final String CONTENT_TYPE_APPLICATION_JSON_UTF_8 = MimeTypes.Type.APPLICATION_JSON_UTF_8.asString(); private static final long TIMEOUT_SECONDS = 5; @@ -81,32 +75,35 @@ public class UniFiControllerRequest { private final int port; - private String path = "/"; - private final boolean unifios; + private final HttpMethod method; + + private String path = "/"; + private String csrfToken; - private Map queryParameters = new HashMap<>(); + private final Map queryParameters = new HashMap<>(); - private Map bodyParameters = new HashMap<>(); + private final Map bodyParameters = new HashMap<>(); private final Class resultType; // Public API - public UniFiControllerRequest(Class resultType, Gson gson, HttpClient httpClient, String host, int port, - String csrfToken, boolean unifios) { + public UniFiControllerRequest(final Class resultType, final Gson gson, final HttpClient httpClient, + final HttpMethod method, final String host, final int port, final String csrfToken, final boolean unifios) { this.resultType = resultType; this.gson = gson; this.httpClient = httpClient; + this.method = method; this.host = host; this.port = port; this.csrfToken = csrfToken; this.unifios = unifios; } - public void setAPIPath(String relativePath) { + public void setAPIPath(final String relativePath) { if (unifios) { this.path = "/proxy/network" + relativePath; } else { @@ -114,24 +111,25 @@ public class UniFiControllerRequest { } } - public void setPath(String path) { + public void setPath(final String path) { this.path = path; } - public void setBodyParameter(String key, Object value) { - this.bodyParameters.put(key, String.valueOf(value)); + public void setBodyParameter(final String key, final Object value) { + this.bodyParameters.put(key, value); } - public void setQueryParameter(String key, Object value) { + public void setQueryParameter(final String key, final Object value) { this.queryParameters.put(key, String.valueOf(value)); } public @Nullable T execute() throws UniFiException { T result = null; - String json = getContent(); + final String json = getContent(); // mgb: only try and unmarshall non-void result types if (!Void.class.equals(resultType)) { - JsonObject jsonObject = JsonParser.parseString(json).getAsJsonObject(); + final JsonObject jsonObject = JsonParser.parseString(json).getAsJsonObject(); + if (jsonObject.has(PROPERTY_DATA) && jsonObject.get(PROPERTY_DATA).isJsonArray()) { result = gson.fromJson(jsonObject.getAsJsonArray(PROPERTY_DATA), resultType); } @@ -143,43 +141,47 @@ public class UniFiControllerRequest { private String getContent() throws UniFiException { String content; - ContentResponse response = getContentResponse(); - int status = response.getStatus(); + final InputStreamResponseListener listener = new InputStreamResponseListener(); + final Response response = getContentResponse(listener); + final int status = response.getStatus(); switch (status) { case HttpStatus.OK_200: - content = response.getContentAsString(); + content = responseToString(listener); if (logger.isTraceEnabled()) { logger.trace("<< {} {} \n{}", status, HttpStatus.getMessage(status), prettyPrintJson(content)); } - String csrfToken = response.getHeaders().get("X-CSRF-Token"); + final String csrfToken = response.getHeaders().get("X-CSRF-Token"); if (csrfToken != null && !csrfToken.isEmpty()) { this.csrfToken = csrfToken; } break; case HttpStatus.BAD_REQUEST_400: + logger.info("UniFi returned a status 400: {}", prettyPrintJson(responseToString(listener))); throw new UniFiInvalidCredentialsException("Invalid Credentials"); case HttpStatus.UNAUTHORIZED_401: throw new UniFiExpiredSessionException("Expired Credentials"); case HttpStatus.FORBIDDEN_403: throw new UniFiNotAuthorizedException("Unauthorized Access"); default: + logger.info("UniFi returned a status code {}: {}", status, prettyPrintJson(responseToString(listener))); throw new UniFiException("Unknown HTTP status code " + status + " returned by the controller"); } return content; } - private ContentResponse getContentResponse() throws UniFiException { - Request request = newRequest(); + private Response getContentResponse(final InputStreamResponseListener listener) throws UniFiException { + final Request request = newRequest(); logger.trace(">> {} {}", request.getMethod(), request.getURI()); - ContentResponse response; + Response response; try { - response = request.send(); + request.send(listener); + response = listener.get(TIMEOUT_SECONDS, TimeUnit.SECONDS); } catch (TimeoutException | InterruptedException e) { throw new UniFiCommunicationException(e); - } catch (ExecutionException e) { + } catch (final ExecutionException e) { // mgb: unwrap the cause and try to cleanly handle it - Throwable cause = e.getCause(); + final Throwable cause = e.getCause(); if (cause instanceof UnknownHostException) { // invalid hostname throw new UniFiInvalidHostException(cause); @@ -193,9 +195,9 @@ public class UniFiControllerRequest { && ((HttpResponseException) cause).getResponse() instanceof ContentResponse) { // the UniFi controller violates the HTTP protocol // - it returns 401 UNAUTHORIZED without the WWW-Authenticate response header - // - this causes an ExceptionException to be thrown + // - this causes an ExecutionException to be thrown // - we unwrap the response from the exception for proper handling of the 401 status code - response = (ContentResponse) ((HttpResponseException) cause).getResponse(); + response = ((HttpResponseException) cause).getResponse(); } else { // catch all throw new UniFiException(cause); @@ -204,23 +206,32 @@ public class UniFiControllerRequest { return response; } + private static String responseToString(final InputStreamResponseListener listener) throws UniFiException { + final ByteArrayOutputStream responseContent = new ByteArrayOutputStream(); + try (InputStream input = listener.getInputStream()) { + input.transferTo(responseContent); + } catch (final IOException e) { + throw new UniFiException(e); + } + return new String(responseContent.toByteArray(), StandardCharsets.UTF_8); + } + public String getCsrfToken() { return csrfToken; } private Request newRequest() { - HttpMethod method = bodyParameters.isEmpty() ? HttpMethod.GET : HttpMethod.POST; - HttpURI uri = new HttpURI(HttpScheme.HTTPS.asString(), host, port, path); - Request request = httpClient.newRequest(uri.toString()).timeout(TIMEOUT_SECONDS, TimeUnit.SECONDS) + final HttpURI uri = new HttpURI(HttpScheme.HTTPS.asString(), host, port, path); + final Request request = httpClient.newRequest(uri.toString()).timeout(TIMEOUT_SECONDS, TimeUnit.SECONDS) .method(method); - for (Entry entry : queryParameters.entrySet()) { + for (final Entry entry : queryParameters.entrySet()) { request.param(entry.getKey(), entry.getValue()); } if (!bodyParameters.isEmpty()) { - String jsonBody = getRequestBodyAsJson(); - ContentProvider content = new StringContentProvider(CONTENT_TYPE_APPLICATION_JSON, jsonBody, - StandardCharsets.UTF_8); - request = request.content(content); + final String jsonBody = gson.toJson(bodyParameters); + + request.content( + new StringContentProvider(CONTENT_TYPE_APPLICATION_JSON_UTF_8, jsonBody, StandardCharsets.UTF_8)); } if (!csrfToken.isEmpty()) { @@ -230,23 +241,15 @@ public class UniFiControllerRequest { return request; } - private String getRequestBodyAsJson() { - JsonObject jsonObject = new JsonObject(); - JsonElement jsonElement = null; - for (Entry entry : bodyParameters.entrySet()) { - try { - jsonElement = JsonParser.parseString(entry.getValue()); - } catch (JsonSyntaxException e) { - jsonElement = new JsonPrimitive(entry.getValue()); - } - jsonObject.add(entry.getKey(), jsonElement); - } - return jsonObject.toString(); - } + private static String prettyPrintJson(final String content) { + try { + final JsonObject json = JsonParser.parseString(content).getAsJsonObject(); + final Gson prettyGson = new GsonBuilder().setPrettyPrinting().create(); - private static String prettyPrintJson(String content) { - JsonObject json = JsonParser.parseString(content).getAsJsonObject(); - Gson prettyGson = new GsonBuilder().setPrettyPrinting().create(); - return prettyGson.toJson(json); + return prettyGson.toJson(json); + } catch (final RuntimeException e) { + // If could not parse the string as json, just return the string + return content; + } } } diff --git a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/UniFiException.java b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/UniFiException.java index 93f58b9f198..ce92b56e6bc 100644 --- a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/UniFiException.java +++ b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/UniFiException.java @@ -12,24 +12,28 @@ */ package org.openhab.binding.unifi.internal.api; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + /** * The {@link UniFiException} represents a binding specific {@link Exception}. * * @author Matthew Bowman - Initial contribution */ +@NonNullByDefault public class UniFiException extends Exception { private static final long serialVersionUID = -7422254981644510570L; - public UniFiException(String message) { + public UniFiException(final String message) { super(message); } - public UniFiException(String message, Throwable cause) { + public UniFiException(final String message, final Throwable cause) { super(message, cause); } - public UniFiException(Throwable cause) { + public UniFiException(final @Nullable Throwable cause) { super(cause); } } diff --git a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/UniFiExpiredSessionException.java b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/UniFiExpiredSessionException.java index c909525c4de..cc801aa7227 100644 --- a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/UniFiExpiredSessionException.java +++ b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/UniFiExpiredSessionException.java @@ -12,16 +12,19 @@ */ package org.openhab.binding.unifi.internal.api; +import org.eclipse.jdt.annotation.NonNullByDefault; + /** * The {@link UniFiExpiredSessionException} signals the session with the controller has expired. * * @author Matthew Bowman - Initial contribution */ +@NonNullByDefault public class UniFiExpiredSessionException extends UniFiException { private static final long serialVersionUID = -2002650048964514035L; - public UniFiExpiredSessionException(String message) { + public UniFiExpiredSessionException(final String message) { super(message); } } diff --git a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/UniFiInvalidCredentialsException.java b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/UniFiInvalidCredentialsException.java index 8cf8a681a12..ee5fde3c2b0 100644 --- a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/UniFiInvalidCredentialsException.java +++ b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/UniFiInvalidCredentialsException.java @@ -12,17 +12,20 @@ */ package org.openhab.binding.unifi.internal.api; +import org.eclipse.jdt.annotation.NonNullByDefault; + /** * The {@link UniFiInvalidCredentialsException} signals the credentials used to authenticate with the controller are * invalid. * * @author Matthew Bowman - Initial contribution */ +@NonNullByDefault public class UniFiInvalidCredentialsException extends UniFiException { private static final long serialVersionUID = -7159360851783088458L; - public UniFiInvalidCredentialsException(String message) { + public UniFiInvalidCredentialsException(final String message) { super(message); } } diff --git a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/UniFiInvalidHostException.java b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/UniFiInvalidHostException.java index ec9ea6a912d..493897f421f 100644 --- a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/UniFiInvalidHostException.java +++ b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/UniFiInvalidHostException.java @@ -12,24 +12,27 @@ */ package org.openhab.binding.unifi.internal.api; +import org.eclipse.jdt.annotation.NonNullByDefault; + /** * The {@link UniFiInvalidHostException} signals there was a problem with the hostname of the controller. * * @author Matthew Bowman - Initial contribution */ +@NonNullByDefault public class UniFiInvalidHostException extends UniFiException { private static final long serialVersionUID = -7261308872245069364L; - public UniFiInvalidHostException(String message) { + public UniFiInvalidHostException(final String message) { super(message); } - public UniFiInvalidHostException(String message, Throwable cause) { + public UniFiInvalidHostException(final String message, final Throwable cause) { super(message, cause); } - public UniFiInvalidHostException(Throwable cause) { + public UniFiInvalidHostException(final Throwable cause) { super(cause); } } diff --git a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/UniFiNotAuthorizedException.java b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/UniFiNotAuthorizedException.java index e89b42447b5..1bc40ef4496 100644 --- a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/UniFiNotAuthorizedException.java +++ b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/UniFiNotAuthorizedException.java @@ -12,24 +12,27 @@ */ package org.openhab.binding.unifi.internal.api; +import org.eclipse.jdt.annotation.NonNullByDefault; + /** * The {@link UniFiNotAuthorizedException} signals the controller denied a request due to non-admin credentials. * * @author Matthew Bowman - Initial contribution */ +@NonNullByDefault public class UniFiNotAuthorizedException extends UniFiException { private static final long serialVersionUID = 1379973398415636322L; - public UniFiNotAuthorizedException(String message) { + public UniFiNotAuthorizedException(final String message) { super(message); } - public UniFiNotAuthorizedException(String message, Throwable cause) { + public UniFiNotAuthorizedException(final String message, final Throwable cause) { super(message, cause); } - public UniFiNotAuthorizedException(Throwable cause) { + public UniFiNotAuthorizedException(final Throwable cause) { super(cause); } } diff --git a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/UniFiSSLException.java b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/UniFiSSLException.java index 59397991cb1..02043f0bd00 100644 --- a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/UniFiSSLException.java +++ b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/UniFiSSLException.java @@ -12,24 +12,27 @@ */ package org.openhab.binding.unifi.internal.api; +import org.eclipse.jdt.annotation.NonNullByDefault; + /** * The {@link UniFiSSLException} signals a failure establishing an SSL connection with the controller. * * @author Matthew Bowman - Initial contribution */ +@NonNullByDefault public class UniFiSSLException extends UniFiException { private static final long serialVersionUID = 4688857482270932413L; - public UniFiSSLException(String message) { + public UniFiSSLException(final String message) { super(message); } - public UniFiSSLException(String message, Throwable cause) { + public UniFiSSLException(final String message, final Throwable cause) { super(message, cause); } - public UniFiSSLException(Throwable cause) { + public UniFiSSLException(final Throwable cause) { super(cause); } } diff --git a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/cache/UniFiCache.java b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/cache/UniFiCache.java index de7cec15d35..44107ff2c20 100644 --- a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/cache/UniFiCache.java +++ b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/cache/UniFiCache.java @@ -14,9 +14,13 @@ package org.openhab.binding.unifi.internal.api.cache; import java.util.Collection; import java.util.HashMap; +import java.util.Locale; import java.util.Map; import java.util.stream.Collectors; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.unifi.internal.api.dto.HasId; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -29,41 +33,65 @@ import org.slf4j.LoggerFactory; * prefix:suffix are searched in the order of their priority. * * @author Matthew Bowman - Initial contribution + * @author Hilbrand Bouwkamp - Moved generic code into this class */ -public abstract class UniFiCache { +@NonNullByDefault +abstract class UniFiCache { + + public enum Prefix { + ALIAS, + DESC, + HOSTNAME, + ID, + IP, + MAC, + NAME; + } private static final String SEPARATOR = ":"; - public static final String PREFIX_ALIAS = "alias"; - - public static final String PREFIX_DESC = "desc"; - - public static final String PREFIX_HOSTNAME = "hostname"; - - public static final String PREFIX_ID = "id"; - - public static final String PREFIX_IP = "ip"; - - public static final String PREFIX_MAC = "mac"; - - public static final String PREFIX_NAME = "name"; - private final Logger logger = LoggerFactory.getLogger(getClass()); + // Map of cid keys to the id. + private final Map mapToId = new HashMap<>(); + // Map of id to data object + private final Map map = new HashMap<>(); + private final Prefix[] prefixes; - private Map map = new HashMap<>(); - - private String[] prefixes; - - protected UniFiCache(String... prefixes) { + protected UniFiCache(final Prefix... prefixes) { this.prefixes = prefixes; } - public final T get(Object id) { - T value = null; - for (String prefix : prefixes) { - String key = prefix + SEPARATOR + id; - if (map.containsKey(key)) { - value = map.get(key); + public void clear() { + map.clear(); + } + + public final @Nullable T get(final @Nullable String cid) { + final @Nullable T value; + + if (cid != null && !cid.isBlank()) { + synchronized (this) { + final String id = getId(cid); + + if (id == null) { + logger.debug("Could not find an entry in the cache for id: '{}'", cid); + value = null; + } else { + value = map.get(id); + } + } + } else { + value = null; + } + return value; + } + + public @Nullable String getId(final String cid) { + String value = null; + for (final Prefix prefix : prefixes) { + final String key = key(prefix, cid); + + if (mapToId.containsKey(key)) { + value = mapToId.get(key); logger.trace("Cache HIT : '{}' -> {}", key, value); break; } else { @@ -73,23 +101,48 @@ public abstract class UniFiCache { return value; } - public final void put(T value) { - for (String prefix : prefixes) { - String suffix = getSuffix(value, prefix); - if (suffix != null && !suffix.isBlank()) { - String key = prefix + SEPARATOR + suffix; - map.put(key, value); + public final void putAll(final T @Nullable [] values) { + if (values != null) { + logger.debug("Put #{} entries in {}: {}", values.length, getClass().getSimpleName(), + lazyFormatAsList(values)); + for (final T value : values) { + put(value.getId(), value); } } } - public final void putAll(UniFiCache cache) { - map.putAll(cache.map); + public final void put(final String id, final T value) { + for (final Prefix prefix : prefixes) { + final String suffix = getSuffix(value, prefix); + + if (suffix != null && !suffix.isBlank()) { + mapToId.put(key(prefix, suffix), id); + } + } + map.put(id, value); + } + + private static String key(final Prefix prefix, final String suffix) { + return prefix.name() + SEPARATOR + suffix.replace(":", "").toLowerCase(Locale.ROOT); } public final Collection values() { return map.values().stream().distinct().collect(Collectors.toList()); } - protected abstract String getSuffix(T value, String prefix); + protected abstract @Nullable String getSuffix(T value, Prefix prefix); + + private static Object lazyFormatAsList(final Object[] arr) { + return new Object() { + + @Override + public String toString() { + String value = ""; + for (final Object o : arr) { + value += "\n - " + o.toString(); + } + return value; + } + }; + } } diff --git a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/cache/UniFiClientCache.java b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/cache/UniFiClientCache.java index eaa07548fe0..5819577393c 100644 --- a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/cache/UniFiClientCache.java +++ b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/cache/UniFiClientCache.java @@ -12,7 +12,15 @@ */ package org.openhab.binding.unifi.internal.api.cache; -import org.openhab.binding.unifi.internal.api.model.UniFiClient; +import static org.openhab.binding.unifi.internal.api.cache.UniFiCache.Prefix.ALIAS; +import static org.openhab.binding.unifi.internal.api.cache.UniFiCache.Prefix.HOSTNAME; +import static org.openhab.binding.unifi.internal.api.cache.UniFiCache.Prefix.ID; +import static org.openhab.binding.unifi.internal.api.cache.UniFiCache.Prefix.IP; +import static org.openhab.binding.unifi.internal.api.cache.UniFiCache.Prefix.MAC; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.unifi.internal.api.dto.UniFiClient; /** * The {@link UniFiClientCache} is a specific implementation of {@link UniFiCache} for the purpose of caching @@ -23,26 +31,28 @@ import org.openhab.binding.unifi.internal.api.model.UniFiClient; * * @author Matthew Bowman - Initial contribution */ -public class UniFiClientCache extends UniFiCache { +@NonNullByDefault +class UniFiClientCache extends UniFiCache { public UniFiClientCache() { - super(PREFIX_ID, PREFIX_MAC, PREFIX_IP, PREFIX_HOSTNAME, PREFIX_ALIAS); + super(ID, MAC, IP, HOSTNAME, ALIAS); } @Override - protected String getSuffix(UniFiClient client, String prefix) { + protected @Nullable String getSuffix(final UniFiClient client, final Prefix prefix) { switch (prefix) { - case PREFIX_ID: + case ID: return client.getId(); - case PREFIX_MAC: + case MAC: return client.getMac(); - case PREFIX_IP: + case IP: return client.getIp(); - case PREFIX_HOSTNAME: + case HOSTNAME: return client.getHostname(); - case PREFIX_ALIAS: + case ALIAS: return client.getAlias(); + default: + return null; } - return null; } } diff --git a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/cache/UniFiControllerCache.java b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/cache/UniFiControllerCache.java new file mode 100644 index 00000000000..174062cca1f --- /dev/null +++ b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/cache/UniFiControllerCache.java @@ -0,0 +1,158 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.unifi.internal.api.cache; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.unifi.internal.api.dto.UniFiClient; +import org.openhab.binding.unifi.internal.api.dto.UniFiDevice; +import org.openhab.binding.unifi.internal.api.dto.UniFiPortTable; +import org.openhab.binding.unifi.internal.api.dto.UniFiSite; +import org.openhab.binding.unifi.internal.api.dto.UniFiWlan; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Class to manager cache for the controller keeping track of all specific cache objects. + * + * @author Matthew Bowman - Initial contribution + * @author Hilbrand Bouwkamp - Moved cache to this dedicated class. + */ +@NonNullByDefault +public class UniFiControllerCache { + + private final Logger logger = LoggerFactory.getLogger(UniFiControllerCache.class); + + private final UniFiSiteCache sitesCache = new UniFiSiteCache(); + private final UniFiWlanCache wlansCache = new UniFiWlanCache(); + private final UniFiDeviceCache devicesCache = new UniFiDeviceCache(); + private final UniFiClientCache clientsCache = new UniFiClientCache(); + private final UniFiClientCache insightsCache = new UniFiClientCache(); + private final Map> devicesToPortTables = new ConcurrentHashMap<>(); + + public void clear() { + sitesCache.clear(); + wlansCache.clear(); + devicesCache.clear(); + clientsCache.clear(); + insightsCache.clear(); + } + + // Sites Cache + + public List setSites(final UniFiSite @Nullable [] sites) { + sitesCache.putAll(sites); + return List.of(sites); + } + + public @Nullable UniFiSite getSite(final @Nullable String id) { + return sitesCache.get(id); + } + + public Collection getSites() { + return sitesCache.values(); + } + + // Wlans Cache + + public void putWlans(final UniFiWlan @Nullable [] wlans) { + wlansCache.putAll(wlans); + } + + public @Nullable UniFiWlan getWlan(@Nullable final String id) { + return wlansCache.get(id); + } + + public Collection getWlans() { + return wlansCache.values(); + } + + // Devices Cache + + public void putDevices(final UniFiDevice @Nullable [] devices) { + devicesCache.putAll(devices); + if (devices != null) { + Stream.of(devices).filter(Objects::nonNull).forEach(d -> { + Stream.ofNullable(d.getPortTable()).filter(ptl -> ptl.length > 0 && ptl[0].isPortPoe()).forEach(pt -> { + Stream.of(pt).forEach(p -> p.setDevice(d)); + devicesToPortTables.put(d.getMac(), + Stream.of(pt).collect(Collectors.toMap(UniFiPortTable::getPortIdx, Function.identity()))); + }); + }); + } + } + + public @Nullable UniFiDevice getDevice(@Nullable final String id) { + return devicesCache.get(id); + } + + public Map getSwitchPorts(@Nullable final String deviceId) { + return deviceId == null ? Map.of() : devicesToPortTables.getOrDefault(deviceId, Map.of()); + } + + public Collection> getSwitchPorts() { + return devicesToPortTables.values(); + } + + // Clients Cache + + public void putClients(final UniFiClient @Nullable [] clients) { + clientsCache.putAll(clients); + } + + public Collection getClients() { + return clientsCache.values(); + } + + public long countClients(final UniFiSite site, final Function filter) { + return getClients().stream().filter(c -> site.isSite(c.getSite())).filter(filter::apply).count(); + } + + public @Nullable UniFiClient getClient(@Nullable final String cid) { + UniFiClient client = null; + if (cid != null && !cid.isBlank()) { + synchronized (this) { + // mgb: first check active clients and fallback to insights if not found + client = clientsCache.get(cid); + if (client == null) { + final String id = clientsCache.getId(cid); + + client = insightsCache.get(id == null ? cid : id); + } + } + if (client == null) { + logger.debug("Could not find a matching client for cid = {}", cid); + } + } + return client; + } + + public synchronized Stream getClientStreamForSite(final UniFiSite site) { + return clientsCache.values().stream().filter(client -> client.getSite().equals(site)); + } + + // Insights Cache + + public void putInsights(final UniFiClient @Nullable [] insights) { + insightsCache.putAll(insights); + } +} diff --git a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/cache/UniFiDeviceCache.java b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/cache/UniFiDeviceCache.java index 04382cbaa1a..8cfd91c977f 100644 --- a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/cache/UniFiDeviceCache.java +++ b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/cache/UniFiDeviceCache.java @@ -12,7 +12,11 @@ */ package org.openhab.binding.unifi.internal.api.cache; -import org.openhab.binding.unifi.internal.api.model.UniFiDevice; +import static org.openhab.binding.unifi.internal.api.cache.UniFiCache.Prefix.MAC; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.unifi.internal.api.dto.UniFiDevice; /** * The {@link UniFiDeviceCache} is a specific implementation of {@link UniFiCache} for the purpose of caching @@ -22,16 +26,17 @@ import org.openhab.binding.unifi.internal.api.model.UniFiDevice; * * @author Matthew Bowman - Initial contribution */ -public class UniFiDeviceCache extends UniFiCache { +@NonNullByDefault +class UniFiDeviceCache extends UniFiCache { public UniFiDeviceCache() { - super(PREFIX_MAC); + super(MAC); } @Override - protected String getSuffix(UniFiDevice device, String prefix) { + protected @Nullable String getSuffix(final UniFiDevice device, final Prefix prefix) { switch (prefix) { - case PREFIX_MAC: + case MAC: return device.getMac(); } return null; diff --git a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/cache/UniFiSiteCache.java b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/cache/UniFiSiteCache.java index 9a9cbdb4828..77f7b658afe 100644 --- a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/cache/UniFiSiteCache.java +++ b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/cache/UniFiSiteCache.java @@ -12,7 +12,13 @@ */ package org.openhab.binding.unifi.internal.api.cache; -import org.openhab.binding.unifi.internal.api.model.UniFiSite; +import static org.openhab.binding.unifi.internal.api.cache.UniFiCache.Prefix.DESC; +import static org.openhab.binding.unifi.internal.api.cache.UniFiCache.Prefix.ID; +import static org.openhab.binding.unifi.internal.api.cache.UniFiCache.Prefix.NAME; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.unifi.internal.api.dto.UniFiSite; /** * The {@link UniFiSiteCache} is a specific implementation of {@link UniFiCache} for the purpose of caching @@ -22,22 +28,24 @@ import org.openhab.binding.unifi.internal.api.model.UniFiSite; * * @author Matthew Bowman - Initial contribution */ -public class UniFiSiteCache extends UniFiCache { +@NonNullByDefault +class UniFiSiteCache extends UniFiCache { public UniFiSiteCache() { - super(PREFIX_ID, PREFIX_NAME, PREFIX_DESC); + super(ID, NAME, DESC); } @Override - protected String getSuffix(UniFiSite site, String prefix) { + protected @Nullable String getSuffix(final UniFiSite site, final Prefix prefix) { switch (prefix) { - case PREFIX_ID: + case ID: return site.getId(); - case PREFIX_NAME: + case NAME: return site.getName(); - case PREFIX_DESC: + case DESC: return site.getDescription(); + default: + return null; } - return null; } } diff --git a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/cache/UniFiWlanCache.java b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/cache/UniFiWlanCache.java new file mode 100644 index 00000000000..01dc03430fe --- /dev/null +++ b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/cache/UniFiWlanCache.java @@ -0,0 +1,48 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.unifi.internal.api.cache; + +import static org.openhab.binding.unifi.internal.api.cache.UniFiCache.Prefix.ID; +import static org.openhab.binding.unifi.internal.api.cache.UniFiCache.Prefix.NAME; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.unifi.internal.api.dto.UniFiWlan; + +/** + * The {@link UniFiWlanCache} is a specific implementation of {@link UniFiCache} for the purpose of caching + * {@link UniFiWlan} instances. + * + * The cache uses the following prefixes: id, name + * + * @author Hilbrand Bouwkamp - Initial contribution + */ +@NonNullByDefault +class UniFiWlanCache extends UniFiCache { + + public UniFiWlanCache() { + super(ID, NAME); + } + + @Override + protected @Nullable String getSuffix(final UniFiWlan wlan, final Prefix prefix) { + switch (prefix) { + case ID: + return wlan.getId(); + case NAME: + return wlan.getName(); + default: + return null; + } + } +} diff --git a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/dto/HasId.java b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/dto/HasId.java new file mode 100644 index 00000000000..7f03bd1c9f4 --- /dev/null +++ b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/dto/HasId.java @@ -0,0 +1,23 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.unifi.internal.api.dto; + +/** + * Data classes that have an id as identifier. + * + * @author Hilbrand Bouwkamp - Initial contribution + */ +public interface HasId { + + String getId(); +} diff --git a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/dto/UnfiPortOverride.java b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/dto/UnfiPortOverride.java new file mode 100644 index 00000000000..bd4a41ff06a --- /dev/null +++ b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/dto/UnfiPortOverride.java @@ -0,0 +1,72 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.unifi.internal.api.dto; + +import com.google.gson.annotations.Expose; + +/** + * The {@link UnfiPortOverride} represents the data model of UniFi port override. + * + * @author Hilbrand Bouwkamp - Initial contribution + */ +public class UnfiPortOverride { + + @Expose + private int portIdx; + + @Expose + private String portconfId; + + @Expose + private String poeMode; + + public UnfiPortOverride() { + // Constructor for GSON. + } + + public UnfiPortOverride(final int portIdx, final String portconfId, final String poeMode) { + this.portIdx = portIdx; + this.portconfId = portconfId; + this.poeMode = poeMode; + } + + public int getPortIdx() { + return portIdx; + } + + public String getPortconfId() { + return portconfId; + } + + public String getPoeMode() { + return poeMode; + } + + public void setPortIdx(final int portIdx) { + this.portIdx = portIdx; + } + + public void setPortconfId(final String portconfId) { + this.portconfId = portconfId; + } + + public void setPoeMode(final String poeMode) { + this.poeMode = poeMode; + } + + @Override + public String toString() { + return String.format("UnfiPortOverride{portIx: '%d', portconfId: '%s', poeMode: '%s'}", portIdx, portconfId, + poeMode); + } +} diff --git a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/model/UniFiClient.java b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/dto/UniFiClient.java similarity index 59% rename from bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/model/UniFiClient.java rename to bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/dto/UniFiClient.java index 85a81949641..28def26fd6a 100644 --- a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/model/UniFiClient.java +++ b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/dto/UniFiClient.java @@ -10,11 +10,11 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.unifi.internal.api.model; +package org.openhab.binding.unifi.internal.api.dto; -import java.util.Calendar; +import java.time.Instant; -import org.openhab.binding.unifi.internal.api.UniFiException; +import org.openhab.binding.unifi.internal.api.cache.UniFiControllerCache; import org.openhab.binding.unifi.internal.api.util.UniFiTidyLowerCaseStringDeserializer; import org.openhab.binding.unifi.internal.api.util.UniFiTimestampDeserializer; @@ -27,38 +27,48 @@ import com.google.gson.annotations.SerializedName; * @author Matthew Bowman - Initial contribution * @author Patrik Wimnell - Blocking / Unblocking client support */ -public abstract class UniFiClient { +public abstract class UniFiClient implements HasId { - protected final transient UniFiController controller; + private final transient UniFiControllerCache cache; @SerializedName("_id") - protected String id; + private String id; - protected String siteId; + private String siteId; @JsonAdapter(UniFiTidyLowerCaseStringDeserializer.class) - protected String mac; + private String mac; - protected String ip; + private String ip; @JsonAdapter(UniFiTidyLowerCaseStringDeserializer.class) - protected String hostname; + private String hostname; @SerializedName("name") @JsonAdapter(UniFiTidyLowerCaseStringDeserializer.class) - protected String alias; + private String alias; - protected Integer uptime; + private Integer uptime; @JsonAdapter(UniFiTimestampDeserializer.class) - protected Calendar lastSeen; + private Instant lastSeen; - protected boolean blocked; + private boolean blocked; - protected UniFiClient(UniFiController controller) { - this.controller = controller; + @SerializedName("is_guest") + private boolean guest; + + @SerializedName("fixed_ip") + private String fixedIp; + + @SerializedName("satisfaction") + private Integer experience; + + protected UniFiClient(final UniFiControllerCache cache) { + this.cache = cache; } + @Override public String getId() { return id; } @@ -68,7 +78,7 @@ public abstract class UniFiClient { } public String getIp() { - return this.ip; + return this.ip == null || this.ip.isBlank() ? this.fixedIp : this.ip; } public String getHostname() { @@ -83,7 +93,7 @@ public abstract class UniFiClient { return uptime; } - public Calendar getLastSeen() { + public Instant getLastSeen() { return lastSeen; } @@ -94,33 +104,31 @@ public abstract class UniFiClient { public abstract Boolean isWired(); public final Boolean isWireless() { - return isWired() == null ? null : (isWired().booleanValue() ? Boolean.FALSE : Boolean.TRUE); + return isWired() == null ? null : Boolean.FALSE.equals(isWired()); } protected abstract String getDeviceMac(); public UniFiSite getSite() { - return controller.getSite(siteId); + return cache.getSite(siteId); } public UniFiDevice getDevice() { - return controller.getDevice(getDeviceMac()); + return cache.getDevice(getDeviceMac()); } - // Functional API - - public void block(boolean blocked) throws UniFiException { - controller.block(this, blocked); + public boolean isGuest() { + return guest; } - public void reconnect() throws UniFiException { - controller.reconnect(this); + public Integer getExperience() { + return experience; } @Override public String toString() { return String.format( - "UniFiClient{id: '%s', mac: '%s', ip: '%s', hostname: '%s', alias: '%s', wired: %b, blocked: %b, device: %s}", - id, mac, ip, hostname, alias, isWired(), blocked, getDevice()); + "UniFiClient{id: '%s', mac: '%s', ip: '%s', hostname: '%s', alias: '%s', wired: %b, guest: %b, blocked: %b, experience: %d, device: %s}", + id, mac, getIp(), hostname, alias, isWired(), guest, blocked, experience, getDevice()); } } diff --git a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/model/UniFiDevice.java b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/dto/UniFiDevice.java similarity index 72% rename from bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/model/UniFiDevice.java rename to bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/dto/UniFiDevice.java index a086b2d5ca4..0cd1e1b38a0 100644 --- a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/model/UniFiDevice.java +++ b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/dto/UniFiDevice.java @@ -10,8 +10,9 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.unifi.internal.api.model; +package org.openhab.binding.unifi.internal.api.dto; +import org.openhab.binding.unifi.internal.api.cache.UniFiControllerCache; import org.openhab.binding.unifi.internal.api.util.UniFiTidyLowerCaseStringDeserializer; import com.google.gson.annotations.JsonAdapter; @@ -22,10 +23,11 @@ import com.google.gson.annotations.SerializedName; * (better known as an Access Point). * * @author Matthew Bowman - Initial contribution + * @author Hilbrand Bouwkamp - Added PoEPort support */ -public class UniFiDevice { +public class UniFiDevice implements HasId { - protected final transient UniFiController controller; + protected final transient UniFiControllerCache cache; @SerializedName("_id") private String id; @@ -39,10 +41,13 @@ public class UniFiDevice { private String siteId; - public UniFiDevice(UniFiController controller) { - this.controller = controller; + private UniFiPortTable[] portTable; + + public UniFiDevice(final UniFiControllerCache cache) { + this.cache = cache; } + @Override public String getId() { return id; } @@ -60,7 +65,11 @@ public class UniFiDevice { } public UniFiSite getSite() { - return controller.getSite(siteId); + return cache.getSite(siteId); + } + + public UniFiPortTable[] getPortTable() { + return portTable; } @Override diff --git a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/dto/UniFiPortOverrides.java b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/dto/UniFiPortOverrides.java new file mode 100644 index 00000000000..f6645781dc5 --- /dev/null +++ b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/dto/UniFiPortOverrides.java @@ -0,0 +1,42 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.unifi.internal.api.dto; + +import java.util.ArrayList; +import java.util.List; + +import com.google.gson.annotations.Expose; + +/** + * The {@link UniFiPortOverrides} represents the data model of UniFi port overrides. + * + * @author Hilbrand Bouwkamp - Initial contribution + */ +public class UniFiPortOverrides { + + @Expose + private final List portOverrides = new ArrayList<>(); + + public void addPortOverride(final UnfiPortOverride unfiPortOverride) { + portOverrides.add(unfiPortOverride); + } + + public void addPortOverride(final int portIdx, final String portconfId, final String poeMode) { + portOverrides.add(new UnfiPortOverride(portIdx, portconfId, poeMode)); + } + + @Override + public String toString() { + return String.format("UniFiPortOverrides: {}", String.join(", ", portOverrides.toArray(new String[0]))); + } +} diff --git a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/dto/UniFiPortTable.java b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/dto/UniFiPortTable.java new file mode 100644 index 00000000000..637ed2dc5ed --- /dev/null +++ b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/dto/UniFiPortTable.java @@ -0,0 +1,89 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.unifi.internal.api.dto; + +/** + * The {@link UniFiPortTable} represents the data model of UniFi port table, which is an extend of port override. + * + * @author Hilbrand Bouwkamp - Initial contribution + */ +public class UniFiPortTable extends UnfiPortOverride { + + private transient UniFiDevice device; + + private String name; + + private boolean enable; + + private boolean up; + + /** + * If true supports PoE. + */ + private boolean portPoe; + + private boolean poeEnable; + + private String poePower; + + private String poeVoltage; + + private String poeCurrent; + + public UniFiDevice getDevice() { + return device; + } + + public void setDevice(final UniFiDevice device) { + this.device = device; + } + + public String getName() { + return name; + } + + public boolean isUp() { + return up; + } + + public boolean isEnabled() { + return enable; + } + + public boolean isPortPoe() { + return portPoe; + } + + public boolean isPoeEnabled() { + return poeEnable; + } + + public String getPoePower() { + return poePower; + } + + public String getPoeVoltage() { + return poeVoltage; + } + + public String getPoeCurrent() { + return poeCurrent; + } + + @Override + public String toString() { + return String.format( + "UniFiPortTable{name: '%s', enable: '%b', up: '%b', portPoe: '%b', poeEnable: '%b, poePower: '%s', poeVoltage: '%s', poeCurrent: '%s'}", + name, enable, up, portPoe, poeEnable, poePower, poeVoltage, poeCurrent); + } +} diff --git a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/model/UniFiSite.java b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/dto/UniFiSite.java similarity index 60% rename from bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/model/UniFiSite.java rename to bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/dto/UniFiSite.java index 6a54b14a405..f72ef5ec513 100644 --- a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/model/UniFiSite.java +++ b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/dto/UniFiSite.java @@ -10,7 +10,9 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.unifi.internal.api.model; +package org.openhab.binding.unifi.internal.api.dto; + +import org.openhab.binding.unifi.internal.api.cache.UniFiControllerCache; import com.google.gson.annotations.SerializedName; @@ -19,12 +21,12 @@ import com.google.gson.annotations.SerializedName; * * @author Matthew Bowman - Initial contribution */ -public class UniFiSite { +public class UniFiSite implements HasId { - private final transient UniFiController controller; + private final transient UniFiControllerCache cache; - public UniFiSite(UniFiController controller) { - this.controller = controller; + public UniFiSite(final UniFiControllerCache cache) { + this.cache = cache; } @SerializedName("_id") @@ -34,6 +36,7 @@ public class UniFiSite { private String desc; + @Override public String getId() { return id; } @@ -46,12 +49,20 @@ public class UniFiSite { return desc; } - public boolean matchesName(String siteName) { + public UniFiControllerCache getCache() { + return cache; + } + + public boolean isSite(final UniFiSite site) { + return site != null && id.equals(site.getId()); + } + + public boolean matchesName(final String siteName) { return siteName.equalsIgnoreCase(desc) || siteName.equalsIgnoreCase(name) || siteName.equalsIgnoreCase(id); } @Override public String toString() { - return String.format("UniFiSite{name: '%s', desc: '%s'}", name, desc); + return String.format("UniFiSite{id: '%s', name: '%s', desc: '%s'}", id, name, desc); } } diff --git a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/model/UniFiUnknownClient.java b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/dto/UniFiUnknownClient.java similarity index 82% rename from bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/model/UniFiUnknownClient.java rename to bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/dto/UniFiUnknownClient.java index 215d8515f8b..ab0646e4514 100644 --- a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/model/UniFiUnknownClient.java +++ b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/dto/UniFiUnknownClient.java @@ -10,7 +10,9 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.unifi.internal.api.model; +package org.openhab.binding.unifi.internal.api.dto; + +import org.openhab.binding.unifi.internal.api.cache.UniFiControllerCache; /** * A {@link UniFiUnknownClient} represents an unknown {@link UniFiClient}. @@ -22,8 +24,8 @@ package org.openhab.binding.unifi.internal.api.model; */ public class UniFiUnknownClient extends UniFiClient { - public UniFiUnknownClient(UniFiController controller) { - super(controller); + public UniFiUnknownClient(final UniFiControllerCache cache) { + super(cache); } @Override diff --git a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/model/UniFiWiredClient.java b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/dto/UniFiWiredClient.java similarity index 80% rename from bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/model/UniFiWiredClient.java rename to bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/dto/UniFiWiredClient.java index f9130908027..62a0d4d4bc6 100644 --- a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/model/UniFiWiredClient.java +++ b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/dto/UniFiWiredClient.java @@ -10,7 +10,9 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.unifi.internal.api.model; +package org.openhab.binding.unifi.internal.api.dto; + +import org.openhab.binding.unifi.internal.api.cache.UniFiControllerCache; /** * A {@link UniFiWiredClient} represents a wired {@link UniFiClient}. @@ -23,8 +25,8 @@ public class UniFiWiredClient extends UniFiClient { private String swMac; - public UniFiWiredClient(UniFiController controller) { - super(controller); + public UniFiWiredClient(final UniFiControllerCache cache) { + super(cache); } @Override diff --git a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/model/UniFiWirelessClient.java b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/dto/UniFiWirelessClient.java similarity index 85% rename from bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/model/UniFiWirelessClient.java rename to bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/dto/UniFiWirelessClient.java index ecba2e6fcba..9cad57fe59f 100644 --- a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/model/UniFiWirelessClient.java +++ b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/dto/UniFiWirelessClient.java @@ -10,8 +10,9 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.unifi.internal.api.model; +package org.openhab.binding.unifi.internal.api.dto; +import org.openhab.binding.unifi.internal.api.cache.UniFiControllerCache; import org.openhab.binding.unifi.internal.api.util.UniFiTidyLowerCaseStringDeserializer; import com.google.gson.annotations.JsonAdapter; @@ -32,8 +33,8 @@ public class UniFiWirelessClient extends UniFiClient { private Integer rssi; - public UniFiWirelessClient(UniFiController controller) { - super(controller); + public UniFiWirelessClient(final UniFiControllerCache cache) { + super(cache); } @Override diff --git a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/dto/UniFiWlan.java b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/dto/UniFiWlan.java new file mode 100644 index 00000000000..1eaf52e32e5 --- /dev/null +++ b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/dto/UniFiWlan.java @@ -0,0 +1,95 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.unifi.internal.api.dto; + +import org.openhab.binding.unifi.internal.api.cache.UniFiControllerCache; + +import com.google.gson.annotations.SerializedName; + +/** + * @author Hilbrand Bouwkamp - Initial contribution + */ +public class UniFiWlan implements HasId { + + protected final transient UniFiControllerCache cache; + + @SerializedName("_id") + private String id; + + private String name; + + private boolean enabled; + + private String security; // ": "wpapsk", + private String wlanBand; // ": "both", + private String wpaEnc; // ": "ccmp", + private String wpaMode;// ": "wpa2", + private String xPassphrase; // : "1234", + private Boolean hideSsid; + private String siteId; + + public UniFiWlan(final UniFiControllerCache cache) { + this.cache = cache; + } + + @Override + public String getId() { + return id; + } + + public String getName() { + return name; + } + + public boolean isEnabled() { + return enabled; + } + + public UniFiSite getSite() { + return cache.getSite(siteId); + } + + public String getSecurity() { + return security; + } + + public String getWlanBand() { + return wlanBand; + } + + public String getWpaEnc() { + return wpaEnc; + } + + public String getWpaMode() { + return wpaMode; + } + + public String getXPassphrase() { + return xPassphrase; + } + + public boolean isHideSsid() { + return Boolean.TRUE.equals(hideSsid); + } + + @Override + public String toString() { + final String xPassphraseString = xPassphrase == null ? "" + : (xPassphrase.substring(0, Math.min(5, xPassphrase.length())) + "*".repeat(10)); + + return String.format( + "UniFiWlan{id: '%s', name: '%s', enable: '%b', security: '%s', wlanBand: '%s', wpaEnc: '%s', wpaMode: '%s', xPassphrase: '%s', hideSsid: '%b', site: '%s'}", + id, name, enabled, security, wlanBand, wpaEnc, wpaMode, xPassphraseString, hideSsid, getSite()); + } +} diff --git a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/model/UniFiController.java b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/model/UniFiController.java deleted file mode 100644 index 0a1d1144de2..00000000000 --- a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/model/UniFiController.java +++ /dev/null @@ -1,335 +0,0 @@ -/** - * Copyright (c) 2010-2022 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.unifi.internal.api.model; - -import java.util.Collection; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.eclipse.jetty.client.HttpClient; -import org.openhab.binding.unifi.internal.api.UniFiException; -import org.openhab.binding.unifi.internal.api.UniFiExpiredSessionException; -import org.openhab.binding.unifi.internal.api.UniFiNotAuthorizedException; -import org.openhab.binding.unifi.internal.api.cache.UniFiClientCache; -import org.openhab.binding.unifi.internal.api.cache.UniFiDeviceCache; -import org.openhab.binding.unifi.internal.api.cache.UniFiSiteCache; -import org.openhab.binding.unifi.internal.api.util.UniFiClientDeserializer; -import org.openhab.binding.unifi.internal.api.util.UniFiClientInstanceCreator; -import org.openhab.binding.unifi.internal.api.util.UniFiDeviceInstanceCreator; -import org.openhab.binding.unifi.internal.api.util.UniFiSiteInstanceCreator; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.google.gson.FieldNamingPolicy; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; - -/** - * The {@link UniFiController} is the main communication point with an external instance of the Ubiquiti Networks - * Controller Software. - * - * @author Matthew Bowman - Initial contribution - * @author Patrik Wimnell - Blocking / Unblocking client support - * @author Jacob Laursen - Fix online/blocked channels (broken by UniFi Controller 5.12.35) - */ -@NonNullByDefault -public class UniFiController { - - private final Logger logger = LoggerFactory.getLogger(UniFiController.class); - - private Map cidToIdCache = new ConcurrentHashMap(); - - private UniFiSiteCache sitesCache = new UniFiSiteCache(); - - private UniFiDeviceCache devicesCache = new UniFiDeviceCache(); - - private UniFiClientCache clientsCache = new UniFiClientCache(); - - private UniFiClientCache insightsCache = new UniFiClientCache(); - - private final HttpClient httpClient; - - private final String host; - - private final int port; - - private final String username; - - private final String password; - - private final boolean unifios; - - private String csrfToken; - - private final Gson gson; - - public UniFiController(HttpClient httpClient, String host, int port, String username, String password, - boolean unifios) { - this.httpClient = httpClient; - this.host = host; - this.port = port; - this.username = username; - this.password = password; - this.unifios = unifios; - this.csrfToken = ""; - UniFiSiteInstanceCreator siteInstanceCreator = new UniFiSiteInstanceCreator(this); - UniFiDeviceInstanceCreator deviceInstanceCreator = new UniFiDeviceInstanceCreator(this); - UniFiClientInstanceCreator clientInstanceCreator = new UniFiClientInstanceCreator(this); - this.gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) - .registerTypeAdapter(UniFiSite.class, siteInstanceCreator) - .registerTypeAdapter(UniFiDevice.class, deviceInstanceCreator) - .registerTypeAdapter(UniFiClient.class, new UniFiClientDeserializer()) - .registerTypeAdapter(UniFiUnknownClient.class, clientInstanceCreator) - .registerTypeAdapter(UniFiWiredClient.class, clientInstanceCreator) - .registerTypeAdapter(UniFiWirelessClient.class, clientInstanceCreator).create(); - } - - // Public API - - public void start() throws UniFiException { - if (unifios) { - obtainCsrfToken(); - } - - login(); - } - - public void stop() throws UniFiException { - logout(); - } - - public void obtainCsrfToken() throws UniFiException { - csrfToken = ""; - - UniFiControllerRequest req = newRequest(Void.class); - req.setPath("/"); - executeRequest(req); - } - - public void login() throws UniFiException { - UniFiControllerRequest req = newRequest(Void.class); - req.setPath(unifios ? "/api/auth/login" : "/api/login"); - req.setBodyParameter("username", username); - req.setBodyParameter("password", password); - // scurb: Changed strict = false to make blocking feature work - req.setBodyParameter("strict", false); - req.setBodyParameter("remember", false); - executeRequest(req); - } - - public void logout() throws UniFiException { - csrfToken = ""; - UniFiControllerRequest req = newRequest(Void.class); - req.setPath(unifios ? "/api/auth/logout" : "/logout"); - executeRequest(req); - } - - public void refresh() throws UniFiException { - synchronized (this) { - sitesCache = getSites(); - devicesCache = getDevices(); - clientsCache = getClients(); - insightsCache = getInsights(); - } - } - - // Site API - - public @Nullable UniFiSite getSite(@Nullable String id) { - UniFiSite site = null; - if (id != null && !id.isBlank()) { - synchronized (this) { - site = sitesCache.get(id); - } - if (site == null) { - logger.debug("Could not find a matching site for id = '{}'", id); - } - } - return site; - } - - // Device API - - public @Nullable UniFiDevice getDevice(@Nullable String id) { - UniFiDevice device = null; - if (id != null && !id.isBlank()) { - synchronized (this) { - device = devicesCache.get(id); - } - if (device == null) { - logger.debug("Could not find a matching device for id = '{}'", id); - } - } - return device; - } - - // Client API - - public @Nullable UniFiClient getClient(@Nullable String cid) { - UniFiClient client = null; - if (cid != null && !cid.isBlank()) { - // Prefer lookups through _id, until initialized use cid. - String id = cidToIdCache.get(cid); - synchronized (this) { - // mgb: first check active clients and fallback to insights if not found - client = clientsCache.get(id != null ? id : cid); - if (client == null) { - client = insightsCache.get(id != null ? id : cid); - } - } - if (client == null) { - logger.debug("Could not find a matching client for cid = {}", cid); - } else { - cidToIdCache.put(cid, client.id); - } - } - return client; - } - - protected void block(UniFiClient client, boolean blocked) throws UniFiException { - UniFiControllerRequest req = newRequest(Void.class); - req.setAPIPath("/api/s/" + client.getSite().getName() + "/cmd/stamgr"); - req.setBodyParameter("cmd", blocked ? "block-sta" : "unblock-sta"); - req.setBodyParameter("mac", client.getMac()); - executeRequest(req); - } - - protected void reconnect(UniFiClient client) throws UniFiException { - UniFiControllerRequest req = newRequest(Void.class); - req.setAPIPath("/api/s/" + client.getSite().getName() + "/cmd/stamgr"); - req.setBodyParameter("cmd", "kick-sta"); - req.setBodyParameter("mac", client.getMac()); - executeRequest(req); - } - - // Internal API - - private UniFiControllerRequest newRequest(Class responseType) { - return new UniFiControllerRequest<>(responseType, gson, httpClient, host, port, csrfToken, unifios); - } - - private @Nullable T executeRequest(UniFiControllerRequest request) throws UniFiException { - T result; - try { - result = request.execute(); - csrfToken = request.getCsrfToken(); - } catch (UniFiExpiredSessionException e) { - login(); - result = executeRequest(request); - } catch (UniFiNotAuthorizedException e) { - logger.warn("Not Authorized! Please make sure your controller credentials have administrator rights"); - result = null; - } - return result; - } - - private UniFiSiteCache getSites() throws UniFiException { - UniFiControllerRequest req = newRequest(UniFiSite[].class); - req.setAPIPath("/api/self/sites"); - UniFiSite[] sites = executeRequest(req); - UniFiSiteCache cache = new UniFiSiteCache(); - if (sites != null) { - logger.debug("Found {} UniFi Site(s): {}", sites.length, lazyFormatAsList(sites)); - for (UniFiSite site : sites) { - cache.put(site); - } - } - return cache; - } - - private UniFiDeviceCache getDevices() throws UniFiException { - UniFiDeviceCache cache = new UniFiDeviceCache(); - Collection sites = sitesCache.values(); - for (UniFiSite site : sites) { - cache.putAll(getDevices(site)); - } - return cache; - } - - private UniFiDeviceCache getDevices(UniFiSite site) throws UniFiException { - UniFiControllerRequest req = newRequest(UniFiDevice[].class); - req.setAPIPath("/api/s/" + site.getName() + "/stat/device"); - UniFiDevice[] devices = executeRequest(req); - UniFiDeviceCache cache = new UniFiDeviceCache(); - if (devices != null) { - logger.debug("Found {} UniFi Device(s): {}", devices.length, lazyFormatAsList(devices)); - for (UniFiDevice device : devices) { - cache.put(device); - } - } - return cache; - } - - private UniFiClientCache getClients() throws UniFiException { - UniFiClientCache cache = new UniFiClientCache(); - Collection sites = sitesCache.values(); - for (UniFiSite site : sites) { - cache.putAll(getClients(site)); - } - return cache; - } - - private UniFiClientCache getClients(UniFiSite site) throws UniFiException { - UniFiControllerRequest req = newRequest(UniFiClient[].class); - req.setAPIPath("/api/s/" + site.getName() + "/stat/sta"); - UniFiClient[] clients = executeRequest(req); - UniFiClientCache cache = new UniFiClientCache(); - if (clients != null) { - logger.debug("Found {} UniFi Client(s): {}", clients.length, lazyFormatAsList(clients)); - for (UniFiClient client : clients) { - cache.put(client); - } - } - return cache; - } - - private UniFiClientCache getInsights() throws UniFiException { - UniFiClientCache cache = new UniFiClientCache(); - Collection sites = sitesCache.values(); - for (UniFiSite site : sites) { - cache.putAll(getInsights(site)); - } - return cache; - } - - private UniFiClientCache getInsights(UniFiSite site) throws UniFiException { - UniFiControllerRequest req = newRequest(UniFiClient[].class); - req.setAPIPath("/api/s/" + site.getName() + "/stat/alluser"); - req.setQueryParameter("within", 168); // scurb: Changed to 7 days. - UniFiClient[] clients = executeRequest(req); - UniFiClientCache cache = new UniFiClientCache(); - if (clients != null) { - logger.debug("Found {} UniFi Insights(s): {}", clients.length, lazyFormatAsList(clients)); - for (UniFiClient client : clients) { - cache.put(client); - } - } - return cache; - } - - private static Object lazyFormatAsList(Object[] arr) { - return new Object() { - - @Override - public String toString() { - String value = ""; - for (Object o : arr) { - value += "\n - " + o.toString(); - } - return value; - } - }; - } -} diff --git a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/util/UniFiClientDeserializer.java b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/util/UniFiClientDeserializer.java index 874505a107b..c6362beb530 100644 --- a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/util/UniFiClientDeserializer.java +++ b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/util/UniFiClientDeserializer.java @@ -14,10 +14,12 @@ package org.openhab.binding.unifi.internal.api.util; import java.lang.reflect.Type; -import org.openhab.binding.unifi.internal.api.model.UniFiClient; -import org.openhab.binding.unifi.internal.api.model.UniFiUnknownClient; -import org.openhab.binding.unifi.internal.api.model.UniFiWiredClient; -import org.openhab.binding.unifi.internal.api.model.UniFiWirelessClient; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.unifi.internal.api.dto.UniFiClient; +import org.openhab.binding.unifi.internal.api.dto.UniFiUnknownClient; +import org.openhab.binding.unifi.internal.api.dto.UniFiWiredClient; +import org.openhab.binding.unifi.internal.api.dto.UniFiWirelessClient; import com.google.gson.JsonDeserializationContext; import com.google.gson.JsonDeserializer; @@ -33,21 +35,21 @@ import com.google.gson.JsonParseException; * * @author Matthew Bowman - Initial contribution */ +@NonNullByDefault public class UniFiClientDeserializer implements JsonDeserializer { private static final String PROPERTY_IS_WIRED = "is_wired"; @Override - public UniFiClient deserialize(JsonElement json, Type type, JsonDeserializationContext context) - throws JsonParseException { - JsonObject jsonObject = json.getAsJsonObject(); - JsonElement isWiredElement = jsonObject.get(PROPERTY_IS_WIRED); + public @Nullable UniFiClient deserialize(final JsonElement json, final Type type, + final JsonDeserializationContext context) throws JsonParseException { + final JsonObject jsonObject = json.getAsJsonObject(); + final JsonElement wiredElement = jsonObject.get(PROPERTY_IS_WIRED); // mgb: if the "is_wired "property is missing, the client is unknown - if (isWiredElement == null) { + if (wiredElement == null) { return context.deserialize(json, UniFiUnknownClient.class); } - boolean isWired = isWiredElement.getAsBoolean(); - if (isWired) { + if (wiredElement.getAsBoolean()) { return context.deserialize(json, UniFiWiredClient.class); } return context.deserialize(json, UniFiWirelessClient.class); diff --git a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/util/UniFiClientInstanceCreator.java b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/util/UniFiClientInstanceCreator.java index 6e902be39bb..94aa924d12c 100644 --- a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/util/UniFiClientInstanceCreator.java +++ b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/util/UniFiClientInstanceCreator.java @@ -14,13 +14,16 @@ package org.openhab.binding.unifi.internal.api.util; import java.lang.reflect.Type; -import org.openhab.binding.unifi.internal.api.model.UniFiClient; -import org.openhab.binding.unifi.internal.api.model.UniFiController; -import org.openhab.binding.unifi.internal.api.model.UniFiUnknownClient; -import org.openhab.binding.unifi.internal.api.model.UniFiWiredClient; -import org.openhab.binding.unifi.internal.api.model.UniFiWirelessClient; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.unifi.internal.api.cache.UniFiControllerCache; +import org.openhab.binding.unifi.internal.api.dto.UniFiClient; +import org.openhab.binding.unifi.internal.api.dto.UniFiUnknownClient; +import org.openhab.binding.unifi.internal.api.dto.UniFiWiredClient; +import org.openhab.binding.unifi.internal.api.dto.UniFiWirelessClient; import com.google.gson.InstanceCreator; +import com.google.gson.JsonSyntaxException; /** * The {@link UniFiClientInstanceCreator} creates instances of {@link UniFiClient}s during the JSON unmarshalling of @@ -28,25 +31,27 @@ import com.google.gson.InstanceCreator; * * @author Matthew Bowman - Initial contribution */ +@NonNullByDefault public class UniFiClientInstanceCreator implements InstanceCreator { - private final UniFiController controller; + private final UniFiControllerCache cache; - public UniFiClientInstanceCreator(UniFiController controller) { - this.controller = controller; + public UniFiClientInstanceCreator(final UniFiControllerCache cache) { + this.cache = cache; } @Override - public UniFiClient createInstance(Type type) { + public UniFiClient createInstance(final @Nullable Type type) { if (UniFiUnknownClient.class.equals(type)) { - return new UniFiUnknownClient(controller); + return new UniFiUnknownClient(cache); } if (UniFiWirelessClient.class.equals(type)) { - return new UniFiWirelessClient(controller); + return new UniFiWirelessClient(cache); } if (UniFiWiredClient.class.equals(type)) { - return new UniFiWiredClient(controller); + return new UniFiWiredClient(cache); + } else { + throw new JsonSyntaxException("Expected a UniFi Client type, but got " + type); } - return null; } } diff --git a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/util/UniFiDeviceInstanceCreator.java b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/util/UniFiDeviceInstanceCreator.java index 39ce9361ae1..0b74a07fa8c 100644 --- a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/util/UniFiDeviceInstanceCreator.java +++ b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/util/UniFiDeviceInstanceCreator.java @@ -14,28 +14,30 @@ package org.openhab.binding.unifi.internal.api.util; import java.lang.reflect.Type; -import org.openhab.binding.unifi.internal.api.model.UniFiController; -import org.openhab.binding.unifi.internal.api.model.UniFiDevice; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.unifi.internal.api.cache.UniFiControllerCache; +import org.openhab.binding.unifi.internal.api.dto.UniFiDevice; import com.google.gson.InstanceCreator; /** - * * The {@link UniFiDeviceInstanceCreator} creates instances of {@link UniFiDevice}s during the JSON unmarshalling of * controller responses. * * @author Matthew Bowman - Initial contribution */ +@NonNullByDefault public class UniFiDeviceInstanceCreator implements InstanceCreator { - private final UniFiController controller; + private final UniFiControllerCache cache; - public UniFiDeviceInstanceCreator(UniFiController controller) { - this.controller = controller; + public UniFiDeviceInstanceCreator(final UniFiControllerCache cache) { + this.cache = cache; } @Override - public UniFiDevice createInstance(Type type) { - return new UniFiDevice(controller); + public UniFiDevice createInstance(final @Nullable Type type) { + return new UniFiDevice(cache); } } diff --git a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/util/UniFiSiteInstanceCreator.java b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/util/UniFiSiteInstanceCreator.java index f3cb07d7ea4..fc10ac1f31d 100644 --- a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/util/UniFiSiteInstanceCreator.java +++ b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/util/UniFiSiteInstanceCreator.java @@ -14,10 +14,13 @@ package org.openhab.binding.unifi.internal.api.util; import java.lang.reflect.Type; -import org.openhab.binding.unifi.internal.api.model.UniFiController; -import org.openhab.binding.unifi.internal.api.model.UniFiSite; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.unifi.internal.api.cache.UniFiControllerCache; +import org.openhab.binding.unifi.internal.api.dto.UniFiSite; import com.google.gson.InstanceCreator; +import com.google.gson.JsonSyntaxException; /** * @@ -26,16 +29,21 @@ import com.google.gson.InstanceCreator; * * @author Matthew Bowman - Initial contribution */ +@NonNullByDefault public class UniFiSiteInstanceCreator implements InstanceCreator { - private final UniFiController controller; + private final UniFiControllerCache cache; - public UniFiSiteInstanceCreator(UniFiController controller) { - this.controller = controller; + public UniFiSiteInstanceCreator(final UniFiControllerCache cache) { + this.cache = cache; } @Override - public UniFiSite createInstance(Type type) { - return new UniFiSite(controller); + public UniFiSite createInstance(final @Nullable Type type) { + if (UniFiSite.class.equals(type)) { + return new UniFiSite(cache); + } else { + throw new JsonSyntaxException("Expected a UniFiSite type, but got " + type); + } } } diff --git a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/util/UniFiTidyLowerCaseStringDeserializer.java b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/util/UniFiTidyLowerCaseStringDeserializer.java index c628f2787c7..77caffcd08c 100644 --- a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/util/UniFiTidyLowerCaseStringDeserializer.java +++ b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/util/UniFiTidyLowerCaseStringDeserializer.java @@ -14,6 +14,9 @@ package org.openhab.binding.unifi.internal.api.util; import java.lang.reflect.Type; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + import com.google.gson.JsonDeserializationContext; import com.google.gson.JsonDeserializer; import com.google.gson.JsonElement; @@ -26,12 +29,13 @@ import com.google.gson.JsonParseException; * * @author Matthew Bowman - Initial contribution */ +@NonNullByDefault public class UniFiTidyLowerCaseStringDeserializer implements JsonDeserializer { @Override - public String deserialize(JsonElement json, Type type, JsonDeserializationContext context) - throws JsonParseException { - String s = json.getAsJsonPrimitive().getAsString(); + public @Nullable String deserialize(final JsonElement json, final Type type, + final JsonDeserializationContext context) throws JsonParseException { + final String s = json.getAsJsonPrimitive().getAsString(); return s.trim().toLowerCase(); } } diff --git a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/util/UniFiTimestampDeserializer.java b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/util/UniFiTimestampDeserializer.java index ae826d0d6d7..4e7ce74b91d 100644 --- a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/util/UniFiTimestampDeserializer.java +++ b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/util/UniFiTimestampDeserializer.java @@ -13,7 +13,10 @@ package org.openhab.binding.unifi.internal.api.util; import java.lang.reflect.Type; -import java.util.Calendar; +import java.time.Instant; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; import com.google.gson.JsonDeserializationContext; import com.google.gson.JsonDeserializer; @@ -25,14 +28,15 @@ import com.google.gson.JsonElement; * * @author Matthew Bowman - Initial contribution */ -public class UniFiTimestampDeserializer implements JsonDeserializer { +@NonNullByDefault +public class UniFiTimestampDeserializer implements JsonDeserializer { @Override - public Calendar deserialize(JsonElement json, Type type, JsonDeserializationContext context) { - String text = json.getAsJsonPrimitive().getAsString(); - long millis = Long.valueOf(text) * 1000; - Calendar cal = Calendar.getInstance(); - cal.setTimeInMillis(millis); - return cal; + public @Nullable Instant deserialize(final JsonElement json, final Type type, + final JsonDeserializationContext context) { + final String text = json.getAsJsonPrimitive().getAsString(); + final long millis = Long.valueOf(text) * 1000; + + return Instant.ofEpochMilli(millis); } } diff --git a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/util/UniFiWlanInstanceCreator.java b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/util/UniFiWlanInstanceCreator.java new file mode 100644 index 00000000000..6d14fa1ada6 --- /dev/null +++ b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/util/UniFiWlanInstanceCreator.java @@ -0,0 +1,48 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.unifi.internal.api.util; + +import java.lang.reflect.Type; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.unifi.internal.api.cache.UniFiControllerCache; +import org.openhab.binding.unifi.internal.api.dto.UniFiWlan; + +import com.google.gson.InstanceCreator; +import com.google.gson.JsonSyntaxException; + +/** + * The {@link UniFiWlanInstanceCreator} creates instances of {@link UniFiWlan}s during the JSON unmarshalling of + * controller responses. + * + * @author Hilbrand Bouwkamp - Initial contribution + */ +@NonNullByDefault +public class UniFiWlanInstanceCreator implements InstanceCreator { + + private final UniFiControllerCache cache; + + public UniFiWlanInstanceCreator(final UniFiControllerCache cache) { + this.cache = cache; + } + + @Override + public UniFiWlan createInstance(final @Nullable Type type) { + if (UniFiWlan.class.equals(type)) { + return new UniFiWlan(cache); + } else { + throw new JsonSyntaxException("Expected a UniFiWlan type, but got " + type); + } + } +} diff --git a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/handler/UniFiBaseThingHandler.java b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/handler/UniFiBaseThingHandler.java index b9c1a566441..5b8e41bf28b 100644 --- a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/handler/UniFiBaseThingHandler.java +++ b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/handler/UniFiBaseThingHandler.java @@ -12,23 +12,26 @@ */ package org.openhab.binding.unifi.internal.handler; -import static org.openhab.core.thing.ThingStatus.*; +import static org.openhab.core.thing.ThingStatus.OFFLINE; +import static org.openhab.core.thing.ThingStatus.ONLINE; import static org.openhab.core.types.RefreshType.REFRESH; import java.lang.reflect.ParameterizedType; +import java.util.Optional; -import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.unifi.internal.api.UniFiController; import org.openhab.binding.unifi.internal.api.UniFiException; -import org.openhab.binding.unifi.internal.api.model.UniFiController; +import org.openhab.binding.unifi.internal.api.cache.UniFiControllerCache; import org.openhab.core.thing.Bridge; -import org.openhab.core.thing.Channel; import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.Thing; import org.openhab.core.thing.ThingStatusDetail; import org.openhab.core.thing.binding.BaseThingHandler; import org.openhab.core.types.Command; +import org.openhab.core.types.State; +import org.openhab.core.types.UnDefType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -43,29 +46,32 @@ public abstract class UniFiBaseThingHandler extends BaseThingHandler { private final Logger logger = LoggerFactory.getLogger(UniFiBaseThingHandler.class); - public UniFiBaseThingHandler(Thing thing) { + public UniFiBaseThingHandler(final Thing thing) { super(thing); } @Override @SuppressWarnings("unchecked") public final void initialize() { - Bridge bridge = getBridge(); + final Bridge bridge = getBridge(); if (bridge == null || bridge.getHandler() == null || !(bridge.getHandler() instanceof UniFiControllerThingHandler)) { updateStatus(OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, - "You must choose a UniFi Controller for this thing."); - return; - } - if (bridge.getStatus() == OFFLINE) { - updateStatus(OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, "The UniFi Controller is currently offline."); + "@text/error.thing.offline.configuration_error"); return; } // mgb: derive the config class from the generic type - Class clazz = (Class) (((ParameterizedType) getClass().getGenericSuperclass()) + final Class clazz = (Class) (((ParameterizedType) getClass().getGenericSuperclass()) .getActualTypeArguments()[1]); - C config = (C) getConfigAs(clazz); - initialize(config); + final C config = (C) getConfigAs(clazz); + if (initialize(config)) { + if (bridge.getStatus() == OFFLINE) { + updateStatus(OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, "@text/error.thing.offline.bridge_offline"); + return; + } else { + updateStatus(ONLINE); + } + } } /** @@ -75,7 +81,7 @@ public abstract class UniFiBaseThingHandler extends BaseThingHandler { */ @SuppressWarnings("null") private final @Nullable UniFiController getController() { - Bridge bridge = getBridge(); + final Bridge bridge = getBridge(); if (bridge != null && bridge.getHandler() != null && (bridge.getHandler() instanceof UniFiControllerThingHandler)) { return ((UniFiControllerThingHandler) bridge.getHandler()).getController(); @@ -83,51 +89,109 @@ public abstract class UniFiBaseThingHandler extends BaseThingHandler { return null; } + private @Nullable E getEntity() { + final UniFiController controller = getController(); + return controller == null ? null : getEntity(controller.getCache()); + } + @Override - public final void handleCommand(ChannelUID channelUID, Command command) { + public final void handleCommand(final ChannelUID channelUID, final Command command) { logger.debug("Handling command = {} for channel = {}", command, channelUID); // mgb: only handle commands if we're ONLINE if (getThing().getStatus() == ONLINE) { - UniFiController controller = getController(); - if (controller != null) { - E entity = getEntity(controller); - if (entity != null) { - if (command == REFRESH) { - refreshChannel(entity, channelUID); - } else { - try { - handleCommand(entity, channelUID, command); - } catch (UniFiException e) { - logger.warn("Unexpected error handling command = {} for channel = {} : {}", command, - channelUID, e.getMessage()); + final E entity = getEntity(); + final UniFiController controller = getController(); + + if (command == REFRESH) { + updateState(entity, channelUID); + } else { + if (entity != null && controller != null) { + try { + if (!handleCommand(controller, entity, channelUID, command)) { + logger.info("Ignoring unsupported command = {} for channel = {}", command, channelUID); } + } catch (final UniFiException e) { + logger.info("Unexpected error handling command = {} for channel = {} : {}", command, channelUID, + e.getMessage()); } + } else { + logger.info( + "Could not handle command {} for channel = {} because no entity/controller data available.", + command, channelUID); } } + } else { + logger.info("Could not handle command {} for channel = {} because thing not online.", command, channelUID); } } protected final void refresh() { // mgb: only refresh if we're ONLINE if (getThing().getStatus() == ONLINE) { - UniFiController controller = getController(); - if (controller != null) { - E entity = getEntity(controller); - if (entity != null) { - for (Channel channel : getThing().getChannels()) { - ChannelUID channelUID = channel.getUID(); - refreshChannel(entity, channelUID); - } - } - } + final E entity = getEntity(); + + getThing().getChannels().forEach(channel -> updateState(entity, channel.getUID())); } } - protected abstract void initialize(@NonNull C config); + private void updateState(final E entity, final ChannelUID channelUID) { + final String channelId = channelUID.getId(); + final State state = Optional.ofNullable(entity).map(e -> getChannelState(e, channelId)) + .orElseGet(() -> getDefaultState(channelId)); - protected abstract @Nullable E getEntity(UniFiController controller); + if (state != UnDefType.NULL) { + updateState(channelUID, state); + } + } - protected abstract void refreshChannel(E entity, ChannelUID channelUID); + /** + * Additional sub class specific initialization. + * If initialization is unsuccessful it should set the thing status and return false. + * if it was successful it should return true + * + * @param config thing configuration + * @return true if initialization was successful + */ + protected abstract boolean initialize(C config); - protected abstract void handleCommand(E entity, ChannelUID channelUID, Command command) throws UniFiException; + /** + * Returns the default state if no data available. Default implementation return {@link UnDefType#UNDEF}. + * + * @param channelId channel to update + * @return default state + */ + protected State getDefaultState(final String channelId) { + return UnDefType.UNDEF; + } + + /** + * Returns the cached UniFi entity object related to this thing. + * + * @param cache cache to get the cached entity from + * @return cached entry or null if not exists + */ + protected abstract @Nullable E getEntity(UniFiControllerCache cache); + + /** + * Returns the state to set for the given channel. If {@link UnDefType#NULL} is returned it means the channel should + * not be updated. + * + * @param entity UniFi entity object to get the state information from + * @param channelId Channel to update + * @return state to set or {@link UnDefType#NULL} if channel state should not be updated. + */ + protected abstract State getChannelState(E entity, String channelId); + + /** + * Send the given command to the UniFi controller. + * + * @param controller controller object to use to send the command to the UniFi controller + * @param entity data object of the thing to send command to + * @param channelUID channel the command is from + * @param command command to send + * @return true if command was send + * @throws UniFiException + */ + protected abstract boolean handleCommand(UniFiController controller, E entity, ChannelUID channelUID, + Command command) throws UniFiException; } diff --git a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/handler/UniFiClientThingHandler.java b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/handler/UniFiClientThingHandler.java index d8fbe8bd203..3d610285cf3 100644 --- a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/handler/UniFiClientThingHandler.java +++ b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/handler/UniFiClientThingHandler.java @@ -12,32 +12,47 @@ */ package org.openhab.binding.unifi.internal.handler; -import static org.openhab.binding.unifi.internal.UniFiBindingConstants.*; -import static org.openhab.core.thing.ThingStatus.*; +import static org.openhab.binding.unifi.internal.UniFiBindingConstants.CHANNEL_AP; +import static org.openhab.binding.unifi.internal.UniFiBindingConstants.CHANNEL_BLOCKED; +import static org.openhab.binding.unifi.internal.UniFiBindingConstants.CHANNEL_CMD; +import static org.openhab.binding.unifi.internal.UniFiBindingConstants.CHANNEL_CMD_RECONNECT; +import static org.openhab.binding.unifi.internal.UniFiBindingConstants.CHANNEL_ESSID; +import static org.openhab.binding.unifi.internal.UniFiBindingConstants.CHANNEL_EXPERIENCE; +import static org.openhab.binding.unifi.internal.UniFiBindingConstants.CHANNEL_GUEST; +import static org.openhab.binding.unifi.internal.UniFiBindingConstants.CHANNEL_IP_ADDRESS; +import static org.openhab.binding.unifi.internal.UniFiBindingConstants.CHANNEL_LAST_SEEN; +import static org.openhab.binding.unifi.internal.UniFiBindingConstants.CHANNEL_MAC_ADDRESS; +import static org.openhab.binding.unifi.internal.UniFiBindingConstants.CHANNEL_ONLINE; +import static org.openhab.binding.unifi.internal.UniFiBindingConstants.CHANNEL_RECONNECT; +import static org.openhab.binding.unifi.internal.UniFiBindingConstants.CHANNEL_RSSI; +import static org.openhab.binding.unifi.internal.UniFiBindingConstants.CHANNEL_SITE; +import static org.openhab.binding.unifi.internal.UniFiBindingConstants.CHANNEL_UPTIME; +import static org.openhab.core.thing.ThingStatus.OFFLINE; import static org.openhab.core.thing.ThingStatusDetail.CONFIGURATION_ERROR; +import java.time.Instant; import java.time.ZoneId; import java.time.ZonedDateTime; -import java.util.Calendar; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.unifi.internal.UniFiBindingConstants; import org.openhab.binding.unifi.internal.UniFiClientThingConfig; +import org.openhab.binding.unifi.internal.api.UniFiController; import org.openhab.binding.unifi.internal.api.UniFiException; -import org.openhab.binding.unifi.internal.api.model.UniFiClient; -import org.openhab.binding.unifi.internal.api.model.UniFiController; -import org.openhab.binding.unifi.internal.api.model.UniFiDevice; -import org.openhab.binding.unifi.internal.api.model.UniFiSite; -import org.openhab.binding.unifi.internal.api.model.UniFiWiredClient; -import org.openhab.binding.unifi.internal.api.model.UniFiWirelessClient; +import org.openhab.binding.unifi.internal.api.cache.UniFiControllerCache; +import org.openhab.binding.unifi.internal.api.dto.UniFiClient; +import org.openhab.binding.unifi.internal.api.dto.UniFiDevice; +import org.openhab.binding.unifi.internal.api.dto.UniFiSite; +import org.openhab.binding.unifi.internal.api.dto.UniFiWiredClient; +import org.openhab.binding.unifi.internal.api.dto.UniFiWirelessClient; import org.openhab.core.library.types.DateTimeType; import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.OnOffType; +import org.openhab.core.library.types.QuantityType; import org.openhab.core.library.types.StringType; +import org.openhab.core.library.unit.Units; import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.Thing; -import org.openhab.core.thing.ThingTypeUID; import org.openhab.core.types.Command; import org.openhab.core.types.State; import org.openhab.core.types.UnDefType; @@ -54,35 +69,30 @@ import org.slf4j.LoggerFactory; @NonNullByDefault public class UniFiClientThingHandler extends UniFiBaseThingHandler { - public static boolean supportsThingType(ThingTypeUID thingTypeUID) { - return UniFiBindingConstants.THING_TYPE_WIRELESS_CLIENT.equals(thingTypeUID); - } - private final Logger logger = LoggerFactory.getLogger(UniFiClientThingHandler.class); private UniFiClientThingConfig config = new UniFiClientThingConfig(); - public UniFiClientThingHandler(Thing thing) { + public UniFiClientThingHandler(final Thing thing) { super(thing); } @Override - protected synchronized void initialize(UniFiClientThingConfig config) { + protected boolean initialize(final UniFiClientThingConfig config) { // mgb: called when the config changes logger.debug("Initializing the UniFi Client Handler with config = {}", config); if (!config.isValid()) { - updateStatus(OFFLINE, CONFIGURATION_ERROR, - "You must define a MAC address, IP address, hostname or alias for this thing."); - return; + updateStatus(OFFLINE, CONFIGURATION_ERROR, "@text/error.thing.client.offline.configuration_error"); + return false; } this.config = config; - updateStatus(ONLINE); + return true; } - private static boolean belongsToSite(UniFiClient client, String siteName) { + private static boolean belongsToSite(final UniFiClient client, final String siteName) { boolean result = true; // mgb: assume true = proof by contradiction if (!siteName.isEmpty()) { - UniFiSite site = client.getSite(); + final UniFiSite site = client.getSite(); // mgb: if the 'site' can't be found or the name doesn't match... if (site == null || !site.matchesName(siteName)) { // mgb: ... then the client doesn't belong to this thing's configured 'site' and we 'filter' it @@ -93,8 +103,8 @@ public class UniFiClientThingHandler extends UniFiBaseThingHandler(0, Units.PERCENT); break; case CHANNEL_LAST_SEEN: // mgb: lastSeen should keep the last state no matter what @@ -126,34 +140,35 @@ public class UniFiClientThingHandler extends UniFiBaseThingHandler(client.getExperience(), Units.PERCENT); + } + break; + default: // mgb: additional wired client channels if (client.isWired() && (client instanceof UniFiWiredClient)) { - state = getWiredChannelState((UniFiWiredClient) client, clientHome, channelID); + state = getWiredChannelState((UniFiWiredClient) client, channelId, state); } // mgb: additional wireless client channels else if (client.isWireless() && (client instanceof UniFiWirelessClient)) { - state = getWirelessChannelState((UniFiWirelessClient) client, clientHome, channelID); + state = getWirelessChannelState((UniFiWirelessClient) client, channelId, state); } break; } - // mgb: only non null states get updates - if (state != UnDefType.NULL) { - updateState(channelID, state); - } - } - - private State getWiredChannelState(UniFiWiredClient client, boolean clientHome, String channelID) { - State state = UnDefType.NULL; return state; } - private State getWirelessChannelState(UniFiWirelessClient client, boolean clientHome, String channelID) { - State state = UnDefType.NULL; - switch (channelID) { + private State getWiredChannelState(final UniFiWiredClient client, final String channelId, + final State defaultState) { + return defaultState; + } + + private State getWirelessChannelState(final UniFiWirelessClient client, final String channelId, + final State defaultState) { + State state = defaultState; + switch (channelId) { // :ap case CHANNEL_AP: - UniFiDevice device = client.getDevice(); - if (clientHome && device != null && device.getName() != null && !device.getName().isBlank()) { + final UniFiDevice device = client.getDevice(); + if (device != null && device.getName() != null && !device.getName().isBlank()) { state = StringType.valueOf(device.getName()); } break; // :essid case CHANNEL_ESSID: - if (clientHome && client.getEssid() != null && !client.getEssid().isBlank()) { + if (client.getEssid() != null && !client.getEssid().isBlank()) { state = StringType.valueOf(client.getEssid()); } break; // :rssi case CHANNEL_RSSI: - if (clientHome && client.getRssi() != null) { + if (client.getRssi() != null) { state = new DecimalType(client.getRssi()); } break; // :reconnect case CHANNEL_RECONNECT: - // nop - read-only channel + // nop - trigger channel so it's always OFF by default + state = OnOffType.OFF; break; } return state; } @Override - protected void handleCommand(UniFiClient client, ChannelUID channelUID, Command command) throws UniFiException { - String channelID = channelUID.getIdWithoutGroup(); + protected boolean handleCommand(final UniFiController controller, final UniFiClient client, + final ChannelUID channelUID, final Command command) throws UniFiException { + final String channelID = channelUID.getIdWithoutGroup(); switch (channelID) { case CHANNEL_BLOCKED: - handleBlockedCommand(client, channelUID, command); - break; + return handleBlockedCommand(controller, client, channelUID, command); + case CHANNEL_CMD: + return handleReconnectCommand(controller, client, channelUID, command); case CHANNEL_RECONNECT: - handleReconnectCommand(client, channelUID, command); - break; + return handleReconnectSwitch(controller, client, channelUID, command); default: - logger.warn("Ignoring unsupported command = {} for channel = {}", command, channelUID); + return false; } } - private void handleBlockedCommand(UniFiClient client, ChannelUID channelUID, Command command) - throws UniFiException { + private boolean handleBlockedCommand(final UniFiController controller, final UniFiClient client, + final ChannelUID channelUID, final Command command) throws UniFiException { if (command instanceof OnOffType) { - client.block(command == OnOffType.ON); + controller.block(client, command == OnOffType.ON); + refresh(); + return true; + } + return false; + } + + private boolean handleReconnectCommand(final UniFiController controller, final UniFiClient client, + final ChannelUID channelUID, final Command command) throws UniFiException { + if (command instanceof StringType && CHANNEL_CMD_RECONNECT.equalsIgnoreCase(command.toFullString())) { + controller.reconnect(client); + return true; } else { - logger.warn("Ignoring unsupported command = {} for channel = {} - valid commands types are: OnOffType", - command, channelUID); + logger.info("Unknown command '{}' given to wireless client thing '{}': client {}", command, + getThing().getUID(), client); + return false; } } - private void handleReconnectCommand(UniFiClient client, ChannelUID channelUID, Command command) - throws UniFiException { - if (command instanceof OnOffType) { - if (command == OnOffType.ON) { - client.reconnect(); - updateState(channelUID, OnOffType.OFF); - } - } else { - logger.warn("Ignoring unsupported command = {} for channel = {} - valid commands types are: OnOffType", - command, channelUID); + private boolean handleReconnectSwitch(final UniFiController controller, final UniFiClient client, + final ChannelUID channelUID, final Command command) throws UniFiException { + if (command instanceof OnOffType && command == OnOffType.ON) { + controller.reconnect(client); + updateState(channelUID, OnOffType.OFF); + refresh(); + return true; } + return false; } } diff --git a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/handler/UniFiControllerThingHandler.java b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/handler/UniFiControllerThingHandler.java index 562704a53be..6c799375f5c 100644 --- a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/handler/UniFiControllerThingHandler.java +++ b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/handler/UniFiControllerThingHandler.java @@ -14,29 +14,32 @@ package org.openhab.binding.unifi.internal.handler; import static org.openhab.core.thing.ThingStatus.OFFLINE; import static org.openhab.core.thing.ThingStatus.ONLINE; -import static org.openhab.core.thing.ThingStatusDetail.*; +import static org.openhab.core.thing.ThingStatus.UNKNOWN; +import static org.openhab.core.thing.ThingStatusDetail.COMMUNICATION_ERROR; +import static org.openhab.core.thing.ThingStatusDetail.CONFIGURATION_ERROR; +import java.util.Collection; +import java.util.List; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jetty.client.HttpClient; -import org.openhab.binding.unifi.internal.UniFiBindingConstants; import org.openhab.binding.unifi.internal.UniFiControllerThingConfig; import org.openhab.binding.unifi.internal.api.UniFiCommunicationException; +import org.openhab.binding.unifi.internal.api.UniFiController; import org.openhab.binding.unifi.internal.api.UniFiException; import org.openhab.binding.unifi.internal.api.UniFiInvalidCredentialsException; import org.openhab.binding.unifi.internal.api.UniFiInvalidHostException; import org.openhab.binding.unifi.internal.api.UniFiSSLException; -import org.openhab.binding.unifi.internal.api.model.UniFiController; import org.openhab.core.thing.Bridge; import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.ThingStatus; import org.openhab.core.thing.ThingStatusDetail; import org.openhab.core.thing.ThingStatusInfo; -import org.openhab.core.thing.ThingTypeUID; import org.openhab.core.thing.binding.BaseBridgeHandler; +import org.openhab.core.thing.binding.ThingHandlerService; import org.openhab.core.thing.binding.builder.ThingStatusInfoBuilder; import org.openhab.core.types.Command; import org.slf4j.Logger; @@ -51,17 +54,10 @@ import org.slf4j.LoggerFactory; @NonNullByDefault public class UniFiControllerThingHandler extends BaseBridgeHandler { - public static boolean supportsThingType(ThingTypeUID thingTypeUID) { - return UniFiBindingConstants.THING_TYPE_CONTROLLER.equals(thingTypeUID); - } - - private static final String STATUS_DESCRIPTION_COMMUNICATION_ERROR = "Error communicating with the UniFi controller"; - - private static final String STATUS_DESCRIPTION_SSL_ERROR = "Error establishing an SSL connection with the UniFi controller"; - - private static final String STATUS_DESCRIPTION_INVALID_CREDENTIALS = "Invalid username and/or password - please double-check your configuration"; - - private static final String STATUS_DESCRIPTION_INVALID_HOSTNAME = "Invalid hostname - please double-check your configuration"; + private static final String STATUS_DESCRIPTION_COMMUNICATION_ERROR = "@text/error.bridge.offline.communication_error"; + private static final String STATUS_DESCRIPTION_SSL_ERROR = "@text/error.bridge.offline.ssl_error"; + private static final String STATUS_DESCRIPTION_INVALID_CREDENTIALS = "@text/error.bridge.offline.invalid_credentials"; + private static final String STATUS_DESCRIPTION_INVALID_HOSTNAME = "@text/error.bridge.offline.invalid_hostname"; private final Logger logger = LoggerFactory.getLogger(UniFiControllerThingHandler.class); @@ -73,7 +69,7 @@ public class UniFiControllerThingHandler extends BaseBridgeHandler { private final HttpClient httpClient; - public UniFiControllerThingHandler(Bridge bridge, HttpClient httpClient) { + public UniFiControllerThingHandler(final Bridge bridge, final HttpClient httpClient) { super(bridge); this.httpClient = httpClient; } @@ -81,40 +77,28 @@ public class UniFiControllerThingHandler extends BaseBridgeHandler { // Public API @Override - public void initialize() { - // mgb: called when the config changes - cancelRefreshJob(); - config = getConfig().as(UniFiControllerThingConfig.class); - logger.debug("Initializing the UniFi Controller Handler with config = {}", config); - try { - controller = new UniFiController(httpClient, config.getHost(), config.getPort(), config.getUsername(), - config.getPassword(), config.isUniFiOS()); - controller.start(); - updateStatus(ONLINE); - } catch (UniFiInvalidHostException e) { - updateStatus(OFFLINE, CONFIGURATION_ERROR, STATUS_DESCRIPTION_INVALID_HOSTNAME); - } catch (UniFiCommunicationException e) { - updateStatus(OFFLINE, COMMUNICATION_ERROR, STATUS_DESCRIPTION_COMMUNICATION_ERROR); - } catch (UniFiSSLException e) { - updateStatus(OFFLINE, CONFIGURATION_ERROR, STATUS_DESCRIPTION_SSL_ERROR); - } catch (UniFiInvalidCredentialsException e) { - updateStatus(OFFLINE, CONFIGURATION_ERROR, STATUS_DESCRIPTION_INVALID_CREDENTIALS); - } catch (UniFiException e) { - logger.error("Unknown error while configuring the UniFi Controller", e); - updateStatus(OFFLINE, CONFIGURATION_ERROR, e.getMessage()); - } + public Collection> getServices() { + return List.of(UniFiThingDiscoveryService.class); } @Override - protected void updateStatus(ThingStatus status, ThingStatusDetail statusDetail, @Nullable String description) { - if (status == ONLINE || (status == OFFLINE && statusDetail == COMMUNICATION_ERROR)) { - scheduleRefreshJob(); - } else if (status == OFFLINE && statusDetail == CONFIGURATION_ERROR) { - cancelRefreshJob(); - } + public void initialize() { + config = getConfigAs(UniFiControllerThingConfig.class); + logger.debug("Initializing the UniFi Controller Handler with config = {}", config); + final UniFiController uc = new UniFiController(httpClient, config.getHost(), config.getPort(), + config.getUsername(), config.getPassword(), config.isUniFiOS()); + + controller = uc; + updateStatus(UNKNOWN); + scheduler.schedule(() -> start(uc), 10, TimeUnit.MILLISECONDS); + } + + @Override + protected void updateStatus(final ThingStatus status, final ThingStatusDetail statusDetail, + @Nullable final String description) { // mgb: update the status only if it's changed - ThingStatusInfo statusInfo = ThingStatusInfoBuilder.create(status, statusDetail).withDescription(description) - .build(); + final ThingStatusInfo statusInfo = ThingStatusInfoBuilder.create(status, statusDetail) + .withDescription(description).build(); if (!statusInfo.equals(getThing().getStatusInfo())) { super.updateStatus(status, statusDetail, description); } @@ -126,7 +110,7 @@ public class UniFiControllerThingHandler extends BaseBridgeHandler { if (controller != null) { try { controller.stop(); - } catch (UniFiException e) { + } catch (final UniFiException e) { // mgb: nop as we're in dispose } controller = null; @@ -134,7 +118,7 @@ public class UniFiControllerThingHandler extends BaseBridgeHandler { } @Override - public void handleCommand(ChannelUID channelUID, Command command) { + public void handleCommand(final ChannelUID channelUID, final Command command) { // nop - read-only binding logger.warn("Ignoring command = {} for channel = {} - the UniFi binding is read-only!", command, channelUID); } @@ -143,26 +127,39 @@ public class UniFiControllerThingHandler extends BaseBridgeHandler { return controller; } - public int getRefreshInterval() { - return config.getRefresh(); - } - // Private API - private void scheduleRefreshJob() { - synchronized (this) { - if (refreshJob == null) { - logger.debug("Scheduling refresh job every {}s", config.getRefresh()); - refreshJob = scheduler.scheduleWithFixedDelay(this::run, 0, config.getRefresh(), TimeUnit.SECONDS); - } + private void start(final UniFiController uc) { + boolean startRefresh = false; + try { + uc.start(); + startRefresh = true; + } catch (final UniFiCommunicationException e) { + updateStatus(OFFLINE, COMMUNICATION_ERROR, STATUS_DESCRIPTION_COMMUNICATION_ERROR); + startRefresh = true; + } catch (final UniFiInvalidHostException e) { + updateStatus(OFFLINE, CONFIGURATION_ERROR, STATUS_DESCRIPTION_INVALID_HOSTNAME); + } catch (final UniFiSSLException e) { + updateStatus(OFFLINE, CONFIGURATION_ERROR, STATUS_DESCRIPTION_SSL_ERROR); + } catch (final UniFiInvalidCredentialsException e) { + updateStatus(OFFLINE, CONFIGURATION_ERROR, STATUS_DESCRIPTION_INVALID_CREDENTIALS); + } catch (final UniFiException e) { + logger.debug("Unknown error while configuring the UniFi Controller", e); + updateStatus(OFFLINE, CONFIGURATION_ERROR, e.getMessage()); + } + if (startRefresh) { + logger.debug("Scheduling refresh job every {}s", config.getRefresh()); + refreshJob = scheduler.scheduleWithFixedDelay(this::run, 0, config.getRefresh(), TimeUnit.SECONDS); } } private void cancelRefreshJob() { synchronized (this) { - if (refreshJob != null) { + final ScheduledFuture rj = refreshJob; + + if (rj != null) { logger.debug("Cancelling refresh job"); - refreshJob.cancel(true); + rj.cancel(true); refreshJob = null; } } @@ -173,21 +170,22 @@ public class UniFiControllerThingHandler extends BaseBridgeHandler { logger.trace("Executing refresh job"); refresh(); updateStatus(ONLINE); - } catch (UniFiCommunicationException e) { + } catch (final UniFiCommunicationException e) { updateStatus(OFFLINE, COMMUNICATION_ERROR, STATUS_DESCRIPTION_COMMUNICATION_ERROR); - } catch (UniFiInvalidCredentialsException e) { + } catch (final UniFiInvalidCredentialsException e) { updateStatus(OFFLINE, CONFIGURATION_ERROR, STATUS_DESCRIPTION_INVALID_CREDENTIALS); - } catch (Exception e) { - logger.warn("Unhandled exception while refreshing the UniFi Controller {} - {}", getThing().getUID(), - e.getMessage()); + } catch (final RuntimeException | UniFiException e) { + logger.debug("Unhandled exception while refreshing the UniFi Controller {}", getThing().getUID(), e); updateStatus(OFFLINE, COMMUNICATION_ERROR, e.getMessage()); } } private void refresh() throws UniFiException { - if (controller != null) { + final UniFiController uc = controller; + + if (uc != null) { logger.debug("Refreshing the UniFi Controller {}", getThing().getUID()); - controller.refresh(); + uc.refresh(); // mgb: then refresh all the client things getThing().getThings().forEach((thing) -> { if (thing.getHandler() instanceof UniFiBaseThingHandler) { diff --git a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/handler/UniFiPoePortThingHandler.java b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/handler/UniFiPoePortThingHandler.java new file mode 100644 index 00000000000..236f1c09128 --- /dev/null +++ b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/handler/UniFiPoePortThingHandler.java @@ -0,0 +1,203 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.unifi.internal.handler; + +import static org.openhab.binding.unifi.internal.UniFiBindingConstants.CHANNEL_ENABLE_PARAMETER_MODE; +import static org.openhab.binding.unifi.internal.UniFiBindingConstants.CHANNEL_ENABLE_PARAMETER_MODE_AUTO; +import static org.openhab.binding.unifi.internal.UniFiBindingConstants.CHANNEL_ENABLE_PARAMETER_MODE_OFF; +import static org.openhab.binding.unifi.internal.UniFiBindingConstants.CHANNEL_ONLINE; +import static org.openhab.binding.unifi.internal.UniFiBindingConstants.CHANNEL_PORT_POE_CMD; +import static org.openhab.binding.unifi.internal.UniFiBindingConstants.CHANNEL_PORT_POE_CMD_POWER_CYCLE; +import static org.openhab.binding.unifi.internal.UniFiBindingConstants.CHANNEL_PORT_POE_CURRENT; +import static org.openhab.binding.unifi.internal.UniFiBindingConstants.CHANNEL_PORT_POE_ENABLE; +import static org.openhab.binding.unifi.internal.UniFiBindingConstants.CHANNEL_PORT_POE_MODE; +import static org.openhab.binding.unifi.internal.UniFiBindingConstants.CHANNEL_PORT_POE_POWER; +import static org.openhab.binding.unifi.internal.UniFiBindingConstants.CHANNEL_PORT_POE_VOLTAGE; +import static org.openhab.core.library.unit.MetricPrefix.MILLI; + +import java.util.HashMap; +import java.util.Map; + +import javax.measure.quantity.ElectricCurrent; +import javax.measure.quantity.ElectricPotential; +import javax.measure.quantity.Power; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.unifi.internal.UniFiPoePortThingConfig; +import org.openhab.binding.unifi.internal.api.UniFiController; +import org.openhab.binding.unifi.internal.api.UniFiException; +import org.openhab.binding.unifi.internal.api.cache.UniFiControllerCache; +import org.openhab.binding.unifi.internal.api.dto.UnfiPortOverride; +import org.openhab.binding.unifi.internal.api.dto.UniFiDevice; +import org.openhab.binding.unifi.internal.api.dto.UniFiPortTable; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.library.types.QuantityType; +import org.openhab.core.library.types.StringType; +import org.openhab.core.library.unit.Units; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.ThingStatusDetail; +import org.openhab.core.types.Command; +import org.openhab.core.types.State; +import org.openhab.core.types.UnDefType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A Power Over Ethernet (PoE) port on a UniFi switch. + * + * @author Hilbrand Bouwkamp - Initial contribution + */ +@NonNullByDefault +public class UniFiPoePortThingHandler + extends UniFiBaseThingHandler, UniFiPoePortThingConfig> { + + private final Logger logger = LoggerFactory.getLogger(UniFiPoePortThingHandler.class); + + private UniFiPoePortThingConfig config = new UniFiPoePortThingConfig(); + private String poeEnableMode = ""; + + public UniFiPoePortThingHandler(final Thing thing) { + super(thing); + } + + @Override + protected boolean initialize(final UniFiPoePortThingConfig config) { + this.config = config; + if (!config.isValid()) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, + "@text/error.thing.poe.offline.configuration_error"); + return false; + } + final String channelConfigPoeEnableMode = (String) getThing().getChannel(CHANNEL_PORT_POE_ENABLE) + .getConfiguration().get(CHANNEL_ENABLE_PARAMETER_MODE); + poeEnableMode = channelConfigPoeEnableMode.isBlank() ? CHANNEL_ENABLE_PARAMETER_MODE_AUTO + : channelConfigPoeEnableMode; + return true; + } + + @Override + protected @Nullable Map getEntity(final UniFiControllerCache cache) { + return cache.getSwitchPorts(config.getMacAddress()); + } + + @Override + protected State getChannelState(final Map ports, final String channelId) { + final UniFiPortTable port = getPort(ports); + + if (port == null) { + logger.debug("No PoE port for thing '{}' could be found in the data. Refresh ignored.", + getThing().getUID()); + return UnDefType.NULL; + } + final State state; + + switch (channelId) { + case CHANNEL_ONLINE: + state = OnOffType.from(port.isUp()); + break; + case CHANNEL_PORT_POE_ENABLE: + state = OnOffType.from(port.isPoeEnabled()); + break; + case CHANNEL_PORT_POE_MODE: + state = StringType.valueOf(port.getPoeMode()); + break; + case CHANNEL_PORT_POE_POWER: + state = new QuantityType(Double.valueOf(port.getPoePower()), Units.WATT); + break; + case CHANNEL_PORT_POE_VOLTAGE: + state = new QuantityType(Double.valueOf(port.getPoeVoltage()), Units.VOLT); + break; + case CHANNEL_PORT_POE_CURRENT: + state = new QuantityType(Double.valueOf(port.getPoeCurrent()), MILLI(Units.AMPERE)); + break; + default: + state = UnDefType.UNDEF; + } + return state; + } + + private @Nullable UniFiPortTable getPort(final Map ports) { + return ports.get(config.getPortNumber()); + } + + @Override + protected boolean handleCommand(final UniFiController controller, final Map ports, + final ChannelUID channelUID, final Command command) throws UniFiException { + final String channelID = channelUID.getIdWithoutGroup(); + + switch (channelID) { + case CHANNEL_PORT_POE_ENABLE: + if (command instanceof OnOffType) { + return handleModeCommand(controller, ports, getPort(ports), + OnOffType.ON == command ? poeEnableMode : CHANNEL_ENABLE_PARAMETER_MODE_OFF); + } + break; + case CHANNEL_PORT_POE_MODE: + if (command instanceof StringType) { + return handleModeCommand(controller, ports, getPort(ports), command.toFullString()); + } + break; + case CHANNEL_PORT_POE_CMD: + if (command instanceof StringType) { + return handleCmd(controller, getPort(ports), command.toFullString()); + } + default: + return false; + } + return false; + } + + private boolean handleModeCommand(final UniFiController controller, final Map ports, + final @Nullable UniFiPortTable portToUpdate, final String poeMode) throws UniFiException { + final UniFiDevice device = controller.getCache().getDevice(config.getMacAddress()); + + if (device == null || portToUpdate == null) { + logger.info("Could not change the PoE port state for thing '{}': device {} or portToUpdate {} null", + getThing().getUID(), device, portToUpdate); + return false; + } else { + final UnfiPortOverride override = new UnfiPortOverride(); + override.setPortIdx(portToUpdate.getPortIdx()); + override.setPortconfId(portToUpdate.getPortconfId()); + override.setPoeMode(poeMode); + final Map newMap = new HashMap<>(ports); + + newMap.put(portToUpdate.getPortIdx(), override); + controller.poeMode(device, newMap); + refresh(); + return true; + } + } + + private boolean handleCmd(final UniFiController controller, @Nullable final UniFiPortTable portToUpdate, + final String command) throws UniFiException { + final UniFiDevice device = controller.getCache().getDevice(config.getMacAddress()); + if (device == null || portToUpdate == null) { + logger.info("Could not change the PoE port state for thing '{}': device {} or portToUpdate {} null", + getThing().getUID(), device, portToUpdate); + return false; + } else { + if (CHANNEL_PORT_POE_CMD_POWER_CYCLE.equalsIgnoreCase(command.replaceAll("[- ]", ""))) { + controller.poePowerCycle(device, portToUpdate.getPortIdx()); + return true; + } else { + logger.info("Unknown command '{}' given to PoE port for thing '{}': device {} or portToUpdate {} null", + command, getThing().getUID(), device, portToUpdate); + return false; + } + } + } +} diff --git a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/handler/UniFiSiteThingHandler.java b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/handler/UniFiSiteThingHandler.java new file mode 100644 index 00000000000..e996efa95e1 --- /dev/null +++ b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/handler/UniFiSiteThingHandler.java @@ -0,0 +1,98 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.unifi.internal.handler; + +import static org.openhab.binding.unifi.internal.UniFiBindingConstants.CHANNEL_GUEST_CLIENTS; +import static org.openhab.binding.unifi.internal.UniFiBindingConstants.CHANNEL_TOTAL_CLIENTS; +import static org.openhab.binding.unifi.internal.UniFiBindingConstants.CHANNEL_WIRED_CLIENTS; +import static org.openhab.binding.unifi.internal.UniFiBindingConstants.CHANNEL_WIRELESS_CLIENTS; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.unifi.internal.UniFiSiteThingConfig; +import org.openhab.binding.unifi.internal.api.UniFiController; +import org.openhab.binding.unifi.internal.api.UniFiException; +import org.openhab.binding.unifi.internal.api.cache.UniFiControllerCache; +import org.openhab.binding.unifi.internal.api.dto.UniFiSite; +import org.openhab.core.library.types.DecimalType; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.ThingStatusDetail; +import org.openhab.core.types.Command; +import org.openhab.core.types.State; +import org.openhab.core.types.UnDefType; + +/** + * The {@link UniFiSiteThingHandler} is responsible for handling commands and status + * updates for {@link UniFiSite} instances. + * + * @author Matthew Bowman - Initial contribution + * @author Hilbrand Bouwkamp - Initial contribution + */ +@NonNullByDefault +public class UniFiSiteThingHandler extends UniFiBaseThingHandler { + + private UniFiSiteThingConfig config = new UniFiSiteThingConfig(); + + public UniFiSiteThingHandler(final Thing thing) { + super(thing); + } + + @Override + protected boolean initialize(final UniFiSiteThingConfig config) { + this.config = config; + if (!config.isValid()) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, + "@text/error.thing.site.offline.configuration_error"); + return false; + } + return true; + } + + @Override + protected @Nullable UniFiSite getEntity(final UniFiControllerCache cache) { + return cache.getSite(config.getSiteID()); + } + + @Override + protected State getChannelState(final UniFiSite site, final String channelId) { + final UniFiControllerCache cache = site.getCache(); + final long count; + + switch (channelId) { + case CHANNEL_TOTAL_CLIENTS: + count = cache.countClients(site, c -> true); + break; + case CHANNEL_WIRELESS_CLIENTS: + count = cache.countClients(site, c -> c.isWireless()); + break; + case CHANNEL_WIRED_CLIENTS: + count = cache.countClients(site, c -> c.isWired()); + break; + case CHANNEL_GUEST_CLIENTS: + count = cache.countClients(site, c -> c.isGuest()); + break; + default: + // Unsupported channel; nothing to update + return UnDefType.NULL; + } + return new DecimalType(count); + } + + @Override + protected boolean handleCommand(final UniFiController controller, final UniFiSite entity, + final ChannelUID channelUID, final Command command) throws UniFiException { + return false; + } +} diff --git a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/handler/UniFiThingDiscoveryService.java b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/handler/UniFiThingDiscoveryService.java new file mode 100644 index 00000000000..f204154ca2b --- /dev/null +++ b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/handler/UniFiThingDiscoveryService.java @@ -0,0 +1,190 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.unifi.internal.handler; + +import static org.openhab.binding.unifi.internal.UniFiBindingConstants.PARAMETER_CID; +import static org.openhab.binding.unifi.internal.UniFiBindingConstants.PARAMETER_MAC_ADDRESS; +import static org.openhab.binding.unifi.internal.UniFiBindingConstants.PARAMETER_PORT_NUMBER; +import static org.openhab.binding.unifi.internal.UniFiBindingConstants.PARAMETER_SID; +import static org.openhab.binding.unifi.internal.UniFiBindingConstants.PARAMETER_SITE; +import static org.openhab.binding.unifi.internal.UniFiBindingConstants.PARAMETER_WID; +import static org.openhab.binding.unifi.internal.UniFiBindingConstants.PARAMETER_WIFI_NAME; + +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.unifi.internal.UniFiBindingConstants; +import org.openhab.binding.unifi.internal.api.UniFiController; +import org.openhab.binding.unifi.internal.api.UniFiException; +import org.openhab.binding.unifi.internal.api.cache.UniFiControllerCache; +import org.openhab.binding.unifi.internal.api.dto.UniFiClient; +import org.openhab.binding.unifi.internal.api.dto.UniFiPortTable; +import org.openhab.binding.unifi.internal.api.dto.UniFiSite; +import org.openhab.binding.unifi.internal.api.dto.UniFiWlan; +import org.openhab.core.config.discovery.AbstractDiscoveryService; +import org.openhab.core.config.discovery.DiscoveryResultBuilder; +import org.openhab.core.config.discovery.DiscoveryService; +import org.openhab.core.thing.ThingUID; +import org.openhab.core.thing.binding.ThingHandler; +import org.openhab.core.thing.binding.ThingHandlerService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Discovery service for detecting things connected to a UniFi controller. + * + * @author Hilbrand Bouwkamp - Initial contribution + */ +@NonNullByDefault +public class UniFiThingDiscoveryService extends AbstractDiscoveryService + implements ThingHandlerService, DiscoveryService { + + /** + * Timeout for discovery time. + */ + private static final int UNIFI_DISCOVERY_TIMEOUT_SECONDS = 30; + private static final long TTL_SECONDS = TimeUnit.MINUTES.toSeconds(5); + private static final int THING_ID_LENGTH = 8; + private static final String DEFAULT_PORTNAME = "Port"; + + private final Logger logger = LoggerFactory.getLogger(UniFiThingDiscoveryService.class); + + private @Nullable UniFiControllerThingHandler bridgeHandler; + + public UniFiThingDiscoveryService() { + super(UniFiBindingConstants.THING_TYPE_SUPPORTED, UNIFI_DISCOVERY_TIMEOUT_SECONDS, false); + } + + @Override + public void deactivate() { + super.deactivate(); + } + + @Override + public void setThingHandler(final ThingHandler handler) { + if (handler instanceof UniFiControllerThingHandler) { + bridgeHandler = (UniFiControllerThingHandler) handler; + } + } + + @Override + public @Nullable ThingHandler getThingHandler() { + return bridgeHandler; + } + + @Override + protected void startScan() { + removeOlderResults(getTimestampOfLastScan()); + final UniFiControllerThingHandler bh = bridgeHandler; + if (bh == null) { + return; + } + final UniFiController controller = bh.getController(); + if (controller == null) { + return; + } + try { + controller.refresh(); + final UniFiControllerCache cache = controller.getCache(); + final ThingUID bridgeUID = bh.getThing().getUID(); + + discoverSites(cache, bridgeUID); + discoverWlans(cache, bridgeUID); + discoverClients(cache, bridgeUID); + discoverPoePorts(cache, bridgeUID); + } catch (final UniFiException e) { + logger.debug("Exception during discovery of UniFi Things", e); + } + } + + private void discoverSites(final UniFiControllerCache cache, final ThingUID bridgeUID) { + for (final UniFiSite site : cache.getSites()) { + final ThingUID thingUID = new ThingUID(UniFiBindingConstants.THING_TYPE_SITE, bridgeUID, + stripIdShort(site.getId())); + final Map properties = Map.of(PARAMETER_SID, site.getId()); + + thingDiscovered(DiscoveryResultBuilder.create(thingUID).withThingType(UniFiBindingConstants.THING_TYPE_SITE) + .withBridge(bridgeUID).withRepresentationProperty(PARAMETER_SID).withTTL(TTL_SECONDS) + .withProperties(properties).withLabel(site.getName()).build()); + } + } + + private void discoverWlans(final UniFiControllerCache cache, final ThingUID bridgeUID) { + for (final UniFiWlan wlan : cache.getWlans()) { + final ThingUID thingUID = new ThingUID(UniFiBindingConstants.THING_TYPE_WLAN, bridgeUID, + stripIdShort(wlan.getId())); + final Map properties = Map.of(PARAMETER_WID, wlan.getId(), PARAMETER_SITE, + wlan.getSite().getName(), PARAMETER_WIFI_NAME, wlan.getName()); + + thingDiscovered(DiscoveryResultBuilder.create(thingUID).withThingType(UniFiBindingConstants.THING_TYPE_WLAN) + .withBridge(bridgeUID).withRepresentationProperty(PARAMETER_WID).withTTL(TTL_SECONDS) + .withProperties(properties).withLabel(wlan.getName()).build()); + } + } + + private void discoverClients(final UniFiControllerCache cache, final ThingUID bridgeUID) { + for (final UniFiClient uc : cache.getClients()) { + final var thingTypeUID = uc.isWireless() ? UniFiBindingConstants.THING_TYPE_WIRELESS_CLIENT + : UniFiBindingConstants.THING_TYPE_WIRED_CLIENT; + final ThingUID thingUID = new ThingUID(thingTypeUID, bridgeUID, stripIdShort(uc.getId())); + final Map properties = Map.of(PARAMETER_CID, uc.getMac(), PARAMETER_SITE, + uc.getSite().getName()); + + thingDiscovered(DiscoveryResultBuilder.create(thingUID).withThingType(thingTypeUID).withBridge(bridgeUID) + .withRepresentationProperty(PARAMETER_CID).withTTL(TTL_SECONDS).withProperties(properties) + .withLabel(uc.getAlias()).build()); + } + } + + /** + * Shorten the id to make it a bit more comprehensible. + * + * @param id id to shorten. + * @return shortened id or if to short the original id + */ + private static String stripIdShort(final String id) { + return id.length() > THING_ID_LENGTH ? id.substring(id.length() - THING_ID_LENGTH) : id; + } + + private void discoverPoePorts(final UniFiControllerCache cache, final ThingUID bridgeUID) { + for (final Map uc : cache.getSwitchPorts()) { + for (final Entry sp : uc.entrySet()) { + final UniFiPortTable pt = sp.getValue(); + final String deviceMac = pt.getDevice().getMac(); + final String id = deviceMac.replace(":", "") + "_" + pt.getPortIdx(); + final ThingUID thingUID = new ThingUID(UniFiBindingConstants.THING_TYPE_POE_PORT, bridgeUID, id); + final Map properties = Map.of(PARAMETER_PORT_NUMBER, pt.getPortIdx(), + PARAMETER_MAC_ADDRESS, deviceMac); + + thingDiscovered(DiscoveryResultBuilder.create(thingUID) + .withThingType(UniFiBindingConstants.THING_TYPE_POE_PORT).withBridge(bridgeUID) + .withTTL(TTL_SECONDS).withProperties(properties).withLabel(portName(pt)).build()); + } + } + } + + /** + * If the PoE port hasn't it's own name, but is named Port with a number the name is prefixed with the device name. + * + * @param pt port object + * @return label for the discovered PoE port + */ + private static @Nullable String portName(final UniFiPortTable pt) { + final String portName = pt.getName(); + + return portName.startsWith(DEFAULT_PORTNAME) ? pt.getDevice().getName() + " " + portName : portName; + } +} diff --git a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/handler/UniFiWlanThingHandler.java b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/handler/UniFiWlanThingHandler.java new file mode 100644 index 00000000000..009387bfeb0 --- /dev/null +++ b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/handler/UniFiWlanThingHandler.java @@ -0,0 +1,170 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.unifi.internal.handler; + +import static org.openhab.binding.unifi.internal.UniFiBindingConstants.CHANNEL_ENABLE; +import static org.openhab.binding.unifi.internal.UniFiBindingConstants.CHANNEL_ESSID; +import static org.openhab.binding.unifi.internal.UniFiBindingConstants.CHANNEL_GUEST_CLIENTS; +import static org.openhab.binding.unifi.internal.UniFiBindingConstants.CHANNEL_PASSPHRASE; +import static org.openhab.binding.unifi.internal.UniFiBindingConstants.CHANNEL_QRCODE_ENCODING; +import static org.openhab.binding.unifi.internal.UniFiBindingConstants.CHANNEL_SECURITY; +import static org.openhab.binding.unifi.internal.UniFiBindingConstants.CHANNEL_SITE; +import static org.openhab.binding.unifi.internal.UniFiBindingConstants.CHANNEL_WIRELESS_CLIENTS; +import static org.openhab.binding.unifi.internal.UniFiBindingConstants.CHANNEL_WLANBAND; +import static org.openhab.binding.unifi.internal.UniFiBindingConstants.CHANNEL_WPAENC; +import static org.openhab.binding.unifi.internal.UniFiBindingConstants.CHANNEL_WPAMODE; + +import java.util.function.Function; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.unifi.internal.UniFiWlanThingConfig; +import org.openhab.binding.unifi.internal.api.UniFiController; +import org.openhab.binding.unifi.internal.api.UniFiException; +import org.openhab.binding.unifi.internal.api.cache.UniFiControllerCache; +import org.openhab.binding.unifi.internal.api.dto.UniFiClient; +import org.openhab.binding.unifi.internal.api.dto.UniFiSite; +import org.openhab.binding.unifi.internal.api.dto.UniFiWirelessClient; +import org.openhab.binding.unifi.internal.api.dto.UniFiWlan; +import org.openhab.core.library.types.DecimalType; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.library.types.StringType; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.ThingStatusDetail; +import org.openhab.core.types.Command; +import org.openhab.core.types.State; +import org.openhab.core.types.UnDefType; + +/** + * + * @author Hilbrand Bouwkamp - Initial contribution + */ +@NonNullByDefault +public class UniFiWlanThingHandler extends UniFiBaseThingHandler { + + private UniFiWlanThingConfig config = new UniFiWlanThingConfig(); + + public UniFiWlanThingHandler(final Thing thing) { + super(thing); + } + + @Override + protected boolean initialize(final UniFiWlanThingConfig config) { + this.config = config; + + if (!config.isValid()) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, + "@text/error.thing.wlan.offline.configuration_error"); + return false; + } + return true; + } + + @Override + protected @Nullable UniFiWlan getEntity(final UniFiControllerCache cache) { + return cache.getWlan(config.getWlanId()); + } + + @Override + protected State getChannelState(final UniFiWlan wlan, final String channelId) { + final State state; + + switch (channelId) { + case CHANNEL_ENABLE: + state = OnOffType.from(wlan.isEnabled()); + break; + case CHANNEL_ESSID: + state = StringType.valueOf(wlan.getName()); + break; + case CHANNEL_SITE: + final UniFiSite site = wlan.getSite(); + if (site != null && site.getDescription() != null && !site.getDescription().isBlank()) { + state = StringType.valueOf(site.getDescription()); + } else { + state = UnDefType.UNDEF; + } + break; + case CHANNEL_WIRELESS_CLIENTS: + state = countClients(wlan, c -> true); + break; + case CHANNEL_GUEST_CLIENTS: + state = countClients(wlan, c -> c.isGuest()); + break; + case CHANNEL_SECURITY: + state = StringType.valueOf(wlan.getSecurity()); + break; + case CHANNEL_WLANBAND: + state = StringType.valueOf(wlan.getWlanBand()); + break; + case CHANNEL_WPAENC: + state = StringType.valueOf(wlan.getWpaEnc()); + break; + case CHANNEL_WPAMODE: + state = StringType.valueOf(wlan.getWpaMode()); + break; + case CHANNEL_PASSPHRASE: + state = StringType.valueOf(wlan.getXPassphrase()); + break; + case CHANNEL_QRCODE_ENCODING: + state = qrcodeEncoding(wlan); + break; + default: + // Unsupported channel; nothing to update + state = UnDefType.NULL; + } + return state; + } + + private static State countClients(final UniFiWlan wlan, final Function filter) { + final UniFiSite site = wlan.getSite(); + return new DecimalType(site.getCache().countClients(site, c -> c instanceof UniFiWirelessClient + && wlan.getName().equals(((UniFiWirelessClient) c).getEssid()) && filter.apply(c))); + } + + /** + * Returns a MERCARD like notation of the Wi-Fi access code. Format: + * WIFI:S:<SSID>;T:WPA|blank;P:<password>;; + * + * @param wlan wlan UniFi entity object containing the data + * @return MERCARD like Wi-Fi access format + * @see https://github.com/zxing/zxing/wiki/Barcode-Contents#wi-fi-network-config-android-ios-11 + */ + private static State qrcodeEncoding(final UniFiWlan wlan) { + final String name = encode(wlan.getName()); + final String xPassphrase = wlan.getXPassphrase(); + final boolean nopass = xPassphrase == null || xPassphrase.isBlank(); + final String mode = nopass ? "nopass" : "WPA"; + final String hidden = wlan.isHideSsid() ? "H:true" : ""; + final String passcode = nopass ? "" : "P:" + encode(xPassphrase); + + return StringType.valueOf(String.format("WIFI:S:%s;T:%s;%s;%s;", name, mode, passcode, hidden)); + } + + private static String encode(final @Nullable String value) { + return value == null ? "" : value.replaceAll("([\\;,\":])", "\\\\$1"); + } + + @Override + protected boolean handleCommand(final UniFiController controller, final UniFiWlan entity, + final ChannelUID channelUID, final Command command) throws UniFiException { + final String channelID = channelUID.getId(); + + if (CHANNEL_ENABLE.equals(channelID) && command instanceof OnOffType) { + controller.enableWifi(entity, OnOffType.ON == command); + return true; + } + return false; + } +} diff --git a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/ssl/UniFiTrustManager.java b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/ssl/UniFiTrustManager.java index 96148a6f74e..b6b258431f1 100644 --- a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/ssl/UniFiTrustManager.java +++ b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/ssl/UniFiTrustManager.java @@ -19,6 +19,9 @@ import java.security.cert.X509Certificate; import javax.net.ssl.SSLEngine; import javax.net.ssl.X509ExtendedTrustManager; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + /** * * The {@link UniFiTrustManager} is a "trust all" implementation of {@link X509ExtendedTrustManager}. @@ -27,6 +30,7 @@ import javax.net.ssl.X509ExtendedTrustManager; * * @author Matthew Bowman - Initial contribution */ +@NonNullByDefault public class UniFiTrustManager extends X509ExtendedTrustManager { private static UniFiTrustManager instance = new UniFiTrustManager(); @@ -42,35 +46,37 @@ public class UniFiTrustManager extends X509ExtendedTrustManager { } @Override - public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { + public void checkClientTrusted(final X509Certificate @Nullable [] chain, final @Nullable String authType) + throws CertificateException { } @Override - public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { + public void checkServerTrusted(final X509Certificate @Nullable [] chain, final @Nullable String authType) + throws CertificateException { } @Override - public X509Certificate[] getAcceptedIssuers() { + public X509Certificate @Nullable [] getAcceptedIssuers() { return null; } @Override - public void checkClientTrusted(X509Certificate[] chain, String authType, Socket socket) - throws CertificateException { + public void checkClientTrusted(final X509Certificate @Nullable [] chain, final @Nullable String authType, + final @Nullable Socket socket) throws CertificateException { } @Override - public void checkClientTrusted(X509Certificate[] chain, String authType, SSLEngine engine) - throws CertificateException { + public void checkClientTrusted(final X509Certificate @Nullable [] chain, final @Nullable String authType, + final @Nullable SSLEngine engine) throws CertificateException { } @Override - public void checkServerTrusted(X509Certificate[] chain, String authType, Socket socket) - throws CertificateException { + public void checkServerTrusted(final X509Certificate @Nullable [] chain, final @Nullable String authType, + final @Nullable Socket socket) throws CertificateException { } @Override - public void checkServerTrusted(X509Certificate[] chain, String authType, SSLEngine engine) - throws CertificateException { + public void checkServerTrusted(final X509Certificate @Nullable [] chain, final @Nullable String authType, + final @Nullable SSLEngine engine) throws CertificateException { } } diff --git a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/ssl/UniFiTrustManagerProvider.java b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/ssl/UniFiTrustManagerProvider.java index d090185b772..75c89d9c674 100644 --- a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/ssl/UniFiTrustManagerProvider.java +++ b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/ssl/UniFiTrustManagerProvider.java @@ -14,6 +14,7 @@ package org.openhab.binding.unifi.internal.ssl; import javax.net.ssl.X509ExtendedTrustManager; +import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.core.io.net.http.TlsTrustManagerProvider; /** @@ -25,6 +26,7 @@ import org.openhab.core.io.net.http.TlsTrustManagerProvider; * @author Matthew Bowman - Initial contribution */ // @Component // [wip] mgb: disabled due to issues with service order loading +@NonNullByDefault public class UniFiTrustManagerProvider implements TlsTrustManagerProvider { @Override diff --git a/bundles/org.openhab.binding.unifi/src/main/resources/OH-INF/config/config.xml b/bundles/org.openhab.binding.unifi/src/main/resources/OH-INF/config/config.xml new file mode 100644 index 00000000000..c09f56e7809 --- /dev/null +++ b/bundles/org.openhab.binding.unifi/src/main/resources/OH-INF/config/config.xml @@ -0,0 +1,94 @@ + + + + + + + Hostname of IP address of the UniFi Controller + unifi + network-address + + + + Port of the UniFi Controller + 8443 + + + + If the UniFi Controller is running on UniFi OS. + false + + + + The username to access the UniFi Controller. + + + + The password to access the UniFi Controller. + password + + + + The refresh interval in seconds to poll the UniFi controller + 10 + + + + + + + The id, name or description of the site + + + + + + + The id or name of the wlan + + + + + + + The MAC address, IP address, hostname or alias of the client + + + + The site where the client should be found (optional) + + + + The interval in seconds to consider the client as home + 180 + + + + + + + The number of the port as reported by the UniFi switch + + + + The MAC address of the switch this port is part of + + + + + + + The value to set when setting PoE on. + + + + + + auto + + + + diff --git a/bundles/org.openhab.binding.unifi/src/main/resources/OH-INF/i18n/unifi.properties b/bundles/org.openhab.binding.unifi/src/main/resources/OH-INF/i18n/unifi.properties index 8fe04b3f4a1..ccd435fd0e6 100644 --- a/bundles/org.openhab.binding.unifi/src/main/resources/OH-INF/i18n/unifi.properties +++ b/bundles/org.openhab.binding.unifi/src/main/resources/OH-INF/i18n/unifi.properties @@ -6,12 +6,26 @@ binding.unifi.description = The UniFi binding integrates the UniFi controller fr # thing types thing-type.unifi.controller.label = UniFi Controller -thing-type.unifi.controller.description = A UniFi controller. +thing-type.unifi.controller.description = A UniFi controller +thing-type.unifi.poePort.label = UniFi PoE Port +thing-type.unifi.poePort.description = A Power Over Ethernet (PoE) port on a UniFi switch +thing-type.unifi.site.label = UniFi Site +thing-type.unifi.site.description = A site defined in a UniFi network +thing-type.unifi.wiredClient.label = UniFi Wired Client +thing-type.unifi.wiredClient.description = A wired client connected to a UniFi switch thing-type.unifi.wirelessClient.label = UniFi Wireless Client thing-type.unifi.wirelessClient.description = A wireless client connected to a UniFi wireless network +thing-type.unifi.wlan.label = UniFi WLAN +thing-type.unifi.wlan.description = A UniFi Wireless LAN # thing types config +thing-type.config.unifi.client.cid.label = Client ID +thing-type.config.unifi.client.cid.description = The MAC address, IP address, hostname or alias of the client +thing-type.config.unifi.client.considerHome.label = Consider Home Interval +thing-type.config.unifi.client.considerHome.description = The interval in seconds to consider the client as home +thing-type.config.unifi.client.site.label = Site +thing-type.config.unifi.client.site.description = The site where the client should be found (optional) thing-type.config.unifi.controller.host.label = Hostname thing-type.config.unifi.controller.host.description = Hostname of IP address of the UniFi Controller thing-type.config.unifi.controller.password.label = Password @@ -24,12 +38,14 @@ thing-type.config.unifi.controller.unifios.label = UniFi OS thing-type.config.unifi.controller.unifios.description = If the UniFi Controller is running on UniFi OS. thing-type.config.unifi.controller.username.label = Username thing-type.config.unifi.controller.username.description = The username to access the UniFi Controller. -thing-type.config.unifi.wirelessClient.cid.label = Client ID -thing-type.config.unifi.wirelessClient.cid.description = The MAC address, IP address, hostname or alias of the client -thing-type.config.unifi.wirelessClient.considerHome.label = Consider Home Interval -thing-type.config.unifi.wirelessClient.considerHome.description = The interval in seconds to consider the client as home -thing-type.config.unifi.wirelessClient.site.label = Site -thing-type.config.unifi.wirelessClient.site.description = The site where the client should be found (optional) +thing-type.config.unifi.poePort.macAddress.label = Switch MAC Address +thing-type.config.unifi.poePort.macAddress.description = The MAC address of the switch this port is part of +thing-type.config.unifi.poePort.portNumber.label = Port Number +thing-type.config.unifi.poePort.portNumber.description = The number of the port as reported by the UniFi switch +thing-type.config.unifi.site.sid.label = Site Id +thing-type.config.unifi.site.sid.description = The id, name or description of the site +thing-type.config.unifi.wlan.wid.label = WLAN Id +thing-type.config.unifi.wlan.wid.description = The id or name of the wlan # channel types @@ -39,6 +55,12 @@ channel-type.unifi.blocked.label = Blocked channel-type.unifi.blocked.description = Is device blocked channel-type.unifi.essid.label = Wireless Network channel-type.unifi.essid.description = Wireless Network (ESSID) the wireless client is connected to +channel-type.unifi.experience.label = Experience +channel-type.unifi.experience.description = The wired/wireless experience of the client +channel-type.unifi.guest.label = Guest +channel-type.unifi.guest.description = Is the client connected a guest +channel-type.unifi.guestClients.label = Guest Clients +channel-type.unifi.guestClients.description = Number of guest clients connected channel-type.unifi.ipAddress.label = IP Address channel-type.unifi.ipAddress.description = IP address of the client channel-type.unifi.lastSeen.label = Last Seen @@ -46,12 +68,76 @@ channel-type.unifi.lastSeen.description = Timestamp of when the client was last channel-type.unifi.macAddress.label = MAC Address channel-type.unifi.macAddress.description = MAC address of the client channel-type.unifi.online.label = Online -channel-type.unifi.online.description = Online status of the wireless client +channel-type.unifi.online.description = Online status of the client +channel-type.unifi.poeCmd.label = PoE Command +channel-type.unifi.poeCmd.description = Command that can be given to the PoE port +channel-type.unifi.poeCmd.command.option.power-cycle = Power Cycle +channel-type.unifi.poeCurrent.label = Port PoE Current +channel-type.unifi.poeCurrent.description = Current usage of the PoE port +channel-type.unifi.poeEnable.label = Enabled +channel-type.unifi.poeEnable.description = If PoE is enabled +channel-type.unifi.poeMode.label = PoE Mode +channel-type.unifi.poeMode.description = The PoE mode the port is in +channel-type.unifi.poeMode.state.option.off = Off +channel-type.unifi.poeMode.state.option.auto = Auto +channel-type.unifi.poeMode.state.option.24v = 24V +channel-type.unifi.poeMode.state.option.passthrough = Passthrough +channel-type.unifi.poePower.label = Port PoE Power +channel-type.unifi.poePower.description = Power usage of the PoE port +channel-type.unifi.poeVoltage.label = Port PoE Voltage +channel-type.unifi.poeVoltage.description = Voltage usage of the PoE port +channel-type.unifi.portOnline.label = Port Active +channel-type.unifi.portOnline.description = PoE port is active +channel-type.unifi.qrcodeEncoding.label = QR Code Encoding +channel-type.unifi.qrcodeEncoding.description = MECARD like encoding to generate a QRCode for easy access to the Wi-Fi network channel-type.unifi.reconnect.label = Reconnect channel-type.unifi.reconnect.description = Forces a client to reconnect channel-type.unifi.rssi.label = Received Signal Strength Indicator channel-type.unifi.rssi.description = Received Signal Strength Indicator (RSSI) of the wireless client +channel-type.unifi.security.label = Security +channel-type.unifi.security.description = Security protocol of the Wi-Fi network channel-type.unifi.site.label = Site Name -channel-type.unifi.site.description = UniFi Site the client is associated with +channel-type.unifi.site.description = UniFi Site the device is associated with +channel-type.unifi.totalClients.label = Total Clients +channel-type.unifi.totalClients.description = Total number of clients connected channel-type.unifi.uptime.label = Uptime channel-type.unifi.uptime.description = Uptime of the client (in seconds) +channel-type.unifi.wiredClients.label = Wired Clients +channel-type.unifi.wiredClients.description = Number of wired clients connected +channel-type.unifi.wirelessClients.label = Wireless Clients +channel-type.unifi.wirelessClients.description = Number of wireless clients connected +channel-type.unifi.wirelessCmd.label = Wireless Command +channel-type.unifi.wirelessCmd.description = Command that can be given to the wireless client +channel-type.unifi.wirelessCmd.command.option.reconnect = Reconnect +channel-type.unifi.wlanBand.label = WLAN Band +channel-type.unifi.wlanBand.description = Wireless LAN band of the Wi-Fi network +channel-type.unifi.wlanEnable.label = Enable +channel-type.unifi.wlanEnable.description = Enable status of the wLAN +channel-type.unifi.wlanEssid.label = Wireless Network +channel-type.unifi.wlanEssid.description = Wireless Network (ESSID) +channel-type.unifi.wpaEnc.label = WPA Encoding +channel-type.unifi.wpaEnc.description = WPA Encoding of the Wi-Fi network +channel-type.unifi.wpaMode.label = WPA Mode +channel-type.unifi.wpaMode.description = WPA Mode of the Wi-Fi network +channel-type.unifi.passphrase.label = Passphrase +channel-type.unifi.passphrase.description = Passphrase of the Wi-Fi network + +# channel types config + +channel-type.config.unifi.poeEnable.mode.label = On Mode +channel-type.config.unifi.poeEnable.mode.description = The value to set when setting PoE on. +channel-type.config.unifi.poeEnable.mode.option.auto = Auto +channel-type.config.unifi.poeEnable.mode.option.24v = 24V +channel-type.config.unifi.poeEnable.mode.option.passthrough = Passthrough + +# status messages + +error.bridge.offline.communication_error = Error communicating with the UniFi controller. +error.bridge.offline.invalid_credentials = Invalid username and/or password - please double-check your configuration. +error.bridge.offline.invalid_hostname = Invalid hostname - please double-check your configuration. +error.bridge.offline.ssl_error = Error establishing an SSL connection with the UniFi controller. +error.thing.client.offline.configuration_error = You must define a MAC address, IP address, hostname or alias for this thing. +error.thing.offline.bridge_offline = The UniFi Controller is currently offline. +error.thing.offline.configuration_error = You must choose a UniFi Controller for this thing. +error.thing.poe.offline.configuration_error = The configuration parameter macAddress must be set and not be empty. +error.thing.site.offline.configuration_error = The configuration parameter sid must be set and not be empty. diff --git a/bundles/org.openhab.binding.unifi/src/main/resources/OH-INF/i18n/unifi_de.properties b/bundles/org.openhab.binding.unifi/src/main/resources/OH-INF/i18n/unifi_de.properties index 4758944a5e9..103fc4ef2c8 100644 --- a/bundles/org.openhab.binding.unifi/src/main/resources/OH-INF/i18n/unifi_de.properties +++ b/bundles/org.openhab.binding.unifi/src/main/resources/OH-INF/i18n/unifi_de.properties @@ -12,6 +12,12 @@ thing-type.unifi.wirelessClient.description = Ein drahtloser Client der mit eine # thing types config +thing-type.config.unifi.client.cid.label = Client-ID +thing-type.config.unifi.client.cid.description = Die MAC-Adresse, IP-Adresse, Hostname oder Alias des Clients +thing-type.config.unifi.client.considerHome.label = Anwesenheitsinterval +thing-type.config.unifi.client.considerHome.description = Das Intervall in Sekunden, um den Client als zu Hause anwesend zu betrachten +thing-type.config.unifi.client.site.label = Site +thing-type.config.unifi.client.site.description = Die Site, auf der der Client gefunden werden soll (optional) thing-type.config.unifi.controller.host.label = Hostname thing-type.config.unifi.controller.host.description = Hostname oder IP-Adresse des UniFi-Controllers thing-type.config.unifi.controller.password.label = Passwort @@ -24,12 +30,6 @@ thing-type.config.unifi.controller.unifios.label = UniFi OS thing-type.config.unifi.controller.unifios.description = Ob der UniFi Controller unter UniFi-OS läuft. thing-type.config.unifi.controller.username.label = Benutzername thing-type.config.unifi.controller.username.description = Der Benutzername für den Zugriff auf den UniFi-Controller. -thing-type.config.unifi.wirelessClient.cid.label = Client-ID -thing-type.config.unifi.wirelessClient.cid.description = Die MAC-Adresse, IP-Adresse, Hostname oder Alias des Clients -thing-type.config.unifi.wirelessClient.considerHome.label = Anwesenheitsinterval -thing-type.config.unifi.wirelessClient.considerHome.description = Das Intervall in Sekunden, um den Client als zu Hause anwesend zu betrachten -thing-type.config.unifi.wirelessClient.site.label = Site -thing-type.config.unifi.wirelessClient.site.description = Die Site, auf der der Client gefunden werden soll (optional) # channel types diff --git a/bundles/org.openhab.binding.unifi/src/main/resources/OH-INF/i18n/unifi_hu.properties b/bundles/org.openhab.binding.unifi/src/main/resources/OH-INF/i18n/unifi_hu.properties index dd5fb0839e9..0989838cf29 100644 --- a/bundles/org.openhab.binding.unifi/src/main/resources/OH-INF/i18n/unifi_hu.properties +++ b/bundles/org.openhab.binding.unifi/src/main/resources/OH-INF/i18n/unifi_hu.properties @@ -12,6 +12,12 @@ thing-type.unifi.wirelessClient.description = A UniFi hálózathoz kapcsolódó # thing types config +thing-type.config.unifi.client.cid.label = Kliens azonosító +thing-type.config.unifi.client.cid.description = Az ügyfél MAC címe, IP címe, gépneve vagy álneve (alias) +thing-type.config.unifi.client.considerHome.label = Itthonlét vizsgálati időköze +thing-type.config.unifi.client.considerHome.description = Az ithonlét vizsgálati időköze másodpercben +thing-type.config.unifi.client.site.label = Telepítési hely +thing-type.config.unifi.client.site.description = A telepítési hely, ahol az ügyfél tartózkodik (nem szükséges) thing-type.config.unifi.controller.host.label = Gépnév thing-type.config.unifi.controller.host.description = A UniFi vezérlő gépneve vagy IP címe thing-type.config.unifi.controller.password.label = Jelszó @@ -24,12 +30,6 @@ thing-type.config.unifi.controller.unifios.label = UniFi OS thing-type.config.unifi.controller.unifios.description = A UniFi vezérlő UniFi OS-t futtat. thing-type.config.unifi.controller.username.label = Felhasználónév thing-type.config.unifi.controller.username.description = A UniFi vezérlőhöz szükséges felhasználói név. -thing-type.config.unifi.wirelessClient.cid.label = Kliens azonosító -thing-type.config.unifi.wirelessClient.cid.description = Az ügyfél MAC címe, IP címe, gépneve vagy álneve (alias) -thing-type.config.unifi.wirelessClient.considerHome.label = Itthonlét vizsgálati időköze -thing-type.config.unifi.wirelessClient.considerHome.description = Az ithonlét vizsgálati időköze másodpercben -thing-type.config.unifi.wirelessClient.site.label = Telepítési hely -thing-type.config.unifi.wirelessClient.site.description = A telepítési hely, ahol az ügyfél tartózkodik (nem szükséges) # channel types diff --git a/bundles/org.openhab.binding.unifi/src/main/resources/OH-INF/i18n/unifi_nl.properties b/bundles/org.openhab.binding.unifi/src/main/resources/OH-INF/i18n/unifi_nl.properties new file mode 100644 index 00000000000..9d28d6df708 --- /dev/null +++ b/bundles/org.openhab.binding.unifi/src/main/resources/OH-INF/i18n/unifi_nl.properties @@ -0,0 +1,143 @@ +# binding + +binding.unifi.name = UniFi Binding +binding.unifi.description = De UniFi-binding integreert de UniFi-controller van Ubiquiti Networks om het volgen van Wi-Fi-cliënten te vergemakkelijken. + +# thing types + +thing-type.unifi.controller.label = UniFi Controller +thing-type.unifi.controller.description = Een UniFi-controller +thing-type.unifi.poePort.label = UniFi PoE-poort +thing-type.unifi.poePort.description = Een Power Over Ethernet (PoE)-poort op een UniFi-switch +thing-type.unifi.site.label = UniFi Site +thing-type.unifi.site.description = Een site gedefinieerd in een UniFi-netwerk +thing-type.unifi.wiredClient.label = UniFi Bekabelde Cliënt +thing-type.unifi.wiredClient.description = Een bekabelde cliënt aangesloten op een UniFi-switch +thing-type.unifi.wirelessClient.label = UniFi Draadloze Cliënt +thing-type.unifi.wirelessClient.description = Een draadloze cliënt die is verbonden met een draadloos UniFi-netwerk +thing-type.unifi.wlan.label = UniFi WLAN +thing-type.unifi.wlan.description = Een UniFi draadloos lokaal netwerk (wLAN) + +# thing types config + +thing-type.config.unifi.client.cid.label = Cliënt-Id +thing-type.config.unifi.client.cid.description = Het MAC-adres, IP-adres, hostnaam of alias van de cliënt +thing-type.config.unifi.client.considerHome.label = Aanwezigheidsinterval +thing-type.config.unifi.client.considerHome.description = Het interval in seconden om de cliënt als thuis te beschouwen +thing-type.config.unifi.client.site.label = Site +thing-type.config.unifi.client.site.description = De site waar de cliënt moet worden gevonden (optioneel) +thing-type.config.unifi.controller.host.label = Hostnaam +thing-type.config.unifi.controller.host.description = Hostnaam van het IP-adres van de UniFi Controller +thing-type.config.unifi.controller.password.label = Wachtwoord +thing-type.config.unifi.controller.password.description = Het wachtwoord voor toegang tot de UniFi Controller. +thing-type.config.unifi.controller.port.label = Poort +thing-type.config.unifi.controller.port.description = Poort van de UniFi Controller +thing-type.config.unifi.controller.refresh.label = Vernieuwingsinterval +thing-type.config.unifi.controller.refresh.description = Het interval in seconden om de UniFi-controller te pollen +thing-type.config.unifi.controller.unifios.label = UniFi OS +thing-type.config.unifi.controller.unifios.description = Of de UniFi Controller op UniFi OS draait. +thing-type.config.unifi.controller.username.label = Gebruikersnaam +thing-type.config.unifi.controller.username.description = De gebruikersnaam voor toegang tot de UniFi Controller. +thing-type.config.unifi.poePort.macAddress.label = Wissel van MAC-adres +thing-type.config.unifi.poePort.macAddress.description = Het MAC-adres van de switch waar deze poort deel van uitmaakt +thing-type.config.unifi.poePort.portNumber.label = Poortnummer +thing-type.config.unifi.poePort.portNumber.description = Het nummer van de poort zoals gerapporteerd door de UniFi-switch +thing-type.config.unifi.site.sid.label = Site-Id +thing-type.config.unifi.site.sid.description = Het id, de naam of beschrijving van de site +thing-type.config.unifi.wlan.wid.label = WLAN-Id +thing-type.config.unifi.wlan.wid.description = Het id of de naam van de wLAN + +# channel types + +channel-type.unifi.ap.label = Toegangspunt +channel-type.unifi.ap.description = Toegangspunt waarmee de draadloze cliënt is verbonden +channel-type.unifi.blocked.label = Geblokkeerd +channel-type.unifi.blocked.description = Is apparaat geblokkeerd +channel-type.unifi.essid.label = Draadloos Netwerk +channel-type.unifi.essid.description = Draadloos netwerk (ESSID) waarmee de draadloze cliënt is verbonden +channel-type.unifi.experience.label = Ervaring +channel-type.unifi.experience.description = De ervaring van de bedraade/draadloze cliënt +channel-type.unifi.guest.label = Gast +channel-type.unifi.guest.description = Is de cliënt verbonden als gast? +channel-type.unifi.guestClients.label = Gasten +channel-type.unifi.guestClients.description = Aantal verbonden gasten +channel-type.unifi.ipAddress.label = IP-adres +channel-type.unifi.ipAddress.description = IP-adres van de cliënt +channel-type.unifi.lastSeen.label = Laatst Gezien +channel-type.unifi.lastSeen.description = Tijdstempel van wanneer de cliënt voor het laatst is gezien +channel-type.unifi.macAddress.label = MAC-adres +channel-type.unifi.macAddress.description = MAC-adres van de cliënt +channel-type.unifi.online.label = Online +channel-type.unifi.online.description = Online status van de cliënt +channel-type.unifi.poeCmd.label = PoE Commando +channel-type.unifi.poeCmd.description = Commando die kan worden gegeven aan de PoE poort +channel-type.unifi.poeCmd.command.option.power\-cycle = Power Cycle +channel-type.unifi.poeCurrent.label = Poort PoE Stroom +channel-type.unifi.poeCurrent.description = Huidig stroom verbruik van de PoE-poort +channel-type.unifi.poeEnable.label = Actief +channel-type.unifi.poeEnable.description = Of PoE is ingeschakeld +channel-type.unifi.poeMode.label = PoE-modus +channel-type.unifi.poeMode.description = De PoE-modus waarin de poort zich bevindt. +channel-type.unifi.poeMode.state.option.off = Uit +channel-type.unifi.poeMode.state.option.auto = Auto +channel-type.unifi.poeMode.state.option.24v = 24V +channel-type.unifi.poeMode.state.option.passthrough = Doorlussen +channel-type.unifi.poePower.label = Poort PoE Stroom +channel-type.unifi.poePower.description = Stroomverbruik van de PoE-poort +channel-type.unifi.poeVoltage.label = Poort PoE Voltage +channel-type.unifi.poeVoltage.description = Voltage van de PoE-poort +channel-type.unifi.portOnline.label = Poort Actief +channel-type.unifi.portOnline.description = Poort is in gebruik +channel-type.unifi.qrcodeEncoding.label = QR Code Codering +channel-type.unifi.qrcodeEncoding.description = MECARD-achtige codering om een QR Code te genereren voor snelle toegang tot het Wi-Fi-netwerk +channel-type.unifi.reconnect.label = Opnieuw Verbinden +channel-type.unifi.reconnect.description = Dwingt een cliënt om opnieuw verbinding te maken +channel-type.unifi.rssi.label = Signaalsterkte +channel-type.unifi.rssi.description = Ontvanger signaal sterkte indicator (RSSI) van de draadloze cliënt +channel-type.unifi.security.label = Beveiliging +channel-type.unifi.security.description = Beveiligingsprotocol van het Wi-Fi-netwerk +channel-type.unifi.site.label = Sitenaam +channel-type.unifi.site.description = UniFi site waaraan het apparaat is gekoppeld +channel-type.unifi.totalClients.label = Totaal Aantal Cliënten +channel-type.unifi.totalClients.description = Totaal aantal aangesloten cliënten +channel-type.unifi.uptime.label = Uptime +channel-type.unifi.uptime.description = Uptime van de cliënt (in seconden) +channel-type.unifi.wiredClients.label = Bedrade Cliënten +channel-type.unifi.wiredClients.description = Aantal aangesloten bedrade cliënten +channel-type.unifi.wirelessClients.label = Draadloze Cliënten +channel-type.unifi.wirelessClients.description = Aantal aangesloten draadloze cliënten +channel-type.unifi.wirelessCmd.label = Wireless Commando +channel-type.unifi.wirelessCmd.description = Commando die aan de draadloze cliënt gegeven kan worden +channel-type.unifi.wirelessCmd.command.option.reconnect = Opnieuw Verbinden +channel-type.unifi.wlanBand.label = WLAN Band +channel-type.unifi.wlanBand.description = Draadloze LAN-band van het Wi-Fi-netwerk +channel-type.unifi.wlanEnable.label = Actief +channel-type.unifi.wlanEnable.description = Of the wLAN actief is +channel-type.unifi.wlanEssid.label = Draadloos netwerk +channel-type.unifi.wlanEssid.description = Draadloos netwerk (ESSID) +channel-type.unifi.wpaEnc.label = WPA-codering +channel-type.unifi.wpaEnc.description = WPA-codering van het Wi-Fi-netwerk +channel-type.unifi.wpaMode.label = WPA-modus +channel-type.unifi.wpaMode.description = WPA-modus van het Wi-Fi-netwerk +channel-type.unifi.passphrase.label = Wachtwoord +channel-type.unifi.passphrase.description = Wachtwoord van het Wi-Fi-netwerk + +# channel types config + +channel-type.config.unifi.poeEnable.mode.label = Aan-modus +channel-type.config.unifi.poeEnable.mode.description = De waarde die moet worden ingesteld wanneer PoE wordt ingeschakeld. +channel-type.config.unifi.poeEnable.mode.option.auto = Auto +channel-type.config.unifi.poeEnable.mode.option.24v = 24V +channel-type.config.unifi.poeEnable.mode.option.passthrough = Doorlussen + +# status messages + +error.bridge.offline.communication_error = Fout bij communicatie met de UniFi-controller. +error.bridge.offline.invalid_credentials = Ongeldige gebruikersnaam en/of wachtwoord - controleer uw configuratie. +error.bridge.offline.invalid_hostname = Ongeldige hostnaam - controleer uw configuratie nogmaals. +error.bridge.offline.ssl_error = Fout bij het tot stand brengen van een SSL-verbinding met de UniFi-controller. +error.thing.client.offline.configuration_error = Je moet een MAC-adres, IP-adres, hostnaam of alias voor dit ding definiëren. +error.thing.offline.bridge_offline = De UniFi-controller is momenteel offline. +error.thing.offline.configuration_error = Je moet hiervoor een UniFi-controller kiezen. +error.thing.poe.offline.configuration_error = De configuratieparameter macAddress moet zijn ingesteld en mag niet leeg zijn. +error.thing.site.offline.configuration_error = De configuratieparameter sid moet ingesteld zijn en mag niet leeg zijn. diff --git a/bundles/org.openhab.binding.unifi/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.unifi/src/main/resources/OH-INF/thing/thing-types.xml index 87069a33ca0..40f70b31828 100644 --- a/bundles/org.openhab.binding.unifi/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.unifi/src/main/resources/OH-INF/thing/thing-types.xml @@ -7,47 +7,83 @@ - A UniFi controller. - - - - - Hostname of IP address of the UniFi Controller - unifi - network-address - - - - Port of the UniFi Controller - 8443 - - - - If the UniFi Controller is running on UniFi OS. - false - - - - The username to access the UniFi Controller. - - - - The password to access the UniFi Controller. - password - - - - The refresh interval in seconds to poll the UniFi controller - 10 - - + A UniFi controller + - + + + + + + + A site defined in a UniFi network + + + + + + + + + sid + + + + + + + + + + + A UniFi Wireless LAN + + + + + + + + + + + + + + + + wid + + + + + + + + + + A wired client connected to a UniFi switch + + + + + + + + + + + + + + cid + + + - @@ -63,44 +99,138 @@ + + + cid - - - - The MAC address, IP address, hostname or alias of the client - - - - The site where the client should be found (optional) - - - - The interval in seconds to consider the client as home - 180 - - - + + + + + + + + A Power Over Ethernet (PoE) port on a UniFi switch + + + + + + + + + + + + + + + + + + Number + + Total number of clients connected + + + + + Number + + Number of wireless clients connected + + + + + Number + + Number of wired clients connected + + + + + Number + + Number of guest clients connected + + + + + Switch + + Enable status of the wLAN + + + + String + + Wireless Network (ESSID) + + + + + String + + Security protocol of the Wi-Fi network + + + + + String + + Wireless LAN band of the Wi-Fi network + + + + + String + + WPA Encoding of the Wi-Fi network + + + + + String + + WPA Mode of the Wi-Fi network + + + + + String + + Passphrase of the Wi-Fi network + + + + + String + + MECARD like encoding to generate a QRCode for easy access to the Wi-Fi network + + + Switch - Online status of the wireless client + Online status of the client String - UniFi Site the client is associated with + UniFi Site the device is associated with @@ -159,10 +289,94 @@ Is device blocked - + + Switch + + Is the client connected a guest + + + + Number:Dimensionless + + The wired/wireless experience of the client + + + + Switch Forces a client to reconnect + + String + + Command that can be given to the wireless client + + + + + + + + + Switch + + PoE port is active + + + + + Switch + + If PoE is enabled + + + + + String + + The PoE mode the port is in + + + + + + + + + + + + String + + Command that can be given to the PoE port + + + + + + + + + Number:Power + + Power usage of the PoE port + + + + + Number:ElectricPotential + + Voltage usage of the PoE port + + + + + Number:ElectricCurrent + + Current usage of the PoE port + + +