From 5437a6685ce278c69c41cb7e3e7b59454b91f896 Mon Sep 17 00:00:00 2001 From: J-N-K Date: Sun, 15 Jun 2025 16:35:13 +0200 Subject: [PATCH] [ring] Refactor discovery and allow multiple accounts (#18767) With this refactoring the device registry is bound to the account bridge, making it possible to use multiple ring accounts. It also changes the discovery service to a `ThingHandlerService, simplifying code. Signed-off-by: Jan N. Klug --- CODEOWNERS | 2 +- .../ring/handler/AbstractRingHandler.java | 26 +-- .../binding/ring/handler/AccountHandler.java | 44 +++--- .../binding/ring/handler/ChimeHandler.java | 4 +- .../binding/ring/handler/DoorbellHandler.java | 10 +- .../ring/handler/OtherDeviceHandler.java | 10 +- .../ring/handler/RingDeviceHandler.java | 36 +++-- .../ring/handler/StickupcamHandler.java | 10 +- .../binding/ring/internal/RestClient.java | 15 +- .../binding/ring/internal/RingAccount.java | 24 +-- .../ring/internal/RingDeviceRegistry.java | 92 +++++------ .../internal/data/AbstractRingDevice.java | 74 ++------- .../binding/ring/internal/data/Chime.java | 27 +--- .../ring/internal/data/DataFactory.java | 51 ------ .../binding/ring/internal/data/Doorbell.java | 28 +--- .../binding/ring/internal/data/Feature.java | 78 --------- .../ring/internal/data/OtherDevice.java | 27 +--- .../binding/ring/internal/data/Profile.java | 9 +- .../ring/internal/data/RingDevice.java | 41 +---- .../ring/internal/data/RingDevices.java | 148 ------------------ .../ring/internal/data/RingDevicesTO.java | 34 ++++ .../ring/internal/data/Stickupcam.java | 27 +--- .../discovery/RingDiscoveryService.java | 106 +++++-------- 23 files changed, 220 insertions(+), 703 deletions(-) delete mode 100644 bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/internal/data/DataFactory.java delete mode 100644 bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/internal/data/Feature.java delete mode 100644 bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/internal/data/RingDevices.java create mode 100644 bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/internal/data/RingDevicesTO.java diff --git a/CODEOWNERS b/CODEOWNERS index 43ae00e8609..29a3b3221f4 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -331,7 +331,7 @@ /bundles/org.openhab.binding.resol/ @ramack /bundles/org.openhab.binding.revogi/ @openhab/add-ons-maintainers /bundles/org.openhab.binding.rfxcom/ @martinvw @paulianttila -/bundles/org.openhab.binding.ring/ @morph166955 +/bundles/org.openhab.binding.ring/ @psmedley /bundles/org.openhab.binding.rme/ @kgoderis /bundles/org.openhab.binding.robonect/ @reyem /bundles/org.openhab.binding.roku/ @mlobstein diff --git a/bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/handler/AbstractRingHandler.java b/bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/handler/AbstractRingHandler.java index ccadcd7507c..98e5b899d1a 100644 --- a/bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/handler/AbstractRingHandler.java +++ b/bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/handler/AbstractRingHandler.java @@ -17,12 +17,13 @@ import java.util.concurrent.TimeUnit; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.ring.internal.RingDeviceRegistry; +import org.openhab.binding.ring.internal.RingAccount; import org.openhab.binding.ring.internal.errors.DeviceNotFoundException; import org.openhab.core.library.types.OnOffType; +import org.openhab.core.thing.Bridge; import org.openhab.core.thing.Thing; -import org.openhab.core.thing.ThingStatus; import org.openhab.core.thing.binding.BaseThingHandler; +import org.openhab.core.thing.binding.BridgeHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -104,15 +105,18 @@ public abstract class AbstractRingHandler extends BaseThingHandler { @Override public void handleRemoval() { - updateStatus(ThingStatus.OFFLINE); - final String id = getThing().getUID().getId(); - final RingDeviceRegistry registry = RingDeviceRegistry.getInstance(); - try { - registry.removeRingDevice(id); - } catch (final DeviceNotFoundException e) { - logger.debug("Exception occurred during execution of handleRemoval(): {}", e.getMessage(), e); - } finally { - updateStatus(ThingStatus.REMOVED); + String id = getThing().getUID().getId(); + Bridge bridge = getBridge(); + if (bridge != null) { + BridgeHandler bridgeHandler = bridge.getHandler(); + if (bridgeHandler instanceof RingAccount ringAccount) { + try { + ringAccount.getDeviceRegistry().removeRingDevice(id); + } catch (DeviceNotFoundException ignored) { + logger.warn("Tried to remove a device that was not present in the ring account: {}", id); + } + } } + super.handleRemoval(); } } diff --git a/bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/handler/AccountHandler.java b/bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/handler/AccountHandler.java index 82a1017bc16..cc1808f9414 100644 --- a/bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/handler/AccountHandler.java +++ b/bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/handler/AccountHandler.java @@ -21,7 +21,9 @@ import java.net.InetAddress; import java.net.NetworkInterface; import java.nio.file.Files; import java.nio.file.Paths; +import java.util.Collection; import java.util.List; +import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledFuture; @@ -34,8 +36,9 @@ import org.openhab.binding.ring.internal.RingAccount; import org.openhab.binding.ring.internal.RingDeviceRegistry; import org.openhab.binding.ring.internal.RingVideoServlet; import org.openhab.binding.ring.internal.data.Profile; -import org.openhab.binding.ring.internal.data.RingDevices; +import org.openhab.binding.ring.internal.data.RingDevicesTO; import org.openhab.binding.ring.internal.data.RingEventTO; +import org.openhab.binding.ring.internal.discovery.RingDiscoveryService; import org.openhab.binding.ring.internal.errors.AuthenticationException; import org.openhab.binding.ring.internal.errors.DuplicateIdException; import org.openhab.binding.ring.internal.utils.RingUtils; @@ -50,6 +53,7 @@ import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.ThingStatus; import org.openhab.core.thing.ThingStatusDetail; import org.openhab.core.thing.binding.BaseBridgeHandler; +import org.openhab.core.thing.binding.ThingHandlerService; import org.openhab.core.types.Command; import org.openhab.core.types.RefreshType; import org.osgi.service.http.HttpService; @@ -75,7 +79,6 @@ public class AccountHandler extends BaseBridgeHandler implements RingAccount { private @Nullable Runnable runnableVideo = null; private @Nullable RingVideoServlet ringVideoServlet; private final HttpService httpService; - private final String thingId; // Current status protected OnOffType status = OnOffType.OFF; @@ -92,7 +95,7 @@ public class AccountHandler extends BaseBridgeHandler implements RingAccount { /** * The registry. */ - private final RingDeviceRegistry registry = RingDeviceRegistry.getInstance(); + private final RingDeviceRegistry registry; /** * The RestClient is used to connect to the Ring Account. */ @@ -129,7 +132,7 @@ public class AccountHandler extends BaseBridgeHandler implements RingAccount { this.networkAddressService = networkAddressService; this.httpService = httpService; this.videoExecutorService = Executors.newCachedThreadPool(); - this.thingId = this.getThing().getUID().getId(); + this.registry = new RingDeviceRegistry(); } @Override @@ -227,7 +230,7 @@ public class AccountHandler extends BaseBridgeHandler implements RingAccount { private void saveRefreshTokenToFile(String refreshToken) { String folderName = OpenHAB.getUserDataFolder() + "/ring"; - String thingId = this.thingId; + String thingId = getThing().getUID().getId(); File folder = new File(folderName); String fileName = folderName + "/ring." + thingId + ".refreshToken"; @@ -246,7 +249,7 @@ public class AccountHandler extends BaseBridgeHandler implements RingAccount { private String getRefreshTokenFromFile() { String refreshToken = ""; String folderName = OpenHAB.getUserDataFolder() + "/ring"; - String thingId = this.thingId; + String thingId = getThing().getUID().getId(); String fileName = folderName + "/ring." + thingId + ".refreshToken"; File file = new File(fileName); if (!file.exists()) { @@ -398,8 +401,8 @@ public class AccountHandler extends BaseBridgeHandler implements RingAccount { private void refreshRegistry() throws JsonParseException, AuthenticationException, DuplicateIdException { logger.debug("AccountHandler - refreshRegistry"); - RingDevices ringDevices = restClient.getRingDevices(userProfile, this); - registry.addRingDevices(ringDevices.getRingDevices()); + RingDevicesTO ringDevices = restClient.getRingDevices(userProfile, this); + registry.addOrUpdateRingDevices(ringDevices); } protected void minuteTick() { @@ -578,21 +581,6 @@ public class AccountHandler extends BaseBridgeHandler implements RingAccount { return ""; } - @Override - public @Nullable RestClient getRestClient() { - return restClient; - } - - @Override - public @Nullable Profile getProfile() { - return userProfile; - } - - @Override - public String getThingId() { - return thingId; - } - /** * Dispose of the refreshJob nicely. */ @@ -607,4 +595,14 @@ public class AccountHandler extends BaseBridgeHandler implements RingAccount { this.videoExecutorService = null; super.dispose(); } + + @Override + public RingDeviceRegistry getDeviceRegistry() { + return registry; + } + + @Override + public Collection> getServices() { + return Set.of(RingDiscoveryService.class); + } } diff --git a/bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/handler/ChimeHandler.java b/bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/handler/ChimeHandler.java index 8f14a274acd..6e97909d616 100644 --- a/bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/handler/ChimeHandler.java +++ b/bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/handler/ChimeHandler.java @@ -47,9 +47,9 @@ public class ChimeHandler extends RingDeviceHandler { logger.debug("Initializing Chime handler"); super.initialize(); - RingDeviceRegistry registry = RingDeviceRegistry.getInstance(); + RingDeviceRegistry registry = getDeviceRegistry(); String id = getThing().getUID().getId(); - if (registry.isInitialized()) { + if (registry != null && registry.isInitialized()) { try { linkDevice(id, Chime.class); updateStatus(ThingStatus.ONLINE); diff --git a/bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/handler/DoorbellHandler.java b/bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/handler/DoorbellHandler.java index 37c5c9b0fc4..a764cd04fbf 100644 --- a/bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/handler/DoorbellHandler.java +++ b/bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/handler/DoorbellHandler.java @@ -54,9 +54,9 @@ public class DoorbellHandler extends RingDeviceHandler { logger.debug("Initializing Doorbell handler"); super.initialize(); - RingDeviceRegistry registry = RingDeviceRegistry.getInstance(); + RingDeviceRegistry registry = getDeviceRegistry(); String id = getThing().getUID().getId(); - if (registry.isInitialized()) { + if (registry != null && registry.isInitialized()) { try { linkDevice(id, Doorbell.class); updateStatus(ThingStatus.ONLINE); @@ -102,13 +102,13 @@ public class DoorbellHandler extends RingDeviceHandler { if (device == null) { initialize(); } - RingDeviceTO deviceTO = gson.fromJson(device.getJsonObject(), RingDeviceTO.class); - if ((deviceTO != null) && (deviceTO.health.batteryPercentage != lastBattery)) { + RingDeviceTO deviceTO = device.getDeviceStatus(); + if (deviceTO.health.batteryPercentage != lastBattery) { logger.debug("Battery Level: {}", deviceTO.health.batteryPercentage); ChannelUID channelUID = new ChannelUID(thing.getUID(), CHANNEL_STATUS_BATTERY); updateState(channelUID, new DecimalType(deviceTO.health.batteryPercentage)); lastBattery = deviceTO.health.batteryPercentage; - } else if (deviceTO != null) { + } else { logger.debug("Battery Level Unchanged for {} - {} vs {}", getThing().getUID().getId(), deviceTO.health.batteryPercentage, lastBattery); } diff --git a/bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/handler/OtherDeviceHandler.java b/bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/handler/OtherDeviceHandler.java index 600fc1f76ab..512a0916bf6 100644 --- a/bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/handler/OtherDeviceHandler.java +++ b/bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/handler/OtherDeviceHandler.java @@ -53,9 +53,9 @@ public class OtherDeviceHandler extends RingDeviceHandler { logger.debug("Initializing Other Device handler"); super.initialize(); - RingDeviceRegistry registry = RingDeviceRegistry.getInstance(); + RingDeviceRegistry registry = getDeviceRegistry(); String id = getThing().getUID().getId(); - if (registry.isInitialized()) { + if (registry != null && registry.isInitialized()) { try { linkDevice(id, OtherDevice.class); updateStatus(ThingStatus.ONLINE); @@ -102,13 +102,13 @@ public class OtherDeviceHandler extends RingDeviceHandler { initialize(); } - RingDeviceTO deviceTO = gson.fromJson(device.getJsonObject(), RingDeviceTO.class); - if ((deviceTO != null) && (deviceTO.health.batteryPercentage != lastBattery)) { + RingDeviceTO deviceTO = device.getDeviceStatus(); + if (deviceTO.health.batteryPercentage != lastBattery) { logger.debug("Battery Level: {}", deviceTO.health.batteryPercentage); ChannelUID channelUID = new ChannelUID(thing.getUID(), CHANNEL_STATUS_BATTERY); updateState(channelUID, new DecimalType(deviceTO.health.batteryPercentage)); lastBattery = deviceTO.health.batteryPercentage; - } else if (deviceTO != null) { + } else { logger.debug("Battery Level Unchanged for {} - {} vs {}", getThing().getUID().getId(), deviceTO.health.batteryPercentage, lastBattery); diff --git a/bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/handler/RingDeviceHandler.java b/bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/handler/RingDeviceHandler.java index 03558d2d580..73187970976 100644 --- a/bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/handler/RingDeviceHandler.java +++ b/bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/handler/RingDeviceHandler.java @@ -16,6 +16,7 @@ import static org.openhab.binding.ring.RingBindingConstants.*; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.ring.internal.RingAccount; import org.openhab.binding.ring.internal.RingDeviceRegistry; import org.openhab.binding.ring.internal.data.RingDevice; import org.openhab.binding.ring.internal.data.RingDeviceTO; @@ -26,8 +27,10 @@ import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.IncreaseDecreaseType; import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.UpDownType; +import org.openhab.core.thing.Bridge; import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.Thing; +import org.openhab.core.thing.binding.BridgeHandler; import org.openhab.core.types.Command; import org.openhab.core.types.RefreshType; @@ -53,6 +56,17 @@ public abstract class RingDeviceHandler extends AbstractRingHandler { super(thing, gson); } + protected @Nullable RingDeviceRegistry getDeviceRegistry() { + Bridge bridge = getBridge(); + if (bridge != null) { + BridgeHandler bridgeHandler = bridge.getHandler(); + if (bridgeHandler instanceof RingAccount ringAccount) { + return ringAccount.getDeviceRegistry(); + } + } + return null; + } + /** * Link the device, and update the device with the status CONFIGURED. * @@ -63,17 +77,15 @@ public abstract class RingDeviceHandler extends AbstractRingHandler { */ protected void linkDevice(String id, Class deviceClass) throws DeviceNotFoundException, IllegalDeviceClassException { - device = RingDeviceRegistry.getInstance().getRingDevice(id); - if (device != null) { - RingDeviceTO deviceTO = gson.fromJson(device.getJsonObject(), RingDeviceTO.class); + RingDeviceRegistry registry = getDeviceRegistry(); + if (registry != null) { + device = registry.getRingDevice(id); + RingDeviceTO deviceTO = device.getDeviceStatus(); if (deviceClass.equals(device.getClass())) { device.setRegistrationStatus(RingDeviceRegistry.Status.CONFIGURED); - device.setRingDeviceHandler(this); - if (deviceTO != null) { - thing.setProperty("Description", deviceTO.description); - thing.setProperty("Kind", deviceTO.kind); - thing.setProperty("Device ID", deviceTO.deviceId); - } + thing.setProperty("Description", deviceTO.description); + thing.setProperty("Kind", deviceTO.kind); + thing.setProperty("Device ID", deviceTO.deviceId); } else { throw new IllegalDeviceClassException("Class '" + deviceClass.getName() + "' expected but '" + device.getClass().getName() + "' found."); @@ -93,10 +105,8 @@ public abstract class RingDeviceHandler extends AbstractRingHandler { updateState(channelUID, enabled); break; case CHANNEL_STATUS_BATTERY: - RingDeviceTO deviceTO = gson.fromJson(device.getJsonObject(), RingDeviceTO.class); - if (deviceTO != null) { - updateState(channelUID, new DecimalType(deviceTO.health.batteryPercentage)); - } + RingDeviceTO deviceTO = device.getDeviceStatus(); + updateState(channelUID, new DecimalType(deviceTO.health.batteryPercentage)); break; default: logger.debug("Command received for an unknown channel: {}", channelUID.getId()); diff --git a/bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/handler/StickupcamHandler.java b/bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/handler/StickupcamHandler.java index 3f942613c71..e94c9785204 100644 --- a/bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/handler/StickupcamHandler.java +++ b/bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/handler/StickupcamHandler.java @@ -54,9 +54,9 @@ public class StickupcamHandler extends RingDeviceHandler { logger.debug("Initializing Stickupcam handler"); super.initialize(); - RingDeviceRegistry registry = RingDeviceRegistry.getInstance(); + RingDeviceRegistry registry = getDeviceRegistry(); String id = getThing().getUID().getId(); - if (registry.isInitialized()) { + if (registry != null && registry.isInitialized()) { try { linkDevice(id, Stickupcam.class); updateStatus(ThingStatus.ONLINE); @@ -102,13 +102,13 @@ public class StickupcamHandler extends RingDeviceHandler { if (device == null) { initialize(); } - RingDeviceTO deviceTO = gson.fromJson(device.getJsonObject(), RingDeviceTO.class); - if ((deviceTO != null) && (deviceTO.health.batteryPercentage != lastBattery)) { + RingDeviceTO deviceTO = device.getDeviceStatus(); + if (deviceTO.health.batteryPercentage != lastBattery) { logger.debug("Battery Level: {}", deviceTO.battery); ChannelUID channelUID = new ChannelUID(thing.getUID(), CHANNEL_STATUS_BATTERY); updateState(channelUID, new DecimalType(deviceTO.health.batteryPercentage)); lastBattery = deviceTO.health.batteryPercentage; - } else if (deviceTO != null) { + } else { logger.debug("Battery Level Unchanged for {} - {} vs {}", getThing().getUID().getId(), deviceTO.health.batteryPercentage, lastBattery); diff --git a/bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/internal/RestClient.java b/bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/internal/RestClient.java index 029646c2105..8b3a080f6d6 100644 --- a/bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/internal/RestClient.java +++ b/bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/internal/RestClient.java @@ -46,10 +46,9 @@ import javax.net.ssl.TrustManager; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.ring.internal.data.DataFactory; import org.openhab.binding.ring.internal.data.ParamBuilder; import org.openhab.binding.ring.internal.data.Profile; -import org.openhab.binding.ring.internal.data.RingDevices; +import org.openhab.binding.ring.internal.data.RingDevicesTO; import org.openhab.binding.ring.internal.data.RingEventTO; import org.openhab.binding.ring.internal.errors.AuthenticationException; import org.openhab.binding.ring.internal.utils.RingUtils; @@ -277,12 +276,7 @@ public class RestClient { } JsonObject oauthToken = getOauthToken(username, password, refToken); - String jsonResult = postRequest(ApiConstants.URL_SESSION, DataFactory.getSessionParams(hardwareId), - oauthToken.get("access_token").getAsString()); - - JsonObject obj = JsonParser.parseString(jsonResult).getAsJsonObject(); - return new Profile((JsonObject) obj.get("profile"), oauthToken.get("refresh_token").getAsString(), - oauthToken.get("access_token").getAsString()); + return new Profile(oauthToken.get("refresh_token").getAsString(), oauthToken.get("access_token").getAsString()); } /** @@ -522,12 +516,11 @@ public class RestClient { * @throws AuthenticationException when request is invalid. * @throws JsonParseException when response is invalid JSON. */ - public RingDevices getRingDevices(Profile profile, RingAccount ringAccount) + public RingDevicesTO getRingDevices(Profile profile, RingAccount ringAccount) throws JsonParseException, AuthenticationException { logger.debug("RestClient - getRingDevices"); String jsonResult = getRequest(ApiConstants.URL_DEVICES, profile); - JsonObject obj = JsonParser.parseString(jsonResult).getAsJsonObject(); - return new RingDevices(obj, ringAccount); + return Objects.requireNonNull(gson.fromJson(jsonResult, RingDevicesTO.class)); } /** diff --git a/bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/internal/RingAccount.java b/bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/internal/RingAccount.java index 159168d19a9..1601f829bc5 100644 --- a/bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/internal/RingAccount.java +++ b/bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/internal/RingAccount.java @@ -13,8 +13,6 @@ package org.openhab.binding.ring.internal; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.ring.internal.data.Profile; /** * The AccountHandler implements this interface to facilitate the @@ -25,26 +23,10 @@ import org.openhab.binding.ring.internal.data.Profile; */ @NonNullByDefault public interface RingAccount { - /** - * Get the linked REST client. + * Get the Device Registry * - * @return the REST client. + * @return the ring device registry */ - public @Nullable RestClient getRestClient(); - - /** - * Get the linked user profile. - * - * @return the user profile. - */ - public @Nullable Profile getProfile(); - - /** - * Get the Account Handler Thing ID - * * - * - * @return the ring account thing id. - */ - public String getThingId(); + RingDeviceRegistry getDeviceRegistry(); } diff --git a/bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/internal/RingDeviceRegistry.java b/bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/internal/RingDeviceRegistry.java index c6e0d6734fa..546c946326e 100644 --- a/bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/internal/RingDeviceRegistry.java +++ b/bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/internal/RingDeviceRegistry.java @@ -13,19 +13,22 @@ package org.openhab.binding.ring.internal; import java.util.Collection; +import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.ring.internal.data.Chime; +import org.openhab.binding.ring.internal.data.Doorbell; +import org.openhab.binding.ring.internal.data.OtherDevice; import org.openhab.binding.ring.internal.data.RingDevice; import org.openhab.binding.ring.internal.data.RingDeviceTO; +import org.openhab.binding.ring.internal.data.RingDevicesTO; +import org.openhab.binding.ring.internal.data.Stickupcam; import org.openhab.binding.ring.internal.errors.DeviceNotFoundException; -import org.openhab.binding.ring.internal.errors.DuplicateIdException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.google.gson.Gson; - /** * Singleton registry of found devices. * @@ -35,15 +38,6 @@ import com.google.gson.Gson; @NonNullByDefault public class RingDeviceRegistry { - private final Gson gson = new Gson(); - - /** - * static Singleton instance. - */ - private static final RingDeviceRegistry INSTANCE = new RingDeviceRegistry(); - /** - * The logger. - */ private final Logger logger = LoggerFactory.getLogger(RingDeviceRegistry.class); /** * Will be set after initialization. @@ -51,51 +45,35 @@ public class RingDeviceRegistry { private boolean initialized; /** - * Key: device id. - * Value: the RingDevice implementation object. + * Key: device id. Value: the RingDevice implementation object. */ - private ConcurrentHashMap devices = new ConcurrentHashMap<>(); + private final Map devices = new ConcurrentHashMap<>(); - /** - * Return a singleton instance of RingDeviceRegistry. - */ - public static RingDeviceRegistry getInstance() { - return INSTANCE; - } - - /** - * Add a new ring device. - */ - public void addRingDevice(RingDevice ringDevice) throws DuplicateIdException { - RingDeviceTO deviceTO = gson.fromJson(ringDevice.getJsonObject(), RingDeviceTO.class); - if (deviceTO != null) { - if (devices.containsKey(deviceTO.id)) { - throw new DuplicateIdException("Ring device with duplicate id " + deviceTO.id + " ignored"); + private void addOrUpdateRingDevice(RingDeviceTO deviceTO, Function creator) { + devices.compute(deviceTO.id, (id, existing) -> { + if (existing == null) { + RingDevice device = creator.apply(deviceTO); + device.setRegistrationStatus(Status.ADDED); + return device; } else { - ringDevice.setRegistrationStatus(Status.ADDED); - devices.put(deviceTO.id, ringDevice); + logger.debug( + "RingDeviceRegistry - addRingDevices - Ring device with duplicate id {} ignored. Updating Json.", + deviceTO.id); + existing.setDeviceStatus(deviceTO); + return existing; } - } + }); } /** * Add a new ring device collection. */ - public synchronized void addRingDevices(Collection ringDevices) { - for (RingDevice device : ringDevices) { - RingDeviceTO deviceTO = gson.fromJson(device.getJsonObject(), RingDeviceTO.class); - if (deviceTO != null) { - logger.debug("RingDeviceRegistry - addRingDevices - Trying: {}", deviceTO.id); - try { - addRingDevice(device); - } catch (DuplicateIdException e) { - logger.debug( - "RingDeviceRegistry - addRingDevices - Ring device with duplicate id {} ignored. Updating Json.", - deviceTO.id); - devices.get(deviceTO.id).setJsonObject(device.getJsonObject()); - } - } - } + public synchronized void addOrUpdateRingDevices(RingDevicesTO ringDevices) { + ringDevices.doorbells.forEach(deviceTO -> addOrUpdateRingDevice(deviceTO, Doorbell::new)); + ringDevices.chimes.forEach(deviceTO -> addOrUpdateRingDevice(deviceTO, Chime::new)); + ringDevices.stickupCams.forEach(deviceTO -> addOrUpdateRingDevice(deviceTO, Stickupcam::new)); + ringDevices.other.forEach(deviceTO -> addOrUpdateRingDevice(deviceTO, OtherDevice::new)); + initialized = true; } @@ -115,9 +93,10 @@ public class RingDeviceRegistry { * @return the RingDevice instance from the registry. * @throws DeviceNotFoundException */ - public @Nullable RingDevice getRingDevice(String id) throws DeviceNotFoundException { - if (devices.containsKey(id)) { - return devices.get(id); + public RingDevice getRingDevice(String id) throws DeviceNotFoundException { + RingDevice device = devices.get(id); + if (device != null) { + return device; } else { throw new DeviceNotFoundException("Device with id '" + id + "' not found"); } @@ -130,8 +109,9 @@ public class RingDeviceRegistry { * @throws DeviceNotFoundException */ public void removeRingDevice(String id) throws DeviceNotFoundException { - if (devices.containsKey(id)) { - devices.remove(id); + RingDevice device = devices.get(id); + if (device != null) { + device.setRegistrationStatus(Status.ADDED); } else { throw new DeviceNotFoundException("Device with id '" + id + "' not found"); } @@ -164,7 +144,6 @@ public class RingDeviceRegistry { * The registry status of the device. * * @author Wim Vissers - * */ public enum Status { /** @@ -172,8 +151,7 @@ public class RingDeviceRegistry { */ ADDED, /** - * When reported to the system as discovered device. It will show up - * in the inbox. + * When reported to the system as discovered device. It will show up in the inbox. */ DISCOVERED, /** diff --git a/bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/internal/data/AbstractRingDevice.java b/bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/internal/data/AbstractRingDevice.java index b28b4b6e547..5f86f70dbbb 100644 --- a/bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/internal/data/AbstractRingDevice.java +++ b/bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/internal/data/AbstractRingDevice.java @@ -13,16 +13,10 @@ package org.openhab.binding.ring.internal.data; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.ring.handler.RingDeviceHandler; -import org.openhab.binding.ring.internal.RingAccount; import org.openhab.binding.ring.internal.RingDeviceRegistry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.google.gson.Gson; -import com.google.gson.JsonObject; - /** * Interface common to all Ring devices. * @@ -32,31 +26,12 @@ import com.google.gson.JsonObject; @NonNullByDefault public abstract class AbstractRingDevice implements RingDevice { - private final Logger logger = LoggerFactory.getLogger(AbstractRingDevice.class); - public final Gson gson = new Gson(); - - /** - * The JsonObject contains the data retrieved from the Ring API, - * or the data to send to the API. - */ - protected JsonObject jsonObject = new JsonObject(); - /** - * The registration status. - */ + private RingDeviceTO deviceStatus; private RingDeviceRegistry.Status registrationStatus = RingDeviceRegistry.Status.ADDED; - /** - * The linked Ring account. - */ - private final RingAccount ringAccount; - /** - * The linked RingDeviceHandler. - */ - private @Nullable RingDeviceHandler ringDeviceHandler; - protected AbstractRingDevice(JsonObject jsonObject, RingAccount ringAccount) { - this.jsonObject = jsonObject; - this.ringAccount = ringAccount; + protected AbstractRingDevice(RingDeviceTO jsonObject) { + this.deviceStatus = jsonObject; } /** @@ -72,52 +47,21 @@ public abstract class AbstractRingDevice implements RingDevice { /** * Set the registration status. * - * @param status + * @param registrationStatus */ @Override public void setRegistrationStatus(RingDeviceRegistry.Status registrationStatus) { this.registrationStatus = registrationStatus; } - /** - * Get the linked Ring Device Handler. - * - * @return the handler. - */ @Override - @Nullable - public RingDeviceHandler getRingDeviceHandler() { - return ringDeviceHandler; - } - - /** - * Set the linked Ring Device Handler. - * - * @param ringDeviceHandler the handler. - */ - @Override - public void setRingDeviceHandler(RingDeviceHandler ringDeviceHandler) { - this.ringDeviceHandler = ringDeviceHandler; - } - - /** - * Get the linked Ring account. - * - * @return the account. - */ - @Override - public RingAccount getRingAccount() { - return ringAccount; + public void setDeviceStatus(RingDeviceTO ringDeviceTO) { + this.deviceStatus = ringDeviceTO; + logger.trace("AbstractRingDevice - setJsonObject - Updated JSON: {}", ringDeviceTO); } @Override - public void setJsonObject(JsonObject jsonObject) { - this.jsonObject = jsonObject; - logger.trace("AbstractRingDevice - setJsonObject - Updated JSON: {}", this.jsonObject); - } - - @Override - public JsonObject getJsonObject() { - return this.jsonObject; + public RingDeviceTO getDeviceStatus() { + return deviceStatus; } } diff --git a/bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/internal/data/Chime.java b/bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/internal/data/Chime.java index 6b26e9e5864..2a3817addf5 100644 --- a/bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/internal/data/Chime.java +++ b/bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/internal/data/Chime.java @@ -13,12 +13,6 @@ package org.openhab.binding.ring.internal.data; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.ring.internal.RingAccount; -import org.openhab.core.config.discovery.DiscoveryResult; -import org.openhab.core.config.discovery.DiscoveryResultBuilder; -import org.openhab.core.thing.ThingUID; - -import com.google.gson.JsonObject; /** * @author Ben Rosenblum - Initial contribution @@ -29,24 +23,9 @@ public class Chime extends AbstractRingDevice { /** * Create Chime instance from JSON object. * - * @param jsonChime the JSON Chime retrieved from the Ring API. - * @param ringAccount the Ring Account in use + * @param deviceTO the JSON Chime retrieved from the Ring API. */ - public Chime(JsonObject jsonChime, RingAccount ringAccount) { - super(jsonChime, ringAccount); - } - - /** - * Get the DiscoveryResult object to identify the device as - * discovered thing. - * - * @return the device as DiscoveryResult instance. - */ - @Override - public DiscoveryResult getDiscoveryResult(RingDeviceTO deviceTO) { - DiscoveryResult result = DiscoveryResultBuilder - .create(new ThingUID("ring:chime:" + getRingAccount().getThingId() + ":" + deviceTO.id)) - .withLabel("Ring Chime - " + deviceTO.description).build(); - return result; + public Chime(RingDeviceTO deviceTO) { + super(deviceTO); } } diff --git a/bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/internal/data/DataFactory.java b/bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/internal/data/DataFactory.java deleted file mode 100644 index 36cce939412..00000000000 --- a/bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/internal/data/DataFactory.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (c) 2010-2025 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.ring.internal.data; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.ring.internal.ApiConstants; - -/** - * @author Wim Vissers - Initial contribution - * @author Ben Rosenblum - Updated for OH4 / New Maintainer - */ - -@NonNullByDefault -public class DataFactory { - public static String getOauthData(String username, String password) { - return ""; - } - - /** - * Get GET parameters for the session API resource. - * - * @return - */ - public static String getSessionParams(String hardwareId) { - ParamBuilder pb = new ParamBuilder(false); - pb.add("device[os]", "android"); - pb.add("device[hardware_id]", hardwareId); - pb.add("device[app_brand]", "ring"); - pb.add("device[metadata][device_model]", "VirtualBox"); - pb.add("device[metadata][resolution]", "600x800"); - pb.add("device[metadata][app_version]", "1.7.29"); - pb.add("device[metadata][app_installation_date]", ""); - pb.add("device[metadata][os_version]", "4.4.4"); - pb.add("device[metadata][manufacturer]", "innotek GmbH"); - pb.add("device[metadata][is_tablet]", "true"); - pb.add("device[metadata][linphone_initialized]", "true"); - pb.add("device[metadata][language]", "en"); - pb.add("api_version", "" + ApiConstants.API_VERSION); - return pb.toString(); - } -} diff --git a/bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/internal/data/Doorbell.java b/bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/internal/data/Doorbell.java index 6b3adc58f98..4618be8ee73 100644 --- a/bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/internal/data/Doorbell.java +++ b/bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/internal/data/Doorbell.java @@ -13,12 +13,6 @@ package org.openhab.binding.ring.internal.data; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.ring.internal.RingAccount; -import org.openhab.core.config.discovery.DiscoveryResult; -import org.openhab.core.config.discovery.DiscoveryResultBuilder; -import org.openhab.core.thing.ThingUID; - -import com.google.gson.JsonObject; /** * @author Wim Vissers - Initial contribution @@ -27,28 +21,12 @@ import com.google.gson.JsonObject; @NonNullByDefault public class Doorbell extends AbstractRingDevice { - /** * Create Doorbell instance from JSON object. * - * @param jsonDoorbell the JSON doorbell (doorbot) retrieved from the Ring API. - * @param ringAccount the Ring Account in use + * @param deviceTO the JSON doorbell (doorbot) retrieved from the Ring API. */ - public Doorbell(JsonObject jsonDoorbell, RingAccount ringAccount) { - super(jsonDoorbell, ringAccount); - } - - /** - * Get the DiscoveryResult object to identify the device as - * discovered thing. - * - * @return the device as DiscoveryResult instance. - */ - @Override - public DiscoveryResult getDiscoveryResult(RingDeviceTO deviceTO) { - DiscoveryResult result = DiscoveryResultBuilder - .create(new ThingUID("ring:doorbell:" + getRingAccount().getThingId() + ":" + deviceTO.id)) - .withLabel("Ring Video Doorbell - " + deviceTO.description).build(); - return result; + public Doorbell(RingDeviceTO deviceTO) { + super(deviceTO); } } diff --git a/bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/internal/data/Feature.java b/bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/internal/data/Feature.java deleted file mode 100644 index 358f5a4eeb2..00000000000 --- a/bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/internal/data/Feature.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (c) 2010-2025 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.ring.internal.data; - -import org.eclipse.jdt.annotation.NonNullByDefault; - -/** - * @author Wim Vissers - Initial contribution - * @author Ben Rosenblum - Updated for OH4 / New Maintainer - */ -@NonNullByDefault -public enum Feature { - - REMOTE_LOGGING_FORMAT_STORING, - REMOTE_LOGGING_LEVEL, - SUBSCRIPTIONS_ENABLED, - STICKUPCAM_SETUP_ENABLED, - VOD_ENABLED, - NW_ENABLED, - NW_V2_ENABLED, - NW_USER_ACTIVATED, - RINGPLUS_ENABLED, - LPD_ENABLED, - REACTIVE_SNOOZING_ENABLED, - PROACTIVE_SNOOZING_ENABLED, - OWNER_PROACTIVE_SNOOZING_ENABLED, - LIVE_VIEW_SETTINGS_ENABLED, - DELETE_ALL_SETTINGS_ENABLED, - POWER_CABLE_ENABLED, - DEVICE_HEALTH_ALERTS_ENABLED, - CHIME_PRO_ENABLED, - MULTIPLE_CALLS_ENABLED, - UJET_ENABLED, - MULTIPLE_DELETE_ENABLED, - DELETE_ALL_ENABLED, - LPD_MOTION_ANNOUNCEMENT_ENABLED, - STARRED_EVENTS_ENABLED, - CHIME_DND_ENABLED, - VIDEO_SEARCH_ENABLED, - FLOODLIGHT_CAM_ENABLED, - NW_LARGER_AREA_ENABLED, - RING_CAM_BATTERY_ENABLED, - ELITE_CAM_ENABLED, - DOORBELL_V2_ENABLED, - SPOTLIGHT_BATTERY_DASHBOARD_CONTROLS_ENABLED, - BYPASS_ACCOUNT_VERIFICATION, - LEGACY_CVR_RETENTION_ENABLED, - NEW_DASHBOARD_ENABLED, - RING_CAM_ENABLED, - RING_SEARCH_ENABLED, - RING_CAM_MOUNT_ENABLED, - RING_ALARM_ENABLED, - IN_APP_CALL_NOTIFICATIONS, - RING_CASH_ELIGIBLE_ENABLED, - NEW_RING_PLAYER_ENABLED, - APP_ALERT_TONES_ENABLED, - MOTION_SNOOZING_ENABLED; - - /** - * The enum is named according to the json names retrieved from - * the Ring API, but in upper case. - * - * @return the json name. - */ - public String getJsonName() { - return this.toString().toLowerCase(); - } -} diff --git a/bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/internal/data/OtherDevice.java b/bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/internal/data/OtherDevice.java index d498716c609..88a34675c5a 100644 --- a/bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/internal/data/OtherDevice.java +++ b/bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/internal/data/OtherDevice.java @@ -13,12 +13,6 @@ package org.openhab.binding.ring.internal.data; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.ring.internal.RingAccount; -import org.openhab.core.config.discovery.DiscoveryResult; -import org.openhab.core.config.discovery.DiscoveryResultBuilder; -import org.openhab.core.thing.ThingUID; - -import com.google.gson.JsonObject; /** * @author Ben Rosenblum - Initial contribution @@ -29,24 +23,9 @@ public class OtherDevice extends AbstractRingDevice { /** * Create OtherDevice instance from JSON object. * - * @param jsonOtherDevice the JSON Other retrieved from the Ring API. - * @param ringAccount the Ring Account in use + * @param deviceTO the JSON Other retrieved from the Ring API. */ - public OtherDevice(JsonObject jsonOtherDevice, RingAccount ringAccount) { - super(jsonOtherDevice, ringAccount); - } - - /** - * Get the DiscoveryResult object to identify the device as - * discovered thing. - * - * @return the device as DiscoveryResult instance. - */ - @Override - public DiscoveryResult getDiscoveryResult(RingDeviceTO deviceTO) { - DiscoveryResult result = DiscoveryResultBuilder - .create(new ThingUID("ring:otherdevice:" + getRingAccount().getThingId() + ":" + deviceTO.id)) - .withLabel("Ring Other Device - " + deviceTO.description).build(); - return result; + public OtherDevice(RingDeviceTO deviceTO) { + super(deviceTO); } } diff --git a/bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/internal/data/Profile.java b/bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/internal/data/Profile.java index 5bf511abe36..a2ee240e3fc 100644 --- a/bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/internal/data/Profile.java +++ b/bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/internal/data/Profile.java @@ -14,8 +14,6 @@ package org.openhab.binding.ring.internal.data; import org.eclipse.jdt.annotation.NonNullByDefault; -import com.google.gson.JsonObject; - /** * {"profile":{ * "id":4445516, @@ -83,23 +81,18 @@ import com.google.gson.JsonObject; @NonNullByDefault public class Profile { - private JsonObject jsonProfile = new JsonObject(); - private JsonObject jsonFeatures = new JsonObject(); private String refreshToken = ""; private String accessToken = ""; /** * Create Profile instance from JSON String. * - * @param jsonProfile the JSON profile retrieved from the Ring API. * @param refreshToken needed for the refresh token so we aren't logging in every time. * Needed as a separate parameter because it's not part of the jsonProfile object. * @param accessToken needed for the access token so we aren't logging in every time. * Needed as a separate parameter because it's not part of the jsonProfile object. */ - public Profile(JsonObject jsonProfile, String refreshToken, String accessToken) { - this.jsonProfile = jsonProfile; - this.jsonFeatures = (JsonObject) jsonProfile.get("features"); + public Profile(String refreshToken, String accessToken) { this.refreshToken = refreshToken; this.accessToken = accessToken; } diff --git a/bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/internal/data/RingDevice.java b/bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/internal/data/RingDevice.java index 622876f8540..bb19322fc2f 100644 --- a/bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/internal/data/RingDevice.java +++ b/bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/internal/data/RingDevice.java @@ -13,13 +13,7 @@ package org.openhab.binding.ring.internal.data; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.ring.handler.RingDeviceHandler; -import org.openhab.binding.ring.internal.RingAccount; import org.openhab.binding.ring.internal.RingDeviceRegistry; -import org.openhab.core.config.discovery.DiscoveryResult; - -import com.google.gson.JsonObject; /** * Interface common to all Ring devices. @@ -29,15 +23,6 @@ import com.google.gson.JsonObject; */ @NonNullByDefault public interface RingDevice { - - /** - * Get the DiscoveryResult object to identify the device as - * discovered thing. - * - * @return the device as DiscoveryResult instance. - */ - DiscoveryResult getDiscoveryResult(RingDeviceTO deviceTO); - /** * Get the registration status. * @@ -52,29 +37,7 @@ public interface RingDevice { */ void setRegistrationStatus(RingDeviceRegistry.Status registrationStatus); - /** - * Get the linked Ring account. - * - * @return the account. - */ - RingAccount getRingAccount(); + void setDeviceStatus(RingDeviceTO ringDeviceTO); - /** - * Get the linked Ring Device Handler. - * - * @return the handler. - */ - @Nullable - RingDeviceHandler getRingDeviceHandler(); - - /** - * Set the linked Ring Device Handler. - * - * @param ringDeviceHandler the handler. - */ - void setRingDeviceHandler(RingDeviceHandler ringDeviceHandler); - - void setJsonObject(JsonObject jsonObject); - - JsonObject getJsonObject(); + RingDeviceTO getDeviceStatus(); } diff --git a/bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/internal/data/RingDevices.java b/bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/internal/data/RingDevices.java deleted file mode 100644 index 0a86208b5ec..00000000000 --- a/bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/internal/data/RingDevices.java +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright (c) 2010-2025 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.ring.internal.data; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.ring.internal.ApiConstants; -import org.openhab.binding.ring.internal.RingAccount; - -import com.google.gson.JsonArray; -import com.google.gson.JsonObject; - -/** - * - * @author Wim Vissers - Initial contribution - * @author Chris Milbert - stickupcam contribution - * @author Ben Rosenblum - Updated for OH4 / New Maintainer - */ - -@NonNullByDefault -public class RingDevices { - private List doorbells = new ArrayList<>(); - private List stickupcams = new ArrayList<>(); - private List chimes = new ArrayList<>(); - private List otherdevices = new ArrayList<>(); - - public RingDevices(JsonObject jsonRingDevices, RingAccount ringAccount) { - addDoorbells((JsonArray) jsonRingDevices.get(ApiConstants.DEVICES_DOORBOTS), ringAccount); - addStickupCams((JsonArray) jsonRingDevices.get(ApiConstants.DEVICES_STICKUP_CAMS), ringAccount); - addChimes((JsonArray) jsonRingDevices.get(ApiConstants.DEVICES_CHIMES), ringAccount); - addOtherDevices((JsonArray) jsonRingDevices.get(ApiConstants.DEVICES_OTHERDEVICE), ringAccount); - } - - /** - * Helper method to create the doorbell list. - * - * @param jsonDoorbells - * @param ringAccount - */ - private void addDoorbells(JsonArray jsonDoorbells, RingAccount ringAccount) { - for (Object obj : jsonDoorbells) { - Doorbell doorbell = new Doorbell((JsonObject) obj, ringAccount); - doorbells.add(doorbell); - } - } - - /** - * Retrieve the Doorbells Collection. - * - * @return - */ - public Collection getDoorbells() { - return doorbells; - } - - /** - * Helper method to create the stickupcam list. - * - * @param jsonStickupcams - * @param ringAccount - */ - private void addStickupCams(JsonArray jsonStickupcams, RingAccount ringAccount) { - for (Object obj : jsonStickupcams) { - Stickupcam stickupcam = new Stickupcam((JsonObject) obj, ringAccount); - stickupcams.add(stickupcam); - } - } - - /** - * Retrieve the Stickupcams Collection. - * - * @return - */ - public Collection getStickupcams() { - return stickupcams; - } - - /** - * Helper method to create the chime list. - * - * @param jsonChimes - * @param ringAccount - */ - private void addChimes(JsonArray jsonChimes, RingAccount ringAccount) { - for (Object obj : jsonChimes) { - Chime chime = new Chime((JsonObject) obj, ringAccount); - chimes.add(chime); - } - } - - /** - * Retrieve the Chimes Collection. - * - * @return - */ - public Collection getChimes() { - return chimes; - } - - /** - * Helper method to create the other list. - * - * @param jsonOther - * @param ringAccount - */ - private void addOtherDevices(JsonArray jsonOtherDevices, RingAccount ringAccount) { - for (Object obj : jsonOtherDevices) { - OtherDevice otherdevice = new OtherDevice((JsonObject) obj, ringAccount); - otherdevices.add(otherdevice); - } - } - - /** - * Retrieve the Others Collection. - * - * @return - */ - public Collection getOtherDevices() { - return otherdevices; - } - - /** - * Retrieve a collection of all devices. - * - * @return - */ - public Collection getRingDevices() { - List result = new ArrayList<>(); - result.addAll(doorbells); - result.addAll(stickupcams); - result.addAll(chimes); - result.addAll(otherdevices); - return result; - } -} diff --git a/bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/internal/data/RingDevicesTO.java b/bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/internal/data/RingDevicesTO.java new file mode 100644 index 00000000000..3c9e854df2f --- /dev/null +++ b/bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/internal/data/RingDevicesTO.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2010-2025 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.ring.internal.data; + +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +import com.google.gson.annotations.SerializedName; + +/** + * The {@link RingDevicesTO} class is a TO containing all devices for an account + * + * @author Jan N. Klug - Initial contribution + */ +@NonNullByDefault +public class RingDevicesTO { + @SerializedName("doorbots") + public List doorbells = List.of(); + public List chimes = List.of(); + @SerializedName("stickup_cams") + public List stickupCams = List.of(); + public List other = List.of(); +} diff --git a/bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/internal/data/Stickupcam.java b/bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/internal/data/Stickupcam.java index 7ac5ae5f4d3..2144d1c8b6b 100644 --- a/bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/internal/data/Stickupcam.java +++ b/bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/internal/data/Stickupcam.java @@ -13,12 +13,6 @@ package org.openhab.binding.ring.internal.data; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.ring.internal.RingAccount; -import org.openhab.core.config.discovery.DiscoveryResult; -import org.openhab.core.config.discovery.DiscoveryResultBuilder; -import org.openhab.core.thing.ThingUID; - -import com.google.gson.JsonObject; /** * @author Chris Milbert - Initial contribution @@ -31,24 +25,9 @@ public class Stickupcam extends AbstractRingDevice { /** * Create Stickup Cam instance from JSON object. * - * @param jsonStickupcam the JSON Stickup Cam retrieved from the Ring API. - * @param ringAccount the Ring Account in use + * @param deviceTO the JSON Stickup Cam retrieved from the Ring API. */ - public Stickupcam(JsonObject jsonStickupcam, RingAccount ringAccount) { - super(jsonStickupcam, ringAccount); - } - - /** - * Get the DiscoveryResult object to identify the device as - * discovered thing. - * - * @return the device as DiscoveryResult instance. - */ - @Override - public DiscoveryResult getDiscoveryResult(RingDeviceTO deviceTO) { - DiscoveryResult result = DiscoveryResultBuilder - .create(new ThingUID("ring:stickupcam:" + getRingAccount().getThingId() + ":" + deviceTO.id)) - .withLabel("Ring Video Stickup Cam - " + deviceTO.description).build(); - return result; + public Stickupcam(RingDeviceTO deviceTO) { + super(deviceTO); } } diff --git a/bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/internal/discovery/RingDiscoveryService.java b/bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/internal/discovery/RingDiscoveryService.java index 3c99798304c..80a60ffd5ad 100644 --- a/bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/internal/discovery/RingDiscoveryService.java +++ b/bundles/org.openhab.binding.ring/src/main/java/org/openhab/binding/ring/internal/discovery/RingDiscoveryService.java @@ -14,21 +14,24 @@ package org.openhab.binding.ring.internal.discovery; import static org.openhab.binding.ring.RingBindingConstants.*; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; +import java.time.Instant; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.ring.handler.AccountHandler; import org.openhab.binding.ring.internal.RingDeviceRegistry; +import org.openhab.binding.ring.internal.data.Chime; +import org.openhab.binding.ring.internal.data.Doorbell; import org.openhab.binding.ring.internal.data.RingDevice; import org.openhab.binding.ring.internal.data.RingDeviceTO; -import org.openhab.core.config.discovery.AbstractDiscoveryService; -import org.openhab.core.config.discovery.DiscoveryService; +import org.openhab.binding.ring.internal.data.Stickupcam; +import org.openhab.core.config.discovery.AbstractThingHandlerDiscoveryService; +import org.openhab.core.config.discovery.DiscoveryResult; +import org.openhab.core.config.discovery.DiscoveryResultBuilder; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.ThingUID; +import org.openhab.core.thing.binding.ThingHandler; import org.osgi.service.component.annotations.Component; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.google.gson.Gson; +import org.osgi.service.component.annotations.ServiceScope; /** * The RingDiscoveryService is responsible for auto detecting a Ring @@ -37,68 +40,39 @@ import com.google.gson.Gson; * @author Wim Vissers - Initial contribution * @author Chris Milbert - Stickupcam contribution * @author Ben Rosenblum - Updated for OH4 / New Maintainer + * @author Jan N. Klug - Refactored to ThingHandlerService */ -@Component(service = DiscoveryService.class, immediate = true, configurationPid = "discovery.ring") +@Component(scope = ServiceScope.PROTOTYPE, service = RingDiscoveryService.class) @NonNullByDefault -public class RingDiscoveryService extends AbstractDiscoveryService { - - private Logger logger = LoggerFactory.getLogger(RingDiscoveryService.class); - private @Nullable ScheduledFuture discoveryJob; - - private final Gson gson = new Gson(); - +public class RingDiscoveryService extends AbstractThingHandlerDiscoveryService { public RingDiscoveryService() { - super(SUPPORTED_THING_TYPES_UIDS, 5, true); - } - - public void activate() { - logger.debug("Starting Ring discovery..."); - startScan(); - startBackgroundDiscovery(); - } - - @Override - public void deactivate() { - logger.debug("Stopping Ring discovery..."); - stopBackgroundDiscovery(); - stopScan(); - } - - private void discover() { - RingDeviceRegistry registry = RingDeviceRegistry.getInstance(); - for (RingDevice device : registry.getRingDevices(RingDeviceRegistry.Status.ADDED)) { - RingDeviceTO deviceTO = gson.fromJson(device.getJsonObject(), RingDeviceTO.class); - if (deviceTO != null) { - thingDiscovered(device.getDiscoveryResult(deviceTO)); - registry.setStatus(deviceTO.id, RingDeviceRegistry.Status.DISCOVERED); - } - } - } - - private void refresh() { - discover(); - } - - @Override - protected void startBackgroundDiscovery() { - discoveryJob = scheduler.scheduleWithFixedDelay(this::refresh, 0, 120, TimeUnit.SECONDS); - } - - @Override - protected void stopBackgroundDiscovery() { - logger.info("Stop Ring background discovery"); - ScheduledFuture job = discoveryJob; - if (job != null) { - job.cancel(true); - } - discoveryJob = null; + super(AccountHandler.class, SUPPORTED_THING_TYPES_UIDS, 5, true); } @Override protected void startScan() { - logger.debug("Starting device search..."); - discover(); + ThingHandler thingHandler = getThingHandler(); + if (thingHandler instanceof AccountHandler accountHandler) { + RingDeviceRegistry registry = accountHandler.getDeviceRegistry(); + ThingUID bridgeUID = accountHandler.getThing().getUID(); + for (RingDevice device : registry.getRingDevices(RingDeviceRegistry.Status.ADDED)) { + RingDeviceTO deviceTO = device.getDeviceStatus(); + ThingTypeUID thingTypeUID = switch (device) { + case Chime chime -> THING_TYPE_CHIME; + case Doorbell doorbell -> THING_TYPE_DOORBELL; + case Stickupcam stickupcam -> THING_TYPE_STICKUPCAM; + default -> THING_TYPE_OTHERDEVICE; + }; + + DiscoveryResult result = DiscoveryResultBuilder + .create(new ThingUID(thingTypeUID, bridgeUID, deviceTO.id)).withLabel(deviceTO.description) + .withBridge(bridgeUID).build(); + + thingDiscovered(result); + registry.setStatus(deviceTO.id, RingDeviceRegistry.Status.DISCOVERED); + } + } } @Override @@ -106,4 +80,10 @@ public class RingDiscoveryService extends AbstractDiscoveryService { removeOlderResults(getTimestampOfLastScan()); super.stopScan(); } + + @Override + public void dispose() { + super.dispose(); + removeOlderResults(Instant.now().toEpochMilli()); + } }