[netatmo] Adding smoke detector module (#12984)

* Adding smoke detector module

Signed-off-by: clinique <gael@lhopital.org>
This commit is contained in:
Gaël L'hopital 2022-06-23 08:37:45 +02:00 committed by GitHub
parent 8d900dbcf5
commit f4f0292dcd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 205 additions and 27 deletions

View File

@ -8,6 +8,7 @@ The Netatmo binding integrates the following Netatmo products:
- *Siren*
- *Outdoor Camera / Presence*. Reports last event, consult picture and video from event/camera.
- *Doorbell*
- *Smoke Detector*
See https://www.netatmo.com/ for details on their product.
@ -574,6 +575,22 @@ Person things are automatically created in discovery process for all known perso
All these channels except at-home are read only.
### Netatmo Smart Smoke Detector
All these channels are read only.
**Supported channels for the Smoke 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 person |
| last-event | subtype | String | Sub-type of event |
| last-event | message | String | Last event message from this person |
## Configuration Examples

View File

@ -66,8 +66,10 @@ 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 Set<String> GROUP_VARIATIONS = Set.of(OPTION_EXTENDED, OPTION_OUTSIDE, OPTION_DOORBELL,
OPTION_PERSON, OPTION_ROOM, OPTION_THERMOSTAT);
OPTION_PERSON, OPTION_ROOM, OPTION_THERMOSTAT, OPTION_SMOKE);
public static final String GROUP_TYPE_TIMESTAMP_EXTENDED = GROUP_TIMESTAMP + OPTION_EXTENDED;
public static final String GROUP_TYPE_BATTERY_EXTENDED = GROUP_BATTERY + OPTION_EXTENDED;
public static final String GROUP_TYPE_PRESSURE_EXTENDED = GROUP_PRESSURE + OPTION_EXTENDED;
@ -79,6 +81,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_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

