[netatmo] Fix doorbell channels flapping (#17367)

* Adressing issue #13002
* Second row of url validity check
* Take snapshot expiration in account

Signed-off-by: Gaël L'hopital <gael@lhopital.org>
This commit is contained in:
Gaël L'hopital 2024-09-09 19:48:06 +02:00 committed by GitHub
parent e4ab0e9bb4
commit 27e08879f6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 47 additions and 25 deletions

View File

@ -90,7 +90,7 @@ public class ChannelGroup {
groupTypes.stream().map(NetatmoThingTypeProvider::toGroupName).collect(Collectors.toSet())); groupTypes.stream().map(NetatmoThingTypeProvider::toGroupName).collect(Collectors.toSet()));
} catch (ReflectiveOperationException e) { } catch (ReflectiveOperationException e) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
"Error creating or initializing helper class : %s".formatted(e.getMessage())); "Error creating or initializing helper class: %s".formatted(e.getMessage()));
} }
} }
} }

View File

@ -38,6 +38,10 @@ public class HomeEvent extends Event {
} }
private record Snapshot(String url, ZonedDateTime expiresAt) { private record Snapshot(String url, ZonedDateTime expiresAt) {
// If the snapshot is expired we consider it as not available, so do not provide the url
public @Nullable String url() {
return expiresAt.isAfter(ZonedDateTime.now().withZoneSameInstant(expiresAt.getZone())) ? url : null;
}
} }
private ZonedDateTime time = ZonedDateTime.now(); private ZonedDateTime time = ZonedDateTime.now();

View File

