[netatmo] Extend webhook support for doorbell and presence camera (#14252)

* [netatmo] Web hook extension for camera events

- Unused camera-event trigger channel removed
- README updated to the real supported channels (compared with channels.xml and code)
- Camera capabilities are now triggering the home-event trigger channel
- New home-event trigger channel introduced at camera level
- New sub-event channels introduced for the presence camera which is updated by web hook events.
- Language file updated
- README updated
- typos fixed
- security-event trigger channel added for the Welcome camera
- Handling of sub-event groups fixed to work with doorbell and presence cameras.

---------

Signed-off-by: Sven Strohschein <sven.strohschein@gmail.com>
Signed-off-by: Sven Strohschein <novanic@gmx.de>
This commit is contained in:
Sven Strohschein
2023-03-26 17:34:35 +02:00
committed by GitHub
parent 1726031ecc
commit a8d91b3950
11 changed files with 275 additions and 155 deletions

View File

@@ -50,6 +50,7 @@ public class NetatmoBindingConstants {
public static final String GROUP_SIGNAL = "signal";
public static final String GROUP_BATTERY = "battery";
public static final String GROUP_SECURITY = "security";
public static final String GROUP_SECURITY_EVENT = "security-event";
public static final String GROUP_CAM_STATUS = "status";
public static final String GROUP_CAM_LIVE = "live";
public static final String GROUP_PRESENCE = "presence";

View File

@@ -43,7 +43,7 @@ import org.openhab.binding.netatmo.internal.handler.channelhelper.ApiBridgeChann
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.EventDoorbellChannelHelper;
import org.openhab.binding.netatmo.internal.handler.channelhelper.EventCameraChannelHelper;
import org.openhab.binding.netatmo.internal.handler.channelhelper.EventPersonChannelHelper;
import org.openhab.binding.netatmo.internal.handler.channelhelper.PersonChannelHelper;
import org.openhab.binding.netatmo.internal.handler.channelhelper.PresenceChannelHelper;
@@ -70,7 +70,7 @@ public enum ModuleType {
HOME(FeatureArea.NONE, "NAHome", ACCOUNT,
Set.of(DeviceCapability.class, HomeCapability.class, ChannelHelperCapability.class),
new ChannelGroup(SecurityChannelHelper.class, GROUP_SECURITY),
new ChannelGroup(SecurityChannelHelper.class, GROUP_SECURITY_EVENT, GROUP_SECURITY),
new ChannelGroup(EnergyChannelHelper.class, GROUP_ENERGY)),
PERSON(FeatureArea.SECURITY, "NAPerson", HOME, Set.of(PersonCapability.class, ChannelHelperCapability.class),
@@ -79,7 +79,7 @@ public enum ModuleType {
WELCOME(FeatureArea.SECURITY, "NACamera", HOME, Set.of(CameraCapability.class, ChannelHelperCapability.class),
ChannelGroup.SIGNAL, ChannelGroup.EVENT,
new ChannelGroup(CameraChannelHelper.class, GROUP_CAM_STATUS, GROUP_CAM_LIVE)),
new ChannelGroup(CameraChannelHelper.class, GROUP_SECURITY_EVENT, GROUP_CAM_STATUS, GROUP_CAM_LIVE)),
TAG(FeatureArea.SECURITY, "NACamDoorTag", WELCOME, Set.of(ChannelHelperCapability.class), ChannelGroup.SIGNAL,
ChannelGroup.BATTERY, ChannelGroup.TIMESTAMP, new ChannelGroup(DoorTagChannelHelper.class, GROUP_TAG)),
@@ -89,12 +89,15 @@ public enum ModuleType {
PRESENCE(FeatureArea.SECURITY, "NOC", HOME, Set.of(PresenceCapability.class, ChannelHelperCapability.class),
ChannelGroup.SIGNAL, ChannelGroup.EVENT,
new ChannelGroup(PresenceChannelHelper.class, GROUP_CAM_STATUS, GROUP_CAM_LIVE, GROUP_PRESENCE)),
new ChannelGroup(PresenceChannelHelper.class, GROUP_SECURITY_EVENT, GROUP_CAM_STATUS, GROUP_CAM_LIVE,
GROUP_PRESENCE),
new ChannelGroup(EventCameraChannelHelper.class, GROUP_SUB_EVENT)),
DOORBELL(FeatureArea.SECURITY, "NDB", HOME, Set.of(DoorbellCapability.class, ChannelHelperCapability.class),
ChannelGroup.SIGNAL,
new ChannelGroup(CameraChannelHelper.class, GROUP_DOORBELL_STATUS, GROUP_DOORBELL_LIVE),
new ChannelGroup(EventDoorbellChannelHelper.class, GROUP_DOORBELL_LAST_EVENT, GROUP_DOORBELL_SUB_EVENT)),
new ChannelGroup(CameraChannelHelper.class, GROUP_SECURITY_EVENT, GROUP_DOORBELL_STATUS,
GROUP_DOORBELL_LIVE),
new ChannelGroup(EventCameraChannelHelper.class, GROUP_DOORBELL_LAST_EVENT, GROUP_DOORBELL_SUB_EVENT)),
WEATHER_STATION(FeatureArea.WEATHER, "NAMain", ACCOUNT,
Set.of(DeviceCapability.class, WeatherCapability.class, MeasureCapability.class,

View File

@@ -13,8 +13,8 @@
package org.openhab.binding.netatmo.internal.api.dto;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.LinkedHashSet;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
@@ -72,8 +72,8 @@ public class WebhookEvent extends Event {
return vignetteUrl;
}
public List<String> getNAObjectList() {
List<String> result = new ArrayList<>();
public Set<String> getNAObjectList() {
Set<String> result = new LinkedHashSet<>();
result.add(getCameraId());
addNotBlank(result, homeId);
addNotBlank(result, deviceId);
@@ -82,9 +82,9 @@ public class WebhookEvent extends Event {
return result;
}
private void addNotBlank(List<String> list, String value) {
private void addNotBlank(Set<String> collection, String value) {
if (!value.isBlank()) {
list.add(value);
collection.add(value);
}
}
}

View File

@@ -13,6 +13,8 @@
package org.openhab.binding.netatmo.internal.handler.capability;
import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
import static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.*;
import static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.toStringType;
import java.util.ArrayList;
import java.util.List;
@@ -26,6 +28,7 @@ import org.openhab.binding.netatmo.internal.api.dto.HomeDataPerson;
import org.openhab.binding.netatmo.internal.api.dto.HomeEvent;
import org.openhab.binding.netatmo.internal.api.dto.HomeStatusModule;
import org.openhab.binding.netatmo.internal.api.dto.NAObject;
import org.openhab.binding.netatmo.internal.api.dto.WebhookEvent;
import org.openhab.binding.netatmo.internal.deserialization.NAObjectMap;
import org.openhab.binding.netatmo.internal.handler.CommonInterface;
import org.openhab.binding.netatmo.internal.handler.channelhelper.CameraChannelHelper;
@@ -33,8 +36,10 @@ import org.openhab.binding.netatmo.internal.handler.channelhelper.ChannelHelper;
import org.openhab.binding.netatmo.internal.providers.NetatmoDescriptionProvider;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.types.Command;
import org.openhab.core.types.StateOption;
import org.openhab.core.types.UnDefType;
/**
* {@link CameraCapability} give to handle Welcome Camera specifics
@@ -77,6 +82,35 @@ public class CameraCapability extends HomeSecurityThingCapability {
}
}
@Override
protected void updateWebhookEvent(WebhookEvent event) {
super.updateWebhookEvent(event);
final ThingUID thingUid = handler.getThing().getUID();
handler.updateState(new ChannelUID(thingUid, GROUP_SUB_EVENT, CHANNEL_EVENT_TYPE),
toStringType(event.getEventType()));
handler.updateState(new ChannelUID(thingUid, GROUP_SUB_EVENT, CHANNEL_EVENT_TIME),
toDateTimeType(event.getTime()));
handler.updateState(new ChannelUID(thingUid, GROUP_SUB_EVENT, CHANNEL_EVENT_SNAPSHOT),
toRawType(event.getSnapshotUrl()));
handler.updateState(new ChannelUID(thingUid, GROUP_SUB_EVENT, CHANNEL_EVENT_SNAPSHOT_URL),
toStringType(event.getSnapshotUrl()));
handler.updateState(new ChannelUID(thingUid, GROUP_SUB_EVENT, CHANNEL_EVENT_VIGNETTE),
toRawType(event.getVignetteUrl()));
handler.updateState(new ChannelUID(thingUid, GROUP_SUB_EVENT, CHANNEL_EVENT_VIGNETTE_URL),
toStringType(event.getVignetteUrl()));
final String message = event.getName();
handler.updateState(new ChannelUID(thingUid, GROUP_SUB_EVENT, CHANNEL_EVENT_MESSAGE),
message == null || message.isBlank() ? UnDefType.NULL : toStringType(message));
// The channel should get triggered at last (after super and sub methods), because this allows rules to access
// the new updated data from the other channels.
final String eventType = event.getEventType().name();
handler.getHomeHandler().ifPresent(homeHandler -> homeHandler.triggerChannel(CHANNEL_HOME_EVENT, eventType));
handler.triggerChannel(CHANNEL_HOME_EVENT, eventType);
}
@Override
public void handleCommand(String channelName, Command command) {
if (command instanceof OnOffType && CHANNEL_MONITORING.equals(channelName)) {

View File

@@ -12,19 +12,12 @@
*/
package org.openhab.binding.netatmo.internal.handler.capability;
import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
import static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.*;
import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.netatmo.internal.api.dto.WebhookEvent;
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;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.types.UnDefType;
/**
* {@link DoorbellCapability} give to handle Welcome Doorbell specifics
@@ -34,33 +27,9 @@ import org.openhab.core.types.UnDefType;
*/
@NonNullByDefault
public class DoorbellCapability extends CameraCapability {
private final ThingUID thingUid;
public DoorbellCapability(CommonInterface handler, NetatmoDescriptionProvider descriptionProvider,
List<ChannelHelper> channelHelpers) {
super(handler, descriptionProvider, channelHelpers);
thingUid = handler.getThing().getUID();
}
@Override
public void updateWebhookEvent(WebhookEvent event) {
super.updateWebhookEvent(event);
handler.updateState(new ChannelUID(thingUid, GROUP_SUB_EVENT, CHANNEL_EVENT_TYPE),
toStringType(event.getEventType()));
handler.updateState(new ChannelUID(thingUid, GROUP_SUB_EVENT, CHANNEL_EVENT_TIME),
toDateTimeType(event.getTime()));
handler.updateState(new ChannelUID(thingUid, GROUP_SUB_EVENT, CHANNEL_EVENT_SNAPSHOT),
toRawType(event.getSnapshotUrl()));
handler.updateState(new ChannelUID(thingUid, GROUP_SUB_EVENT, CHANNEL_EVENT_SNAPSHOT_URL),
toStringType(event.getSnapshotUrl()));
handler.updateState(new ChannelUID(thingUid, GROUP_SUB_EVENT, CHANNEL_EVENT_VIGNETTE),
toRawType(event.getVignetteUrl()));
handler.updateState(new ChannelUID(thingUid, GROUP_SUB_EVENT, CHANNEL_EVENT_VIGNETTE_URL),
toStringType(event.getVignetteUrl()));
String message = event.getName();
handler.updateState(new ChannelUID(thingUid, GROUP_SUB_EVENT, CHANNEL_EVENT_MESSAGE),
message == null || message.isBlank() ? UnDefType.NULL : toStringType(message));
}
}

View File

@@ -24,21 +24,21 @@ import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.types.State;
/**
* The {@link EventDoorbellChannelHelper} handles specific channels of doorbell events
* The {@link EventCameraChannelHelper} handles specific channels of sub-events (presence camera and doorbell).
*
* @author Gaël L'hopital - Initial contribution
*
*/
@NonNullByDefault
public class EventDoorbellChannelHelper extends EventChannelHelper {
public class EventCameraChannelHelper extends EventChannelHelper {
public EventDoorbellChannelHelper(Set<String> providedGroups) {
public EventCameraChannelHelper(Set<String> providedGroups) {
super(providedGroups);
}
@Override
protected @Nullable State internalGetHomeEvent(String channelId, @Nullable String groupId, HomeEvent event) {
if (groupId != null && GROUP_DOORBELL_SUB_EVENT.startsWith(groupId)) {
if (groupId != null && groupId.startsWith(GROUP_SUB_EVENT)) {
switch (channelId) {
case CHANNEL_EVENT_TYPE:
return toStringType(event.getEventType());

View File

@@ -71,6 +71,7 @@ channel-group-type.netatmo.rain.channel.sum-1.description = Quantity of water ov
channel-group-type.netatmo.rain.channel.sum-24.label = Rain 24h
channel-group-type.netatmo.rain.channel.sum-24.description = Quantity of water during the current day.
channel-group-type.netatmo.security.label = Home Security
channel-group-type.netatmo.security-event.label = Home Security Event
channel-group-type.netatmo.setpoint.label = Setpoint
channel-group-type.netatmo.setpoint.channel.end.label = Setpoint End
channel-group-type.netatmo.setpoint.channel.end.description = End time of the currently applied setpoint.
@@ -80,9 +81,16 @@ channel-group-type.netatmo.signal.label = Signal
channel-group-type.netatmo.siren.label = Siren Status
channel-group-type.netatmo.status-doorbell.label = Camera Status
channel-group-type.netatmo.status.label = Camera Status
channel-group-type.netatmo.sub-event.label = Sub Event
channel-group-type.netatmo.sub-event.channel.time.label = Sub-Event Timestamp
channel-group-type.netatmo.sub-event.channel.time.description = Moment when the sub-event occurred.
channel-group-type.netatmo.sub-event.channel.vignette.label = Vignette
channel-group-type.netatmo.sub-event.channel.vignette.description = Vignette of the Snapshot.
channel-group-type.netatmo.sub-event.channel.vignette-url.label = Vignette URL
channel-group-type.netatmo.sub-event.channel.vignette-url.description = URL of the vignette.
channel-group-type.netatmo.sub-event-doorbell.label = Sub Event
channel-group-type.netatmo.sub-event-doorbell.channel.time.label = Sub-Event Timestamp
channel-group-type.netatmo.sub-event-doorbell.channel.time.description = Moment when then sub-event occurred.
channel-group-type.netatmo.sub-event-doorbell.channel.time.description = Moment when the sub-event occurred.
channel-group-type.netatmo.sub-event-doorbell.channel.vignette.label = Vignette
channel-group-type.netatmo.sub-event-doorbell.channel.vignette.description = Vignette of the Snapshot.
channel-group-type.netatmo.sub-event-doorbell.channel.vignette-url.label = Vignette URL
@@ -136,7 +144,6 @@ channel-type.netatmo.avatar-picture.label = Avatar Picture
channel-type.netatmo.avatar-picture.description = Avatar of this person.
channel-type.netatmo.battery-status.label = Battery Status
channel-type.netatmo.battery-status.description = Description of the battery status.
channel-type.netatmo.camera-event.label = Camera Event
channel-type.netatmo.camera-id.label = Camera ID
channel-type.netatmo.camera-id.description = ID of the camera that triggered the event.
channel-type.netatmo.co2.label = CO2

View File

@@ -58,19 +58,6 @@
<state readOnly="true"/>
</channel-type>
<channel-type id="camera-event">
<kind>trigger</kind>
<label>Camera Event</label>
<event>
<options>
<option value="ANIMAL"/>
<option value="HUMAN"/>
<option value="MOVEMENT"/>
<option value="VEHICLE"/>
</options>
</event>
</channel-type>
<channel-type id="battery-status">
<item-type>String</item-type>
<label>Battery Status</label>

View File

@@ -10,6 +10,12 @@
<channel id="person-count" typeId="person-count"/>
<channel id="unknown-person-count" typeId="unknown-person-count"/>
<channel id="unknown-person-picture" typeId="unknown-person-picture"/>
</channels>
</channel-group-type>
<channel-group-type id="security-event">
<label>Home Security Event</label>
<channels>
<channel id="home-event" typeId="home-event"/>
</channels>
</channel-group-type>
@@ -98,13 +104,35 @@
</channels>
</channel-group-type>
<channel-group-type id="sub-event">
<label>Sub Event</label>
<channels>
<channel id="type" typeId="event-type"/>
<channel id="time" typeId="timestamp">
<label>Sub-Event Timestamp</label>
<description>Moment when the sub-event occurred.</description>
</channel>
<channel id="message" typeId="message"/>
<channel id="snapshot" typeId="event-picture"/>
<channel id="snapshot-url" typeId="event-picture-url"/>
<channel id="vignette" typeId="event-picture">
<label>Vignette</label>
<description>Vignette of the Snapshot.</description>
</channel>
<channel id="vignette-url" typeId="event-picture-url">
<label>Vignette URL</label>
<description>URL of the vignette.</description>
</channel>
</channels>
</channel-group-type>
<channel-group-type id="sub-event-doorbell">
<label>Sub Event</label>
<channels>
<channel id="type" typeId="event-type"/>
<channel id="time" typeId="timestamp">
<label>Sub-Event Timestamp</label>
<description>Moment when then sub-event occurred.</description>
<description>Moment when the sub-event occurred.</description>
</channel>
<channel id="message" typeId="message"/>
<channel id="snapshot" typeId="event-picture"/>

View File

@@ -0,0 +1,59 @@
/**
* Copyright (c) 2010-2023 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.channelhelper;
import static org.junit.jupiter.api.Assertions.*;
import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
import java.util.Collections;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.openhab.binding.netatmo.internal.api.data.EventType;
import org.openhab.binding.netatmo.internal.api.dto.HomeEvent;
import org.openhab.core.library.types.StringType;
import org.openhab.core.types.State;
/**
* @author Sven Strohschein - Initial contribution
*/
public class EventCameraChannelHelperTest {
private EventCameraChannelHelper helper;
@BeforeEach
public void before() {
helper = new EventCameraChannelHelper(Collections.emptySet());
}
@Test
public void testInternalGetHomeEventGroupSubEvent() {
State state = helper.internalGetHomeEvent(CHANNEL_EVENT_TYPE, GROUP_SUB_EVENT, new HomeEvent());
assertTrue(state instanceof StringType);
assertEquals(EventType.UNKNOWN.name(), state.toString());
}
@Test
public void testInternalGetHomeEventGroupDoorbellSubEvent() {
State state = helper.internalGetHomeEvent(CHANNEL_EVENT_TYPE, GROUP_DOORBELL_SUB_EVENT, new HomeEvent());
assertTrue(state instanceof StringType);
assertEquals(EventType.UNKNOWN.name(), state.toString());
}
@Test
public void testInternalGetHomeEventGroupDoorbellStatus() {
State state = helper.internalGetHomeEvent(CHANNEL_EVENT_TYPE, GROUP_DOORBELL_STATUS, new HomeEvent());
// Only sub-event groups are handled by EventCameraChannelHelper. GROUP_DOORBELL_STATUS isn't a sub-event group.
assertNull(state);
}
}