-
-
Notifications
You must be signed in to change notification settings - Fork 3.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[netatmo] Support for switching video surveillance on/off (#7938) #7968
Changes from all commits
78699f9
ae6b891
66abc11
4f2cd51
bd9cfc1
de62002
ad272b7
76c47b8
2318dce
f875381
8d35063
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,15 +16,26 @@ | |
import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*; | ||
|
||
import org.eclipse.jdt.annotation.NonNull; | ||
import org.eclipse.jdt.annotation.NonNullByDefault; | ||
import org.eclipse.smarthome.core.i18n.TimeZoneProvider; | ||
import org.eclipse.smarthome.core.library.types.StringType; | ||
import org.eclipse.smarthome.core.library.types.OnOffType; | ||
import org.eclipse.smarthome.core.thing.ChannelUID; | ||
import org.eclipse.smarthome.core.thing.Thing; | ||
import org.eclipse.smarthome.core.types.Command; | ||
import org.eclipse.smarthome.core.types.State; | ||
import org.eclipse.smarthome.core.types.UnDefType; | ||
import org.eclipse.smarthome.io.net.http.HttpUtil; | ||
import org.json.JSONException; | ||
import org.json.JSONObject; | ||
import org.openhab.binding.netatmo.internal.ChannelTypeUtils; | ||
import org.openhab.binding.netatmo.internal.handler.NetatmoModuleHandler; | ||
|
||
import io.swagger.client.model.NAWelcomeCamera; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
import java.io.IOException; | ||
import java.util.Optional; | ||
|
||
/** | ||
* {@link CameraHandler} is the class used to handle Camera Data | ||
|
@@ -33,20 +44,42 @@ | |
* NAWelcomeCameraHandler) | ||
* | ||
*/ | ||
@NonNullByDefault | ||
public abstract class CameraHandler extends NetatmoModuleHandler<NAWelcomeCamera> { | ||
|
||
private static final String PING_URL_PATH = "/command/ping"; | ||
private static final String STATUS_CHANGE_URL_PATH = "/command/changestatus"; | ||
private static final String LIVE_PICTURE = "/live/snapshot_720.jpg"; | ||
|
||
protected CameraHandler(@NonNull Thing thing, final TimeZoneProvider timeZoneProvider) { | ||
private final Logger logger = LoggerFactory.getLogger(CameraHandler.class); | ||
|
||
private Optional<CameraAddress> cameraAddress = Optional.empty(); | ||
|
||
protected CameraHandler(Thing thing, final TimeZoneProvider timeZoneProvider) { | ||
super(thing, timeZoneProvider); | ||
} | ||
|
||
@Override | ||
public void handleCommand(ChannelUID channelUID, Command command) { | ||
String channelId = channelUID.getId(); | ||
switch (channelId) { | ||
case CHANNEL_CAMERA_STATUS: | ||
case CHANNEL_WELCOME_CAMERA_STATUS: | ||
if(command == OnOffType.ON) { | ||
switchVideoSurveillance(true); | ||
} else if(command == OnOffType.OFF) { | ||
switchVideoSurveillance(false); | ||
} | ||
break; | ||
} | ||
super.handleCommand(channelUID, command); | ||
} | ||
|
||
@Override | ||
protected void updateProperties(NAWelcomeCamera moduleData) { | ||
updateProperties(null, moduleData.getType()); | ||
} | ||
|
||
@SuppressWarnings("null") | ||
@Override | ||
protected State getNAThingProperty(@NonNull String channelId) { | ||
switch (channelId) { | ||
|
@@ -69,93 +102,136 @@ protected State getNAThingProperty(@NonNull String channelId) { | |
} | ||
|
||
protected State getStatusState() { | ||
return module != null ? toOnOffType(module.getStatus()) : UnDefType.UNDEF; | ||
return getModule().map(m -> toOnOffType(m.getStatus())).orElse(UnDefType.UNDEF); | ||
} | ||
|
||
protected State getSdStatusState() { | ||
return module != null ? toOnOffType(module.getSdStatus()) : UnDefType.UNDEF; | ||
return getModule().map(m -> toOnOffType(m.getSdStatus())).orElse(UnDefType.UNDEF); | ||
} | ||
|
||
protected State getAlimStatusState() { | ||
return module != null ? toOnOffType(module.getAlimStatus()) : UnDefType.UNDEF; | ||
return getModule().map(m -> toOnOffType(m.getAlimStatus())).orElse(UnDefType.UNDEF); | ||
} | ||
|
||
protected State getIsLocalState() { | ||
return module != null ? toOnOffType(module.getIsLocal()) : UnDefType.UNDEF; | ||
return getModule().map(m -> toOnOffType(m.getIsLocal())).orElse(UnDefType.UNDEF); | ||
} | ||
|
||
protected State getLivePictureURLState() { | ||
String livePictureURL = getLivePictureURL(); | ||
return livePictureURL == null ? UnDefType.UNDEF : toStringType(livePictureURL); | ||
return getLivePictureURL().map(ChannelTypeUtils::toStringType).orElse(UnDefType.UNDEF); | ||
} | ||
|
||
protected State getLivePictureState() { | ||
String livePictureURL = getLivePictureURL(); | ||
return livePictureURL == null ? UnDefType.UNDEF : HttpUtil.downloadImage(livePictureURL); | ||
Optional<String> livePictureURL = getLivePictureURL(); | ||
return livePictureURL.isPresent() ? HttpUtil.downloadImage(livePictureURL.get()) : UnDefType.UNDEF; | ||
} | ||
|
||
protected State getLiveStreamState() { | ||
String liveStreamURL = getLiveStreamURL(); | ||
return liveStreamURL == null ? UnDefType.UNDEF : new StringType(liveStreamURL); | ||
return getLiveStreamURL().map(ChannelTypeUtils::toStringType).orElse(UnDefType.UNDEF); | ||
} | ||
|
||
/** | ||
* Get the url for the live snapshot | ||
* | ||
* @return Url of the live snapshot | ||
*/ | ||
private String getLivePictureURL() { | ||
String result = getVpnUrl(); | ||
if (result != null) { | ||
result += LIVE_PICTURE; | ||
} | ||
return result; | ||
private Optional<String> getLivePictureURL() { | ||
return getVpnUrl().map(u -> u += LIVE_PICTURE); | ||
} | ||
|
||
/** | ||
* Get the url for the live stream depending wether local or not | ||
* | ||
* @return Url of the live stream | ||
*/ | ||
private String getLiveStreamURL() { | ||
String result = getVpnUrl(); | ||
if (result == null) { | ||
return null; | ||
private Optional<String> getLiveStreamURL() { | ||
Optional<String> result = getVpnUrl(); | ||
if (!result.isPresent()) { | ||
return Optional.empty(); | ||
} | ||
|
||
StringBuilder resultStringBuilder = new StringBuilder(result); | ||
StringBuilder resultStringBuilder = new StringBuilder(result.get()); | ||
resultStringBuilder.append("/live/index"); | ||
if (isLocal()) { | ||
resultStringBuilder.append("_local"); | ||
} | ||
resultStringBuilder.append(".m3u8"); | ||
return resultStringBuilder.toString(); | ||
return Optional.of(resultStringBuilder.toString()); | ||
} | ||
|
||
@SuppressWarnings("null") | ||
protected String getVpnUrl() { | ||
return (module == null) ? null : module.getVpnUrl(); | ||
private Optional<String> getVpnUrl() { | ||
return getModule().map(NAWelcomeCamera::getVpnUrl); | ||
} | ||
|
||
public String getStreamURL(String videoId) { | ||
String result = getVpnUrl(); | ||
if (result == null) { | ||
return null; | ||
public Optional<String> getStreamURL(String videoId) { | ||
Optional<String> result = getVpnUrl(); | ||
if (!result.isPresent()) { | ||
return Optional.empty(); | ||
} | ||
|
||
StringBuilder resultStringBuilder = new StringBuilder(result); | ||
StringBuilder resultStringBuilder = new StringBuilder(result.get()); | ||
resultStringBuilder.append("/vod/"); | ||
resultStringBuilder.append(videoId); | ||
resultStringBuilder.append("/index"); | ||
if (isLocal()) { | ||
resultStringBuilder.append("_local"); | ||
} | ||
resultStringBuilder.append(".m3u8"); | ||
return resultStringBuilder.toString(); | ||
return Optional.of(resultStringBuilder.toString()); | ||
} | ||
|
||
@SuppressWarnings("null") | ||
private boolean isLocal() { | ||
return (module == null || module.getIsLocal() == null) ? false : module.getIsLocal(); | ||
return getModule().map(NAWelcomeCamera::getIsLocal).orElse(false); | ||
} | ||
|
||
private void switchVideoSurveillance(boolean isOn) { | ||
Optional<String> localCameraURL = getLocalCameraURL(); | ||
if (localCameraURL.isPresent()) { | ||
String url = localCameraURL.get() + STATUS_CHANGE_URL_PATH + "?status="; | ||
if(isOn) { | ||
url += "on"; | ||
} else { | ||
url += "off"; | ||
} | ||
executeGETRequest(url); | ||
|
||
invalidateParentCacheAndRefresh(); | ||
} | ||
} | ||
|
||
protected Optional<String> getLocalCameraURL() { | ||
Optional<String> vpnURLOptional = getVpnUrl(); | ||
if (vpnURLOptional.isPresent()) { | ||
final String vpnURL = vpnURLOptional.get(); | ||
|
||
//The local address is (re-)requested when it wasn't already determined or when the vpn address was changed. | ||
if (!cameraAddress.isPresent() || cameraAddress.get().isVpnURLChanged(vpnURL)) { | ||
Optional<JSONObject> json = executeGETRequestJSON(vpnURL + PING_URL_PATH); | ||
cameraAddress = json.map(j -> j.optString("local_url", null)) | ||
.map(localURL -> new CameraAddress(vpnURL, localURL)); | ||
} | ||
} | ||
return cameraAddress.map(CameraAddress::getLocalURL); | ||
} | ||
|
||
private Optional<JSONObject> executeGETRequestJSON(String url) { | ||
try { | ||
return executeGETRequest(url).map(JSONObject::new); | ||
} catch (JSONException e) { | ||
logger.warn("Error on parsing the content as JSON!", e); | ||
} | ||
return Optional.empty(); | ||
} | ||
|
||
protected Optional<String> executeGETRequest(String url) { | ||
try { | ||
String content = HttpUtil.executeUrl("GET", url, 5000); | ||
if (content != null && !content.isEmpty()) { | ||
return Optional.of(content); | ||
} | ||
} catch (IOException e) { | ||
logger.warn("Error on accessing local camera url!", e); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A DEBUG log will be sufficient I think. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think that such an exception should probably be elevated to the caller since I don't think that simple logging would be how all callers would like this particular issue to be handled. |
||
} | ||
return Optional.empty(); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A DEBUG log will be sufficient I think.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Debug? When this error occurs, it is an error, the functionality is broken when it occurs. That shouldn't happen. Actually warning isn't enough.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My general rule for logging levels is:
Now if a JSONException does occur here it doesn't really reflect a failure in the binding so much a failure in an external system and can be thought of more or less akin to connection loss. If you think that such a json parsing failure should be elevated above "warn" then I suggest changing the thing status to OFFLINE to reflect that.