Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[unifi] Add support for new thing type access point #17499

Merged
merged 48 commits into from
Oct 4, 2024
Merged
Show file tree
Hide file tree
Changes from 44 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
aba268b
Initial commit
Sep 16, 2023
025d02e
Lots of fixes and improvements
Sep 17, 2023
281b085
Repeat property is evil!
Sep 17, 2023
b4c89e4
Updated thing descriptions and cleanup
Sep 19, 2023
374a7b2
Fixed command handling for bridge
Sep 19, 2023
6a52431
Fixed blinkIndicator action
Sep 19, 2023
937418b
Cleanup
Sep 19, 2023
a2613ca
Removed ColorTemp tag from effectPalette
Sep 19, 2023
29b3e60
Send None as effectPalette value to reset
Sep 19, 2023
a7a6538
Prevent DiscoveryService from receiving retained app messages
Sep 21, 2023
5f2d242
Missing slash added
Sep 21, 2023
740833d
Various fixes and improvements
Sep 21, 2023
03a9e75
Initialise all items (no more UNDEF types)
Sep 21, 2023
414c83f
Increased init wait time
Sep 22, 2023
8d437bf
Smoother app initialisation
Sep 22, 2023
e31a09f
Fixed display command
Sep 22, 2023
9121f1c
Channel impr. / dont accept commands during sync
Sep 23, 2023
5911d66
Brightness settings working now
Sep 23, 2023
e3f601f
pushIcon fixed, faster init
Sep 24, 2023
88834a5
Removed unused buttonControl channel
Sep 25, 2023
c3afa9d
Set app active when found in rotation
Sep 26, 2023
6940ffb
Initial notification impl
Oct 8, 2023
8ad7c29
Added custom notifications
Oct 8, 2023
45d15fc
Fixed trigger channels
Oct 9, 2023
9da914e
Map stats.matrix field added in AL 0.89
Oct 16, 2023
80adca6
Added DSL Rule Action support
Apr 9, 2024
eeedc8c
Spotless and pom version fixes
Apr 9, 2024
350b2f7
Updated copyright notice
Apr 9, 2024
b076c81
Added deep sleep action
Apr 10, 2024
9d17a6e
lifetime and overlay channels added
Apr 10, 2024
ae4409b
added app-updates
Oct 1, 2024
7ffc6ee
added app-updates
Oct 1, 2024
9b4738e
Added access point thing
Oct 2, 2024
e69b572
Added access point thing
Oct 2, 2024
aa429ab
use mac address on discovered ap
Oct 2, 2024
76187e8
Revert "added app-updates"
Oct 3, 2024
801c5cb
reverting awtrix bundle
Oct 3, 2024
ac4eeb3
cleanup
Oct 3, 2024
1f966eb
inverted disable/enable logic
Oct 3, 2024
a2c4c31
thing-types.xml aktualisieren
DrRSatzteil Oct 3, 2024
0eeebba
fixes various review comments
Oct 4, 2024
a78d7bb
added missing site config for ap
Oct 4, 2024
46de992
dont provide the site as channel
Oct 4, 2024
8d14e65
fixed representation property
Oct 4, 2024
8a02ae8
fixes style issues mentioned ni review comments
Oct 4, 2024
ee4bfa4
another style change from review
Oct 4, 2024
0230e4b
remove todo
DrRSatzteil Oct 4, 2024
09afb09
corrected channel id for ap and properties cleanup
Oct 4, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions bundles/org.openhab.binding.unifi/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ This binding integrates with [Ubiquiti UniFi Networks](https://www.ubnt.com/prod
- `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

Expand Down Expand Up @@ -103,6 +104,15 @@ The following table describes the `poePort` configuration parameters:
| 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`
Expand Down Expand Up @@ -225,6 +235,14 @@ The `poePort` information that is retrieved is available as these channels:
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 |
|------------|-----------|------------------------------------|-------------|
| enabled | 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.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/**
* 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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,12 @@ public final class UniFiBindingConstants {
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";
Expand Down Expand Up @@ -93,6 +95,9 @@ public final class UniFiBindingConstants {
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";
Expand All @@ -113,6 +118,9 @@ public final class UniFiBindingConstants {
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
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
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;
Expand Down Expand Up @@ -87,6 +88,8 @@ public boolean supportsThingType(final ThingTypeUID thingTypeUID) {
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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,15 @@ public void enableWifi(final UniFiWlan wlan, final boolean enable) throws UniFiE
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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,10 @@ public Collection<UniFiClient> getClients() {
return clientsCache.values();
}

public Collection<UniFiDevice> getDevices() {
return devicesCache.values();
}

public long countClients(final UniFiSite site, final Predicate<UniFiClient> filter) {
return getClients().stream().filter(c -> site.isSite(c.getSite())).filter(filter::test).count();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ public class UniFiDevice implements HasId {

private String model;

private String type;

private String name;

private String siteId;
Expand All @@ -46,6 +48,16 @@ public class UniFiDevice implements HasId {

private JsonObject[] portOverrides;

private boolean disabled;

public boolean isDisabled() {
return disabled;
}

public String getType() {
return type;
}

public UniFiDevice(final UniFiControllerCache cache) {
this.cache = cache;
}
Expand Down Expand Up @@ -81,6 +93,7 @@ public JsonObject[] getPortOverrides() {

@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());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/**
* 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()) {
// ToDo Error message
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();

switch (channelID) {
case CHANNEL_AP_ENABLE:
if (command instanceof OnOffType) {
return handleEnableCommand(controller, device, channelUID, command);
}
break;
default:
return false;
}
return false;
}

private boolean handleEnableCommand(final UniFiController controller, final UniFiDevice device,
final ChannelUID channelUID, final Command command) throws UniFiException {
if (command instanceof OnOffType) {
controller.disableAccessPoint(device, command == OnOffType.OFF);
refresh();
return true;
}
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,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.PARAMETER_CID;
import static org.openhab.binding.unifi.internal.UniFiBindingConstants.PARAMETER_MAC_ADDRESS;
import static org.openhab.binding.unifi.internal.UniFiBindingConstants.PARAMETER_PORT_NUMBER;
Expand All @@ -31,6 +32,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.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;
Expand Down Expand Up @@ -83,6 +85,7 @@ protected void startScan() {
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);
}
Expand Down Expand Up @@ -128,6 +131,20 @@ private void discoverClients(final UniFiControllerCache cache, final ThingUID br
}
}

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.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,17 @@
</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>
Expand Down
Loading