Error in capabilities associated to things (#20296)

Signed-off-by: gael@lhopital.org <gael@lhopital.org>
This commit is contained in:
Gaël L'hopital
2026-02-28 11:27:37 +01:00
committed by GitHub
parent e03b6a03f4
commit f43837b89d
8 changed files with 41 additions and 43 deletions
@@ -12,7 +12,6 @@
*/
package org.openhab.binding.netatmo.internal;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -99,8 +98,7 @@ public class NetatmoHandlerFactory extends BaseThingHandlerFactory {
@Modified
public void configChanged(Map<String, @Nullable Object> config) {
BindingConfiguration newConf = ConfigParser.configurationAs(config, BindingConfiguration.class);
if (newConf != null) {
if (ConfigParser.configurationAs(config, BindingConfiguration.class) instanceof BindingConfiguration newConf) {
configuration.update(newConf);
}
}
@@ -125,9 +123,7 @@ public class NetatmoHandlerFactory extends BaseThingHandlerFactory {
CommonInterface handler = moduleType.isABridge() ? new DeviceHandler((Bridge) thing, timeZoneProvider)
: new ModuleHandler(thing, timeZoneProvider);
List<ChannelHelper> helpers = new ArrayList<>();
helpers.addAll(moduleType.channelGroups.stream().map(ChannelGroup::getHelperInstance).toList());
List<ChannelHelper> helpers = moduleType.channelGroups.stream().map(ChannelGroup::getHelperInstance).toList();
moduleType.capabilities.forEach(capability -> {
Capability newCap = null;
@@ -73,8 +73,7 @@ public enum ModuleType {
new ChannelGroup(ApiBridgeChannelHelper.class, GROUP_MONITORING)),
HOME(FeatureArea.NONE, "NAHome", 1, "home", ACCOUNT,
Set.of(DeviceCapability.class, HomeCapability.class, ChannelHelperCapability.class,
RefreshCapability.class),
Set.of(HomeCapability.class, ChannelHelperCapability.class, RefreshCapability.class),
new ChannelGroup(SecurityChannelHelper.class, GROUP_SECURITY_EVENT, GROUP_SECURITY),
new ChannelGroup(EnergyChannelHelper.class, GROUP_ENERGY)),
@@ -33,6 +33,8 @@ import org.openhab.binding.netatmo.internal.config.NAThingConfiguration;
import org.openhab.binding.netatmo.internal.handler.capability.Capability;
import org.openhab.binding.netatmo.internal.handler.capability.CapabilityMap;
import org.openhab.binding.netatmo.internal.handler.capability.HomeCapability;
import org.openhab.binding.netatmo.internal.handler.capability.ParentUpdateCapability;
import org.openhab.binding.netatmo.internal.handler.capability.RefreshCapability;
import org.openhab.binding.netatmo.internal.handler.capability.RestCapability;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Channel;
@@ -230,13 +232,14 @@ public interface CommonInterface {
setThingStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED, null);
} else if (!ThingStatus.ONLINE.equals(bridge.getStatus())) {
setThingStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, null);
getCapabilities().getParentUpdate().ifPresent(Capability::dispose);
getCapabilities().getRefresh().ifPresent(Capability::dispose);
getCapabilities().get(ParentUpdateCapability.class).ifPresent(Capability::dispose);
getCapabilities().getOrDescendant(RefreshCapability.class).ifPresent(Capability::dispose);
} else {
setThingStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE, null);
getCapabilities().getParentUpdate().ifPresentOrElse(Capability::initialize, () -> {
getCapabilities().get(ParentUpdateCapability.class).ifPresentOrElse(Capability::initialize, () -> {
int interval = getThingConfigAs(NAThingConfiguration.class).getRefreshInterval();
getCapabilities().getRefresh().ifPresent(cap -> cap.setInterval(Duration.ofSeconds(interval)));
getCapabilities().getOrDescendant(RefreshCapability.class)
.ifPresent(cap -> cap.setInterval(Duration.ofSeconds(interval)));
});
}
}
@@ -154,7 +154,7 @@ public class Capability {
public void expireData() {
CommonInterface bridgeHandler = handler.getBridgeHandler();
if (bridgeHandler != null && handler.getCapabilities().getRefresh().isEmpty()) {
if (bridgeHandler != null && handler.getCapabilities().getOrDescendant(RefreshCapability.class).isEmpty()) {
bridgeHandler.expireData();
}
}
@@ -16,7 +16,6 @@ import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* {@link CapabilityMap} is a specialized Map designed to store capabilities
@@ -29,33 +28,27 @@ public class CapabilityMap extends ConcurrentHashMap<Class<?>, Capability> {
private static final long serialVersionUID = -3043492242108419801L;
public void put(Capability capability) {
Class<?> clazz = capability.getClass();
if (super.get(clazz) == null) {
Class<? extends Capability> clazz = capability.getClass();
if (getOrDescendant(clazz).isEmpty()) {
super.put(clazz, capability);
capability.initialize();
} else {
throw new IllegalArgumentException("%s is already present".formatted(clazz.getSimpleName()));
}
}
public <T extends Capability> Optional<T> get(Class<T> clazz) {
@SuppressWarnings("unchecked")
T cap = (T) super.get(clazz);
return Optional.ofNullable(cap);
}
public <T extends Capability> void remove(Class<?> clazz) {
@Nullable
Capability cap = super.remove(clazz);
if (cap != null) {
cap.dispose();
Capability cap = super.get(clazz);
if (cap == null) {
return Optional.empty();
}
return Optional.of(clazz.cast(cap));
}
public Optional<RefreshCapability> getRefresh() {
return values().stream().filter(RefreshCapability.class::isInstance).map(RefreshCapability.class::cast)
.findFirst();
}
public Optional<Capability> getParentUpdate() {
return values().stream().filter(ParentUpdateCapability.class::isInstance).findFirst();
public <T extends Capability> Optional<T> getOrDescendant(Class<T> clazz) {
return values().stream()
.filter(cap -> clazz.isAssignableFrom(cap.getClass()) || cap.getClass().isAssignableFrom(clazz))
.map(cap -> clazz.cast(cap)).findFirst();
}
}
@@ -28,21 +28,12 @@ import org.openhab.binding.netatmo.internal.handler.CommonInterface;
public class DeviceCapability extends Capability {
private static final int DATA_AGE_LIMIT_S = 3600;
/**
* Whether the device is owned or not by the user (a favorite station or a guest station is not owned by the user).
* It will be updated when handling the result of the getstationsdata API.
* It must be initialized to false to be sure that the first call to the API will not fail for a favorite/guest
* weather stations.
*/
protected boolean owned;
public DeviceCapability(CommonInterface handler) {
super(handler);
}
@Override
protected void updateNAMain(NAMain newData) {
owned = !newData.isReadOnly();
if (firstLaunch) {
newData.getPlace().ifPresent(place -> {
place.getCity().map(city -> properties.put(PROPERTY_CITY, city));
@@ -33,7 +33,7 @@ import org.openhab.binding.netatmo.internal.handler.CommonInterface;
*
*/
@NonNullByDefault
public abstract class RestCapability<T extends RestManager> extends DeviceCapability {
public abstract class RestCapability<T extends RestManager> extends Capability {
private @Nullable T api;
private Class<T> restManagerClass;
@@ -18,6 +18,7 @@ import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.netatmo.internal.api.NetatmoException;
import org.openhab.binding.netatmo.internal.api.WeatherApi;
import org.openhab.binding.netatmo.internal.api.dto.NAMain;
import org.openhab.binding.netatmo.internal.api.dto.NAObject;
import org.openhab.binding.netatmo.internal.handler.CommonInterface;
import org.slf4j.Logger;
@@ -33,10 +34,25 @@ import org.slf4j.LoggerFactory;
public class WeatherCapability extends CacheCapability<WeatherApi> {
private final Logger logger = LoggerFactory.getLogger(WeatherCapability.class);
/**
* Whether the device is owned or not by the user (a favorite station or a guest station is not owned by the user).
* It will be updated when handling the result of the getstationsdata API.
* It must be initialized to false to be sure that the first call to the API will not fail for a favorite/guest
* weather stations.
*/
protected boolean owned;
public WeatherCapability(CommonInterface handler) {
super(handler, Duration.ofSeconds(10), WeatherApi.class);
}
@Override
protected void updateNAMain(NAMain newData) {
if (firstLaunch) {
owned = !newData.isReadOnly();
}
}
@Override
protected List<NAObject> getFreshData(WeatherApi api) {
try {