2 * Copyright (c) 2010-2020 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.ChannelTypeUtils.*;
16 import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
18 import java.util.Calendar;
19 import java.util.Comparator;
20 import java.util.Optional;
22 import java.util.TreeSet;
23 import java.util.function.Function;
25 import org.eclipse.jdt.annotation.NonNullByDefault;
26 import org.eclipse.jdt.annotation.Nullable;
27 import org.openhab.core.i18n.TimeZoneProvider;
28 import org.openhab.core.library.types.DecimalType;
29 import org.openhab.core.library.types.StringType;
30 import org.openhab.core.thing.Thing;
31 import org.openhab.core.types.State;
32 import org.openhab.core.types.UnDefType;
33 import org.openhab.binding.netatmo.internal.ChannelTypeUtils;
34 import org.openhab.binding.netatmo.internal.camera.CameraHandler;
35 import org.openhab.binding.netatmo.internal.handler.AbstractNetatmoThingHandler;
36 import org.openhab.binding.netatmo.internal.handler.NetatmoDeviceHandler;
37 import org.openhab.binding.netatmo.internal.webhook.NAWebhookCameraEvent;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
41 import io.swagger.client.model.NAWelcomeEvent;
42 import io.swagger.client.model.NAWelcomeHome;
43 import io.swagger.client.model.NAWelcomePlace;
44 import io.swagger.client.model.NAWelcomeSnapshot;
45 import io.swagger.client.model.NAWelcomeSubEvent;
48 * {@link NAWelcomeHomeHandler} is the class used to handle the Welcome Home Data
50 * @author Gaƫl L'hopital - Initial contribution
51 * @author Ing. Peter Weiss - Welcome camera implementation
55 public class NAWelcomeHomeHandler extends NetatmoDeviceHandler<NAWelcomeHome> {
56 private final Logger logger = LoggerFactory.getLogger(NAWelcomeHomeHandler.class);
58 private int iPersons = -1;
59 private int iUnknowns = -1;
60 private @Nullable NAWelcomeEvent lastEvent;
61 private boolean isNewLastEvent;
62 private @Nullable Integer dataTimeStamp;
64 public NAWelcomeHomeHandler(Thing thing, final TimeZoneProvider timeZoneProvider) {
65 super(thing, timeZoneProvider);
69 protected Optional<NAWelcomeHome> updateReadings() {
70 Optional<NAWelcomeHome> result = getBridgeHandler().flatMap(handler -> handler.getWelcomeDataBody(getId()))
71 .map(dataBody -> dataBody.getHomes().stream().filter(device -> device.getId().equalsIgnoreCase(getId()))
72 .findFirst().orElse(null));
73 // data time stamp is updated to now as WelcomeDataBody does not provide any information according to this need
74 dataTimeStamp = (int) (Calendar.getInstance().getTimeInMillis() / 1000);
75 result.ifPresent(home -> {
76 home.getCameras().forEach(camera -> childs.put(camera.getId(), camera));
78 // Check how many persons are at home
82 logger.debug("welcome home '{}' calculate Persons at home count", getId());
83 home.getPersons().forEach(person -> {
84 iPersons += person.getOutOfSight() ? 0 : 1;
85 if (person.getPseudo() != null) {
86 childs.put(person.getId(), person);
88 iUnknowns += person.getOutOfSight() ? 0 : 1;
92 NAWelcomeEvent previousLastEvent = lastEvent;
93 lastEvent = home.getEvents().stream().max(Comparator.comparingInt(NAWelcomeEvent::getTime)).orElse(null);
94 isNewLastEvent = previousLastEvent != null && !previousLastEvent.equals(lastEvent);
100 protected State getNAThingProperty(String channelId) {
101 Optional<NAWelcomeEvent> lastEvt = getLastEvent();
103 case CHANNEL_WELCOME_HOME_CITY:
104 return getPlaceInfo(NAWelcomePlace::getCity);
105 case CHANNEL_WELCOME_HOME_COUNTRY:
106 return getPlaceInfo(NAWelcomePlace::getCountry);
107 case CHANNEL_WELCOME_HOME_TIMEZONE:
108 return getPlaceInfo(NAWelcomePlace::getTimezone);
109 case CHANNEL_WELCOME_HOME_PERSONCOUNT:
110 return iPersons != -1 ? new DecimalType(iPersons) : UnDefType.UNDEF;
111 case CHANNEL_WELCOME_HOME_UNKNOWNCOUNT:
112 return iUnknowns != -1 ? new DecimalType(iUnknowns) : UnDefType.UNDEF;
113 case CHANNEL_WELCOME_EVENT_TYPE:
114 return lastEvt.map(e -> toStringType(e.getType())).orElse(UnDefType.UNDEF);
115 case CHANNEL_WELCOME_EVENT_TIME:
116 return lastEvt.map(e -> toDateTimeType(e.getTime(), timeZoneProvider.getTimeZone()))
117 .orElse(UnDefType.UNDEF);
118 case CHANNEL_WELCOME_EVENT_CAMERAID:
119 if (lastEvt.isPresent()) {
120 return findNAThing(lastEvt.get().getCameraId()).map(c -> toStringType(c.getThing().getLabel()))
121 .orElse(UnDefType.UNDEF);
123 return UnDefType.UNDEF;
125 case CHANNEL_WELCOME_EVENT_PERSONID:
126 if (lastEvt.isPresent()) {
127 return findNAThing(lastEvt.get().getPersonId()).map(p -> toStringType(p.getThing().getLabel()))
128 .orElse(UnDefType.UNDEF);
130 return UnDefType.UNDEF;
132 case CHANNEL_WELCOME_EVENT_SNAPSHOT:
133 return findSnapshotURL().map(url -> toRawType(url)).orElse(UnDefType.UNDEF);
134 case CHANNEL_WELCOME_EVENT_SNAPSHOT_URL:
135 return findSnapshotURL().map(ChannelTypeUtils::toStringType).orElse(UnDefType.UNDEF);
136 case CHANNEL_WELCOME_EVENT_VIDEO_URL:
137 if (lastEvt.isPresent() && lastEvt.get().getVideoId() != null) {
138 String cameraId = lastEvt.get().getCameraId();
139 Optional<AbstractNetatmoThingHandler> thing = findNAThing(cameraId);
140 if (thing.isPresent()) {
141 CameraHandler eventCamera = (CameraHandler) thing.get();
142 Optional<String> streamUrl = eventCamera.getStreamURL(lastEvt.get().getVideoId());
143 if (streamUrl.isPresent()) {
144 return new StringType(streamUrl.get());
148 return UnDefType.UNDEF;
149 case CHANNEL_WELCOME_EVENT_VIDEOSTATUS:
150 return lastEvt.map(e -> e.getVideoId() != null ? toStringType(e.getVideoStatus()) : UnDefType.UNDEF)
151 .orElse(UnDefType.UNDEF);
152 case CHANNEL_WELCOME_EVENT_ISARRIVAL:
153 return lastEvt.map(e -> toOnOffType(e.getIsArrival())).orElse(UnDefType.UNDEF);
154 case CHANNEL_WELCOME_EVENT_MESSAGE:
155 return findEventMessage().map(m -> (State) new StringType(m.replace("<b>", "").replace("</b>", "")))
156 .orElse(UnDefType.UNDEF);
157 case CHANNEL_WELCOME_EVENT_SUBTYPE:
158 return lastEvt.map(e -> toDecimalType(e.getSubType())).orElse(UnDefType.UNDEF);
160 return super.getNAThingProperty(channelId);
164 protected void triggerChannelIfRequired(String channelId) {
165 if (isNewLastEvent) {
166 if (CHANNEL_CAMERA_EVENT.equals(channelId)) {
167 findDetectedObjectTypes(getLastEvent())
168 .forEach(detectedType -> triggerChannel(channelId, detectedType));
171 super.triggerChannelIfRequired(channelId);
174 private static Set<String> findDetectedObjectTypes(Optional<NAWelcomeEvent> eventOptional) {
175 Set<String> detectedObjectTypes = new TreeSet<>();
176 if (!eventOptional.isPresent()) {
177 return detectedObjectTypes;
180 NAWelcomeEvent event = eventOptional.get();
182 if (NAWebhookCameraEvent.EventTypeEnum.MOVEMENT.toString().equals(event.getType())) {
183 if (event.getPersonId() != null) {
184 detectedObjectTypes.add(NAWelcomeSubEvent.TypeEnum.HUMAN.name());
186 Optional<NAWelcomeSubEvent.TypeEnum> detectedCategory = findDetectedCategory(event);
187 if (detectedCategory.isPresent()) {
188 detectedObjectTypes.add(detectedCategory.get().name());
190 detectedObjectTypes.add(NAWebhookCameraEvent.EventTypeEnum.MOVEMENT.name());
195 event.getEventList().forEach(subEvent -> {
196 String detectedObjectType = subEvent.getType().name();
197 detectedObjectTypes.add(detectedObjectType);
199 return detectedObjectTypes;
202 private static Optional<NAWelcomeSubEvent.TypeEnum> findDetectedCategory(NAWelcomeEvent event) {
203 NAWelcomeEvent.CategoryEnum category = event.getCategory();
204 if (category != null) {
205 // It is safe to convert the enum, both enums have the same values.
206 return Optional.of(NAWelcomeSubEvent.TypeEnum.valueOf(category.name()));
208 return Optional.empty();
211 private Optional<String> findEventMessage() {
212 Optional<NAWelcomeEvent> lastEvt = getLastEvent();
213 if (lastEvt.isPresent()) {
215 String message = lastEvt.get().getMessage();
216 if (message != null) {
217 return Optional.of(message);
220 return lastEvt.flatMap(this::findFirstSubEvent).map(NAWelcomeSubEvent::getMessage);
222 return Optional.empty();
226 * Returns the Url of the picture
228 * @return Url of the picture or null
230 protected Optional<String> findSnapshotURL() {
231 Optional<NAWelcomeEvent> lastEvt = getLastEvent();
232 if (lastEvt.isPresent()) {
234 NAWelcomeSnapshot snapshot = lastEvt.get().getSnapshot();
235 if (snapshot == null) {
236 snapshot = lastEvt.flatMap(this::findFirstSubEvent).map(NAWelcomeSubEvent::getSnapshot).orElse(null);
239 if (snapshot != null && snapshot.getId() != null && snapshot.getKey() != null) {
240 return Optional.of(WELCOME_PICTURE_URL + "?" + WELCOME_PICTURE_IMAGEID + "=" + snapshot.getId() + "&"
241 + WELCOME_PICTURE_KEY + "=" + snapshot.getKey());
243 logger.debug("Unable to build snapshot url for Home : {}", getId());
246 return Optional.empty();
250 protected Optional<Integer> getDataTimestamp() {
251 Integer timestamp = dataTimeStamp;
252 return timestamp != null ? Optional.of(timestamp) : Optional.empty();
255 private State getPlaceInfo(Function<NAWelcomePlace, String> infoGetFunction) {
256 return getDevice().map(d -> toStringType(infoGetFunction.apply(d.getPlace()))).orElse(UnDefType.UNDEF);
259 private Optional<NAWelcomeSubEvent> findFirstSubEvent(NAWelcomeEvent event) {
260 return Optional.ofNullable(event).map(NAWelcomeEvent::getEventList)
261 .flatMap(subEvents -> subEvents.stream().findFirst());
264 private Optional<NAWelcomeEvent> getLastEvent() {
265 NAWelcomeEvent evt = lastEvent;
266 return evt != null ? Optional.of(evt) : Optional.empty();