[netatmo] Fix Live Picture not always available (#16679)

* Adressing issue on live picture

Signed-off-by: gael@lhopital.org <gael@lhopital.org>
Signed-off-by: root <gael@lhopital.org>
This commit is contained in:
Gaël L'hopital 2024-04-29 13:30:00 +02:00 committed by GitHub
parent cf21184c1b
commit 5f7282b21b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 52 additions and 35 deletions

View File

@ -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:**

View File

@ -63,7 +63,7 @@ public class WebhookEvent extends Event {
@Override
public @Nullable String getPersonId() {
return persons.size() > 0 ? persons.keySet().iterator().next() : null;
return persons.isEmpty() ? null : persons.keySet().iterator().next();
}
@Override

View File

@ -40,6 +40,7 @@ import org.openhab.core.config.core.Configuration;
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 @@ public class CameraCapability extends HomeSecurityThingCapability {
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);
}

View File

@ -36,7 +36,7 @@ import org.openhab.core.types.UnDefType;
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 class CameraChannelHelper extends ChannelHelper {
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) : "");
}
}

View File

@ -40,6 +40,7 @@ import org.openhab.core.types.UnDefType;
*/
@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 class ChannelTypeUtils {
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;
}