]> git.basschouten.com Git - openhab-addons.git/blob
8fda400ce152827b9e8f2df572c577662fa85f80
[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.camera;
14
15 import static org.openhab.binding.netatmo.internal.ChannelTypeUtils.*;
16 import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
17
18 import java.io.IOException;
19 import java.util.Optional;
20
21 import org.eclipse.jdt.annotation.NonNullByDefault;
22 import org.json.JSONException;
23 import org.json.JSONObject;
24 import org.openhab.binding.netatmo.internal.ChannelTypeUtils;
25 import org.openhab.binding.netatmo.internal.handler.NetatmoModuleHandler;
26 import org.openhab.core.i18n.TimeZoneProvider;
27 import org.openhab.core.io.net.http.HttpUtil;
28 import org.openhab.core.library.types.OnOffType;
29 import org.openhab.core.thing.ChannelUID;
30 import org.openhab.core.thing.Thing;
31 import org.openhab.core.types.Command;
32 import org.openhab.core.types.State;
33 import org.openhab.core.types.UnDefType;
34 import org.slf4j.Logger;
35 import org.slf4j.LoggerFactory;
36
37 import io.swagger.client.model.NAWelcomeCamera;
38
39 /**
40  * {@link CameraHandler} is the class used to handle Camera Data
41  *
42  * @author Sven Strohschein - Initial contribution (partly moved code from NAWelcomeCameraHandler to introduce
43  *         inheritance, see NAWelcomeCameraHandler)
44  *
45  */
46 @NonNullByDefault
47 public abstract class CameraHandler extends NetatmoModuleHandler<NAWelcomeCamera> {
48
49     private static final String PING_URL_PATH = "/command/ping";
50     private static final String STATUS_CHANGE_URL_PATH = "/command/changestatus";
51     private static final String LIVE_PICTURE = "/live/snapshot_720.jpg";
52
53     private final Logger logger = LoggerFactory.getLogger(CameraHandler.class);
54
55     private Optional<CameraAddress> cameraAddress;
56
57     protected CameraHandler(Thing thing, final TimeZoneProvider timeZoneProvider) {
58         super(thing, timeZoneProvider);
59         cameraAddress = Optional.empty();
60     }
61
62     @Override
63     public void handleCommand(ChannelUID channelUID, Command command) {
64         String channelId = channelUID.getId();
65         switch (channelId) {
66             case CHANNEL_CAMERA_STATUS:
67             case CHANNEL_WELCOME_CAMERA_STATUS:
68                 if (command == OnOffType.ON) {
69                     switchVideoSurveillance(true);
70                 } else if (command == OnOffType.OFF) {
71                     switchVideoSurveillance(false);
72                 }
73                 break;
74         }
75         super.handleCommand(channelUID, command);
76     }
77
78     @Override
79     protected void updateProperties(NAWelcomeCamera moduleData) {
80         updateProperties(null, moduleData.getType());
81     }
82
83     @Override
84     protected State getNAThingProperty(String channelId) {
85         switch (channelId) {
86             case CHANNEL_CAMERA_STATUS:
87                 return getStatusState();
88             case CHANNEL_CAMERA_SDSTATUS:
89                 return getSdStatusState();
90             case CHANNEL_CAMERA_ALIMSTATUS:
91                 return getAlimStatusState();
92             case CHANNEL_CAMERA_ISLOCAL:
93                 return getIsLocalState();
94             case CHANNEL_CAMERA_LIVEPICTURE_URL:
95                 return getLivePictureURLState();
96             case CHANNEL_CAMERA_LIVEPICTURE:
97                 return getLivePictureState();
98             case CHANNEL_CAMERA_LIVESTREAM_URL:
99                 return getLiveStreamState();
100         }
101         return super.getNAThingProperty(channelId);
102     }
103
104     protected State getStatusState() {
105         return getModule().map(m -> toOnOffType(m.getStatus())).orElse(UnDefType.UNDEF);
106     }
107
108     protected State getSdStatusState() {
109         return getModule().map(m -> toOnOffType(m.getSdStatus())).orElse(UnDefType.UNDEF);
110     }
111
112     protected State getAlimStatusState() {
113         return getModule().map(m -> toOnOffType(m.getAlimStatus())).orElse(UnDefType.UNDEF);
114     }
115
116     protected State getIsLocalState() {
117         return getModule().map(m -> toOnOffType(m.isIsLocal())).orElse(UnDefType.UNDEF);
118     }
119
120     protected State getLivePictureURLState() {
121         return getLivePictureURL().map(ChannelTypeUtils::toStringType).orElse(UnDefType.UNDEF);
122     }
123
124     protected State getLivePictureState() {
125         Optional<String> livePictureURL = getLivePictureURL();
126         return livePictureURL.isPresent() ? toRawType(livePictureURL.get()) : UnDefType.UNDEF;
127     }
128
129     protected State getLiveStreamState() {
130         return getLiveStreamURL().map(ChannelTypeUtils::toStringType).orElse(UnDefType.UNDEF);
131     }
132
133     /**
134      * Get the url for the live snapshot
135      *
136      * @return Url of the live snapshot
137      */
138     private Optional<String> getLivePictureURL() {
139         return getVpnUrl().map(u -> u += LIVE_PICTURE);
140     }
141
142     /**
143      * Get the url for the live stream depending wether local or not
144      *
145      * @return Url of the live stream
146      */
147     private Optional<String> getLiveStreamURL() {
148         Optional<String> result = getVpnUrl();
149         if (!result.isPresent()) {
150             return Optional.empty();
151         }
152
153         StringBuilder resultStringBuilder = new StringBuilder(result.get());
154         resultStringBuilder.append("/live/index");
155         if (isLocal()) {
156             resultStringBuilder.append("_local");
157         }
158         resultStringBuilder.append(".m3u8");
159         return Optional.of(resultStringBuilder.toString());
160     }
161
162     private Optional<String> getVpnUrl() {
163         return getModule().map(NAWelcomeCamera::getVpnUrl);
164     }
165
166     public Optional<String> getStreamURL(String videoId) {
167         Optional<String> result = getVpnUrl();
168         if (!result.isPresent()) {
169             return Optional.empty();
170         }
171
172         StringBuilder resultStringBuilder = new StringBuilder(result.get());
173         resultStringBuilder.append("/vod/");
174         resultStringBuilder.append(videoId);
175         resultStringBuilder.append("/index");
176         if (isLocal()) {
177             resultStringBuilder.append("_local");
178         }
179         resultStringBuilder.append(".m3u8");
180         return Optional.of(resultStringBuilder.toString());
181     }
182
183     private boolean isLocal() {
184         return getModule().map(NAWelcomeCamera::isIsLocal).orElse(false);
185     }
186
187     private void switchVideoSurveillance(boolean isOn) {
188         Optional<String> localCameraURL = getLocalCameraURL();
189         if (localCameraURL.isPresent()) {
190             String url = localCameraURL.get() + STATUS_CHANGE_URL_PATH + "?status=";
191             if (isOn) {
192                 url += "on";
193             } else {
194                 url += "off";
195             }
196             executeGETRequest(url);
197
198             invalidateParentCacheAndRefresh();
199         }
200     }
201
202     protected Optional<String> getLocalCameraURL() {
203         Optional<String> vpnURLOptional = getVpnUrl();
204         Optional<CameraAddress> address = cameraAddress;
205         if (vpnURLOptional.isPresent()) {
206             final String vpnURL = vpnURLOptional.get();
207
208             // The local address is (re-)requested when it wasn't already determined or when the vpn address was
209             // changed.
210             if (!address.isPresent() || address.get().isVpnURLChanged(vpnURL)) {
211                 Optional<JSONObject> json = executeGETRequestJSON(vpnURL + PING_URL_PATH);
212                 address = json.map(j -> j.optString("local_url", null))
213                         .map(localURL -> new CameraAddress(vpnURL, localURL));
214                 cameraAddress = address;
215             }
216         }
217         return address.map(CameraAddress::getLocalURL);
218     }
219
220     private Optional<JSONObject> executeGETRequestJSON(String url) {
221         try {
222             return executeGETRequest(url).map(JSONObject::new);
223         } catch (JSONException e) {
224             logger.warn("Error on parsing the content as JSON!", e);
225         }
226         return Optional.empty();
227     }
228
229     protected Optional<String> executeGETRequest(String url) {
230         try {
231             String content = HttpUtil.executeUrl("GET", url, 5000);
232             if (content != null && !content.isEmpty()) {
233                 return Optional.of(content);
234             }
235         } catch (IOException e) {
236             logger.warn("Error on accessing local camera url!", e);
237         }
238         return Optional.empty();
239     }
240
241     @Override
242     protected boolean isReachable() {
243         Optional<NAWelcomeCamera> module = getModule();
244         return module.isPresent() ? !"disconnected".equalsIgnoreCase(module.get().getStatus()) : false;
245     }
246 }