From dec6483e2c0346940b9f1d8ab3a02ad427cdbccf Mon Sep 17 00:00:00 2001 From: Hilbrand Bouwkamp Date: Mon, 16 May 2022 23:14:00 +0200 Subject: [PATCH] [unifi] New site, wlan, wiredClient, and poePort. Discovery support (#11959) * [unifi] New wiredClient and poePort, Discovery support This change adds the following changes: - 2 new things: a wired client and POE port. - Adds discovery of clients and poePort. - Adds guest channel to client thing. Also included some refactoring and bug fixes. This change includes changes made by Matthew Bowman that he created on his own branch but were never completed. Closes #9609: Implemented async http call, which should fix the buffer overflow. Closes #10375: At least should avoid the stack overflow. Closes #11964: cid will be handled in lower case. * Removed type from UniFiCache constructor It's redundant and only used for logging. * Added UniFi Site and wLAN things * Improved default state handling Updated refresh/state update, to also update when no data available. Simplified usage of cache: call cache directly instead of implicit via controller class. Made getDefaultState generic to all things, and simplified passing channelId instead of channelUID to sub methods. * Moved dto objects to dto package. * Added support for client experience * Made fields private No need to have them protected. * Added PoE power-cycle command Also added wireless client as command as this better fits with the openHAB model to handle commands that are only one way and not have a state. * Updated readme * [unifi] Added client/guest count to wlan * Fix QRcode construction and added hidden ssid support in qrcode string Also-by: Matthew Bowman Signed-off-by: Hilbrand Bouwkamp Co-authored-by: Matthew Bowman --- bundles/org.openhab.binding.unifi/README.md | 167 +++++++-- .../unifi/internal/UniFiBindingConstants.java | 57 ++- .../internal/UniFiClientThingConfig.java | 24 +- .../internal/UniFiControllerThingConfig.java | 33 ++ .../internal/UniFiPoePortThingConfig.java | 47 +++ .../unifi/internal/UniFiSiteThingConfig.java | 47 +++ .../internal/UniFiThingHandlerFactory.java | 44 ++- .../unifi/internal/UniFiWlanThingConfig.java | 47 +++ .../api/UniFiCommunicationException.java | 5 +- .../unifi/internal/api/UniFiController.java | 290 +++++++++++++++ .../{model => }/UniFiControllerRequest.java | 131 +++---- .../unifi/internal/api/UniFiException.java | 10 +- .../api/UniFiExpiredSessionException.java | 5 +- .../api/UniFiInvalidCredentialsException.java | 5 +- .../api/UniFiInvalidHostException.java | 9 +- .../api/UniFiNotAuthorizedException.java | 9 +- .../unifi/internal/api/UniFiSSLException.java | 9 +- .../unifi/internal/api/cache/UniFiCache.java | 123 +++++-- .../internal/api/cache/UniFiClientCache.java | 30 +- .../api/cache/UniFiControllerCache.java | 158 +++++++++ .../internal/api/cache/UniFiDeviceCache.java | 15 +- .../internal/api/cache/UniFiSiteCache.java | 24 +- .../internal/api/cache/UniFiWlanCache.java | 48 +++ .../binding/unifi/internal/api/dto/HasId.java | 23 ++ .../internal/api/dto/UnfiPortOverride.java | 72 ++++ .../api/{model => dto}/UniFiClient.java | 66 ++-- .../api/{model => dto}/UniFiDevice.java | 21 +- .../internal/api/dto/UniFiPortOverrides.java | 42 +++ .../internal/api/dto/UniFiPortTable.java | 89 +++++ .../api/{model => dto}/UniFiSite.java | 25 +- .../{model => dto}/UniFiUnknownClient.java | 8 +- .../api/{model => dto}/UniFiWiredClient.java | 8 +- .../{model => dto}/UniFiWirelessClient.java | 7 +- .../unifi/internal/api/dto/UniFiWlan.java | 95 +++++ .../internal/api/model/UniFiController.java | 335 ------------------ .../api/util/UniFiClientDeserializer.java | 24 +- .../api/util/UniFiClientInstanceCreator.java | 31 +- .../api/util/UniFiDeviceInstanceCreator.java | 18 +- .../api/util/UniFiSiteInstanceCreator.java | 22 +- .../UniFiTidyLowerCaseStringDeserializer.java | 10 +- .../api/util/UniFiTimestampDeserializer.java | 20 +- .../api/util/UniFiWlanInstanceCreator.java | 48 +++ .../handler/UniFiBaseThingHandler.java | 148 +++++--- .../handler/UniFiClientThingHandler.java | 226 +++++++----- .../handler/UniFiControllerThingHandler.java | 132 ++++--- .../handler/UniFiPoePortThingHandler.java | 203 +++++++++++ .../handler/UniFiSiteThingHandler.java | 98 +++++ .../handler/UniFiThingDiscoveryService.java | 190 ++++++++++ .../handler/UniFiWlanThingHandler.java | 170 +++++++++ .../unifi/internal/ssl/UniFiTrustManager.java | 28 +- .../ssl/UniFiTrustManagerProvider.java | 2 + .../main/resources/OH-INF/config/config.xml | 94 +++++ .../resources/OH-INF/i18n/unifi.properties | 104 +++++- .../resources/OH-INF/i18n/unifi_de.properties | 12 +- .../resources/OH-INF/i18n/unifi_hu.properties | 12 +- .../resources/OH-INF/i18n/unifi_nl.properties | 143 ++++++++ .../resources/OH-INF/thing/thing-types.xml | 324 ++++++++++++++--- 57 files changed, 3277 insertions(+), 910 deletions(-) create mode 100644 bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/UniFiPoePortThingConfig.java create mode 100644 bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/UniFiSiteThingConfig.java create mode 100644 bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/UniFiWlanThingConfig.java create mode 100644 bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/UniFiController.java rename bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/{model => }/UniFiControllerRequest.java (61%) create mode 100644 bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/cache/UniFiControllerCache.java create mode 100644 bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/cache/UniFiWlanCache.java create mode 100644 bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/dto/HasId.java create mode 100644 bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/dto/UnfiPortOverride.java rename bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/{model => dto}/UniFiClient.java (59%) rename bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/{model => dto}/UniFiDevice.java (72%) create mode 100644 bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/dto/UniFiPortOverrides.java create mode 100644 bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/dto/UniFiPortTable.java rename bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/{model => dto}/UniFiSite.java (60%) rename bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/{model => dto}/UniFiUnknownClient.java (82%) rename bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/{model => dto}/UniFiWiredClient.java (80%) rename bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/{model => dto}/UniFiWirelessClient.java (85%) create mode 100644 bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/dto/UniFiWlan.java delete mode 100644 bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/model/UniFiController.java create mode 100644 bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/util/UniFiWlanInstanceCreator.java create mode 100644 bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/handler/UniFiPoePortThingHandler.java create mode 100644 bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/handler/UniFiSiteThingHandler.java create mode 100644 bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/handler/UniFiThingDiscoveryService.java create mode 100644 bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/handler/UniFiWlanThingHandler.java create mode 100644 bundles/org.openhab.binding.unifi/src/main/resources/OH-INF/config/config.xml create mode 100644 bundles/org.openhab.binding.unifi/src/main/resources/OH-INF/i18n/unifi_nl.properties diff --git a/bundles/org.openhab.binding.unifi/README.md b/bundles/org.openhab.binding.unifi/README.md index 2edd31671..3135eeed7 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 ed7ac82a8..5f994d350 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 7eecdcf82..1ea54d467 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 ad5a136a1..d1ba889bf 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 000000000..d631daee8 --- /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 000000000..24c34282d --- /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 b8ed63124..020efec6d 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 000000000..8725067a1 --- /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 992fbdf6c..693590d68 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 000000000..d0c6678a0 --- /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 b5185fef8..4acf1e0b4 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 93f58b9f1..ce92b56e6 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 c909525c4..cc801aa72 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 8cf8a681a..ee5fde3c2 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 ec9ea6a91..493897f42 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 e89b42447..1bc40ef44 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 59397991c..02043f0bd 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 de7cec15d..44107ff2c 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 eaa07548f..581957739 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 000000000..174062cca --- /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 04382cbaa..8cfd91c97 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 9a9cbdb48..77f7b658a 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 000000000..01dc03430 --- /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 000000000..7f03bd1c9 --- /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 000000000..bd4a41ff0 --- /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 85a819496..28def26fd 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 a086b2d5c..0cd1e1b38 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 000000000..f6645781d --- /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 000000000..637ed2dc5 --- /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 6a54b14a4..f72ef5ec5 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 215d8515f..ab0646e45 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 f91309080..62a0d4d4b 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 ecba2e6fc..9cad57fe5 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 000000000..1eaf52e32 --- /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 0a1d1144d..000000000 --- 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 874505a10..c6362beb5 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 6e902be39..94aa924d1 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 39ce9361a..0b74a07fa 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 f3cb07d7e..fc10ac1f3 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 c628f2787..77caffcd0 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 ae826d0d6..4e7ce74b9 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 000000000..6d14fa1ad --- /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 b9c1a5664..5b8e41bf2 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 d8fbe8bd2..3d610285c 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 562704a53..6c799375f 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 000000000..236f1c091 --- /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 000000000..e996efa95 --- /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 000000000..f204154ca --- /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 000000000..009387bfe --- /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 96148a6f7..b6b258431 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 d090185b7..75c89d9c6 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 000000000..c09f56e78 --- /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 8fe04b3f4..ccd435fd0 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 4758944a5..103fc4ef2 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 dd5fb0839..0989838cf 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 000000000..9d28d6df7 --- /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 87069a33c..40f70b318 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 + + +