@ -27,7 +27,6 @@ import java.util.Collection;
import java.util.Deque; import java.util.Deque;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
@ -183,8 +182,9 @@ public class ApiBridgeHandler extends BaseBridgeHandler {
updateStatus(ThingStatus.ONLINE); updateStatus(ThingStatus.ONLINE);
getThing().getThings().stream().filter(Thing::isEnabled).map(Thing::getHandler).filter(Objects::nonNull) getThing().getThings().stream().filter(Thing::isEnabled).map(Thing::getHandler)
.map(CommonInterface.class::cast).forEach(CommonInterface::expireData); .filter(CommonInterface.class::isInstance).map(CommonInterface.class::cast)
.forEach(CommonInterface::expireData);
} }
private boolean authenticate(@Nullable String code, @Nullable String redirectUri) { private boolean authenticate(@Nullable String code, @Nullable String redirectUri) {
@ -353,7 +353,7 @@ public class ApiBridgeHandler extends BaseBridgeHandler {
throw exception; throw exception;
} catch (NetatmoException e) { } catch (NetatmoException e) {
if (e.getStatusCode() == ServiceError.MAXIMUM_USAGE_REACHED) { if (e.getStatusCode() == ServiceError.MAXIMUM_USAGE_REACHED) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "@text/maximum-usage-reached");
prepareReconnection(null, null); prepareReconnection(null, null);
} }
throw e; throw e;

View File

@ -37,6 +37,7 @@ import org.openhab.binding.netatmo.internal.handler.CommonInterface;
import org.openhab.binding.netatmo.internal.handler.channelhelper.CameraChannelHelper; import org.openhab.binding.netatmo.internal.handler.channelhelper.CameraChannelHelper;
import org.openhab.binding.netatmo.internal.handler.channelhelper.ChannelHelper; import org.openhab.binding.netatmo.internal.handler.channelhelper.ChannelHelper;
import org.openhab.binding.netatmo.internal.providers.NetatmoDescriptionProvider; import org.openhab.binding.netatmo.internal.providers.NetatmoDescriptionProvider;
import org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils;
import org.openhab.core.config.core.Configuration; import org.openhab.core.config.core.Configuration;
import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.OnOffType;
import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.ChannelUID;
@ -96,9 +97,11 @@ public class CameraCapability extends HomeSecurityThingCapability {
eventHelper.setUrls(newVpnUrl, localUrl); eventHelper.setUrls(newVpnUrl, localUrl);
} }
vpnUrl = newVpnUrl; vpnUrl = newVpnUrl;
if (!SdCardStatus.SD_CARD_WORKING.equals(newData.getSdStatus()) if (!SdCardStatus.SD_CARD_WORKING.equals(newData.getSdStatus())) {
|| !AlimentationStatus.ALIM_CORRECT_POWER.equals(newData.getAlimStatus())) { statusReason = newData.getSdStatus().toString();
statusReason = "%s, %s".formatted(newData.getSdStatus(), newData.getAlimStatus()); }
if (!AlimentationStatus.ALIM_CORRECT_POWER.equals(newData.getAlimStatus())) {
statusReason = newData.getAlimStatus().toString();
} }
} }
@ -125,12 +128,10 @@ public class CameraCapability extends HomeSecurityThingCapability {
private void updateSubGroup(WebhookEvent event, String group) { private void updateSubGroup(WebhookEvent event, String group) {
handler.updateState(group, CHANNEL_EVENT_TYPE, toStringType(event.getEventType())); handler.updateState(group, CHANNEL_EVENT_TYPE, toStringType(event.getEventType()));
handler.updateState(group, CHANNEL_EVENT_TIME, toDateTimeType(event.getTime())); handler.updateState(group, CHANNEL_EVENT_TIME, toDateTimeType(event.getTime()));
handler.updateState(group, CHANNEL_EVENT_SNAPSHOT, toRawType(event.getSnapshotUrl())); updatePictureIfUrlPresent(event.getSnapshotUrl(), group, CHANNEL_EVENT_SNAPSHOT, CHANNEL_EVENT_SNAPSHOT_URL);
handler.updateState(group, CHANNEL_EVENT_SNAPSHOT_URL, toStringType(event.getSnapshotUrl())); updatePictureIfUrlPresent(event.getVignetteUrl(), group, CHANNEL_EVENT_VIGNETTE, CHANNEL_EVENT_VIGNETTE_URL);
handler.updateState(group, CHANNEL_EVENT_VIGNETTE, toRawType(event.getVignetteUrl())); handler.updateState(group, CHANNEL_EVENT_SUBTYPE, Objects.requireNonNull(
handler.updateState(group, CHANNEL_EVENT_VIGNETTE_URL, toStringType(event.getVignetteUrl())); event.getSubTypeDescription().map(ChannelTypeUtils::toStringType).orElse(UnDefType.NULL)));
handler.updateState(group, CHANNEL_EVENT_SUBTYPE,
Objects.requireNonNull(event.getSubTypeDescription().map(d -> toStringType(d)).orElse(UnDefType.NULL)));
final String message = event.getName(); final String message = event.getName();
handler.updateState(group, CHANNEL_EVENT_MESSAGE, handler.updateState(group, CHANNEL_EVENT_MESSAGE,
message == null || message.isBlank() ? UnDefType.NULL : toStringType(message)); message == null || message.isBlank() ? UnDefType.NULL : toStringType(message));
@ -139,6 +140,14 @@ public class CameraCapability extends HomeSecurityThingCapability {
handler.updateState(personChannelUID, personId); handler.updateState(personChannelUID, personId);
} }
private void updatePictureIfUrlPresent(@Nullable String snapShotUrl, String group, String pictureChannel,
String urlChannel) {
if (snapShotUrl != null) {
handler.updateState(group, pictureChannel, toRawType(snapShotUrl));
handler.updateState(group, urlChannel, toStringType(snapShotUrl));
}
}
@Override @Override
public void handleCommand(String channelName, Command command) { public void handleCommand(String channelName, Command command) {
if (command instanceof OnOffType && CHANNEL_MONITORING.equals(channelName)) { if (command instanceof OnOffType && CHANNEL_MONITORING.equals(channelName)) {

View File

@ -34,6 +34,7 @@ import org.openhab.binding.netatmo.internal.api.dto.WebhookEvent;
import org.openhab.binding.netatmo.internal.handler.CommonInterface; import org.openhab.binding.netatmo.internal.handler.CommonInterface;
import org.openhab.binding.netatmo.internal.handler.channelhelper.ChannelHelper; import org.openhab.binding.netatmo.internal.handler.channelhelper.ChannelHelper;
import org.openhab.binding.netatmo.internal.providers.NetatmoDescriptionProvider; import org.openhab.binding.netatmo.internal.providers.NetatmoDescriptionProvider;
import org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils;
import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.OnOffType;
import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.ChannelUID;
import org.openhab.core.types.Command; import org.openhab.core.types.Command;
@ -79,8 +80,8 @@ public class PersonCapability extends HomeSecurityThingCapability {
protected void updateWebhookEvent(WebhookEvent event) { protected void updateWebhookEvent(WebhookEvent event) {
super.updateWebhookEvent(event); super.updateWebhookEvent(event);
handler.updateState(GROUP_LAST_EVENT, CHANNEL_EVENT_SUBTYPE, handler.updateState(GROUP_LAST_EVENT, CHANNEL_EVENT_SUBTYPE, Objects.requireNonNull(
Objects.requireNonNull(event.getSubTypeDescription().map(d -> toStringType(d)).orElse(UnDefType.NULL))); event.getSubTypeDescription().map(ChannelTypeUtils::toStringType).orElse(UnDefType.NULL)));
final String message = event.getName(); final String message = event.getName();
handler.updateState(GROUP_LAST_EVENT, CHANNEL_EVENT_MESSAGE, handler.updateState(GROUP_LAST_EVENT, CHANNEL_EVENT_MESSAGE,

View File

@ -13,7 +13,7 @@
package org.openhab.binding.netatmo.internal.handler.channelhelper; package org.openhab.binding.netatmo.internal.handler.channelhelper;
import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*; import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
import static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.*; import static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.toStringType;
import java.util.Set; import java.util.Set;
@ -43,10 +43,10 @@ public class EventCameraChannelHelper extends EventChannelHelper {
case CHANNEL_EVENT_TYPE -> toStringType(event.getEventType()); case CHANNEL_EVENT_TYPE -> toStringType(event.getEventType());
case CHANNEL_EVENT_TIME -> new DateTimeType(event.getTime()); case CHANNEL_EVENT_TIME -> new DateTimeType(event.getTime());
case CHANNEL_EVENT_MESSAGE -> toStringType(event.getName()); case CHANNEL_EVENT_MESSAGE -> toStringType(event.getName());
case CHANNEL_EVENT_SNAPSHOT -> toRawType(event.getSnapshotUrl()); case CHANNEL_EVENT_SNAPSHOT -> checkUrlPresence(event.getSnapshotUrl(), true);
case CHANNEL_EVENT_SNAPSHOT_URL -> toStringType(event.getSnapshotUrl()); case CHANNEL_EVENT_SNAPSHOT_URL -> checkUrlPresence(event.getSnapshotUrl(), false);
case CHANNEL_EVENT_VIGNETTE -> toRawType(event.getVignetteUrl()); case CHANNEL_EVENT_VIGNETTE -> checkUrlPresence(event.getVignetteUrl(), true);
case CHANNEL_EVENT_VIGNETTE_URL -> toStringType(event.getVignetteUrl()); case CHANNEL_EVENT_VIGNETTE_URL -> checkUrlPresence(event.getVignetteUrl(), false);
default -> super.internalGetHomeEvent(channelId, groupId, event); default -> super.internalGetHomeEvent(channelId, groupId, event);
}; };
} }

View File

@ -72,17 +72,24 @@ public class EventChannelHelper extends ChannelHelper {
case CHANNEL_EVENT_CAMERA_ID -> toStringType(event.getCameraId()); case CHANNEL_EVENT_CAMERA_ID -> toStringType(event.getCameraId());
case CHANNEL_EVENT_SUBTYPE -> case CHANNEL_EVENT_SUBTYPE ->
event.getSubTypeDescription().map(ChannelTypeUtils::toStringType).orElse(UnDefType.NULL); event.getSubTypeDescription().map(ChannelTypeUtils::toStringType).orElse(UnDefType.NULL);
case CHANNEL_EVENT_SNAPSHOT -> toRawType(event.getSnapshotUrl()); case CHANNEL_EVENT_SNAPSHOT -> checkUrlPresence(event.getSnapshotUrl(), true);
case CHANNEL_EVENT_SNAPSHOT_URL -> toStringType(event.getSnapshotUrl()); case CHANNEL_EVENT_SNAPSHOT_URL -> checkUrlPresence(event.getSnapshotUrl(), false);
default -> null; default -> null;
}; };
} }
protected @Nullable State checkUrlPresence(@Nullable String url, boolean asRaw) {
return url != null ? asRaw ? toRawType(url) : toStringType(url) : null;
}
@Override @Override
protected @Nullable State internalGetHomeEvent(String channelId, @Nullable String groupId, HomeEvent event) { protected @Nullable State internalGetHomeEvent(String channelId, @Nullable String groupId, HomeEvent event) {
String videoId = event.getVideoId();
if (videoId == null) {
return null;
}
return switch (channelId) { return switch (channelId) {
case CHANNEL_EVENT_VIDEO_STATUS -> case CHANNEL_EVENT_VIDEO_STATUS -> toStringType(event.getVideoStatus());
event.getVideoId() != null ? toStringType(event.getVideoStatus()) : UnDefType.NULL;
case CHANNEL_EVENT_VIDEO_LOCAL_URL -> getStreamURL(true, event.getVideoId(), event.getVideoStatus()); case CHANNEL_EVENT_VIDEO_LOCAL_URL -> getStreamURL(true, event.getVideoId(), event.getVideoStatus());
case CHANNEL_EVENT_VIDEO_VPN_URL -> getStreamURL(false, event.getVideoId(), event.getVideoStatus()); case CHANNEL_EVENT_VIDEO_VPN_URL -> getStreamURL(false, event.getVideoId(), event.getVideoStatus());
default -> null; default -> null;

View File

@ -465,6 +465,7 @@ device-not-connected = Thing is not reachable
data-over-limit = Data seems quite old data-over-limit = Data seems quite old
request-time-out = Request timed out - will attempt reconnection later request-time-out = Request timed out - will attempt reconnection later
deserialization-unknown = Deserialization lead to an unknown code deserialization-unknown = Deserialization lead to an unknown code
maximum-usage-reached = Maximum usage reached. Will try reconnection after `reconnectInterval` seconds.
homestatus-unknown-error = Unknown error homestatus-unknown-error = Unknown error
homestatus-internal-error = Internal error homestatus-internal-error = Internal error