2 * Copyright (c) 2010-2021 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.camera;
15 import static org.openhab.binding.netatmo.internal.ChannelTypeUtils.*;
16 import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
18 import java.io.IOException;
19 import java.util.Optional;
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;
37 import io.swagger.client.model.NAWelcomeCamera;
40 * {@link CameraHandler} is the class used to handle Camera Data
42 * @author Sven Strohschein - Initial contribution (partly moved code from NAWelcomeCameraHandler to introduce
43 * inheritance, see NAWelcomeCameraHandler)
47 public abstract class CameraHandler extends NetatmoModuleHandler<NAWelcomeCamera> {
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";
53 private final Logger logger = LoggerFactory.getLogger(CameraHandler.class);
55 private Optional<CameraAddress> cameraAddress;
57 protected CameraHandler(Thing thing, final TimeZoneProvider timeZoneProvider) {
58 super(thing, timeZoneProvider);
59 cameraAddress = Optional.empty();
63 public void handleCommand(ChannelUID channelUID, Command command) {
64 String channelId = channelUID.getId();
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);
75 super.handleCommand(channelUID, command);
79 protected void updateProperties(NAWelcomeCamera moduleData) {
80 updateProperties(null, moduleData.getType());
84 protected State getNAThingProperty(String 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();
101 return super.getNAThingProperty(channelId);
104 protected State getStatusState() {
105 return getModule().map(m -> toOnOffType(m.getStatus())).orElse(UnDefType.UNDEF);
108 protected State getSdStatusState() {
109 return getModule().map(m -> toOnOffType(m.getSdStatus())).orElse(UnDefType.UNDEF);
112 protected State getAlimStatusState() {
113 return getModule().map(m -> toOnOffType(m.getAlimStatus())).orElse(UnDefType.UNDEF);
116 protected State getIsLocalState() {
117 return getModule().map(m -> toOnOffType(m.isIsLocal())).orElse(UnDefType.UNDEF);
120 protected State getLivePictureURLState() {
121 return getLivePictureURL().map(ChannelTypeUtils::toStringType).orElse(UnDefType.UNDEF);
124 protected State getLivePictureState() {
125 Optional<String> livePictureURL = getLivePictureURL();
126 return livePictureURL.isPresent() ? toRawType(livePictureURL.get()) : UnDefType.UNDEF;
129 protected State getLiveStreamState() {
130 return getLiveStreamURL().map(ChannelTypeUtils::toStringType).orElse(UnDefType.UNDEF);
134 * Get the url for the live snapshot
136 * @return Url of the live snapshot
138 private Optional<String> getLivePictureURL() {
139 return getVpnUrl().map(u -> u += LIVE_PICTURE);
143 * Get the url for the live stream depending wether local or not
145 * @return Url of the live stream
147 private Optional<String> getLiveStreamURL() {
148 Optional<String> result = getVpnUrl();
149 if (!result.isPresent()) {
150 return Optional.empty();
153 StringBuilder resultStringBuilder = new StringBuilder(result.get());
154 resultStringBuilder.append("/live/index");
156 resultStringBuilder.append("_local");
158 resultStringBuilder.append(".m3u8");
159 return Optional.of(resultStringBuilder.toString());
162 private Optional<String> getVpnUrl() {
163 return getModule().map(NAWelcomeCamera::getVpnUrl);
166 public Optional<String> getStreamURL(String videoId) {
167 Optional<String> result = getVpnUrl();
168 if (!result.isPresent()) {
169 return Optional.empty();
172 StringBuilder resultStringBuilder = new StringBuilder(result.get());
173 resultStringBuilder.append("/vod/");
174 resultStringBuilder.append(videoId);
175 resultStringBuilder.append("/index");
177 resultStringBuilder.append("_local");
179 resultStringBuilder.append(".m3u8");
180 return Optional.of(resultStringBuilder.toString());
183 private boolean isLocal() {
184 return getModule().map(NAWelcomeCamera::isIsLocal).orElse(false);
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=";
196 executeGETRequest(url);
198 invalidateParentCacheAndRefresh();
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();
208 // The local address is (re-)requested when it wasn't already determined or when the vpn address was
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;
217 return address.map(CameraAddress::getLocalURL);
220 private Optional<JSONObject> executeGETRequestJSON(String url) {
222 return executeGETRequest(url).map(JSONObject::new);
223 } catch (JSONException e) {
224 logger.warn("Error on parsing the content as JSON!", e);
226 return Optional.empty();
229 protected Optional<String> executeGETRequest(String url) {
231 String content = HttpUtil.executeUrl("GET", url, 5000);
232 if (content != null && !content.isEmpty()) {
233 return Optional.of(content);
235 } catch (IOException e) {
236 logger.warn("Error on accessing local camera url!", e);
238 return Optional.empty();
242 protected boolean isReachable() {
243 Optional<NAWelcomeCamera> module = getModule();
244 return module.isPresent() ? !"disconnected".equalsIgnoreCase(module.get().getStatus()) : false;