[unifi] Various stability improvements (#14249)

* [unifi] Various stability improvements

- Fixed bug, causing nullpointer, in devices not reporting voltage/ampere values on PoE ports.
- Added some null checks to avoid having null pointer exceptions.
- Handled timeout exception that is in ExecutionException.
- Improved handling case where server returns data, but it's not from the api, and therefor can't be parsed.
- Improved adding additional text to the error messages by adding the reported exception message too.

Signed-off-by: Hilbrand Bouwkamp <hilbrand@h72.nl>
This commit is contained in:
Hilbrand Bouwkamp 2023-01-20 13:12:57 +01:00 committed by GitHub
parent a78db1feb2
commit bd23bcc87d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 99 additions and 68 deletions

View File

@ -22,7 +22,11 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
@NonNullByDefault @NonNullByDefault
public class UniFiCommunicationException extends UniFiException { public class UniFiCommunicationException extends UniFiException {
private static final long serialVersionUID = -7261308872245069364L; private static final long serialVersionUID = 1L;
public UniFiCommunicationException(final String message) {
super(message);
}
public UniFiCommunicationException(final Throwable cause) { public UniFiCommunicationException(final Throwable cause) {
super(cause); super(cause);

View File

@ -178,7 +178,7 @@ public class UniFiController {
public boolean poeMode(final UniFiDevice device, final List<JsonObject> data) throws UniFiException { public boolean poeMode(final UniFiDevice device, final List<JsonObject> data) throws UniFiException {
// Safety check to make sure no empty data is send to avoid corrupting override data on the device. // Safety check to make sure no empty data is send to avoid corrupting override data on the device.
if (data.isEmpty() || data.stream().anyMatch(p -> p.entrySet().isEmpty())) { if (data.isEmpty() || data.stream().anyMatch(p -> p.entrySet().isEmpty())) {
logger.info("Not overriding port for '{}', because port data contains empty json: {}", device.getName(), logger.info("Not overriding port for '{}', because port data contains empty JSON: {}", device.getName(),
poeGson.toJson(data)); poeGson.toJson(data));
return false; return false;
} else { } else {

View File

@ -47,6 +47,7 @@ import org.slf4j.LoggerFactory;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.GsonBuilder; import com.google.gson.GsonBuilder;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonParser; import com.google.gson.JsonParser;
/** /**
@ -59,6 +60,8 @@ import com.google.gson.JsonParser;
@NonNullByDefault @NonNullByDefault
class UniFiControllerRequest<T> { class UniFiControllerRequest<T> {
private static final String CONTROLLER_PARSE_ERROR = "@text/error.controller.parse_error";
private static final String CONTENT_TYPE_APPLICATION_JSON_UTF_8 = MimeTypes.Type.APPLICATION_JSON_UTF_8.asString(); private static final String CONTENT_TYPE_APPLICATION_JSON_UTF_8 = MimeTypes.Type.APPLICATION_JSON_UTF_8.asString();
private static final long TIMEOUT_SECONDS = 5; private static final long TIMEOUT_SECONDS = 5;
@ -128,10 +131,20 @@ class UniFiControllerRequest<T> {
final String json = getContent(); final String json = getContent();
// mgb: only try and unmarshall non-void result types // mgb: only try and unmarshall non-void result types
if (!Void.class.equals(resultType)) { if (!Void.class.equals(resultType)) {
final JsonObject jsonObject = JsonParser.parseString(json).getAsJsonObject(); try {
final JsonObject jsonObject = JsonParser.parseString(json).getAsJsonObject();
if (jsonObject.has(PROPERTY_DATA) && jsonObject.get(PROPERTY_DATA).isJsonArray()) { if (jsonObject.has(PROPERTY_DATA) && jsonObject.get(PROPERTY_DATA).isJsonArray()) {
result = (T) gson.fromJson(jsonObject.getAsJsonArray(PROPERTY_DATA), resultType); result = (T) gson.fromJson(jsonObject.getAsJsonArray(PROPERTY_DATA), resultType);
}
} catch (final JsonParseException e) {
logger.debug(
"Could not parse content retrieved from the server. Is the configuration pointing to the right server/port?, {}",
e.getMessage());
if (logger.isTraceEnabled()) {
prettyPrintJson(json);
}
throw new UniFiCommunicationException(CONTROLLER_PARSE_ERROR);
} }
} }
return result; return result;
@ -182,7 +195,9 @@ class UniFiControllerRequest<T> {
} catch (final ExecutionException e) { } catch (final ExecutionException e) {
// mgb: unwrap the cause and try to cleanly handle it // mgb: unwrap the cause and try to cleanly handle it
final Throwable cause = e.getCause(); final Throwable cause = e.getCause();
if (cause instanceof UnknownHostException) { if (cause instanceof TimeoutException) {
throw new UniFiCommunicationException(e);
} else if (cause instanceof UnknownHostException) {
// invalid hostname // invalid hostname
throw new UniFiInvalidHostException(cause); throw new UniFiInvalidHostException(cause);
} else if (cause instanceof ConnectException) { } else if (cause instanceof ConnectException) {
@ -242,14 +257,15 @@ class UniFiControllerRequest<T> {
return request; return request;
} }
private static String prettyPrintJson(final String content) { private String prettyPrintJson(final String content) {
try { try {
final JsonObject json = JsonParser.parseString(content).getAsJsonObject(); final JsonObject json = JsonParser.parseString(content).getAsJsonObject();
final Gson prettyGson = new GsonBuilder().setPrettyPrinting().create(); final Gson prettyGson = new GsonBuilder().setPrettyPrinting().create();
return prettyGson.toJson(json); return prettyGson.toJson(json);
} catch (final RuntimeException e) { } catch (final RuntimeException e) {
// If could not parse the string as json, just return the string logger.debug("RuntimeException pretty printing JSON. Returning the raw content.", e);
// If could not parse the string as JSON, just return the string
return content; return content;
} }
} }

View File

@ -16,7 +16,9 @@ import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jdt.annotation.Nullable;
@ -103,13 +105,12 @@ abstract class UniFiCache<T extends @Nullable HasId> {
public final void putAll(final T @Nullable [] values) { public final void putAll(final T @Nullable [] values) {
if (values != null) { if (values != null) {
logger.debug("Put #{} entries in {}: {}", values.length, getClass().getSimpleName(), if (logger.isDebugEnabled()) {
lazyFormatAsList(values)); logger.debug("Put #{} entries in {}: {}", values.length, getClass().getSimpleName(),
for (final T value : values) { Stream.of(values).filter(Objects::nonNull).map(Object::toString)
if (value != null) { .collect(Collectors.joining(System.lineSeparator() + " - ")));
put(value.getId(), value);
}
} }
Stream.of(values).filter(Objects::nonNull).forEach(value -> put(value.getId(), value));
} }
} }
@ -133,18 +134,4 @@ abstract class UniFiCache<T extends @Nullable HasId> {
} }
protected abstract @Nullable String getSuffix(T value, Prefix prefix); protected abstract @Nullable String getSuffix(T value, Prefix prefix);
private static Object lazyFormatAsList(final Object[] arr) {
return new Object() {
@Override
public String toString() {
String value = "";
for (final Object o : arr) {
value += "\n - " + o.toString();
}
return value;
}
};
}
} }

View File

@ -17,7 +17,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function; import java.util.function.Predicate;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
@ -138,8 +138,8 @@ public class UniFiControllerCache {
return clientsCache.values(); return clientsCache.values();
} }
public long countClients(final UniFiSite site, final Function<UniFiClient, Boolean> filter) { public long countClients(final UniFiSite site, final Predicate<UniFiClient> filter) {
return getClients().stream().filter(c -> site.isSite(c.getSite())).filter(filter::apply).count(); return getClients().stream().filter(c -> site.isSite(c.getSite())).filter(filter::test).count();
} }
public @Nullable UniFiClient getClient(@Nullable final String cid) { public @Nullable UniFiClient getClient(@Nullable final String cid) {

View File

@ -101,10 +101,10 @@ public abstract class UniFiClient implements HasId {
return blocked; return blocked;
} }
public abstract Boolean isWired(); public abstract boolean isWired();
public final Boolean isWireless() { public final boolean isWireless() {
return isWired() == null ? null : Boolean.FALSE.equals(isWired()); return !isWired();
} }
protected abstract String getDeviceMac(); protected abstract String getDeviceMac();

View File

@ -14,7 +14,7 @@ package org.openhab.binding.unifi.internal.api.dto;
/** /**
* Tuple to store both the {@link UniFiPortTable}, which contains the all information related to the port, * Tuple to store both the {@link UniFiPortTable}, which contains the all information related to the port,
* and the {@link UnfiPortOverrideJsonObject}, which contains the raw json data of the port override. * and the {@link UnfiPortOverrideJsonObject}, which contains the raw JSON data of the port override.
* *
* @author Hilbrand Bouwkamp - Initial contribution * @author Hilbrand Bouwkamp - Initial contribution
*/ */

View File

@ -79,13 +79,13 @@ public class UniFiSwitchPorts {
} }
/** /**
* Returns the override data as list with json objects after calling the updateMethod on the data for the given * Returns the override data as list with JSON objects after calling the updateMethod on the data for the given
* portIdx. * portIdx.
* The update method changes the data in the internal structure. * The update method changes the data in the internal structure.
* *
* @param portIdx port to call updateMethod for * @param portIdx port to call updateMethod for
* @param updateMethod method to call to update data for a specific port * @param updateMethod method to call to update data for a specific port
* @return Returns a list of json objects of all override data * @return Returns a list of JSON objects of all override data
*/ */
public List<JsonObject> updatedList(final int portIdx, final Consumer<UnfiPortOverrideJsonObject> updateMethod) { public List<JsonObject> updatedList(final int portIdx, final Consumer<UnfiPortOverrideJsonObject> updateMethod) {
@SuppressWarnings("null") @SuppressWarnings("null")
@ -103,7 +103,7 @@ public class UniFiSwitchPorts {
* Set the port override object. If it's for a specific port set bind it to the port data, otherwise store it as * Set the port override object. If it's for a specific port set bind it to the port data, otherwise store it as
* generic data. * generic data.
* *
* @param jsonObject json object to set * @param jsonObject JSON object to set
*/ */
public void setOverride(final JsonObject jsonObject) { public void setOverride(final JsonObject jsonObject) {
if (UnfiPortOverrideJsonObject.hasPortIdx(jsonObject)) { if (UnfiPortOverrideJsonObject.hasPortIdx(jsonObject)) {

View File

@ -29,12 +29,12 @@ public class UniFiUnknownClient extends UniFiClient {
} }
@Override @Override
public Boolean isWired() { public boolean isWired() {
return null; // mgb: no is_wired property in the json return false; // mgb: no is_wired property in the JSON
} }
@Override @Override
public String getDeviceMac() { public String getDeviceMac() {
return null; // mgb: no device mac in the json return null; // mgb: no device mac in the JSON
} }
} }

View File

@ -30,7 +30,7 @@ public class UniFiWiredClient extends UniFiClient {
} }
@Override @Override
public Boolean isWired() { public boolean isWired() {
return true; return true;
} }

View File

@ -38,7 +38,7 @@ public class UniFiWirelessClient extends UniFiClient {
} }
@Override @Override
public Boolean isWired() { public boolean isWired() {
return false; return false;
} }

View File

@ -165,7 +165,7 @@ public class UniFiClientThingHandler extends UniFiBaseThingHandler<UniFiClient,
protected State getChannelState(final UniFiClient client, final String channelId) { protected State getChannelState(final UniFiClient client, final String channelId) {
final boolean clientHome = isClientHome(client); final boolean clientHome = isClientHome(client);
final UniFiDevice device = client.getDevice(); final UniFiDevice device = client.getDevice();
final UniFiSite site = (device == null ? null : device.getSite()); final UniFiSite site = device == null ? null : device.getSite();
State state = getDefaultState(channelId); State state = getDefaultState(channelId);
switch (channelId) { switch (channelId) {

View File

@ -59,6 +59,7 @@ public class UniFiControllerThingHandler extends BaseBridgeHandler {
private static final String STATUS_DESCRIPTION_SSL_ERROR = "@text/error.bridge.offline.ssl_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_CREDENTIALS = "@text/error.bridge.offline.invalid_credentials";
private static final String STATUS_DESCRIPTION_INVALID_HOSTNAME = "@text/error.bridge.offline.invalid_hostname"; private static final String STATUS_DESCRIPTION_INVALID_HOSTNAME = "@text/error.bridge.offline.invalid_hostname";
private static final String I18N_STATUS_WITH_ARGUMENTS = "%s [\"%s\"]";
private final Logger logger = LoggerFactory.getLogger(UniFiControllerThingHandler.class); private final Logger logger = LoggerFactory.getLogger(UniFiControllerThingHandler.class);
@ -138,14 +139,14 @@ public class UniFiControllerThingHandler extends BaseBridgeHandler {
uc.start(); uc.start();
startRefresh = true; startRefresh = true;
} catch (final UniFiCommunicationException e) { } catch (final UniFiCommunicationException e) {
updateStatus(OFFLINE, COMMUNICATION_ERROR, STATUS_DESCRIPTION_COMMUNICATION_ERROR); updateStatusOffline(COMMUNICATION_ERROR, STATUS_DESCRIPTION_COMMUNICATION_ERROR, e.getMessage());
startRefresh = true; startRefresh = true;
} catch (final UniFiInvalidHostException e) { } catch (final UniFiInvalidHostException e) {
updateStatus(OFFLINE, CONFIGURATION_ERROR, STATUS_DESCRIPTION_INVALID_HOSTNAME); updateStatusOffline(CONFIGURATION_ERROR, STATUS_DESCRIPTION_INVALID_HOSTNAME, e.getMessage());
} catch (final UniFiSSLException e) { } catch (final UniFiSSLException e) {
updateStatus(OFFLINE, CONFIGURATION_ERROR, STATUS_DESCRIPTION_SSL_ERROR); updateStatusOffline(CONFIGURATION_ERROR, STATUS_DESCRIPTION_SSL_ERROR, e.getMessage());
} catch (final UniFiInvalidCredentialsException e) { } catch (final UniFiInvalidCredentialsException e) {
updateStatus(OFFLINE, CONFIGURATION_ERROR, STATUS_DESCRIPTION_INVALID_CREDENTIALS); updateStatusOffline(CONFIGURATION_ERROR, STATUS_DESCRIPTION_INVALID_CREDENTIALS, e.getMessage());
} catch (final UniFiException e) { } catch (final UniFiException e) {
logger.debug("Unknown error while configuring the UniFi Controller", e); logger.debug("Unknown error while configuring the UniFi Controller", e);
updateStatus(OFFLINE, CONFIGURATION_ERROR, e.getMessage()); updateStatus(OFFLINE, CONFIGURATION_ERROR, e.getMessage());
@ -174,15 +175,20 @@ public class UniFiControllerThingHandler extends BaseBridgeHandler {
refresh(); refresh();
updateStatus(ONLINE); updateStatus(ONLINE);
} catch (final UniFiCommunicationException e) { } catch (final UniFiCommunicationException e) {
updateStatus(OFFLINE, COMMUNICATION_ERROR, STATUS_DESCRIPTION_COMMUNICATION_ERROR); updateStatusOffline(COMMUNICATION_ERROR, STATUS_DESCRIPTION_COMMUNICATION_ERROR, e.getMessage());
} catch (final UniFiInvalidCredentialsException e) { } catch (final UniFiInvalidCredentialsException e) {
updateStatus(OFFLINE, CONFIGURATION_ERROR, STATUS_DESCRIPTION_INVALID_CREDENTIALS); updateStatusOffline(CONFIGURATION_ERROR, STATUS_DESCRIPTION_INVALID_CREDENTIALS, e.getMessage());
} catch (final RuntimeException | UniFiException e) { } catch (final RuntimeException | UniFiException e) {
logger.debug("Unhandled exception while refreshing the UniFi Controller {}", getThing().getUID(), e); logger.debug("Unhandled exception while refreshing the UniFi Controller {}", getThing().getUID(), e);
updateStatus(OFFLINE, COMMUNICATION_ERROR, e.getMessage()); updateStatus(OFFLINE, COMMUNICATION_ERROR, e.getMessage());
} }
} }
private void updateStatusOffline(final ThingStatusDetail thingStatusDetail, final String i18nKey,
final @Nullable String argument) {
updateStatus(OFFLINE, thingStatusDetail, String.format(I18N_STATUS_WITH_ARGUMENTS, i18nKey, argument));
}
private void refresh() throws UniFiException { private void refresh() throws UniFiException {
final UniFiController uc = controller; final UniFiController uc = controller;

View File

@ -25,9 +25,8 @@ import static org.openhab.binding.unifi.internal.UniFiBindingConstants.CHANNEL_P
import static org.openhab.binding.unifi.internal.UniFiBindingConstants.CHANNEL_PORT_POE_VOLTAGE; import static org.openhab.binding.unifi.internal.UniFiBindingConstants.CHANNEL_PORT_POE_VOLTAGE;
import static org.openhab.core.library.unit.MetricPrefix.MILLI; import static org.openhab.core.library.unit.MetricPrefix.MILLI;
import javax.measure.quantity.ElectricCurrent; import javax.measure.Quantity;
import javax.measure.quantity.ElectricPotential; import javax.measure.Unit;
import javax.measure.quantity.Power;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jdt.annotation.Nullable;
@ -126,13 +125,13 @@ public class UniFiPoePortThingHandler extends UniFiBaseThingHandler<UniFiSwitchP
state = StringType.valueOf(port.getPoeMode()); state = StringType.valueOf(port.getPoeMode());
break; break;
case CHANNEL_PORT_POE_POWER: case CHANNEL_PORT_POE_POWER:
state = new QuantityType<Power>(Double.valueOf(port.getPoePower()), Units.WATT); state = safeDouble(port.getPoePower(), Units.WATT);
break; break;
case CHANNEL_PORT_POE_VOLTAGE: case CHANNEL_PORT_POE_VOLTAGE:
state = new QuantityType<ElectricPotential>(Double.valueOf(port.getPoeVoltage()), Units.VOLT); state = safeDouble(port.getPoeVoltage(), Units.VOLT);
break; break;
case CHANNEL_PORT_POE_CURRENT: case CHANNEL_PORT_POE_CURRENT:
state = new QuantityType<ElectricCurrent>(Double.valueOf(port.getPoeCurrent()), MILLI(Units.AMPERE)); state = safeDouble(port.getPoeCurrent(), MILLI(Units.AMPERE));
break; break;
default: default:
state = UnDefType.UNDEF; state = UnDefType.UNDEF;
@ -140,6 +139,15 @@ public class UniFiPoePortThingHandler extends UniFiBaseThingHandler<UniFiSwitchP
return state; return state;
} }
private <Q extends Quantity<Q>> State safeDouble(final String value, final Unit<Q> unit) {
try {
return value == null ? UnDefType.UNDEF : QuantityType.valueOf(Double.parseDouble(value), unit);
} catch (final NumberFormatException e) {
logger.debug("Could not parse value '{}' for unit {}", value, unit);
return UnDefType.UNDEF;
}
}
private State setOfflineOnNoPoEPortData() { private State setOfflineOnNoPoEPortData() {
if (getThing().getStatus() != ThingStatus.OFFLINE) { if (getThing().getStatus() != ThingStatus.OFFLINE) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,

View File

@ -127,8 +127,9 @@ public class UniFiThingDiscoveryService extends AbstractDiscoveryService
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,
stripIdShort(wlan.getId())); stripIdShort(wlan.getId()));
final Map<String, Object> properties = Map.of(PARAMETER_WID, wlan.getId(), PARAMETER_SITE, final String siteName = wlan.getSite() == null ? "" : wlan.getSite().getName();
wlan.getSite().getName(), PARAMETER_WIFI_NAME, wlan.getName()); final Map<String, Object> properties = Map.of(PARAMETER_WID, wlan.getId(), PARAMETER_SITE, siteName,
PARAMETER_WIFI_NAME, wlan.getName());
thingDiscovered(DiscoveryResultBuilder.create(thingUID).withThingType(UniFiBindingConstants.THING_TYPE_WLAN) thingDiscovered(DiscoveryResultBuilder.create(thingUID).withThingType(UniFiBindingConstants.THING_TYPE_WLAN)
.withBridge(bridgeUID).withRepresentationProperty(PARAMETER_WID).withTTL(TTL_SECONDS) .withBridge(bridgeUID).withRepresentationProperty(PARAMETER_WID).withTTL(TTL_SECONDS)
@ -157,7 +158,7 @@ public class UniFiThingDiscoveryService extends AbstractDiscoveryService
* @return shortened id or if to short the original id * @return shortened id or if to short the original id
*/ */
private static String stripIdShort(final String id) { private static String stripIdShort(final String id) {
return id.length() > THING_ID_LENGTH ? id.substring(id.length() - THING_ID_LENGTH) : id; return id != null && id.length() > THING_ID_LENGTH ? id.substring(id.length() - THING_ID_LENGTH) : id;
} }
private void discoverPoePorts(final UniFiControllerCache cache, final ThingUID bridgeUID) { private void discoverPoePorts(final UniFiControllerCache cache, final ThingUID bridgeUID) {

View File

@ -24,7 +24,7 @@ import static org.openhab.binding.unifi.internal.UniFiBindingConstants.CHANNEL_W
import static org.openhab.binding.unifi.internal.UniFiBindingConstants.CHANNEL_WPAENC; import static org.openhab.binding.unifi.internal.UniFiBindingConstants.CHANNEL_WPAENC;
import static org.openhab.binding.unifi.internal.UniFiBindingConstants.CHANNEL_WPAMODE; import static org.openhab.binding.unifi.internal.UniFiBindingConstants.CHANNEL_WPAMODE;
import java.util.function.Function; import java.util.function.Predicate;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jdt.annotation.Nullable;
@ -48,6 +48,7 @@ import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType; import org.openhab.core.types.UnDefType;
/** /**
* The {@link UniFiWlanThingHandler} is responsible for handling commands and status updates for a wireless network.
* *
* @author Hilbrand Bouwkamp - Initial contribution * @author Hilbrand Bouwkamp - Initial contribution
*/ */
@ -127,10 +128,17 @@ public class UniFiWlanThingHandler extends UniFiBaseThingHandler<UniFiWlan, UniF
return state; return state;
} }
private static State countClients(final UniFiWlan wlan, final Function<UniFiClient, Boolean> filter) { private static State countClients(final UniFiWlan wlan, final Predicate<UniFiClient> filter) {
final UniFiSite site = wlan.getSite(); 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))); if (site == null) {
return UnDefType.UNDEF;
} else {
return new DecimalType(site.getCache().countClients(site,
c -> c instanceof UniFiWirelessClient
&& (wlan.getName() != null && wlan.getName().equals(((UniFiWirelessClient) c).getEssid()))
&& filter.test(c)));
}
} }
/** /**
@ -161,7 +169,7 @@ public class UniFiWlanThingHandler extends UniFiBaseThingHandler<UniFiWlan, UniF
final ChannelUID channelUID, final Command command) throws UniFiException { final ChannelUID channelUID, final Command command) throws UniFiException {
final String channelID = channelUID.getId(); final String channelID = channelUID.getId();
if (CHANNEL_ENABLE.equals(channelID) && command instanceof OnOffType) { if (CHANNEL_ENABLE.equals(channelID) && command instanceof OnOffType && entity.getSite() != null) {
controller.enableWifi(entity, OnOffType.ON == command); controller.enableWifi(entity, OnOffType.ON == command);
return true; return true;
} }

View File

@ -132,10 +132,11 @@ channel-type.config.unifi.poeEnable.mode.option.passthrough = Passthrough
# status messages # status messages
error.bridge.offline.communication_error = Error communicating with the UniFi controller. error.bridge.offline.communication_error = Error communicating with the UniFi controller: {0}
error.bridge.offline.invalid_credentials = Invalid username and/or password - please double-check your configuration. error.bridge.offline.invalid_credentials = Invalid username and/or password - please double-check your configuration: {0}
error.bridge.offline.invalid_hostname = Invalid hostname - please double-check your configuration. error.bridge.offline.invalid_hostname = Invalid hostname - please double-check your configuration: {0}
error.bridge.offline.ssl_error = Error establishing an SSL connection with the UniFi controller. error.bridge.offline.ssl_error = Error establishing an SSL connection with the UniFi controller: {0}
error.controller.parse_error = Could not parse JSON from the UniFi Controller. Is the bridge configured with the correct host and/or port?
error.thing.client.offline.configuration_error = You must define a MAC address, IP address, hostname or alias for this thing. 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.bridge_offline = The UniFi Controller is currently offline.
error.thing.offline.configuration_error = You must choose a UniFi Controller for this thing. error.thing.offline.configuration_error = You must choose a UniFi Controller for this thing.