2 * Copyright (c) 2010-2021 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.netatmo.internal.welcome;
15 import static org.openhab.binding.netatmo.internal.APIUtils.*;
16 import static org.openhab.binding.netatmo.internal.ChannelTypeUtils.*;
17 import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
19 import java.util.Calendar;
20 import java.util.Comparator;
21 import java.util.Optional;
23 import java.util.TreeSet;
24 import java.util.function.Function;
26 import org.eclipse.jdt.annotation.NonNullByDefault;
27 import org.eclipse.jdt.annotation.Nullable;
28 import org.openhab.binding.netatmo.internal.ChannelTypeUtils;
29 import org.openhab.binding.netatmo.internal.camera.CameraHandler;
30 import org.openhab.binding.netatmo.internal.handler.AbstractNetatmoThingHandler;
31 import org.openhab.binding.netatmo.internal.handler.NetatmoDeviceHandler;
32 import org.openhab.binding.netatmo.internal.webhook.NAWebhookCameraEvent;
33 import org.openhab.core.i18n.TimeZoneProvider;
34 import org.openhab.core.library.types.DecimalType;
35 import org.openhab.core.library.types.StringType;
36 import org.openhab.core.thing.Thing;
37 import org.openhab.core.types.State;
38 import org.openhab.core.types.UnDefType;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
42 import io.swagger.client.model.NAWelcomeEvent;
43 import io.swagger.client.model.NAWelcomeHome;
44 import io.swagger.client.model.NAWelcomePlace;
45 import io.swagger.client.model.NAWelcomeSnapshot;
46 import io.swagger.client.model.NAWelcomeSubEvent;
49 * {@link NAWelcomeHomeHandler} is the class used to handle the Welcome Home Data
51 * @author Gaƫl L'hopital - Initial contribution
52 * @author Ing. Peter Weiss - Welcome camera implementation
56 public class NAWelcomeHomeHandler extends NetatmoDeviceHandler<NAWelcomeHome> {
57 private final Logger logger = LoggerFactory.getLogger(NAWelcomeHomeHandler.class);
59 private int iPersons = -1;
60 private int iUnknowns = -1;
61 private @Nullable NAWelcomeEvent lastEvent;
62 private boolean isNewLastEvent;
63 private @Nullable Integer dataTimeStamp;
65 public NAWelcomeHomeHandler(Thing thing, final TimeZoneProvider timeZoneProvider) {
66 super(thing, timeZoneProvider);
70 protected Optional<NAWelcomeHome> updateReadings() {
71 Optional<NAWelcomeHome> result = getBridgeHandler().flatMap(handler -> handler.getWelcomeDataBody(getId()))
72 .map(dataBody -> nonNullStream(dataBody.getHomes())
73 .filter(device -> device.getId().equalsIgnoreCase(getId())).findFirst().orElse(null));
74 // data time stamp is updated to now as WelcomeDataBody does not provide any information according to this need
75 dataTimeStamp = (int) (Calendar.getInstance().getTimeInMillis() / 1000);
76 result.ifPresent(home -> {
77 nonNullList(home.getCameras()).forEach(camera -> childs.put(camera.getId(), camera));
79 // Check how many persons are at home
83 logger.debug("welcome home '{}' calculate Persons at home count", getId());
84 nonNullList(home.getPersons()).forEach(person -> {
85 iPersons += person.isOutOfSight() ? 0 : 1;
86 if (person.getPseudo() != null) {
87 childs.put(person.getId(), person);
89 iUnknowns += person.isOutOfSight() ? 0 : 1;
93 NAWelcomeEvent previousLastEvent = lastEvent;
94 lastEvent = nonNullStream(home.getEvents()).max(Comparator.comparingInt(NAWelcomeEvent::getTime))
96 isNewLastEvent = previousLastEvent != null && !previousLastEvent.equals(lastEvent);
102 protected State getNAThingProperty(String channelId) {
103 Optional<NAWelcomeEvent> lastEvt = getLastEvent();
105 case CHANNEL_WELCOME_HOME_CITY:
106 return getPlaceInfo(NAWelcomePlace::getCity);
107 case CHANNEL_WELCOME_HOME_COUNTRY:
108 return getPlaceInfo(NAWelcomePlace::getCountry);
109 case CHANNEL_WELCOME_HOME_TIMEZONE:
110 return getPlaceInfo(NAWelcomePlace::getTimezone);
111 case CHANNEL_WELCOME_HOME_PERSONCOUNT:
112 return iPersons != -1 ? new DecimalType(iPersons) : UnDefType.UNDEF;
113 case CHANNEL_WELCOME_HOME_UNKNOWNCOUNT:
114 return iUnknowns != -1 ? new DecimalType(iUnknowns) : UnDefType.UNDEF;
115 case CHANNEL_WELCOME_EVENT_TYPE:
116 return lastEvt.map(e -> toStringType(e.getType())).orElse(UnDefType.UNDEF);
117 case CHANNEL_WELCOME_EVENT_TIME:
118 return lastEvt.map(e -> toDateTimeType(e.getTime(), timeZoneProvider.getTimeZone()))
119 .orElse(UnDefType.UNDEF);
120 case CHANNEL_WELCOME_EVENT_CAMERAID:
121 if (lastEvt.isPresent()) {
122 return findNAThing(lastEvt.get().getCameraId()).map(c -> toStringType(c.getThing().getLabel()))
123 .orElse(UnDefType.UNDEF);
125 return UnDefType.UNDEF;
127 case CHANNEL_WELCOME_EVENT_PERSONID:
128 if (lastEvt.isPresent()) {
129 return findNAThing(lastEvt.get().getPersonId()).map(p -> toStringType(p.getThing().getLabel()))
130 .orElse(UnDefType.UNDEF);
132 return UnDefType.UNDEF;
134 case CHANNEL_WELCOME_EVENT_SNAPSHOT:
135 return findSnapshotURL().map(url -> toRawType(url)).orElse(UnDefType.UNDEF);
136 case CHANNEL_WELCOME_EVENT_SNAPSHOT_URL:
137 return findSnapshotURL().map(ChannelTypeUtils::toStringType).orElse(UnDefType.UNDEF);
138 case CHANNEL_WELCOME_EVENT_VIDEO_URL:
139 if (lastEvt.isPresent() && lastEvt.get().getVideoId() != null) {
140 String cameraId = lastEvt.get().getCameraId();
141 Optional<AbstractNetatmoThingHandler> thing = findNAThing(cameraId);
142 if (thing.isPresent()) {
143 CameraHandler eventCamera = (CameraHandler) thing.get();
144 Optional<String> streamUrl = eventCamera.getStreamURL(lastEvt.get().getVideoId());
145 if (streamUrl.isPresent()) {
146 return new StringType(streamUrl.get());
150 return UnDefType.UNDEF;
151 case CHANNEL_WELCOME_EVENT_VIDEOSTATUS:
152 return lastEvt.map(e -> e.getVideoId() != null ? toStringType(e.getVideoStatus()) : UnDefType.UNDEF)
153 .orElse(UnDefType.UNDEF);
154 case CHANNEL_WELCOME_EVENT_ISARRIVAL:
155 return lastEvt.map(e -> toOnOffType(e.isIsArrival())).orElse(UnDefType.UNDEF);
156 case CHANNEL_WELCOME_EVENT_MESSAGE:
157 return findEventMessage().map(m -> (State) new StringType(m.replace("<b>", "").replace("</b>", "")))
158 .orElse(UnDefType.UNDEF);
159 case CHANNEL_WELCOME_EVENT_SUBTYPE:
160 return lastEvt.map(e -> toDecimalType(e.getSubType())).orElse(UnDefType.UNDEF);
162 return super.getNAThingProperty(channelId);
166 protected void triggerChannelIfRequired(String channelId) {
167 if (isNewLastEvent) {
168 if (CHANNEL_CAMERA_EVENT.equals(channelId)) {
169 findDetectedObjectTypes(getLastEvent())
170 .forEach(detectedType -> triggerChannel(channelId, detectedType));
173 super.triggerChannelIfRequired(channelId);
176 private static Set<String> findDetectedObjectTypes(Optional<NAWelcomeEvent> eventOptional) {
177 Set<String> detectedObjectTypes = new TreeSet<>();
178 if (!eventOptional.isPresent()) {
179 return detectedObjectTypes;
182 NAWelcomeEvent event = eventOptional.get();
184 if (NAWebhookCameraEvent.EventTypeEnum.MOVEMENT.toString().equals(event.getType())) {
185 if (event.getPersonId() != null) {
186 detectedObjectTypes.add(NAWelcomeSubEvent.TypeEnum.HUMAN.name());
188 Optional<NAWelcomeSubEvent.TypeEnum> detectedCategory = findDetectedCategory(event);
189 if (detectedCategory.isPresent()) {
190 detectedObjectTypes.add(detectedCategory.get().name());
192 detectedObjectTypes.add(NAWebhookCameraEvent.EventTypeEnum.MOVEMENT.name());
197 nonNullList(event.getEventList()).forEach(subEvent -> {
198 String detectedObjectType = subEvent.getType().name();
199 detectedObjectTypes.add(detectedObjectType);
201 return detectedObjectTypes;
204 private static Optional<NAWelcomeSubEvent.TypeEnum> findDetectedCategory(NAWelcomeEvent event) {
205 NAWelcomeEvent.CategoryEnum category = event.getCategory();
206 if (category != null) {
207 // It is safe to convert the enum, both enums have the same values.
208 return Optional.of(NAWelcomeSubEvent.TypeEnum.valueOf(category.name()));
210 return Optional.empty();
213 private Optional<String> findEventMessage() {
214 Optional<NAWelcomeEvent> lastEvt = getLastEvent();
215 if (lastEvt.isPresent()) {
217 String message = lastEvt.get().getMessage();
218 if (message != null) {
219 return Optional.of(message);
222 return lastEvt.flatMap(this::findFirstSubEvent).map(NAWelcomeSubEvent::getMessage);
224 return Optional.empty();
228 * Returns the Url of the picture
230 * @return Url of the picture or null
232 protected Optional<String> findSnapshotURL() {
233 Optional<NAWelcomeEvent> lastEvt = getLastEvent();
234 if (lastEvt.isPresent()) {
236 NAWelcomeSnapshot snapshot = lastEvt.get().getSnapshot();
237 if (snapshot == null) {
238 snapshot = lastEvt.flatMap(this::findFirstSubEvent).map(NAWelcomeSubEvent::getSnapshot).orElse(null);
241 if (snapshot != null && snapshot.getId() != null && snapshot.getKey() != null) {
242 return Optional.of(WELCOME_PICTURE_URL + "?" + WELCOME_PICTURE_IMAGEID + "=" + snapshot.getId() + "&"
243 + WELCOME_PICTURE_KEY + "=" + snapshot.getKey());
245 logger.debug("Unable to build snapshot url for Home : {}", getId());
248 return Optional.empty();
252 protected Optional<Integer> getDataTimestamp() {
253 Integer timestamp = dataTimeStamp;
254 return timestamp != null ? Optional.of(timestamp) : Optional.empty();
257 private State getPlaceInfo(Function<NAWelcomePlace, String> infoGetFunction) {
258 return getDevice().map(d -> toStringType(infoGetFunction.apply(d.getPlace()))).orElse(UnDefType.UNDEF);
261 private Optional<NAWelcomeSubEvent> findFirstSubEvent(NAWelcomeEvent event) {
262 return Optional.ofNullable(event).map(NAWelcomeEvent::getEventList)
263 .flatMap(subEvents -> nonNullStream(subEvents).findFirst());
266 private Optional<NAWelcomeEvent> getLastEvent() {
267 NAWelcomeEvent evt = lastEvent;
268 return evt != null ? Optional.of(evt) : Optional.empty();