@ -37,6 +37,7 @@ 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;
@ -133,6 +134,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 == PresenceCapability.class) {
newCap = new PresenceCapability(handler, stateDescriptionProvider, helpers);
} else if (capability == MeasureCapability.class) {

View File

@ -77,7 +77,7 @@ public class SecurityApi extends RestManager {
throw new NetatmoException("home should not be null");
}
public Collection<HomeEvent> getCameraEvents(String homeId, String deviceId, String deviceType)
public Collection<HomeEvent> getDeviceEvents(String homeId, String deviceId, String deviceType)
throws NetatmoException {
UriBuilder uriBuilder = getApiUriBuilder(SUB_PATH_GET_EVENTS, PARAM_HOME_ID, homeId, PARAM_DEVICE_ID, deviceId,
PARAM_DEVICES_TYPE, deviceType);

View File

@ -51,6 +51,7 @@ public class ChannelGroup {
public static final ChannelGroup LOCATION = new ChannelGroup(LocationChannelHelper.class, GROUP_LOCATION);
public static final ChannelGroup BATTERY_EXT = new ChannelGroup(BatteryChannelHelper.class,
GROUP_TYPE_BATTERY_EXTENDED);
public static final ChannelGroup TIMESTAMP = new ChannelGroup(TimestampChannelHelper.class, GROUP_TIMESTAMP);
public static final ChannelGroup TSTAMP_EXT = new ChannelGroup(TimestampChannelHelper.class,
GROUP_TYPE_TIMESTAMP_EXTENDED);
public static final ChannelGroup TEMP_OUTSIDE_EXT = new ChannelGroup(TemperatureChannelHelper.class,

View File

@ -23,29 +23,46 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
*/
@NonNullByDefault
public enum EventSubType {
SD_CARD_MISSING(List.of(EventType.SD), 1),
SD_CARD_INSERTED(List.of(EventType.SD), 2),
SD_CARD_FORMATTED(List.of(EventType.SD), 3),
SD_CARD_WORKING(List.of(EventType.SD), 4),
SD_CARD_DEFECTIVE(List.of(EventType.SD), 5),
SD_CARD_INCOMPATIBLE_SPEED(List.of(EventType.SD), 6),
SD_CARD_INSUFFICIENT_SPACE(List.of(EventType.SD), 7),
ALIM_INCORRECT_POWER(List.of(EventType.ALIM), 1),
ALIM_CORRECT_POWER(List.of(EventType.ALIM), 2),
// SD Card sub events
SD_CARD_MISSING(1, EventType.SD),
SD_CARD_INSERTED(2, EventType.SD),
SD_CARD_FORMATTED(3, EventType.SD),
SD_CARD_WORKING(4, EventType.SD),
SD_CARD_DEFECTIVE(5, EventType.SD),
SD_CARD_INCOMPATIBLE_SPEED(6, EventType.SD),
SD_CARD_INSUFFICIENT_SPACE(7, EventType.SD),
// Alimentation sub events
ALIM_INCORRECT_POWER(1, EventType.ALIM),
ALIM_CORRECT_POWER(2, EventType.ALIM),
// Smoke detector sub events
DETECTION_CHAMBER_CLEAN(0, EventType.DETECTION_CHAMBER_STATUS),
DETECTION_CHAMBER_DIRTY(1, EventType.DETECTION_CHAMBER_STATUS),
BATTERY_LOW(0, EventType.BATTERY_STATUS),
BATTERY_VERY_LOW(1, EventType.BATTERY_STATUS),
SMOKE_CLEARED(0, EventType.SMOKE),
SMOKE_DETECTED(1, EventType.SMOKE),
SOUND_TEST_OK(0, EventType.SOUND_TEST),
SOUND_TEST_ERROR(1, EventType.SOUND_TEST),
DETECTOR_READY(0, EventType.TAMPERED),
DETECTOR_TAMPERED(1, EventType.TAMPERED),
WIFI_STATUS_OK(1, EventType.WIFI_STATUS),
WIFI_STATUS_ERROR(0, EventType.WIFI_STATUS),
// Artificially implemented by the binding subtypes
PERSON_ARRIVAL(List.of(EventType.PERSON, EventType.PERSON_HOME), 1),
PERSON_SEEN(List.of(EventType.PERSON), 2),
PERSON_DEPARTURE(List.of(EventType.PERSON_AWAY), 1),
MOVEMENT_HUMAN(List.of(EventType.MOVEMENT, EventType.HUMAN), 1),
MOVEMENT_VEHICLE(List.of(EventType.MOVEMENT), 2),
MOVEMENT_ANIMAL(List.of(EventType.MOVEMENT, EventType.ANIMAL), 3);
PERSON_ARRIVAL(1, EventType.PERSON, EventType.PERSON_HOME),
PERSON_SEEN(2, EventType.PERSON),
PERSON_DEPARTURE(1, EventType.PERSON_AWAY),
MOVEMENT_HUMAN(1, EventType.MOVEMENT, EventType.HUMAN),
MOVEMENT_VEHICLE(2, EventType.MOVEMENT),
MOVEMENT_ANIMAL(3, EventType.MOVEMENT, EventType.ANIMAL);
public final List<EventType> types;
public final int subType;
EventSubType(List<EventType> types, int i) {
this.types = types;
EventSubType(int i, EventType... types) {
this.types = List.of(types);
this.subType = i;
}
}

View File

@ -98,7 +98,31 @@ public enum EventType {
INCOMING_CALL(ModuleType.DOORBELL),
@SerializedName("missed_call") // When a call has not been answered by anyone
MISSED_CALL(ModuleType.DOORBELL);
MISSED_CALL(ModuleType.DOORBELL),
@SerializedName("hush") // When the smoke detection is activated or deactivated
HUSH(ModuleType.SMOKE_DETECTOR),
@SerializedName("smoke") // When smoke is detected or smoke is cleared
SMOKE(ModuleType.SMOKE_DETECTOR),
@SerializedName("tampered") // When smoke detector is ready or tampered
TAMPERED(ModuleType.SMOKE_DETECTOR),
@SerializedName("wifi_status") // When wifi status is updated
WIFI_STATUS(ModuleType.SMOKE_DETECTOR),
@SerializedName("battery_status") // When battery status is too low
BATTERY_STATUS(ModuleType.SMOKE_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),
@SerializedName("new_device")
NEW_DEVICE(ModuleType.HOME);
private final Set<ModuleType> appliesTo;

View File

@ -36,10 +36,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.CameraChannelHelper;
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;
@ -51,7 +53,6 @@ import org.openhab.binding.netatmo.internal.handler.channelhelper.SecurityChanne
import org.openhab.binding.netatmo.internal.handler.channelhelper.SetpointChannelHelper;
import org.openhab.binding.netatmo.internal.handler.channelhelper.SirenChannelHelper;
import org.openhab.binding.netatmo.internal.handler.channelhelper.Therm1ChannelHelper;
import org.openhab.binding.netatmo.internal.handler.channelhelper.TimestampChannelHelper;
import org.openhab.binding.netatmo.internal.handler.channelhelper.WindChannelHelper;
import org.openhab.core.thing.ThingTypeUID;
@ -80,8 +81,7 @@ public enum ModuleType {
ChannelGroup.EVENT, new ChannelGroup(CameraChannelHelper.class, GROUP_CAM_STATUS, GROUP_CAM_LIVE)),
SIREN(FeatureArea.SECURITY, "NIS", WELCOME, Set.of(ChannelHelperCapability.class), ChannelGroup.SIGNAL,
ChannelGroup.BATTERY, new ChannelGroup(TimestampChannelHelper.class, GROUP_TIMESTAMP),
new ChannelGroup(SirenChannelHelper.class, GROUP_SIREN)),
ChannelGroup.BATTERY, ChannelGroup.TIMESTAMP, new ChannelGroup(SirenChannelHelper.class, GROUP_SIREN)),
PRESENCE(FeatureArea.SECURITY, "NOC", HOME,
Set.of(EventCapability.class, PresenceCapability.class, ChannelHelperCapability.class), ChannelGroup.SIGNAL,
@ -135,7 +135,11 @@ public enum ModuleType {
ROOM(FeatureArea.ENERGY, "NARoom", HOME, Set.of(RoomCapability.class, ChannelHelperCapability.class),
new ChannelGroup(RoomChannelHelper.class, GROUP_TYPE_ROOM_PROPERTIES, GROUP_TYPE_ROOM_TEMPERATURE),
new ChannelGroup(SetpointChannelHelper.class, GROUP_SETPOINT));
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));
public static final EnumSet<ModuleType> AS_SET = EnumSet.allOf(ModuleType.class);

View File

@ -97,7 +97,7 @@ public class CameraCapability extends HomeSecurityThingCapability {
public List<NAObject> updateReadings() {
List<NAObject> result = new ArrayList<>();
securityCapability.ifPresent(cap -> {
Collection<HomeEvent> events = cap.getCameraEvents(handler.getId(), moduleType.apiName);
Collection<HomeEvent> events = cap.getDeviceEvents(handler.getId(), moduleType.apiName);
if (!events.isEmpty()) {
HomeEvent event = events.iterator().next();
result.add(event);

View File

@ -110,10 +110,10 @@ class SecurityCapability extends RestCapability<SecurityApi> {
});
}
public Collection<HomeEvent> getCameraEvents(String cameraId, String deviceType) {
public Collection<HomeEvent> getDeviceEvents(String cameraId, String deviceType) {
return getApi().map(api -> {
try {
return api.getCameraEvents(handler.getId(), cameraId, deviceType);
return api.getDeviceEvents(handler.getId(), cameraId, deviceType);
} catch (NetatmoException e) {
logger.warn("Error retrieving last events of camera '{}' : {}", cameraId, e.getMessage());
return null;

View File

@ -0,0 +1,51 @@
/**
* Copyright (c) 2010-2022 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.netatmo.internal.handler.capability;
import java.util.ArrayList;
import java.util.Collection;
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
*
* @author Gaël L'hopital - Initial contribution
*
*/
@NonNullByDefault
public class SmokeCapability extends HomeSecurityThingCapability {
public SmokeCapability(CommonInterface handler, NetatmoDescriptionProvider descriptionProvider,
List<ChannelHelper> channelHelpers) {
super(handler, descriptionProvider, channelHelpers);
}
@Override
public List<NAObject> updateReadings() {
List<NAObject> result = new ArrayList<>();
securityCapability.ifPresent(cap -> {
Collection<HomeEvent> events = cap.getDeviceEvents(handler.getId(), moduleType.apiName);
if (!events.isEmpty()) {
result.add(events.iterator().next());
}
});
return result;
}
}

View File

@ -31,6 +31,9 @@ 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.
@ -167,6 +170,18 @@ channel-type.netatmo.event-subtype.state.option.PERSON_SEEN = Person has been se
channel-type.netatmo.event-subtype.state.option.MOVEMENT_HUMAN = Human seen
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.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
channel-type.netatmo.event-subtype.state.option.BATTERY_VERY_LOW = Battery very low
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-type.label = Event Type
channel-type.netatmo.event-type.description = Description of the event.
channel-type.netatmo.event-type.state.option.PERSON = Face detected
@ -192,6 +207,14 @@ channel-type.netatmo.event-type.state.option.ALIM = Power status changed
channel-type.netatmo.event-type.state.option.ACCEPTED_CALL = Call is incoming
channel-type.netatmo.event-type.state.option.INCOMING_CALL = Call has been answered by a user
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.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.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
@ -356,6 +379,8 @@ thing-type.netatmo.room.label = Room
thing-type.netatmo.room.description = A room in your house.
thing-type.netatmo.siren.label = Siren Module
thing-type.netatmo.siren.description = The Netatmo Smart Indoor Siren.
thing-type.netatmo.smoke-detector.label = Smoke Detector
thing-type.netatmo.smoke-detector.description = The Netatmo Smart Smoke Detector.
thing-type.netatmo.thermostat.label = Thermostat Module
thing-type.netatmo.thermostat.description = The Thermostat device placed in a given room.
thing-type.netatmo.valve.label = Radiator valve

View File

@ -357,6 +357,14 @@
<option value="ACCEPTED_CALL">Call is incoming</option>
<option value="INCOMING_CALL">Call has been answered by a user</option>
<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="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>
</options>
</state>
</channel-type>
@ -382,6 +390,18 @@
<option value="MOVEMENT_HUMAN">Human seen</option>
<option value="MOVEMENT_VEHICLE">Car seen</option>
<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="DETECTION_CHAMBER_CLEAN">Detection chamber clean</option>
<option value="DETECTION_CHAMBER_DIRTY">Detection chamber dusty</option>
<option value="BATTERY_LOW">Battery low</option>
<option value="BATTERY_VERY_LOW">Battery very low</option>
<option value="SMOKE_CLEARED">Smoke cleared</option>
<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>
</options>
</state>
</channel-type>

View File

@ -140,6 +140,19 @@
</channels>
</channel-group-type>
<channel-group-type id="last-event-smoke">
<label>Last Event</label>
<channels>
<channel id="type" typeId="event-type"/>
<channel id="time" typeId="timestamp">
<label>Event Timestamp</label>
<description>Moment when event occurred.</description>
</channel>
<channel id="subtype" typeId="event-subtype"/>
<channel id="message" typeId="message"/>
</channels>
</channel-group-type>
<channel-group-type id="last-event-person">
<label>Last Event</label>
<channels>