[unifi] Add network thing (#18335)

Signed-off-by: Thomas Lauterbach <thomas@pop-os.amfthome.org>
This commit is contained in:
Thomas Lauterbach
2025-03-12 21:58:40 +01:00
committed by GitHub
parent cde8814fce
commit 5ef6840d22
15 changed files with 490 additions and 3 deletions
@@ -11,6 +11,7 @@ This binding integrates with [Ubiquiti UniFi Networks](https://www.ubnt.com/prod
- `wiredClient` - A wired client connected to the UniFi network - `wiredClient` - A wired client connected to the UniFi network
- `poePort` - A PoE (Power over Ethernet) port on a UniFi switch - `poePort` - A PoE (Power over Ethernet) port on a UniFi switch
- `accessPoint` - An access point managed by the UniFi controller software - `accessPoint` - An access point managed by the UniFi controller software
- `network` - A network managed by the UniFi controller software.
## Discovery ## Discovery
@@ -114,6 +115,14 @@ The following table describes the `accessPoint` configuration parameters:
| mac | The MAC address of the access point | Required | - | | mac | The MAC address of the access point | Required | - |
| site | The site where the access point should be found | Optional | - | | site | The site where the access point should be found | Optional | - |
### `network`
The following table describes the `network` configuration parameters:
| Parameter | Description | Config | Default |
| ------------ | -------------------------------------------------------------|--------- | ------- |
| nid | The id of the network | Required | - |
## Channels ## Channels
### `site` ### `site`
@@ -252,6 +261,17 @@ The `accessPoint` information that is retrieved is available as these channels:
| experience | Number:Dimensionless | The average health indication of the connected clients | Read | | experience | Number:Dimensionless | The average health indication of the connected clients | Read |
| led | Switch | Switch the LED on or off | Read, Write | | led | Switch | Switch the LED on or off | Read, Write |
### `network`
The `network` information that is retrieved is available as these channels:
| Channel ID | Item Type | Description | Permissions |
|-----------------|-----------|---------------------------------------------------------------------------------|-------------|
| enable | Switch | Enable status of the network | Read, Write |
| totalClients | Number | Total number of clients connected | Read |
| site | String | UniFi Site the client is associated with | Read |
| purpose | String | Purpose of the network (e.g. Corporate, Guest, WAN, VPN, VLAN Only) | Read |
## Rule Actions ## Rule Actions
As an alternative to using the `guestVoucher` and `guestVouchersGenerate` channels on the `site` thing, it is possible to use rule actions on the thing to generate, revoke and list guest vouchers. As an alternative to using the `guestVoucher` and `guestVouchersGenerate` channels on the `site` thing, it is possible to use rule actions on the thing to generate, revoke and list guest vouchers.
@@ -34,16 +34,18 @@ public final class UniFiBindingConstants {
// List of all Thing Types // List of all Thing Types
public static final ThingTypeUID THING_TYPE_CONTROLLER = new ThingTypeUID(BINDING_ID, "controller"); 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_SITE = new ThingTypeUID(BINDING_ID, "site");
public static final ThingTypeUID THING_TYPE_NETWORK = new ThingTypeUID(BINDING_ID, "network");
public static final ThingTypeUID THING_TYPE_WLAN = new ThingTypeUID(BINDING_ID, "wlan"); 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_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_WIRELESS_CLIENT = new ThingTypeUID(BINDING_ID, "wirelessClient");
public static final ThingTypeUID THING_TYPE_POE_PORT = new ThingTypeUID(BINDING_ID, "poePort"); public static final ThingTypeUID THING_TYPE_POE_PORT = new ThingTypeUID(BINDING_ID, "poePort");
public static final ThingTypeUID THING_TYPE_ACCESS_POINT = new ThingTypeUID(BINDING_ID, "accessPoint"); public static final ThingTypeUID THING_TYPE_ACCESS_POINT = new ThingTypeUID(BINDING_ID, "accessPoint");
public static final Set<ThingTypeUID> ALL_THING_TYPE_SUPPORTED = Set.of(THING_TYPE_CONTROLLER, THING_TYPE_SITE, public static final Set<ThingTypeUID> ALL_THING_TYPE_SUPPORTED = Set.of(THING_TYPE_CONTROLLER, THING_TYPE_SITE,
THING_TYPE_NETWORK, THING_TYPE_WLAN, THING_TYPE_WIRED_CLIENT, THING_TYPE_WIRELESS_CLIENT,
THING_TYPE_POE_PORT, THING_TYPE_ACCESS_POINT);
public static final Set<ThingTypeUID> THING_TYPE_SUPPORTED = Set.of(THING_TYPE_SITE, THING_TYPE_NETWORK,
THING_TYPE_WLAN, THING_TYPE_WIRED_CLIENT, THING_TYPE_WIRELESS_CLIENT, THING_TYPE_POE_PORT, THING_TYPE_WLAN, THING_TYPE_WIRED_CLIENT, THING_TYPE_WIRELESS_CLIENT, THING_TYPE_POE_PORT,
THING_TYPE_ACCESS_POINT); THING_TYPE_ACCESS_POINT);
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, THING_TYPE_ACCESS_POINT);
// List of site channels // List of site channels
public static final String CHANNEL_TOTAL_CLIENTS = "totalClients"; public static final String CHANNEL_TOTAL_CLIENTS = "totalClients";
@@ -53,6 +55,9 @@ public final class UniFiBindingConstants {
public static final String CHANNEL_GUEST_VOUCHER = "guestVoucher"; public static final String CHANNEL_GUEST_VOUCHER = "guestVoucher";
public static final String CHANNEL_GUEST_VOUCHERS_GENERATE = "guestVouchersGenerate"; public static final String CHANNEL_GUEST_VOUCHERS_GENERATE = "guestVouchersGenerate";
// List of network channels
public static final String CHANNEL_PURPOSE = "purpose";
// List of wlan channels // List of wlan channels
public static final String CHANNEL_SECURITY = "security"; public static final String CHANNEL_SECURITY = "security";
public static final String CHANNEL_WLANBAND = "wlanBand"; public static final String CHANNEL_WLANBAND = "wlanBand";
@@ -112,6 +117,7 @@ public final class UniFiBindingConstants {
public static final String PARAMETER_CID = "cid"; public static final String PARAMETER_CID = "cid";
public static final String PARAMETER_SID = "sid"; public static final String PARAMETER_SID = "sid";
public static final String PARAMETER_WID = "wid"; public static final String PARAMETER_WID = "wid";
public static final String PARAMETER_NID = "nid";
public static final String PARAMETER_VOUCHER_COUNT = "voucherCount"; public static final String PARAMETER_VOUCHER_COUNT = "voucherCount";
public static final String PARAMETER_VOUCHER_EXPIRATION = "voucherExpiration"; public static final String PARAMETER_VOUCHER_EXPIRATION = "voucherExpiration";
public static final String PARAMETER_VOUCHER_USERS = "voucherUsers"; public static final String PARAMETER_VOUCHER_USERS = "voucherUsers";
@@ -0,0 +1,47 @@
/*
* Copyright (c) 2010-2025 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.UniFiNetworkThingHandler;
/**
* The {@link UniFiNetworkThingConfig} encapsulates all the configuration options for an instance of the
* {@link UniFiNetworkThingHandler}.
*
* @author Thomas Lauterbach - Initial contribution
*/
@NonNullByDefault
@SuppressWarnings("unused")
public class UniFiNetworkThingConfig {
private String nid = "";
public String getNetworkId() {
return nid;
}
private void setNetworkId(final String nid) {
// method to avoid auto format mark the field as final
this.nid = nid;
}
public boolean isValid() {
return !nid.isBlank();
}
@Override
public String toString() {
return String.format("UniFiNetworkThingConfig{nid: '%s'}", nid);
}
}
@@ -21,6 +21,7 @@ import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.openhab.binding.unifi.internal.handler.UniFiAccessPointThingHandler; import org.openhab.binding.unifi.internal.handler.UniFiAccessPointThingHandler;
import org.openhab.binding.unifi.internal.handler.UniFiClientThingHandler; import org.openhab.binding.unifi.internal.handler.UniFiClientThingHandler;
import org.openhab.binding.unifi.internal.handler.UniFiControllerThingHandler; import org.openhab.binding.unifi.internal.handler.UniFiControllerThingHandler;
import org.openhab.binding.unifi.internal.handler.UniFiNetworkThingHandler;
import org.openhab.binding.unifi.internal.handler.UniFiPoePortThingHandler; import org.openhab.binding.unifi.internal.handler.UniFiPoePortThingHandler;
import org.openhab.binding.unifi.internal.handler.UniFiSiteThingHandler; import org.openhab.binding.unifi.internal.handler.UniFiSiteThingHandler;
import org.openhab.binding.unifi.internal.handler.UniFiWlanThingHandler; import org.openhab.binding.unifi.internal.handler.UniFiWlanThingHandler;
@@ -82,6 +83,8 @@ public class UniFiThingHandlerFactory extends BaseThingHandlerFactory {
return new UniFiControllerThingHandler((Bridge) thing, httpClient); return new UniFiControllerThingHandler((Bridge) thing, httpClient);
} else if (THING_TYPE_SITE.equals(thingTypeUID)) { } else if (THING_TYPE_SITE.equals(thingTypeUID)) {
return new UniFiSiteThingHandler(thing); return new UniFiSiteThingHandler(thing);
} else if (THING_TYPE_NETWORK.equals(thingTypeUID)) {
return new UniFiNetworkThingHandler(thing);
} else if (THING_TYPE_WLAN.equals(thingTypeUID)) { } else if (THING_TYPE_WLAN.equals(thingTypeUID)) {
return new UniFiWlanThingHandler(thing); return new UniFiWlanThingHandler(thing);
} else if (THING_TYPE_WIRELESS_CLIENT.equals(thingTypeUID) || THING_TYPE_WIRED_CLIENT.equals(thingTypeUID)) { } else if (THING_TYPE_WIRELESS_CLIENT.equals(thingTypeUID) || THING_TYPE_WIRED_CLIENT.equals(thingTypeUID)) {
@@ -14,6 +14,7 @@ package org.openhab.binding.unifi.internal.api;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jdt.annotation.Nullable;
@@ -23,6 +24,7 @@ import org.openhab.binding.unifi.internal.api.cache.UniFiControllerCache;
import org.openhab.binding.unifi.internal.api.dto.UnfiPortOverrideJsonObject; import org.openhab.binding.unifi.internal.api.dto.UnfiPortOverrideJsonObject;
import org.openhab.binding.unifi.internal.api.dto.UniFiClient; 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.UniFiDevice;
import org.openhab.binding.unifi.internal.api.dto.UniFiNetwork;
import org.openhab.binding.unifi.internal.api.dto.UniFiSite; import org.openhab.binding.unifi.internal.api.dto.UniFiSite;
import org.openhab.binding.unifi.internal.api.dto.UniFiSwitchPorts; import org.openhab.binding.unifi.internal.api.dto.UniFiSwitchPorts;
import org.openhab.binding.unifi.internal.api.dto.UniFiUnknownClient; import org.openhab.binding.unifi.internal.api.dto.UniFiUnknownClient;
@@ -34,6 +36,7 @@ import org.openhab.binding.unifi.internal.api.util.UnfiPortOverrideJsonElementDe
import org.openhab.binding.unifi.internal.api.util.UniFiClientDeserializer; 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.UniFiClientInstanceCreator;
import org.openhab.binding.unifi.internal.api.util.UniFiDeviceInstanceCreator; import org.openhab.binding.unifi.internal.api.util.UniFiDeviceInstanceCreator;
import org.openhab.binding.unifi.internal.api.util.UniFiNetworkInstanceCreator;
import org.openhab.binding.unifi.internal.api.util.UniFiSiteInstanceCreator; import org.openhab.binding.unifi.internal.api.util.UniFiSiteInstanceCreator;
import org.openhab.binding.unifi.internal.api.util.UniFiVoucherInstanceCreator; import org.openhab.binding.unifi.internal.api.util.UniFiVoucherInstanceCreator;
import org.openhab.binding.unifi.internal.api.util.UniFiWlanInstanceCreator; import org.openhab.binding.unifi.internal.api.util.UniFiWlanInstanceCreator;
@@ -87,12 +90,14 @@ public class UniFiController {
this.timeoutSeconds = timeoutSeconds; this.timeoutSeconds = timeoutSeconds;
this.csrfToken = ""; this.csrfToken = "";
final UniFiSiteInstanceCreator siteInstanceCreator = new UniFiSiteInstanceCreator(cache); final UniFiSiteInstanceCreator siteInstanceCreator = new UniFiSiteInstanceCreator(cache);
final UniFiNetworkInstanceCreator networkInstanceCreator = new UniFiNetworkInstanceCreator(cache);
final UniFiWlanInstanceCreator wlanInstanceCreator = new UniFiWlanInstanceCreator(cache); final UniFiWlanInstanceCreator wlanInstanceCreator = new UniFiWlanInstanceCreator(cache);
final UniFiDeviceInstanceCreator deviceInstanceCreator = new UniFiDeviceInstanceCreator(cache); final UniFiDeviceInstanceCreator deviceInstanceCreator = new UniFiDeviceInstanceCreator(cache);
final UniFiClientInstanceCreator clientInstanceCreator = new UniFiClientInstanceCreator(cache); final UniFiClientInstanceCreator clientInstanceCreator = new UniFiClientInstanceCreator(cache);
final UniFiVoucherInstanceCreator voucherInstanceCreator = new UniFiVoucherInstanceCreator(cache); final UniFiVoucherInstanceCreator voucherInstanceCreator = new UniFiVoucherInstanceCreator(cache);
this.gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) this.gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
.registerTypeAdapter(UniFiSite.class, siteInstanceCreator) .registerTypeAdapter(UniFiSite.class, siteInstanceCreator)
.registerTypeAdapter(UniFiNetwork.class, networkInstanceCreator)
.registerTypeAdapter(UniFiWlan.class, wlanInstanceCreator) .registerTypeAdapter(UniFiWlan.class, wlanInstanceCreator)
.registerTypeAdapter(UniFiDevice.class, deviceInstanceCreator) .registerTypeAdapter(UniFiDevice.class, deviceInstanceCreator)
.registerTypeAdapter(UniFiClient.class, new UniFiClientDeserializer()) .registerTypeAdapter(UniFiClient.class, new UniFiClientDeserializer())
@@ -149,6 +154,7 @@ public class UniFiController {
synchronized (this) { synchronized (this) {
cache.clear(); cache.clear();
final Collection<UniFiSite> sites = refreshSites(); final Collection<UniFiSite> sites = refreshSites();
refreshNetworks(sites);
refreshWlans(sites); refreshWlans(sites);
refreshDevices(sites); refreshDevices(sites);
refreshClients(sites); refreshClients(sites);
@@ -208,8 +214,17 @@ public class UniFiController {
refresh(); refresh();
} }
public void enableNetwork(final UniFiNetwork network, final boolean enable) throws UniFiException {
final UniFiControllerRequest<Void> req = newRequest(Void.class, HttpMethod.PUT, gson);
req.setAPIPath(String.format("/api/s/%s/group/networkconf", network.getSite().getName()));
req.setBodyParameter("data", Map.of("enabled", enable));
req.setBodyParameter("id", new String[] { network.getId() });
executeRequest(req);
refresh();
}
public void enableWifi(final UniFiWlan wlan, final boolean enable) throws UniFiException { public void enableWifi(final UniFiWlan wlan, final boolean enable) throws UniFiException {
final UniFiControllerRequest<Void> req = newRequest(Void.class, HttpMethod.PUT, poeGson); final UniFiControllerRequest<Void> req = newRequest(Void.class, HttpMethod.PUT, gson);
req.setAPIPath(String.format("/api/s/%s/rest/wlanconf/%s", wlan.getSite().getName(), wlan.getId())); req.setAPIPath(String.format("/api/s/%s/rest/wlanconf/%s", wlan.getSite().getName(), wlan.getId()));
req.setBodyParameter("_id", wlan.getId()); req.setBodyParameter("_id", wlan.getId());
req.setBodyParameter("enabled", enable ? "true" : "false"); req.setBodyParameter("enabled", enable ? "true" : "false");
@@ -309,6 +324,18 @@ public class UniFiController {
return cache.setSites(executeRequest(req)); return cache.setSites(executeRequest(req));
} }
private void refreshNetworks(final Collection<UniFiSite> sites) throws UniFiException {
for (final UniFiSite site : sites) {
cache.putNetworks(getNetworks(site));
}
}
private UniFiNetwork @Nullable [] getNetworks(final UniFiSite site) throws UniFiException {
final UniFiControllerRequest<UniFiNetwork[]> req = newRequest(UniFiNetwork[].class, HttpMethod.GET, gson);
req.setAPIPath(String.format("/api/s/%s/rest/networkconf", site.getName()));
return executeRequest(req);
}
private void refreshWlans(final Collection<UniFiSite> sites) throws UniFiException { private void refreshWlans(final Collection<UniFiSite> sites) throws UniFiException {
for (final UniFiSite site : sites) { for (final UniFiSite site : sites) {
cache.putWlans(getWlans(site)); cache.putWlans(getWlans(site));
@@ -25,6 +25,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.unifi.internal.api.dto.UniFiClient; 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.UniFiDevice;
import org.openhab.binding.unifi.internal.api.dto.UniFiNetwork;
import org.openhab.binding.unifi.internal.api.dto.UniFiPortTuple; import org.openhab.binding.unifi.internal.api.dto.UniFiPortTuple;
import org.openhab.binding.unifi.internal.api.dto.UniFiSite; import org.openhab.binding.unifi.internal.api.dto.UniFiSite;
import org.openhab.binding.unifi.internal.api.dto.UniFiSwitchPorts; import org.openhab.binding.unifi.internal.api.dto.UniFiSwitchPorts;
@@ -46,6 +47,7 @@ public class UniFiControllerCache {
private final Logger logger = LoggerFactory.getLogger(UniFiControllerCache.class); private final Logger logger = LoggerFactory.getLogger(UniFiControllerCache.class);
private final UniFiSiteCache sitesCache = new UniFiSiteCache(); private final UniFiSiteCache sitesCache = new UniFiSiteCache();
private final UniFiNetworkCache networksCache = new UniFiNetworkCache();
private final UniFiWlanCache wlansCache = new UniFiWlanCache(); private final UniFiWlanCache wlansCache = new UniFiWlanCache();
private final UniFiDeviceCache devicesCache = new UniFiDeviceCache(); private final UniFiDeviceCache devicesCache = new UniFiDeviceCache();
private final UniFiClientCache clientsCache = new UniFiClientCache(); private final UniFiClientCache clientsCache = new UniFiClientCache();
@@ -55,6 +57,7 @@ public class UniFiControllerCache {
public void clear() { public void clear() {
sitesCache.clear(); sitesCache.clear();
networksCache.clear();
wlansCache.clear(); wlansCache.clear();
devicesCache.clear(); devicesCache.clear();
clientsCache.clear(); clientsCache.clear();
@@ -77,6 +80,20 @@ public class UniFiControllerCache {
return sitesCache.values(); return sitesCache.values();
} }
// Networks Cache
public void putNetworks(final UniFiNetwork @Nullable [] networks) {
networksCache.putAll(networks);
}
public @Nullable UniFiNetwork getNetwork(@Nullable final String id) {
return networksCache.get(id);
}
public Collection<UniFiNetwork> getNetworks() {
return networksCache.values();
}
// Wlans Cache // Wlans Cache
public void putWlans(final UniFiWlan @Nullable [] wlans) { public void putWlans(final UniFiWlan @Nullable [] wlans) {
@@ -0,0 +1,48 @@
/*
* Copyright (c) 2010-2025 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.UniFiNetwork;
/**
* The {@link UniFiNetworkCache} is a specific implementation of {@link UniFiCache} for the purpose of caching
* {@link UniFiNetwork} instances.
*
* The cache uses the following prefixes: <code>id</code>, <code>name</code>
*
* @author Thomas Lauterbach - Initial contribution
*/
@NonNullByDefault
class UniFiNetworkCache extends UniFiCache<UniFiNetwork> {
public UniFiNetworkCache() {
super(ID, NAME);
}
@Override
protected @Nullable String getSuffix(final UniFiNetwork network, final Prefix prefix) {
switch (prefix) {
case ID:
return network.getId();
case NAME:
return network.getName();
default:
return null;
}
}
}
@@ -61,6 +61,8 @@ public abstract class UniFiClient implements HasId {
@SerializedName("satisfaction") @SerializedName("satisfaction")
private Integer experience; private Integer experience;
private String networkId;
protected UniFiClient(final UniFiControllerCache cache) { protected UniFiClient(final UniFiControllerCache cache) {
this.cache = cache; this.cache = cache;
} }
@@ -122,6 +124,10 @@ public abstract class UniFiClient implements HasId {
return experience; return experience;
} }
public UniFiNetwork getNetwork() {
return cache.getNetwork(networkId);
}
@Override @Override
public String toString() { public String toString() {
return String.format( return String.format(
@@ -0,0 +1,66 @@
/*
* Copyright (c) 2010-2025 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 Thomas Lauterbach - Initial contribution
*/
public class UniFiNetwork implements HasId {
protected final transient UniFiControllerCache cache;
@SerializedName("_id")
private String id;
private String name;
private String siteId;
private boolean enabled;
private String purpose;
public UniFiNetwork(final UniFiControllerCache cache) {
this.cache = cache;
}
@Override
public String getId() {
return id;
}
public String getName() {
return name;
}
public UniFiSite getSite() {
return cache.getSite(siteId);
}
public boolean isEnabled() {
return enabled;
}
public String getPurpose() {
return purpose;
}
@Override
public String toString() {
return String.format("UniFiNetwork{id: '%s', name: '%s'}", id, name);
}
}
@@ -0,0 +1,48 @@
/*
* Copyright (c) 2010-2025 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.UniFiNetwork;
import com.google.gson.InstanceCreator;
import com.google.gson.JsonSyntaxException;
/**
* The {@link UniFiNetworkInstanceCreator} creates instances of {@link UniFiNetwork}s during the JSON unmarshalling of
* controller responses.
*
* @author Thomas Lauterbach - Initial contribution
*/
@NonNullByDefault
public class UniFiNetworkInstanceCreator implements InstanceCreator<UniFiNetwork> {
private final UniFiControllerCache cache;
public UniFiNetworkInstanceCreator(final UniFiControllerCache cache) {
this.cache = cache;
}
@Override
public UniFiNetwork createInstance(final @Nullable Type type) {
if (UniFiNetwork.class.equals(type)) {
return new UniFiNetwork(cache);
} else {
throw new JsonSyntaxException("Expected a UniFiNetwork type, but got " + type);
}
}
}
@@ -0,0 +1,119 @@
/*
* Copyright (c) 2010-2025 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.*;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.unifi.internal.UniFiNetworkThingConfig;
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.UniFiNetwork;
import org.openhab.binding.unifi.internal.api.dto.UniFiSite;
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;
/**
* The {@link UniFiNetworkThingHandler} is responsible for handling commands and status updates for a wireless network.
*
* @author Thomas Lauterbach - Initial contribution
*/
@NonNullByDefault
public class UniFiNetworkThingHandler extends UniFiBaseThingHandler<UniFiNetwork, UniFiNetworkThingConfig> {
private UniFiNetworkThingConfig config = new UniFiNetworkThingConfig();
public UniFiNetworkThingHandler(final Thing thing) {
super(thing);
}
@Override
protected boolean initialize(final UniFiNetworkThingConfig config) {
this.config = config;
if (!config.isValid()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/error.thing.network.offline.configuration_error");
return false;
}
return true;
}
@Override
protected @Nullable UniFiNetwork getEntity(final UniFiControllerCache cache) {
return cache.getNetwork(config.getNetworkId());
}
@Override
protected State getChannelState(final UniFiNetwork network, final String channelId) {
final State state;
switch (channelId) {
case CHANNEL_ENABLE:
state = OnOffType.from(network.isEnabled());
break;
case CHANNEL_SITE:
final UniFiSite site = network.getSite();
if (site != null && site.getDescription() != null && !site.getDescription().isBlank()) {
state = StringType.valueOf(site.getDescription());
} else {
state = UnDefType.UNDEF;
}
break;
case CHANNEL_PURPOSE:
state = StringType.valueOf(network.getPurpose());
break;
case CHANNEL_TOTAL_CLIENTS:
state = countClients(network);
break;
default:
// Unsupported channel; nothing to update
state = UnDefType.NULL;
}
return state;
}
private static State countClients(final UniFiNetwork network) {
final UniFiSite site = network.getSite();
if (site == null) {
return UnDefType.UNDEF;
} else {
return new DecimalType(site.getCache().countClients(site, c -> c instanceof UniFiClient client
&& (network.getName() != null && network.equals(client.getNetwork()))));
}
}
@Override
protected boolean handleCommand(final UniFiController controller, final UniFiNetwork network,
final ChannelUID channelUID, Command command) throws UniFiException {
final String channelID = channelUID.getId();
if (CHANNEL_ENABLE.equals(channelID) && command instanceof OnOffType && network.getSite() != null) {
controller.enableNetwork(network, OnOffType.ON == command);
return true;
}
return false;
}
}
@@ -15,6 +15,7 @@ package org.openhab.binding.unifi.internal.handler;
import static org.openhab.binding.unifi.internal.UniFiBindingConstants.DEVICE_TYPE_UAP; import static org.openhab.binding.unifi.internal.UniFiBindingConstants.DEVICE_TYPE_UAP;
import static org.openhab.binding.unifi.internal.UniFiBindingConstants.PARAMETER_CID; 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_MAC_ADDRESS;
import static org.openhab.binding.unifi.internal.UniFiBindingConstants.PARAMETER_NID;
import static org.openhab.binding.unifi.internal.UniFiBindingConstants.PARAMETER_PORT_NUMBER; 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_SID;
import static org.openhab.binding.unifi.internal.UniFiBindingConstants.PARAMETER_SITE; import static org.openhab.binding.unifi.internal.UniFiBindingConstants.PARAMETER_SITE;
@@ -32,6 +33,7 @@ import org.openhab.binding.unifi.internal.api.UniFiException;
import org.openhab.binding.unifi.internal.api.cache.UniFiControllerCache; 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.UniFiClient;
import org.openhab.binding.unifi.internal.api.dto.UniFiDevice; import org.openhab.binding.unifi.internal.api.dto.UniFiDevice;
import org.openhab.binding.unifi.internal.api.dto.UniFiNetwork;
import org.openhab.binding.unifi.internal.api.dto.UniFiPortTuple; import org.openhab.binding.unifi.internal.api.dto.UniFiPortTuple;
import org.openhab.binding.unifi.internal.api.dto.UniFiSite; import org.openhab.binding.unifi.internal.api.dto.UniFiSite;
import org.openhab.binding.unifi.internal.api.dto.UniFiSwitchPorts; import org.openhab.binding.unifi.internal.api.dto.UniFiSwitchPorts;
@@ -81,6 +83,7 @@ public class UniFiThingDiscoveryService extends AbstractThingHandlerDiscoverySer
final ThingUID bridgeUID = thingHandler.getThing().getUID(); final ThingUID bridgeUID = thingHandler.getThing().getUID();
discoverSites(cache, bridgeUID); discoverSites(cache, bridgeUID);
discoverNetworks(cache, bridgeUID);
discoverWlans(cache, bridgeUID); discoverWlans(cache, bridgeUID);
discoverClients(cache, bridgeUID); discoverClients(cache, bridgeUID);
discoverPoePorts(cache, bridgeUID); discoverPoePorts(cache, bridgeUID);
@@ -102,6 +105,19 @@ public class UniFiThingDiscoveryService extends AbstractThingHandlerDiscoverySer
} }
} }
private void discoverNetworks(final UniFiControllerCache cache, final ThingUID bridgeUID) {
for (final UniFiNetwork network : cache.getNetworks()) {
final ThingUID thingUID = new ThingUID(UniFiBindingConstants.THING_TYPE_NETWORK, bridgeUID,
stripIdShort(network.getId()));
final Map<String, Object> properties = Map.of(PARAMETER_NID, network.getId());
thingDiscovered(
DiscoveryResultBuilder.create(thingUID).withThingType(UniFiBindingConstants.THING_TYPE_NETWORK)
.withBridge(bridgeUID).withRepresentationProperty(PARAMETER_NID).withTTL(TTL_SECONDS)
.withProperties(properties).withLabel(network.getName()).build());
}
}
private void discoverWlans(final UniFiControllerCache cache, final ThingUID bridgeUID) { private void discoverWlans(final UniFiControllerCache cache, final ThingUID bridgeUID) {
for (final UniFiWlan wlan : cache.getWlans()) { for (final UniFiWlan wlan : cache.getWlans()) {
final ThingUID thingUID = new ThingUID(UniFiBindingConstants.THING_TYPE_WLAN, bridgeUID, final ThingUID thingUID = new ThingUID(UniFiBindingConstants.THING_TYPE_WLAN, bridgeUID,
@@ -50,6 +50,13 @@
</parameter> </parameter>
</config-description> </config-description>
<config-description uri="thing-type:unifi:network">
<parameter name="nid" type="text" required="true">
<label>Network Id</label>
<description>The id or name of the network</description>
</parameter>
</config-description>
<config-description uri="thing-type:unifi:wlan"> <config-description uri="thing-type:unifi:wlan">
<parameter name="wid" type="text" required="true"> <parameter name="wid" type="text" required="true">
<label>WLAN Id</label> <label>WLAN Id</label>
@@ -17,6 +17,9 @@ thing-type.unifi.accessPoint.channel.online.description = Online status of the d
thing-type.unifi.accessPoint.channel.uptime.description = Uptime of the device (in seconds) thing-type.unifi.accessPoint.channel.uptime.description = Uptime of the device (in seconds)
thing-type.unifi.controller.label = UniFi Controller 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.network.label = UniFi Network
thing-type.unifi.network.description = A UniFi Network
thing-type.unifi.network.channel.site.description = UniFi Site the network is associated with
thing-type.unifi.poePort.label = UniFi PoE Port 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.poePort.description = A Power Over Ethernet (PoE) port on a UniFi switch
thing-type.unifi.site.label = UniFi Site thing-type.unifi.site.label = UniFi Site
@@ -54,6 +57,8 @@ thing-type.config.unifi.controller.unifios.label = UniFi OS
thing-type.config.unifi.controller.unifios.description = If the UniFi Controller is running on UniFi OS. thing-type.config.unifi.controller.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.label = Username
thing-type.config.unifi.controller.username.description = The username to access the UniFi Controller. thing-type.config.unifi.controller.username.description = The username to access the UniFi Controller.
thing-type.config.unifi.network.nid.label = Network Id
thing-type.config.unifi.network.nid.description = The id or name of the network
thing-type.config.unifi.poePort.macAddress.label = Switch MAC Address 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.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.label = Port Number
@@ -106,6 +111,8 @@ channel-type.unifi.macAddress.label = MAC Address
channel-type.unifi.macAddress.description = MAC address of the client channel-type.unifi.macAddress.description = MAC address of the client
channel-type.unifi.name.label = Name channel-type.unifi.name.label = Name
channel-type.unifi.name.description = Name of the client channel-type.unifi.name.description = Name of the client
channel-type.unifi.networkEnable.label = Enable
channel-type.unifi.networkEnable.description = Enable status of the network
channel-type.unifi.online.label = Online channel-type.unifi.online.label = Online
channel-type.unifi.online.description = Online status of the client channel-type.unifi.online.description = Online status of the client
channel-type.unifi.passphrase.label = Passphrase channel-type.unifi.passphrase.label = Passphrase
@@ -129,6 +136,13 @@ channel-type.unifi.poeVoltage.label = Port PoE Voltage
channel-type.unifi.poeVoltage.description = Voltage usage of the PoE port channel-type.unifi.poeVoltage.description = Voltage usage of the PoE port
channel-type.unifi.portOnline.label = Port Active channel-type.unifi.portOnline.label = Port Active
channel-type.unifi.portOnline.description = PoE port is active channel-type.unifi.portOnline.description = PoE port is active
channel-type.unifi.purpose.label = Purpose
channel-type.unifi.purpose.description = Purpose of the network
channel-type.unifi.purpose.state.option.corporate = Corporate
channel-type.unifi.purpose.state.option.guest = Guest
channel-type.unifi.purpose.state.option.remote-user-vpn = VPN
channel-type.unifi.purpose.state.option.wan = WAN
channel-type.unifi.purpose.state.option.vlan-only = VLAN Only
channel-type.unifi.qrcodeEncoding.label = QR Code Encoding 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.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.label = Reconnect
@@ -195,6 +209,7 @@ error.thing.poe.offline.configuration_error = The configuration parameter macAdd
error.thing.poe.offline.nodata_error = No data for the PoE port could be found in the UniFi API data. See TRACE log for actual API data. error.thing.poe.offline.nodata_error = No data for the PoE port could be found in the UniFi API data. See TRACE log for actual API data.
error.thing.site.offline.configuration_error = The configuration parameter sid must be set and not be empty. error.thing.site.offline.configuration_error = The configuration parameter sid must be set and not be empty.
error.thing.ap.offline.configuration_error = The configuration parameter mac must be set and not be empty error.thing.ap.offline.configuration_error = The configuration parameter mac must be set and not be empty
error.thing.network.offline.configuration_error = The configuration parameter networkId must be set and not be empty
# actions # actions
@@ -34,6 +34,27 @@
<config-description-ref uri="thing-type:unifi:site"/> <config-description-ref uri="thing-type:unifi:site"/>
</thing-type> </thing-type>
<thing-type id="network">
<supported-bridge-type-refs>
<bridge-type-ref id="controller"/>
</supported-bridge-type-refs>
<label>UniFi Network</label>
<description>A UniFi Network</description>
<channels>
<channel id="enable" typeId="networkEnable"/>
<channel id="totalClients" typeId="totalClients"/>
<channel id="site" typeId="site">
<description>UniFi Site the network is associated with</description>
</channel>
<channel id="purpose" typeId="purpose"/>
</channels>
<representation-property>nid</representation-property>
<config-description-ref uri="thing-type:unifi:network"/>
</thing-type>
<thing-type id="wlan"> <thing-type id="wlan">
<supported-bridge-type-refs> <supported-bridge-type-refs>
<bridge-type-ref id="controller"/> <bridge-type-ref id="controller"/>
@@ -491,4 +512,25 @@
</state> </state>
</channel-type> </channel-type>
<channel-type id="purpose">
<item-type>String</item-type>
<label>Purpose</label>
<description>Purpose of the network</description>
<state readOnly="true">
<options>
<option value="corporate">Corporate</option>
<option value="guest">Guest</option>
<option value="remote-user-vpn">VPN</option>
<option value="wan">WAN</option>
<option value="vlan-only">VLAN Only</option>
</options>
</state>
</channel-type>
<channel-type id="networkEnable">
<item-type>Switch</item-type>
<label>Enable</label>
<description>Enable status of the network</description>
</channel-type>
</thing:thing-descriptions> </thing:thing-descriptions>