[Netatmo] Enhance webhook handling and dispatching (#15045)

* Enhance webhook handling and dispatching

Signed-off-by: clinique <gael@lhopital.org>

* Corrects flapping channels on Home when using single home for security and energy

Signed-off-by: clinique <gael@lhopital.org>

* Some code enhancement

Signed-off-by: clinique <gael@lhopital.org>

* Adding some missing EventType submitted on the webhook

Signed-off-by: clinique <gael@lhopital.org>

---------

Signed-off-by: clinique <gael@lhopital.org>
This commit is contained in:
Gaël L'hopital 2023-06-13 20:35:45 +02:00 committed by GitHub
parent c9c6e95807
commit d40c20b2dd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 295 additions and 189 deletions

View File

@ -460,6 +460,7 @@ The Home thing has the following configuration elements:
| securityId | String | No | Id of a home holding security monitoring devices | | securityId | String | No | Id of a home holding security monitoring devices |
At least one of these parameter must be filled - at most two : At least one of these parameter must be filled - at most two :
* id or securityId * id or securityId
* id or energyId * id or energyId
* securityId and energyId * securityId and energyId

View File

@ -18,7 +18,6 @@ import java.net.URI;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
import javax.ws.rs.core.UriBuilder; import javax.ws.rs.core.UriBuilder;
@ -94,7 +93,7 @@ public class SecurityApi extends RestManager {
// Remove unneeded events being before oldestKnown // Remove unneeded events being before oldestKnown
return events.stream().filter(event -> freshestEventTime == null || event.getTime().isAfter(freshestEventTime)) return events.stream().filter(event -> freshestEventTime == null || event.getTime().isAfter(freshestEventTime))
.sorted(Comparator.comparing(HomeEvent::getTime).reversed()).collect(Collectors.toList()); .sorted(Comparator.comparing(HomeEvent::getTime).reversed()).toList();
} }
public List<HomeEvent> getPersonEvents(String homeId, String personId) throws NetatmoException { public List<HomeEvent> getPersonEvents(String homeId, String personId) throws NetatmoException {

View File

@ -43,6 +43,8 @@ public enum EventSubType {
BATTERY_VERY_LOW(1, EventType.BATTERY_STATUS), BATTERY_VERY_LOW(1, EventType.BATTERY_STATUS),
SMOKE_CLEARED(0, EventType.SMOKE), SMOKE_CLEARED(0, EventType.SMOKE),
SMOKE_DETECTED(1, EventType.SMOKE), SMOKE_DETECTED(1, EventType.SMOKE),
HUSH_ACTIVATED(0, EventType.HUSH),
HUSH_DEACTIVATED(1, EventType.HUSH),
SOUND_TEST_OK(0, EventType.SOUND_TEST), SOUND_TEST_OK(0, EventType.SOUND_TEST),
SOUND_TEST_ERROR(1, EventType.SOUND_TEST), SOUND_TEST_ERROR(1, EventType.SOUND_TEST),
DETECTOR_READY(0, EventType.TAMPERED), DETECTOR_READY(0, EventType.TAMPERED),

View File

@ -28,6 +28,8 @@ import com.google.gson.annotations.SerializedName;
@NonNullByDefault @NonNullByDefault
public enum EventType { public enum EventType {
UNKNOWN(), UNKNOWN(),
@SerializedName("webhook_activation") // Ack of a 'webhook set' Api Call
WEBHOOK_ACTIVATION(ModuleType.ACCOUNT),
@SerializedName("person") // When the Indoor Camera detects a face @SerializedName("person") // When the Indoor Camera detects a face
PERSON(ModuleType.PERSON, ModuleType.WELCOME), PERSON(ModuleType.PERSON, ModuleType.WELCOME),
@ -71,6 +73,15 @@ public enum EventType {
@SerializedName("module_end_update") // Module's firmware update is over @SerializedName("module_end_update") // Module's firmware update is over
MODULE_END_UPDATE(ModuleType.WELCOME), MODULE_END_UPDATE(ModuleType.WELCOME),
@SerializedName("tag_big_move") // Module's firmware update is over
TAG_BIG_MOVE(ModuleType.WELCOME),
@SerializedName("tag_open") // Module's firmware update is over
TAG_OPEN(ModuleType.WELCOME),
@SerializedName("tag_small_move") // Module's firmware update is over
TAG_SMALL_MOVE(ModuleType.WELCOME),
@SerializedName("connection") // When the camera connects to Netatmo servers @SerializedName("connection") // When the camera connects to Netatmo servers
CONNECTION(ModuleType.WELCOME, ModuleType.PRESENCE), CONNECTION(ModuleType.WELCOME, ModuleType.PRESENCE),

View File

@ -38,17 +38,50 @@ public class HomeData extends NAThing implements NAModule, LocationEx {
public class HomesDataResponse extends ApiResponse<ListBodyResponse<HomeData>> { public class HomesDataResponse extends ApiResponse<ListBodyResponse<HomeData>> {
} }
public class Security extends HomeData {
private NAObjectMap<HomeDataPerson> persons = new NAObjectMap<>();
public NAObjectMap<HomeDataPerson> getPersons() {
return persons;
}
public List<HomeDataPerson> getKnownPersons() {
return persons.values().stream().filter(HomeDataPerson::isKnown).toList();
}
}
public class Energy extends HomeData {
private String temperatureControlMode = "";
private SetpointMode thermMode = SetpointMode.UNKNOWN;
private int thermSetpointDefaultDuration;
private List<ThermProgram> schedules = List.of();
public int getThermSetpointDefaultDuration() {
return thermSetpointDefaultDuration;
}
public SetpointMode getThermMode() {
return thermMode;
}
public String getTemperatureControlMode() {
return temperatureControlMode;
}
public List<ThermProgram> getThermSchedules() {
return schedules;
}
public @Nullable ThermProgram getActiveProgram() {
return schedules.stream().filter(ThermProgram::isSelected).findFirst().orElse(null);
}
}
private double altitude; private double altitude;
private double[] coordinates = {}; private double[] coordinates = {};
private @Nullable String country; private @Nullable String country;
private @Nullable String timezone; private @Nullable String timezone;
private @Nullable String temperatureControlMode;
private SetpointMode thermMode = SetpointMode.UNKNOWN;
private int thermSetpointDefaultDuration;
private List<ThermProgram> schedules = List.of();
private NAObjectMap<HomeDataPerson> persons = new NAObjectMap<>();
private NAObjectMap<HomeDataRoom> rooms = new NAObjectMap<>(); private NAObjectMap<HomeDataRoom> rooms = new NAObjectMap<>();
private NAObjectMap<HomeDataModule> modules = new NAObjectMap<>(); private NAObjectMap<HomeDataModule> modules = new NAObjectMap<>();
@ -77,26 +110,6 @@ public class HomeData extends NAThing implements NAModule, LocationEx {
return Optional.ofNullable(timezone); return Optional.ofNullable(timezone);
} }
public int getThermSetpointDefaultDuration() {
return thermSetpointDefaultDuration;
}
public SetpointMode getThermMode() {
return thermMode;
}
public NAObjectMap<HomeDataPerson> getPersons() {
return persons;
}
public List<HomeDataPerson> getKnownPersons() {
return persons.values().stream().filter(HomeDataPerson::isKnown).collect(Collectors.toList());
}
public Optional<String> getTemperatureControlMode() {
return Optional.ofNullable(temperatureControlMode);
}
public NAObjectMap<HomeDataRoom> getRooms() { public NAObjectMap<HomeDataRoom> getRooms() {
return rooms; return rooms;
} }
@ -108,12 +121,4 @@ public class HomeData extends NAThing implements NAModule, LocationEx {
public Set<FeatureArea> getFeatures() { public Set<FeatureArea> getFeatures() {
return getModules().values().stream().map(m -> m.getType().feature).collect(Collectors.toSet()); return getModules().values().stream().map(m -> m.getType().feature).collect(Collectors.toSet());
} }
public List<ThermProgram> getThermSchedules() {
return schedules;
}
public @Nullable ThermProgram getActiveProgram() {
return schedules.stream().filter(ThermProgram::isSelected).findFirst().orElse(null);
}
} }

View File

@ -31,7 +31,7 @@ public class HomeStatusPerson extends NAThing {
return ModuleType.PERSON; return ModuleType.PERSON;
} }
public boolean isOutOfSight() { public boolean atHome() {
return outOfSight; return !outOfSight;
} }
} }

View File

@ -32,22 +32,26 @@ public class NAHomeStatus {
public class HomeStatus extends NAThing { public class HomeStatus extends NAThing {
private @Nullable NAObjectMap<HomeStatusModule> modules; private @Nullable NAObjectMap<HomeStatusModule> modules;
private @Nullable NAObjectMap<HomeStatusPerson> persons;
private @Nullable NAObjectMap<Room> rooms;
public NAObjectMap<HomeStatusModule> getModules() { public NAObjectMap<HomeStatusModule> getModules() {
NAObjectMap<HomeStatusModule> localModules = modules; NAObjectMap<HomeStatusModule> localModules = modules;
return localModules != null ? localModules : new NAObjectMap<>(); return localModules != null ? localModules : new NAObjectMap<>();
} }
}
public NAObjectMap<HomeStatusPerson> getPersons() { public class Energy extends HomeStatus {
NAObjectMap<HomeStatusPerson> localPersons = persons; private NAObjectMap<Room> rooms = new NAObjectMap<>();
return localPersons != null ? localPersons : new NAObjectMap<>();
}
public NAObjectMap<Room> getRooms() { public NAObjectMap<Room> getRooms() {
NAObjectMap<Room> localRooms = rooms; return rooms;
return localRooms != null ? localRooms : new NAObjectMap<>(); }
}
public class Security extends HomeStatus {
private NAObjectMap<HomeStatusPerson> persons = new NAObjectMap<>();
public NAObjectMap<HomeStatusPerson> getPersons() {
return persons;
} }
} }

View File

@ -19,6 +19,9 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.netatmo.internal.api.NetatmoException; import org.openhab.binding.netatmo.internal.api.NetatmoException;
import org.openhab.binding.netatmo.internal.api.data.ModuleType; import org.openhab.binding.netatmo.internal.api.data.ModuleType;
import org.openhab.binding.netatmo.internal.api.dto.HomeData;
import org.openhab.binding.netatmo.internal.api.dto.NAHomeStatus;
import org.openhab.binding.netatmo.internal.api.dto.NAHomeStatus.HomeStatus;
import org.openhab.core.i18n.TimeZoneProvider; import org.openhab.core.i18n.TimeZoneProvider;
import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.OpenClosedType; import org.openhab.core.library.types.OpenClosedType;
@ -49,22 +52,26 @@ public class NADeserializer {
.registerTypeAdapter(NAObjectMap.class, new NAObjectMapDeserializer()) .registerTypeAdapter(NAObjectMap.class, new NAObjectMapDeserializer())
.registerTypeAdapter(NAPushType.class, new NAPushTypeDeserializer()) .registerTypeAdapter(NAPushType.class, new NAPushTypeDeserializer())
.registerTypeAdapter(ModuleType.class, new ModuleTypeDeserializer()) .registerTypeAdapter(ModuleType.class, new ModuleTypeDeserializer())
.registerTypeAdapter(ZonedDateTime.class, .registerTypeAdapter(HomeStatus.class,
(JsonDeserializer<ZonedDateTime>) (json, type, jsonDeserializationContext) -> { (JsonDeserializer<HomeStatus>) (json, type, context) -> context.deserialize(json,
long netatmoTS = json.getAsJsonPrimitive().getAsLong(); json.getAsJsonObject().has("persons") ? NAHomeStatus.Security.class
Instant i = Instant.ofEpochSecond(netatmoTS); : NAHomeStatus.Energy.class))
return ZonedDateTime.ofInstant(i, timeZoneProvider.getTimeZone()); .registerTypeAdapter(HomeData.class,
}) (JsonDeserializer<HomeData>) (json, type, context) -> context.deserialize(json,
json.getAsJsonObject().has("therm_mode") ? HomeData.Energy.class
: HomeData.Security.class))
.registerTypeAdapter(ZonedDateTime.class, (JsonDeserializer<ZonedDateTime>) (json, type, context) -> {
long netatmoTS = json.getAsJsonPrimitive().getAsLong();
Instant i = Instant.ofEpochSecond(netatmoTS);
return ZonedDateTime.ofInstant(i, timeZoneProvider.getTimeZone());
})
.registerTypeAdapter(OnOffType.class, .registerTypeAdapter(OnOffType.class,
(JsonDeserializer<OnOffType>) (json, type, jsonDeserializationContext) -> OnOffType (JsonDeserializer<OnOffType>) (json, type, context) -> OnOffType
.from(json.getAsJsonPrimitive().getAsString())) .from(json.getAsJsonPrimitive().getAsString()))
.registerTypeAdapter(OpenClosedType.class, .registerTypeAdapter(OpenClosedType.class, (JsonDeserializer<OpenClosedType>) (json, type, context) -> {
(JsonDeserializer<OpenClosedType>) (json, type, jsonDeserializationContext) -> { String value = json.getAsJsonPrimitive().getAsString().toUpperCase();
String value = json.getAsJsonPrimitive().getAsString().toUpperCase(); return "TRUE".equals(value) || "1".equals(value) ? OpenClosedType.CLOSED : OpenClosedType.OPEN;
return "TRUE".equals(value) || "1".equals(value) ? OpenClosedType.CLOSED }).create();
: OpenClosedType.OPEN;
})
.create();
} }
public <T> T deserialize(Class<T> clazz, String json) throws NetatmoException { public <T> T deserialize(Class<T> clazz, String json) throws NetatmoException {

View File

@ -32,6 +32,7 @@ import com.google.gson.JsonElement;
*/ */
@NonNullByDefault @NonNullByDefault
class NAPushTypeDeserializer implements JsonDeserializer<NAPushType> { class NAPushTypeDeserializer implements JsonDeserializer<NAPushType> {
private final Logger logger = LoggerFactory.getLogger(NAPushTypeDeserializer.class); private final Logger logger = LoggerFactory.getLogger(NAPushTypeDeserializer.class);
@Override @Override
@ -40,15 +41,19 @@ class NAPushTypeDeserializer implements JsonDeserializer<NAPushType> {
final String[] elements = string.split("-"); final String[] elements = string.split("-");
ModuleType moduleType = ModuleType.UNKNOWN; ModuleType moduleType = ModuleType.UNKNOWN;
EventType eventType = EventType.UNKNOWN; EventType eventType = EventType.UNKNOWN;
if (elements.length == 2) { if (elements.length == 2) {
moduleType = fromNetatmoObject(elements[0]); moduleType = fromNetatmoObject(elements[0]);
eventType = fromEvent(elements[1]); eventType = fromEvent(elements[1]);
} else { } else if (elements.length == 1) {
logger.warn("Unexpected syntax received for push_type field : {}", string); moduleType = ModuleType.ACCOUNT;
eventType = fromEvent(string);
} }
if (moduleType.equals(ModuleType.UNKNOWN) || eventType.equals(EventType.UNKNOWN)) { if (moduleType.equals(ModuleType.UNKNOWN) || eventType.equals(EventType.UNKNOWN)) {
logger.warn("Unknown module or event type : {}, deserialized to '{}-{}'", string, moduleType, eventType); logger.warn("Unknown module or event type : {}, deserialized to '{}-{}'", string, moduleType, eventType);
} }
return new NAPushType(moduleType, eventType); return new NAPushType(moduleType, eventType);
} }

View File

@ -61,6 +61,7 @@ import org.openhab.binding.netatmo.internal.api.WeatherApi;
import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.FeatureArea; import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.FeatureArea;
import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.Scope; import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.Scope;
import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.ServiceError; import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.ServiceError;
import org.openhab.binding.netatmo.internal.api.dto.HomeData;
import org.openhab.binding.netatmo.internal.api.dto.HomeDataModule; import org.openhab.binding.netatmo.internal.api.dto.HomeDataModule;
import org.openhab.binding.netatmo.internal.api.dto.NAMain; import org.openhab.binding.netatmo.internal.api.dto.NAMain;
import org.openhab.binding.netatmo.internal.api.dto.NAModule; import org.openhab.binding.netatmo.internal.api.dto.NAModule;
@ -395,8 +396,9 @@ public class ApiBridgeHandler extends BaseBridgeHandler {
|| h.getFeatures().contains(FeatureArea.WEATHER) && h.getFeatures().size() == 1)) || h.getFeatures().contains(FeatureArea.WEATHER) && h.getFeatures().size() == 1))
.forEach(home -> { .forEach(home -> {
action.apply(home, accountUID).ifPresent(homeUID -> { action.apply(home, accountUID).ifPresent(homeUID -> {
home.getKnownPersons().forEach(person -> action.apply(person, homeUID)); if (home instanceof HomeData.Security securityData) {
securityData.getKnownPersons().forEach(person -> action.apply(person, homeUID));
}
Map<String, ThingUID> bridgesUids = new HashMap<>(); Map<String, ThingUID> bridgesUids = new HashMap<>();
home.getRooms().values().stream().forEach(room -> { home.getRooms().values().stream().forEach(room -> {

View File

@ -18,7 +18,6 @@ import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
@ -134,8 +133,7 @@ public interface CommonInterface {
if (thing instanceof Bridge) { if (thing instanceof Bridge) {
return ((Bridge) thing).getThings().stream().filter(Thing::isEnabled) return ((Bridge) thing).getThings().stream().filter(Thing::isEnabled)
.filter(th -> th.getStatusInfo().getStatusDetail() != ThingStatusDetail.BRIDGE_OFFLINE) .filter(th -> th.getStatusInfo().getStatusDetail() != ThingStatusDetail.BRIDGE_OFFLINE)
.map(Thing::getHandler).filter(Objects::nonNull).map(CommonInterface.class::cast) .map(Thing::getHandler).filter(Objects::nonNull).map(CommonInterface.class::cast).toList();
.collect(Collectors.toList());
} }
return List.of(); return List.of();
} }

View File

@ -12,13 +12,20 @@
*/ */
package org.openhab.binding.netatmo.internal.handler.capability; package org.openhab.binding.netatmo.internal.handler.capability;
import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
import static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.*;
import java.util.List; import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.netatmo.internal.api.dto.NAObject; import org.openhab.binding.netatmo.internal.api.dto.NAObject;
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.core.thing.ChannelUID;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.types.UnDefType;
/** /**
* {@link AlarmEventCapability} gives the ability to handle Alarm modules events * {@link AlarmEventCapability} gives the ability to handle Alarm modules events
@ -34,6 +41,22 @@ public class AlarmEventCapability extends HomeSecurityThingCapability {
super(handler, descriptionProvider, channelHelpers); super(handler, descriptionProvider, channelHelpers);
} }
@Override
protected void updateWebhookEvent(WebhookEvent event) {
super.updateWebhookEvent(event);
final ThingUID thingUid = handler.getThing().getUID();
handler.updateState(new ChannelUID(thingUid, GROUP_LAST_EVENT, CHANNEL_EVENT_TYPE),
toStringType(event.getEventType()));
handler.updateState(new ChannelUID(thingUid, GROUP_LAST_EVENT, CHANNEL_EVENT_TIME),
toDateTimeType(event.getTime()));
handler.updateState(new ChannelUID(thingUid, GROUP_LAST_EVENT, CHANNEL_EVENT_SUBTYPE),
event.getSubTypeDescription().map(d -> toStringType(d)).orElse(UnDefType.NULL));
final String message = event.getName();
handler.updateState(new ChannelUID(thingUid, GROUP_LAST_EVENT, CHANNEL_EVENT_MESSAGE),
message == null || message.isBlank() ? UnDefType.NULL : toStringType(message));
}
@Override @Override
public List<NAObject> updateReadings() { public List<NAObject> updateReadings() {
return getSecurityCapability().map(cap -> cap.getDeviceLastEvent(handler.getId(), moduleType.apiName)) return getSecurityCapability().map(cap -> cap.getDeviceLastEvent(handler.getId(), moduleType.apiName))

View File

@ -17,7 +17,6 @@ import static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.*;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jdt.annotation.Nullable;
@ -35,8 +34,10 @@ 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.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.thing.Thing;
import org.openhab.core.thing.ThingUID; import org.openhab.core.thing.ThingUID;
import org.openhab.core.types.Command; import org.openhab.core.types.Command;
import org.openhab.core.types.State;
import org.openhab.core.types.StateOption; import org.openhab.core.types.StateOption;
import org.openhab.core.types.UnDefType; import org.openhab.core.types.UnDefType;
@ -53,6 +54,8 @@ public class CameraCapability extends HomeSecurityThingCapability {
protected @Nullable String localUrl; protected @Nullable String localUrl;
protected @Nullable String vpnUrl; protected @Nullable String vpnUrl;
private boolean hasSubEventGroup;
private boolean hasLastEventGroup;
public CameraCapability(CommonInterface handler, NetatmoDescriptionProvider descriptionProvider, public CameraCapability(CommonInterface handler, NetatmoDescriptionProvider descriptionProvider,
List<ChannelHelper> channelHelpers) { List<ChannelHelper> channelHelpers) {
@ -63,6 +66,13 @@ public class CameraCapability extends HomeSecurityThingCapability {
"CameraCapability must find a CameraChannelHelper, please file a bug report.")); "CameraCapability must find a CameraChannelHelper, please file a bug report."));
} }
@Override
public void initialize() {
Thing thing = handler.getThing();
hasSubEventGroup = !thing.getChannelsOfGroup(GROUP_SUB_EVENT).isEmpty();
hasLastEventGroup = !thing.getChannelsOfGroup(GROUP_LAST_EVENT).isEmpty();
}
@Override @Override
public void updateHomeStatusModule(HomeStatusModule newData) { public void updateHomeStatusModule(HomeStatusModule newData) {
super.updateHomeStatusModule(newData); super.updateHomeStatusModule(newData);
@ -85,23 +95,13 @@ public class CameraCapability extends HomeSecurityThingCapability {
protected void updateWebhookEvent(WebhookEvent event) { protected void updateWebhookEvent(WebhookEvent event) {
super.updateWebhookEvent(event); super.updateWebhookEvent(event);
final ThingUID thingUid = handler.getThing().getUID(); if (hasSubEventGroup) {
handler.updateState(new ChannelUID(thingUid, GROUP_SUB_EVENT, CHANNEL_EVENT_TYPE), updateSubGroup(event, thing.getUID(), GROUP_SUB_EVENT);
toStringType(event.getEventType())); }
handler.updateState(new ChannelUID(thingUid, GROUP_SUB_EVENT, CHANNEL_EVENT_TIME),
toDateTimeType(event.getTime()));
handler.updateState(new ChannelUID(thingUid, GROUP_SUB_EVENT, CHANNEL_EVENT_SNAPSHOT),
toRawType(event.getSnapshotUrl()));
handler.updateState(new ChannelUID(thingUid, GROUP_SUB_EVENT, CHANNEL_EVENT_SNAPSHOT_URL),
toStringType(event.getSnapshotUrl()));
handler.updateState(new ChannelUID(thingUid, GROUP_SUB_EVENT, CHANNEL_EVENT_VIGNETTE),
toRawType(event.getVignetteUrl()));
handler.updateState(new ChannelUID(thingUid, GROUP_SUB_EVENT, CHANNEL_EVENT_VIGNETTE_URL),
toStringType(event.getVignetteUrl()));
final String message = event.getName(); if (hasLastEventGroup) {
handler.updateState(new ChannelUID(thingUid, GROUP_SUB_EVENT, CHANNEL_EVENT_MESSAGE), updateSubGroup(event, thing.getUID(), GROUP_LAST_EVENT);
message == null || message.isBlank() ? UnDefType.NULL : toStringType(message)); }
// The channel should get triggered at last (after super and sub methods), because this allows rules to access // The channel should get triggered at last (after super and sub methods), because this allows rules to access
// the new updated data from the other channels. // the new updated data from the other channels.
@ -111,6 +111,25 @@ public class CameraCapability extends HomeSecurityThingCapability {
handler.triggerChannel(CHANNEL_HOME_EVENT, eventType); handler.triggerChannel(CHANNEL_HOME_EVENT, eventType);
} }
private void updateSubGroup(WebhookEvent event, ThingUID thingUid, String group) {
handler.updateState(new ChannelUID(thingUid, group, CHANNEL_EVENT_TYPE), toStringType(event.getEventType()));
handler.updateState(new ChannelUID(thingUid, group, CHANNEL_EVENT_TIME), toDateTimeType(event.getTime()));
handler.updateState(new ChannelUID(thingUid, group, CHANNEL_EVENT_SNAPSHOT), toRawType(event.getSnapshotUrl()));
handler.updateState(new ChannelUID(thingUid, group, CHANNEL_EVENT_SNAPSHOT_URL),
toStringType(event.getSnapshotUrl()));
handler.updateState(new ChannelUID(thingUid, group, CHANNEL_EVENT_VIGNETTE), toRawType(event.getVignetteUrl()));
handler.updateState(new ChannelUID(thingUid, group, CHANNEL_EVENT_VIGNETTE_URL),
toStringType(event.getVignetteUrl()));
handler.updateState(new ChannelUID(thingUid, group, CHANNEL_EVENT_SUBTYPE),
event.getSubTypeDescription().map(d -> toStringType(d)).orElse(UnDefType.NULL));
final String message = event.getName();
handler.updateState(new ChannelUID(thingUid, group, CHANNEL_EVENT_MESSAGE),
message == null || message.isBlank() ? UnDefType.NULL : toStringType(message));
State personId = event.getPersons().isEmpty() ? UnDefType.NULL
: toStringType(event.getPersons().values().iterator().next().getId());
handler.updateState(personChannelUID, personId);
}
@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)) {
@ -125,8 +144,8 @@ public class CameraCapability extends HomeSecurityThingCapability {
super.beforeNewData(); super.beforeNewData();
getSecurityCapability().ifPresent(cap -> { getSecurityCapability().ifPresent(cap -> {
NAObjectMap<HomeDataPerson> persons = cap.getPersons(); NAObjectMap<HomeDataPerson> persons = cap.getPersons();
descriptionProvider.setStateOptions(personChannelUID, persons.values().stream() descriptionProvider.setStateOptions(personChannelUID,
.map(p -> new StateOption(p.getId(), p.getName())).collect(Collectors.toList())); persons.values().stream().map(p -> new StateOption(p.getId(), p.getName())).toList());
}); });
} }

View File

@ -63,32 +63,32 @@ public class Capability {
public final @Nullable String setNewData(NAObject newData) { public final @Nullable String setNewData(NAObject newData) {
beforeNewData(); beforeNewData();
if (newData instanceof HomeData) { if (newData instanceof HomeData homeData) {
updateHomeData((HomeData) newData); updateHomeData(homeData);
} }
if (newData instanceof HomeStatus) { if (newData instanceof HomeStatus homeStatus) {
updateHomeStatus((HomeStatus) newData); updateHomeStatus(homeStatus);
} }
if (newData instanceof HomeStatusModule) { if (newData instanceof HomeStatusModule homeStatusModule) {
updateHomeStatusModule((HomeStatusModule) newData); updateHomeStatusModule(homeStatusModule);
} }
if (newData instanceof Event) {
updateEvent((Event) newData); if (newData instanceof HomeEvent homeEvent) {
updateHomeEvent(homeEvent);
} else if (newData instanceof WebhookEvent webhookEvent && webhookEvent.getEventType().validFor(moduleType)) {
updateWebhookEvent(webhookEvent);
} else if (newData instanceof Event event) {
updateEvent(event);
} }
if (newData instanceof WebhookEvent) {
updateWebhookEvent((WebhookEvent) newData); if (newData instanceof NAThing naThing) {
updateNAThing(naThing);
} }
if (newData instanceof HomeEvent) { if (newData instanceof NAMain naMain) {
updateHomeEvent((HomeEvent) newData); updateNAMain(naMain);
} }
if (newData instanceof NAThing) { if (newData instanceof Device device) {
updateNAThing((NAThing) newData); updateNADevice(device);
}
if (newData instanceof NAMain) {
updateNAMain((NAMain) newData);
}
if (newData instanceof Device) {
updateNADevice((Device) newData);
} }
afterNewData(newData); afterNewData(newData);
return statusReason; return statusReason;

View File

@ -15,7 +15,6 @@ package org.openhab.binding.netatmo.internal.handler.capability;
import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*; import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.util.stream.Collectors;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.netatmo.internal.api.EnergyApi; import org.openhab.binding.netatmo.internal.api.EnergyApi;
@ -26,6 +25,7 @@ import org.openhab.binding.netatmo.internal.api.dto.HomeData;
import org.openhab.binding.netatmo.internal.api.dto.HomeDataModule; import org.openhab.binding.netatmo.internal.api.dto.HomeDataModule;
import org.openhab.binding.netatmo.internal.api.dto.HomeDataRoom; import org.openhab.binding.netatmo.internal.api.dto.HomeDataRoom;
import org.openhab.binding.netatmo.internal.api.dto.HomeStatusModule; import org.openhab.binding.netatmo.internal.api.dto.HomeStatusModule;
import org.openhab.binding.netatmo.internal.api.dto.NAHomeStatus;
import org.openhab.binding.netatmo.internal.api.dto.NAHomeStatus.HomeStatus; import org.openhab.binding.netatmo.internal.api.dto.NAHomeStatus.HomeStatus;
import org.openhab.binding.netatmo.internal.api.dto.Room; import org.openhab.binding.netatmo.internal.api.dto.Room;
import org.openhab.binding.netatmo.internal.config.HomeConfiguration; import org.openhab.binding.netatmo.internal.config.HomeConfiguration;
@ -65,38 +65,41 @@ public class EnergyCapability extends RestCapability<EnergyApi> {
@Override @Override
protected void updateHomeData(HomeData homeData) { protected void updateHomeData(HomeData homeData) {
NAObjectMap<HomeDataRoom> rooms = homeData.getRooms(); if (homeData instanceof HomeData.Energy energyData) {
NAObjectMap<HomeDataModule> modules = homeData.getModules(); NAObjectMap<HomeDataRoom> rooms = energyData.getRooms();
handler.getActiveChildren(FeatureArea.ENERGY).forEach(childHandler -> { NAObjectMap<HomeDataModule> modules = energyData.getModules();
String childId = childHandler.getId(); handler.getActiveChildren(FeatureArea.ENERGY).forEach(childHandler -> {
rooms.getOpt(childId) String childId = childHandler.getId();
.ifPresentOrElse(roomData -> childHandler.setNewData(roomData.ignoringForThingUpdate()), () -> { rooms.getOpt(childId)
modules.getOpt(childId) .ifPresentOrElse(roomData -> childHandler.setNewData(roomData.ignoringForThingUpdate()), () -> {
.ifPresent(childData -> childHandler.setNewData(childData.ignoringForThingUpdate())); modules.getOpt(childId).ifPresent(
modules.values().stream().filter(module -> childId.equals(module.getBridge())) childData -> childHandler.setNewData(childData.ignoringForThingUpdate()));
.forEach(bridgedModule -> childHandler.setNewData(bridgedModule)); modules.values().stream().filter(module -> childId.equals(module.getBridge()))
}); .forEach(bridgedModule -> childHandler.setNewData(bridgedModule));
}); });
descriptionProvider.setStateOptions(new ChannelUID(thing.getUID(), GROUP_ENERGY, CHANNEL_PLANNING), });
homeData.getThermSchedules().stream().map(p -> new StateOption(p.getId(), p.getName())) descriptionProvider.setStateOptions(new ChannelUID(thing.getUID(), GROUP_ENERGY, CHANNEL_PLANNING),
.collect(Collectors.toList())); energyData.getThermSchedules().stream().map(p -> new StateOption(p.getId(), p.getName())).toList());
setPointDefaultDuration = homeData.getThermSetpointDefaultDuration(); setPointDefaultDuration = energyData.getThermSetpointDefaultDuration();
}
} }
@Override @Override
protected void updateHomeStatus(HomeStatus homeStatus) { protected void updateHomeStatus(HomeStatus homeStatus) {
NAObjectMap<Room> rooms = homeStatus.getRooms(); if (homeStatus instanceof NAHomeStatus.Energy energyStatus) {
NAObjectMap<HomeStatusModule> modules = homeStatus.getModules(); NAObjectMap<Room> rooms = energyStatus.getRooms();
handler.getActiveChildren(FeatureArea.ENERGY).forEach(childHandler -> { NAObjectMap<HomeStatusModule> modules = energyStatus.getModules();
String childId = childHandler.getId(); handler.getActiveChildren(FeatureArea.ENERGY).forEach(childHandler -> {
rooms.getOpt(childId).ifPresentOrElse(roomData -> childHandler.setNewData(roomData), () -> { String childId = childHandler.getId();
modules.getOpt(childId).ifPresent(moduleData -> { rooms.getOpt(childId).ifPresentOrElse(roomData -> childHandler.setNewData(roomData), () -> {
childHandler.setNewData(moduleData); modules.getOpt(childId).ifPresent(moduleData -> {
modules.values().stream().filter(module -> childId.equals(module.getBridge())) childHandler.setNewData(moduleData);
.forEach(bridgedModule -> childHandler.setNewData(bridgedModule)); modules.values().stream().filter(module -> childId.equals(module.getBridge()))
.forEach(bridgedModule -> childHandler.setNewData(bridgedModule));
});
}); });
}); });
}); }
} }
public void setThermPoint(String roomId, SetpointMode mode, long endtime, double temp) { public void setThermPoint(String roomId, SetpointMode mode, long endtime, double temp) {

View File

@ -13,11 +13,11 @@
package org.openhab.binding.netatmo.internal.handler.capability; package org.openhab.binding.netatmo.internal.handler.capability;
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 java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
@ -28,13 +28,16 @@ import org.openhab.binding.netatmo.internal.api.dto.Event;
import org.openhab.binding.netatmo.internal.api.dto.HomeDataModule; import org.openhab.binding.netatmo.internal.api.dto.HomeDataModule;
import org.openhab.binding.netatmo.internal.api.dto.HomeEvent; import org.openhab.binding.netatmo.internal.api.dto.HomeEvent;
import org.openhab.binding.netatmo.internal.api.dto.NAObject; import org.openhab.binding.netatmo.internal.api.dto.NAObject;
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.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.thing.ThingUID;
import org.openhab.core.types.Command; import org.openhab.core.types.Command;
import org.openhab.core.types.StateOption; import org.openhab.core.types.StateOption;
import org.openhab.core.types.UnDefType;
/** /**
* {@link PersonCapability} gives the ability to handle Person specifics * {@link PersonCapability} gives the ability to handle Person specifics
@ -60,7 +63,7 @@ public class PersonCapability extends HomeSecurityThingCapability {
Stream<HomeDataModule> cameras = cap.getModules().values().stream() Stream<HomeDataModule> cameras = cap.getModules().values().stream()
.filter(module -> module.getType() == ModuleType.WELCOME); .filter(module -> module.getType() == ModuleType.WELCOME);
descriptionProvider.setStateOptions(cameraChannelUID, descriptionProvider.setStateOptions(cameraChannelUID,
cameras.map(p -> new StateOption(p.getId(), p.getName())).collect(Collectors.toList())); cameras.map(p -> new StateOption(p.getId(), p.getName())).toList());
}); });
} }
@ -71,6 +74,28 @@ public class PersonCapability extends HomeSecurityThingCapability {
} }
} }
@Override
protected void updateWebhookEvent(WebhookEvent event) {
super.updateWebhookEvent(event);
ThingUID thingUid = thing.getUID();
handler.updateState(new ChannelUID(thingUid, GROUP_LAST_EVENT, CHANNEL_EVENT_SUBTYPE),
event.getSubTypeDescription().map(d -> toStringType(d)).orElse(UnDefType.NULL));
final String message = event.getName();
handler.updateState(new ChannelUID(thingUid, GROUP_LAST_EVENT, CHANNEL_EVENT_MESSAGE),
message == null || message.isBlank() ? UnDefType.NULL : toStringType(message));
handler.updateState(new ChannelUID(thingUid, GROUP_LAST_EVENT, CHANNEL_EVENT_TIME),
toDateTimeType(event.getTime()));
handler.updateState(new ChannelUID(thingUid, GROUP_LAST_EVENT, CHANNEL_EVENT_SNAPSHOT),
toRawType(event.getSnapshotUrl()));
handler.updateState(cameraChannelUID, toStringType(event.getCameraId()));
}
@Override @Override
public void updateEvent(Event event) { public void updateEvent(Event event) {
super.updateEvent(event); super.updateEvent(event);

View File

@ -31,6 +31,7 @@ import org.openhab.binding.netatmo.internal.api.dto.HomeDataPerson;
import org.openhab.binding.netatmo.internal.api.dto.HomeEvent; import org.openhab.binding.netatmo.internal.api.dto.HomeEvent;
import org.openhab.binding.netatmo.internal.api.dto.HomeStatusModule; import org.openhab.binding.netatmo.internal.api.dto.HomeStatusModule;
import org.openhab.binding.netatmo.internal.api.dto.HomeStatusPerson; import org.openhab.binding.netatmo.internal.api.dto.HomeStatusPerson;
import org.openhab.binding.netatmo.internal.api.dto.NAHomeStatus;
import org.openhab.binding.netatmo.internal.api.dto.NAHomeStatus.HomeStatus; import org.openhab.binding.netatmo.internal.api.dto.NAHomeStatus.HomeStatus;
import org.openhab.binding.netatmo.internal.api.dto.NAObject; import org.openhab.binding.netatmo.internal.api.dto.NAObject;
import org.openhab.binding.netatmo.internal.config.HomeConfiguration; import org.openhab.binding.netatmo.internal.config.HomeConfiguration;
@ -69,34 +70,38 @@ class SecurityCapability extends RestCapability<SecurityApi> {
@Override @Override
protected void updateHomeData(HomeData homeData) { protected void updateHomeData(HomeData homeData) {
persons = homeData.getPersons(); if (homeData instanceof HomeData.Security securityData) {
modules = homeData.getModules(); persons = securityData.getPersons();
handler.getActiveChildren(FeatureArea.SECURITY).forEach(childHandler -> { modules = homeData.getModules();
String childId = childHandler.getId(); handler.getActiveChildren(FeatureArea.SECURITY).forEach(childHandler -> {
persons.getOpt(childId) String childId = childHandler.getId();
.ifPresentOrElse(personData -> childHandler.setNewData(personData.ignoringForThingUpdate()), () -> { persons.getOpt(childId).ifPresentOrElse(
modules.getOpt(childId) personData -> childHandler.setNewData(personData.ignoringForThingUpdate()), () -> {
.ifPresent(childData -> childHandler.setNewData(childData.ignoringForThingUpdate())); modules.getOpt(childId).ifPresent(
modules.values().stream().filter(module -> childId.equals(module.getBridge())) childData -> childHandler.setNewData(childData.ignoringForThingUpdate()));
.forEach(bridgedModule -> childHandler.setNewData(bridgedModule)); modules.values().stream().filter(module -> childId.equals(module.getBridge()))
}); .forEach(bridgedModule -> childHandler.setNewData(bridgedModule));
}); });
});
}
} }
@Override @Override
protected void updateHomeStatus(HomeStatus homeStatus) { protected void updateHomeStatus(HomeStatus homeStatus) {
NAObjectMap<HomeStatusPerson> persons = homeStatus.getPersons(); if (homeStatus instanceof NAHomeStatus.Security securityStatus) {
NAObjectMap<HomeStatusModule> modules = homeStatus.getModules(); NAObjectMap<HomeStatusPerson> persons = securityStatus.getPersons();
handler.getActiveChildren(FeatureArea.SECURITY).forEach(childHandler -> { NAObjectMap<HomeStatusModule> modules = securityStatus.getModules();
String childId = childHandler.getId(); handler.getActiveChildren(FeatureArea.SECURITY).forEach(childHandler -> {
persons.getOpt(childId).ifPresentOrElse(personData -> childHandler.setNewData(personData), () -> { String childId = childHandler.getId();
modules.getOpt(childId).ifPresent(childData -> { persons.getOpt(childId).ifPresentOrElse(personData -> childHandler.setNewData(personData), () -> {
childHandler.setNewData(childData); modules.getOpt(childId).ifPresent(childData -> {
modules.values().stream().filter(module -> childId.equals(module.getBridge())) childHandler.setNewData(childData);
.forEach(bridgedModule -> childHandler.setNewData(bridgedModule)); modules.values().stream().filter(module -> childId.equals(module.getBridge()))
.forEach(bridgedModule -> childHandler.setNewData(bridgedModule));
});
}); });
}); });
}); }
} }
@Override @Override

View File

@ -50,13 +50,12 @@ public class EnergyChannelHelper extends ChannelHelper {
@Override @Override
protected @Nullable State internalGetProperty(String channelId, NAThing data, Configuration config) { protected @Nullable State internalGetProperty(String channelId, NAThing data, Configuration config) {
if (data instanceof HomeData) { if (data instanceof HomeData.Energy energyData) {
HomeData homeData = (HomeData) data; SetpointMode thermMode = energyData.getThermMode();
SetpointMode thermMode = homeData.getThermMode(); ThermProgram currentProgram = energyData.getActiveProgram();
ThermProgram currentProgram = homeData.getActiveProgram();
switch (channelId) { switch (channelId) {
case CHANNEL_SETPOINT_DURATION: case CHANNEL_SETPOINT_DURATION:
return toQuantityType(homeData.getThermSetpointDefaultDuration(), Units.MINUTE); return toQuantityType(energyData.getThermSetpointDefaultDuration(), Units.MINUTE);
case CHANNEL_PLANNING: case CHANNEL_PLANNING:
return (currentProgram != null ? toStringType(currentProgram.getName()) : null); return (currentProgram != null ? toStringType(currentProgram.getName()) : null);
case CHANNEL_SETPOINT_END_TIME: case CHANNEL_SETPOINT_END_TIME:

View File

@ -54,7 +54,7 @@ public class PersonChannelHelper extends ChannelHelper {
HomeStatusPerson person = (HomeStatusPerson) naThing; HomeStatusPerson person = (HomeStatusPerson) naThing;
switch (channelId) { switch (channelId) {
case CHANNEL_PERSON_AT_HOME: case CHANNEL_PERSON_AT_HOME:
return OnOffType.from(!person.isOutOfSight()); return OnOffType.from(person.atHome());
case CHANNEL_LAST_SEEN: case CHANNEL_LAST_SEEN:
return toDateTimeType(person.getLastSeen()); return toDateTimeType(person.getLastSeen());
} }

View File

@ -17,15 +17,14 @@ import static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.toRawT
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.netatmo.internal.api.dto.HomeData; import org.openhab.binding.netatmo.internal.api.dto.HomeData;
import org.openhab.binding.netatmo.internal.api.dto.HomeDataPerson;
import org.openhab.binding.netatmo.internal.api.dto.HomeStatusPerson; import org.openhab.binding.netatmo.internal.api.dto.HomeStatusPerson;
import org.openhab.binding.netatmo.internal.api.dto.NAHomeStatus.HomeStatus; import org.openhab.binding.netatmo.internal.api.dto.NAHomeStatus;
import org.openhab.binding.netatmo.internal.api.dto.NAObject; import org.openhab.binding.netatmo.internal.api.dto.NAObject;
import org.openhab.binding.netatmo.internal.deserialization.NAObjectMap;
import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.DecimalType;
import org.openhab.core.types.State; import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType; import org.openhab.core.types.UnDefType;
@ -50,16 +49,11 @@ public class SecurityChannelHelper extends ChannelHelper {
@Override @Override
public void setNewData(@Nullable NAObject data) { public void setNewData(@Nullable NAObject data) {
super.setNewData(data); super.setNewData(data);
if (data instanceof HomeData) { if (data instanceof HomeData.Security securityData) {
HomeData homeData = (HomeData) data; knownIds = securityData.getKnownPersons().stream().map(HomeDataPerson::getId).toList();
knownIds = homeData.getPersons().values().stream().filter(person -> person.isKnown()).map(p -> p.getId()) } else if (data instanceof NAHomeStatus.Security securityStatus) {
.collect(Collectors.toList()); List<HomeStatusPerson> present = securityStatus.getPersons().values().stream()
} .filter(HomeStatusPerson::atHome).toList();
if (data instanceof HomeStatus) {
HomeStatus status = (HomeStatus) data;
NAObjectMap<HomeStatusPerson> allPersons = status.getPersons();
List<HomeStatusPerson> present = allPersons.values().stream().filter(p -> !p.isOutOfSight())
.collect(Collectors.toList());
persons = present.size(); persons = present.size();
unknowns = present.stream().filter(person -> !knownIds.contains(person.getId())).count(); unknowns = present.stream().filter(person -> !knownIds.contains(person.getId())).count();

View File

@ -18,7 +18,6 @@ import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Collectors;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jdt.annotation.Nullable;
@ -63,8 +62,7 @@ public class NetatmoThingTypeProvider implements ThingTypeProvider {
@Override @Override
public Collection<ThingType> getThingTypes(@Nullable Locale locale) { public Collection<ThingType> getThingTypes(@Nullable Locale locale) {
return ModuleType.AS_SET.stream().filter(mt -> mt != ModuleType.UNKNOWN) return ModuleType.AS_SET.stream().filter(mt -> mt != ModuleType.UNKNOWN)
.map(mt -> Optional.ofNullable(getThingType(mt.thingTypeUID, locale))).map(Optional::get) .map(mt -> Optional.ofNullable(getThingType(mt.thingTypeUID, locale))).map(Optional::get).toList();
.collect(Collectors.toList());
} }
@Override @Override
@ -95,7 +93,7 @@ public class NetatmoThingTypeProvider implements ThingTypeProvider {
private List<ChannelGroupDefinition> getGroupDefinitions(ModuleType thingType) { private List<ChannelGroupDefinition> getGroupDefinitions(ModuleType thingType) {
return thingType.getGroupTypes().stream().map(groupType -> new ChannelGroupDefinition(toGroupName(groupType), return thingType.getGroupTypes().stream().map(groupType -> new ChannelGroupDefinition(toGroupName(groupType),
new ChannelGroupTypeUID(BINDING_ID, groupType))).collect(Collectors.toList()); new ChannelGroupTypeUID(BINDING_ID, groupType))).toList();
} }
public static String toGroupName(String groupeTypeName) { public static String toGroupName(String groupeTypeName) {

View File

@ -70,8 +70,8 @@ channel-group-type.netatmo.rain.channel.sum-1.label = Rain 1h
channel-group-type.netatmo.rain.channel.sum-1.description = Quantity of water over last hour. channel-group-type.netatmo.rain.channel.sum-1.description = Quantity of water over last hour.
channel-group-type.netatmo.rain.channel.sum-24.label = Rain 24h channel-group-type.netatmo.rain.channel.sum-24.label = Rain 24h
channel-group-type.netatmo.rain.channel.sum-24.description = Quantity of water during the current day. channel-group-type.netatmo.rain.channel.sum-24.description = Quantity of water during the current day.
channel-group-type.netatmo.security.label = Home Security
channel-group-type.netatmo.security-event.label = Home Security Event channel-group-type.netatmo.security-event.label = Home Security Event
channel-group-type.netatmo.security.label = Home Security
channel-group-type.netatmo.setpoint.label = Setpoint channel-group-type.netatmo.setpoint.label = Setpoint
channel-group-type.netatmo.setpoint.channel.end.label = Setpoint End channel-group-type.netatmo.setpoint.channel.end.label = Setpoint End
channel-group-type.netatmo.setpoint.channel.end.description = End time of the currently applied setpoint. channel-group-type.netatmo.setpoint.channel.end.description = End time of the currently applied setpoint.
@ -81,13 +81,6 @@ channel-group-type.netatmo.signal.label = Signal
channel-group-type.netatmo.siren.label = Siren Status channel-group-type.netatmo.siren.label = Siren Status
channel-group-type.netatmo.status-doorbell.label = Camera Status channel-group-type.netatmo.status-doorbell.label = Camera Status
channel-group-type.netatmo.status.label = Camera Status channel-group-type.netatmo.status.label = Camera Status
channel-group-type.netatmo.sub-event.label = Sub Event
channel-group-type.netatmo.sub-event.channel.time.label = Sub-Event Timestamp
channel-group-type.netatmo.sub-event.channel.time.description = Moment when the sub-event occurred.
channel-group-type.netatmo.sub-event.channel.vignette.label = Vignette
channel-group-type.netatmo.sub-event.channel.vignette.description = Vignette of the Snapshot.
channel-group-type.netatmo.sub-event.channel.vignette-url.label = Vignette URL
channel-group-type.netatmo.sub-event.channel.vignette-url.description = URL of the vignette.
channel-group-type.netatmo.sub-event-doorbell.label = Sub Event channel-group-type.netatmo.sub-event-doorbell.label = Sub Event
channel-group-type.netatmo.sub-event-doorbell.channel.time.label = Sub-Event Timestamp channel-group-type.netatmo.sub-event-doorbell.channel.time.label = Sub-Event Timestamp
channel-group-type.netatmo.sub-event-doorbell.channel.time.description = Moment when the sub-event occurred. channel-group-type.netatmo.sub-event-doorbell.channel.time.description = Moment when the sub-event occurred.
@ -95,6 +88,13 @@ channel-group-type.netatmo.sub-event-doorbell.channel.vignette.label = Vignette
channel-group-type.netatmo.sub-event-doorbell.channel.vignette.description = Vignette of the Snapshot. channel-group-type.netatmo.sub-event-doorbell.channel.vignette.description = Vignette of the Snapshot.
channel-group-type.netatmo.sub-event-doorbell.channel.vignette-url.label = Vignette URL channel-group-type.netatmo.sub-event-doorbell.channel.vignette-url.label = Vignette URL
channel-group-type.netatmo.sub-event-doorbell.channel.vignette-url.description = URL of the vignette. channel-group-type.netatmo.sub-event-doorbell.channel.vignette-url.description = URL of the vignette.
channel-group-type.netatmo.sub-event.label = Sub Event
channel-group-type.netatmo.sub-event.channel.time.label = Sub-Event Timestamp
channel-group-type.netatmo.sub-event.channel.time.description = Moment when the sub-event occurred.
channel-group-type.netatmo.sub-event.channel.vignette.label = Vignette
channel-group-type.netatmo.sub-event.channel.vignette.description = Vignette of the Snapshot.
channel-group-type.netatmo.sub-event.channel.vignette-url.label = Vignette URL
channel-group-type.netatmo.sub-event.channel.vignette-url.description = URL of the vignette.
channel-group-type.netatmo.tag.label = Door Tag channel-group-type.netatmo.tag.label = Door Tag
channel-group-type.netatmo.temperature-extended.label = Temperature channel-group-type.netatmo.temperature-extended.label = Temperature
channel-group-type.netatmo.temperature-extended.channel.max-time.label = Today Max Timestamp channel-group-type.netatmo.temperature-extended.channel.max-time.label = Today Max Timestamp
@ -189,6 +189,8 @@ channel-type.netatmo.event-subtype.state.option.BATTERY_LOW = Battery low
channel-type.netatmo.event-subtype.state.option.BATTERY_VERY_LOW = Battery very low channel-type.netatmo.event-subtype.state.option.BATTERY_VERY_LOW = Battery very low
channel-type.netatmo.event-subtype.state.option.SMOKE_CLEARED = Smoke cleared channel-type.netatmo.event-subtype.state.option.SMOKE_CLEARED = Smoke cleared
channel-type.netatmo.event-subtype.state.option.SMOKE_DETECTED = Smoke detected channel-type.netatmo.event-subtype.state.option.SMOKE_DETECTED = Smoke detected
channel-type.netatmo.event-subtype.state.option.HUSH_ACTIVATED = Smoke detection activated
channel-type.netatmo.event-subtype.state.option.HUSH_DEACTIVATED = Smoke detection deactivated
channel-type.netatmo.event-subtype.state.option.WIFI_STATUS_OK = Wi-Fi status ok channel-type.netatmo.event-subtype.state.option.WIFI_STATUS_OK = Wi-Fi status ok
channel-type.netatmo.event-subtype.state.option.WIFI_STATUS_ERROR = Wi-Fi status error channel-type.netatmo.event-subtype.state.option.WIFI_STATUS_ERROR = Wi-Fi status error
channel-type.netatmo.event-subtype.state.option.CO_OK = Carbon Monoxide OK channel-type.netatmo.event-subtype.state.option.CO_OK = Carbon Monoxide OK

View File

@ -404,6 +404,8 @@
<option value="BATTERY_VERY_LOW">Battery very low</option> <option value="BATTERY_VERY_LOW">Battery very low</option>
<option value="SMOKE_CLEARED">Smoke cleared</option> <option value="SMOKE_CLEARED">Smoke cleared</option>
<option value="SMOKE_DETECTED">Smoke detected</option> <option value="SMOKE_DETECTED">Smoke detected</option>
<option value="HUSH_ACTIVATED">Smoke detection activated</option>
<option value="HUSH_DEACTIVATED">Smoke detection deactivated</option>
<option value="WIFI_STATUS_OK">Wi-Fi status ok</option> <option value="WIFI_STATUS_OK">Wi-Fi status ok</option>
<option value="WIFI_STATUS_ERROR">Wi-Fi status error</option> <option value="WIFI_STATUS_ERROR">Wi-Fi status error</option>
<option value="CO_OK">Carbon Monoxide OK</option> <option value="CO_OK">Carbon Monoxide OK</option>

View File

@ -17,6 +17,7 @@ import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
import java.util.Collections; import java.util.Collections;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.openhab.binding.netatmo.internal.api.data.EventType; import org.openhab.binding.netatmo.internal.api.data.EventType;
@ -27,9 +28,10 @@ import org.openhab.core.types.State;
/** /**
* @author Sven Strohschein - Initial contribution * @author Sven Strohschein - Initial contribution
*/ */
@NonNullByDefault
public class EventCameraChannelHelperTest { public class EventCameraChannelHelperTest {
private EventCameraChannelHelper helper; private @NonNullByDefault({}) EventCameraChannelHelper helper;
@BeforeEach @BeforeEach
public void before() { public void before() {