diff --git a/bundles/org.openhab.binding.netatmo/README.md b/bundles/org.openhab.binding.netatmo/README.md index 9fa7a8fac7a6e..fb80901a2d617 100644 --- a/bundles/org.openhab.binding.netatmo/README.md +++ b/bundles/org.openhab.binding.netatmo/README.md @@ -528,7 +528,7 @@ Warnings: | status | monitoring | Switch | Read-write | State of the camera (video surveillance on/off) | | status | sd-card | String | Read-only | State of the SD card | | status | alim | String | Read-only | State of the power connector | -| live | picture | Image | Read-only | Camera Live Snapshot | +| live | picture (**) | Image | Read-only | Camera Live Snapshot | | live | local-picture-url | String | Read-only | Local Url of the live snapshot for this camera | | live | vpn-picture-url | String | Read-only | Url of the live snapshot for this camera through Netatmo VPN. | | live | local-stream-url (*) | String | Read-only | Local Url of the live stream for this camera (accessible if openhab server and camera are located on the same lan. | @@ -547,6 +547,7 @@ Warnings: | last-event | person-id | String | Read-only | Id of the person the event is about (if any) | (*) This channel is configurable : low, poor, high. +(**) This channel handles the REFRESH command for on demand update. **Supported channels for the Presence Camera thing:** diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/WebhookEvent.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/WebhookEvent.java index 9d492207ce0f0..2eefa7bf7cbb4 100644 --- a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/WebhookEvent.java +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/WebhookEvent.java @@ -63,7 +63,7 @@ public ZonedDateTime getTime() { @Override public @Nullable String getPersonId() { - return persons.size() > 0 ? persons.keySet().iterator().next() : null; + return persons.isEmpty() ? null : persons.keySet().iterator().next(); } @Override diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/CameraCapability.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/CameraCapability.java index 2debb89e65f67..d110659c86823 100644 --- a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/CameraCapability.java +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/CameraCapability.java @@ -40,6 +40,7 @@ import org.openhab.core.library.types.OnOffType; import org.openhab.core.thing.ChannelUID; import org.openhab.core.types.Command; +import org.openhab.core.types.RefreshType; import org.openhab.core.types.State; import org.openhab.core.types.StateOption; import org.openhab.core.types.UnDefType; @@ -141,6 +142,9 @@ private void updateSubGroup(WebhookEvent event, String group) { public void handleCommand(String channelName, Command command) { if (command instanceof OnOffType && CHANNEL_MONITORING.equals(channelName)) { getSecurityCapability().ifPresent(cap -> cap.changeStatus(localUrl, OnOffType.ON.equals(command))); + } else if (command instanceof RefreshType && CHANNEL_LIVEPICTURE.equals(channelName)) { + handler.updateState(GROUP_CAM_LIVE, CHANNEL_LIVEPICTURE, + toRawType(cameraHelper.getLivePictureURL(localUrl != null, true))); } else { super.handleCommand(channelName, command); } diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/CameraChannelHelper.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/CameraChannelHelper.java index 5e63cf85d790f..21dde6e409fff 100644 --- a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/CameraChannelHelper.java +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/CameraChannelHelper.java @@ -36,7 +36,7 @@ public class CameraChannelHelper extends ChannelHelper { private static final String QUALITY_CONF_ENTRY = "quality"; private static final String LIVE_PICTURE = "/live/snapshot_720.jpg"; - private boolean isLocal; + private @Nullable String vpnUrl; private @Nullable String localUrl; @@ -44,56 +44,66 @@ public CameraChannelHelper(Set providedGroups) { super(providedGroups); } - public void setUrls(String vpnUrl, @Nullable String localUrl) { + public void setUrls(@Nullable String vpnUrl, @Nullable String localUrl) { this.localUrl = localUrl; this.vpnUrl = vpnUrl; - this.isLocal = localUrl != null; - } - - public @Nullable String getLocalURL() { - return localUrl; } @Override protected @Nullable State internalGetProperty(String channelId, NAThing naThing, Configuration config) { if (naThing instanceof HomeStatusModule camera) { - boolean isMonitoring = OnOffType.ON.equals(camera.getMonitoring()); - switch (channelId) { - case CHANNEL_MONITORING: - return camera.getMonitoring(); - case CHANNEL_SD_CARD: - return toStringType(camera.getSdStatus()); - case CHANNEL_ALIM_STATUS: - return toStringType(camera.getAlimStatus()); - case CHANNEL_LIVEPICTURE_VPN_URL: - return toStringType(getLivePictureURL(false, isMonitoring)); - case CHANNEL_LIVEPICTURE_LOCAL_URL: - return toStringType(getLivePictureURL(true, isMonitoring)); - case CHANNEL_LIVEPICTURE: - return toRawType(getLivePictureURL(isLocal, isMonitoring)); - case CHANNEL_LIVESTREAM_VPN_URL: - return getLiveStreamURL(false, (String) config.get(QUALITY_CONF_ENTRY), isMonitoring); - case CHANNEL_LIVESTREAM_LOCAL_URL: - return getLiveStreamURL(true, (String) config.get(QUALITY_CONF_ENTRY), isMonitoring); - } + return switch (channelId) { + case CHANNEL_MONITORING -> camera.getMonitoring(); + case CHANNEL_SD_CARD -> toStringType(camera.getSdStatus()); + case CHANNEL_ALIM_STATUS -> toStringType(camera.getAlimStatus()); + default -> liveChannels(channelId, (String) config.get(QUALITY_CONF_ENTRY), camera, + OnOffType.ON.equals(camera.getMonitoring()), localUrl != null); + }; } return null; } - private @Nullable String getLivePictureURL(boolean local, boolean isMonitoring) { - String url = local ? localUrl : vpnUrl; - if (!isMonitoring || (local && !isLocal) || url == null) { + public @Nullable String getLivePictureURL(boolean local, boolean isMonitoring) { + String url = getUrl(local); + if (!isMonitoring || url == null) { return null; } return "%s%s".formatted(url, LIVE_PICTURE); } + private @Nullable State liveChannels(String channelId, String qualityConf, HomeStatusModule camera, + boolean isMonitoring, boolean isLocal) { + if (vpnUrl == null) { + setUrls(camera.getVpnUrl(), localUrl); + } + return switch (channelId) { + case CHANNEL_LIVESTREAM_LOCAL_URL -> + isLocal ? getLiveStreamURL(true, qualityConf, isMonitoring) : UnDefType.NULL; + case CHANNEL_LIVEPICTURE_LOCAL_URL -> + isLocal ? toStringType(getLivePictureURL(true, isMonitoring)) : UnDefType.NULL; + case CHANNEL_LIVESTREAM_VPN_URL -> getLiveStreamURL(false, qualityConf, isMonitoring); + case CHANNEL_LIVEPICTURE_VPN_URL -> toStringType(getLivePictureURL(false, isMonitoring)); + case CHANNEL_LIVEPICTURE -> { + State result = toRawType(getLivePictureURL(isLocal, isMonitoring)); + if (UnDefType.NULL.equals(result) && isLocal) {// If local read is unsuccessfull, try the VPN version + result = toRawType(getLivePictureURL(false, isMonitoring)); + } + yield result; + } + default -> null; + }; + } + + private @Nullable String getUrl(boolean local) { + return local ? localUrl : vpnUrl; + } + private State getLiveStreamURL(boolean local, @Nullable String configQual, boolean isMonitoring) { - String url = local ? localUrl : vpnUrl; - if (!isMonitoring || (local && !isLocal) || url == null) { + String url = getUrl(local); + if (!isMonitoring || url == null) { return UnDefType.NULL; } String finalQual = configQual != null ? configQual : "poor"; - return toStringType("%s/live/%s", url, local ? "files/%s/index.m3u8".formatted(finalQual) : "index.m3u8"); + return toStringType("%s/live/%sindex.m3u8", url, local ? "files/%s/".formatted(finalQual) : ""); } } diff --git a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/utils/ChannelTypeUtils.java b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/utils/ChannelTypeUtils.java index 30f425fa2a12a..8fe4c3bf09efe 100644 --- a/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/utils/ChannelTypeUtils.java +++ b/bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/utils/ChannelTypeUtils.java @@ -40,6 +40,7 @@ */ @NonNullByDefault public class ChannelTypeUtils { + private static final int DEFAULT_TIMEOUT_MS = 30000; public static @Nullable QuantityType commandToQuantity(Command command, MeasureClass measureClass) { Measure measureDef = measureClass.measureDefinition; @@ -90,7 +91,8 @@ public static State toQuantityType(@Nullable Number value, Unit unit) { public static State toRawType(@Nullable String pictureUrl) { if (pictureUrl != null) { - RawType picture = HttpUtil.downloadImage(pictureUrl); + // Retrieving local picture can be quite long then extend the timeout. + RawType picture = HttpUtil.downloadImage(pictureUrl, DEFAULT_TIMEOUT_MS); if (picture != null) { return picture; }