From 202a8647e0d7f4ff82110269ba70207d64408de5 Mon Sep 17 00:00:00 2001 From: Hilbrand Bouwkamp Date: Sat, 31 Dec 2022 11:03:56 +0100 Subject: [PATCH] [unifi] Fix bug with combination of other data/ports (#14060) - It seems to throw an exception when updating internal cache. It can happen if you have a switch that has both PoE ports and other PoE ports or data in the port override. - Fixed logout, should be POST instead of GET. - Fixed typo in channel-type.config.unifi.poeEnable.mode.option.pasv24 should be without appending v. - Removed compiler warnings. Signed-off-by: Hilbrand Bouwkamp --- .../unifi/internal/api/UniFiController.java | 23 ++-- .../internal/api/UniFiControllerRequest.java | 4 +- .../unifi/internal/api/cache/UniFiCache.java | 4 +- .../api/cache/UniFiControllerCache.java | 39 +++--- .../internal/api/cache/UniFiDeviceCache.java | 3 +- ...t.java => UnfiPortOverrideJsonObject.java} | 11 +- .../unifi/internal/api/dto/UniFiDevice.java | 6 +- .../internal/api/dto/UniFiPortTuple.java | 8 +- .../internal/api/dto/UniFiSwitchPorts.java | 122 ++++++++++++++++++ ...fiPortOverrideJsonElementDeserializer.java | 8 +- .../handler/UniFiBaseThingHandler.java | 7 +- .../handler/UniFiControllerThingHandler.java | 11 +- .../handler/UniFiPoePortThingHandler.java | 91 +++++++------ .../handler/UniFiThingDiscoveryService.java | 7 +- .../resources/OH-INF/i18n/unifi.properties | 2 +- 15 files changed, 241 insertions(+), 105 deletions(-) rename bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/dto/{UnfiPortOverrideJsonElement.java => UnfiPortOverrideJsonObject.java} (84%) create mode 100644 bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/dto/UniFiSwitchPorts.java diff --git a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/UniFiController.java b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/UniFiController.java index 41f18fd3f..905e3a900 100644 --- a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/UniFiController.java +++ b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/UniFiController.java @@ -14,18 +14,17 @@ package org.openhab.binding.unifi.internal.api; import java.util.Collection; import java.util.List; -import java.util.Map; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.http.HttpMethod; import org.openhab.binding.unifi.internal.api.cache.UniFiControllerCache; -import org.openhab.binding.unifi.internal.api.dto.UnfiPortOverrideJsonElement; +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.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; import org.openhab.binding.unifi.internal.api.dto.UniFiUnknownClient; import org.openhab.binding.unifi.internal.api.dto.UniFiWiredClient; import org.openhab.binding.unifi.internal.api.dto.UniFiWirelessClient; @@ -42,6 +41,7 @@ import org.slf4j.LoggerFactory; import com.google.gson.FieldNamingPolicy; import com.google.gson.Gson; import com.google.gson.GsonBuilder; +import com.google.gson.JsonObject; /** * The {@link UniFiController} is the main communication point with an external instance of the Ubiquiti Networks @@ -94,7 +94,7 @@ public class UniFiController { .registerTypeAdapter(UniFiWiredClient.class, clientInstanceCreator) .registerTypeAdapter(UniFiWirelessClient.class, clientInstanceCreator).create(); this.poeGson = new GsonBuilder() - .registerTypeAdapter(UnfiPortOverrideJsonElement.class, new UnfiPortOverrideJsonElementDeserializer()) + .registerTypeAdapter(UnfiPortOverrideJsonObject.class, new UnfiPortOverrideJsonElementDeserializer()) .create(); } @@ -133,7 +133,7 @@ public class UniFiController { public void logout() throws UniFiException { csrfToken = ""; - final UniFiControllerRequest req = newRequest(Void.class, HttpMethod.GET, gson); + final UniFiControllerRequest req = newRequest(Void.class, HttpMethod.POST, gson); req.setPath(unifios ? "/api/auth/logout" : "/logout"); executeRequest(req); } @@ -153,7 +153,7 @@ public class UniFiController { return cache; } - public @Nullable Map getSwitchPorts(@Nullable final String deviceId) { + public @Nullable UniFiSwitchPorts getSwitchPorts(@Nullable final String deviceId) { return cache.getSwitchPorts(deviceId); } @@ -175,10 +175,9 @@ public class UniFiController { refresh(); } - public boolean poeMode(final UniFiDevice device, final List data) - throws UniFiException { + public boolean poeMode(final UniFiDevice device, final List data) throws UniFiException { // 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.getJsonObject().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(), poeGson.toJson(data)); return false; @@ -225,7 +224,7 @@ public class UniFiController { throws UniFiException { T result; try { - result = request.execute(); + result = (T) request.execute(); csrfToken = request.getCsrfToken(); } catch (final UniFiExpiredSessionException e) { if (fromLogin) { @@ -234,11 +233,11 @@ public class UniFiController { throw new UniFiCommunicationException(e); } else { login(); - result = executeRequest(request); + result = (T) executeRequest(request); } } catch (final UniFiNotAuthorizedException e) { logger.warn("Not Authorized! Please make sure your controller credentials have administrator rights"); - result = null; + result = (T) null; } return result; } diff --git a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/UniFiControllerRequest.java b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/UniFiControllerRequest.java index 2d0215889..1a9946a46 100644 --- a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/UniFiControllerRequest.java +++ b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/UniFiControllerRequest.java @@ -124,14 +124,14 @@ class UniFiControllerRequest { } public @Nullable T execute() throws UniFiException { - T result = null; + T result = (T) null; final String json = getContent(); // mgb: only try and unmarshall non-void result types if (!Void.class.equals(resultType)) { final JsonObject jsonObject = JsonParser.parseString(json).getAsJsonObject(); if (jsonObject.has(PROPERTY_DATA) && jsonObject.get(PROPERTY_DATA).isJsonArray()) { - result = gson.fromJson(jsonObject.getAsJsonArray(PROPERTY_DATA), resultType); + result = (T) gson.fromJson(jsonObject.getAsJsonArray(PROPERTY_DATA), resultType); } } return result; diff --git a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/cache/UniFiCache.java b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/cache/UniFiCache.java index 44107ff2c..50a468ee2 100644 --- a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/cache/UniFiCache.java +++ b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/cache/UniFiCache.java @@ -106,7 +106,9 @@ abstract class UniFiCache { logger.debug("Put #{} entries in {}: {}", values.length, getClass().getSimpleName(), lazyFormatAsList(values)); for (final T value : values) { - put(value.getId(), value); + if (value != null) { + put(value.getId(), value); + } } } } diff --git a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/cache/UniFiControllerCache.java b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/cache/UniFiControllerCache.java index 698cb42b8..fbc3d40e9 100644 --- a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/cache/UniFiControllerCache.java +++ b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/cache/UniFiControllerCache.java @@ -13,7 +13,6 @@ package org.openhab.binding.unifi.internal.api.cache; import java.util.Collection; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; @@ -23,12 +22,11 @@ import java.util.stream.Stream; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.unifi.internal.api.dto.UnfiPortOverrideJsonElement; import org.openhab.binding.unifi.internal.api.dto.UniFiClient; import org.openhab.binding.unifi.internal.api.dto.UniFiDevice; -import org.openhab.binding.unifi.internal.api.dto.UniFiPortTable; import org.openhab.binding.unifi.internal.api.dto.UniFiPortTuple; 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.UniFiWlan; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -49,7 +47,7 @@ public class UniFiControllerCache { private final UniFiDeviceCache devicesCache = new UniFiDeviceCache(); private final UniFiClientCache clientsCache = new UniFiClientCache(); private final UniFiClientCache insightsCache = new UniFiClientCache(); - private final Map> devicesToPortTables = new ConcurrentHashMap<>(); + private final Map devicesToPortTables = new ConcurrentHashMap<>(); public void clear() { sitesCache.clear(); @@ -94,23 +92,23 @@ public class UniFiControllerCache { devicesCache.putAll(devices); if (devices != null) { Stream.of(devices).filter(Objects::nonNull).forEach(d -> { - Stream.ofNullable(d.getPortTable()).flatMap(pt -> Stream.of(pt)).filter(UniFiPortTable::isPortPoe) - .forEach(p -> { - final Map tupleTable = devicesToPortTables - .computeIfAbsent(d.getMac(), tt -> new HashMap<>()); - final UniFiPortTuple tuple = tupleTable.computeIfAbsent(p.getPortIdx(), - t -> new UniFiPortTuple()); + Stream.ofNullable(d.getPortTable()).forEach(pt -> { + final UniFiSwitchPorts switchPorts = devicesToPortTables.computeIfAbsent(d.getMac(), + p -> new UniFiSwitchPorts()); - tuple.setDevice(d); - tuple.setTable(p); - }); + Stream.of(pt).forEach(p -> { + @SuppressWarnings("null") + final UniFiPortTuple tuple = switchPorts.computeIfAbsent(p.getPortIdx()); + + tuple.setDevice(d); + tuple.setTable(p); + }); + }); Stream.ofNullable(d.getPortOverrides()).forEach(po -> { - final Map tupleTable = devicesToPortTables.get(d.getMac()); + final UniFiSwitchPorts tupleTable = devicesToPortTables.get(d.getMac()); if (tupleTable != null) { - Stream.of(po).filter(pof -> !pof.getAsJsonObject().entrySet().isEmpty()) - .map(UnfiPortOverrideJsonElement::new).forEach(p -> tupleTable - .computeIfAbsent(p.getPortIdx(), t -> new UniFiPortTuple()).setJsonElement(p)); + Stream.of(po).forEach(p -> tupleTable.setOverride(p)); } }); }); @@ -121,11 +119,12 @@ public class UniFiControllerCache { return devicesCache.get(id); } - public Map getSwitchPorts(@Nullable final String deviceId) { - return deviceId == null ? Map.of() : devicesToPortTables.getOrDefault(deviceId, Map.of()); + public UniFiSwitchPorts getSwitchPorts(@Nullable final String deviceId) { + return deviceId == null ? new UniFiSwitchPorts() + : devicesToPortTables.getOrDefault(deviceId, new UniFiSwitchPorts()); } - public Collection> getSwitchPorts() { + public Collection getSwitchPorts() { return devicesToPortTables.values(); } diff --git a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/cache/UniFiDeviceCache.java b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/cache/UniFiDeviceCache.java index 8cfd91c97..f9fd2d636 100644 --- a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/cache/UniFiDeviceCache.java +++ b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/cache/UniFiDeviceCache.java @@ -38,7 +38,8 @@ class UniFiDeviceCache extends UniFiCache { switch (prefix) { case MAC: return device.getMac(); + default: + return null; } - return null; } } diff --git a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/dto/UnfiPortOverrideJsonElement.java b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/dto/UnfiPortOverrideJsonObject.java similarity index 84% rename from bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/dto/UnfiPortOverrideJsonElement.java rename to bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/dto/UnfiPortOverrideJsonObject.java index 2562503b7..91cdcd0ab 100644 --- a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/dto/UnfiPortOverrideJsonElement.java +++ b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/dto/UnfiPortOverrideJsonObject.java @@ -12,7 +12,6 @@ */ package org.openhab.binding.unifi.internal.api.dto; -import com.google.gson.JsonElement; import com.google.gson.JsonObject; /** @@ -22,7 +21,7 @@ import com.google.gson.JsonObject; * * @author Hilbrand Bouwkamp - Initial contribution */ -public class UnfiPortOverrideJsonElement { +public class UnfiPortOverrideJsonObject { private static final String PORT_IDX = "port_idx"; private static final String PORT_CONF_ID = "port_conf_id"; @@ -30,14 +29,18 @@ public class UnfiPortOverrideJsonElement { private final JsonObject jsonObject; - public UnfiPortOverrideJsonElement(final JsonElement element) { - this.jsonObject = element.getAsJsonObject(); + public UnfiPortOverrideJsonObject(final JsonObject Object) { + this.jsonObject = Object.getAsJsonObject(); } public JsonObject getJsonObject() { return jsonObject; } + public static boolean hasPortIdx(final JsonObject jsonObject) { + return jsonObject.has(PORT_IDX); + } + public int getPortIdx() { return jsonObject.get(PORT_IDX).getAsInt(); } diff --git a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/dto/UniFiDevice.java b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/dto/UniFiDevice.java index 644ba3e63..551065a1b 100644 --- a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/dto/UniFiDevice.java +++ b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/dto/UniFiDevice.java @@ -15,7 +15,7 @@ package org.openhab.binding.unifi.internal.api.dto; import org.openhab.binding.unifi.internal.api.cache.UniFiControllerCache; import org.openhab.binding.unifi.internal.api.util.UniFiTidyLowerCaseStringDeserializer; -import com.google.gson.JsonElement; +import com.google.gson.JsonObject; import com.google.gson.annotations.JsonAdapter; import com.google.gson.annotations.SerializedName; @@ -44,7 +44,7 @@ public class UniFiDevice implements HasId { private UniFiPortTable[] portTable; - private JsonElement[] portOverrides; + private JsonObject[] portOverrides; public UniFiDevice(final UniFiControllerCache cache) { this.cache = cache; @@ -75,7 +75,7 @@ public class UniFiDevice implements HasId { return portTable; } - public JsonElement[] getPortOverrides() { + public JsonObject[] getPortOverrides() { return portOverrides; } diff --git a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/dto/UniFiPortTuple.java b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/dto/UniFiPortTuple.java index fa9865d87..fa83eff17 100644 --- a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/dto/UniFiPortTuple.java +++ b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/dto/UniFiPortTuple.java @@ -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, - * and the {@link UnfiPortOverrideJsonElement}, 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 */ @@ -24,7 +24,7 @@ public class UniFiPortTuple { private UniFiPortTable table; - private UnfiPortOverrideJsonElement jsonElement; + private UnfiPortOverrideJsonObject jsonElement; public UniFiDevice getDevice() { return device; @@ -46,11 +46,11 @@ public class UniFiPortTuple { this.table = table; } - public UnfiPortOverrideJsonElement getJsonElement() { + public UnfiPortOverrideJsonObject getJsonElement() { return jsonElement; } - public void setJsonElement(final UnfiPortOverrideJsonElement jsonElement) { + public void setJsonElement(final UnfiPortOverrideJsonObject jsonElement) { this.jsonElement = jsonElement; } } diff --git a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/dto/UniFiSwitchPorts.java b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/dto/UniFiSwitchPorts.java new file mode 100644 index 000000000..91850f648 --- /dev/null +++ b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/dto/UniFiSwitchPorts.java @@ -0,0 +1,122 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.unifi.internal.api.dto; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.function.Consumer; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +import com.google.gson.JsonObject; + +/** + * Data object to keep track of all port data, including all port_override data (both for ports and additional data) on + * a switch device. + * + * @author Hilbrand Bouwkamp - Initial contribution + */ +@NonNullByDefault +public class UniFiSwitchPorts { + + /** + * Port data grouped by port id. + */ + private final Map ports = new HashMap<>(); + /** + * Additional none port specific override data. Keep track to send to device when updating override data. + */ + private final Set otherOverrides = new HashSet<>(); + + /** + * Return port data for the given port + * + * @param portIdx port to get the data for + * @return Return port data for the given port + */ + public @Nullable UniFiPortTuple getPort(final int portIdx) { + return ports.get(portIdx); + } + + /** + * Return port data for the given port or if none exists set a new data object and return it. + * + * @param portIdx port to get the data for + * @return Return port data for the given port or if none exists set a new data object and return it. + */ + public UniFiPortTuple computeIfAbsent(final int portIdx) { + final UniFiPortTuple tuple = ports.computeIfAbsent(portIdx, t -> new UniFiPortTuple()); + if (tuple == null) { + // This should never happen because ports can never contain a null value, and computeIfAbsent should never + // return null. However to satisfy the compiler a check for null was added. + throw new IllegalStateException("UniFiPortTuple is null for portIdx " + portIdx); + } + return tuple; + } + + /** + * @return Returns the list of PoE Ports. + */ + public List getPoePorts() { + return ports.values().stream().filter(e -> e.getTable().isPortPoe()).collect(Collectors.toList()); + } + + /** + * Returns the override data as list with json objects after calling the updateMethod on the data for the given + * portIdx. + * The update method changes the data in the internal structure. + * + * @param portIdx port to call updateMethod for + * @param updateMethod method to call to update data for a specific port + * @return Returns a list of json objects of all override data + */ + public List updatedList(final int portIdx, final Consumer updateMethod) { + @SuppressWarnings("null") + final List updatedList = ports.entrySet().stream() + .map(e -> e.getValue().getJsonElement()).filter(Objects::nonNull).collect(Collectors.toList()); + + updatedList.stream().filter(p -> p.getPortIdx() == portIdx).findAny().ifPresent(updateMethod::accept); + + return Stream + .concat(otherOverrides.stream(), updatedList.stream().map(UnfiPortOverrideJsonObject::getJsonObject)) + .collect(Collectors.toList()); + } + + /** + * 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. + * + * @param jsonObject json object to set + */ + public void setOverride(final JsonObject jsonObject) { + if (UnfiPortOverrideJsonObject.hasPortIdx(jsonObject)) { + final UnfiPortOverrideJsonObject po = new UnfiPortOverrideJsonObject(jsonObject); + final UniFiPortTuple tuple = ports.get(po.getPortIdx()); + + if (tuple == null) { + otherOverrides.add(jsonObject); + } else { + tuple.setJsonElement(po); + } + } else { + otherOverrides.add(jsonObject); + } + } +} diff --git a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/util/UnfiPortOverrideJsonElementDeserializer.java b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/util/UnfiPortOverrideJsonElementDeserializer.java index 498a54d8b..dc5167bbc 100644 --- a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/util/UnfiPortOverrideJsonElementDeserializer.java +++ b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/api/util/UnfiPortOverrideJsonElementDeserializer.java @@ -15,22 +15,22 @@ package org.openhab.binding.unifi.internal.api.util; import java.lang.reflect.Type; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.unifi.internal.api.dto.UnfiPortOverrideJsonElement; +import org.openhab.binding.unifi.internal.api.dto.UnfiPortOverrideJsonObject; import com.google.gson.JsonElement; import com.google.gson.JsonSerializationContext; import com.google.gson.JsonSerializer; /** - * Serializer for {@link UnfiPortOverrideJsonElement}. Returns the content of the jsonObject in the class. + * Serializer for {@link UnfiPortOverrideJsonObject}. Returns the content of the jsonObject in the class. * * @author Hilbrand Bouwkamp - Initial contribution */ @NonNullByDefault -public class UnfiPortOverrideJsonElementDeserializer implements JsonSerializer { +public class UnfiPortOverrideJsonElementDeserializer implements JsonSerializer { @Override - public JsonElement serialize(final UnfiPortOverrideJsonElement src, final Type typeOfSrc, + public JsonElement serialize(final UnfiPortOverrideJsonObject src, final Type typeOfSrc, final JsonSerializationContext context) { return src.getJsonObject(); } diff --git a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/handler/UniFiBaseThingHandler.java b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/handler/UniFiBaseThingHandler.java index 5b8e41bf2..299d23ab8 100644 --- a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/handler/UniFiBaseThingHandler.java +++ b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/handler/UniFiBaseThingHandler.java @@ -61,6 +61,7 @@ public abstract class UniFiBaseThingHandler extends BaseThingHandler { return; } // mgb: derive the config class from the generic type + @SuppressWarnings("null") final Class clazz = (Class) (((ParameterizedType) getClass().getGenericSuperclass()) .getActualTypeArguments()[1]); final C config = (C) getConfigAs(clazz); @@ -99,7 +100,7 @@ public abstract class UniFiBaseThingHandler extends BaseThingHandler { logger.debug("Handling command = {} for channel = {}", command, channelUID); // mgb: only handle commands if we're ONLINE if (getThing().getStatus() == ONLINE) { - final E entity = getEntity(); + final @Nullable E entity = getEntity(); final UniFiController controller = getController(); if (command == REFRESH) { @@ -128,13 +129,13 @@ public abstract class UniFiBaseThingHandler extends BaseThingHandler { protected final void refresh() { // mgb: only refresh if we're ONLINE if (getThing().getStatus() == ONLINE) { - final E entity = getEntity(); + final @Nullable E entity = getEntity(); getThing().getChannels().forEach(channel -> updateState(entity, channel.getUID())); } } - private void updateState(final E entity, final ChannelUID channelUID) { + private void updateState(final @Nullable E entity, final ChannelUID channelUID) { final String channelId = channelUID.getId(); final State state = Optional.ofNullable(entity).map(e -> getChannelState(e, channelId)) .orElseGet(() -> getDefaultState(channelId)); diff --git a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/handler/UniFiControllerThingHandler.java b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/handler/UniFiControllerThingHandler.java index 6c799375f..151fd0c49 100644 --- a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/handler/UniFiControllerThingHandler.java +++ b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/handler/UniFiControllerThingHandler.java @@ -39,6 +39,7 @@ import org.openhab.core.thing.ThingStatus; import org.openhab.core.thing.ThingStatusDetail; import org.openhab.core.thing.ThingStatusInfo; import org.openhab.core.thing.binding.BaseBridgeHandler; +import org.openhab.core.thing.binding.ThingHandler; import org.openhab.core.thing.binding.ThingHandlerService; import org.openhab.core.thing.binding.builder.ThingStatusInfoBuilder; import org.openhab.core.types.Command; @@ -107,13 +108,15 @@ public class UniFiControllerThingHandler extends BaseBridgeHandler { @Override public void dispose() { cancelRefreshJob(); + final UniFiController controller = this.controller; + if (controller != null) { try { controller.stop(); } catch (final UniFiException e) { // mgb: nop as we're in dispose } - controller = null; + this.controller = null; } } @@ -188,8 +191,10 @@ public class UniFiControllerThingHandler extends BaseBridgeHandler { uc.refresh(); // mgb: then refresh all the client things getThing().getThings().forEach((thing) -> { - if (thing.getHandler() instanceof UniFiBaseThingHandler) { - ((UniFiBaseThingHandler) thing.getHandler()).refresh(); + final ThingHandler handler = thing.getHandler(); + + if (handler instanceof UniFiBaseThingHandler) { + ((UniFiBaseThingHandler) handler).refresh(); } }); } diff --git a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/handler/UniFiPoePortThingHandler.java b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/handler/UniFiPoePortThingHandler.java index 90501cb79..b847c8001 100644 --- a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/handler/UniFiPoePortThingHandler.java +++ b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/handler/UniFiPoePortThingHandler.java @@ -25,11 +25,6 @@ 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.core.library.unit.MetricPrefix.MILLI; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.stream.Collectors; - import javax.measure.quantity.ElectricCurrent; import javax.measure.quantity.ElectricPotential; import javax.measure.quantity.Power; @@ -40,14 +35,15 @@ import org.openhab.binding.unifi.internal.UniFiPoePortThingConfig; import org.openhab.binding.unifi.internal.api.UniFiController; import org.openhab.binding.unifi.internal.api.UniFiException; import org.openhab.binding.unifi.internal.api.cache.UniFiControllerCache; -import org.openhab.binding.unifi.internal.api.dto.UnfiPortOverrideJsonElement; import org.openhab.binding.unifi.internal.api.dto.UniFiDevice; import org.openhab.binding.unifi.internal.api.dto.UniFiPortTable; import org.openhab.binding.unifi.internal.api.dto.UniFiPortTuple; +import org.openhab.binding.unifi.internal.api.dto.UniFiSwitchPorts; import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.QuantityType; import org.openhab.core.library.types.StringType; import org.openhab.core.library.unit.Units; +import org.openhab.core.thing.Channel; import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.Thing; import org.openhab.core.thing.ThingStatus; @@ -64,8 +60,7 @@ import org.slf4j.LoggerFactory; * @author Hilbrand Bouwkamp - Initial contribution */ @NonNullByDefault -public class UniFiPoePortThingHandler - extends UniFiBaseThingHandler, UniFiPoePortThingConfig> { +public class UniFiPoePortThingHandler extends UniFiBaseThingHandler { private final Logger logger = LoggerFactory.getLogger(UniFiPoePortThingHandler.class); @@ -84,20 +79,30 @@ public class UniFiPoePortThingHandler "@text/error.thing.poe.offline.configuration_error"); return false; } - final String channelConfigPoeEnableMode = (String) getThing().getChannel(CHANNEL_PORT_POE_ENABLE) - .getConfiguration().get(CHANNEL_ENABLE_PARAMETER_MODE); - poeEnableMode = channelConfigPoeEnableMode.isBlank() ? CHANNEL_ENABLE_PARAMETER_MODE_AUTO - : channelConfigPoeEnableMode; - return true; + return initPoeEnableMode(); + } + + private boolean initPoeEnableMode() { + final Channel channel = getThing().getChannel(CHANNEL_PORT_POE_ENABLE); + + if (channel == null) { + return false; + } else { + final String channelConfigPoeEnableMode = (String) channel.getConfiguration() + .get(CHANNEL_ENABLE_PARAMETER_MODE); + poeEnableMode = channelConfigPoeEnableMode.isBlank() ? CHANNEL_ENABLE_PARAMETER_MODE_AUTO + : channelConfigPoeEnableMode; + return true; + } } @Override - protected @Nullable Map getEntity(final UniFiControllerCache cache) { + protected @Nullable UniFiSwitchPorts getEntity(final UniFiControllerCache cache) { return cache.getSwitchPorts(config.getMacAddress()); } @Override - protected State getChannelState(final Map ports, final String channelId) { + protected State getChannelState(final UniFiSwitchPorts ports, final String channelId) { final UniFiPortTuple portTuple = getPort(ports); if (portTuple == null) { @@ -143,30 +148,30 @@ public class UniFiPoePortThingHandler return UnDefType.NULL; } - private @Nullable UniFiPortTuple getPort(final Map ports) { - return ports.get(config.getPortNumber()); + private @Nullable UniFiPortTuple getPort(final UniFiSwitchPorts ports) { + return ports.getPort(config.getPortNumber()); } @Override - protected boolean handleCommand(final UniFiController controller, final Map ports, + protected boolean handleCommand(final UniFiController controller, final UniFiSwitchPorts ports, final ChannelUID channelUID, final Command command) throws UniFiException { final String channelID = channelUID.getIdWithoutGroup(); switch (channelID) { case CHANNEL_PORT_POE_ENABLE: if (command instanceof OnOffType) { - return handleModeCommand(controller, ports, getPort(ports), + return handleModeCommand(controller, ports, OnOffType.ON == command ? poeEnableMode : CHANNEL_ENABLE_PARAMETER_MODE_OFF); } break; case CHANNEL_PORT_POE_MODE: if (command instanceof StringType) { - return handleModeCommand(controller, ports, getPort(ports), command.toFullString()); + return handleModeCommand(controller, ports, command.toFullString()); } break; case CHANNEL_PORT_POE_CMD: if (command instanceof StringType) { - return handleCmd(controller, getPort(ports), command.toFullString()); + return handleCmd(controller, ports, command.toFullString()); } default: return false; @@ -174,39 +179,39 @@ public class UniFiPoePortThingHandler return false; } - private boolean handleModeCommand(final UniFiController controller, final Map ports, - final @Nullable UniFiPortTuple uniFiPortTuple, final String poeMode) throws UniFiException { - final UniFiDevice device = controller.getCache().getDevice(config.getMacAddress()); + private boolean handleModeCommand(final UniFiController controller, final UniFiSwitchPorts ports, + final String poeMode) throws UniFiException { + final @Nullable UniFiDevice device = controller.getCache().getDevice(config.getMacAddress()); - if (device == null || uniFiPortTuple == null) { - logger.info("Could not change the PoE port state for thing '{}': device {} or portToUpdate {} null", - getThing().getUID(), device, uniFiPortTuple); - } else { - final List updatedList = ports.entrySet().stream() - .map(e -> e.getValue().getJsonElement()).filter(Objects::nonNull).collect(Collectors.toList()); - - updatedList.stream().filter(p -> p.getPortIdx() == uniFiPortTuple.getPortIdx()).findAny() - .ifPresent(p -> p.setPoeMode(poeMode)); - controller.poeMode(device, updatedList); + if (canUpdate(device, ports) && device != null) { + controller.poeMode(device, ports.updatedList(config.getPortNumber(), p -> p.setPoeMode(poeMode))); // No refresh because UniFi device takes some time to update. Therefore a refresh would only show the // old state. } return true; } - private boolean handleCmd(final UniFiController controller, @Nullable final UniFiPortTuple portToUpdate, - final String command) throws UniFiException { - final UniFiDevice device = controller.getCache().getDevice(config.getMacAddress()); - if (device == null || portToUpdate == null) { - logger.info("Could not change the PoE port state for thing '{}': device {} or portToUpdate {} null", - getThing().getUID(), device, portToUpdate); - } else { + private boolean handleCmd(final UniFiController controller, final UniFiSwitchPorts ports, final String command) + throws UniFiException { + final @Nullable UniFiDevice device = controller.getCache().getDevice(config.getMacAddress()); + + if (canUpdate(device, ports) && device != null) { if (CHANNEL_PORT_POE_CMD_POWER_CYCLE.equalsIgnoreCase(command.replaceAll("[- ]", ""))) { - controller.poePowerCycle(device, portToUpdate.getPortIdx()); + controller.poePowerCycle(device, config.getPortNumber()); } else { logger.info("Unknown command '{}' given to PoE port for thing '{}': device {} or portToUpdate {} null", - command, getThing().getUID(), device, portToUpdate); + command, getThing().getUID(), device, ports); } + + } + return true; + } + + private boolean canUpdate(final @Nullable UniFiDevice device, final UniFiSwitchPorts ports) { + if (device == null || getPort(ports) == null) { + logger.info("Could not change the PoE port state for thing '{}': device {} or portToUpdate {} null", + getThing().getUID(), device, config.getPortNumber()); + return false; } return true; } diff --git a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/handler/UniFiThingDiscoveryService.java b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/handler/UniFiThingDiscoveryService.java index 0580b6b94..477eb1995 100644 --- a/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/handler/UniFiThingDiscoveryService.java +++ b/bundles/org.openhab.binding.unifi/src/main/java/org/openhab/binding/unifi/internal/handler/UniFiThingDiscoveryService.java @@ -21,7 +21,6 @@ import static org.openhab.binding.unifi.internal.UniFiBindingConstants.PARAMETER import static org.openhab.binding.unifi.internal.UniFiBindingConstants.PARAMETER_WIFI_NAME; import java.util.Map; -import java.util.Map.Entry; import java.util.concurrent.TimeUnit; import java.util.regex.Pattern; @@ -34,6 +33,7 @@ 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.UniFiPortTuple; 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.UniFiWlan; import org.openhab.core.config.discovery.AbstractDiscoveryService; import org.openhab.core.config.discovery.DiscoveryResultBuilder; @@ -161,9 +161,8 @@ public class UniFiThingDiscoveryService extends AbstractDiscoveryService } private void discoverPoePorts(final UniFiControllerCache cache, final ThingUID bridgeUID) { - for (final Map uc : cache.getSwitchPorts()) { - for (final Entry sp : uc.entrySet()) { - final UniFiPortTuple pt = sp.getValue(); + for (final UniFiSwitchPorts uc : cache.getSwitchPorts()) { + for (final UniFiPortTuple pt : uc.getPoePorts()) { final String deviceMac = pt.getDevice().getMac(); final String id = deviceMac.replace(":", "") + "_" + pt.getPortIdx(); final ThingUID thingUID = new ThingUID(UniFiBindingConstants.THING_TYPE_POE_PORT, bridgeUID, id); diff --git a/bundles/org.openhab.binding.unifi/src/main/resources/OH-INF/i18n/unifi.properties b/bundles/org.openhab.binding.unifi/src/main/resources/OH-INF/i18n/unifi.properties index dd5f1bf1e..1de0ea01f 100644 --- a/bundles/org.openhab.binding.unifi/src/main/resources/OH-INF/i18n/unifi.properties +++ b/bundles/org.openhab.binding.unifi/src/main/resources/OH-INF/i18n/unifi.properties @@ -127,7 +127,7 @@ channel-type.unifi.wpaMode.description = WPA Mode of the Wi-Fi network channel-type.config.unifi.poeEnable.mode.label = On Mode channel-type.config.unifi.poeEnable.mode.description = The value to set when setting PoE on. channel-type.config.unifi.poeEnable.mode.option.auto = Auto -channel-type.config.unifi.poeEnable.mode.option.pasv24v = 24V +channel-type.config.unifi.poeEnable.mode.option.pasv24 = 24V channel-type.config.unifi.poeEnable.mode.option.passthrough = Passthrough # status messages