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;
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) {
*/
package org.openhab.binding.netatmo.internal.api.data;
+import java.util.EnumSet;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
@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),
@SerializedName("new_device")
NEW_DEVICE(ModuleType.HOME);
+ public static final EnumSet<EventType> AS_SET = EnumSet.allOf(EventType.class);
+
private final Set<ModuleType> appliesTo;
EventType(ModuleType... appliesTo) {
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;
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, ChannelGroup.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)),
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());
- }
}
return Stream.of(EventSubType.values()).filter(v -> v.types.contains(getEventType()) && v.subType == subType)
.findFirst();
}
-
- public void setEventType(EventType type) {
- this.type = type;
- }
}
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;
*/
@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();
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());
+ result.addAll(getPersons().keySet());
+ return result;
+ }
+
+ private void addNotBlank(List<String> list, String value) {
+ if (!value.isBlank()) {
+ list.add(value);
+ }
+ }
}
*/
@NonNullByDefault
public class NAPushType {
+ public final static NAPushType UNKNOWN = new NAPushType(ModuleType.UNKNOWN, EventType.UNKNOWN);
+
private final ModuleType moduleType;
private final EventType event;
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
*/
@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);
}
}
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;
if (newData instanceof Event) {
updateEvent((Event) newData);
}
+ if (newData instanceof WebhookEvent) {
+ updateWebhookEvent((WebhookEvent) newData);
+ }
if (newData instanceof HomeEvent) {
updateHomeEvent((HomeEvent) 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
}
--- /dev/null
+/**
+ * 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));
+ }
+}
+++ /dev/null
-/**
- * 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.Optional;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.openhab.binding.netatmo.internal.handler.ApiBridgeHandler;
-import org.openhab.binding.netatmo.internal.handler.CommonInterface;
-import org.openhab.binding.netatmo.internal.servlet.WebhookServlet;
-
-/**
- * {@link EventCapability} is the base class for handlers subject to receive event notifications.
- * This class registers to NetatmoServletService so it can be notified when an event arrives.
- *
- * @author Gaël L'hopital - Initial contribution
- *
- */
-@NonNullByDefault
-public class EventCapability extends Capability {
- private Optional<WebhookServlet> webhook = Optional.empty();
-
- public EventCapability(CommonInterface handler) {
- super(handler);
- }
-
- @Override
- public void initialize() {
- ApiBridgeHandler accountHandler = handler.getAccountHandler();
- if (accountHandler != null) {
- webhook = accountHandler.getWebHookServlet();
- webhook.ifPresent(servlet -> servlet.registerDataListener(handler.getId(), this));
- }
- }
-
- @Override
- public void dispose() {
- webhook.ifPresent(servlet -> servlet.unregisterDataListener(handler.getId()));
- }
-}
import java.util.Optional;
import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.netatmo.internal.handler.ApiBridgeHandler;
import org.openhab.binding.netatmo.internal.handler.CommonInterface;
import org.openhab.binding.netatmo.internal.handler.channelhelper.ChannelHelper;
import org.openhab.binding.netatmo.internal.handler.channelhelper.EventChannelHelper;
import org.openhab.binding.netatmo.internal.providers.NetatmoDescriptionProvider;
+import org.openhab.binding.netatmo.internal.servlet.WebhookServlet;
/**
* {@link HomeSecurityThingCapability} is the ancestor of capabilities hosted by a security home
protected final NetatmoDescriptionProvider descriptionProvider;
protected final EventChannelHelper eventHelper;
+ private Optional<WebhookServlet> webhook = Optional.empty();
protected Optional<SecurityCapability> securityCapability = Optional.empty();
protected Optional<HomeCapability> homeCapability = Optional.empty();
super.initialize();
securityCapability = handler.getHomeCapability(SecurityCapability.class);
homeCapability = handler.getHomeCapability(HomeCapability.class);
+ ApiBridgeHandler accountHandler = handler.getAccountHandler();
+ if (accountHandler != null) {
+ webhook = accountHandler.getWebHookServlet();
+ webhook.ifPresent(servlet -> servlet.registerDataListener(handler.getId(), this));
+ }
+ }
+
+ @Override
+ public void dispose() {
+ webhook.ifPresent(servlet -> servlet.unregisterDataListener(handler.getId()));
+ super.dispose();
}
}
package org.openhab.binding.netatmo.internal.servlet;
import java.io.IOException;
-import java.io.InputStream;
import java.net.URI;
import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.List;
import java.util.Map;
-import java.util.Scanner;
import java.util.concurrent.ConcurrentHashMap;
import javax.servlet.http.HttpServletRequest;
import org.openhab.binding.netatmo.internal.api.dto.WebhookEvent;
import org.openhab.binding.netatmo.internal.deserialization.NADeserializer;
import org.openhab.binding.netatmo.internal.handler.ApiBridgeHandler;
-import org.openhab.binding.netatmo.internal.handler.capability.EventCapability;
+import org.openhab.binding.netatmo.internal.handler.capability.Capability;
import org.osgi.service.http.HttpService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class WebhookServlet extends NetatmoServlet {
private static final long serialVersionUID = -354583910860541214L;
- private final Map<String, EventCapability> dataListeners = new ConcurrentHashMap<>();
+ private final Map<String, Capability> dataListeners = new ConcurrentHashMap<>();
private final Logger logger = LoggerFactory.getLogger(WebhookServlet.class);
private final SecurityApi securityApi;
private final NADeserializer deserializer;
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws IOException {
replyQuick(resp);
- processEvent(inputStreamToString(req.getInputStream()));
+ processEvent(new String(req.getInputStream().readAllBytes(), StandardCharsets.UTF_8));
}
private void processEvent(String data) throws IOException {
logger.debug("Event transmitted from restService : {}", data);
try {
WebhookEvent event = deserializer.deserialize(WebhookEvent.class, data);
- List<String> toBeNotified = new ArrayList<>();
- toBeNotified.add(event.getCameraId());
- toBeNotified.addAll(event.getPersons().keySet());
- notifyListeners(toBeNotified, event);
+ notifyListeners(event);
} catch (NetatmoException e) {
logger.debug("Error deserializing webhook data received : {}. {}", data, e.getMessage());
}
resp.setContentType(MediaType.APPLICATION_JSON);
resp.setHeader("Access-Control-Allow-Origin", "*");
resp.setHeader("Access-Control-Allow-Methods", HttpMethod.POST);
- resp.setIntHeader("Access-Control-Max-Age", 3600);
resp.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
+ resp.setIntHeader("Access-Control-Max-Age", 3600);
resp.getWriter().write("");
}
- private String inputStreamToString(InputStream is) throws IOException {
- String value = "";
- try (Scanner scanner = new Scanner(is)) {
- scanner.useDelimiter("\\A");
- value = scanner.hasNext() ? scanner.next() : "";
- }
- return value;
- }
-
- private void notifyListeners(List<String> tobeNotified, WebhookEvent event) {
- tobeNotified.forEach(id -> {
- EventCapability module = dataListeners.get(id);
+ private void notifyListeners(WebhookEvent event) {
+ event.getNAObjectList().forEach(id -> {
+ Capability module = dataListeners.get(id);
if (module != null) {
module.setNewData(event);
}
});
}
- public void registerDataListener(String id, EventCapability eventCapability) {
- dataListeners.put(id, eventCapability);
+ public void registerDataListener(String id, Capability capability) {
+ dataListeners.put(id, capability);
}
public void unregisterDataListener(String id) {
return picture;
}
}
- return UnDefType.UNDEF;
+ return UnDefType.NULL;
}
}
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.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
<option value="ALIM">Power status changed</option>
<option value="ACCEPTED_CALL">Call is incoming</option>
<option value="INCOMING_CALL">Call has been answered by a user</option>
+ <option value="RTC">Button pressed</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="ALIM"/>
<option value="ACCEPTED_CALL"/>
<option value="INCOMING_CALL"/>
+ <option value="RTC"/>
<option value="MISSED_CALL"/>
</options>
</event>