Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[netatmo] Adding Webhook event support for Doorbell #12972

Merged
merged 5 commits into from
Jun 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
import org.openhab.binding.netatmo.internal.handler.capability.Capability;
import org.openhab.binding.netatmo.internal.handler.capability.ChannelHelperCapability;
import org.openhab.binding.netatmo.internal.handler.capability.DeviceCapability;
import org.openhab.binding.netatmo.internal.handler.capability.EventCapability;
import org.openhab.binding.netatmo.internal.handler.capability.DoorbellCapability;
import org.openhab.binding.netatmo.internal.handler.capability.HomeCapability;
import org.openhab.binding.netatmo.internal.handler.capability.MeasureCapability;
import org.openhab.binding.netatmo.internal.handler.capability.PersonCapability;
Expand Down Expand Up @@ -121,14 +121,14 @@ private BaseThingHandler buildHandler(Thing thing, ModuleType moduleType) {
newCap = new DeviceCapability(handler);
} else if (capability == AirCareCapability.class) {
newCap = new AirCareCapability(handler);
} else if (capability == EventCapability.class) {
newCap = new EventCapability(handler);
} else if (capability == HomeCapability.class) {
newCap = new HomeCapability(handler, stateDescriptionProvider);
} else if (capability == WeatherCapability.class) {
newCap = new WeatherCapability(handler);
} else if (capability == RoomCapability.class) {
newCap = new RoomCapability(handler);
} else if (capability == DoorbellCapability.class) {
newCap = new DoorbellCapability(handler, stateDescriptionProvider, helpers);
} else if (capability == PersonCapability.class) {
newCap = new PersonCapability(handler, stateDescriptionProvider, helpers);
} else if (capability == CameraCapability.class) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
*/
package org.openhab.binding.netatmo.internal.api.data;

import java.util.EnumSet;
import java.util.Set;

import org.eclipse.jdt.annotation.NonNullByDefault;
Expand Down Expand Up @@ -97,9 +98,14 @@ public enum EventType {
@SerializedName("incoming_call") // When a call as been answered by a user
INCOMING_CALL(ModuleType.DOORBELL),

@SerializedName("rtc") // Button pressed
RTC(ModuleType.DOORBELL),

@SerializedName("missed_call") // When a call has not been answered by anyone
MISSED_CALL(ModuleType.DOORBELL);

public static final EnumSet<EventType> AS_SET = EnumSet.allOf(EventType.class);

private final Set<ModuleType> appliesTo;

EventType(ModuleType... appliesTo) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
import org.openhab.binding.netatmo.internal.handler.capability.Capability;
import org.openhab.binding.netatmo.internal.handler.capability.ChannelHelperCapability;
import org.openhab.binding.netatmo.internal.handler.capability.DeviceCapability;
import org.openhab.binding.netatmo.internal.handler.capability.EventCapability;
import org.openhab.binding.netatmo.internal.handler.capability.DoorbellCapability;
import org.openhab.binding.netatmo.internal.handler.capability.HomeCapability;
import org.openhab.binding.netatmo.internal.handler.capability.MeasureCapability;
import org.openhab.binding.netatmo.internal.handler.capability.PersonCapability;
Expand Down Expand Up @@ -66,30 +66,28 @@ public enum ModuleType {
ACCOUNT(FeatureArea.NONE, "", null, Set.of()),

HOME(FeatureArea.NONE, "NAHome", ACCOUNT,
Set.of(DeviceCapability.class, EventCapability.class, HomeCapability.class, ChannelHelperCapability.class),
Set.of(DeviceCapability.class, HomeCapability.class, ChannelHelperCapability.class),
new ChannelGroup(SecurityChannelHelper.class, GROUP_SECURITY),
new ChannelGroup(EnergyChannelHelper.class, GROUP_ENERGY)),

PERSON(FeatureArea.SECURITY, "NAPerson", HOME,
Set.of(EventCapability.class, PersonCapability.class, ChannelHelperCapability.class),
PERSON(FeatureArea.SECURITY, "NAPerson", HOME, Set.of(PersonCapability.class, ChannelHelperCapability.class),
new ChannelGroup(PersonChannelHelper.class, GROUP_PERSON),
new ChannelGroup(EventPersonChannelHelper.class, GROUP_PERSON_LAST_EVENT)),

WELCOME(FeatureArea.SECURITY, "NACamera", HOME,
Set.of(EventCapability.class, CameraCapability.class, ChannelHelperCapability.class), ChannelGroup.SIGNAL,
ChannelGroup.EVENT, new ChannelGroup(CameraChannelHelper.class, GROUP_CAM_STATUS, GROUP_CAM_LIVE)),
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)),

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

PRESENCE(FeatureArea.SECURITY, "NOC", HOME,
Set.of(EventCapability.class, PresenceCapability.class, ChannelHelperCapability.class), ChannelGroup.SIGNAL,
ChannelGroup.EVENT,
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)),

DOORBELL(FeatureArea.SECURITY, "NDB", HOME,
Set.of(EventCapability.class, CameraCapability.class, ChannelHelperCapability.class), ChannelGroup.SIGNAL,
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)),

Expand Down Expand Up @@ -204,9 +202,4 @@ public static ModuleType from(ThingTypeUID thingTypeUID) {
return ModuleType.AS_SET.stream().filter(mt -> mt.thingTypeUID.equals(thingTypeUID)).findFirst()
.orElseThrow(() -> new IllegalArgumentException());
}

