## 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:
## 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 |
| ------------ | -------------------------------------------------------------|--------- | ------- |
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`
##### `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:
-
-| 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 |
+### `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 |
+
_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
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" }
```
*/
package org.openhab.binding.unifi.internal;
+import java.util.Set;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
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";
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";
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
+ }
}
*/
package org.openhab.binding.unifi.internal;
+import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.unifi.internal.handler.UniFiClientThingHandler;
/**
*
* @author Matthew Bowman - Initial contribution
*/
+@NonNullByDefault
+@SuppressWarnings("unused")
public class UniFiClientThingConfig {
private String cid = "";
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;
}
- public int getConsiderHome() {
- return considerHome;
+ private void setSite(final String site) {
+ // method to avoid ide auto format mark the field as final
+ this.site = site;
}
- public UniFiClientThingConfig tidy() {
- cid = cid.trim().toLowerCase();
- site = site.trim().toLowerCase();
- return this;
+ public int getConsiderHome() {
+ return considerHome;
}
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);
*/
package org.openhab.binding.unifi.internal;
+import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.unifi.internal.handler.UniFiControllerThingHandler;
/**
*
* @author Matthew Bowman - Initial contribution
*/
+@NonNullByDefault
+@SuppressWarnings("unused")
public class UniFiControllerThingConfig {
private String host = "unifi";
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();
}
--- /dev/null
+/**
+ * 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();
+ }
+}
--- /dev/null
+/**
+ * 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);
+ }
+}
*/
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;
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;
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
+ public boolean supportsThingType(final ThingTypeUID thingTypeUID) {
+ return ALL_THING_TYPE_SUPPORTED.contains(thingTypeUID);
}
@Override
- protected @Nullable ThingHandler createHandler(Thing thing) {
- ThingTypeUID thingTypeUID = thing.getThingTypeUID();
- if (UniFiControllerThingHandler.supportsThingType(thingTypeUID)) {
+ 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;
}
--- /dev/null
+/**
+ * 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);
+ }
+}
*/
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);
}
}
--- /dev/null
+/**
+ * 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);
+ }
+}
--- /dev/null
+/**
+ * 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.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.ConnectException;
+import java.net.UnknownHostException;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import javax.net.ssl.SSLException;
+
+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.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.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+
+/**
+ * The {@link UniFiControllerRequest} encapsulates a request sent by the {@link UniFiController}.
+ *
+ * @author Matthew Bowman - Initial contribution
+ *
+ * @param <T> The response type expected as a result of the request's execution
+ */
+@NonNullByDefault
+class UniFiControllerRequest<T> {
+
+ private static final String CONTENT_TYPE_APPLICATION_JSON_UTF_8 = MimeTypes.Type.APPLICATION_JSON_UTF_8.asString();
+
+ private static final long TIMEOUT_SECONDS = 5;
+
+ private static final String PROPERTY_DATA = "data";
+
+ private final Logger logger = LoggerFactory.getLogger(UniFiControllerRequest.class);
+
+ private final Gson gson;
+
+ private final HttpClient httpClient;
+
+ private final String host;
+
+ private final int port;
+
+ private final boolean unifios;
+
+ private final HttpMethod method;
+
+ private String path = "/";
+
+ private String csrfToken;
+
+ private final Map<String, String> queryParameters = new HashMap<>();
+
+ private final Map<String, Object> bodyParameters = new HashMap<>();
+
+ private final Class<T> resultType;
+
+ // Public API
+
+ 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(final String relativePath) {
+ if (unifios) {
+ this.path = "/proxy/network" + relativePath;
+ } else {
+ this.path = relativePath;
+ }
+ }
+
+ public void setPath(final String path) {
+ this.path = path;
+ }
+
+ public void setBodyParameter(final String key, final Object value) {
+ this.bodyParameters.put(key, 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;
+ final String json = getContent();
+ // mgb: only try and unmarshall non-void result types
+ if (!Void.class.equals(resultType)) {
+ 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);
+ }
+ }
+ return result;
+ }
+
+ // Private API
+
+ private String getContent() throws UniFiException {
+ String content;
+ final InputStreamResponseListener listener = new InputStreamResponseListener();
+ final Response response = getContentResponse(listener);
+ final int status = response.getStatus();
+ switch (status) {
+ case HttpStatus.OK_200:
+ content = responseToString(listener);
+ if (logger.isTraceEnabled()) {
+ logger.trace("<< {} {} \n{}", status, HttpStatus.getMessage(status), prettyPrintJson(content));
+ }
+
+ 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 Response getContentResponse(final InputStreamResponseListener listener) throws UniFiException {
+ final Request request = newRequest();
+ logger.trace(">> {} {}", request.getMethod(), request.getURI());
+ Response response;
+ try {
+ request.send(listener);
+ response = listener.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
+ } catch (TimeoutException | InterruptedException e) {
+ throw new UniFiCommunicationException(e);
+ } catch (final ExecutionException e) {
+ // mgb: unwrap the cause and try to cleanly handle it
+ final Throwable cause = e.getCause();
+ if (cause instanceof UnknownHostException) {
+ // invalid hostname
+ throw new UniFiInvalidHostException(cause);
+ } else if (cause instanceof ConnectException) {
+ // cannot connect
+ throw new UniFiCommunicationException(cause);
+ } else if (cause instanceof SSLException) {
+ // cannot establish ssl connection
+ throw new UniFiSSLException(cause);
+ } else if (cause instanceof HttpResponseException
+ && ((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 ExecutionException to be thrown
+ // - we unwrap the response from the exception for proper handling of the 401 status code
+ response = ((HttpResponseException) cause).getResponse();
+ } else {
+ // catch all
+ throw new UniFiException(cause);
+ }
+ }
+ 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() {
+ 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 (final Entry<String, String> entry : queryParameters.entrySet()) {
+ request.param(entry.getKey(), entry.getValue());
+ }
+ if (!bodyParameters.isEmpty()) {
+ final String jsonBody = gson.toJson(bodyParameters);
+
+ request.content(
+ new StringContentProvider(CONTENT_TYPE_APPLICATION_JSON_UTF_8, jsonBody, StandardCharsets.UTF_8));
+ }
+
+ if (!csrfToken.isEmpty()) {
+ request.header("x-csrf-token", this.csrfToken);
+ }
+
+ return request;
+ }
+
+ private static String prettyPrintJson(final String content) {
+ try {
+ final JsonObject json = JsonParser.parseString(content).getAsJsonObject();
+ final Gson prettyGson = new GsonBuilder().setPrettyPrinting().create();
+
+ return prettyGson.toJson(json);
+ } catch (final RuntimeException e) {
+ // If could not parse the string as json, just return the string
+ return content;
+ }
+ }
+}
*/
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);
}
}
*/
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);
}
}
*/
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);
}
}
*/
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);
}
}
*/
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);
}
}
*/
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);
}
}
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;
* <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";
+ 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;
- public static final String PREFIX_NAME = "name";
+ protected UniFiCache(final Prefix... prefixes) {
+ this.prefixes = prefixes;
+ }
- private final Logger logger = LoggerFactory.getLogger(getClass());
+ public void clear() {
+ map.clear();
+ }
- private Map<String, T> map = new HashMap<>();
+ public final @Nullable T get(final @Nullable String cid) {
+ final @Nullable T value;
- private String[] prefixes;
+ if (cid != null && !cid.isBlank()) {
+ synchronized (this) {
+ final String id = getId(cid);
- protected UniFiCache(String... prefixes) {
- this.prefixes = prefixes;
+ 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 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 @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 {
return value;
}
- public final void put(T value) {
- for (String prefix : prefixes) {
- String suffix = getSuffix(value, prefix);
+ 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 put(final String id, final T value) {
+ for (final Prefix prefix : prefixes) {
+ final String suffix = getSuffix(value, prefix);
+
if (suffix != null && !suffix.isBlank()) {
- String key = prefix + SEPARATOR + suffix;
- map.put(key, value);
+ mapToId.put(key(prefix, suffix), id);
}
}
+ map.put(id, value);
}
- public final void putAll(UniFiCache<T> cache) {
- map.putAll(cache.map);
+ 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;
+ }
+ };
+ }
}
*/
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
*
* @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;
}
}
--- /dev/null
+/**
+ * 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);
+ }
+}
*/
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
*
* @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;
*/
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
*
* @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;
}
}
--- /dev/null
+/**
+ * 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;
+ }
+ }
+}
--- /dev/null
+/**
+ * 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();
+}
--- /dev/null
+/**
+ * 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);
+ }
+}
--- /dev/null
+/**
+ * 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.time.Instant;
+
+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;
+
+import com.google.gson.annotations.JsonAdapter;
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * The {@link UniFiClient} is the base data model for any (wired or wireless) connected to a UniFi network.
+ *
+ * @author Matthew Bowman - Initial contribution
+ * @author Patrik Wimnell - Blocking / Unblocking client support
+ */
+public abstract class UniFiClient implements HasId {
+
+ private final transient UniFiControllerCache cache;
+
+ @SerializedName("_id")
+ private String id;
+
+ private String siteId;
+
+ @JsonAdapter(UniFiTidyLowerCaseStringDeserializer.class)
+ private String mac;
+
+ private String ip;
+
+ @JsonAdapter(UniFiTidyLowerCaseStringDeserializer.class)
+ private String hostname;
+
+ @SerializedName("name")
+ @JsonAdapter(UniFiTidyLowerCaseStringDeserializer.class)
+ private String alias;
+
+ private Integer uptime;
+
+ @JsonAdapter(UniFiTimestampDeserializer.class)
+ private Instant lastSeen;
+
+ private boolean blocked;
+
+ @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;
+ }
+
+ public String getMac() {
+ return mac;
+ }
+
+ public String getIp() {
+ return this.ip == null || this.ip.isBlank() ? this.fixedIp : this.ip;
+ }
+
+ public String getHostname() {
+ return hostname;
+ }
+
+ public String getAlias() {
+ return alias;
+ }
+
+ public Integer getUptime() {
+ return uptime;
+ }
+
+ public Instant getLastSeen() {
+ return lastSeen;
+ }
+
+ public boolean isBlocked() {
+ return blocked;
+ }
+
+ public abstract Boolean isWired();
+
+ public final Boolean isWireless() {
+ return isWired() == null ? null : Boolean.FALSE.equals(isWired());
+ }
+
+ protected abstract String getDeviceMac();
+
+ public UniFiSite getSite() {
+ return cache.getSite(siteId);
+ }
+
+ public UniFiDevice getDevice() {
+ return cache.getDevice(getDeviceMac());
+ }
+
+ public boolean isGuest() {
+ return guest;
+ }
+
+ 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, guest: %b, blocked: %b, experience: %d, device: %s}",
+ id, mac, getIp(), hostname, alias, isWired(), guest, blocked, experience, getDevice());
+ }
+}
--- /dev/null
+/**
+ * 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 org.openhab.binding.unifi.internal.api.util.UniFiTidyLowerCaseStringDeserializer;
+
+import com.google.gson.annotations.JsonAdapter;
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * The {@link UniFiDevice} represents the data model of a UniFi Wireless Device
+ * (better known as an Access Point).
+ *
+ * @author Matthew Bowman - Initial contribution
+ * @author Hilbrand Bouwkamp - Added PoEPort support
+ */
+public class UniFiDevice implements HasId {
+
+ protected final transient UniFiControllerCache cache;
+
+ @SerializedName("_id")
+ private String id;
+
+ @JsonAdapter(UniFiTidyLowerCaseStringDeserializer.class)
+ private String mac;
+
+ private String model;
+
+ private String name;
+
+ private String siteId;
+
+ private UniFiPortTable[] portTable;
+
+ public UniFiDevice(final UniFiControllerCache cache) {
+ this.cache = cache;
+ }
+
+ @Override
+ public String getId() {
+ return id;
+ }
+
+ public String getModel() {
+ return model;
+ }
+
+ public String getName() {
+ return name == null || name.isBlank() ? mac : name;
+ }
+
+ public String getMac() {
+ return mac;
+ }
+
+ public UniFiSite getSite() {
+ return cache.getSite(siteId);
+ }
+
+ public UniFiPortTable[] getPortTable() {
+ return portTable;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("UniFiDevice{mac: '%s', name: '%s', model: '%s', site: %s}", mac, name, model, getSite());
+ }
+}
--- /dev/null
+/**
+ * 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])));
+ }
+}
--- /dev/null
+/**
+ * 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);
+ }
+}
--- /dev/null
+/**
+ * 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;
+
+/**
+ * The {@link UniFiSite} represents the data model of a UniFi site.
+ *
+ * @author Matthew Bowman - Initial contribution
+ */
+public class UniFiSite implements HasId {
+
+ private final transient UniFiControllerCache cache;
+
+ public UniFiSite(final UniFiControllerCache cache) {
+ this.cache = cache;
+ }
+
+ @SerializedName("_id")
+ private String id;
+
+ private String name;
+
+ private String desc;
+
+ @Override
+ public String getId() {
+ return id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getDescription() {
+ return desc;
+ }
+
+ 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{id: '%s', name: '%s', desc: '%s'}", id, name, desc);
+ }
+}
--- /dev/null
+/**
+ * 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;
+
+/**
+ * A {@link UniFiUnknownClient} represents an unknown {@link UniFiClient}.
+ *
+ * An unknown client is neither a {@link UniFiWiredClient} nor a {@link UniFiWirelessClient}
+ * because the <code>is_wired</code> property was missing from the JSON response of the controller.
+ *
+ * @author Matthew Bowman - Initial contribution
+ */
+public class UniFiUnknownClient extends UniFiClient {
+
+ public UniFiUnknownClient(final UniFiControllerCache cache) {
+ super(cache);
+ }
+
+ @Override
+ public Boolean isWired() {
+ return null; // mgb: no is_wired property in the json
+ }
+
+ @Override
+ public String getDeviceMac() {
+ return null; // mgb: no device mac in the json
+ }
+}
--- /dev/null
+/**
+ * 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;
+
+/**
+ * A {@link UniFiWiredClient} represents a wired {@link UniFiClient}.
+ *
+ * A wired client is physically connected to the network - typically it is connected via an Ethernet cable.
+ *
+ * @author Matthew Bowman - Initial contribution
+ */
+public class UniFiWiredClient extends UniFiClient {
+
+ private String swMac;
+
+ public UniFiWiredClient(final UniFiControllerCache cache) {
+ super(cache);
+ }
+
+ @Override
+ public Boolean isWired() {
+ return true;
+ }
+
+ @Override
+ public String getDeviceMac() {
+ return swMac;
+ }
+}
--- /dev/null
+/**
+ * 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 org.openhab.binding.unifi.internal.api.util.UniFiTidyLowerCaseStringDeserializer;
+
+import com.google.gson.annotations.JsonAdapter;
+
+/**
+ * A {@link UniFiWirelessClient} represents a wireless {@link UniFiClient}.
+ *
+ * A wireless client is not physically connected to the network - typically it is connected via a Wi-Fi adapter.
+ *
+ * @author Matthew Bowman - Initial contribution
+ */
+public class UniFiWirelessClient extends UniFiClient {
+
+ @JsonAdapter(UniFiTidyLowerCaseStringDeserializer.class)
+ private String apMac;
+
+ private String essid;
+
+ private Integer rssi;
+
+ public UniFiWirelessClient(final UniFiControllerCache cache) {
+ super(cache);
+ }
+
+ @Override
+ public Boolean isWired() {
+ return false;
+ }
+
+ @Override
+ public String getDeviceMac() {
+ return apMac;
+ }
+
+ public String getEssid() {
+ return essid;
+ }
+
+ public Integer getRssi() {
+ return rssi;
+ }
+}
--- /dev/null
+/**
+ * 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());
+ }
+}
+++ /dev/null
-/**
- * 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.Calendar;
-
-import org.openhab.binding.unifi.internal.api.UniFiException;
-import org.openhab.binding.unifi.internal.api.util.UniFiTidyLowerCaseStringDeserializer;
-import org.openhab.binding.unifi.internal.api.util.UniFiTimestampDeserializer;
-
-import com.google.gson.annotations.JsonAdapter;
-import com.google.gson.annotations.SerializedName;
-
-/**
- * The {@link UniFiClient} is the base data model for any (wired or wireless) connected to a UniFi network.
- *
- * @author Matthew Bowman - Initial contribution
- * @author Patrik Wimnell - Blocking / Unblocking client support
- */
-public abstract class UniFiClient {
-
- protected final transient UniFiController controller;
-
- @SerializedName("_id")
- protected String id;
-
- protected String siteId;
-
- @JsonAdapter(UniFiTidyLowerCaseStringDeserializer.class)
- protected String mac;
-
- protected String ip;
-
- @JsonAdapter(UniFiTidyLowerCaseStringDeserializer.class)
- protected String hostname;
-
- @SerializedName("name")
- @JsonAdapter(UniFiTidyLowerCaseStringDeserializer.class)
- protected String alias;
-
- protected Integer uptime;
-
- @JsonAdapter(UniFiTimestampDeserializer.class)
- protected Calendar lastSeen;
-
- protected boolean blocked;
-
- protected UniFiClient(UniFiController controller) {
- this.controller = controller;
- }
-
- public String getId() {
- return id;
- }
-
- public String getMac() {
- return mac;
- }
-
- public String getIp() {
- return this.ip;
- }
-
- public String getHostname() {
- return hostname;
- }
-
- public String getAlias() {
- return alias;
- }
-
- public Integer getUptime() {
- return uptime;
- }
-
- public Calendar getLastSeen() {
- return lastSeen;
- }
-
- public boolean isBlocked() {
- return blocked;
- }
-
- public abstract Boolean isWired();
-
- public final Boolean isWireless() {
- return isWired() == null ? null : (isWired().booleanValue() ? Boolean.FALSE : Boolean.TRUE);
- }
-
- protected abstract String getDeviceMac();
-
- public UniFiSite getSite() {
- return controller.getSite(siteId);
- }
-
- public UniFiDevice getDevice() {
- return controller.getDevice(getDeviceMac());
- }
-
- // Functional API
-
- public void block(boolean blocked) throws UniFiException {
- controller.block(this, blocked);
- }
-
- public void reconnect() throws UniFiException {
- controller.reconnect(this);
- }
-
- @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());
- }
-}
+++ /dev/null
-/**
- * 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;
- }
- };
- }
-}
+++ /dev/null
-/**
- * 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.net.ConnectException;
-import java.net.UnknownHostException;
-import java.nio.charset.StandardCharsets;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-
-import javax.net.ssl.SSLException;
-
-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.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}.
- *
- * @author Matthew Bowman - Initial contribution
- *
- * @param <T> The response type expected as a result of the request's execution
- */
-@NonNullByDefault
-public class UniFiControllerRequest<T> {
-
- private static final String CONTENT_TYPE_APPLICATION_JSON = MimeTypes.Type.APPLICATION_JSON.asString();
-
- private static final long TIMEOUT_SECONDS = 5;
-
- private static final String PROPERTY_DATA = "data";
-
- private final Logger logger = LoggerFactory.getLogger(UniFiControllerRequest.class);
-
- private final Gson gson;
-
- private final HttpClient httpClient;
-
- private final String host;
-
- private final int port;
-
- private String path = "/";
-
- private final boolean unifios;
-
- private String csrfToken;
-
- private Map<String, String> queryParameters = new HashMap<>();
-
- private Map<String, String> 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) {
- this.resultType = resultType;
- this.gson = gson;
- this.httpClient = httpClient;
- this.host = host;
- this.port = port;
- this.csrfToken = csrfToken;
- this.unifios = unifios;
- }
-
- public void setAPIPath(String relativePath) {
- if (unifios) {
- this.path = "/proxy/network" + relativePath;
- } else {
- this.path = relativePath;
- }
- }
-
- public void setPath(String path) {
- this.path = path;
- }
-
- public void setBodyParameter(String key, Object value) {
- this.bodyParameters.put(key, String.valueOf(value));
- }
-
- public void setQueryParameter(String key, Object value) {
- this.queryParameters.put(key, String.valueOf(value));
- }
-
- public @Nullable T execute() throws UniFiException {
- T result = null;
- String json = getContent();
- // mgb: only try and unmarshall non-void result types
- if (!Void.class.equals(resultType)) {
- JsonObject jsonObject = JsonParser.parseString(json).getAsJsonObject();
- if (jsonObject.has(PROPERTY_DATA) && jsonObject.get(PROPERTY_DATA).isJsonArray()) {
- result = gson.fromJson(jsonObject.getAsJsonArray(PROPERTY_DATA), resultType);
- }
- }
- return result;
- }
-
- // Private API
-
- private String getContent() throws UniFiException {
- String content;
- ContentResponse response = getContentResponse();
- int status = response.getStatus();
- switch (status) {
- case HttpStatus.OK_200:
- content = response.getContentAsString();
- if (logger.isTraceEnabled()) {
- logger.trace("<< {} {} \n{}", status, HttpStatus.getMessage(status), prettyPrintJson(content));
- }
-
- String csrfToken = response.getHeaders().get("X-CSRF-Token");
- if (csrfToken != null && !csrfToken.isEmpty()) {
- this.csrfToken = csrfToken;
- }
- break;
- case HttpStatus.BAD_REQUEST_400:
- throw new UniFiInvalidCredentialsException("Invalid Credentials");
- case HttpStatus.UNAUTHORIZED_401:
- throw new UniFiExpiredSessionException("Expired Credentials");
- case HttpStatus.FORBIDDEN_403:
- throw new UniFiNotAuthorizedException("Unauthorized Access");
- default:
- throw new UniFiException("Unknown HTTP status code " + status + " returned by the controller");
- }
- return content;
- }
-
- private ContentResponse getContentResponse() throws UniFiException {
- Request request = newRequest();
- logger.trace(">> {} {}", request.getMethod(), request.getURI());
- ContentResponse response;
- try {
- response = request.send();
- } catch (TimeoutException | InterruptedException e) {
- throw new UniFiCommunicationException(e);
- } catch (ExecutionException e) {
- // mgb: unwrap the cause and try to cleanly handle it
- Throwable cause = e.getCause();
- if (cause instanceof UnknownHostException) {
- // invalid hostname
- throw new UniFiInvalidHostException(cause);
- } else if (cause instanceof ConnectException) {
- // cannot connect
- throw new UniFiCommunicationException(cause);
- } else if (cause instanceof SSLException) {
- // cannot establish ssl connection
- throw new UniFiSSLException(cause);
- } else if (cause instanceof HttpResponseException
- && ((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
- // - we unwrap the response from the exception for proper handling of the 401 status code
- response = (ContentResponse) ((HttpResponseException) cause).getResponse();
- } else {
- // catch all
- throw new UniFiException(cause);
- }
- }
- return response;
- }
-
- 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)
- .method(method);
- for (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);
- }
-
- if (!csrfToken.isEmpty()) {
- request.header("x-csrf-token", this.csrfToken);
- }
-
- 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(String content) {
- JsonObject json = JsonParser.parseString(content).getAsJsonObject();
- Gson prettyGson = new GsonBuilder().setPrettyPrinting().create();
- return prettyGson.toJson(json);
- }
-}
+++ /dev/null
-/**
- * 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 org.openhab.binding.unifi.internal.api.util.UniFiTidyLowerCaseStringDeserializer;
-
-import com.google.gson.annotations.JsonAdapter;
-import com.google.gson.annotations.SerializedName;
-
-/**
- * The {@link UniFiDevice} represents the data model of a UniFi Wireless Device
- * (better known as an Access Point).
- *
- * @author Matthew Bowman - Initial contribution
- */
-public class UniFiDevice {
-
- protected final transient UniFiController controller;
-
- @SerializedName("_id")
- private String id;
-
- @JsonAdapter(UniFiTidyLowerCaseStringDeserializer.class)
- private String mac;
-
- private String model;
-
- private String name;
-
- private String siteId;
-
- public UniFiDevice(UniFiController controller) {
- this.controller = controller;
- }
-
- public String getId() {
- return id;
- }
-
- public String getModel() {
- return model;
- }
-
- public String getName() {
- return name == null || name.isBlank() ? mac : name;
- }
-
- public String getMac() {
- return mac;
- }
-
- public UniFiSite getSite() {
- return controller.getSite(siteId);
- }
-
- @Override
- public String toString() {
- return String.format("UniFiDevice{mac: '%s', name: '%s', model: '%s', site: %s}", mac, name, model, getSite());
- }
-}
+++ /dev/null
-/**
- * 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 com.google.gson.annotations.SerializedName;
-
-/**
- * The {@link UniFiSite} represents the data model of a UniFi site.
- *
- * @author Matthew Bowman - Initial contribution
- */
-public class UniFiSite {
-
- private final transient UniFiController controller;
-
- public UniFiSite(UniFiController controller) {
- this.controller = controller;
- }
-
- @SerializedName("_id")
- private String id;
-
- private String name;
-
- private String desc;
-
- public String getId() {
- return id;
- }
-
- public String getName() {
- return name;
- }
-
- public String getDescription() {
- return desc;
- }
-
- public boolean matchesName(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);
- }
-}
+++ /dev/null
-/**
- * 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;
-
-/**
- * A {@link UniFiUnknownClient} represents an unknown {@link UniFiClient}.
- *
- * An unknown client is neither a {@link UniFiWiredClient} nor a {@link UniFiWirelessClient}
- * because the <code>is_wired</code> property was missing from the JSON response of the controller.
- *
- * @author Matthew Bowman - Initial contribution
- */
-public class UniFiUnknownClient extends UniFiClient {
-
- public UniFiUnknownClient(UniFiController controller) {
- super(controller);
- }
-
- @Override
- public Boolean isWired() {
- return null; // mgb: no is_wired property in the json
- }
-
- @Override
- public String getDeviceMac() {
- return null; // mgb: no device mac in the json
- }
-}
+++ /dev/null
-/**
- * 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;
-
-/**
- * A {@link UniFiWiredClient} represents a wired {@link UniFiClient}.
- *
- * A wired client is physically connected to the network - typically it is connected via an Ethernet cable.
- *
- * @author Matthew Bowman - Initial contribution
- */
-public class UniFiWiredClient extends UniFiClient {
-
- private String swMac;
-
- public UniFiWiredClient(UniFiController controller) {
- super(controller);
- }
-
- @Override
- public Boolean isWired() {
- return true;
- }
-
- @Override
- public String getDeviceMac() {
- return swMac;
- }
-}
+++ /dev/null
-/**
- * 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 org.openhab.binding.unifi.internal.api.util.UniFiTidyLowerCaseStringDeserializer;
-
-import com.google.gson.annotations.JsonAdapter;
-
-/**
- * A {@link UniFiWirelessClient} represents a wireless {@link UniFiClient}.
- *
- * A wireless client is not physically connected to the network - typically it is connected via a Wi-Fi adapter.
- *
- * @author Matthew Bowman - Initial contribution
- */
-public class UniFiWirelessClient extends UniFiClient {
-
- @JsonAdapter(UniFiTidyLowerCaseStringDeserializer.class)
- private String apMac;
-
- private String essid;
-
- private Integer rssi;
-
- public UniFiWirelessClient(UniFiController controller) {
- super(controller);
- }
-
- @Override
- public Boolean isWired() {
- return false;
- }
-
- @Override
- public String getDeviceMac() {
- return apMac;
- }
-
- public String getEssid() {
- return essid;
- }
-
- public Integer getRssi() {
- return rssi;
- }
-}
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;
*
* @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);
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
*
* @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;
}
}
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);
}
}
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;
/**
*
*
* @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);
+ }
}
}
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;
*
* @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();
}
}
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;
*
* @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);
}
}
--- /dev/null
+/**
+ * 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);
+ }
+ }
+}
*/
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;
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);
+ }
+ }
}
/**
*/
@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();
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()));
+ }
+ }
+
+ 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));
+
+ if (state != UnDefType.NULL) {
+ updateState(channelUID, state);
}
}
- protected abstract void initialize(@NonNull C config);
+ /**
+ * 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);
+
+ /**
+ * 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;
+ }
- protected abstract @Nullable E getEntity(UniFiController controller);
+ /**
+ * 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);
- protected abstract void refreshChannel(E entity, ChannelUID channelUID);
+ /**
+ * 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);
- protected abstract void handleCommand(E entity, ChannelUID channelUID, Command command) throws UniFiException;
+ /**
+ * 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;
}
*/
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;
@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
}
@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;
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:
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
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
// :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;
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;
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);
- }
+ return state;
}
- private State getWiredChannelState(UniFiWiredClient client, boolean clientHome, String channelID) {
- State state = UnDefType.NULL;
- return state;
+ private State getWiredChannelState(final UniFiWiredClient client, final String channelId,
+ final State defaultState) {
+ return defaultState;
}
- private State getWirelessChannelState(UniFiWirelessClient client, boolean clientHome, String channelID) {
- State state = UnDefType.NULL;
- switch (channelID) {
+ 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);
- } else {
- logger.warn("Ignoring unsupported command = {} for channel = {} - valid commands types are: OnOffType",
- command, channelUID);
+ controller.block(client, command == OnOffType.ON);
+ refresh();
+ return true;
}
+ 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);
- }
+ 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 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;
}
}
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;
@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);
private final HttpClient httpClient;
- public UniFiControllerThingHandler(Bridge bridge, HttpClient httpClient) {
+ public UniFiControllerThingHandler(final Bridge bridge, final HttpClient httpClient) {
super(bridge);
this.httpClient = httpClient;
}
// Public API
+ @Override
+ public Collection<Class<? extends ThingHandlerService>> getServices() {
+ return List.of(UniFiThingDiscoveryService.class);
+ }
+
@Override
public void initialize() {
- // mgb: called when the config changes
- cancelRefreshJob();
- config = getConfig().as(UniFiControllerThingConfig.class);
+ config = getConfigAs(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());
- }
+ 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(ThingStatus status, ThingStatusDetail statusDetail, @Nullable String description) {
- if (status == ONLINE || (status == OFFLINE && statusDetail == COMMUNICATION_ERROR)) {
- scheduleRefreshJob();
- } else if (status == OFFLINE && statusDetail == CONFIGURATION_ERROR) {
- cancelRefreshJob();
- }
+ 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);
}
if (controller != null) {
try {
controller.stop();
- } catch (UniFiException e) {
+ } catch (final UniFiException e) {
// mgb: nop as we're in dispose
}
controller = null;
}
@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);
}
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;
}
}
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) {
--- /dev/null
+/**
+ * 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;
+ }
+ }
+ }
+}
--- /dev/null
+/**
+ * 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;
+ }
+}
--- /dev/null
+/**
+ * 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;
+ }
+}
--- /dev/null
+/**
+ * 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:<SSID>;T:WPA|blank;P:<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;
+ }
+}
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}.
*
* @author Matthew Bowman - Initial contribution
*/
+@NonNullByDefault
public class UniFiTrustManager extends X509ExtendedTrustManager {
private static UniFiTrustManager instance = new UniFiTrustManager();
}
@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 {
}
}
import javax.net.ssl.X509ExtendedTrustManager;
+import org.eclipse.jdt.annotation.NonNullByDefault;
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
--- /dev/null
+<?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>
# 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
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
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
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.
# 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
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
# 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ó
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
--- /dev/null
+# 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.
<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>
- <thing-type id="wirelessClient">
+ <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>
<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>
<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>