]> git.basschouten.com Git - openhab-addons.git/blob
1971c0308b7f73e6425a7cd8e99e36e283656fe9
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 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.nest.internal.sdm.handler;
14
15 import static org.openhab.binding.nest.internal.sdm.SDMBindingConstants.*;
16 import static org.openhab.binding.nest.internal.sdm.dto.SDMCommands.SDMGenerateCameraImageRequest.EVENT_IMAGE_VALIDITY;
17
18 import java.math.BigDecimal;
19 import java.time.Duration;
20 import java.time.ZonedDateTime;
21 import java.util.stream.Stream;
22
23 import org.eclipse.jdt.annotation.NonNullByDefault;
24 import org.eclipse.jdt.annotation.Nullable;
25 import org.openhab.binding.nest.internal.sdm.SDMBindingConstants;
26 import org.openhab.binding.nest.internal.sdm.api.SDMAPI;
27 import org.openhab.binding.nest.internal.sdm.dto.SDMCommands.SDMGenerateCameraImageRequest;
28 import org.openhab.binding.nest.internal.sdm.dto.SDMCommands.SDMGenerateCameraImageResponse;
29 import org.openhab.binding.nest.internal.sdm.dto.SDMCommands.SDMGenerateCameraImageResults;
30 import org.openhab.binding.nest.internal.sdm.dto.SDMCommands.SDMGenerateCameraRtspStreamRequest;
31 import org.openhab.binding.nest.internal.sdm.dto.SDMCommands.SDMGenerateCameraRtspStreamResponse;
32 import org.openhab.binding.nest.internal.sdm.dto.SDMCommands.SDMGenerateCameraRtspStreamResults;
33 import org.openhab.binding.nest.internal.sdm.dto.SDMEvent;
34 import org.openhab.binding.nest.internal.sdm.dto.SDMEvent.SDMDeviceEvent;
35 import org.openhab.binding.nest.internal.sdm.dto.SDMEvent.SDMResourceUpdate;
36 import org.openhab.binding.nest.internal.sdm.dto.SDMEvent.SDMResourceUpdateEvents;
37 import org.openhab.binding.nest.internal.sdm.exception.FailedSendingSDMDataException;
38 import org.openhab.binding.nest.internal.sdm.exception.InvalidSDMAccessTokenException;
39 import org.openhab.core.config.core.Configuration;
40 import org.openhab.core.i18n.TimeZoneProvider;
41 import org.openhab.core.library.types.DateTimeType;
42 import org.openhab.core.library.types.RawType;
43 import org.openhab.core.library.types.StringType;
44 import org.openhab.core.thing.Channel;
45 import org.openhab.core.thing.Thing;
46 import org.openhab.core.types.State;
47 import org.openhab.core.types.UnDefType;
48 import org.slf4j.Logger;
49 import org.slf4j.LoggerFactory;
50
51 /**
52  * The {@link SDMCameraHandler} handles state updates of SDM devices with a camera.
53  *
54  * @author Brian Higginbotham - Initial contribution
55  * @author Wouter Born - Initial contribution
56  */
57 @NonNullByDefault
58 public class SDMCameraHandler extends SDMBaseHandler {
59
60     private final Logger logger = LoggerFactory.getLogger(SDMCameraHandler.class);
61
62     private @Nullable ZonedDateTime lastChimeEventTimestamp;
63     private @Nullable ZonedDateTime lastMotionEventTimestamp;
64     private @Nullable ZonedDateTime lastPersonEventTimestamp;
65     private @Nullable ZonedDateTime lastSoundEventTimestamp;
66
67     public SDMCameraHandler(Thing thing, TimeZoneProvider timeZoneProvider) {
68         super(thing, timeZoneProvider);
69     }
70
71     private void updateLiveStreamChannels() throws FailedSendingSDMDataException, InvalidSDMAccessTokenException {
72         boolean channelLinked = Stream.of(CHANNEL_LIVE_STREAM_CURRENT_TOKEN, CHANNEL_LIVE_STREAM_EXPIRATION_TIMESTAMP,
73                 CHANNEL_LIVE_STREAM_EXTENSION_TOKEN, CHANNEL_LIVE_STREAM_URL).anyMatch(this::isLinked);
74         if (!channelLinked) {
75             logger.debug("Not updating live stream channels (channels are not linked)");
76             return;
77         }
78
79         logger.debug("Updating live stream channels");
80
81         SDMGenerateCameraRtspStreamResponse response = executeDeviceCommand(new SDMGenerateCameraRtspStreamRequest());
82         if (response == null) {
83             logger.debug("Cannot update live stream channels (empty response)");
84             return;
85         }
86
87         SDMGenerateCameraRtspStreamResults results = response.results;
88         if (results != null) {
89             updateState(CHANNEL_LIVE_STREAM_CURRENT_TOKEN, new StringType(results.streamToken));
90             updateState(CHANNEL_LIVE_STREAM_EXPIRATION_TIMESTAMP,
91                     new DateTimeType(results.expiresAt.withZoneSameInstant(timeZoneProvider.getTimeZone())));
92             updateState(CHANNEL_LIVE_STREAM_EXTENSION_TOKEN, new StringType(results.streamExtensionToken));
93             updateState(CHANNEL_LIVE_STREAM_URL, new StringType(results.streamUrls.rtspUrl));
94         }
95     }
96
97     @Override
98     public void onEvent(SDMEvent event) {
99         super.onEvent(event);
100
101         SDMResourceUpdate resourceUpdate = event.resourceUpdate;
102         if (resourceUpdate == null) {
103             logger.debug("Skipping event without resource update");
104             return;
105         }
106
107         SDMResourceUpdateEvents events = resourceUpdate.events;
108         if (events == null) {
109             logger.debug("Skipping resource update without events");
110             return;
111         }
112
113         try {
114             SDMDeviceEvent deviceEvent = events.cameraMotionEvent;
115             if (deviceEvent != null) {
116                 lastMotionEventTimestamp = updateImageChannelsForEvent(CHANNEL_MOTION_EVENT_TIMESTAMP,
117                         CHANNEL_MOTION_EVENT_IMAGE, lastMotionEventTimestamp, event.timestamp, deviceEvent);
118             }
119
120             deviceEvent = events.cameraPersonEvent;
121             if (deviceEvent != null) {
122                 lastPersonEventTimestamp = updateImageChannelsForEvent(CHANNEL_PERSON_EVENT_TIMESTAMP,
123                         CHANNEL_PERSON_EVENT_IMAGE, lastPersonEventTimestamp, event.timestamp, deviceEvent);
124             }
125
126             deviceEvent = events.cameraSoundEvent;
127             if (deviceEvent != null) {
128                 lastSoundEventTimestamp = updateImageChannelsForEvent(CHANNEL_SOUND_EVENT_TIMESTAMP,
129                         CHANNEL_SOUND_EVENT_IMAGE, lastSoundEventTimestamp, event.timestamp, deviceEvent);
130             }
131
132             deviceEvent = events.doorbellChimeEvent;
133             if (deviceEvent != null) {
134                 lastChimeEventTimestamp = updateImageChannelsForEvent(CHANNEL_CHIME_EVENT_TIMESTAMP,
135                         CHANNEL_CHIME_EVENT_IMAGE, lastChimeEventTimestamp, event.timestamp, deviceEvent);
136             }
137         } catch (FailedSendingSDMDataException | InvalidSDMAccessTokenException e) {
138             logger.warn("Handling SDM event failed for {}", thing.getUID(), e);
139         }
140     }
141
142     private @Nullable ZonedDateTime updateImageChannelsForEvent(String timeChannelName, String imageChannelName,
143             @Nullable ZonedDateTime lastEventTimestamp, ZonedDateTime eventTimestamp, SDMDeviceEvent event)
144             throws FailedSendingSDMDataException, InvalidSDMAccessTokenException {
145         boolean newerEvent = lastEventTimestamp == null || lastEventTimestamp.isBefore(eventTimestamp);
146         if (!newerEvent) {
147             logger.debug("Skipping {} channel update (more recent event already occurred)", imageChannelName);
148             return lastEventTimestamp;
149         }
150
151         if (!isLinked(imageChannelName)) {
152             logger.debug("Not downloading image for {} channel update (channel is not linked)", imageChannelName);
153         } else if (Duration.between(eventTimestamp, ZonedDateTime.now()).compareTo(EVENT_IMAGE_VALIDITY) > 0) {
154             logger.debug("Cannot download image for {} channel update (event image has expired)", imageChannelName);
155             updateState(timeChannelName, UnDefType.NULL);
156         } else {
157             BigDecimal imageWidth = null;
158             BigDecimal imageHeight = null;
159
160             Channel channel = getThing().getChannel(imageChannelName);
161             if (channel != null) {
162                 Configuration configuration = channel.getConfiguration();
163                 imageWidth = (BigDecimal) configuration.get(SDMBindingConstants.CONFIG_PROPERTY_IMAGE_WIDTH);
164                 imageHeight = (BigDecimal) configuration.get(SDMBindingConstants.CONFIG_PROPERTY_IMAGE_HEIGHT);
165             }
166
167             updateState(imageChannelName, getCameraImage(event.eventId, imageWidth, imageHeight));
168         }
169
170         updateState(timeChannelName,
171                 new DateTimeType(eventTimestamp.withZoneSameInstant(timeZoneProvider.getTimeZone())));
172
173         logger.debug("Updated {} channel and {} with image of event at {}", imageChannelName, timeChannelName,
174                 eventTimestamp);
175
176         updateLiveStreamChannels();
177
178         return eventTimestamp;
179     }
180
181     private State getCameraImage(String eventId, @Nullable BigDecimal imageWidth, @Nullable BigDecimal imageHeight)
182             throws FailedSendingSDMDataException, InvalidSDMAccessTokenException {
183         SDMGenerateCameraImageResponse response = executeDeviceCommand(new SDMGenerateCameraImageRequest(eventId));
184         if (response == null) {
185             logger.debug("Cannot get image for camera event (empty response)");
186             return UnDefType.NULL;
187         }
188
189         SDMGenerateCameraImageResults results = response.results;
190         if (results == null) {
191             logger.debug("Cannot get image for camera event (no results)");
192             return UnDefType.NULL;
193         }
194
195         SDMAPI api = getAPI();
196         if (api == null) {
197             logger.debug("Cannot get image for camera event (handler has no bridge)");
198             return UnDefType.NULL;
199         }
200
201         byte[] imageBytes = api.getCameraImage(results.url, results.token, imageWidth, imageHeight);
202         return new RawType(imageBytes, "image/jpeg");
203     }
204 }