[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 <mgb@otr.mx>
Signed-off-by: Hilbrand Bouwkamp <hilbrand@h72.nl>
Co-authored-by: Matthew Bowman <mgb@otr.mx>
This commit is contained in:
Hilbrand Bouwkamp 2022-05-16 23:14:00 +02:00 committed by GitHub
parent d9b5f2d911
commit dec6483e2c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
57 changed files with 3277 additions and 910 deletions

View File

@ -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" }
```

View File

@ -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<ThingTypeUID> 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<ThingTypeUID> 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
}
}

View File

@ -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);

View File

@ -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();
}

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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<Void> req = newRequest(Void.class, HttpMethod.GET, gson);
req.setPath("/");
executeRequest(req);
}
public void login() throws UniFiException {
final UniFiControllerRequest<Void> 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<Void> 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<UniFiSite> sites = refreshSites();
refreshWlans(sites);
refreshDevices(sites);
refreshClients(sites);
refreshInsights(sites);
}
}
public UniFiControllerCache getCache() {
return cache;
}
public @Nullable Map<Integer, UniFiPortTable> getSwitchPorts(@Nullable final String deviceId) {
return cache.getSwitchPorts(deviceId);
}
public void block(final UniFiClient client, final boolean blocked) throws UniFiException {
final UniFiControllerRequest<Void> 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<Void> 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<Integer, UnfiPortOverride> data) throws UniFiException {
final UniFiControllerRequest<Void> 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<Void> 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<Void> 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 <T> UniFiControllerRequest<T> newRequest(final Class<T> responseType, final HttpMethod method,
final Gson gson) {
return new UniFiControllerRequest<>(responseType, gson, httpClient, method, host, port, csrfToken, unifios);
}
private <T> @Nullable T executeRequest(final UniFiControllerRequest<T> request) throws UniFiException {
return executeRequest(request, false);
}
private <T> @Nullable T executeRequest(final UniFiControllerRequest<T> 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<UniFiSite> refreshSites() throws UniFiException {
final UniFiControllerRequest<UniFiSite[]> req = newRequest(UniFiSite[].class, HttpMethod.GET, gson);
req.setAPIPath("/api/self/sites");
return cache.setSites(executeRequest(req));
}
private void refreshWlans(final Collection<UniFiSite> sites) throws UniFiException {
for (final UniFiSite site : sites) {
cache.putWlans(getWlans(site));
}
}
private UniFiWlan @Nullable [] getWlans(final UniFiSite site) throws UniFiException {
final UniFiControllerRequest<UniFiWlan[]> 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<UniFiSite> sites) throws UniFiException {
for (final UniFiSite site : sites) {
cache.putDevices(getDevices(site));
}
}
private UniFiDevice @Nullable [] getDevices(final UniFiSite site) throws UniFiException {
final UniFiControllerRequest<UniFiDevice[]> 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<UniFiSite> sites) throws UniFiException {
for (final UniFiSite site : sites) {
cache.putClients(getClients(site));
}
}
private UniFiClient @Nullable [] getClients(final UniFiSite site) throws UniFiException {
final UniFiControllerRequest<UniFiClient[]> 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<UniFiSite> sites) throws UniFiException {
for (final UniFiSite site : sites) {
cache.putInsights(getInsights(site));
}
}
private UniFiClient @Nullable [] getInsights(final UniFiSite site) throws UniFiException {
final UniFiControllerRequest<UniFiClient[]> 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);
}
}

View File

@ -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 <T> The response type expected as a result of the request's execution
*/
@NonNullByDefault
public class UniFiControllerRequest<T> {
class UniFiControllerRequest<T> {
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<T> {
private final int port;
private String path = "/";
private final boolean unifios;
private final HttpMethod method;
private String path = "/";
private String csrfToken;
private Map<String, String> queryParameters = new HashMap<>();
private final Map<String, String> queryParameters = new HashMap<>();
private Map<String, String> bodyParameters = new HashMap<>();
private final Map<String, Object> bodyParameters = new HashMap<>();
private final Class<T> resultType;
// Public API
public UniFiControllerRequest(Class<T> resultType, Gson gson, HttpClient httpClient, String host, int port,
String csrfToken, boolean unifios) {
public UniFiControllerRequest(final Class<T> 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<T> {
}
}
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<T> {
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<T> {
&& ((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<T> {
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<String, String> entry : queryParameters.entrySet()) {
for (final Entry<String, String> 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<T> {
return request;
}
private String getRequestBodyAsJson() {
JsonObject jsonObject = new JsonObject();
JsonElement jsonElement = null;
for (Entry<String, String> 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;
}
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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;
* <code>prefix:suffix</code> 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<T> {
@NonNullByDefault
abstract class UniFiCache<T extends @Nullable HasId> {
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<String, String> mapToId = new HashMap<>();
// Map of id to data object
private final Map<String, T> map = new HashMap<>();
private final Prefix[] prefixes;
private Map<String, T> 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<T> {
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<T> 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<T> 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;
}
};
}
}

View File

@ -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<UniFiClient> {
@NonNullByDefault
class UniFiClientCache extends UniFiCache<UniFiClient> {
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;
}
}

View File

@ -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<String, Map<Integer, UniFiPortTable>> devicesToPortTables = new ConcurrentHashMap<>();
public void clear() {
sitesCache.clear();
wlansCache.clear();
devicesCache.clear();
clientsCache.clear();
insightsCache.clear();
}
// Sites Cache
public List<UniFiSite> 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<UniFiSite> 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<UniFiWlan> 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<Integer, UniFiPortTable> getSwitchPorts(@Nullable final String deviceId) {
return deviceId == null ? Map.of() : devicesToPortTables.getOrDefault(deviceId, Map.of());
}
public Collection<Map<Integer, UniFiPortTable>> getSwitchPorts() {
return devicesToPortTables.values();
}
// Clients Cache
public void putClients(final UniFiClient @Nullable [] clients) {
clientsCache.putAll(clients);
}
public Collection<UniFiClient> getClients() {
return clientsCache.values();
}
public long countClients(final UniFiSite site, final Function<UniFiClient, Boolean> 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<UniFiClient> 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);
}
}

View File

@ -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<UniFiDevice> {
@NonNullByDefault
class UniFiDeviceCache extends UniFiCache<UniFiDevice> {
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;

View File

@ -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<UniFiSite> {
@NonNullByDefault
class UniFiSiteCache extends UniFiCache<UniFiSite> {
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;
}
}

View File

@ -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: <code>id</code>, <code>name</code>
*
* @author Hilbrand Bouwkamp - Initial contribution
*/
@NonNullByDefault
class UniFiWlanCache extends UniFiCache<UniFiWlan> {
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;
}
}
}

View File

@ -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();
}

View File

@ -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);
}
}

View File

@ -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());
}
}

View File

@ -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

View File

@ -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<UnfiPortOverride> 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])));
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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());
}
}

View File

@ -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<String, String> cidToIdCache = new ConcurrentHashMap<String, String>();
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<Void> req = newRequest(Void.class);
req.setPath("/");
executeRequest(req);
}
public void login() throws UniFiException {
UniFiControllerRequest<Void> 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<Void> 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<Void> 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<Void> 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 <T> UniFiControllerRequest<T> newRequest(Class<T> responseType) {
return new UniFiControllerRequest<>(responseType, gson, httpClient, host, port, csrfToken, unifios);
}
private <T> @Nullable T executeRequest(UniFiControllerRequest<T> 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<UniFiSite[]> 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<UniFiSite> sites = sitesCache.values();
for (UniFiSite site : sites) {
cache.putAll(getDevices(site));
}
return cache;
}
private UniFiDeviceCache getDevices(UniFiSite site) throws UniFiException {
UniFiControllerRequest<UniFiDevice[]> 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<UniFiSite> sites = sitesCache.values();
for (UniFiSite site : sites) {
cache.putAll(getClients(site));
}
return cache;
}
private UniFiClientCache getClients(UniFiSite site) throws UniFiException {
UniFiControllerRequest<UniFiClient[]> 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<UniFiSite> sites = sitesCache.values();
for (UniFiSite site : sites) {
cache.putAll(getInsights(site));
}
return cache;
}
private UniFiClientCache getInsights(UniFiSite site) throws UniFiException {
UniFiControllerRequest<UniFiClient[]> 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;
}
};
}
}

View File

@ -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<UniFiClient> {
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);

View File

@ -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<UniFiClient> {
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;
}
}

View File

@ -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<UniFiDevice> {
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);
}
}

View File

@ -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<UniFiSite> {
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);
}
}
}

View File

@ -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<String> {
@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();
}
}

View File

@ -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<Calendar> {
@NonNullByDefault
public class UniFiTimestampDeserializer implements JsonDeserializer<Instant> {
@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);
}
}

View File

@ -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<UniFiWlan> {
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);
}
}
}

View File

@ -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<E, C> 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<E, C> 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<E, C> 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;
}

View File

@ -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<UniFiClient, UniFiClientThingConfig> {
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<UniFiClient,
}
@Override
protected synchronized @Nullable UniFiClient getEntity(UniFiController controller) {
UniFiClient client = controller.getClient(config.getClientID());
protected @Nullable UniFiClient getEntity(final UniFiControllerCache cache) {
final UniFiClient client = cache.getClient(config.getClientID());
// mgb: short circuit
if (client == null || !belongsToSite(client, config.getSite())) {
return null;
@ -102,10 +112,10 @@ public class UniFiClientThingHandler extends UniFiBaseThingHandler<UniFiClient,
return client;
}
private State getDefaultState(String channelID, boolean clientHome) {
State state = UnDefType.NULL;
@Override
protected State getDefaultState(final String channelID) {
final State state;
switch (channelID) {
case CHANNEL_ONLINE:
case CHANNEL_SITE:
case CHANNEL_AP:
case CHANNEL_ESSID:
@ -113,11 +123,15 @@ public class UniFiClientThingHandler extends UniFiBaseThingHandler<UniFiClient,
case CHANNEL_MAC_ADDRESS:
case CHANNEL_IP_ADDRESS:
case CHANNEL_BLOCKED:
state = (clientHome ? UnDefType.NULL : UnDefType.UNDEF); // skip the update if the client is home
state = UnDefType.UNDEF;
break;
case CHANNEL_UPTIME:
// mgb: uptime should default to 0 seconds
state = (clientHome ? UnDefType.NULL : new DecimalType(0)); // skip the update if the client is home
state = DecimalType.ZERO;
break;
case CHANNEL_EXPERIENCE:
// mgb: uptime + experience should default to 0
state = new QuantityType<>(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<UniFiClient,
case CHANNEL_RECONNECT:
state = OnOffType.OFF;
break;
default:
state = UnDefType.NULL;
break;
}
return state;
}
private synchronized boolean isClientHome(UniFiClient client) {
boolean online = false;
if (client != null) {
Calendar lastSeen = client.getLastSeen();
if (lastSeen == null) {
logger.warn("Could not determine if client is online: cid = {}, lastSeen = null", config.getClientID());
} else {
Calendar considerHome = (Calendar) lastSeen.clone();
considerHome.add(Calendar.SECOND, config.getConsiderHome());
Calendar now = Calendar.getInstance();
online = (now.compareTo(considerHome) < 0);
}
private synchronized boolean isClientHome(final UniFiClient client) {
final boolean online;
final Instant lastSeen = client.getLastSeen();
if (lastSeen == null) {
online = false;
logger.warn("Could not determine if client is online: cid = {}, lastSeen = null", config.getClientID());
} else {
final Instant considerHomeExpiry = lastSeen.plusSeconds(config.getConsiderHome());
online = Instant.now().isBefore(considerHomeExpiry);
}
return online;
}
@Override
protected void refreshChannel(UniFiClient client, ChannelUID channelUID) {
boolean clientHome = isClientHome(client);
UniFiDevice device = client.getDevice();
UniFiSite site = (device == null ? null : device.getSite());
String channelID = channelUID.getIdWithoutGroup();
State state = getDefaultState(channelID, clientHome);
switch (channelID) {
protected State getChannelState(final UniFiClient client, final String channelId) {
final boolean clientHome = isClientHome(client);
final UniFiDevice device = client.getDevice();
final UniFiSite site = (device == null ? null : device.getSite());
State state = getDefaultState(channelId);
switch (channelId) {
// mgb: common wired + wireless client channels
// :online
@ -163,28 +178,28 @@ public class UniFiClientThingHandler extends UniFiBaseThingHandler<UniFiClient,
// :site
case CHANNEL_SITE:
if (clientHome && site != null && site.getDescription() != null && !site.getDescription().isBlank()) {
if (site != null && site.getDescription() != null && !site.getDescription().isBlank()) {
state = StringType.valueOf(site.getDescription());
}
break;
// :macAddress
case CHANNEL_MAC_ADDRESS:
if (clientHome && client.getMac() != null && !client.getMac().isBlank()) {
if (client.getMac() != null && !client.getMac().isBlank()) {
state = StringType.valueOf(client.getMac());
}
break;
// :ipAddress
case CHANNEL_IP_ADDRESS:
if (clientHome && client.getIp() != null && !client.getIp().isBlank()) {
if (client.getIp() != null && !client.getIp().isBlank()) {
state = StringType.valueOf(client.getIp());
}
break;
// :uptime
case CHANNEL_UPTIME:
if (clientHome && client.getUptime() != null) {
if (client.getUptime() != null) {
state = new DecimalType(client.getUptime());
}
break;
@ -193,8 +208,7 @@ public class UniFiClientThingHandler extends UniFiBaseThingHandler<UniFiClient,
case CHANNEL_LAST_SEEN:
// mgb: we don't check clientOnline as lastSeen is also included in the Insights data
if (client.getLastSeen() != null) {
state = new DateTimeType(
ZonedDateTime.ofInstant(client.getLastSeen().toInstant(), ZoneId.systemDefault()));
state = new DateTimeType(ZonedDateTime.ofInstant(client.getLastSeen(), ZoneId.systemDefault()));
}
break;
@ -203,97 +217,119 @@ public class UniFiClientThingHandler extends UniFiBaseThingHandler<UniFiClient,
state = OnOffType.from(client.isBlocked());
break;
// :guest
case CHANNEL_GUEST:
state = OnOffType.from(client.isGuest());
break;
// :experience
case CHANNEL_EXPERIENCE:
if (client.getExperience() != null) {
state = new QuantityType<>(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;
}
}

View File

@ -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<Class<? extends ThingHandlerService>> 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) {

View File

@ -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<Map<Integer, UniFiPortTable>, 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<Integer, UniFiPortTable> getEntity(final UniFiControllerCache cache) {
return cache.getSwitchPorts(config.getMacAddress());
}
@Override
protected State getChannelState(final Map<Integer, UniFiPortTable> 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<Power>(Double.valueOf(port.getPoePower()), Units.WATT);
break;
case CHANNEL_PORT_POE_VOLTAGE:
state = new QuantityType<ElectricPotential>(Double.valueOf(port.getPoeVoltage()), Units.VOLT);
break;
case CHANNEL_PORT_POE_CURRENT:
state = new QuantityType<ElectricCurrent>(Double.valueOf(port.getPoeCurrent()), MILLI(Units.AMPERE));
break;
default:
state = UnDefType.UNDEF;
}
return state;
}
private @Nullable UniFiPortTable getPort(final Map<Integer, UniFiPortTable> ports) {
return ports.get(config.getPortNumber());
}
@Override
protected boolean handleCommand(final UniFiController controller, final Map<Integer, UniFiPortTable> 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<Integer, UniFiPortTable> 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<Integer, UnfiPortOverride> 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;
}
}
}
}

View File

@ -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<UniFiSite, UniFiSiteThingConfig> {
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;
}
}

View File

@ -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<String, Object> 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<String, Object> 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<String, Object> 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<Integer, UniFiPortTable> uc : cache.getSwitchPorts()) {
for (final Entry<Integer, UniFiPortTable> 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<String, Object> 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;
}
}

View File

@ -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<UniFiWlan, UniFiWlanThingConfig> {
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<UniFiClient, Boolean> 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:
* <code>WIFI:S:&lt;SSID>;T:WPA|blank;P:&lt;password>;;</code>
*
* @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;
}
}

View File

@ -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 {
}
}

View File

@ -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

View File

@ -0,0 +1,94 @@
<?xml version="1.0" encoding="UTF-8"?>
<config-description:config-descriptions
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0 https://openhab.org/schemas/config-description-1.0.0.xsd">
<config-description uri="thing-type:unifi:controller">
<parameter name="host" type="text" required="true">
<label>Hostname</label>
<description>Hostname of IP address of the UniFi Controller</description>
<default>unifi</default>
<context>network-address</context>
</parameter>
<parameter name="port" type="integer" max="65535" min="1" required="false">
<label>Port</label>
<description>Port of the UniFi Controller</description>
<default>8443</default>
</parameter>
<parameter name="unifios" type="boolean" required="true">
<label>UniFi OS</label>
<description>If the UniFi Controller is running on UniFi OS.</description>
<default>false</default>
</parameter>
<parameter name="username" type="text" required="true">
<label>Username</label>
<description>The username to access the UniFi Controller.</description>
</parameter>
<parameter name="password" type="text" required="true">
<label>Password</label>
<description>The password to access the UniFi Controller.</description>
<context>password</context>
</parameter>
<parameter name="refresh" type="integer" required="false" unit="s">
<label>Refresh Interval</label>
<description>The refresh interval in seconds to poll the UniFi controller</description>
<default>10</default>
</parameter>
</config-description>
<config-description uri="thing-type:unifi:site">
<parameter name="sid" type="text" required="true">
<label>Site Id</label>
<description>The id, name or description of the site</description>
</parameter>
</config-description>
<config-description uri="thing-type:unifi:wlan">
<parameter name="wid" type="text" required="true">
<label>WLAN Id</label>
<description>The id or name of the wlan</description>
</parameter>
</config-description>
<config-description uri="thing-type:unifi:client">
<parameter name="cid" type="text" required="true">
<label>Client ID</label>
<description>The MAC address, IP address, hostname or alias of the client</description>
</parameter>
<parameter name="site" type="text" required="false">
<label>Site</label>
<description>The site where the client should be found (optional)</description>
</parameter>
<parameter name="considerHome" type="integer" required="false" unit="s">
<label>Consider Home Interval</label>
<description>The interval in seconds to consider the client as home</description>
<default>180</default>
</parameter>
</config-description>
<config-description uri="thing-type:unifi:poePort">
<parameter name="portNumber" type="integer" required="true">
<label>Port Number</label>
<description>The number of the port as reported by the UniFi switch</description>
</parameter>
<parameter name="macAddress" type="text" required="true">
<label>Switch MAC Address</label>
<description>The MAC address of the switch this port is part of</description>
</parameter>
</config-description>
<config-description uri="channel-type:unifi:poeEnable">
<parameter name="mode" type="text">
<label>On Mode</label>
<description>The value to set when setting PoE on.</description>
<options>
<option value="auto">Auto</option>
<option value="24v">24V</option>
<option value="passthrough">Passthrough</option>
</options>
<default>auto</default>
</parameter>
</config-description>
</config-description:config-descriptions>

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -7,47 +7,83 @@
<bridge-type id="controller">
<label>UniFi Controller</label>
<description>A UniFi controller.</description>
<config-description>
<parameter name="host" type="text" required="true">
<label>Hostname</label>
<description>Hostname of IP address of the UniFi Controller</description>
<default>unifi</default>
<context>network-address</context>
</parameter>
<parameter name="port" type="integer" max="65535" min="1" required="false">
<label>Port</label>
<description>Port of the UniFi Controller</description>
<default>8443</default>
</parameter>
<parameter name="unifios" type="boolean" required="true">
<label>UniFi OS</label>
<description>If the UniFi Controller is running on UniFi OS.</description>
<default>false</default>
</parameter>
<parameter name="username" type="text" required="true">
<label>Username</label>
<description>The username to access the UniFi Controller.</description>
</parameter>
<parameter name="password" type="text" required="true">
<label>Password</label>
<description>The password to access the UniFi Controller.</description>
<context>password</context>
</parameter>
<parameter name="refresh" type="integer" required="false">
<label>Refresh Interval</label>
<description>The refresh interval in seconds to poll the UniFi controller</description>
<default>10</default>
</parameter>
</config-description>
<description>A UniFi controller</description>
<config-description-ref uri="thing-type:unifi:controller"/>
</bridge-type>
<!-- <thing-type id="wiredClient"> .. coming soon .. </thing-type> -->
<thing-type id="site">
<supported-bridge-type-refs>
<bridge-type-ref id="controller"/>
</supported-bridge-type-refs>
<label>UniFi Site</label>
<description>A site defined in a UniFi network</description>
<channels>
<channel id="totalClients" typeId="totalClients"/>
<channel id="wirelessClients" typeId="wirelessClients"/>
<channel id="wiredClients" typeId="wiredClients"/>
<channel id="guestClients" typeId="guestClients"/>
</channels>
<representation-property>sid</representation-property>
<config-description-ref uri="thing-type:unifi:site"/>
</thing-type>
<thing-type id="wlan">
<supported-bridge-type-refs>
<bridge-type-ref id="controller"/>
</supported-bridge-type-refs>
<label>UniFi WLAN</label>
<description>A UniFi Wireless LAN</description>
<channels>
<channel id="enable" typeId="wlanEnable"/>
<channel id="wirelessClients" typeId="wirelessClients"/>
<channel id="guestClients" typeId="guestClients"/>
<channel id="essid" typeId="wlanEssid"/>
<channel id="site" typeId="site"/>
<channel id="security" typeId="security"/>
<channel id="wlanBand" typeId="wlanBand"/>
<channel id="wpaEnc" typeId="wpaEnc"/>
<channel id="wpaMode" typeId="wpaMode"/>
<channel id="passphrase" typeId="passphrase"/>
<channel id="qrcodeEncoding" typeId="qrcodeEncoding"/>
</channels>
<representation-property>wid</representation-property>
<config-description-ref uri="thing-type:unifi:wlan"/>
</thing-type>
<thing-type id="wiredClient">
<supported-bridge-type-refs>
<bridge-type-ref id="controller"/>
</supported-bridge-type-refs>
<label>UniFi Wired Client</label>
<description>A wired client connected to a UniFi switch</description>
<channels>
<!-- common wired + wireless client channels -->
<channel id="online" typeId="online"/>
<channel id="site" typeId="site"/>
<channel id="macAddress" typeId="macAddress"/>
<channel id="ipAddress" typeId="ipAddress"/>
<channel id="uptime" typeId="uptime"/>
<channel id="lastSeen" typeId="lastSeen"/>
<channel id="blocked" typeId="blocked"/>
<channel id="experience" typeId="experience"/>
</channels>
<representation-property>cid</representation-property>
<config-description-ref uri="thing-type:unifi:client"/>
</thing-type>
<thing-type id="wirelessClient">
<supported-bridge-type-refs>
<bridge-type-ref id="controller"/>
</supported-bridge-type-refs>
@ -63,44 +99,138 @@
<channel id="uptime" typeId="uptime"/>
<channel id="lastSeen" typeId="lastSeen"/>
<channel id="blocked" typeId="blocked"/>
<channel id="experience" typeId="experience"/>
<!-- additional wireless client channels -->
<channel id="guest" typeId="guest"/>
<channel id="ap" typeId="ap"/>
<channel id="essid" typeId="essid"/>
<channel id="rssi" typeId="rssi"/>
<channel id="cmd" typeId="wirelessCmd"/>
<channel id="reconnect" typeId="reconnect"/>
</channels>
<representation-property>cid</representation-property>
<config-description>
<parameter name="cid" type="text" required="true">
<label>Client ID</label>
<description>The MAC address, IP address, hostname or alias of the client</description>
</parameter>
<parameter name="site" type="text" required="false">
<label>Site</label>
<description>The site where the client should be found (optional)</description>
</parameter>
<parameter name="considerHome" type="integer" required="false">
<label>Consider Home Interval</label>
<description>The interval in seconds to consider the client as home</description>
<default>180</default>
</parameter>
</config-description>
<config-description-ref uri="thing-type:unifi:client"/>
</thing-type>
<thing-type id="poePort">
<supported-bridge-type-refs>
<bridge-type-ref id="controller"/>
</supported-bridge-type-refs>
<label>UniFi PoE Port</label>
<description>A Power Over Ethernet (PoE) port on a UniFi switch</description>
<channels>
<channel id="online" typeId="portOnline"/>
<channel id="mode" typeId="poeMode"/>
<channel id="enable" typeId="poeEnable"/>
<channel id="cmd" typeId="poeCmd"/>
<channel id="power" typeId="poePower"/>
<channel id="voltage" typeId="poeVoltage"/>
<channel id="current" typeId="poeCurrent"/>
</channels>
<config-description-ref uri="thing-type:unifi:poePort"/>
</thing-type>
<!-- Channels -->
<channel-type id="totalClients">
<item-type>Number</item-type>
<label>Total Clients</label>
<description>Total number of clients connected</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="wirelessClients">
<item-type>Number</item-type>
<label>Wireless Clients</label>
<description>Number of wireless clients connected</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="wiredClients">
<item-type>Number</item-type>
<label>Wired Clients</label>
<description>Number of wired clients connected</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="guestClients">
<item-type>Number</item-type>
<label>Guest Clients</label>
<description>Number of guest clients connected</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="wlanEnable">
<item-type>Switch</item-type>
<label>Enable</label>
<description>Enable status of the wLAN</description>
</channel-type>
<channel-type id="wlanEssid">
<item-type>String</item-type>
<label>Wireless Network</label>
<description>Wireless Network (ESSID)</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="security" advanced="true">
<item-type>String</item-type>
<label>Security</label>
<description>Security protocol of the Wi-Fi network</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="wlanBand" advanced="true">
<item-type>String</item-type>
<label>WLAN Band</label>
<description>Wireless LAN band of the Wi-Fi network</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="wpaEnc" advanced="true">
<item-type>String</item-type>
<label>WPA Encoding</label>
<description>WPA Encoding of the Wi-Fi network</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="wpaMode" advanced="true">
<item-type>String</item-type>
<label>WPA Mode</label>
<description>WPA Mode of the Wi-Fi network</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="passphrase" advanced="true">
<item-type>String</item-type>
<label>Passphrase</label>
<description>Passphrase of the Wi-Fi network</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="qrcodeEncoding">
<item-type>String</item-type>
<label>QR Code Encoding</label>
<description>MECARD like encoding to generate a QRCode for easy access to the Wi-Fi network</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="online">
<item-type>Switch</item-type>
<label>Online</label>
<description>Online status of the wireless client</description>
<description>Online status of the client</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="site">
<item-type>String</item-type>
<label>Site Name</label>
<description>UniFi Site the client is associated with</description>
<description>UniFi Site the device is associated with</description>
<state readOnly="true"></state>
</channel-type>
@ -159,10 +289,94 @@
<description>Is device blocked</description>
</channel-type>
<channel-type id="reconnect">
<channel-type id="guest">
<item-type>Switch</item-type>
<label>Guest</label>
<description>Is the client connected a guest</description>
</channel-type>
<channel-type id="experience">
<item-type>Number:Dimensionless</item-type>
<label>Experience</label>
<description>The wired/wireless experience of the client</description>
<state pattern="%d %unit%" readOnly="true"></state>
</channel-type>
<channel-type id="reconnect" advanced="true">
<item-type>Switch</item-type>
<label>Reconnect</label>
<description>Forces a client to reconnect</description>
</channel-type>
<channel-type id="wirelessCmd">
<item-type>String</item-type>
<label>Wireless Command</label>
<description>Command that can be given to the wireless client</description>
<command>
<options>
<option value="reconnect">Reconnect</option>
</options>
</command>
</channel-type>
<channel-type id="portOnline">
<item-type>Switch</item-type>
<label>Port Active</label>
<description>PoE port is active</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="poeEnable">
<item-type>Switch</item-type>
<label>Enabled</label>
<description>If PoE is enabled</description>
<config-description-ref uri="channel-type:unifi:poeEnable"/>
</channel-type>
<channel-type id="poeMode">
<item-type>String</item-type>
<label>PoE Mode</label>
<description>The PoE mode the port is in</description>
<state>
<options>
<option value="off">Off</option>
<option value="auto">Auto</option>
<option value="24v">24V</option>
<option value="passthrough">Passthrough</option>
</options>
</state>
</channel-type>
<channel-type id="poeCmd">
<item-type>String</item-type>
<label>PoE Command</label>
<description>Command that can be given to the PoE port</description>
<command>
<options>
<option value="power-cycle">Power Cycle</option>
</options>
</command>
</channel-type>
<channel-type id="poePower" advanced="true">
<item-type>Number:Power</item-type>
<label>Port PoE Power</label>
<description>Power usage of the PoE port</description>
<state pattern="%.2f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="poeVoltage" advanced="true">
<item-type>Number:ElectricPotential</item-type>
<label>Port PoE Voltage</label>
<description>Voltage usage of the PoE port</description>
<state pattern="%.2f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="poeCurrent" advanced="true">
<item-type>Number:ElectricCurrent</item-type>
<label>Port PoE Current</label>
<description>Current usage of the PoE port</description>
<state pattern="%.2f %unit%" readOnly="true"/>
</channel-type>
</thing:thing-descriptions>