diff --git a/bundles/org.openhab.binding.netatmo/README.md b/bundles/org.openhab.binding.netatmo/README.md index f0def84b7..a2151ff80 100644 --- a/bundles/org.openhab.binding.netatmo/README.md +++ b/bundles/org.openhab.binding.netatmo/README.md @@ -10,6 +10,7 @@ The Netatmo binding integrates the following Netatmo products: - _Doorbell_ - _Smoke Detector_ - _Smart Door Sensor_ +- _Carbon Monoxide Detector_ See for details on their product. @@ -94,6 +95,8 @@ Now that you have got your bridge _ONLINE_ you can now start a scan with the bin | room | Thing | NARoom | A room in your house. | id | | valve | Thing | NRV | A valve controlling a radiator. | id | | tag | Thing | NACamDoorTag | A door / window sensor | id | +| smoke-detector | Thing | NSD | A Smoke Detector | id | +| co-detector | Thing | NCO | A Carbon Monoxide Alarm | id | ### Webhook @@ -642,6 +645,22 @@ All these channels are read only. | last-event | subtype | String | Sub-type of event | | last-event | message | String | Last event message from this person | +### Netatmo Smart Carbon Monoxide Detector + +All these channels are read only. + +**Supported channels for the Carbon Monoxide Detector thing:** + +| Channel Group | Channel Id | Item Type | Description | +| ------------- | ---------- | ------------ | ------------------------------------------------ | +| signal | strength | Number | Signal strength (0 for no signal, 1 for weak...) | +| signal | value | Number:Power | Signal strength in dBm | +| timestamp | last-seen | DateTime | Last time the module reported its presence | +| last-event | type | String | Type of event | +| last-event | time | DateTime | Moment of the last event for this detector | +| last-event | subtype | String | Sub-type of event | +| last-event | message | String | Last event message from this detector | + ## Configuration Examples ### things/netatmo.things diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/NetatmoBindingConstants.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/NetatmoBindingConstants.java index a728889a9..2f8ef0636 100644 --- a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/NetatmoBindingConstants.java +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/NetatmoBindingConstants.java @@ -68,9 +68,9 @@ public class NetatmoBindingConstants { public static final String OPTION_PERSON = "-person"; public static final String OPTION_ROOM = "-room"; public static final String OPTION_THERMOSTAT = "-thermostat"; - public static final String OPTION_SMOKE = "-smoke"; + public static final String OPTION_ALARM = "-alarm"; public static final Set GROUP_VARIATIONS = Set.of(OPTION_EXTENDED, OPTION_OUTSIDE, OPTION_DOORBELL, - OPTION_PERSON, OPTION_ROOM, OPTION_THERMOSTAT, OPTION_SMOKE); + OPTION_PERSON, OPTION_ROOM, OPTION_THERMOSTAT, OPTION_ALARM); public static final String GROUP_TYPE_TIMESTAMP_EXTENDED = GROUP_TIMESTAMP + OPTION_EXTENDED; public static final String GROUP_TYPE_BATTERY_EXTENDED = GROUP_BATTERY + OPTION_EXTENDED; @@ -83,7 +83,7 @@ public class NetatmoBindingConstants { public static final String GROUP_DOORBELL_LAST_EVENT = GROUP_LAST_EVENT + OPTION_DOORBELL; public static final String GROUP_DOORBELL_SUB_EVENT = GROUP_SUB_EVENT + OPTION_DOORBELL; public static final String GROUP_PERSON_LAST_EVENT = GROUP_LAST_EVENT + OPTION_PERSON; - public static final String GROUP_SMOKE_LAST_EVENT = GROUP_LAST_EVENT + OPTION_SMOKE; + public static final String GROUP_ALARM_LAST_EVENT = GROUP_LAST_EVENT + OPTION_ALARM; public static final String GROUP_TYPE_ROOM_TEMPERATURE = GROUP_TEMPERATURE + OPTION_ROOM; public static final String GROUP_TYPE_ROOM_PROPERTIES = GROUP_PROPERTIES + OPTION_ROOM; public static final String GROUP_TYPE_TH_PROPERTIES = GROUP_PROPERTIES + OPTION_THERMOSTAT; diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/NetatmoHandlerFactory.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/NetatmoHandlerFactory.java index 4053372bf..3d85c3e1f 100644 --- a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/NetatmoHandlerFactory.java +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/NetatmoHandlerFactory.java @@ -19,6 +19,7 @@ import java.util.Map; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jetty.client.HttpClient; +import org.openhab.binding.netatmo.internal.api.data.ChannelGroup; import org.openhab.binding.netatmo.internal.api.data.ModuleType; import org.openhab.binding.netatmo.internal.config.BindingConfiguration; import org.openhab.binding.netatmo.internal.deserialization.NADeserializer; @@ -27,6 +28,7 @@ import org.openhab.binding.netatmo.internal.handler.CommonInterface; import org.openhab.binding.netatmo.internal.handler.DeviceHandler; import org.openhab.binding.netatmo.internal.handler.ModuleHandler; import org.openhab.binding.netatmo.internal.handler.capability.AirCareCapability; +import org.openhab.binding.netatmo.internal.handler.capability.AlarmEventCapability; import org.openhab.binding.netatmo.internal.handler.capability.CameraCapability; import org.openhab.binding.netatmo.internal.handler.capability.Capability; import org.openhab.binding.netatmo.internal.handler.capability.ChannelHelperCapability; @@ -37,7 +39,6 @@ import org.openhab.binding.netatmo.internal.handler.capability.MeasureCapability import org.openhab.binding.netatmo.internal.handler.capability.PersonCapability; import org.openhab.binding.netatmo.internal.handler.capability.PresenceCapability; import org.openhab.binding.netatmo.internal.handler.capability.RoomCapability; -import org.openhab.binding.netatmo.internal.handler.capability.SmokeCapability; import org.openhab.binding.netatmo.internal.handler.capability.WeatherCapability; import org.openhab.binding.netatmo.internal.handler.channelhelper.ChannelHelper; import org.openhab.binding.netatmo.internal.providers.NetatmoDescriptionProvider; @@ -113,8 +114,8 @@ public class NetatmoHandlerFactory extends BaseThingHandlerFactory { CommonInterface handler = moduleType.isABridge() ? new DeviceHandler((Bridge) thing) : new ModuleHandler(thing); List helpers = new ArrayList<>(); - moduleType.channelGroups - .forEach(channelGroup -> channelGroup.getHelperInstance().ifPresent(helper -> helpers.add(helper))); + + helpers.addAll(moduleType.channelGroups.stream().map(ChannelGroup::getHelperInstance).toList()); moduleType.capabilities.forEach(capability -> { Capability newCap = null; @@ -134,8 +135,8 @@ public class NetatmoHandlerFactory extends BaseThingHandlerFactory { newCap = new PersonCapability(handler, stateDescriptionProvider, helpers); } else if (capability == CameraCapability.class) { newCap = new CameraCapability(handler, stateDescriptionProvider, helpers); - } else if (capability == SmokeCapability.class) { - newCap = new SmokeCapability(handler, stateDescriptionProvider, helpers); + } else if (capability == AlarmEventCapability.class) { + newCap = new AlarmEventCapability(handler, stateDescriptionProvider, helpers); } else if (capability == PresenceCapability.class) { newCap = new PresenceCapability(handler, stateDescriptionProvider, helpers); } else if (capability == MeasureCapability.class) { diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/data/ChannelGroup.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/data/ChannelGroup.java index 0b3fa8ab0..c3cc59c24 100644 --- a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/data/ChannelGroup.java +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/data/ChannelGroup.java @@ -14,7 +14,6 @@ package org.openhab.binding.netatmo.internal.api.data; import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*; -import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; @@ -32,8 +31,6 @@ import org.openhab.binding.netatmo.internal.handler.channelhelper.SignalChannelH import org.openhab.binding.netatmo.internal.handler.channelhelper.TemperatureChannelHelper; import org.openhab.binding.netatmo.internal.handler.channelhelper.TimestampChannelHelper; import org.openhab.binding.netatmo.internal.providers.NetatmoThingTypeProvider; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * The {@link ChannelGroup} makes the link between a channel helper and some group types. It also @@ -66,8 +63,9 @@ public class ChannelGroup { GROUP_NOISE); public static final ChannelGroup HUMIDITY = new ChannelGroup(HumidityChannelHelper.class, MeasureClass.HUMIDITY, GROUP_HUMIDITY); + public static final ChannelGroup ALARM_LAST_EVENT = new ChannelGroup(EventChannelHelper.class, + GROUP_ALARM_LAST_EVENT); - private final Logger logger = LoggerFactory.getLogger(ChannelGroup.class); private final Class helper; public final Set groupTypes; public final Set extensions; @@ -86,13 +84,13 @@ public class ChannelGroup { this.extensions = extensions; } - public Optional getHelperInstance() { + public ChannelHelper getHelperInstance() { try { - return Optional.of(helper.getConstructor(Set.class).newInstance( - groupTypes.stream().map(NetatmoThingTypeProvider::toGroupName).collect(Collectors.toSet()))); + return helper.getConstructor(Set.class).newInstance( + groupTypes.stream().map(NetatmoThingTypeProvider::toGroupName).collect(Collectors.toSet())); } catch (ReflectiveOperationException e) { - logger.warn("Error creating or initializing helper class : {}", e.getMessage()); + throw new IllegalArgumentException( + "Error creating or initializing helper class : %s".formatted(e.getMessage())); } - return Optional.empty(); } } diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/data/EventSubType.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/data/EventSubType.java index 7c937b979..7cf497e66 100644 --- a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/data/EventSubType.java +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/data/EventSubType.java @@ -32,7 +32,7 @@ public enum EventSubType { SD_CARD_INCOMPATIBLE_SPEED(6, EventType.SD), SD_CARD_INSUFFICIENT_SPACE(7, EventType.SD), - // Alimentation sub events + // Power sub events ALIM_INCORRECT_POWER(1, EventType.ALIM), ALIM_CORRECT_POWER(2, EventType.ALIM), @@ -47,6 +47,12 @@ public enum EventSubType { SOUND_TEST_ERROR(1, EventType.SOUND_TEST), DETECTOR_READY(0, EventType.TAMPERED), DETECTOR_TAMPERED(1, EventType.TAMPERED), + + // Carbon Monoxide Alarm + CO_OK(0, EventType.CO_DETECTED), + CO_PRE_ALARM(1, EventType.CO_DETECTED), + CO_ALARM(2, EventType.CO_DETECTED), + WIFI_STATUS_OK(1, EventType.WIFI_STATUS), WIFI_STATUS_ERROR(0, EventType.WIFI_STATUS), diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/data/EventType.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/data/EventType.java index 5e383efae..dfb8de510 100644 --- a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/data/EventType.java +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/data/EventType.java @@ -111,22 +111,25 @@ public enum EventType { SMOKE(ModuleType.SMOKE_DETECTOR), @SerializedName("tampered") // When smoke detector is ready or tampered - TAMPERED(ModuleType.SMOKE_DETECTOR), + TAMPERED(ModuleType.SMOKE_DETECTOR, ModuleType.CO_DETECTOR), @SerializedName("wifi_status") // When wifi status is updated - WIFI_STATUS(ModuleType.SMOKE_DETECTOR), + WIFI_STATUS(ModuleType.SMOKE_DETECTOR, ModuleType.CO_DETECTOR), @SerializedName("battery_status") // When battery status is too low - BATTERY_STATUS(ModuleType.SMOKE_DETECTOR), + BATTERY_STATUS(ModuleType.SMOKE_DETECTOR, ModuleType.CO_DETECTOR), @SerializedName("detection_chamber_status") // When the detection chamber is dusty or clean DETECTION_CHAMBER_STATUS(ModuleType.SMOKE_DETECTOR), @SerializedName("sound_test") // Sound test result - SOUND_TEST(ModuleType.SMOKE_DETECTOR), + SOUND_TEST(ModuleType.SMOKE_DETECTOR, ModuleType.CO_DETECTOR), @SerializedName("new_device") - NEW_DEVICE(ModuleType.HOME); + NEW_DEVICE(ModuleType.HOME), + + @SerializedName("co_detected") + CO_DETECTED(ModuleType.CO_DETECTOR); public static final EnumSet AS_SET = EnumSet.allOf(EventType.class); diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/data/ModuleType.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/data/ModuleType.java index 2314d1092..de5e14d9b 100644 --- a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/data/ModuleType.java +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/data/ModuleType.java @@ -18,14 +18,15 @@ import static org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.*; import java.net.URI; import java.util.EnumSet; import java.util.List; +import java.util.Optional; import java.util.Set; -import java.util.stream.Collectors; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.FeatureArea; import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.MeasureClass; import org.openhab.binding.netatmo.internal.handler.capability.AirCareCapability; +import org.openhab.binding.netatmo.internal.handler.capability.AlarmEventCapability; import org.openhab.binding.netatmo.internal.handler.capability.CameraCapability; import org.openhab.binding.netatmo.internal.handler.capability.Capability; import org.openhab.binding.netatmo.internal.handler.capability.ChannelHelperCapability; @@ -36,14 +37,12 @@ import org.openhab.binding.netatmo.internal.handler.capability.MeasureCapability import org.openhab.binding.netatmo.internal.handler.capability.PersonCapability; import org.openhab.binding.netatmo.internal.handler.capability.PresenceCapability; import org.openhab.binding.netatmo.internal.handler.capability.RoomCapability; -import org.openhab.binding.netatmo.internal.handler.capability.SmokeCapability; import org.openhab.binding.netatmo.internal.handler.capability.WeatherCapability; import org.openhab.binding.netatmo.internal.handler.channelhelper.AirQualityChannelHelper; import org.openhab.binding.netatmo.internal.handler.channelhelper.ApiBridgeChannelHelper; import org.openhab.binding.netatmo.internal.handler.channelhelper.CameraChannelHelper; import org.openhab.binding.netatmo.internal.handler.channelhelper.DoorTagChannelHelper; import org.openhab.binding.netatmo.internal.handler.channelhelper.EnergyChannelHelper; -import org.openhab.binding.netatmo.internal.handler.channelhelper.EventChannelHelper; import org.openhab.binding.netatmo.internal.handler.channelhelper.EventDoorbellChannelHelper; import org.openhab.binding.netatmo.internal.handler.channelhelper.EventPersonChannelHelper; import org.openhab.binding.netatmo.internal.handler.channelhelper.PersonChannelHelper; @@ -59,13 +58,14 @@ import org.openhab.binding.netatmo.internal.handler.channelhelper.WindChannelHel import org.openhab.core.thing.ThingTypeUID; /** - * This enum all handled Netatmo modules and devices along with their capabilities + * This enum describes all Netatmo modules and devices along with their capabilities. * * @author Gaël L'hopital - Initial contribution */ @NonNullByDefault public enum ModuleType { UNKNOWN(FeatureArea.NONE, "", null, Set.of()), + ACCOUNT(FeatureArea.NONE, "", null, Set.of(), new ChannelGroup(ApiBridgeChannelHelper.class, GROUP_MONITORING)), HOME(FeatureArea.NONE, "NAHome", ACCOUNT, @@ -140,13 +140,15 @@ public enum ModuleType { new ChannelGroup(RoomChannelHelper.class, GROUP_TYPE_ROOM_PROPERTIES, GROUP_TYPE_ROOM_TEMPERATURE), new ChannelGroup(SetpointChannelHelper.class, GROUP_SETPOINT)), - SMOKE_DETECTOR(FeatureArea.SECURITY, "NSD", HOME, Set.of(SmokeCapability.class, ChannelHelperCapability.class), - ChannelGroup.SIGNAL, ChannelGroup.TIMESTAMP, - new ChannelGroup(EventChannelHelper.class, GROUP_SMOKE_LAST_EVENT)); + SMOKE_DETECTOR(FeatureArea.SECURITY, "NSD", HOME, Set.of(AlarmEventCapability.class, ChannelHelperCapability.class), + ChannelGroup.SIGNAL, ChannelGroup.TIMESTAMP, ChannelGroup.ALARM_LAST_EVENT), + + CO_DETECTOR(FeatureArea.SECURITY, "NCO", HOME, Set.of(AlarmEventCapability.class, ChannelHelperCapability.class), + ChannelGroup.SIGNAL, ChannelGroup.TIMESTAMP, ChannelGroup.ALARM_LAST_EVENT); public static final EnumSet AS_SET = EnumSet.allOf(ModuleType.class); - private final @Nullable ModuleType bridgeType; + private final Optional bridgeType; public final Set channelGroups; public final Set> capabilities; public final ThingTypeUID thingTypeUID; @@ -155,7 +157,7 @@ public enum ModuleType { ModuleType(FeatureArea feature, String apiName, @Nullable ModuleType bridge, Set> capabilities, ChannelGroup... channelGroups) { - this.bridgeType = bridge; + this.bridgeType = Optional.ofNullable(bridge); this.feature = feature; this.capabilities = capabilities; this.apiName = apiName; @@ -167,21 +169,16 @@ public enum ModuleType { return !channelGroups.contains(ChannelGroup.SIGNAL); } - public boolean isABridge() { - for (ModuleType mt : ModuleType.values()) { - if (this.equals(mt.bridgeType)) { - return true; - } - } - return false; + public boolean isABridge() { // I am a bridge if any module references me as being so + return AS_SET.stream().anyMatch(mt -> this.equals(mt.getBridge())); } public List getExtensions() { - return channelGroups.stream().map(cg -> cg.extensions).flatMap(Set::stream).collect(Collectors.toList()); + return channelGroups.stream().map(cg -> cg.extensions).flatMap(Set::stream).toList(); } - public Set getGroupTypes() { - return channelGroups.stream().map(cg -> cg.groupTypes).flatMap(Set::stream).collect(Collectors.toSet()); + public List getGroupTypes() { + return channelGroups.stream().map(cg -> cg.groupTypes).flatMap(Set::stream).toList(); } public int[] getSignalLevels() { @@ -191,29 +188,29 @@ public enum ModuleType { : WIFI_SIGNAL_LEVELS; } throw new IllegalArgumentException( - "This should not be called for module type : " + name() + ", please file a bug report."); + "getSignalLevels should not be called for module type : '%s', please file a bug report." + .formatted(name())); } public ModuleType getBridge() { - ModuleType bridge = bridgeType; - return bridge != null ? bridge : ModuleType.UNKNOWN; + return bridgeType.orElse(UNKNOWN); } public URI getConfigDescription() { return URI.create(BINDING_ID + ":" + (equals(ACCOUNT) ? "api_bridge" : equals(HOME) ? "home" - : (isLogical() ? "virtual" - : ModuleType.UNKNOWN.equals(getBridge()) ? "configurable" : "device"))); + : (isLogical() ? "virtual" : UNKNOWN.equals(getBridge()) ? "configurable" : "device"))); } public int getDepth() { - ModuleType parent = bridgeType; - return parent == null ? 1 : 1 + parent.getDepth(); + ModuleType parent = getBridge(); + return parent == UNKNOWN ? 1 : parent.getDepth() + 1; } public static ModuleType from(ThingTypeUID thingTypeUID) { - return ModuleType.AS_SET.stream().filter(mt -> mt.thingTypeUID.equals(thingTypeUID)).findFirst() - .orElseThrow(() -> new IllegalArgumentException()); + return AS_SET.stream().filter(mt -> mt.thingTypeUID.equals(thingTypeUID)).findFirst() + .orElseThrow(() -> new IllegalArgumentException( + "No known ModuleType matched '%s'".formatted(thingTypeUID.toString()))); } } diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/data/NetatmoConstants.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/data/NetatmoConstants.java index c80518022..e2d692a3f 100644 --- a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/data/NetatmoConstants.java +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/data/NetatmoConstants.java @@ -197,10 +197,13 @@ public class NetatmoConstants { WRITE_DOORBELL, @SerializedName("access_doorbell") ACCESS_DOORBELL, + @SerializedName("read_carbonmonoxidedetector") + READ_CARBONMONOXIDEDETECTOR, UNKNOWN; } private static final Scope[] SMOKE_SCOPES = { Scope.READ_SMOKEDETECTOR }; + private static final Scope[] CARBON_MONOXIDE_SCOPES = { Scope.READ_CARBONMONOXIDEDETECTOR }; private static final Scope[] AIR_CARE_SCOPES = { Scope.READ_HOMECOACH }; private static final Scope[] WEATHER_SCOPES = { Scope.READ_STATION }; private static final Scope[] THERMOSTAT_SCOPES = { Scope.READ_THERMOSTAT, Scope.WRITE_THERMOSTAT }; @@ -212,7 +215,7 @@ public class NetatmoConstants { AIR_CARE(AIR_CARE_SCOPES), WEATHER(WEATHER_SCOPES), ENERGY(THERMOSTAT_SCOPES), - SECURITY(WELCOME_SCOPES, PRESENCE_SCOPES, SMOKE_SCOPES, DOORBELL_SCOPES), + SECURITY(WELCOME_SCOPES, PRESENCE_SCOPES, SMOKE_SCOPES, DOORBELL_SCOPES, CARBON_MONOXIDE_SCOPES), NONE(); public static String ALL_SCOPES = EnumSet.allOf(FeatureArea.class).stream().map(fa -> fa.scopes) diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/ApiBridgeHandler.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/ApiBridgeHandler.java index 52018abb7..d210d91de 100644 --- a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/ApiBridgeHandler.java +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/ApiBridgeHandler.java @@ -43,6 +43,7 @@ import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.util.InputStreamContentProvider; +import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpStatus; @@ -150,6 +151,7 @@ public class ApiBridgeHandler extends BaseBridgeHandler { String refreshToken = connectApi.authorize(configuration, code, redirectUri); if (configuration.refreshToken.isBlank()) { + logger.trace("Adding refresh token to configuration : {}", refreshToken); Configuration thingConfig = editConfiguration(); thingConfig.put(ApiHandlerConfiguration.REFRESH_TOKEN, refreshToken); updateConfiguration(thingConfig); @@ -254,6 +256,7 @@ public class ApiBridgeHandler extends BaseBridgeHandler { try (InputStreamContentProvider inputStreamContentProvider = new InputStreamContentProvider(stream)) { request.content(inputStreamContentProvider, contentType); } + logger.trace(" -with payload : {} ", payload); } if (isLinked(requestCountChannelUID)) { @@ -265,22 +268,25 @@ public class ApiBridgeHandler extends BaseBridgeHandler { } updateState(requestCountChannelUID, new DecimalType(requestsTimestamps.size())); } + logger.trace(" -with headers : {} ", + String.join(", ", request.getHeaders().stream().map(HttpField::toString).toList())); ContentResponse response = request.send(); Code statusCode = HttpStatus.getCode(response.getStatus()); String responseBody = new String(response.getContent(), StandardCharsets.UTF_8); - logger.trace("executeUri returned : code {} body {}", statusCode, responseBody); + logger.trace(" -returned : code {} body {}", statusCode, responseBody); - if (statusCode != Code.OK) { - try { - ApiError error = deserializer.deserialize(ApiError.class, responseBody); - throw new NetatmoException(error); - } catch (NetatmoException e) { - logger.debug("Error deserializing payload from error response", e); - throw new NetatmoException(statusCode.getMessage()); - } + if (statusCode == Code.OK) { + return deserializer.deserialize(clazz, responseBody); } - return deserializer.deserialize(clazz, responseBody); + + NetatmoException exception; + try { + exception = new NetatmoException(deserializer.deserialize(ApiError.class, responseBody)); + } catch (NetatmoException e) { + exception = new NetatmoException("Error deserializing error : %s".formatted(statusCode.getMessage())); + } + throw exception; } catch (NetatmoException e) { if (e.getStatusCode() == ServiceError.MAXIMUM_USAGE_REACHED) { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/SmokeCapability.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/AlarmEventCapability.java similarity index 62% rename from bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/SmokeCapability.java rename to bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/AlarmEventCapability.java index 904a1cf67..44005b0db 100644 --- a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/SmokeCapability.java +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/AlarmEventCapability.java @@ -12,39 +12,31 @@ */ package org.openhab.binding.netatmo.internal.handler.capability; -import java.util.ArrayList; import java.util.List; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.netatmo.internal.api.dto.HomeEvent; import org.openhab.binding.netatmo.internal.api.dto.NAObject; import org.openhab.binding.netatmo.internal.handler.CommonInterface; import org.openhab.binding.netatmo.internal.handler.channelhelper.ChannelHelper; import org.openhab.binding.netatmo.internal.providers.NetatmoDescriptionProvider; /** - * {@link SmokeCapability} gives the ability to handle Smoke detector specifics + * {@link AlarmEventCapability} gives the ability to handle Alarm modules events * * @author Gaël L'hopital - Initial contribution * */ @NonNullByDefault -public class SmokeCapability extends HomeSecurityThingCapability { +public class AlarmEventCapability extends HomeSecurityThingCapability { - public SmokeCapability(CommonInterface handler, NetatmoDescriptionProvider descriptionProvider, + public AlarmEventCapability(CommonInterface handler, NetatmoDescriptionProvider descriptionProvider, List channelHelpers) { super(handler, descriptionProvider, channelHelpers); } @Override public List updateReadings() { - List result = new ArrayList<>(); - securityCapability.ifPresent(cap -> { - HomeEvent event = cap.getLastDeviceEvent(handler.getId(), moduleType.apiName); - if (event != null) { - result.add(event); - } - }); - return result; + return securityCapability.map(cap -> cap.getDeviceLastEvent(handler.getId(), moduleType.apiName)) + .map(event -> List.of((NAObject) event)).orElse(List.of()); } } diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/CameraCapability.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/CameraCapability.java index 8e9da2246..35b25ad63 100644 --- a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/CameraCapability.java +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/CameraCapability.java @@ -100,7 +100,7 @@ public class CameraCapability extends HomeSecurityThingCapability { public List updateReadings() { List result = new ArrayList<>(); securityCapability.ifPresent(cap -> { - HomeEvent event = cap.getLastDeviceEvent(handler.getId(), moduleType.apiName); + HomeEvent event = cap.getDeviceLastEvent(handler.getId(), moduleType.apiName); if (event != null) { result.add(event); result.addAll(event.getSubevents()); diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/SecurityCapability.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/SecurityCapability.java index 96b6174fa..1bc19c8e9 100644 --- a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/SecurityCapability.java +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/SecurityCapability.java @@ -162,24 +162,24 @@ class SecurityCapability extends RestCapability { return event; } - public @Nullable HomeEvent getLastDeviceEvent(String cameraId, String deviceType) { - HomeEvent event = eventBuffer.get(cameraId); + public @Nullable HomeEvent getDeviceLastEvent(String moduleId, String deviceType) { + HomeEvent event = eventBuffer.get(moduleId); if (event == null) { - Collection events = requestDeviceEvents(cameraId, deviceType); + Collection events = requestDeviceEvents(moduleId, deviceType); if (!events.isEmpty()) { event = events.iterator().next(); - eventBuffer.put(cameraId, event); + eventBuffer.put(moduleId, event); } } return event; } - private Collection requestDeviceEvents(String cameraId, String deviceType) { + private Collection requestDeviceEvents(String moduleId, String deviceType) { return getApi().map(api -> { try { - return api.getDeviceEvents(handler.getId(), cameraId, deviceType); + return api.getDeviceEvents(handler.getId(), moduleId, deviceType); } catch (NetatmoException e) { - logger.warn("Error retrieving last events of camera '{}' : {}", cameraId, e.getMessage()); + logger.warn("Error retrieving last events of camera '{}' : {}", moduleId, e.getMessage()); return null; } }).orElse(List.of()); diff --git a/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/i18n/netatmo.properties b/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/i18n/netatmo.properties index 4f2f8042c..aa6e85628 100644 --- a/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/i18n/netatmo.properties +++ b/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/i18n/netatmo.properties @@ -18,6 +18,9 @@ channel-group-type.netatmo.energy.label = Home Energy channel-group-type.netatmo.energy.channel.end.label = Mode End channel-group-type.netatmo.energy.channel.end.description = End time of the currently applied setpoint. channel-group-type.netatmo.humidity.label = Humidity +channel-group-type.netatmo.last-event-alarm.label = Last Event +channel-group-type.netatmo.last-event-alarm.channel.time.label = Event Timestamp +channel-group-type.netatmo.last-event-alarm.channel.time.description = Moment when event occurred. channel-group-type.netatmo.last-event-doorbell.label = Last Event channel-group-type.netatmo.last-event-doorbell.channel.local-video-url.label = Video Local URL channel-group-type.netatmo.last-event-doorbell.channel.local-video-url.description = Local URL of the event recording. @@ -31,9 +34,6 @@ channel-group-type.netatmo.last-event-person.channel.snapshot.description = Pict channel-group-type.netatmo.last-event-person.channel.snapshot-url.description = URL for the picture of the last event for this person. channel-group-type.netatmo.last-event-person.channel.time.label = Person Timestamp channel-group-type.netatmo.last-event-person.channel.time.description = Moment of the last event for this person. -channel-group-type.netatmo.last-event-smoke.label = Last Event -channel-group-type.netatmo.last-event-smoke.channel.time.label = Event Timestamp -channel-group-type.netatmo.last-event-smoke.channel.time.description = Moment when event occurred. channel-group-type.netatmo.last-event.label = Last Event channel-group-type.netatmo.last-event.channel.local-video-url.label = Video Local URL channel-group-type.netatmo.last-event.channel.local-video-url.description = Local URL of the event recording. @@ -174,8 +174,8 @@ channel-type.netatmo.event-subtype.state.option.MOVEMENT_VEHICLE = Car seen channel-type.netatmo.event-subtype.state.option.MOVEMENT_ANIMAL = Animal seen channel-type.netatmo.event-subtype.state.option.SOUND_TEST_OK = Alarm test successful channel-type.netatmo.event-subtype.state.option.SOUND_TEST_ERROR = Alarm test failed -channel-type.netatmo.event-subtype.state.option.DETECTOR_READY = Smoke detector installed -channel-type.netatmo.event-subtype.state.option.DETECTOR_TAMPERED = Smoke detector tampered +channel-type.netatmo.event-subtype.state.option.DETECTOR_READY = Detector installed +channel-type.netatmo.event-subtype.state.option.DETECTOR_TAMPERED = Detector tampered channel-type.netatmo.event-subtype.state.option.DETECTION_CHAMBER_CLEAN = Detection chamber clean channel-type.netatmo.event-subtype.state.option.DETECTION_CHAMBER_DIRTY = Detection chamber dusty channel-type.netatmo.event-subtype.state.option.BATTERY_LOW = Battery low @@ -184,6 +184,9 @@ channel-type.netatmo.event-subtype.state.option.SMOKE_CLEARED = Smoke cleared channel-type.netatmo.event-subtype.state.option.SMOKE_DETECTED = Smoke detected channel-type.netatmo.event-subtype.state.option.WIFI_STATUS_OK = Wi-Fi status ok channel-type.netatmo.event-subtype.state.option.WIFI_STATUS_ERROR = Wi-Fi status error +channel-type.netatmo.event-subtype.state.option.CO_OK = Carbon Monoxide OK +channel-type.netatmo.event-subtype.state.option.CO_PRE_ALARM = Carbon Monoxide Pre-alarm +channel-type.netatmo.event-subtype.state.option.CO_ALARM = Carbon Monoxide alarrm channel-type.netatmo.event-type.label = Event Type channel-type.netatmo.event-type.description = Description of the event. channel-type.netatmo.event-type.state.option.PERSON = Face detected @@ -212,12 +215,13 @@ channel-type.netatmo.event-type.state.option.RTC = Button pressed channel-type.netatmo.event-type.state.option.MISSED_CALL = Call has not been answered by anyone channel-type.netatmo.event-type.state.option.HUSH = Smoke detector status channel-type.netatmo.event-type.state.option.SMOKE = Smoke detection -channel-type.netatmo.event-type.state.option.TAMPERED = Smoke Detector tamper +channel-type.netatmo.event-type.state.option.TAMPERED = Detector tamper channel-type.netatmo.event-type.state.option.WIFI_STATUS = Wifi status channel-type.netatmo.event-type.state.option.BATTERY_STATUS = Battery status channel-type.netatmo.event-type.state.option.DETECTION_CHAMBER_STATUS = Detection chamber status channel-type.netatmo.event-type.state.option.SOUND_TEST = Sound test channel-type.netatmo.event-type.state.option.NEW_DEVICE = A device has been added +channel-type.netatmo.event-type.state.option.CO_DETECTED = Carbon Monoxide detection channel-type.netatmo.floodlight-mode.label = Floodlight channel-type.netatmo.floodlight-mode.description = State of the floodlight (On/Off/Auto) channel-type.netatmo.floodlight-mode.state.option.ON = On @@ -378,6 +382,8 @@ extensible-channel-type.timestamp.pattern = %1$tA, %1$td.%1$tm. %1$tH:%1$tM thing-type.netatmo.account.label = Netatmo Account thing-type.netatmo.account.description = This bridge represents an account, gateway to Netatmo API. +thing-type.netatmo.co-detector.label = Carbon Monoxide Alarm +thing-type.netatmo.co-detector.description = The Netatmo Smart Carbon Monoxide Alarm device. thing-type.netatmo.doorbell.label = Smart Video Doorbell thing-type.netatmo.doorbell.description = The Netatmo Smart Video Doorbell device. thing-type.netatmo.tag.label = Smart Door Sensor diff --git a/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/channels.xml b/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/channels.xml index 97efdce82..2a0f6a5bb 100644 --- a/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/channels.xml +++ b/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/channels.xml @@ -375,12 +375,13 @@ - + + @@ -408,8 +409,8 @@ - - + + @@ -418,6 +419,9 @@ + + + diff --git a/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/security.xml b/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/security.xml index cf410d75a..fa817e532 100644 --- a/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/security.xml +++ b/bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/security.xml @@ -140,7 +140,7 @@ - +