]> git.basschouten.com Git - openhab-addons.git/blob
9a269e75a540762e0ec1bc63c45bbfc45ad264bc
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2020 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.ChannelTypeUtils.*;
16 import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
17
18 import java.util.Calendar;
19 import java.util.Comparator;
20 import java.util.Optional;
21 import java.util.Set;
22 import java.util.TreeSet;
23 import java.util.function.Function;
24
25 import org.eclipse.jdt.annotation.NonNullByDefault;
26 import org.eclipse.jdt.annotation.Nullable;
27 import org.openhab.binding.netatmo.internal.ChannelTypeUtils;
28 import org.openhab.binding.netatmo.internal.camera.CameraHandler;
29 import org.openhab.binding.netatmo.internal.handler.AbstractNetatmoThingHandler;
30 import org.openhab.binding.netatmo.internal.handler.NetatmoDeviceHandler;
31 import org.openhab.binding.netatmo.internal.webhook.NAWebhookCameraEvent;
32 import org.openhab.core.i18n.TimeZoneProvider;
33 import org.openhab.core.library.types.DecimalType;
34 import org.openhab.core.library.types.StringType;
35 import org.openhab.core.thing.Thing;
36 import org.openhab.core.types.State;
37 import org.openhab.core.types.UnDefType;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
40
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;
46
47 /**
48  * {@link NAWelcomeHomeHandler} is the class used to handle the Welcome Home Data
49  *
50  * @author GaĆ«l L'hopital - Initial contribution
51  * @author Ing. Peter Weiss - Welcome camera implementation
52  *
53  */
54 @NonNullByDefault
55 public class NAWelcomeHomeHandler extends NetatmoDeviceHandler<NAWelcomeHome> {
56     private final Logger logger = LoggerFactory.getLogger(NAWelcomeHomeHandler.class);
57
58     private int iPersons = -1;
59     private int iUnknowns = -1;
60     private @Nullable NAWelcomeEvent lastEvent;
61     private boolean isNewLastEvent;
62     private @Nullable Integer dataTimeStamp;
63
64     public NAWelcomeHomeHandler(Thing thing, final TimeZoneProvider timeZoneProvider) {
65         super(thing, timeZoneProvider);
66     }
67
68     @Override
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));
77
78             // Check how many persons are at home
79             iPersons = 0;
80             iUnknowns = 0;
81
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);
87                 } else {
88                     iUnknowns += person.getOutOfSight() ? 0 : 1;
89                 }
90             });
91
92             NAWelcomeEvent previousLastEvent = lastEvent;
93             lastEvent = home.getEvents().stream().max(Comparator.comparingInt(NAWelcomeEvent::getTime)).orElse(null);
94             isNewLastEvent = previousLastEvent != null && !previousLastEvent.equals(lastEvent);
95         });
96         return result;
97     }
98
99     @Override
100     protected State getNAThingProperty(String channelId) {
101         Optional<NAWelcomeEvent> lastEvt = getLastEvent();
102         switch (channelId) {
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);
122                 } else {
123                     return UnDefType.UNDEF;
124                 }
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);
129                 } else {
130                     return UnDefType.UNDEF;
131                 }
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());
145                         }
146                     }
147                 }
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);
159         }
160         return super.getNAThingProperty(channelId);
161     }
162
163     @Override
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));
169             }
170         }
171         super.triggerChannelIfRequired(channelId);
172     }
173
174     private static Set<String> findDetectedObjectTypes(Optional<NAWelcomeEvent> eventOptional) {
175         Set<String> detectedObjectTypes = new TreeSet<>();
176         if (!eventOptional.isPresent()) {
177             return detectedObjectTypes;
178         }
179
180         NAWelcomeEvent event = eventOptional.get();
181
182         if (NAWebhookCameraEvent.EventTypeEnum.MOVEMENT.toString().equals(event.getType())) {
183             if (event.getPersonId() != null) {
184                 detectedObjectTypes.add(NAWelcomeSubEvent.TypeEnum.HUMAN.name());
185             } else {
186                 Optional<NAWelcomeSubEvent.TypeEnum> detectedCategory = findDetectedCategory(event);
187                 if (detectedCategory.isPresent()) {
188                     detectedObjectTypes.add(detectedCategory.get().name());
189                 } else {
190                     detectedObjectTypes.add(NAWebhookCameraEvent.EventTypeEnum.MOVEMENT.name());
191                 }
192             }
193         }
194
195         event.getEventList().forEach(subEvent -> {
196             String detectedObjectType = subEvent.getType().name();
197             detectedObjectTypes.add(detectedObjectType);
198         });
199         return detectedObjectTypes;
200     }
201
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()));
207         }
208         return Optional.empty();
209     }
210
211     private Optional<String> findEventMessage() {
212         Optional<NAWelcomeEvent> lastEvt = getLastEvent();
213         if (lastEvt.isPresent()) {
214             @Nullable
215             String message = lastEvt.get().getMessage();
216             if (message != null) {
217                 return Optional.of(message);
218             }
219
220             return lastEvt.flatMap(this::findFirstSubEvent).map(NAWelcomeSubEvent::getMessage);
221         }
222         return Optional.empty();
223     }
224
225     /**
226      * Returns the Url of the picture
227      *
228      * @return Url of the picture or null
229      */
230     protected Optional<String> findSnapshotURL() {
231         Optional<NAWelcomeEvent> lastEvt = getLastEvent();
232         if (lastEvt.isPresent()) {
233             @Nullable
234             NAWelcomeSnapshot snapshot = lastEvt.get().getSnapshot();
235             if (snapshot == null) {
236                 snapshot = lastEvt.flatMap(this::findFirstSubEvent).map(NAWelcomeSubEvent::getSnapshot).orElse(null);
237             }
238
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());
242             } else {
243                 logger.debug("Unable to build snapshot url for Home : {}", getId());
244             }
245         }
246         return Optional.empty();
247     }
248
249     @Override
250     protected Optional<Integer> getDataTimestamp() {
251         Integer timestamp = dataTimeStamp;
252         return timestamp != null ? Optional.of(timestamp) : Optional.empty();
253     }
254
255     private State getPlaceInfo(Function<NAWelcomePlace, String> infoGetFunction) {
256         return getDevice().map(d -> toStringType(infoGetFunction.apply(d.getPlace()))).orElse(UnDefType.UNDEF);
257     }
258
259     private Optional<NAWelcomeSubEvent> findFirstSubEvent(NAWelcomeEvent event) {
260         return Optional.ofNullable(event).map(NAWelcomeEvent::getEventList)
261                 .flatMap(subEvents -> subEvents.stream().findFirst());
262     }
263
264     private Optional<NAWelcomeEvent> getLastEvent() {
265         NAWelcomeEvent evt = lastEvent;
266         return evt != null ? Optional.of(evt) : Optional.empty();
267     }
268 }