]> git.basschouten.com Git - openhab-addons.git/commitdiff
[freeboxos] Complete Alarm system handling (#17233)
authorGaël L'hopital <gael@lhopital.org>
Tue, 20 Aug 2024 16:24:37 +0000 (18:24 +0200)
committerGitHub <noreply@github.com>
Tue, 20 Aug 2024 16:24:37 +0000 (18:24 +0200)
* Initiating the addition of the PIR sensor
* Finalized integration of the alarm system
* Corrected bug in initialization of basic-shutter

Signed-off-by: Gaël L'hopital <gael@lhopital.org>
14 files changed:
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/FreeboxOsBindingConstants.java
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/FreeboxOsHandlerFactory.java
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/ApiHandler.java
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/api/rest/HomeManager.java
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/config/NodeConfigurationBuilder.java
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/AlarmHandler.java
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/BasicShutterHandler.java
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/HomeNodeHandler.java
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/KeyfobHandler.java
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/PirHandler.java [new file with mode: 0644]
bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/ShutterHandler.java
bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/i18n/freeboxos.properties
bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/thing/channel-types.xml
bundles/org.openhab.binding.freeboxos/src/main/resources/OH-INF/thing/home-thing-type.xml

index 9c588c3343857355b2b8faf7ec0aa0aa4b36a0bd..e09a228f0756d86e9a808dc23e537bd24371d016 100644 (file)
@@ -71,9 +71,9 @@ public class FreeboxOsBindingConstants {
     public static final Set<ThingTypeUID> THINGS_TYPES_UIDS = Set.of(THING_TYPE_FXS, THING_TYPE_DECT, THING_TYPE_CALL,
             THING_TYPE_HOST, THING_TYPE_VM, THING_TYPE_PLAYER, THING_TYPE_ACTIVE_PLAYER, THING_TYPE_DELTA,
             THING_TYPE_REVOLUTION, THING_TYPE_REPEATER, THING_TYPE_WIFI_HOST, THING_TYPE_FREEPLUG);
-    public static final Set<ThingTypeUID> HOME_TYPES_UIDS = Set.of(Category.BASIC_SHUTTER.getThingTypeUID(),
-            Category.SHUTTER.getThingTypeUID(), Category.KFB.getThingTypeUID(), Category.CAMERA.getThingTypeUID(),
-            Category.ALARM.getThingTypeUID());
+    public static final Set<ThingTypeUID> HOME_TYPES_UIDS = Set.of(Category.BASIC_SHUTTER.thingTypeUID,
+            Category.SHUTTER.thingTypeUID, Category.KFB.thingTypeUID, Category.CAMERA.thingTypeUID,
+            Category.ALARM.thingTypeUID, Category.PIR.thingTypeUID);
 
     protected static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Stream
             .of(BRIDGE_TYPE_UIDS, THINGS_TYPES_UIDS, HOME_TYPES_UIDS).flatMap(Set::stream).collect(Collectors.toSet());
@@ -175,20 +175,30 @@ public class FreeboxOsBindingConstants {
     public static final String XDSL_UPTIME = "uptime";
 
     // Home channels
+    public static final String TIMESTAMP_POSTFIX = "-timestamp";
+
     public static final String KEYFOB_ENABLE = "enable";
+    public static final String KEYFOB_PUSHED = "pushed";
+    public static final String KEYFOB_PUSHED_UPDATE = KEYFOB_PUSHED + TIMESTAMP_POSTFIX;
+
     public static final String NODE_BATTERY = "battery";
     public static final String SHUTTER_POSITION = "position-set";
     public static final String SHUTTER_STOP = "stop";
     public static final String BASIC_SHUTTER_STATE = "state";
     public static final String BASIC_SHUTTER_UP = "up";
     public static final String BASIC_SHUTTER_DOWN = "down";
-    // public static final String BASIC_SHUTTER_CMD = "basic-shutter";
     public static final String ALARM_PIN = "pin";
     public static final String ALARM_SOUND = "sound";
     public static final String ALARM_VOLUME = "volume";
     public static final String ALARM_TIMEOUT1 = "timeout1";
     public static final String ALARM_TIMEOUT2 = "timeout2";
     public static final String ALARM_TIMEOUT3 = "timeout3";
+    public static final String ALARM_STATE = "state";
+
+    public static final String PIR_TAMPER = "tamper";
+    public static final String PIR_TRIGGER = "trigger";
+    public static final String PIR_TAMPER_UPDATE = PIR_TAMPER + TIMESTAMP_POSTFIX;
+    public static final String PIR_TRIGGER_UPDATE = PIR_TRIGGER + TIMESTAMP_POSTFIX;
 
     public static final Set<Command> TRUE_COMMANDS = Set.of(OnOffType.ON, UpDownType.UP, OpenClosedType.OPEN);
     public static final Set<Class<?>> ON_OFF_CLASSES = Set.of(OnOffType.class, UpDownType.class, OpenClosedType.class);
index 850f6e01cb3f6d24e8fb8d7bc1a0aab0156c13a2..9f3a0927c28c46e24e69eaf7f898e1f61ceb4337 100644 (file)
@@ -20,7 +20,6 @@ import java.util.concurrent.TimeUnit;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
-import org.eclipse.jetty.client.HttpClient;
 import org.openhab.binding.freeboxos.internal.api.ApiHandler;
 import org.openhab.binding.freeboxos.internal.api.rest.FreeboxOsSession;
 import org.openhab.binding.freeboxos.internal.api.rest.HomeManager.Category;
@@ -35,6 +34,7 @@ import org.openhab.binding.freeboxos.internal.handler.FreeplugHandler;
 import org.openhab.binding.freeboxos.internal.handler.FxsHandler;
 import org.openhab.binding.freeboxos.internal.handler.HostHandler;
 import org.openhab.binding.freeboxos.internal.handler.KeyfobHandler;
+import org.openhab.binding.freeboxos.internal.handler.PirHandler;
 import org.openhab.binding.freeboxos.internal.handler.PlayerHandler;
 import org.openhab.binding.freeboxos.internal.handler.RepeaterHandler;
 import org.openhab.binding.freeboxos.internal.handler.RevolutionHandler;
@@ -56,6 +56,7 @@ import org.openhab.core.thing.binding.ThingHandlerFactory;
 import org.osgi.service.component.ComponentContext;
 import org.osgi.service.component.annotations.Activate;
 import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
 import org.osgi.service.component.annotations.Modified;
 import org.osgi.service.component.annotations.Reference;
 import org.slf4j.Logger;
@@ -76,7 +77,6 @@ public class FreeboxOsHandlerFactory extends BaseThingHandlerFactory {
 
     private final NetworkAddressService networkAddressService;
     private final AudioHTTPServer audioHTTPServer;
-    private final HttpClient httpClient;
     private final ApiHandler apiHandler;
     private String callbackURL = "";
 
@@ -88,13 +88,19 @@ public class FreeboxOsHandlerFactory extends BaseThingHandlerFactory {
         super.activate(componentContext);
 
         this.audioHTTPServer = audioHTTPServer;
-        this.httpClient = httpClientFactory.getCommonHttpClient();
         this.networkAddressService = networkAddressService;
-        this.apiHandler = new ApiHandler(httpClient, timeZoneProvider);
+        this.apiHandler = new ApiHandler(httpClientFactory, timeZoneProvider);
 
         configChanged(config);
     }
 
+    @Override
+    @Deactivate
+    public void deactivate(ComponentContext componentContext) {
+        super.deactivate(componentContext);
+        apiHandler.dispose();
+    }
+
     @Modified
     public void configChanged(Map<String, Object> config) {
         String timeout = (String) config.getOrDefault(TIMEOUT, "8");
@@ -149,16 +155,18 @@ public class FreeboxOsHandlerFactory extends BaseThingHandlerFactory {
             return new ActivePlayerHandler(thing);
         } else if (THING_TYPE_PLAYER.equals(thingTypeUID)) {
             return new PlayerHandler(thing);
-        } else if (Category.BASIC_SHUTTER.getThingTypeUID().equals(thingTypeUID)) {
+        } else if (Category.BASIC_SHUTTER.thingTypeUID.equals(thingTypeUID)) {
             return new BasicShutterHandler(thing);
-        } else if (Category.SHUTTER.getThingTypeUID().equals(thingTypeUID)) {
+        } else if (Category.SHUTTER.thingTypeUID.equals(thingTypeUID)) {
             return new ShutterHandler(thing);
-        } else if (Category.ALARM.getThingTypeUID().equals(thingTypeUID)) {
+        } else if (Category.ALARM.thingTypeUID.equals(thingTypeUID)) {
             return new AlarmHandler(thing);
-        } else if (Category.KFB.getThingTypeUID().equals(thingTypeUID)) {
+        } else if (Category.KFB.thingTypeUID.equals(thingTypeUID)) {
             return new KeyfobHandler(thing);
-        } else if (Category.CAMERA.getThingTypeUID().equals(thingTypeUID)) {
+        } else if (Category.CAMERA.thingTypeUID.equals(thingTypeUID)) {
             return new CameraHandler(thing);
+        } else if (Category.PIR.thingTypeUID.equals(thingTypeUID)) {
+            return new PirHandler(thing);
         }
 
         return null;
index 6f760e52cbb589c89fb38efd9479c46bc1f7199d..9fcb2ad5c88eca87c494228a06bac327b596463f 100644 (file)
@@ -32,11 +32,13 @@ import org.eclipse.jetty.http.HttpHeader;
 import org.eclipse.jetty.http.HttpMethod;
 import org.eclipse.jetty.http.HttpStatus;
 import org.eclipse.jetty.http.HttpStatus.Code;
+import org.openhab.binding.freeboxos.internal.FreeboxOsBindingConstants;
 import org.openhab.binding.freeboxos.internal.api.deserialization.ForegroundAppDeserializer;
 import org.openhab.binding.freeboxos.internal.api.deserialization.ListDeserializer;
 import org.openhab.binding.freeboxos.internal.api.deserialization.StrictEnumTypeAdapterFactory;
 import org.openhab.binding.freeboxos.internal.api.rest.PlayerManager.ForegroundApp;
 import org.openhab.core.i18n.TimeZoneProvider;
+import org.openhab.core.io.net.http.HttpClientFactory;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -58,9 +60,10 @@ import inet.ipaddr.mac.MACAddress;
  */
 @NonNullByDefault
 public class ApiHandler {
-    public static final String AUTH_HEADER = "X-Fbx-App-Auth";
     private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
     private static final String CONTENT_TYPE = "application/json; charset=" + DEFAULT_CHARSET.name();
+    private static final int RESPONSE_BUFFER_SIZE = 65536;
+    public static final String AUTH_HEADER = "X-Fbx-App-Auth";
 
     private final Logger logger = LoggerFactory.getLogger(ApiHandler.class);
     private final HttpClient httpClient;
@@ -68,8 +71,7 @@ public class ApiHandler {
 
     private long timeoutInMs = TimeUnit.SECONDS.toMillis(8);
 
-    public ApiHandler(HttpClient httpClient, TimeZoneProvider timeZoneProvider) {
-        this.httpClient = httpClient;
+    public ApiHandler(HttpClientFactory httpClientFactory, TimeZoneProvider timeZoneProvider) {
         this.gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
                 .registerTypeAdapter(ZonedDateTime.class,
                         (JsonDeserializer<ZonedDateTime>) (json, type, jsonDeserializationContext) -> {
@@ -86,6 +88,21 @@ public class ApiHandler {
                 .registerTypeAdapter(ForegroundApp.class, new ForegroundAppDeserializer())
                 .registerTypeAdapter(List.class, new ListDeserializer()).serializeNulls()
                 .registerTypeAdapterFactory(new StrictEnumTypeAdapterFactory()).create();
+        httpClient = httpClientFactory.createHttpClient(FreeboxOsBindingConstants.BINDING_ID);
+        httpClient.setResponseBufferSize(RESPONSE_BUFFER_SIZE);
+        try {
+            httpClient.start();
+        } catch (Exception e) {
+            logger.warn("Unable to start httpClient: {}", e.getMessage());
+        }
+    }
+
+    public void dispose() {
+        try {
+            httpClient.stop();
+        } catch (Exception e) {
+            logger.warn("Unable to stop httpClient: {}", e.getMessage());
+        }
     }
 
     public synchronized <T> T executeUri(URI uri, HttpMethod method, Class<T> clazz, @Nullable String sessionToken,
index 2170a2fbdc5a3d78a09fde9cbc0fc1a83c88bce3..4bdf8617ff0bc113468f463860f0baac62922b7d 100644 (file)
@@ -14,8 +14,10 @@ package org.openhab.binding.freeboxos.internal.api.rest;
 
 import static org.openhab.binding.freeboxos.internal.FreeboxOsBindingConstants.BINDING_ID;
 
+import java.util.Comparator;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 
 import org.eclipse.jdt.annotation.NonNull;
 import org.eclipse.jdt.annotation.NonNullByDefault;
@@ -102,17 +104,25 @@ public class HomeManager extends RestManager {
         }
     }
 
-    private static record LogEntry(long timestamp, int value) {
+    public static record LogEntry(Long timestamp, int value) {
     }
 
     public static record Endpoint(int id, String name, String label, EpType epType, Visibility visibility, int refresh,
-            ValueType valueType, EndpointUi ui, @Nullable String category, Object value, List<LogEntry> history) {
+            ValueType valueType, EndpointUi ui, @Nullable String category, Object value,
+            @Nullable List<LogEntry> history) {
+
+        private static final Comparator<LogEntry> HISTORY_COMPARATOR = Comparator.comparing(LogEntry::timestamp);
+
         private enum Visibility {
             INTERNAL,
             NORMAL,
             DASHBOARD,
             UNKNOWN
         }
+
+        public Optional<LogEntry> getLastChange() {
+            return history != null ? history.stream().max(HISTORY_COMPARATOR) : Optional.empty();
+        }
     }
 
     private enum Status {
@@ -129,16 +139,13 @@ public class HomeManager extends RestManager {
         ALARM,
         KFB,
         CAMERA,
+        PIR,
         UNKNOWN;
 
-        private final ThingTypeUID thingTypeUID;
+        public final ThingTypeUID thingTypeUID;
 
         Category() {
-            thingTypeUID = new ThingTypeUID(BINDING_ID, name().toLowerCase());
-        }
-
-        public ThingTypeUID getThingTypeUID() {
-            return thingTypeUID;
+            thingTypeUID = new ThingTypeUID(BINDING_ID, name().toLowerCase().replace('_', '-'));
         }
     }
 
@@ -148,6 +155,17 @@ public class HomeManager extends RestManager {
 
     public static record HomeNode(int id, @Nullable String name, @Nullable String label, Category category,
             Status status, List<Endpoint> showEndpoints, Map<String, String> props, NodeType type) {
+
+        private static final Comparator<Endpoint> ENPOINT_COMPARATOR = Comparator.comparing(Endpoint::refresh);
+
+        public Optional<Endpoint> getMinRefresh() {
+            return showEndpoints.stream().filter(ep -> EpType.SIGNAL.equals(ep.epType) && ep.refresh() != 0)
+                    .min(ENPOINT_COMPARATOR);
+        }
+
+        public Optional<Endpoint> getEndpoint(int slotId) {
+            return showEndpoints.stream().filter(ep -> ep.id == slotId).findAny();
+        }
     }
 
     public HomeManager(FreeboxOsSession session) throws FreeboxException {
index 18e0c882f5759848f350c90cd5f3e2015bc334d4..dda8d651ea99b2423214f2bfdf1b7863cfaa7d7a 100644 (file)
@@ -41,7 +41,7 @@ public class NodeConfigurationBuilder {
         if (node.category() == Category.UNKNOWN) {
             return Optional.empty();
         }
-        ThingUID thingUID = new ThingUID(node.category().getThingTypeUID(), bridgeUID, Integer.toString(node.id()));
+        ThingUID thingUID = new ThingUID(node.category().thingTypeUID, bridgeUID, Integer.toString(node.id()));
         DiscoveryResultBuilder discoveryResultBuilder = DiscoveryResultBuilder.create(thingUID);
         discoveryResultBuilder.withProperty(ClientConfiguration.ID, node.id()).withLabel(node.label())
                 .withRepresentationProperty(ClientConfiguration.ID).withBridge(bridgeUID);
index ca62189f0bb6fca189da05146167e62f88f732cd..74cae9b0bfc6784ff8f2457a4ea9971f20b7e31f 100644 (file)
@@ -14,8 +14,10 @@ package org.openhab.binding.freeboxos.internal.handler;
 
 import static org.openhab.binding.freeboxos.internal.FreeboxOsBindingConstants.*;
 
+import java.util.Optional;
+
 import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.openhab.binding.freeboxos.internal.api.rest.HomeManager;
+import org.openhab.binding.freeboxos.internal.api.rest.HomeManager.Endpoint;
 import org.openhab.binding.freeboxos.internal.api.rest.HomeManager.EndpointState;
 import org.openhab.core.library.types.DecimalType;
 import org.openhab.core.library.types.QuantityType;
@@ -38,7 +40,7 @@ public class AlarmHandler extends HomeNodeHandler {
     }
 
     @Override
-    protected State getChannelState(HomeManager homeManager, String channelId, EndpointState state) {
+    protected State getChannelState(String channelId, EndpointState state, Optional<Endpoint> endPoint) {
         String value = state.value();
 
         if (value == null) {
@@ -47,7 +49,7 @@ public class AlarmHandler extends HomeNodeHandler {
 
         return switch (channelId) {
             case NODE_BATTERY -> DecimalType.valueOf(value);
-            case ALARM_PIN -> StringType.valueOf(value);
+            case ALARM_STATE, ALARM_PIN -> StringType.valueOf(value);
             case ALARM_SOUND, ALARM_VOLUME -> QuantityType.valueOf(value + " %");
             case ALARM_TIMEOUT1, ALARM_TIMEOUT2, ALARM_TIMEOUT3 -> QuantityType.valueOf(value + " s");
             default -> UnDefType.NULL;
index ee0492ddff966699e7f2dd1be892efb4a9510f43..46487e1041f49b30f86c3a287ea43529b2a608bc 100644 (file)
@@ -15,6 +15,7 @@ package org.openhab.binding.freeboxos.internal.handler;
 import static org.openhab.binding.freeboxos.internal.FreeboxOsBindingConstants.*;
 
 import java.util.List;
+import java.util.Optional;
 import java.util.Set;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
@@ -50,7 +51,7 @@ public class BasicShutterHandler extends HomeNodeHandler {
     }
 
     @Override
-    protected State getChannelState(HomeManager homeManager, String channelId, EndpointState state) {
+    protected State getChannelState(String channelId, EndpointState state, Optional<Endpoint> endPoint) {
         String value = state.value();
         return value != null && channelId.equals(BASIC_SHUTTER_STATE)
                 ? state.asBoolean() ? OpenClosedType.CLOSED : OpenClosedType.OPEN
index df79ddd21af6303a3d6ed837042c14b592b93c19..269958e6da7ee77cbb4e3a340ae6a191f47b3dae 100644 (file)
  */
 package org.openhab.binding.freeboxos.internal.handler;
 
+import static org.openhab.binding.freeboxos.internal.FreeboxOsBindingConstants.TIMESTAMP_POSTFIX;
+
 import java.math.BigDecimal;
-import java.util.Comparator;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
@@ -53,48 +55,52 @@ public abstract class HomeNodeHandler extends ApiConsumerHandler {
         HomeNode node = getManager(HomeManager.class).getHomeNode(getClientId());
 
         // Gets the lowest refresh time or else, we'll keep configuration default
-        node.showEndpoints().stream().filter(ep -> ep.epType() == EpType.SIGNAL).filter(ep -> ep.refresh() != 0)
-                .min(Comparator.comparing(Endpoint::refresh)).map(Endpoint::refresh).ifPresent(rate -> {
-                    Configuration thingConfig = editConfiguration();
-                    thingConfig.put(ApiConsumerConfiguration.REFRESH_INTERVAL, Integer.toString(rate / 1000));
-                    updateConfiguration(thingConfig);
-                });
+        node.getMinRefresh().map(Endpoint::refresh).ifPresent(rate -> {
+            Configuration thingConfig = editConfiguration();
+            thingConfig.put(ApiConsumerConfiguration.REFRESH_INTERVAL, Integer.toString(rate / 1000));
+            updateConfiguration(thingConfig);
+        });
 
         properties.putAll(node.props());
 
         getThing().getChannels().forEach(channel -> {
             Configuration conf = channel.getConfiguration();
-            node.type().endpoints().stream().filter(ep -> ep.name().equals(channel.getUID().getIdWithoutGroup()))
+            String channelId = channel.getUID().getIdWithoutGroup();
+            node.type().endpoints().stream().filter(ep -> ep.name().equals(channelId))
                     .forEach(endPoint -> conf.put(endPoint.epType().asConfId(), endPoint.id()));
-            internalConfigureChannel(channel.getUID().getIdWithoutGroup(), conf, node.type().endpoints());
+            internalConfigureChannel(channelId, conf, node.type().endpoints());
         });
     }
 
     protected void internalConfigureChannel(String channelId, Configuration conf, List<Endpoint> endpoints) {
+        if (channelId.endsWith(TIMESTAMP_POSTFIX)) {
+            String baseEndpoint = channelId.replace(TIMESTAMP_POSTFIX, "");
+            endpoints.stream().filter(ep -> ep.name().equals(baseEndpoint)).forEach(ep -> {
+                conf.put(ep.name(), ep.id());
+                conf.put("signal", ep.id());
+            });
+        }
     }
 
     @Override
     protected void internalPoll() throws FreeboxException {
         HomeManager homeManager = getManager(HomeManager.class);
-        getThing().getChannels().stream().filter(channel -> isLinked(channel.getUID())).forEach(channel -> {
-            State result = UnDefType.UNDEF;
+        HomeNode node = homeManager.getHomeNode(getClientId());
+        List<Channel> linkedChannels = getThing().getChannels().stream().filter(channel -> isLinked(channel.getUID()))
+                .toList();
+
+        for (Channel channel : linkedChannels) {
+            State result = null;
             Integer slotId = getSlotId(channel.getConfiguration(), EpType.SIGNAL.asConfId());
             if (slotId instanceof Integer) {
-                try {
-                    EndpointState state = homeManager.getEndpointsState(getClientId(), slotId);
-                    if (state != null) {
-                        result = getChannelState(homeManager, channel.getUID().getIdWithoutGroup(), state);
-                    } else {
-                        result = getChannelState(homeManager, channel.getUID().getIdWithoutGroup());
-                    }
-                } catch (FreeboxException e) {
-                    logger.warn("Error updating channel: {}", e.getMessage());
+                EndpointState state = homeManager.getEndpointsState(getClientId(), slotId);
+                Optional<Endpoint> endPoint = node.getEndpoint(slotId);
+                if (state != null) {
+                    result = getChannelState(channel.getUID().getIdWithoutGroup(), state, endPoint);
                 }
-            } else {
-                result = getChannelState(homeManager, channel.getUID().getIdWithoutGroup());
             }
-            updateState(channel.getUID(), result);
-        });
+            updateState(channel.getUID(), result != null ? result : UnDefType.UNDEF);
+        }
     }
 
     @Override
@@ -126,9 +132,5 @@ public abstract class HomeNodeHandler extends ApiConsumerHandler {
         return false;
     }
 
-    protected State getChannelState(HomeManager homeManager, String channelWG) {
-        return UnDefType.UNDEF;
-    }
-
-    protected abstract State getChannelState(HomeManager homeManager, String channelId, EndpointState state);
+    protected abstract State getChannelState(String channelId, EndpointState state, Optional<Endpoint> endPoint);
 }
index 35caf5ea791c531b7cad3beb65fdebc00ce7f95e..524330a22da22b5895c593f5e1bdc32225140e91 100644 (file)
@@ -14,11 +14,19 @@ package org.openhab.binding.freeboxos.internal.handler;
 
 import static org.openhab.binding.freeboxos.internal.FreeboxOsBindingConstants.*;
 
+import java.time.Instant;
+import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
+import java.util.Objects;
+import java.util.Optional;
+
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.openhab.binding.freeboxos.internal.api.FreeboxException;
 import org.openhab.binding.freeboxos.internal.api.rest.HomeManager;
+import org.openhab.binding.freeboxos.internal.api.rest.HomeManager.Endpoint;
 import org.openhab.binding.freeboxos.internal.api.rest.HomeManager.EndpointState;
 import org.openhab.core.config.core.Configuration;
+import org.openhab.core.library.types.DateTimeType;
 import org.openhab.core.library.types.DecimalType;
 import org.openhab.core.library.types.OnOffType;
 import org.openhab.core.thing.Thing;
@@ -40,7 +48,15 @@ public class KeyfobHandler extends HomeNodeHandler {
     }
 
     @Override
-    protected State getChannelState(HomeManager homeManager, String channelId, EndpointState state) {
+    protected State getChannelState(String channelId, EndpointState state, Optional<Endpoint> endPoint) {
+        if (channelId.startsWith(KEYFOB_PUSHED)) {
+            return Objects.requireNonNull(endPoint.map(ep -> ep
+                    .getLastChange().map(
+                            change -> (State) (KEYFOB_PUSHED.equals(channelId) ? new DecimalType(change.value())
+                                    : new DateTimeType(ZonedDateTime
+                                            .ofInstant(Instant.ofEpochSecond(change.timestamp()), ZoneOffset.UTC))))
+                    .orElse(UnDefType.UNDEF)).orElse(UnDefType.UNDEF));
+        }
         String value = state.value();
         if (value != null) {
             switch (channelId) {
diff --git a/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/PirHandler.java b/bundles/org.openhab.binding.freeboxos/src/main/java/org/openhab/binding/freeboxos/internal/handler/PirHandler.java
new file mode 100644 (file)
index 0000000..191189e
--- /dev/null
@@ -0,0 +1,69 @@
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.freeboxos.internal.handler;
+
+import static org.openhab.binding.freeboxos.internal.FreeboxOsBindingConstants.*;
+
+import java.time.Instant;
+import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
+import java.util.Objects;
+import java.util.Optional;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.freeboxos.internal.api.rest.HomeManager.Endpoint;
+import org.openhab.binding.freeboxos.internal.api.rest.HomeManager.EndpointState;
+import org.openhab.core.library.types.DateTimeType;
+import org.openhab.core.library.types.DecimalType;
+import org.openhab.core.library.types.OnOffType;
+import org.openhab.core.library.types.OpenClosedType;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.types.State;
+import org.openhab.core.types.UnDefType;
+
+/**
+ * The {@link PirHandler} is responsible for handling everything associated to
+ * any Freebox Home PIR motion detection thing type.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class PirHandler extends HomeNodeHandler {
+
+    public PirHandler(Thing thing) {
+        super(thing);
+    }
+
+    @Override
+    protected State getChannelState(String channelId, EndpointState state, Optional<Endpoint> endPoint) {
+        if (PIR_TAMPER_UPDATE.equals(channelId) || PIR_TRIGGER_UPDATE.equals(channelId)) {
+            return Objects.requireNonNull(endPoint.map(ep -> ep.getLastChange()
+                    .map(change -> (State) new DateTimeType(
+                            ZonedDateTime.ofInstant(Instant.ofEpochSecond(change.timestamp()), ZoneOffset.UTC)))
+                    .orElse(UnDefType.UNDEF)).orElse(UnDefType.UNDEF));
+        }
+
+        String value = state.value();
+
+        if (value == null) {
+            return UnDefType.NULL;
+        }
+
+        return switch (channelId) {
+            case NODE_BATTERY -> DecimalType.valueOf(value);
+            case PIR_TAMPER -> state.asBoolean() ? OpenClosedType.OPEN : OpenClosedType.CLOSED;
+            case PIR_TRIGGER -> OnOffType.from(value);
+            default -> UnDefType.NULL;
+        };
+    }
+}
index 20ff18d1c7740277bc6a04ad381b4baab990b1be..04dc141555656152170263fbc75b2d81194c20bb 100644 (file)
@@ -15,6 +15,7 @@ package org.openhab.binding.freeboxos.internal.handler;
 import static org.openhab.binding.freeboxos.internal.FreeboxOsBindingConstants.*;
 
 import java.util.List;
+import java.util.Optional;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.openhab.binding.freeboxos.internal.api.FreeboxException;
@@ -49,7 +50,7 @@ public class ShutterHandler extends HomeNodeHandler {
     }
 
     @Override
-    protected State getChannelState(HomeManager homeManager, String channelId, EndpointState state) {
+    protected State getChannelState(String channelId, EndpointState state, Optional<Endpoint> endPoint) {
         String value = state.value();
         return value != null && channelId.equals(SHUTTER_POSITION) ? QuantityType.valueOf(value + " %")
                 : UnDefType.NULL;
index 663b4b942b0a26c0e8910d8d2040011c87fe08d6..995690b6d8c89af08e23388b6da3494e7c8d4901 100644 (file)
@@ -46,6 +46,14 @@ thing-type.freeboxos.host.label = Network Device
 thing-type.freeboxos.host.description = Provides network device reachability
 thing-type.freeboxos.kfb.label = Freebox Keyfob
 thing-type.freeboxos.kfb.description = A keyfob configured in your Freebox Server
+thing-type.freeboxos.kfb.channel.pushed-timestamp.label = Timestamp
+thing-type.freeboxos.kfb.channel.pushed-timestamp.description = Timestamp of the last action on the keyfob
+thing-type.freeboxos.pir.label = Freebox Home PIR
+thing-type.freeboxos.pir.description = A motion Sensor
+thing-type.freeboxos.pir.channel.tamper-timestamp.label = Tamper Timestamp
+thing-type.freeboxos.pir.channel.tamper-timestamp.description = Timestamp of the last cover tampered state change
+thing-type.freeboxos.pir.channel.trigger-timestamp.label = Trigger Timestamp
+thing-type.freeboxos.pir.channel.trigger-timestamp.description = Timestamp of the last state change
 thing-type.freeboxos.player.label = Freebox Player
 thing-type.freeboxos.player.description = The player is the device connected to your TV
 thing-type.freeboxos.repeater.label = Wifi Repeater
@@ -209,6 +217,13 @@ channel-type.freeboxos.afp-file-status.description = Status of Mac OS File Shari
 channel-type.freeboxos.airmedia-status.label = Air Media Enabled
 channel-type.freeboxos.airmedia-status.description = Indicates whether Air Media is enabled
 channel-type.freeboxos.alarm-pin.label = PIN Code
+channel-type.freeboxos.alarm-state.label = Alarm State
+channel-type.freeboxos.alarm-state.description = Current state of the alarm system
+channel-type.freeboxos.alarm-state.state.option.idle = Idle
+channel-type.freeboxos.alarm-state.state.option.alarm1_arming = Arming (Absent Mode)
+channel-type.freeboxos.alarm-state.state.option.alarm1_armed = Armed (Absent Mode)
+channel-type.freeboxos.alarm-state.state.option.alarm2_arming = Arming (Night Mode)
+channel-type.freeboxos.alarm-state.state.option.alarm2_armed = Armed (Night Mode)
 channel-type.freeboxos.alarm-timeout.label = Alarm Duration
 channel-type.freeboxos.alarm-volume.label = Alarm Volume
 channel-type.freeboxos.alternate-ring.label = Alternating Ring
@@ -282,6 +297,11 @@ channel-type.freeboxos.key-code.state.option.ok = OK
 channel-type.freeboxos.key-code.state.option.home = Home
 channel-type.freeboxos.keyfob-enable.label = Keyfob Enabled
 channel-type.freeboxos.keyfob-enable.description = Activates / deactivates the keyfob
+channel-type.freeboxos.kfb-pushed.label = Key Code Pushed
+channel-type.freeboxos.kfb-pushed.description = Last key pushed on the remote
+channel-type.freeboxos.kfb-pushed.state.option.1 = Arm Absent Mode
+channel-type.freeboxos.kfb-pushed.state.option.2 = Disarm
+channel-type.freeboxos.kfb-pushed.state.option.3 = Arm Night Mode
 channel-type.freeboxos.lcd-brightness.label = Screen Brightness
 channel-type.freeboxos.lcd-brightness.description = Brightness level of the screen in percent
 channel-type.freeboxos.lcd-forced.label = Forced Orientation
@@ -323,6 +343,8 @@ channel-type.freeboxos.package.description = Name of the package currently activ
 channel-type.freeboxos.phone-event.label = Phone Event
 channel-type.freeboxos.phone-event.description = Triggers when an event related to the phone has been detected
 channel-type.freeboxos.phone-number.label = Phone Number
+channel-type.freeboxos.pir-tamper.label = Cover Tamper
+channel-type.freeboxos.pir-trigger.label = Détection
 channel-type.freeboxos.player-status.label = Player Status
 channel-type.freeboxos.player-status.description = Status of the Freebox TV player
 channel-type.freeboxos.reachable.label = Reachable
index 4dee0b5198b70e5be5245959cf753057c1b9d9cd..7cfb9b4502ce739e40dabe676f37ce75be90d372 100644 (file)
                <state readOnly="true" pattern="%.2f dBm"/>
        </channel-type>
 
+       <channel-type id="pir-trigger">
+               <item-type>Switch</item-type>
+               <label>Détection</label>
+               <category>oh:freeboxos:mouvement</category>
+       </channel-type>
+
+       <channel-type id="pir-tamper">
+               <item-type>Contact</item-type>
+               <label>Cover Tamper</label>
+               <category>oh:freeboxos:warning</category>
+       </channel-type>
+
+       <channel-type id="kfb-pushed">
+               <item-type>Number</item-type>
+               <label>Key Code Pushed</label>
+               <description>Last key pushed on the remote</description>
+               <state readOnly="true" pattern="%d">
+                       <options>
+                               <option value="1">Arm Absent Mode</option>
+                               <option value="2">Disarm</option>
+                               <option value="3">Arm Night Mode</option>
+                       </options>
+               </state>
+       </channel-type>
+
+       <channel-type id="alarm-state">
+               <item-type>String</item-type>
+               <label>Alarm State</label>
+               <description>Current state of the alarm system</description>
+               <state readOnly="true" pattern="%s">
+                       <options>
+                               <option value="idle">Idle</option>
+                               <option value="alarm1_arming">Arming (Absent Mode)</option>
+                               <option value="alarm1_armed">Armed (Absent Mode)</option>
+                               <option value="alarm2_arming">Arming (Night Mode)</option>
+                               <option value="alarm2_armed">Armed (Night Mode)</option>
+                       </options>
+               </state>
+       </channel-type>
+
        <channel-type id="xdsl-ready">
                <item-type>Switch</item-type>
                <label>Ready</label>
index fb70314e1ce0e90ae721e3564eb642af8b90c231..c8255065ce289eb6cae4b3e07b1dc6d90bee2303 100644 (file)
                <config-description-ref uri="thing-type:freeboxos:home-node"/>
        </thing-type>
 
+       <thing-type id="pir">
+               <supported-bridge-type-refs>
+                       <bridge-type-ref id="api"/>
+               </supported-bridge-type-refs>
+
+               <label>Freebox Home PIR</label>
+               <description>A motion Sensor</description>
+
+               <channels>
+                       <channel id="trigger" typeId="pir-trigger"/>
+                       <channel id="trigger-timestamp" typeId="timestamp">
+                               <label>Trigger Timestamp</label>
+                               <description>Timestamp of the last state change</description>
+                       </channel>
+                       <channel id="tamper" typeId="pir-tamper"/>
+                       <channel id="tamper-timestamp" typeId="timestamp">
+                               <label>Tamper Timestamp</label>
+                               <description>Timestamp of the last cover tampered state change</description>
+                       </channel>
+                       <channel id="battery" typeId="system.battery-level"/>
+               </channels>
+
+               <representation-property>id</representation-property>
+
+               <config-description-ref uri="thing-type:freeboxos:home-node"/>
+       </thing-type>
+
        <thing-type id="alarm">
                <supported-bridge-type-refs>
                        <bridge-type-ref id="api"/>
@@ -30,6 +57,7 @@
                <description>The Alarm system configured in your Freebox Server</description>
 
                <channels>
+                       <channel id="state" typeId="alarm-state"/>
                        <channel id="pin" typeId="alarm-pin"/>
                        <channel id="sound" typeId="alarm-volume">
                                <label>Bips Volume</label>
                <description>A keyfob configured in your Freebox Server</description>
 
                <channels>
+                       <channel id="pushed" typeId="kfb-pushed"/>
+                       <channel id="pushed-timestamp" typeId="timestamp">
+                               <label>Timestamp</label>
+                               <description>Timestamp of the last action on the keyfob</description>
+                       </channel>
                        <channel id="enable" typeId="keyfob-enable"/>
                        <channel id="battery" typeId="system.battery-level"/>
                </channels>