]> git.basschouten.com Git - openhab-addons.git/blob
6363baba3d0288eec8868fe2b54c61a0b83503ad
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2022 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.netatmo.internal.welcome;
14
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.*;
18
19 import java.util.Calendar;
20 import java.util.Comparator;
21 import java.util.Optional;
22 import java.util.Set;
23 import java.util.TreeSet;
24 import java.util.function.Function;
25
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;
41
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;
47
48 /**
49  * {@link NAWelcomeHomeHandler} is the class used to handle the Welcome Home Data
50  *
51  * @author GaĆ«l L'hopital - Initial contribution
52  * @author Ing. Peter Weiss - Welcome camera implementation
53  *
54  */
55 @NonNullByDefault
56 public class NAWelcomeHomeHandler extends NetatmoDeviceHandler<NAWelcomeHome> {
57     private final Logger logger = LoggerFactory.getLogger(NAWelcomeHomeHandler.class);
58
59     private int iPersons = -1;
60     private int iUnknowns = -1;
61     private @Nullable NAWelcomeEvent lastEvent;
62     private boolean isNewLastEvent;
63     private @Nullable Integer dataTimeStamp;
64
65     public NAWelcomeHomeHandler(Thing thing, final TimeZoneProvider timeZoneProvider) {
66         super(thing, timeZoneProvider);
67     }
68
69     @Override
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));
78
79             // Check how many persons are at home
80             iPersons = 0;
81             iUnknowns = 0;
82
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);
88                 } else {
89                     iUnknowns += person.isOutOfSight() ? 0 : 1;
90                 }
91             });
92
93             NAWelcomeEvent previousLastEvent = lastEvent;
94             lastEvent = nonNullStream(home.getEvents()).max(Comparator.comparingInt(NAWelcomeEvent::getTime))
95                     .orElse(null);
96             isNewLastEvent = previousLastEvent != null && !previousLastEvent.equals(lastEvent);
97         });
98         return result;
99     }
100
101     @Override
102     protected State getNAThingProperty(String channelId) {
103         Optional<NAWelcomeEvent> lastEvt = getLastEvent();
104         switch (channelId) {
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);
124                 } else {
125                     return UnDefType.UNDEF;
126                 }
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);
131                 } else {
132                     return UnDefType.UNDEF;
133                 }
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());
147                         }
148                     }
149                 }
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);
161         }
162         return super.getNAThingProperty(channelId);
163     }
164
165     @Override
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));
171             }
172         }
173         super.triggerChannelIfRequired(channelId);
174     }
175
176     private static Set<String> findDetectedObjectTypes(Optional<NAWelcomeEvent> eventOptional) {
177         Set<String> detectedObjectTypes = new TreeSet<>();
178         if (!eventOptional.isPresent()) {
179             return detectedObjectTypes;
180         }
181
182         NAWelcomeEvent event = eventOptional.get();
183
184         if (NAWebhookCameraEvent.EventTypeEnum.MOVEMENT.toString().equals(event.getType())) {
185             if (event.getPersonId() != null) {
186                 detectedObjectTypes.add(NAWelcomeSubEvent.TypeEnum.HUMAN.name());
187             } else {
188                 Optional<NAWelcomeSubEvent.TypeEnum> detectedCategory = findDetectedCategory(event);
189                 if (detectedCategory.isPresent()) {
190                     detectedObjectTypes.add(detectedCategory.get().name());
191                 } else {
192                     detectedObjectTypes.add(NAWebhookCameraEvent.EventTypeEnum.MOVEMENT.name());
193                 }
194             }
195         }
196
197         nonNullList(event.getEventList()).forEach(subEvent -> {
198             String detectedObjectType = subEvent.getType().name();
199             detectedObjectTypes.add(detectedObjectType);
200         });
201         return detectedObjectTypes;
202     }
203
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()));
209         }
210         return Optional.empty();
211     }
212
213     private Optional<String> findEventMessage() {
214         Optional<NAWelcomeEvent> lastEvt = getLastEvent();
215         if (lastEvt.isPresent()) {
216             @Nullable
217             String message = lastEvt.get().getMessage();
218             if (message != null) {
219                 return Optional.of(message);
220             }
221
222             return lastEvt.flatMap(this::findFirstSubEvent).map(NAWelcomeSubEvent::getMessage);
223         }
224         return Optional.empty();
225     }
226
227     /**
228      * Returns the Url of the picture
229      *
230      * @return Url of the picture or null
231      */
232     protected Optional<String> findSnapshotURL() {
233         Optional<NAWelcomeEvent> lastEvt = getLastEvent();
234         if (lastEvt.isPresent()) {
235             @Nullable
236             NAWelcomeSnapshot snapshot = lastEvt.get().getSnapshot();
237             if (snapshot == null) {
238                 snapshot = lastEvt.flatMap(this::findFirstSubEvent).map(NAWelcomeSubEvent::getSnapshot).orElse(null);
239             }
240
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());
244             } else {
245                 logger.debug("Unable to build snapshot url for Home : {}", getId());
246             }
247         }
248         return Optional.empty();
249     }
250
251     @Override
252     protected Optional<Integer> getDataTimestamp() {
253         Integer timestamp = dataTimeStamp;
254         return timestamp != null ? Optional.of(timestamp) : Optional.empty();
255     }
256
257     private State getPlaceInfo(Function<NAWelcomePlace, String> infoGetFunction) {
258         return getDevice().map(d -> toStringType(infoGetFunction.apply(d.getPlace()))).orElse(UnDefType.UNDEF);
259     }
260
261     private Optional<NAWelcomeSubEvent> findFirstSubEvent(NAWelcomeEvent event) {
262         return Optional.ofNullable(event).map(NAWelcomeEvent::getEventList)
263                 .flatMap(subEvents -> nonNullStream(subEvents).findFirst());
264     }
265
266     private Optional<NAWelcomeEvent> getLastEvent() {
267         NAWelcomeEvent evt = lastEvent;
268         return evt != null ? Optional.of(evt) : Optional.empty();
269     }
270 }