[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>
Signed-off-by: Ciprian Pascu <contact@ciprianpascu.ro>
This commit is contained in:
Gaël L'hopital 2024-09-09 19:48:06 +02:00 committed by Ciprian Pascu
parent 233e3ed64b
commit 0334e86c81
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()));
} catch (ReflectiveOperationException e) {
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) {
// 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();

View File

@ -27,7 +27,6 @@ import java.util.Collection;
import java.util.Deque;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ExecutionException;
@ -183,8 +182,9 @@ public class ApiBridgeHandler extends BaseBridgeHandler {
updateStatus(ThingStatus.ONLINE);
getThing().getThings().stream().filter(Thing::isEnabled).map(Thing::getHandler).filter(Objects::nonNull)
.map(CommonInterface.class::cast).forEach(CommonInterface::expireData);
getThing().getThings().stream().filter(Thing::isEnabled).map(Thing::getHandler)
.filter(CommonInterface.class::isInstance).map(CommonInterface.class::cast)
.forEach(CommonInterface::expireData);
}
private boolean authenticate(@Nullable String code, @Nullable String redirectUri) {
@ -353,7 +353,7 @@ public class ApiBridgeHandler extends BaseBridgeHandler {
throw exception;
} catch (NetatmoException e) {
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);
}
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.ChannelHelper;
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.library.types.OnOffType;
import org.openhab.core.thing.ChannelUID;
@ -96,9 +97,11 @@ public class CameraCapability extends HomeSecurityThingCapability {
eventHelper.setUrls(newVpnUrl, localUrl);
}
vpnUrl = newVpnUrl;
if (!SdCardStatus.SD_CARD_WORKING.equals(newData.getSdStatus())
|| !AlimentationStatus.ALIM_CORRECT_POWER.equals(newData.getAlimStatus())) {
statusReason = "%s, %s".formatted(newData.getSdStatus(), newData.getAlimStatus());
if (!SdCardStatus.SD_CARD_WORKING.equals(newData.getSdStatus())) {
statusReason = newData.getSdStatus().toString();
}
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) {
handler.updateState(group, CHANNEL_EVENT_TYPE, toStringType(event.getEventType()));
handler.updateState(group, CHANNEL_EVENT_TIME, toDateTimeType(event.getTime()));
handler.updateState(group, CHANNEL_EVENT_SNAPSHOT, toRawType(event.getSnapshotUrl()));
handler.updateState(group, CHANNEL_EVENT_SNAPSHOT_URL, toStringType(event.getSnapshotUrl()));
handler.updateState(group, CHANNEL_EVENT_VIGNETTE, toRawType(event.getVignetteUrl()));
handler.updateState(group, CHANNEL_EVENT_VIGNETTE_URL, toStringType(event.getVignetteUrl()));
handler.updateState(group, CHANNEL_EVENT_SUBTYPE,
Objects.requireNonNull(event.getSubTypeDescription().map(d -> toStringType(d)).orElse(UnDefType.NULL)));
updatePictureIfUrlPresent(event.getSnapshotUrl(), group, CHANNEL_EVENT_SNAPSHOT, CHANNEL_EVENT_SNAPSHOT_URL);
updatePictureIfUrlPresent(event.getVignetteUrl(), group, CHANNEL_EVENT_VIGNETTE, CHANNEL_EVENT_VIGNETTE_URL);
handler.updateState(group, CHANNEL_EVENT_SUBTYPE, Objects.requireNonNull(
event.getSubTypeDescription().map(ChannelTypeUtils::toStringType).orElse(UnDefType.NULL)));
final String message = event.getName();
handler.updateState(group, CHANNEL_EVENT_MESSAGE,
message == null || message.isBlank() ? UnDefType.NULL : toStringType(message));
@ -139,6 +140,14 @@ public class CameraCapability extends HomeSecurityThingCapability {
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
public void handleCommand(String channelName, Command command) {
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.channelhelper.ChannelHelper;
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.thing.ChannelUID;
import org.openhab.core.types.Command;
@ -79,8 +80,8 @@ public class PersonCapability extends HomeSecurityThingCapability {
protected void updateWebhookEvent(WebhookEvent event) {
super.updateWebhookEvent(event);
handler.updateState(GROUP_LAST_EVENT, CHANNEL_EVENT_SUBTYPE,
Objects.requireNonNull(event.getSubTypeDescription().map(d -> toStringType(d)).orElse(UnDefType.NULL)));
handler.updateState(GROUP_LAST_EVENT, CHANNEL_EVENT_SUBTYPE, Objects.requireNonNull(
event.getSubTypeDescription().map(ChannelTypeUtils::toStringType).orElse(UnDefType.NULL)));
final String message = event.getName();
handler.updateState(GROUP_LAST_EVENT, CHANNEL_EVENT_MESSAGE,

View File

@ -13,7 +13,7 @@
package org.openhab.binding.netatmo.internal.handler.channelhelper;
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;
@ -43,10 +43,10 @@ public class EventCameraChannelHelper extends EventChannelHelper {
case CHANNEL_EVENT_TYPE -> toStringType(event.getEventType());
case CHANNEL_EVENT_TIME -> new DateTimeType(event.getTime());
case CHANNEL_EVENT_MESSAGE -> toStringType(event.getName());
case CHANNEL_EVENT_SNAPSHOT -> toRawType(event.getSnapshotUrl());
case CHANNEL_EVENT_SNAPSHOT_URL -> toStringType(event.getSnapshotUrl());
case CHANNEL_EVENT_VIGNETTE -> toRawType(event.getVignetteUrl());
case CHANNEL_EVENT_VIGNETTE_URL -> toStringType(event.getVignetteUrl());
case CHANNEL_EVENT_SNAPSHOT -> checkUrlPresence(event.getSnapshotUrl(), true);
case CHANNEL_EVENT_SNAPSHOT_URL -> checkUrlPresence(event.getSnapshotUrl(), false);
case CHANNEL_EVENT_VIGNETTE -> checkUrlPresence(event.getVignetteUrl(), true);
case CHANNEL_EVENT_VIGNETTE_URL -> checkUrlPresence(event.getVignetteUrl(), false);
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_SUBTYPE ->
event.getSubTypeDescription().map(ChannelTypeUtils::toStringType).orElse(UnDefType.NULL);
case CHANNEL_EVENT_SNAPSHOT -> toRawType(event.getSnapshotUrl());
case CHANNEL_EVENT_SNAPSHOT_URL -> toStringType(event.getSnapshotUrl());
case CHANNEL_EVENT_SNAPSHOT -> checkUrlPresence(event.getSnapshotUrl(), true);
case CHANNEL_EVENT_SNAPSHOT_URL -> checkUrlPresence(event.getSnapshotUrl(), false);
default -> null;
};
}
protected @Nullable State checkUrlPresence(@Nullable String url, boolean asRaw) {
return url != null ? asRaw ? toRawType(url) : toStringType(url) : null;
}
@Override
protected @Nullable State internalGetHomeEvent(String channelId, @Nullable String groupId, HomeEvent event) {
String videoId = event.getVideoId();
if (videoId == null) {
return null;
}
return switch (channelId) {
case CHANNEL_EVENT_VIDEO_STATUS ->
event.getVideoId() != null ? toStringType(event.getVideoStatus()) : UnDefType.NULL;
case CHANNEL_EVENT_VIDEO_STATUS -> toStringType(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());
default -> null;

View File

@ -465,6 +465,7 @@ device-not-connected = Thing is not reachable
data-over-limit = Data seems quite old
request-time-out = Request timed out - will attempt reconnection later
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-internal-error = Internal error