- `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
+- `accessPoint` - An access point managed by the UniFi controller software
## Discovery
| 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 |
+### `accessPoint`
+
+The following table describes the `accessPoint` configuration parameters:
+
+| Parameter | Description | Config | Default |
+| ------------ | ------------------------------------------------|--------- | ------- |
+| mac | The MAC address of the access point | Required | - |
+| site | The site where the access point should be found | Optional | - |
+
## Channels
### `site`
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`.
+### `accessPoint`
+
+The `accessPoint` information that is retrieved is available as these channels:
+
+| Channel ID | Item Type | Description | Permissions |
+|------------|-----------|------------------------------------|-------------|
+| enable | Switch | Enable or disable the access point | Read, Write |
+
## 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.
--- /dev/null
+/**
+ * Copyright (c) 2010-2024 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 UniFiAccessPointThingConfig} encapsulates all the configuration options for an instance of the
+ * {@link org.openhab.binding.unifi.internal.handler.UniFiAccessPointThingHandler}.
+ *
+ * @author Thomas Lauterbach - Initial contribution
+ */
+@NonNullByDefault
+@SuppressWarnings("unused")
+public class UniFiAccessPointThingConfig {
+
+ private String macAddress = "";
+
+ private String site = "";
+
+ public String getSite() {
+ return site;
+ }
+
+ private void setSite(final String site) {
+ // method to avoid ide auto format mark the field as final
+ this.site = site;
+ }
+
+ public 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();
+ }
+}
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 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,
- 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);
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_WIRED_CLIENT, THING_TYPE_WIRELESS_CLIENT, THING_TYPE_POE_PORT, THING_TYPE_ACCESS_POINT);
// List of site channels
public static final String CHANNEL_TOTAL_CLIENTS = "totalClients";
public static final String CHANNEL_PORT_POE_VOLTAGE = "voltage";
public static final String CHANNEL_PORT_POE_CURRENT = "current";
+ // List of access point channels
+ public static final String CHANNEL_AP_ENABLE = "enable";
+
// List of all Parameters
public static final String PARAMETER_HOST = "host";
public static final String PARAMETER_PORT = "port";
public static final String PARAMETER_MAC_ADDRESS = "macAddress";
public static final String PARAMETER_WIFI_NAME = "wifi";
+ // UniFi device types
+ public static final String DEVICE_TYPE_UAP = "uap";
+
private UniFiBindingConstants() {
// Constants class
}
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.UniFiAccessPointThingHandler;
import org.openhab.binding.unifi.internal.handler.UniFiClientThingHandler;
import org.openhab.binding.unifi.internal.handler.UniFiControllerThingHandler;
import org.openhab.binding.unifi.internal.handler.UniFiPoePortThingHandler;
return new UniFiClientThingHandler(thing);
} else if (THING_TYPE_POE_PORT.equals(thingTypeUID)) {
return new UniFiPoePortThingHandler(thing);
+ } else if (THING_TYPE_ACCESS_POINT.equals(thingTypeUID)) {
+ return new UniFiAccessPointThingHandler(thing);
}
return null;
}
refresh();
}
+ public void disableAccessPoint(final UniFiDevice device, final boolean disable) throws UniFiException {
+ final UniFiControllerRequest<Void> req = newRequest(Void.class, HttpMethod.PUT, gson);
+ req.setAPIPath(String.format("/api/s/%s/rest/device/%s", device.getSite().getName(), device.getId()));
+ req.setBodyParameter("_id", device.getId());
+ req.setBodyParameter("disabled", disable ? "true" : "false");
+ executeRequest(req);
+ refresh();
+ }
+
public void generateVouchers(final UniFiSite site, final int count, final int expiration, final int users,
@Nullable Integer upLimit, @Nullable Integer downLimit, @Nullable Integer dataQuota) throws UniFiException {
final UniFiControllerRequest<Void> req = newRequest(Void.class, HttpMethod.POST, gson);
return devicesCache.get(id);
}
+ public Collection<UniFiDevice> getDevices() {
+ return devicesCache.values();
+ }
+
public UniFiSwitchPorts getSwitchPorts(@Nullable final String deviceId) {
return deviceId == null ? new UniFiSwitchPorts()
: devicesToPortTables.getOrDefault(deviceId, new UniFiSwitchPorts());
private String model;
+ private String type;
+
private String name;
private String siteId;
private JsonObject[] portOverrides;
+ private boolean disabled;
+
public UniFiDevice(final UniFiControllerCache cache) {
this.cache = cache;
}
return id;
}
+ public String getType() {
+ return type;
+ }
+
public String getModel() {
return model;
}
return portOverrides;
}
+ public boolean isDisabled() {
+ return disabled;
+ }
+
@Override
public String toString() {
- return String.format("UniFiDevice{mac: '%s', name: '%s', model: '%s', site: %s}", mac, name, model, getSite());
+ return String.format("UniFiDevice{mac: '%s', name: '%s', type: %s, model: '%s', disabled: %b, site: %s}", mac,
+ name, type, model, disabled, getSite());
}
}
--- /dev/null
+/**
+ * Copyright (c) 2010-2024 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_AP_ENABLE;
+import static org.openhab.binding.unifi.internal.UniFiBindingConstants.DEVICE_TYPE_UAP;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.unifi.internal.UniFiAccessPointThingConfig;
+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.UniFiDevice;
+import org.openhab.binding.unifi.internal.api.dto.UniFiSite;
+import org.openhab.core.library.types.OnOffType;
+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.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * An access point managed by the UniFi controller software.
+ *
+ * @author Thomas Lauterbach - Initial contribution
+ */
+@NonNullByDefault
+public class UniFiAccessPointThingHandler extends UniFiBaseThingHandler<UniFiDevice, UniFiAccessPointThingConfig> {
+
+ private final Logger logger = LoggerFactory.getLogger(UniFiAccessPointThingHandler.class);
+
+ private UniFiAccessPointThingConfig config = new UniFiAccessPointThingConfig();
+
+ public UniFiAccessPointThingHandler(final Thing thing) {
+ super(thing);
+ }
+
+ private static boolean belongsToSite(final UniFiDevice client, final String siteName) {
+ boolean result = true;
+ if (!siteName.isEmpty()) {
+ final UniFiSite site = client.getSite();
+ if (site == null || !site.matchesName(siteName)) {
+ result = false;
+ }
+ }
+ return result;
+ }
+
+ @Override
+ protected boolean initialize(final UniFiAccessPointThingConfig config) {
+ this.config = config;
+ if (!config.isValid()) {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
+ "@text/error.thing.ap.offline.configuration_error");
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ protected @Nullable UniFiDevice getEntity(final UniFiControllerCache cache) {
+ final UniFiDevice device = cache.getDevice(config.getMacAddress());
+ if (device == null || !belongsToSite(device, config.getSite()) || !DEVICE_TYPE_UAP.equals(device.getType())) {
+ return null;
+ }
+ return device;
+ }
+
+ @Override
+ protected State getChannelState(final UniFiDevice device, final String channelId) {
+ State state = getDefaultState(channelId);
+
+ switch (channelId) {
+ case CHANNEL_AP_ENABLE:
+ state = OnOffType.from(!device.isDisabled());
+ break;
+ }
+ return state;
+ }
+
+ @Override
+ protected boolean handleCommand(final UniFiController controller, final UniFiDevice device,
+ final ChannelUID channelUID, final Command command) throws UniFiException {
+ final String channelID = channelUID.getIdWithoutGroup();
+
+ if (CHANNEL_AP_ENABLE.equals(channelID) && command instanceof OnOffType onOffCommand) {
+ return handleEnableCommand(controller, device, channelUID, onOffCommand);
+ }
+ return false;
+ }
+
+ private boolean handleEnableCommand(final UniFiController controller, final UniFiDevice device,
+ final ChannelUID channelUID, final OnOffType command) throws UniFiException {
+ controller.disableAccessPoint(device, command == OnOffType.OFF);
+ refresh();
+ return true;
+ }
+}
*/
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.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 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.UniFiDevice;
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.UniFiSwitchPorts;
discoverWlans(cache, bridgeUID);
discoverClients(cache, bridgeUID);
discoverPoePorts(cache, bridgeUID);
+ discoverAccessPoints(cache, bridgeUID);
} catch (final UniFiException e) {
logger.debug("Exception during discovery of UniFi Things", e);
}
}
}
+ private void discoverAccessPoints(final UniFiControllerCache cache, final ThingUID bridgeUID) {
+ for (final UniFiDevice ud : cache.getDevices()) {
+ if (DEVICE_TYPE_UAP.equals(ud.getType())) {
+ final var thingTypeUID = UniFiBindingConstants.THING_TYPE_ACCESS_POINT;
+ final ThingUID thingUID = new ThingUID(thingTypeUID, bridgeUID, stripIdShort(ud.getId()));
+ final Map<String, Object> properties = Map.of(PARAMETER_SITE, ud.getSite().getName(),
+ PARAMETER_MAC_ADDRESS, ud.getMac());
+ thingDiscovered(DiscoveryResultBuilder.create(thingUID).withThingType(thingTypeUID)
+ .withBridge(bridgeUID).withRepresentationProperty(PARAMETER_MAC_ADDRESS).withTTL(TTL_SECONDS)
+ .withProperties(properties).withLabel(ud.getName()).build());
+ }
+ }
+ }
+
/**
* Shorten the id to make it a bit more comprehensible.
*
</parameter>
</config-description>
+ <config-description uri="thing-type:unifi:accessPoint">
+ <parameter name="macAddress" type="text" required="true">
+ <label>Access Point MAC Address</label>
+ <description>The MAC address of the access point</description>
+ </parameter>
+ <parameter name="site" type="text" required="false">
+ <label>Site</label>
+ <description>The site where the access point should be found (optional)</description>
+ </parameter>
+ </config-description>
+
<config-description uri="channel-type:unifi:poeEnable">
<parameter name="mode" type="text">
<label>On Mode</label>
# thing types
+thing-type.unifi.accessPoint.label = UniFi Access Point
+thing-type.unifi.accessPoint.description = An access point managed by a UniFi controller
thing-type.unifi.controller.label = UniFi Controller
thing-type.unifi.controller.description = A UniFi controller
thing-type.unifi.poePort.label = UniFi PoE Port
# thing types config
+thing-type.config.unifi.accessPoint.macAddress.label = Access Point MAC Address
+thing-type.config.unifi.accessPoint.macAddress.description = The MAC address of the access point
+thing-type.config.unifi.accessPoint.site.label = Site
+thing-type.config.unifi.accessPoint.site.description = The site where the access point should be found (optional)
thing-type.config.unifi.client.cid.label = Client ID
thing-type.config.unifi.client.cid.description = The MAC address, IP address, hostname or name of the client
thing-type.config.unifi.client.considerHome.label = Consider Home Interval
channel-type.unifi.ap.label = Access Point
channel-type.unifi.ap.description = Access Point the wireless client is connected to
+channel-type.unifi.apEnable.label = Enabled
+channel-type.unifi.apEnable.description = If the access point is enabled
channel-type.unifi.blocked.label = Blocked
channel-type.unifi.blocked.description = Is device blocked
channel-type.unifi.essid.label = Wireless Network
channel-type.config.unifi.poeEnable.mode.option.auto = Auto
channel-type.config.unifi.poeEnable.mode.option.pasv24 = 24V
channel-type.config.unifi.poeEnable.mode.option.passthrough = Passthrough
-channel-type.config.unifi.guestVouchersGenerate.voucherCount.label = Number
-channel-type.config.unifi.guestVouchersGenerate.voucherCount.description = Number of vouchers to create
-channel-type.config.unifi.guestVouchersGenerate.voucherExpiration.label = Expiration Time
-channel-type.config.unifi.guestVouchersGenerate.voucherExpiration.description = Minutes a voucher is valid after activation
-channel-type.config.unifi.guestVouchersGenerate.voucherUsers.label = Users
-channel-type.config.unifi.guestVouchersGenerate.voucherUsers.description = Number of users for voucher, 0 if no limit
-channel-type.config.unifi.guestVouchersGenerate.voucherUpLimit.label = Upload Speed Limit
-channel-type.config.unifi.guestVouchersGenerate.voucherUpLimit.description = Upload speed limit in kbps, no limit if not set
-channel-type.config.unifi.guestVouchersGenerate.voucherDownLimit.label = Download Speed Limit
-channel-type.config.unifi.guestVouchersGenerate.voucherDownLimit.description = Download speed limit in kbps, no limit if not set
-channel-type.config.unifi.guestVouchersGenerate.voucherDataQuota.label = Data Transfer Quota
-channel-type.config.unifi.guestVouchersGenerate.voucherDataQuota.description = Data transfer quota in MB per user, no limit if not set
-channel-type.config.unifi.guestVouchersGenerate.option.GENERATE = Generate
# status messages
error.thing.poe.offline.configuration_error = The configuration parameter macAddress must be set and not be empty.
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.ap.offline.configuration_error = The configuration parameter mac must be set and not be empty
# actions
<config-description-ref uri="thing-type:unifi:poePort"/>
</thing-type>
+ <thing-type id="accessPoint">
+ <supported-bridge-type-refs>
+ <bridge-type-ref id="controller"/>
+ </supported-bridge-type-refs>
+
+ <label>UniFi Access Point</label>
+ <description>An access point managed by a UniFi controller</description>
+
+ <channels>
+ <channel id="enable" typeId="apEnable"/>
+ </channels>
+
+ <config-description-ref uri="thing-type:unifi:accessPoint"/>
+ </thing-type>
+
<!-- Channels -->
<channel-type id="totalClients">
<state pattern="%.2f %unit%" readOnly="true"/>
</channel-type>
+ <channel-type id="apEnable" advanced="false">
+ <item-type>Switch</item-type>
+ <label>Enabled</label>
+ <description>If the access point is enabled</description>
+ </channel-type>
+
</thing:thing-descriptions>