[Netatmo] Adding Carbon Monoxide sensor (#14543)

* Added Carbon Monoxide detector

---------

Signed-off-by: clinique <gael@lhopital.org>
This commit is contained in:
Gaël L'hopital 2023-03-08 19:27:35 +01:00 committed by GitHub
parent 50cdd02447
commit 90b2279a29
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 128 additions and 93 deletions

View File

@ -10,6 +10,7 @@ The Netatmo binding integrates the following Netatmo products:
- _Doorbell_
- _Smoke Detector_
- _Smart Door Sensor_
- _Carbon Monoxide Detector_
See <https://www.netatmo.com/> 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

View File

@ -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<String> 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;

View File

@ -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<ChannelHelper> 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) {

View File

@ -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<? extends ChannelHelper> helper;
public final Set<String> groupTypes;
public final Set<String> extensions;
@ -86,13 +84,13 @@ public class ChannelGroup {
this.extensions = extensions;
}
public Optional<ChannelHelper> 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();
}
}

View File

@ -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),

View File

@ -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<EventType> AS_SET = EnumSet.allOf(EventType.class);

View File

@ -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<ModuleType> AS_SET = EnumSet.allOf(ModuleType.class);
private final @Nullable ModuleType bridgeType;
private final Optional<ModuleType> bridgeType;
public final Set<ChannelGroup> channelGroups;
public final Set<Class<? extends Capability>> capabilities;
public final ThingTypeUID thingTypeUID;
@ -155,7 +157,7 @@ public enum ModuleType {
ModuleType(FeatureArea feature, String apiName, @Nullable ModuleType bridge,
Set<Class<? extends Capability>> 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<String> 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<String> getGroupTypes() {
return channelGroups.stream().map(cg -> cg.groupTypes).flatMap(Set::stream).collect(Collectors.toSet());
public List<String> 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())));
}
}

View File

@ -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)

View File

@ -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());

View File

@ -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<ChannelHelper> channelHelpers) {
super(handler, descriptionProvider, channelHelpers);
}
@Override
public List<NAObject> updateReadings() {
List<NAObject> 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());
}
}

View File

@ -100,7 +100,7 @@ public class CameraCapability extends HomeSecurityThingCapability {
public List<NAObject> updateReadings() {
List<NAObject> 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());

View File

@ -162,24 +162,24 @@ class SecurityCapability extends RestCapability<SecurityApi> {
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<HomeEvent> events = requestDeviceEvents(cameraId, deviceType);
Collection<HomeEvent> events = requestDeviceEvents(moduleId, deviceType);
if (!events.isEmpty()) {
event = events.iterator().next();
eventBuffer.put(cameraId, event);
eventBuffer.put(moduleId, event);
}
}
return event;
}
private Collection<HomeEvent> requestDeviceEvents(String cameraId, String deviceType) {
private Collection<HomeEvent> 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());

View File

@ -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

View File

@ -375,12 +375,13 @@
<option value="MISSED_CALL">Call has not been answered by anyone</option>
<option value="HUSH">Smoke detector status</option>
<option value="SMOKE">Smoke detection</option>
<option value="TAMPERED">Smoke Detector tamper</option>
<option value="TAMPERED">Detector tamper</option>
<option value="WIFI_STATUS">Wifi status</option>
<option value="BATTERY_STATUS">Battery status</option>
<option value="DETECTION_CHAMBER_STATUS">Detection chamber status</option>
<option value="SOUND_TEST">Sound test</option>
<option value="NEW_DEVICE">A device has been added</option>
<option value="CO_DETECTED">Carbon Monoxide detection</option>
</options>
</state>
</channel-type>
@ -408,8 +409,8 @@
<option value="MOVEMENT_ANIMAL">Animal seen</option>
<option value="SOUND_TEST_OK">Alarm test successful</option>
<option value="SOUND_TEST_ERROR">Alarm test failed</option>
<option value="DETECTOR_READY">Smoke detector installed</option>
<option value="DETECTOR_TAMPERED">Smoke detector tampered</option>
<option value="DETECTOR_READY">Detector installed</option>
<option value="DETECTOR_TAMPERED">Detector tampered</option>
<option value="DETECTION_CHAMBER_CLEAN">Detection chamber clean</option>
<option value="DETECTION_CHAMBER_DIRTY">Detection chamber dusty</option>
<option value="BATTERY_LOW">Battery low</option>
@ -418,6 +419,9 @@
<option value="SMOKE_DETECTED">Smoke detected</option>
<option value="WIFI_STATUS_OK">Wi-Fi status ok</option>
<option value="WIFI_STATUS_ERROR">Wi-Fi status error</option>
<option value="CO_OK">Carbon Monoxide OK</option>
<option value="CO_PRE_ALARM">Carbon Monoxide Pre-alarm</option>
<option value="CO_ALARM">Carbon Monoxide alarrm</option>
</options>
</state>
</channel-type>

View File

@ -140,7 +140,7 @@
</channels>
</channel-group-type>
<channel-group-type id="last-event-smoke">
<channel-group-type id="last-event-alarm">
<label>Last Event</label>
<channels>
<channel id="type" typeId="event-type"/>