mirror of
https://github.com/danieldemus/openhab-addons
synced 2026-07-29 12:34:21 +02:00
Consolidate bridge generation checks (#20379)
Signed-off-by: Jacob Laursen <jacob-github@vindvejr.dk>
This commit is contained in:
+59
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 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.hue.internal;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Utility class for extracting the generation of a Hue Bridge from its model ID.
|
||||
* The generation is determined from the bridge model ID, which follows the pattern {@code BSBxxx}.
|
||||
*
|
||||
* Currently the following generations are known:
|
||||
*
|
||||
* <ul>
|
||||
* <li>Generation 1: Original Hue Bridge (round white, model ID "BSB001")</li>
|
||||
* <li>Generation 2: Hue Bridge (square white, model ID "BSB002")</li>
|
||||
* <li>Generation 3: Hue Bridge Pro (square black, model ID "BSB003")</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author Jacob Laursen - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public final class HueBridgeModel {
|
||||
|
||||
/**
|
||||
* Pattern to extract the generation number from the model ID of the Hue Bridge.
|
||||
* The model ID follows the format {@code BSBxxx} where {@code xxx} is a zero-padded generation number,
|
||||
* for example {@code BSB002} for generation 2 and {@code BSB003} for generation 3 (Pro).
|
||||
*/
|
||||
private static final Pattern BSB_MODEL_ID_PATTERN = Pattern.compile("^BSB(\\d{3})$");
|
||||
|
||||
private HueBridgeModel() {
|
||||
// utility class
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the generation of the Hue Bridge from its model ID.
|
||||
*
|
||||
* @param modelId the model ID of the Hue Bridge, e.g., "BSB001", "BSB002", "BSB003"
|
||||
* @return the generation number of the Hue Bridge, or 0 if the model ID is unknown or does not match the expected
|
||||
* pattern
|
||||
*/
|
||||
public static int getGeneration(String modelId) {
|
||||
Matcher matcher = BSB_MODEL_ID_PATTERN.matcher(modelId);
|
||||
return matcher.matches() ? Integer.parseInt(matcher.group(1)) : 0;
|
||||
}
|
||||
}
|
||||
+19
-16
@@ -58,21 +58,23 @@ public class Clip2ThingDiscoveryService extends AbstractThingHandlerDiscoverySer
|
||||
private static final int DISCOVERY_INTERVAL_SECONDS = 600;
|
||||
|
||||
/**
|
||||
* Two maps of resource types and respective thing types to be discovered for non- motion aware (v2) and motion
|
||||
* aware (v3+) bridges respectively.
|
||||
* Resource types and respective thing types to be discovered for v2 bridges.
|
||||
*/
|
||||
public static final Map<Boolean, Map<ResourceType, ThingTypeUID>> DISCOVERY_TYPES = Map.of( //
|
||||
false, Map.of( // non- motion aware v2 bridge
|
||||
ResourceType.DEVICE, THING_TYPE_DEVICE, //
|
||||
ResourceType.ROOM, THING_TYPE_ROOM, //
|
||||
ResourceType.ZONE, THING_TYPE_ZONE, //
|
||||
ResourceType.BRIDGE_HOME, THING_TYPE_ZONE),
|
||||
true, Map.of( // motion aware v3+ bridge
|
||||
ResourceType.DEVICE, THING_TYPE_DEVICE, //
|
||||
ResourceType.ROOM, THING_TYPE_ROOM, //
|
||||
ResourceType.ZONE, THING_TYPE_ZONE, //
|
||||
ResourceType.BRIDGE_HOME, THING_TYPE_ZONE, //
|
||||
ResourceType.MOTION_AREA_CONFIGURATION, THING_TYPE_AREA));
|
||||
public static final Map<ResourceType, ThingTypeUID> DISCOVERY_TYPES_V2 = Map.of( //
|
||||
ResourceType.DEVICE, THING_TYPE_DEVICE, //
|
||||
ResourceType.ROOM, THING_TYPE_ROOM, //
|
||||
ResourceType.ZONE, THING_TYPE_ZONE, //
|
||||
ResourceType.BRIDGE_HOME, THING_TYPE_ZONE);
|
||||
|
||||
/**
|
||||
* Resource types and respective thing types to be discovered for v3+ bridges.
|
||||
*/
|
||||
public static final Map<ResourceType, ThingTypeUID> DISCOVERY_TYPES_V3 = Map.of( //
|
||||
ResourceType.DEVICE, THING_TYPE_DEVICE, //
|
||||
ResourceType.ROOM, THING_TYPE_ROOM, //
|
||||
ResourceType.ZONE, THING_TYPE_ZONE, //
|
||||
ResourceType.BRIDGE_HOME, THING_TYPE_ZONE, //
|
||||
ResourceType.MOTION_AREA_CONFIGURATION, THING_TYPE_AREA);
|
||||
|
||||
private @Nullable ScheduledFuture<?> discoveryTask;
|
||||
|
||||
@@ -102,8 +104,9 @@ public class Clip2ThingDiscoveryService extends AbstractThingHandlerDiscoverySer
|
||||
if (thingHandler.getThing().getStatus() == ThingStatus.ONLINE) {
|
||||
try {
|
||||
ThingUID bridgeUID = thingHandler.getThing().getUID();
|
||||
for (Entry<ResourceType, ThingTypeUID> entry : DISCOVERY_TYPES.get(thingHandler.motionAware())
|
||||
.entrySet()) {
|
||||
for (Entry<ResourceType, ThingTypeUID> entry : (thingHandler.getBridgeGeneration() >= 3
|
||||
? DISCOVERY_TYPES_V3
|
||||
: DISCOVERY_TYPES_V2).entrySet()) {
|
||||
for (Resource resource : thingHandler.getResources(new ResourceReference().setType(entry.getKey()))
|
||||
.getResources()) {
|
||||
|
||||
|
||||
+11
-23
@@ -19,13 +19,12 @@ import java.util.Dictionary;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.jmdns.ServiceInfo;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.hue.internal.HueBridgeModel;
|
||||
import org.openhab.binding.hue.internal.connection.Clip2Bridge;
|
||||
import org.openhab.core.config.discovery.DiscoveryResult;
|
||||
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
||||
@@ -56,26 +55,6 @@ import org.slf4j.LoggerFactory;
|
||||
@NonNullByDefault
|
||||
public class HueBridgeMDNSDiscoveryParticipant implements MDNSDiscoveryParticipant {
|
||||
|
||||
private static final Pattern BSB_MODEL_ID_PATTERN = Pattern.compile("^BSB(\\d{3})$");
|
||||
|
||||
/**
|
||||
* Checks if the given model ID is a BSB model and if its version is 003 or above.
|
||||
*
|
||||
* @param modelId the model ID to check
|
||||
* @return true if the model ID is a BSB model with version 003 or above, false otherwise
|
||||
*/
|
||||
public static boolean modelIsOrAboveBSB003(@Nullable String modelId) {
|
||||
if (modelId == null) {
|
||||
return false;
|
||||
}
|
||||
Matcher matcher = BSB_MODEL_ID_PATTERN.matcher(modelId);
|
||||
if (!matcher.matches()) {
|
||||
return false;
|
||||
}
|
||||
int version = Integer.parseInt(matcher.group(1));
|
||||
return version >= 3;
|
||||
}
|
||||
|
||||
private static final String SERVICE_TYPE = "_hue._tcp.local.";
|
||||
private static final String MDNS_PROPERTY_BRIDGE_ID = "bridgeid";
|
||||
private static final String MDNS_PROPERTY_MODEL_ID = "modelid";
|
||||
@@ -183,10 +162,19 @@ public class HueBridgeMDNSDiscoveryParticipant implements MDNSDiscoveryParticipa
|
||||
String id = service.getPropertyString(MDNS_PROPERTY_BRIDGE_ID);
|
||||
if (id != null && !id.isBlank()) {
|
||||
id = id.toLowerCase();
|
||||
if (modelIsOrAboveBSB003(service.getPropertyString(MDNS_PROPERTY_MODEL_ID))) {
|
||||
String modelId = service.getPropertyString(MDNS_PROPERTY_MODEL_ID);
|
||||
int generation = modelId != null ? HueBridgeModel.getGeneration(modelId) : 0;
|
||||
if (generation >= 3) {
|
||||
return new ThingUID(THING_TYPE_BRIDGE_API2, id);
|
||||
} else if (generation == 1) {
|
||||
// The original Hue Bridge (round white) does not support CLIP 2,
|
||||
// so we can directly return the ThingUID for the original bridge without further checks.
|
||||
return new ThingUID(THING_TYPE_BRIDGE, id);
|
||||
}
|
||||
try {
|
||||
// For square white bridges of generation 2, we need to check if CLIP 2 is supported
|
||||
// to determine the correct ThingTypeUID. This is because some bridges could still
|
||||
// be running older firmware without CLIP 2 support.
|
||||
return Clip2Bridge.isClip2Supported(service.getHostAddresses()[0])
|
||||
? new ThingUID(THING_TYPE_BRIDGE_API2, id)
|
||||
: new ThingUID(THING_TYPE_BRIDGE, id);
|
||||
|
||||
+29
-18
@@ -32,6 +32,7 @@ import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.hue.internal.HueBridgeModel;
|
||||
import org.openhab.binding.hue.internal.api.dto.clip2.MetaData;
|
||||
import org.openhab.binding.hue.internal.api.dto.clip2.ProductData;
|
||||
import org.openhab.binding.hue.internal.api.dto.clip2.Resource;
|
||||
@@ -45,7 +46,6 @@ import org.openhab.binding.hue.internal.config.Clip2BridgeConfig;
|
||||
import org.openhab.binding.hue.internal.connection.Clip2Bridge;
|
||||
import org.openhab.binding.hue.internal.connection.HueTlsTrustManagerProvider;
|
||||
import org.openhab.binding.hue.internal.discovery.Clip2ThingDiscoveryService;
|
||||
import org.openhab.binding.hue.internal.discovery.HueBridgeMDNSDiscoveryParticipant;
|
||||
import org.openhab.binding.hue.internal.exceptions.ApiException;
|
||||
import org.openhab.binding.hue.internal.exceptions.AssetNotLoadedException;
|
||||
import org.openhab.binding.hue.internal.exceptions.HttpUnauthorizedException;
|
||||
@@ -110,14 +110,18 @@ public class Clip2BridgeHandler extends BaseBridgeHandler {
|
||||
private static final String AUTOMATION_CHANNEL_DESCRIPTION_KEY = "dynamic-channel.automation-enable.description";
|
||||
|
||||
/**
|
||||
* Two lists of resource references that need to be mass down loaded for non- motion aware (v2) and motion
|
||||
* aware (v3+) bridges respectively.
|
||||
* NOTE: the SCENE resources must be mass down loaded first!
|
||||
* Resource references that need to be mass downloaded for v2 bridges.
|
||||
* NOTE: the SCENE resources must be mass downloaded first!
|
||||
*/
|
||||
public static final Map<Boolean, List<ResourceReference>> MASS_DOWNLOAD_RESOURCE_REFERENCES = Map.of( //
|
||||
false, List.of(SCENE, DEVICE, ROOM, ZONE), // non- motion aware v2 bridge
|
||||
true, List.of(SCENE, DEVICE, ROOM, ZONE, AREA) // motion aware v3+ bridge
|
||||
);
|
||||
public static final List<ResourceReference> MASS_DOWNLOAD_RESOURCE_REFERENCES_V2 = List.of(SCENE, DEVICE, ROOM,
|
||||
ZONE);
|
||||
|
||||
/**
|
||||
* Resource references that need to be mass downloaded for v3+ bridges.
|
||||
* NOTE: the SCENE resources must be mass downloaded first!
|
||||
*/
|
||||
public static final List<ResourceReference> MASS_DOWNLOAD_RESOURCE_REFERENCES_V3 = List.of(SCENE, DEVICE, ROOM,
|
||||
ZONE, AREA);
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(Clip2BridgeHandler.class);
|
||||
|
||||
@@ -143,7 +147,13 @@ public class Clip2BridgeHandler extends BaseBridgeHandler {
|
||||
private boolean assetsLoaded;
|
||||
private int applKeyRetriesRemaining;
|
||||
private int connectRetriesRemaining;
|
||||
private boolean motionAware; // true if the bridge is a v3+ model that supports the motion aware feature
|
||||
|
||||
/**
|
||||
* The generation of the bridge model, as returned by the DEVICE resource, used to determine if certain features are
|
||||
* supported.
|
||||
* For example, the motion aware feature is only supported on v3+ models.
|
||||
*/
|
||||
private int bridgeGeneration;
|
||||
|
||||
public Clip2BridgeHandler(Bridge bridge, HttpClientFactory httpClientFactory, ThingRegistry thingRegistry,
|
||||
LocaleProvider localeProvider, TranslationProvider translationProvider) {
|
||||
@@ -502,8 +512,8 @@ public class Clip2BridgeHandler extends BaseBridgeHandler {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean useSignifyCaCertificateVersion2 = HueBridgeMDNSDiscoveryParticipant
|
||||
.modelIsOrAboveBSB003(thing.getProperties().get(Thing.PROPERTY_MODEL_ID));
|
||||
String modelId = thing.getProperties().get(Thing.PROPERTY_MODEL_ID);
|
||||
boolean useSignifyCaCertificateVersion2 = modelId != null && HueBridgeModel.getGeneration(modelId) >= 3;
|
||||
HueTlsTrustManagerProvider trustManagerProvider = new HueTlsTrustManagerProvider(ipAddress + ":443",
|
||||
config.useSelfSignedCertificate, useSignifyCaCertificateVersion2);
|
||||
|
||||
@@ -706,7 +716,7 @@ public class Clip2BridgeHandler extends BaseBridgeHandler {
|
||||
properties.put(PROPERTY_PRODUCT_ARCHETYPE, productData.getProductArchetype().toString());
|
||||
properties.put(PROPERTY_PRODUCT_CERTIFIED, productData.getCertified().toString());
|
||||
|
||||
motionAware = !"BSB002".equals(productData.getModelId());
|
||||
bridgeGeneration = HueBridgeModel.getGeneration(productData.getModelId());
|
||||
}
|
||||
break; // we only needed the BRIDGE_V2 or BRIDGE_V3 resource
|
||||
}
|
||||
@@ -779,7 +789,8 @@ public class Clip2BridgeHandler extends BaseBridgeHandler {
|
||||
logger.debug("updateThingsNow()");
|
||||
try {
|
||||
Clip2Bridge bridge = getClip2Bridge();
|
||||
for (ResourceReference reference : MASS_DOWNLOAD_RESOURCE_REFERENCES.get(motionAware)) {
|
||||
for (ResourceReference reference : bridgeGeneration >= 3 ? MASS_DOWNLOAD_RESOURCE_REFERENCES_V3
|
||||
: MASS_DOWNLOAD_RESOURCE_REFERENCES_V2) {
|
||||
ResourceType resourceType = reference.getType();
|
||||
List<Resource> resourceList = bridge.getResources(reference).getResources();
|
||||
switch (resourceType) {
|
||||
@@ -947,11 +958,11 @@ public class Clip2BridgeHandler extends BaseBridgeHandler {
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for whether the bridge is motion aware
|
||||
*
|
||||
* @return
|
||||
* Getter for the bridge generation, as determined from the model id.
|
||||
*
|
||||
* @return the bridge generation, or 0 if the generation is unknown.
|
||||
*/
|
||||
public boolean motionAware() {
|
||||
return motionAware;
|
||||
public int getBridgeGeneration() {
|
||||
return bridgeGeneration;
|
||||
}
|
||||
}
|
||||
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 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.hue.internal;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link HueBridgeModel}.
|
||||
*
|
||||
* @author Jacob Laursen - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class HueBridgeModelTest {
|
||||
|
||||
@Test
|
||||
public void testHueBridgeModelGenerationParsing() {
|
||||
assertThat(HueBridgeModel.getGeneration("UNKNOWN"), is(0));
|
||||
assertThat(HueBridgeModel.getGeneration("BSB001"), is(1));
|
||||
assertThat(HueBridgeModel.getGeneration("BSB002"), is(2));
|
||||
assertThat(HueBridgeModel.getGeneration("BSB003"), is(3));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user