]> git.basschouten.com Git - openhab-addons.git/commitdiff
[Netatmo] Enhance webhook handling and dispatching (#15045)
authorGaël L'hopital <gael@lhopital.org>
Tue, 13 Jun 2023 18:35:45 +0000 (20:35 +0200)
committerGitHub <noreply@github.com>
Tue, 13 Jun 2023 18:35:45 +0000 (20:35 +0200)
* 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>
24 files changed:
bundles/org.openhab.binding.netatmo/README.md
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/SecurityApi.java
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/data/EventSubType.java
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/data/EventType.java
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/HomeData.java
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/HomeStatusPerson.java
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/api/dto/NAHomeStatus.java
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/deserialization/NADeserializer.java
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/deserialization/NAPushTypeDeserializer.java
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/ApiBridgeHandler.java
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/CommonInterface.java
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/AlarmEventCapability.java
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/CameraCapability.java
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/Capability.java
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/EnergyCapability.java
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/PersonCapability.java
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/capability/SecurityCapability.java
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/EnergyChannelHelper.java
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/PersonChannelHelper.java
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/handler/channelhelper/SecurityChannelHelper.java
bundles/org.openhab.binding.netatmo/src/main/java/org/openhab/binding/netatmo/internal/providers/NetatmoThingTypeProvider.java
bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/i18n/netatmo.properties
bundles/org.openhab.binding.netatmo/src/main/resources/OH-INF/thing/channels.xml
bundles/org.openhab.binding.netatmo/src/test/java/org/openhab/binding/netatmo/internal/handler/channelhelper/EventCameraChannelHelperTest.java

index 93fe27c99f235cf610007bcc0ee7710342e56109..6cd14ea6e8e1ba3c06a037b792a9fc0583c176af 100644 (file)
@@ -460,6 +460,7 @@ The Home thing has the following configuration elements:
 | securityId | String | No       | Id of a home holding security monitoring devices                                    |
 
 At least one of these parameter must be filled - at most two : 
+
 * id or securityId
 * id or energyId
 * securityId and energyId
index 8acaef8c72e08ce618fc311e0ddbba3a121c1dc4..1b82c00f983aad0221e331ad339efd2a76c76388 100644 (file)
@@ -18,7 +18,6 @@ import java.net.URI;
 import java.time.ZonedDateTime;
 import java.util.Comparator;
 import java.util.List;
-import java.util.stream.Collectors;
 
 import javax.ws.rs.core.UriBuilder;
 
@@ -94,7 +93,7 @@ public class SecurityApi extends RestManager {
 
         // Remove unneeded events being before oldestKnown
         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 {
index 7cf497e66c86da582c69982d8545dffaeff8ad4e..44748ffc103ebe5d866dcd8be3dd2f1414022c59 100644 (file)
@@ -43,6 +43,8 @@ public enum EventSubType {
     BATTERY_VERY_LOW(1, EventType.BATTERY_STATUS),
     SMOKE_CLEARED(0, 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_ERROR(1, EventType.SOUND_TEST),
     DETECTOR_READY(0, EventType.TAMPERED),
index dfb8de510a7deab8e1669a670e81fa2f01311b79..c7ff566c7de830f06ed4a6202e3ea91348e1a121 100644 (file)
@@ -28,6 +28,8 @@ import com.google.gson.annotations.SerializedName;
 @NonNullByDefault
 public enum EventType {
     UNKNOWN(),
+    @SerializedName("webhook_activation") // Ack of a 'webhook set' Api Call
+    WEBHOOK_ACTIVATION(ModuleType.ACCOUNT),
 
     @SerializedName("person") // When the Indoor Camera detects a face
     PERSON(ModuleType.PERSON, ModuleType.WELCOME),
@@ -71,6 +73,15 @@ public enum EventType {
     @SerializedName("module_end_update") // Module's firmware update is over
     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
     CONNECTION(ModuleType.WELCOME, ModuleType.PRESENCE),
 
index 78cc04350e2c58bb404d1c881513974f7fc802ef..0a971c6b8df577b0d89d063f8f67c46b26b088f7 100644 (file)
@@ -38,17 +38,50 @@ public class HomeData extends NAThing implements NAModule, LocationEx {
     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[] coordinates = {};
     private @Nullable String country;
     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<HomeDataModule> modules = new NAObjectMap<>();
 
@@ -77,26 +110,6 @@ public class HomeData extends NAThing implements NAModule, LocationEx {
         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() {
         return rooms;
     }
@@ -108,12 +121,4 @@ public class HomeData extends NAThing implements NAModule, LocationEx {
     public Set<FeatureArea> getFeatures() {
         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);
-    }
 }
index 17d02300895a2657c827df0b25db46169f98f8a2..8ede32652e73c101ff178a1ad4370ef4a29e365a 100644 (file)
@@ -31,7 +31,7 @@ public class HomeStatusPerson extends NAThing {
         return ModuleType.PERSON;
     }
 
-    public boolean isOutOfSight() {
-        return outOfSight;
+    public boolean atHome() {
+        return !outOfSight;
     }
 }
index c7a18ec075cb7332dc61619fee0bfaa758391196..f13e2d97644e2c7c8938c8aadc6389518bdc909b 100644 (file)
@@ -32,22 +32,26 @@ public class NAHomeStatus {
 
     public class HomeStatus extends NAThing {
         private @Nullable NAObjectMap<HomeStatusModule> modules;
-        private @Nullable NAObjectMap<HomeStatusPerson> persons;
-        private @Nullable NAObjectMap<Room> rooms;
 
         public NAObjectMap<HomeStatusModule> getModules() {
             NAObjectMap<HomeStatusModule> localModules = modules;
             return localModules != null ? localModules : new NAObjectMap<>();
         }
+    }
 
-        public NAObjectMap<HomeStatusPerson> getPersons() {
-            NAObjectMap<HomeStatusPerson> localPersons = persons;
-            return localPersons != null ? localPersons : new NAObjectMap<>();
-        }
+    public class Energy extends HomeStatus {
+        private NAObjectMap<Room> rooms = new NAObjectMap<>();
 
         public NAObjectMap<Room> getRooms() {
-            NAObjectMap<Room> localRooms = rooms;
-            return localRooms != null ? localRooms : new NAObjectMap<>();
+            return rooms;
+        }
+    }
+
+    public class Security extends HomeStatus {
+        private NAObjectMap<HomeStatusPerson> persons = new NAObjectMap<>();
+
+        public NAObjectMap<HomeStatusPerson> getPersons() {
+            return persons;
         }
     }
 
index 4213b18bdcbcfaa1abc4a23c385220906c2285fe..93c931d3c1581aa4fcf0c20cc0e97a3f2acbc25a 100644 (file)
@@ -19,6 +19,9 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
 import org.openhab.binding.netatmo.internal.api.NetatmoException;
 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.library.types.OnOffType;
 import org.openhab.core.library.types.OpenClosedType;
@@ -49,22 +52,26 @@ public class NADeserializer {
                 .registerTypeAdapter(NAObjectMap.class, new NAObjectMapDeserializer())
                 .registerTypeAdapter(NAPushType.class, new NAPushTypeDeserializer())
                 .registerTypeAdapter(ModuleType.class, new ModuleTypeDeserializer())
-                .registerTypeAdapter(ZonedDateTime.class,
-                        (JsonDeserializer<ZonedDateTime>) (json, type, jsonDeserializationContext) -> {
-                            long netatmoTS = json.getAsJsonPrimitive().getAsLong();
-                            Instant i = Instant.ofEpochSecond(netatmoTS);
-                            return ZonedDateTime.ofInstant(i, timeZoneProvider.getTimeZone());
-                        })
+                .registerTypeAdapter(HomeStatus.class,
+                        (JsonDeserializer<HomeStatus>) (json, type, context) -> context.deserialize(json,
+                                json.getAsJsonObject().has("persons") ? NAHomeStatus.Security.class
+                                        : NAHomeStatus.Energy.class))
+                .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,
-                        (JsonDeserializer<OnOffType>) (json, type, jsonDeserializationContext) -> OnOffType
+                        (JsonDeserializer<OnOffType>) (json, type, context) -> OnOffType
                                 .from(json.getAsJsonPrimitive().getAsString()))
-                .registerTypeAdapter(OpenClosedType.class,
-                        (JsonDeserializer<OpenClosedType>) (json, type, jsonDeserializationContext) -> {
-                            String value = json.getAsJsonPrimitive().getAsString().toUpperCase();
-                            return "TRUE".equals(value) || "1".equals(value) ? OpenClosedType.CLOSED
-                                    : OpenClosedType.OPEN;
-                        })
-                .create();
+                .registerTypeAdapter(OpenClosedType.class, (JsonDeserializer<OpenClosedType>) (json, type, context) -> {
+                    String value = json.getAsJsonPrimitive().getAsString().toUpperCase();
+                    return "TRUE".equals(value) || "1".equals(value) ? OpenClosedType.CLOSED : OpenClosedType.OPEN;
+                }).create();
     }
 
     public <T> T deserialize(Class<T> clazz, String json) throws NetatmoException {
index 69767428b6d2150b8c8654e722e9e017599bb03e..d552ddea868d052972a64bb854cf9e3bfdd6d90a 100644 (file)
@@ -32,6 +32,7 @@ import com.google.gson.JsonElement;
  */
 @NonNullByDefault
 class NAPushTypeDeserializer implements JsonDeserializer<NAPushType> {
+
     private final Logger logger = LoggerFactory.getLogger(NAPushTypeDeserializer.class);
 
     @Override
@@ -40,15 +41,19 @@ class NAPushTypeDeserializer implements JsonDeserializer<NAPushType> {
         final String[] elements = string.split("-");
         ModuleType moduleType = ModuleType.UNKNOWN;
         EventType eventType = EventType.UNKNOWN;
+
         if (elements.length == 2) {
             moduleType = fromNetatmoObject(elements[0]);
             eventType = fromEvent(elements[1]);
-        } else {
-            logger.warn("Unexpected syntax received for push_type field : {}", string);
+        } else if (elements.length == 1) {
+            moduleType = ModuleType.ACCOUNT;
+            eventType = fromEvent(string);
         }
+
         if (moduleType.equals(ModuleType.UNKNOWN) || eventType.equals(EventType.UNKNOWN)) {
             logger.warn("Unknown module or event type : {}, deserialized to '{}-{}'", string, moduleType, eventType);
         }
+
         return new NAPushType(moduleType, eventType);
     }
 
index 9e7ad42a39681cd69891f4d457cbaba757669dbc..c3b8209694609816a0843833023060b5143476cb 100644 (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.Scope;
 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.NAMain;
 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))
                         .forEach(home -> {
                             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<>();
 
                                 home.getRooms().values().stream().forEach(room -> {
index c80eec1df168366819abe9bb115767c5ba8ccb5b..91637754459dc5b1e84e4ed1a21f040d1c0ceba8 100644 (file)
@@ -18,7 +18,6 @@ import java.util.Objects;
 import java.util.Optional;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.TimeUnit;
-import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
@@ -134,8 +133,7 @@ public interface CommonInterface {
         if (thing instanceof Bridge) {
             return ((Bridge) thing).getThings().stream().filter(Thing::isEnabled)
                     .filter(th -> th.getStatusInfo().getStatusDetail() != ThingStatusDetail.BRIDGE_OFFLINE)
-                    .map(Thing::getHandler).filter(Objects::nonNull).map(CommonInterface.class::cast)
-                    .collect(Collectors.toList());
+                    .map(Thing::getHandler).filter(Objects::nonNull).map(CommonInterface.class::cast).toList();
         }
         return List.of();
     }
index 83c4a1256714f185651158150e646d1c3b668d78..08c380d485078852bd35d8aa4c983da561a4b203 100644 (file)
  */
 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 org.eclipse.jdt.annotation.NonNullByDefault;
 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.channelhelper.ChannelHelper;
 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
@@ -34,6 +41,22 @@ public class AlarmEventCapability extends HomeSecurityThingCapability {
         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
     public List<NAObject> updateReadings() {
         return getSecurityCapability().map(cap -> cap.getDeviceLastEvent(handler.getId(), moduleType.apiName))
index fbc6418c87231d8f786258104e39ff7046688c31..c63294a5620aad20e343861b8c19f3d331cf3d67 100644 (file)
@@ -17,7 +17,6 @@ import static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.*;
 
 import java.util.ArrayList;
 import java.util.List;
-import java.util.stream.Collectors;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
 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.core.library.types.OnOffType;
 import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.Thing;
 import org.openhab.core.thing.ThingUID;
 import org.openhab.core.types.Command;
+import org.openhab.core.types.State;
 import org.openhab.core.types.StateOption;
 import org.openhab.core.types.UnDefType;
 
@@ -53,6 +54,8 @@ public class CameraCapability extends HomeSecurityThingCapability {
 
     protected @Nullable String localUrl;
     protected @Nullable String vpnUrl;
+    private boolean hasSubEventGroup;
+    private boolean hasLastEventGroup;
 
     public CameraCapability(CommonInterface handler, NetatmoDescriptionProvider descriptionProvider,
             List<ChannelHelper> channelHelpers) {
@@ -63,6 +66,13 @@ public class CameraCapability extends HomeSecurityThingCapability {
                         "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
     public void updateHomeStatusModule(HomeStatusModule newData) {
         super.updateHomeStatusModule(newData);
@@ -85,23 +95,13 @@ public class CameraCapability extends HomeSecurityThingCapability {
     protected void updateWebhookEvent(WebhookEvent event) {
         super.updateWebhookEvent(event);
 
-        final ThingUID thingUid = handler.getThing().getUID();
-        handler.updateState(new ChannelUID(thingUid, GROUP_SUB_EVENT, CHANNEL_EVENT_TYPE),
-                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()));
+        if (hasSubEventGroup) {
+            updateSubGroup(event, thing.getUID(), GROUP_SUB_EVENT);
+        }
 
-        final String message = event.getName();
-        handler.updateState(new ChannelUID(thingUid, GROUP_SUB_EVENT, CHANNEL_EVENT_MESSAGE),
-                message == null || message.isBlank() ? UnDefType.NULL : toStringType(message));
+        if (hasLastEventGroup) {
+            updateSubGroup(event, thing.getUID(), GROUP_LAST_EVENT);
+        }
 
         // 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.
@@ -111,6 +111,25 @@ public class CameraCapability extends HomeSecurityThingCapability {
         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
     public void handleCommand(String channelName, Command command) {
         if (command instanceof OnOffType && CHANNEL_MONITORING.equals(channelName)) {
@@ -125,8 +144,8 @@ public class CameraCapability extends HomeSecurityThingCapability {
         super.beforeNewData();
         getSecurityCapability().ifPresent(cap -> {
             NAObjectMap<HomeDataPerson> persons = cap.getPersons();
-            descriptionProvider.setStateOptions(personChannelUID, persons.values().stream()
-                    .map(p -> new StateOption(p.getId(), p.getName())).collect(Collectors.toList()));
+            descriptionProvider.setStateOptions(personChannelUID,
+                    persons.values().stream().map(p -> new StateOption(p.getId(), p.getName())).toList());
         });
     }
 
index 9a88c2d4852fb8ad63ad06d37f5381315ecb804f..f7c75e0038f98f7f65240791ff40cec5162fa839 100644 (file)
@@ -63,32 +63,32 @@ public class Capability {
 
     public final @Nullable String setNewData(NAObject newData) {
         beforeNewData();
-        if (newData instanceof HomeData) {
-            updateHomeData((HomeData) newData);
+        if (newData instanceof HomeData homeData) {
+            updateHomeData(homeData);
         }
-        if (newData instanceof HomeStatus) {
-            updateHomeStatus((HomeStatus) newData);
+        if (newData instanceof HomeStatus homeStatus) {
+            updateHomeStatus(homeStatus);
         }
-        if (newData instanceof HomeStatusModule) {
-            updateHomeStatusModule((HomeStatusModule) newData);
+        if (newData instanceof HomeStatusModule homeStatusModule) {
+            updateHomeStatusModule(homeStatusModule);
         }
-        if (newData instanceof Event) {
-            updateEvent((Event) newData);
-        }
-        if (newData instanceof WebhookEvent) {
-            updateWebhookEvent((WebhookEvent) newData);
-        }
-        if (newData instanceof HomeEvent) {
-            updateHomeEvent((HomeEvent) 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 NAThing) {
-            updateNAThing((NAThing) newData);
+
+        if (newData instanceof NAThing naThing) {
+            updateNAThing(naThing);
         }
-        if (newData instanceof NAMain) {
-            updateNAMain((NAMain) newData);
+        if (newData instanceof NAMain naMain) {
+            updateNAMain(naMain);
         }
-        if (newData instanceof Device) {
-            updateNADevice((Device) newData);
+        if (newData instanceof Device device) {
+            updateNADevice(device);
         }
         afterNewData(newData);
         return statusReason;
index efd6eb22572211de33fa7aa28ae60aae46281077..832d2252dcd0adc529cefccacb119c2849685aff 100644 (file)
@@ -15,7 +15,6 @@ package org.openhab.binding.netatmo.internal.handler.capability;
 import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
 
 import java.time.ZonedDateTime;
-import java.util.stream.Collectors;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
 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.HomeDataRoom;
 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.Room;
 import org.openhab.binding.netatmo.internal.config.HomeConfiguration;
@@ -65,38 +65,41 @@ public class EnergyCapability extends RestCapability<EnergyApi> {
 
     @Override
     protected void updateHomeData(HomeData homeData) {
-        NAObjectMap<HomeDataRoom> rooms = homeData.getRooms();
-        NAObjectMap<HomeDataModule> modules = homeData.getModules();
-        handler.getActiveChildren(FeatureArea.ENERGY).forEach(childHandler -> {
-            String childId = childHandler.getId();
-            rooms.getOpt(childId)
-                    .ifPresentOrElse(roomData -> childHandler.setNewData(roomData.ignoringForThingUpdate()), () -> {
-                        modules.getOpt(childId)
-                                .ifPresent(childData -> childHandler.setNewData(childData.ignoringForThingUpdate()));
-                        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()))
-                        .collect(Collectors.toList()));
-        setPointDefaultDuration = homeData.getThermSetpointDefaultDuration();
+        if (homeData instanceof HomeData.Energy energyData) {
+            NAObjectMap<HomeDataRoom> rooms = energyData.getRooms();
+            NAObjectMap<HomeDataModule> modules = energyData.getModules();
+            handler.getActiveChildren(FeatureArea.ENERGY).forEach(childHandler -> {
+                String childId = childHandler.getId();
+                rooms.getOpt(childId)
+                        .ifPresentOrElse(roomData -> childHandler.setNewData(roomData.ignoringForThingUpdate()), () -> {
+                            modules.getOpt(childId).ifPresent(
+                                    childData -> childHandler.setNewData(childData.ignoringForThingUpdate()));
+                            modules.values().stream().filter(module -> childId.equals(module.getBridge()))
+                                    .forEach(bridgedModule -> childHandler.setNewData(bridgedModule));
+                        });
+            });
+            descriptionProvider.setStateOptions(new ChannelUID(thing.getUID(), GROUP_ENERGY, CHANNEL_PLANNING),
+                    energyData.getThermSchedules().stream().map(p -> new StateOption(p.getId(), p.getName())).toList());
+            setPointDefaultDuration = energyData.getThermSetpointDefaultDuration();
+        }
     }
 
     @Override
     protected void updateHomeStatus(HomeStatus homeStatus) {
-        NAObjectMap<Room> rooms = homeStatus.getRooms();
-        NAObjectMap<HomeStatusModule> modules = homeStatus.getModules();
-        handler.getActiveChildren(FeatureArea.ENERGY).forEach(childHandler -> {
-            String childId = childHandler.getId();
-            rooms.getOpt(childId).ifPresentOrElse(roomData -> childHandler.setNewData(roomData), () -> {
-                modules.getOpt(childId).ifPresent(moduleData -> {
-                    childHandler.setNewData(moduleData);
-                    modules.values().stream().filter(module -> childId.equals(module.getBridge()))
-                            .forEach(bridgedModule -> childHandler.setNewData(bridgedModule));
+        if (homeStatus instanceof NAHomeStatus.Energy energyStatus) {
+            NAObjectMap<Room> rooms = energyStatus.getRooms();
+            NAObjectMap<HomeStatusModule> modules = energyStatus.getModules();
+            handler.getActiveChildren(FeatureArea.ENERGY).forEach(childHandler -> {
+                String childId = childHandler.getId();
+                rooms.getOpt(childId).ifPresentOrElse(roomData -> childHandler.setNewData(roomData), () -> {
+                    modules.getOpt(childId).ifPresent(moduleData -> {
+                        childHandler.setNewData(moduleData);
+                        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) {
index ce73a84cafd0fc1488976021df5d984f5810aa2b..7881aecff595c3e080a31f25adc288a75d4302d4 100644 (file)
 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.time.ZonedDateTime;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
 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.HomeEvent;
 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.channelhelper.ChannelHelper;
 import org.openhab.binding.netatmo.internal.providers.NetatmoDescriptionProvider;
 import org.openhab.core.library.types.OnOffType;
 import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.ThingUID;
 import org.openhab.core.types.Command;
 import org.openhab.core.types.StateOption;
+import org.openhab.core.types.UnDefType;
 
 /**
  * {@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()
                     .filter(module -> module.getType() == ModuleType.WELCOME);
             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
     public void updateEvent(Event event) {
         super.updateEvent(event);
index c2b8fd41a81f9d01e882ee5916d4999554d08fe8..84e422ae208927b80dc2157c95b8dea928f812ef 100644 (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.HomeStatusModule;
 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.NAObject;
 import org.openhab.binding.netatmo.internal.config.HomeConfiguration;
@@ -69,34 +70,38 @@ class SecurityCapability extends RestCapability<SecurityApi> {
 
     @Override
     protected void updateHomeData(HomeData homeData) {
-        persons = homeData.getPersons();
-        modules = homeData.getModules();
-        handler.getActiveChildren(FeatureArea.SECURITY).forEach(childHandler -> {
-            String childId = childHandler.getId();
-            persons.getOpt(childId)
-                    .ifPresentOrElse(personData -> childHandler.setNewData(personData.ignoringForThingUpdate()), () -> {
-                        modules.getOpt(childId)
-                                .ifPresent(childData -> childHandler.setNewData(childData.ignoringForThingUpdate()));
-                        modules.values().stream().filter(module -> childId.equals(module.getBridge()))
-                                .forEach(bridgedModule -> childHandler.setNewData(bridgedModule));
-                    });
-        });
+        if (homeData instanceof HomeData.Security securityData) {
+            persons = securityData.getPersons();
+            modules = homeData.getModules();
+            handler.getActiveChildren(FeatureArea.SECURITY).forEach(childHandler -> {
+                String childId = childHandler.getId();
+                persons.getOpt(childId).ifPresentOrElse(
+                        personData -> childHandler.setNewData(personData.ignoringForThingUpdate()), () -> {
+                            modules.getOpt(childId).ifPresent(
+                                    childData -> childHandler.setNewData(childData.ignoringForThingUpdate()));
+                            modules.values().stream().filter(module -> childId.equals(module.getBridge()))
+                                    .forEach(bridgedModule -> childHandler.setNewData(bridgedModule));
+                        });
+            });
+        }
     }
 
     @Override
     protected void updateHomeStatus(HomeStatus homeStatus) {
-        NAObjectMap<HomeStatusPerson> persons = homeStatus.getPersons();
-        NAObjectMap<HomeStatusModule> modules = homeStatus.getModules();
-        handler.getActiveChildren(FeatureArea.SECURITY).forEach(childHandler -> {
-            String childId = childHandler.getId();
-            persons.getOpt(childId).ifPresentOrElse(personData -> childHandler.setNewData(personData), () -> {
-                modules.getOpt(childId).ifPresent(childData -> {
-                    childHandler.setNewData(childData);
-                    modules.values().stream().filter(module -> childId.equals(module.getBridge()))
-                            .forEach(bridgedModule -> childHandler.setNewData(bridgedModule));
+        if (homeStatus instanceof NAHomeStatus.Security securityStatus) {
+            NAObjectMap<HomeStatusPerson> persons = securityStatus.getPersons();
+            NAObjectMap<HomeStatusModule> modules = securityStatus.getModules();
+            handler.getActiveChildren(FeatureArea.SECURITY).forEach(childHandler -> {
+                String childId = childHandler.getId();
+                persons.getOpt(childId).ifPresentOrElse(personData -> childHandler.setNewData(personData), () -> {
+                    modules.getOpt(childId).ifPresent(childData -> {
+                        childHandler.setNewData(childData);
+                        modules.values().stream().filter(module -> childId.equals(module.getBridge()))
+                                .forEach(bridgedModule -> childHandler.setNewData(bridgedModule));
+                    });
                 });
             });
-        });
+        }
     }
 
     @Override
index 3f9852d1a6b107032e8ab219453f4a479f341a7c..b6fdf8e2a6cfdb1d82c749c187bfdf08e640225a 100644 (file)
@@ -50,13 +50,12 @@ public class EnergyChannelHelper extends ChannelHelper {
 
     @Override
     protected @Nullable State internalGetProperty(String channelId, NAThing data, Configuration config) {
-        if (data instanceof HomeData) {
-            HomeData homeData = (HomeData) data;
-            SetpointMode thermMode = homeData.getThermMode();
-            ThermProgram currentProgram = homeData.getActiveProgram();
+        if (data instanceof HomeData.Energy energyData) {
+            SetpointMode thermMode = energyData.getThermMode();
+            ThermProgram currentProgram = energyData.getActiveProgram();
             switch (channelId) {
                 case CHANNEL_SETPOINT_DURATION:
-                    return toQuantityType(homeData.getThermSetpointDefaultDuration(), Units.MINUTE);
+                    return toQuantityType(energyData.getThermSetpointDefaultDuration(), Units.MINUTE);
                 case CHANNEL_PLANNING:
                     return (currentProgram != null ? toStringType(currentProgram.getName()) : null);
                 case CHANNEL_SETPOINT_END_TIME:
index affb4c17f7932025f067df1c2b8746e705f2e58b..6ad93027707506552211de06d243ee3135f7a995 100644 (file)
@@ -54,7 +54,7 @@ public class PersonChannelHelper extends ChannelHelper {
             HomeStatusPerson person = (HomeStatusPerson) naThing;
             switch (channelId) {
                 case CHANNEL_PERSON_AT_HOME:
-                    return OnOffType.from(!person.isOutOfSight());
+                    return OnOffType.from(person.atHome());
                 case CHANNEL_LAST_SEEN:
                     return toDateTimeType(person.getLastSeen());
             }
index 44732fcebd6a242d0d5aaddd9288643ccfb956c4..ea1886122e6562bed4dd587ba8257b1b593544ad 100644 (file)
@@ -17,15 +17,14 @@ import static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.toRawT
 
 import java.util.List;
 import java.util.Set;
-import java.util.stream.Collectors;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
 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.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.deserialization.NAObjectMap;
 import org.openhab.core.library.types.DecimalType;
 import org.openhab.core.types.State;
 import org.openhab.core.types.UnDefType;
@@ -50,16 +49,11 @@ public class SecurityChannelHelper extends ChannelHelper {
     @Override
     public void setNewData(@Nullable NAObject data) {
         super.setNewData(data);
-        if (data instanceof HomeData) {
-            HomeData homeData = (HomeData) data;
-            knownIds = homeData.getPersons().values().stream().filter(person -> person.isKnown()).map(p -> p.getId())
-                    .collect(Collectors.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());
+        if (data instanceof HomeData.Security securityData) {
+            knownIds = securityData.getKnownPersons().stream().map(HomeDataPerson::getId).toList();
+        } else if (data instanceof NAHomeStatus.Security securityStatus) {
+            List<HomeStatusPerson> present = securityStatus.getPersons().values().stream()
+                    .filter(HomeStatusPerson::atHome).toList();
 
             persons = present.size();
             unknowns = present.stream().filter(person -> !knownIds.contains(person.getId())).count();
index 320f7f1e9d67100fbdf194ea870ee21383f8e4cf..cb66f0e220775351481176b79d1a21c225ba9902 100644 (file)
@@ -18,7 +18,6 @@ import java.util.Collection;
 import java.util.List;
 import java.util.Locale;
 import java.util.Optional;
-import java.util.stream.Collectors;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
@@ -63,8 +62,7 @@ public class NetatmoThingTypeProvider implements ThingTypeProvider {
     @Override
     public Collection<ThingType> getThingTypes(@Nullable Locale locale) {
         return ModuleType.AS_SET.stream().filter(mt -> mt != ModuleType.UNKNOWN)
-                .map(mt -> Optional.ofNullable(getThingType(mt.thingTypeUID, locale))).map(Optional::get)
-                .collect(Collectors.toList());
+                .map(mt -> Optional.ofNullable(getThingType(mt.thingTypeUID, locale))).map(Optional::get).toList();
     }
 
     @Override
@@ -95,7 +93,7 @@ public class NetatmoThingTypeProvider implements ThingTypeProvider {
 
     private List<ChannelGroupDefinition> getGroupDefinitions(ModuleType thingType) {
         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) {
index a7eb434fbf4bc9f0c09a9e89eb617b221baa0fe8..d86de3707ebd6a6455e68deadc7fceaf839ef225 100644 (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-24.label = Rain 24h
 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.label = Home Security
 channel-group-type.netatmo.setpoint.label = Setpoint
 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.
@@ -81,13 +81,6 @@ channel-group-type.netatmo.signal.label = Signal
 channel-group-type.netatmo.siren.label = Siren Status
 channel-group-type.netatmo.status-doorbell.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.channel.time.label = Sub-Event Timestamp
 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-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.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.temperature-extended.label = Temperature
 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.SMOKE_CLEARED = Smoke cleared
 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_ERROR = Wi-Fi status error
 channel-type.netatmo.event-subtype.state.option.CO_OK = Carbon Monoxide OK
index 08efba66f0c085f26555032a13b25265757c6825..ae4b96ff0ea205b846e5038bba5ba8a3cfe7c7ea 100644 (file)
                                <option value="BATTERY_VERY_LOW">Battery very low</option>
                                <option value="SMOKE_CLEARED">Smoke cleared</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_ERROR">Wi-Fi status error</option>
                                <option value="CO_OK">Carbon Monoxide OK</option>
index 3460529490e4ad229280d0d5da9e2afd7289281c..e96a07d34ce70acd03acf81aa10f53873cf924e0 100644 (file)
@@ -17,6 +17,7 @@ import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
 
 import java.util.Collections;
 
+import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.openhab.binding.netatmo.internal.api.data.EventType;
@@ -27,9 +28,10 @@ import org.openhab.core.types.State;
 /**
  * @author Sven Strohschein - Initial contribution
  */
+@NonNullByDefault
 public class EventCameraChannelHelperTest {
 
-    private EventCameraChannelHelper helper;
+    private @NonNullByDefault({}) EventCameraChannelHelper helper;
 
     @BeforeEach
     public void before() {