public static ModuleType from(String apiName) {
return ModuleType.AS_SET.stream().filter(mt -> apiName.equals(mt.apiName)).findFirst()
.orElseThrow(() -> new IllegalArgumentException());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,4 @@ public Optional<EventSubType> getSubTypeDescription() {
return Stream.of(EventSubType.values()).filter(v -> v.types.contains(getEventType()) && v.subType == subType)
.findFirst();
}

public void setEventType(EventType type) {
this.type = type;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
package org.openhab.binding.netatmo.internal.api.dto;

import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.List;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
Expand All @@ -29,9 +31,11 @@
*/
@NonNullByDefault
public class WebhookEvent extends Event {
private @NonNullByDefault({}) NAPushType pushType;
private NAPushType pushType = NAPushType.UNKNOWN;
private String homeId = "";
private String deviceId = "";
private @Nullable String snapshotUrl;
private @Nullable String vignetteUrl;
private NAObjectMap<Person> persons = new NAObjectMap<>();
// Webhook does not provide the event generation time, so we'll use the event reception time
private ZonedDateTime time = ZonedDateTime.now();
Expand Down Expand Up @@ -63,4 +67,24 @@ public ZonedDateTime getTime() {
public @Nullable String getSnapshotUrl() {
return snapshotUrl;
}

public @Nullable String getVignetteUrl() {
return vignetteUrl;
}

public List<String> getNAObjectList() {
List<String> result = new ArrayList<>();
result.add(getCameraId());
addNotBlank(result, homeId);
addNotBlank(result, deviceId);
addNotBlank(result, getCameraId());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it expected to add camera ID twice?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it is. Some values may be empty. In some cases (e.g. smoke detector), the cameraId will be blank but the deviceId will be filled. Redundant entries will be ignored.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not fully convinced, there is certainly something I do not understand...
As you are apparently convinced you have to call this twice, let's go, we have no more time to debate.

result.addAll(getPersons().keySet());
return result;
}

private void addNotBlank(List<String> list, String value) {
if (!value.isBlank()) {
list.add(value);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
*/
@NonNullByDefault
public class NAPushType {
public final static NAPushType UNKNOWN = new NAPushType(ModuleType.UNKNOWN, EventType.UNKNOWN);

private final ModuleType moduleType;
private final EventType event;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,12 @@
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.netatmo.internal.api.data.EventType;
import org.openhab.binding.netatmo.internal.api.data.ModuleType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;

/**
* Specialized deserializer for push_type field
Expand All @@ -31,21 +32,41 @@
*/
@NonNullByDefault
class NAPushTypeDeserializer implements JsonDeserializer<NAPushType> {
private final Logger logger = LoggerFactory.getLogger(NAPushTypeDeserializer.class);

@Override
public @Nullable NAPushType deserialize(JsonElement json, Type clazz, JsonDeserializationContext context)
throws JsonParseException {
String string = json.getAsString();
String[] elements = string.split("-");
if (elements.length > 1) {
try {
ModuleType moduleType = ModuleType.from(elements[0]);
EventType eventType = EventType.valueOf(elements[1].toUpperCase());

return new NAPushType(moduleType, eventType);
} catch (IllegalArgumentException e) {
}
public @Nullable NAPushType deserialize(JsonElement json, Type clazz, JsonDeserializationContext context) {
final String string = json.getAsString();
final String[] elements = string.split("-");
ModuleType moduleType = ModuleType.UNKNOWN;
EventType eventType = EventType.UNKNOWN;
if (elements.length == 2) {
moduleType = fromNetatmoObject(elements[0]);
eventType = fromEvent(elements[1]);
} else {
logger.warn("Unexpected syntax received for push_type field : {}", string);
}
if (moduleType.equals(ModuleType.UNKNOWN) || eventType.equals(EventType.UNKNOWN)) {
logger.warn("Unknown module or event type : {}, deserialized to '{}-{}'", string, moduleType, eventType);
}
throw new JsonParseException("Error deserializing : " + string);
return new NAPushType(moduleType, eventType);
}

/**
* @param apiName : Netatmo Object name (NSD, NACamera...)
* @return moduletype value if found, or else Unknown
*/
public static ModuleType fromNetatmoObject(String apiName) {
return ModuleType.AS_SET.stream().filter(mt -> apiName.equals(mt.apiName)).findFirst()
.orElse(ModuleType.UNKNOWN);
}

/**
* @param apiName : Netatmo Event name (hush, off, on ...)
* @return eventType value if found, or else Unknown
*/
public static EventType fromEvent(String apiName) {
return EventType.AS_SET.stream().filter(et -> apiName.equalsIgnoreCase(et.name())).findFirst()
.orElse(EventType.UNKNOWN);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import org.openhab.binding.netatmo.internal.api.dto.NAMain;
import org.openhab.binding.netatmo.internal.api.dto.NAObject;
import org.openhab.binding.netatmo.internal.api.dto.NAThing;
import org.openhab.binding.netatmo.internal.api.dto.WebhookEvent;
import org.openhab.binding.netatmo.internal.handler.CommonInterface;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.binding.ThingHandlerService;
Expand Down Expand Up @@ -74,6 +75,9 @@ public class Capability {
if (newData instanceof Event) {
updateEvent((Event) newData);
}
if (newData instanceof WebhookEvent) {
updateWebhookEvent((WebhookEvent) newData);
}
if (newData instanceof HomeEvent) {
updateHomeEvent((HomeEvent) newData);
}
Expand Down Expand Up @@ -137,6 +141,10 @@ protected void updateEvent(Event newData) {
// do nothing by default, can be overridden by subclasses
}

protected void updateWebhookEvent(WebhookEvent newData) {
// do nothing by default, can be overridden by subclasses
}

protected void updateNADevice(Device newData) {
// do nothing by default, can be overridden by subclasses
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/**
* 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 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
*
* @author Gaël L'hopital - Initial contribution
*
*/
@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));
}
}

This file was deleted.

Loading