]> git.basschouten.com Git - openhab-addons.git/commitdiff
[mynice] Binding for IT4Wifi module (Nice gate doors) (#12940)
authorGaël L'hopital <gael@lhopital.org>
Fri, 10 Mar 2023 09:33:39 +0000 (10:33 +0100)
committerGitHub <noreply@github.com>
Fri, 10 Mar 2023 09:33:39 +0000 (10:33 +0100)
Signed-off-by: clinique <gael@lhopital.org>
31 files changed:
CODEOWNERS
bom/openhab-addons/pom.xml
bundles/org.openhab.binding.mynice/NOTICE [new file with mode: 0644]
bundles/org.openhab.binding.mynice/README.md [new file with mode: 0644]
bundles/org.openhab.binding.mynice/pom.xml [new file with mode: 0644]
bundles/org.openhab.binding.mynice/src/main/feature/feature.xml [new file with mode: 0644]
bundles/org.openhab.binding.mynice/src/main/java/org/openhab/binding/mynice/internal/MyNiceBindingConstants.java [new file with mode: 0644]
bundles/org.openhab.binding.mynice/src/main/java/org/openhab/binding/mynice/internal/MyNiceHandlerFactory.java [new file with mode: 0644]
bundles/org.openhab.binding.mynice/src/main/java/org/openhab/binding/mynice/internal/config/It4WifiConfiguration.java [new file with mode: 0644]
bundles/org.openhab.binding.mynice/src/main/java/org/openhab/binding/mynice/internal/discovery/MyNiceDiscoveryParticipant.java [new file with mode: 0644]
bundles/org.openhab.binding.mynice/src/main/java/org/openhab/binding/mynice/internal/discovery/MyNiceDiscoveryService.java [new file with mode: 0644]
bundles/org.openhab.binding.mynice/src/main/java/org/openhab/binding/mynice/internal/handler/GateHandler.java [new file with mode: 0644]
bundles/org.openhab.binding.mynice/src/main/java/org/openhab/binding/mynice/internal/handler/It4WifiConnector.java [new file with mode: 0644]
bundles/org.openhab.binding.mynice/src/main/java/org/openhab/binding/mynice/internal/handler/It4WifiHandler.java [new file with mode: 0644]
bundles/org.openhab.binding.mynice/src/main/java/org/openhab/binding/mynice/internal/handler/MyNiceDataListener.java [new file with mode: 0644]
bundles/org.openhab.binding.mynice/src/main/java/org/openhab/binding/mynice/internal/xml/MyNiceXStream.java [new file with mode: 0644]
bundles/org.openhab.binding.mynice/src/main/java/org/openhab/binding/mynice/internal/xml/RequestBuilder.java [new file with mode: 0644]
bundles/org.openhab.binding.mynice/src/main/java/org/openhab/binding/mynice/internal/xml/dto/Authentication.java [new file with mode: 0644]
bundles/org.openhab.binding.mynice/src/main/java/org/openhab/binding/mynice/internal/xml/dto/CommandType.java [new file with mode: 0644]
bundles/org.openhab.binding.mynice/src/main/java/org/openhab/binding/mynice/internal/xml/dto/Device.java [new file with mode: 0644]
bundles/org.openhab.binding.mynice/src/main/java/org/openhab/binding/mynice/internal/xml/dto/Error.java [new file with mode: 0644]
bundles/org.openhab.binding.mynice/src/main/java/org/openhab/binding/mynice/internal/xml/dto/Event.java [new file with mode: 0644]
bundles/org.openhab.binding.mynice/src/main/java/org/openhab/binding/mynice/internal/xml/dto/Interface.java [new file with mode: 0644]
bundles/org.openhab.binding.mynice/src/main/java/org/openhab/binding/mynice/internal/xml/dto/Properties.java [new file with mode: 0644]
bundles/org.openhab.binding.mynice/src/main/java/org/openhab/binding/mynice/internal/xml/dto/Property.java [new file with mode: 0644]
bundles/org.openhab.binding.mynice/src/main/java/org/openhab/binding/mynice/internal/xml/dto/Response.java [new file with mode: 0644]
bundles/org.openhab.binding.mynice/src/main/java/org/openhab/binding/mynice/internal/xml/dto/T4Command.java [new file with mode: 0644]
bundles/org.openhab.binding.mynice/src/main/resources/OH-INF/addon/addon.xml [new file with mode: 0644]
bundles/org.openhab.binding.mynice/src/main/resources/OH-INF/i18n/mynice.properties [new file with mode: 0644]
bundles/org.openhab.binding.mynice/src/main/resources/OH-INF/thing/thing-types.xml [new file with mode: 0644]
bundles/pom.xml

index f1f204fa412cdaa9955e57b7613c2ac4846b7341..69fc52c11ce4ed6345a0b99f7c4f41f9a077b4dc 100644 (file)
 /bundles/org.openhab.binding.mqtt.homie/ @davidgraeff
 /bundles/org.openhab.binding.mycroft/ @dalgwen
 /bundles/org.openhab.binding.mybmw/ @weymann @ntruchsess
+/bundles/org.openhab.binding.mynice/ @clinique
 /bundles/org.openhab.binding.myq/ @digitaldan
 /bundles/org.openhab.binding.mystrom/ @pail23
 /bundles/org.openhab.binding.nanoleaf/ @raepple @stefan-hoehn
index efe39a63389da06310ed1e448bf9f1c0173b0264..28893eca65d6404e66458171d18415fac739ab79 100644 (file)
       <artifactId>org.openhab.binding.mybmw</artifactId>
       <version>${project.version}</version>
     </dependency>
+    <dependency>
+      <groupId>org.openhab.addons.bundles</groupId>
+      <artifactId>org.openhab.binding.mynice</artifactId>
+      <version>${project.version}</version>
+    </dependency>
     <dependency>
       <groupId>org.openhab.addons.bundles</groupId>
       <artifactId>org.openhab.binding.myq</artifactId>
diff --git a/bundles/org.openhab.binding.mynice/NOTICE b/bundles/org.openhab.binding.mynice/NOTICE
new file mode 100644 (file)
index 0000000..38d625e
--- /dev/null
@@ -0,0 +1,13 @@
+This content is produced and maintained by the openHAB project.
+
+* Project home: https://www.openhab.org
+
+== Declared Project Licenses
+
+This program and the accompanying materials are made available under the terms
+of the Eclipse Public License 2.0 which is available at
+https://www.eclipse.org/legal/epl-2.0/.
+
+== Source Code
+
+https://github.com/openhab/openhab-addons
diff --git a/bundles/org.openhab.binding.mynice/README.md b/bundles/org.openhab.binding.mynice/README.md
new file mode 100644 (file)
index 0000000..d4c7e65
--- /dev/null
@@ -0,0 +1,114 @@
+# MyNice Binding
+
+This binding implements the support of the IT4Wifi module through the NHK protocol and enables management of Nice gates actuators.
+IT4Wifi is a bridge between the TP4 bus of your gate and your Ethernet network.
+
+## Supported Things
+
+- `it4wifi`: The Bridge between openHAB and your module.
+- `swing`: A Thing representing a swinging (two rotating doors) gate.
+- `sliding`: A Thing representing a sliding gate.
+
+## Discovery
+
+The binding will auto-discover (by MDNS) your module, creating the associated `it4wifi` bridge.
+
+Once discovered, a user named “org.openhab.binding.mynice” will be created on it.
+You will have to grant him permissions using the MyNice app (Android or IOS).
+
+Once configuration of the bridge is completed, your gate(s) will also be auto-discovered and added to the Inbox.
+
+## Thing Configuration
+
+First configuration should be done via UI discovery, this will let you get automatically the password provided by the IT4Wifi module.
+Once done, you can also create your things via *.things file.
+
+### `it4wifi` Bridge Configuration
+
+| Name       | Type | Description                                                            | Default | Required | Advanced |
+|------------|------|------------------------------------------------------------------------|---------|----------|----------|
+| hostname   | text | Hostname or IP address of the device                                   | N/A     | yes      | no       |
+| password   | text | Password to access the device                                          | N/A     | yes      | no       |
+| macAddress | text | The MAC address of the IT4Wifi                                         | N/A     | yes      | no       |
+| username   | text | Pairing Key needed to access the device, provided by the bridge itself | N/A     | yes      | no       |
+
+### Gates Thing Configuration
+
+| Name       | Type | Description                                                            | Default | Required | Advanced |
+|------------|------|------------------------------------------------------------------------|---------|----------|----------|
+| id         | text | ID of the gate on the TP4 bus connected to the bridge                  | N/A     | yes      | no       |
+
+## Channels
+
+There is no channel associated with the bridge.
+
+Channels available for the gates are :
+
+| Channel   | Type   | Read/Write | Description                                              |
+|-----------|--------|------------|----------------------------------------------------------|
+| status    | String | R          | Description of the current status of the door (1)        |
+| obstruct  | Switch | R          | Flags an obstruction, blocking the door                  |
+| moving    | Switch | R          | Indicates if the device is currently operating a command |
+| command   | String | W          | Send a given command to the gate (2)                     |
+| t4command | String | W          | Send a T4 Command to the gate                            |
+
+(1) : can be open, closed, opening, closing, stopped.
+(2) : must be "stop","open","close"
+
+### T4 Commands
+
+Depending upon your gate model and motor capabilities, some T4 commands can be used.
+The list of available commands for your model will be automatically discovered by the binding.
+This information is stored in the `allowedT4` property held by the gate Thing itself.
+
+Complete list of T4 Commands :
+
+| Command | Action                     |
+|---------|----------------------------|
+| MDAx    | Step by Step               |
+| MDAy    | Stop (as remote control)   |
+| MDAz    | Open (as remote control)   |
+| MDA0    | Close (as remote control)  |
+| MDA1    | Partial opening 1          |
+| MDA2    | Partial opening 2          |
+| MDA3    | Partial opening 3          |
+| MDBi    | Apartment Step by Step     |
+| MDBj    | Step by Step high priority |
+| MDBk    | Open and block             |
+| MDBl    | Close and block            |
+| MDBm    | Block                      |
+| MDEw    | Release                    |
+| MDEx    | Courtesy ligh timer on     |
+| MDEy    | Courtesy light on-off      |
+| MDEz    | Step by Step master door   |
+| MDE0    | Open master door           |
+| MDE1    | Close master door          |
+| MDE2    | Step by Step slave door    |
+| MDE3    | Open slave door            |
+| MDE4    | Close slave door           |
+| MDE5    | Release and Open           |
+| MDFh    | Release and Close          |
+
+## Full Example
+
+### things/mynice.things
+
+```java
+Bridge mynice:it4wifi:83eef09166 "Nice IT4WIFI" @ "portail" [
+            hostname="192.168.0.198",
+            macAddress="00:xx:zz:dd:ff:gg",
+            password="v***************************zU=",
+            username="neo_prod"] {
+      swing 1 "Nice POA3 Moteur Portail" @ "portail" [id="1"]
+}
+```
+
+### items/mynice.items
+
+```java
+String   NiceIT4WIFI_GateStatus    "Gate Status" <gate>   (gMyniceSwing) ["Status","Opening"]     {channel="mynice:swing:83eef09166:1:status"}
+String   NiceIT4WIFI_Obstruction   "Obstruction" <none>   (gMyniceSwing)                          {channel="mynice:swing:83eef09166:1:obstruct"}
+Switch   NiceIT4WIFI_Moving        "Moving"      <motion> (gMyniceSwing) ["Status","Vibration"]   {channel="mynice:swing:83eef09166:1:moving"}
+String   NiceIT4WIFI_Command       "Command"     <none>   (gMyniceSwing)                          {channel="mynice:swing:83eef09166:1:command"}
+
+```
diff --git a/bundles/org.openhab.binding.mynice/pom.xml b/bundles/org.openhab.binding.mynice/pom.xml
new file mode 100644 (file)
index 0000000..47d08b3
--- /dev/null
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.openhab.addons.bundles</groupId>
+    <artifactId>org.openhab.addons.reactor.bundles</artifactId>
+    <version>4.0.0-SNAPSHOT</version>
+  </parent>
+
+  <artifactId>org.openhab.binding.mynice</artifactId>
+
+  <name>openHAB Add-ons :: Bundles :: MyNice Binding</name>
+
+</project>
diff --git a/bundles/org.openhab.binding.mynice/src/main/feature/feature.xml b/bundles/org.openhab.binding.mynice/src/main/feature/feature.xml
new file mode 100644 (file)
index 0000000..001b78d
--- /dev/null
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<features name="org.openhab.binding.mynice-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
+       <repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
+
+       <feature name="openhab-binding-mynice" description="MyNice Binding" version="${project.version}">
+               <feature>openhab-runtime-base</feature>
+               <bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.mynice/${project.version}</bundle>
+       </feature>
+</features>
diff --git a/bundles/org.openhab.binding.mynice/src/main/java/org/openhab/binding/mynice/internal/MyNiceBindingConstants.java b/bundles/org.openhab.binding.mynice/src/main/java/org/openhab/binding/mynice/internal/MyNiceBindingConstants.java
new file mode 100644 (file)
index 0000000..b785ac6
--- /dev/null
@@ -0,0 +1,43 @@
+/**
+ * Copyright (c) 2010-2023 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.mynice.internal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.core.thing.ThingTypeUID;
+
+/**
+ * The {@link MyNiceBindingConstants} class defines common constants, which are used across the whole binding.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class MyNiceBindingConstants {
+    private static final String BINDING_ID = "mynice";
+
+    // List of all Channel ids
+    public static final String DOOR_STATUS = "status";
+    public static final String DOOR_OBSTRUCTED = "obstruct";
+    public static final String DOOR_MOVING = "moving";
+    public static final String DOOR_COMMAND = "command";
+    public static final String DOOR_T4_COMMAND = "t4command";
+
+    // List of all Thing Type UIDs
+    public static final ThingTypeUID BRIDGE_TYPE_IT4WIFI = new ThingTypeUID(BINDING_ID, "it4wifi");
+    public static final ThingTypeUID THING_TYPE_SWING = new ThingTypeUID(BINDING_ID, "swing");
+    public static final ThingTypeUID THING_TYPE_SLIDING = new ThingTypeUID(BINDING_ID, "sliding");
+
+    // Configuration element of a portal
+    public static final String DEVICE_ID = "id";
+
+    public static final String ALLOWED_T4 = "allowedT4";
+}
diff --git a/bundles/org.openhab.binding.mynice/src/main/java/org/openhab/binding/mynice/internal/MyNiceHandlerFactory.java b/bundles/org.openhab.binding.mynice/src/main/java/org/openhab/binding/mynice/internal/MyNiceHandlerFactory.java
new file mode 100644 (file)
index 0000000..a5fc54a
--- /dev/null
@@ -0,0 +1,61 @@
+/**
+ * Copyright (c) 2010-2023 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.mynice.internal;
+
+import static org.openhab.binding.mynice.internal.MyNiceBindingConstants.*;
+
+import java.util.Set;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.mynice.internal.handler.GateHandler;
+import org.openhab.binding.mynice.internal.handler.It4WifiHandler;
+import org.openhab.core.thing.Bridge;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingTypeUID;
+import org.openhab.core.thing.binding.BaseThingHandlerFactory;
+import org.openhab.core.thing.binding.ThingHandler;
+import org.openhab.core.thing.binding.ThingHandlerFactory;
+import org.osgi.service.component.annotations.Component;
+
+/**
+ * The {@link MyNiceHandlerFactory} is responsible for creating thing handlers.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+@Component(configurationPid = "binding.mynice", service = ThingHandlerFactory.class)
+public class MyNiceHandlerFactory extends BaseThingHandlerFactory {
+    private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(BRIDGE_TYPE_IT4WIFI, THING_TYPE_SWING,
+            THING_TYPE_SLIDING);
+
+    @Override
+    public boolean supportsThingType(ThingTypeUID thingTypeUID) {
+        return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
+    }
+
+    @Override
+    protected @Nullable ThingHandler createHandler(Thing thing) {
+        ThingTypeUID thingTypeUID = thing.getThingTypeUID();
+
+        if (BRIDGE_TYPE_IT4WIFI.equals(thingTypeUID)) {
+            return new It4WifiHandler((Bridge) thing);
+        } else if (THING_TYPE_SWING.equals(thingTypeUID)) {
+            return new GateHandler(thing);
+        } else if (THING_TYPE_SLIDING.equals(thingTypeUID)) {
+            return new GateHandler(thing);
+        }
+
+        return null;
+    }
+}
diff --git a/bundles/org.openhab.binding.mynice/src/main/java/org/openhab/binding/mynice/internal/config/It4WifiConfiguration.java b/bundles/org.openhab.binding.mynice/src/main/java/org/openhab/binding/mynice/internal/config/It4WifiConfiguration.java
new file mode 100644 (file)
index 0000000..6b92fd3
--- /dev/null
@@ -0,0 +1,31 @@
+/**
+ * Copyright (c) 2010-2023 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.mynice.internal.config;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * The {@link It4WifiConfiguration} class contains fields mapping thing configuration parameters.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class It4WifiConfiguration {
+    public static final String PASSWORD = "password";
+    public static final String HOSTNAME = "hostname";
+
+    public String username = "";
+    public String hostname = "";
+    public String macAddress = "";
+    public String password = "";
+}
diff --git a/bundles/org.openhab.binding.mynice/src/main/java/org/openhab/binding/mynice/internal/discovery/MyNiceDiscoveryParticipant.java b/bundles/org.openhab.binding.mynice/src/main/java/org/openhab/binding/mynice/internal/discovery/MyNiceDiscoveryParticipant.java
new file mode 100644 (file)
index 0000000..267d7c6
--- /dev/null
@@ -0,0 +1,85 @@
+/**
+ * Copyright (c) 2010-2023 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.mynice.internal.discovery;
+
+import static org.openhab.binding.mynice.internal.MyNiceBindingConstants.BRIDGE_TYPE_IT4WIFI;
+import static org.openhab.binding.mynice.internal.config.It4WifiConfiguration.HOSTNAME;
+import static org.openhab.core.thing.Thing.PROPERTY_MAC_ADDRESS;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+import javax.jmdns.ServiceInfo;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.core.config.discovery.DiscoveryResult;
+import org.openhab.core.config.discovery.DiscoveryResultBuilder;
+import org.openhab.core.config.discovery.mdns.MDNSDiscoveryParticipant;
+import org.openhab.core.thing.ThingTypeUID;
+import org.openhab.core.thing.ThingUID;
+import org.osgi.service.component.annotations.Component;
+
+/**
+ * The {@link MyNiceDiscoveryParticipant} is responsible for discovering the IT4Wifi bridge using mDNS discovery service
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@Component
+@NonNullByDefault
+public class MyNiceDiscoveryParticipant implements MDNSDiscoveryParticipant {
+    private static final String PROPERTY_MODEL = "model";
+    private static final String PROPERTY_DEVICE_ID = "deviceid";
+    private static final String MAC_REGEX = "^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})|([0-9a-fA-F]{4}\\.[0-9a-fA-F]{4}\\.[0-9a-fA-F]{4})$";
+    private static final Pattern MAC_PATTERN = Pattern.compile(MAC_REGEX);
+
+    @Override
+    public Set<ThingTypeUID> getSupportedThingTypeUIDs() {
+        return Set.of(BRIDGE_TYPE_IT4WIFI);
+    }
+
+    @Override
+    public String getServiceType() {
+        return "_nap._tcp.local.";
+    }
+
+    @Override
+    public @Nullable DiscoveryResult createResult(ServiceInfo service) {
+        ThingUID thingUID = getThingUID(service);
+        String[] hostNames = service.getHostAddresses();
+        if (thingUID != null && hostNames.length > 0) {
+            String label = service.getPropertyString(PROPERTY_MODEL);
+            String macAddress = service.getPropertyString(PROPERTY_DEVICE_ID);
+
+            return DiscoveryResultBuilder.create(thingUID).withLabel(label)
+                    .withRepresentationProperty(PROPERTY_MAC_ADDRESS).withThingType(BRIDGE_TYPE_IT4WIFI)
+                    .withProperties(Map.of(HOSTNAME, hostNames[0], PROPERTY_MAC_ADDRESS, macAddress)).build();
+        }
+        return null;
+    }
+
+    @Override
+    public @Nullable ThingUID getThingUID(ServiceInfo service) {
+        String macAddress = service.getPropertyString(PROPERTY_DEVICE_ID);
+        if (macAddress != null && validate(macAddress)) {
+            macAddress = macAddress.replaceAll("[^a-fA-F0-9]", "").toLowerCase();
+            return new ThingUID(BRIDGE_TYPE_IT4WIFI, macAddress);
+        }
+        return null;
+    }
+
+    private boolean validate(String mac) {
+        return MAC_PATTERN.matcher(mac).find();
+    }
+}
diff --git a/bundles/org.openhab.binding.mynice/src/main/java/org/openhab/binding/mynice/internal/discovery/MyNiceDiscoveryService.java b/bundles/org.openhab.binding.mynice/src/main/java/org/openhab/binding/mynice/internal/discovery/MyNiceDiscoveryService.java
new file mode 100644 (file)
index 0000000..19dc182
--- /dev/null
@@ -0,0 +1,118 @@
+/**
+ * Copyright (c) 2010-2023 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.mynice.internal.discovery;
+
+import static org.openhab.binding.mynice.internal.MyNiceBindingConstants.*;
+
+import java.util.List;
+import java.util.Set;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.mynice.internal.handler.It4WifiHandler;
+import org.openhab.binding.mynice.internal.handler.MyNiceDataListener;
+import org.openhab.binding.mynice.internal.xml.dto.CommandType;
+import org.openhab.binding.mynice.internal.xml.dto.Device;
+import org.openhab.core.config.discovery.AbstractDiscoveryService;
+import org.openhab.core.config.discovery.DiscoveryResult;
+import org.openhab.core.config.discovery.DiscoveryResultBuilder;
+import org.openhab.core.thing.ThingUID;
+import org.openhab.core.thing.binding.ThingHandler;
+import org.openhab.core.thing.binding.ThingHandlerService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link MyNiceDiscoveryService} is responsible for discovering all things
+ * except the It4Wifi bridge itself
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class MyNiceDiscoveryService extends AbstractDiscoveryService
+        implements MyNiceDataListener, ThingHandlerService {
+
+    private static final int SEARCH_TIME = 5;
+    private final Logger logger = LoggerFactory.getLogger(MyNiceDiscoveryService.class);
+
+    private @Nullable It4WifiHandler bridgeHandler;
+
+    /**
+     * Creates a MyNiceDiscoveryService with background discovery disabled.
+     */
+    public MyNiceDiscoveryService() {
+        super(Set.of(THING_TYPE_SWING), SEARCH_TIME, false);
+    }
+
+    @Override
+    public void setThingHandler(ThingHandler handler) {
+        if (handler instanceof It4WifiHandler it4Handler) {
+            bridgeHandler = it4Handler;
+        }
+    }
+
+    @Override
+    public @Nullable ThingHandler getThingHandler() {
+        return bridgeHandler;
+    }
+
+    @Override
+    public void activate() {
+        super.activate(null);
+        It4WifiHandler handler = bridgeHandler;
+        if (handler != null) {
+            handler.registerDataListener(this);
+        }
+    }
+
+    @Override
+    public void deactivate() {
+        It4WifiHandler handler = bridgeHandler;
+        if (handler != null) {
+            handler.unregisterDataListener(this);
+        }
+        super.deactivate();
+    }
+
+    @Override
+    public void onDataFetched(List<Device> devices) {
+        It4WifiHandler handler = bridgeHandler;
+        if (handler != null) {
+            ThingUID bridgeUID = handler.getThing().getUID();
+            devices.stream().filter(device -> device.type != null).forEach(device -> {
+                ThingUID thingUID = switch (device.type) {
+                    case SWING -> new ThingUID(THING_TYPE_SWING, bridgeUID, device.id);
+                    case SLIDING -> new ThingUID(THING_TYPE_SLIDING, bridgeUID, device.id);
+                    default -> null;
+                };
+
+                if (thingUID != null) {
+                    DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withBridge(bridgeUID)
+                            .withLabel(String.format("%s %s", device.manuf, device.prod))
+                            .withRepresentationProperty(DEVICE_ID).withProperty(DEVICE_ID, device.id).build();
+                    thingDiscovered(discoveryResult);
+                } else {
+                    logger.info("`{}` type of device is not yet supported", device.type);
+                }
+            });
+        }
+    }
+
+    @Override
+    protected void startScan() {
+        It4WifiHandler handler = bridgeHandler;
+        if (handler != null) {
+            handler.sendCommand(CommandType.INFO);
+        }
+    }
+}
diff --git a/bundles/org.openhab.binding.mynice/src/main/java/org/openhab/binding/mynice/internal/handler/GateHandler.java b/bundles/org.openhab.binding.mynice/src/main/java/org/openhab/binding/mynice/internal/handler/GateHandler.java
new file mode 100644 (file)
index 0000000..8f5e9fb
--- /dev/null
@@ -0,0 +1,129 @@
+/**
+ * Copyright (c) 2010-2023 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.mynice.internal.handler;
+
+import static org.openhab.binding.mynice.internal.MyNiceBindingConstants.*;
+import static org.openhab.core.thing.Thing.*;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.mynice.internal.xml.dto.CommandType;
+import org.openhab.binding.mynice.internal.xml.dto.Device;
+import org.openhab.binding.mynice.internal.xml.dto.T4Command;
+import org.openhab.core.library.types.OnOffType;
+import org.openhab.core.library.types.StringType;
+import org.openhab.core.thing.Bridge;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.binding.BaseThingHandler;
+import org.openhab.core.thing.binding.BridgeHandler;
+import org.openhab.core.types.Command;
+import org.openhab.core.types.RefreshType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class GateHandler extends BaseThingHandler implements MyNiceDataListener {
+    private static final String OPENING = "opening";
+    private static final String CLOSING = "closing";
+
+    private final Logger logger = LoggerFactory.getLogger(GateHandler.class);
+
+    private String id = "";
+
+    public GateHandler(Thing thing) {
+        super(thing);
+    }
+
+    @Override
+    public void initialize() {
+        id = (String) getConfig().get(DEVICE_ID);
+        getBridgeHandler().ifPresent(h -> h.registerDataListener(this));
+    }
+
+    @Override
+    public void dispose() {
+        getBridgeHandler().ifPresent(h -> h.unregisterDataListener(this));
+    }
+
+    private Optional<It4WifiHandler> getBridgeHandler() {
+        Bridge bridge = getBridge();
+        if (bridge != null) {
+            BridgeHandler handler = bridge.getHandler();
+            if (handler instanceof It4WifiHandler it4Handler) {
+                return Optional.of(it4Handler);
+            }
+        }
+        return Optional.empty();
+    }
+
+    @Override
+    public void handleCommand(ChannelUID channelUID, Command command) {
+        if (command instanceof RefreshType) {
+            return;
+        } else {
+            handleCommand(channelUID.getId(), command.toString());
+        }
+    }
+
+    private void handleCommand(String channelId, String command) {
+        if (DOOR_COMMAND.equals(channelId)) {
+            getBridgeHandler().ifPresent(handler -> handler.sendCommand(id, command));
+        } else if (DOOR_T4_COMMAND.equals(channelId)) {
+            String allowed = thing.getProperties().get(ALLOWED_T4);
+            if (allowed != null && allowed.contains(command)) {
+                getBridgeHandler().ifPresent(handler -> {
+                    try {
+                        T4Command t4 = T4Command.fromCode(command);
+                        handler.sendCommand(id, t4);
+                    } catch (IllegalArgumentException e) {
+                        logger.warn("{} is not a valid T4 command", command);
+                    }
+                });
+            } else {
+                logger.warn("This thing does not accept the T4 command '{}'", command);
+            }
+        }
+    }
+
+    @Override
+    public void onDataFetched(List<Device> devices) {
+        devices.stream().filter(d -> id.equals(d.id)).findFirst().map(device -> {
+            updateStatus(ThingStatus.ONLINE);
+            if (thing.getProperties().isEmpty()) {
+                int value = Integer.parseInt(device.properties.t4allowed.values, 16);
+                List<String> t4Allowed = T4Command.fromBitmask(value).stream().map(Enum::name).toList();
+                updateProperties(Map.of(PROPERTY_VENDOR, device.manuf, PROPERTY_MODEL_ID, device.prod,
+                        PROPERTY_SERIAL_NUMBER, device.serialNr, PROPERTY_HARDWARE_VERSION, device.versionHW,
+                        PROPERTY_FIRMWARE_VERSION, device.versionFW, ALLOWED_T4, String.join(",", t4Allowed)));
+            }
+            if (device.prod != null) {
+                getBridgeHandler().ifPresent(h -> h.sendCommand(CommandType.STATUS));
+            } else {
+                String status = device.properties.doorStatus;
+                updateState(DOOR_STATUS, new StringType(status));
+                updateState(DOOR_OBSTRUCTED, OnOffType.from("1".equals(device.properties.obstruct)));
+                updateState(DOOR_MOVING, OnOffType.from(status.equals(CLOSING) || status.equals(OPENING)));
+            }
+            return true;
+        });
+    }
+}
diff --git a/bundles/org.openhab.binding.mynice/src/main/java/org/openhab/binding/mynice/internal/handler/It4WifiConnector.java b/bundles/org.openhab.binding.mynice/src/main/java/org/openhab/binding/mynice/internal/handler/It4WifiConnector.java
new file mode 100644 (file)
index 0000000..0a5a0c7
--- /dev/null
@@ -0,0 +1,141 @@
+/**
+ * Copyright (c) 2010-2023 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.mynice.internal.handler;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.security.KeyManagementException;
+import java.security.NoSuchAlgorithmException;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.TrustManager;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.core.io.net.http.TrustAllTrustManager;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link It4WifiConnector} is responsible for connecting reading, writing and disconnecting from the It4Wifi.
+ *
+ * @author Gaël L'hopital - Initial Contribution
+ */
+@NonNullByDefault
+public class It4WifiConnector extends Thread {
+    private static final int SERVER_PORT = 443;
+    private static final char ETX = '\u0003';
+    private static final char STX = '\u0002';
+
+    private final Logger logger = LoggerFactory.getLogger(It4WifiConnector.class);
+    private final It4WifiHandler handler;
+    private final SSLSocket sslsocket;
+
+    private @NonNullByDefault({}) InputStreamReader in;
+    private @NonNullByDefault({}) OutputStreamWriter out;
+
+    public It4WifiConnector(String hostname, It4WifiHandler handler) {
+        super(It4WifiConnector.class.getName());
+        this.handler = handler;
+        try {
+            SSLContext sslContext = SSLContext.getInstance("SSL");
+            sslContext.init(null, new TrustManager[] { TrustAllTrustManager.getInstance() }, null);
+            sslsocket = (SSLSocket) sslContext.getSocketFactory().createSocket(hostname, SERVER_PORT);
+            setDaemon(true);
+        } catch (NoSuchAlgorithmException | KeyManagementException | IOException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+
+    @Override
+    public void run() {
+        String buffer = "";
+        try {
+            connect();
+            while (!interrupted()) {
+                int data;
+                while ((data = in.read()) != -1) {
+                    if (data == STX) {
+                        buffer = "";
+                    } else if (data == ETX) {
+                        handler.received(buffer);
+                    } else {
+                        buffer += (char) data;
+                    }
+                }
+            }
+            handler.connectorInterrupted("IT4WifiConnector interrupted");
+            dispose();
+        } catch (IOException e) {
+            handler.connectorInterrupted(e.getMessage());
+        }
+    }
+
+    public synchronized void sendCommand(String command) {
+        logger.debug("Sending ItT4Wifi :{}", command);
+        try {
+            out.write(STX + command + ETX);
+            out.flush();
+        } catch (IOException e) {
+            handler.connectorInterrupted(e.getMessage());
+        }
+    }
+
+    private void disconnect() {
+        logger.debug("Disconnecting");
+
+        if (in != null) {
+            try {
+                in.close();
+            } catch (IOException ignore) {
+            }
+        }
+        if (out != null) {
+            try {
+                out.close();
+            } catch (IOException ignore) {
+            }
+        }
+
+        in = null;
+        out = null;
+
+        logger.debug("Disconnected");
+    }
+
+    /**
+     * Stop the device thread
+     *
+     * @throws IOException
+     */
+    public void dispose() {
+        interrupt();
+        disconnect();
+        try {
+            sslsocket.close();
+        } catch (IOException e) {
+            logger.warn("Error closing sslsocket : {}", e.getMessage());
+        }
+    }
+
+    private void connect() throws IOException {
+        disconnect();
+        logger.debug("Initiating connection to IT4Wifi on port {}...", SERVER_PORT);
+
+        sslsocket.startHandshake();
+        in = new InputStreamReader(sslsocket.getInputStream());
+        out = new OutputStreamWriter(sslsocket.getOutputStream());
+        handler.handShaked();
+    }
+}
diff --git a/bundles/org.openhab.binding.mynice/src/main/java/org/openhab/binding/mynice/internal/handler/It4WifiHandler.java b/bundles/org.openhab.binding.mynice/src/main/java/org/openhab/binding/mynice/internal/handler/It4WifiHandler.java
new file mode 100644 (file)
index 0000000..b6535a5
--- /dev/null
@@ -0,0 +1,244 @@
+/**
+ * Copyright (c) 2010-2023 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.mynice.internal.handler;
+
+import static org.openhab.core.thing.Thing.*;
+import static org.openhab.core.types.RefreshType.REFRESH;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.mynice.internal.config.It4WifiConfiguration;
+import org.openhab.binding.mynice.internal.discovery.MyNiceDiscoveryService;
+import org.openhab.binding.mynice.internal.xml.MyNiceXStream;
+import org.openhab.binding.mynice.internal.xml.RequestBuilder;
+import org.openhab.binding.mynice.internal.xml.dto.CommandType;
+import org.openhab.binding.mynice.internal.xml.dto.Device;
+import org.openhab.binding.mynice.internal.xml.dto.Event;
+import org.openhab.binding.mynice.internal.xml.dto.Response;
+import org.openhab.binding.mynice.internal.xml.dto.T4Command;
+import org.openhab.core.config.core.Configuration;
+import org.openhab.core.thing.Bridge;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingStatusDetail;
+import org.openhab.core.thing.binding.BaseBridgeHandler;
+import org.openhab.core.thing.binding.ThingHandlerService;
+import org.openhab.core.types.Command;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link It4WifiHandler} is responsible for handling commands, which are
+ * sent to one of the channels.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class It4WifiHandler extends BaseBridgeHandler {
+    private static final int MAX_HANDSHAKE_ATTEMPTS = 3;
+    private static final int KEEPALIVE_DELAY_S = 235; // Timeout seems to be at 6 min
+
+    private final Logger logger = LoggerFactory.getLogger(It4WifiHandler.class);
+    private final List<MyNiceDataListener> dataListeners = new CopyOnWriteArrayList<>();
+    private final MyNiceXStream xstream = new MyNiceXStream();
+
+    private @NonNullByDefault({}) RequestBuilder reqBuilder;
+    private @Nullable It4WifiConnector connector;
+    private @Nullable ScheduledFuture<?> keepAliveJob;
+    private List<Device> devices = new ArrayList<>();
+    private int handshakeAttempts = 0;
+
+    public It4WifiHandler(Bridge thing) {
+        super(thing);
+    }
+
+    @Override
+    public Collection<Class<? extends ThingHandlerService>> getServices() {
+        return Set.of(MyNiceDiscoveryService.class);
+    }
+
+    public void registerDataListener(MyNiceDataListener dataListener) {
+        dataListeners.add(dataListener);
+        notifyListeners(devices);
+    }
+
+    public void unregisterDataListener(MyNiceDataListener dataListener) {
+        dataListeners.remove(dataListener);
+    }
+
+    @Override
+    public void handleCommand(ChannelUID channelUID, Command command) {
+        if (REFRESH.equals(command)) {
+            sendCommand(CommandType.INFO);
+        }
+    }
+
+    @Override
+    public void initialize() {
+        if (getConfigAs(It4WifiConfiguration.class).username.isBlank()) {
+            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "@text/conf-error-no-username");
+        } else {
+            updateStatus(ThingStatus.UNKNOWN);
+            scheduler.execute(() -> startConnector());
+        }
+    }
+
+    @Override
+    public void dispose() {
+        It4WifiConnector localConnector = connector;
+        if (localConnector != null) {
+            localConnector.dispose();
+        }
+        freeKeepAlive();
+    }
+
+    private void startConnector() {
+        It4WifiConfiguration config = getConfigAs(It4WifiConfiguration.class);
+        freeKeepAlive();
+        reqBuilder = new RequestBuilder(config.macAddress, config.username);
+        It4WifiConnector localConnector = new It4WifiConnector(config.hostname, this);
+        localConnector.start();
+        connector = localConnector;
+    }
+
+    private void freeKeepAlive() {
+        ScheduledFuture<?> keepAlive = keepAliveJob;
+        if (keepAlive != null) {
+            keepAlive.cancel(true);
+        }
+        keepAliveJob = null;
+    }
+
+    public void received(String command) {
+        logger.debug("Received : {}", command);
+        Event event = xstream.deserialize(command);
+        if (event.error != null) {
+            logger.warn("Error code {} received : {}", event.error.code, event.error.info);
+        } else {
+            if (event instanceof Response) {
+                handleResponse((Response) event);
+            } else {
+                notifyListeners(event.getDevices());
+            }
+        }
+    }
+
+    private void handleResponse(Response response) {
+        switch (response.type) {
+            case PAIR:
+                Configuration thingConfig = editConfiguration();
+                thingConfig.put(It4WifiConfiguration.PASSWORD, response.authentication.pwd);
+                updateConfiguration(thingConfig);
+                logger.info("Pairing key updated in Configuration.");
+                sendCommand(CommandType.VERIFY);
+                return;
+            case VERIFY:
+                if (keepAliveJob != null) { // means we are connected
+                    return;
+                }
+                switch (response.authentication.perm) {
+                    case admin:
+                    case user:
+                        sendCommand(CommandType.CONNECT);
+                        return;
+                    case wait:
+                        updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING,
+                                "@text/conf-pending-validation");
+                        scheduler.schedule(() -> handShaked(), 15, TimeUnit.SECONDS);
+                        return;
+                    default:
+                        return;
+                }
+            case CONNECT:
+                String sc = response.authentication.sc;
+                It4WifiConfiguration config = getConfigAs(It4WifiConfiguration.class);
+                if (sc != null) {
+                    reqBuilder.setChallenges(sc, response.authentication.id, config.password);
+                    keepAliveJob = scheduler.scheduleWithFixedDelay(() -> sendCommand(CommandType.VERIFY),
+                            KEEPALIVE_DELAY_S, KEEPALIVE_DELAY_S, TimeUnit.SECONDS);
+                    sendCommand(CommandType.INFO);
+                }
+                return;
+            case INFO:
+                updateStatus(ThingStatus.ONLINE);
+                if (thing.getProperties().isEmpty()) {
+                    Map<String, String> properties = Map.of(PROPERTY_VENDOR, response.intf.manuf, PROPERTY_MODEL_ID,
+                            response.intf.prod, PROPERTY_SERIAL_NUMBER, response.intf.serialNr,
+                            PROPERTY_HARDWARE_VERSION, response.intf.versionHW, PROPERTY_FIRMWARE_VERSION,
+                            response.intf.versionFW);
+                    updateProperties(properties);
+                }
+                notifyListeners(response.getDevices());
+                return;
+            case STATUS:
+                notifyListeners(response.getDevices());
+                return;
+            case CHANGE:
+                logger.debug("Change command accepted");
+                return;
+            default:
+                logger.warn("Unhandled response type : {}", response.type);
+        }
+    }
+
+    public void handShaked() {
+        handshakeAttempts = 0;
+        It4WifiConfiguration config = getConfigAs(It4WifiConfiguration.class);
+        sendCommand(config.password.isBlank() ? CommandType.PAIR : CommandType.VERIFY);
+    }
+
+    private void notifyListeners(List<Device> list) {
+        devices = list;
+        dataListeners.forEach(listener -> listener.onDataFetched(devices));
+    }
+
+    private void sendCommand(String command) {
+        It4WifiConnector localConnector = connector;
+        if (localConnector != null) {
+            localConnector.sendCommand(command);
+        } else {
+            logger.warn("Tried to send a command when IT4WifiConnector is not initialized.");
+        }
+    }
+
+    public void sendCommand(CommandType command) {
+        sendCommand(reqBuilder.buildMessage(command));
+    }
+
+    public void sendCommand(String id, String command) {
+        sendCommand(reqBuilder.buildMessage(id, command.toLowerCase()));
+    }
+
+    public void sendCommand(String id, T4Command t4) {
+        sendCommand(reqBuilder.buildMessage(id, t4));
+    }
+
+    public void connectorInterrupted(@Nullable String message) {
+        if (handshakeAttempts++ <= MAX_HANDSHAKE_ATTEMPTS) {
+            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, message);
+            startConnector();
+        } else {
+            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "@text/error-handshake-limit");
+            connector = null;
+        }
+    }
+}
diff --git a/bundles/org.openhab.binding.mynice/src/main/java/org/openhab/binding/mynice/internal/handler/MyNiceDataListener.java b/bundles/org.openhab.binding.mynice/src/main/java/org/openhab/binding/mynice/internal/handler/MyNiceDataListener.java
new file mode 100644 (file)
index 0000000..aa2112c
--- /dev/null
@@ -0,0 +1,30 @@
+/**
+ * Copyright (c) 2010-2023 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.mynice.internal.handler;
+
+import java.util.List;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.mynice.internal.xml.dto.Device;
+
+/**
+ * The {@link MyNiceDataListener} is notified by the bridge thing handler with updated data from
+ * the IP4Wifi.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public interface MyNiceDataListener {
+
+    public void onDataFetched(List<Device> devices);
+}
diff --git a/bundles/org.openhab.binding.mynice/src/main/java/org/openhab/binding/mynice/internal/xml/MyNiceXStream.java b/bundles/org.openhab.binding.mynice/src/main/java/org/openhab/binding/mynice/internal/xml/MyNiceXStream.java
new file mode 100644 (file)
index 0000000..e572f88
--- /dev/null
@@ -0,0 +1,63 @@
+/**
+ * Copyright (c) 2010-2023 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.mynice.internal.xml;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.mynice.internal.xml.dto.Authentication;
+import org.openhab.binding.mynice.internal.xml.dto.Authentication.UserPerm;
+import org.openhab.binding.mynice.internal.xml.dto.CommandType;
+import org.openhab.binding.mynice.internal.xml.dto.Device;
+import org.openhab.binding.mynice.internal.xml.dto.Device.DeviceType;
+import org.openhab.binding.mynice.internal.xml.dto.Error;
+import org.openhab.binding.mynice.internal.xml.dto.Event;
+import org.openhab.binding.mynice.internal.xml.dto.Interface;
+import org.openhab.binding.mynice.internal.xml.dto.Properties;
+import org.openhab.binding.mynice.internal.xml.dto.Response;
+
+import com.thoughtworks.xstream.XStream;
+import com.thoughtworks.xstream.io.xml.StaxDriver;
+
+/**
+ * The {@link MyNiceXStream} class is a utility class that wraps an XStream object and provide additional
+ * functionality specific to the MyNice binding. It automatically load the correct converter classes and
+ * processes the XStream annotations used by the object classes.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+
+@NonNullByDefault
+public class MyNiceXStream extends XStream {
+    public static final String XML_HEADER = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>";
+
+    public MyNiceXStream() {
+        super(new StaxDriver());
+        allowTypesByWildcard(new String[] { Response.class.getPackageName() + ".**" });
+        setClassLoader(getClass().getClassLoader());
+        autodetectAnnotations(true);
+        ignoreUnknownElements();
+        alias("Response", Response.class);
+        alias("Event", Event.class);
+        alias("Authentication", Authentication.class);
+        alias("CommandType", CommandType.class);
+        alias("UserPerm", UserPerm.class);
+        alias("DeviceType", DeviceType.class);
+        alias("Error", Error.class);
+        alias("Interface", Interface.class);
+        alias("Device", Device.class);
+        alias("Properties", Properties.class);
+    }
+
+    public Event deserialize(String response) {
+        return (Event) fromXML(XML_HEADER + response);
+    }
+}
diff --git a/bundles/org.openhab.binding.mynice/src/main/java/org/openhab/binding/mynice/internal/xml/RequestBuilder.java b/bundles/org.openhab.binding.mynice/src/main/java/org/openhab/binding/mynice/internal/xml/RequestBuilder.java
new file mode 100644 (file)
index 0000000..da480fb
--- /dev/null
@@ -0,0 +1,128 @@
+/**
+ * Copyright (c) 2010-2023 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.mynice.internal.xml;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Base64;
+import java.util.Base64.Encoder;
+import java.util.UUID;
+
+import javax.xml.bind.DatatypeConverter;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.mynice.internal.xml.dto.CommandType;
+import org.openhab.binding.mynice.internal.xml.dto.T4Command;
+
+/**
+ * The {@link RequestBuilder} is responsible for building a string request from the CommandType
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class RequestBuilder {
+    private static final Encoder BASE64_ENCODER = Base64.getEncoder();
+
+    public static final String USERNAME = "%un%";
+    public static final String CLIENT_CHALLENGE = "%cc%";
+    private static final String START_REQUEST = "<Request id=\"%s\" source=\"openhab\" target=\"%s\" gw=\"gwID\" protocolType=\"NHK\" protocolVersion=\"1.0\" type=\"%s\">\r\n";
+    private static final String END_REQUEST = "%s%s</Request>";
+    private static final String DOOR_ACTION = "<DoorAction>%s</DoorAction>";
+    private static final String T4_ACTION = "<T4Action>%s</T4Action>";
+    private static final String SIGN = "<Sign>%s</Sign>";
+
+    private final String clientChallenge = UUID.randomUUID().toString().substring(0, 8);
+    private final byte[] clientChallengeArr = invertArray(DatatypeConverter.parseHexBinary(clientChallenge));
+    private final MessageDigest digest;
+    private final String it4WifiMac;
+    private final String username;
+
+    private int sessionId = 0;
+    private int commandSequence = 0;
+    private byte[] sessionPassword = {};
+
+    public RequestBuilder(String it4WifiMac, String username) {
+        try {
+            this.digest = MessageDigest.getInstance("SHA-256");
+            this.it4WifiMac = it4WifiMac;
+            this.username = username;
+        } catch (NoSuchAlgorithmException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+
+    private String buildSign(CommandType command, String message) {
+        if (command.signNeeded) {
+            byte[] msgHash = sha256(message.getBytes());
+            byte[] sign = sha256(msgHash, sessionPassword);
+            return String.format(SIGN, BASE64_ENCODER.encodeToString(sign));
+        }
+        return "";
+    }
+
+    public String buildMessage(String id, String command) {
+        return buildMessage(CommandType.CHANGE, id, String.format(DOOR_ACTION, command.toLowerCase()));
+    }
+
+    public String buildMessage(String id, T4Command t4) {
+        return buildMessage(CommandType.CHANGE, id, String.format(T4_ACTION, t4.name()));
+    }
+
+    public String buildMessage(CommandType command, Object... bodyParms) {
+        String startRequest = String.format(START_REQUEST, getCommandId(), it4WifiMac, command);
+        String body = startRequest + getBody(command, bodyParms);
+        String sign = buildSign(command, body);
+        return String.format(END_REQUEST, body, sign);
+    }
+
+    public String getBody(CommandType command, Object... bodyParms) {
+        String result = command.body;
+        if (result.length() != 0) {
+            result = result.replace(USERNAME, username);
+            result = result.replace(CLIENT_CHALLENGE, clientChallenge);
+            result = String.format(result, bodyParms);
+        }
+        return result;
+    }
+
+    public int getCommandId() {
+        return (commandSequence++ << 8) | sessionId;
+    }
+
+    public void setChallenges(String serverChallenge, int sessionId, String password) {
+        byte[] serverChallengeArr = invertArray(DatatypeConverter.parseHexBinary(serverChallenge));
+        byte[] pairingPassword = Base64.getDecoder().decode(password);
+        this.sessionPassword = sha256(pairingPassword, serverChallengeArr, clientChallengeArr);
+        this.sessionId = sessionId & 255;
+    }
+
+    private byte[] sha256(byte[]... values) {
+        for (byte[] data : values) {
+            digest.update(data);
+        }
+        return digest.digest();
+    }
+
+    private static byte[] invertArray(byte[] data) {
+        byte[] result = new byte[data.length];
+        int i = data.length - 1;
+        int c = 0;
+        while (i >= 0) {
+            int c2 = c + 1;
+            result[c] = data[i];
+            i--;
+            c = c2;
+        }
+        return result;
+    }
+}
diff --git a/bundles/org.openhab.binding.mynice/src/main/java/org/openhab/binding/mynice/internal/xml/dto/Authentication.java b/bundles/org.openhab.binding.mynice/src/main/java/org/openhab/binding/mynice/internal/xml/dto/Authentication.java
new file mode 100644 (file)
index 0000000..0c5be7d
--- /dev/null
@@ -0,0 +1,40 @@
+/**
+ * Copyright (c) 2010-2023 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.mynice.internal.xml.dto;
+
+import com.thoughtworks.xstream.annotations.XStreamAsAttribute;
+
+/**
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+public class Authentication {
+    public enum UserPerm {
+        wait,
+        user,
+        admin;
+    }
+
+    @XStreamAsAttribute
+    public int id;
+    @XStreamAsAttribute
+    public String pwd;
+    @XStreamAsAttribute
+    private String username;
+    @XStreamAsAttribute
+    public UserPerm perm;
+    @XStreamAsAttribute
+    public boolean notify;
+    @XStreamAsAttribute
+    public String sc;
+}
diff --git a/bundles/org.openhab.binding.mynice/src/main/java/org/openhab/binding/mynice/internal/xml/dto/CommandType.java b/bundles/org.openhab.binding.mynice/src/main/java/org/openhab/binding/mynice/internal/xml/dto/CommandType.java
new file mode 100644 (file)
index 0000000..e99d759
--- /dev/null
@@ -0,0 +1,42 @@
+/**
+ * Copyright (c) 2010-2023 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.mynice.internal.xml.dto;
+
+import static org.openhab.binding.mynice.internal.xml.RequestBuilder.*;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * The CommandType enum lists all handled command with according syntax
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public enum CommandType {
+    PAIR(false,
+            "<Authentication username=\"" + USERNAME
+                    + "\" cc=\"null\" CType=\"phone\" OSType=\"Android\" OSVer=\"6.0.1\"/>"),
+    VERIFY(false, "<User username=\"" + USERNAME + "\"/>"),
+    CONNECT(false, "<Authentication username=\"" + USERNAME + "\" cc=\"" + CLIENT_CHALLENGE + "\"/>"),
+    INFO(true, ""),
+    STATUS(true, ""),
+    CHANGE(true, "<Devices><Device id=\"%s\"><Services>%s</Services></Device></Devices>");
+
+    public final boolean signNeeded;
+    public final String body;
+
+    CommandType(boolean signNeeded, String body) {
+        this.signNeeded = signNeeded;
+        this.body = body;
+    }
+}
diff --git a/bundles/org.openhab.binding.mynice/src/main/java/org/openhab/binding/mynice/internal/xml/dto/Device.java b/bundles/org.openhab.binding.mynice/src/main/java/org/openhab/binding/mynice/internal/xml/dto/Device.java
new file mode 100644 (file)
index 0000000..e4a0b3b
--- /dev/null
@@ -0,0 +1,57 @@
+/**
+ * Copyright (c) 2010-2023 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.mynice.internal.xml.dto;
+
+import com.thoughtworks.xstream.annotations.XStreamAlias;
+import com.thoughtworks.xstream.annotations.XStreamAsAttribute;
+
+/**
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+public class Device {
+    public enum DeviceType {
+        SECTIONAL,
+        UP_AND_OVER,
+        SLIDING,
+        BARRIER,
+        SWING;
+    }
+
+    @XStreamAsAttribute
+    public String id;
+
+    @XStreamAlias("Type")
+    public DeviceType type;
+
+    @XStreamAlias("Manuf")
+    public String manuf;
+
+    @XStreamAlias("Prod")
+    public String prod;
+
+    @XStreamAlias("Desc")
+    public String desc;
+
+    @XStreamAlias("VersionHW")
+    public String versionHW;
+
+    @XStreamAlias("VersionFW")
+    public String versionFW;
+
+    @XStreamAlias("SerialNr")
+    public String serialNr;
+
+    @XStreamAlias("Properties")
+    public Properties properties;
+}
diff --git a/bundles/org.openhab.binding.mynice/src/main/java/org/openhab/binding/mynice/internal/xml/dto/Error.java b/bundles/org.openhab.binding.mynice/src/main/java/org/openhab/binding/mynice/internal/xml/dto/Error.java
new file mode 100644 (file)
index 0000000..4e753d1
--- /dev/null
@@ -0,0 +1,26 @@
+/**
+ * Copyright (c) 2010-2023 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.mynice.internal.xml.dto;
+
+import com.thoughtworks.xstream.annotations.XStreamAlias;
+
+/**
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+public class Error {
+    @XStreamAlias("Code")
+    public int code;
+    @XStreamAlias("Info")
+    public String info;
+}
diff --git a/bundles/org.openhab.binding.mynice/src/main/java/org/openhab/binding/mynice/internal/xml/dto/Event.java b/bundles/org.openhab.binding.mynice/src/main/java/org/openhab/binding/mynice/internal/xml/dto/Event.java
new file mode 100644 (file)
index 0000000..3a55872
--- /dev/null
@@ -0,0 +1,49 @@
+/**
+ * Copyright (c) 2010-2023 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.mynice.internal.xml.dto;
+
+import java.util.List;
+
+import org.eclipse.jdt.annotation.NonNull;
+
+import com.thoughtworks.xstream.annotations.XStreamAlias;
+import com.thoughtworks.xstream.annotations.XStreamAsAttribute;
+
+/**
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+public class Event {
+    @XStreamAsAttribute
+    private String id;
+    @XStreamAsAttribute
+    private String source;
+    @XStreamAsAttribute
+    private String target;
+    @XStreamAsAttribute
+    private String protocolType;
+    @XStreamAsAttribute
+    private String protocolVersion;
+    @XStreamAsAttribute
+    public CommandType type;
+    @XStreamAlias("Error")
+    public Error error;
+
+    @XStreamAlias("Devices")
+    private List<Device> devices;
+
+    public @NonNull List<Device> getDevices() {
+        List<Device> localDevices = devices;
+        return localDevices == null ? List.of() : localDevices;
+    }
+}
diff --git a/bundles/org.openhab.binding.mynice/src/main/java/org/openhab/binding/mynice/internal/xml/dto/Interface.java b/bundles/org.openhab.binding.mynice/src/main/java/org/openhab/binding/mynice/internal/xml/dto/Interface.java
new file mode 100644 (file)
index 0000000..02e15d0
--- /dev/null
@@ -0,0 +1,36 @@
+/**
+ * Copyright (c) 2010-2023 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.mynice.internal.xml.dto;
+
+import com.thoughtworks.xstream.annotations.XStreamAlias;
+
+/**
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+public class Interface {
+    @XStreamAlias("Zone")
+    public String zone;
+    @XStreamAlias("DST")
+    public String dst;
+    @XStreamAlias("VersionHW")
+    public String versionHW;
+    @XStreamAlias("VersionFW")
+    public String versionFW;
+    @XStreamAlias("Manuf")
+    public String manuf;
+    @XStreamAlias("Prod")
+    public String prod;
+    @XStreamAlias("SerialNr")
+    public String serialNr;
+}
diff --git a/bundles/org.openhab.binding.mynice/src/main/java/org/openhab/binding/mynice/internal/xml/dto/Properties.java b/bundles/org.openhab.binding.mynice/src/main/java/org/openhab/binding/mynice/internal/xml/dto/Properties.java
new file mode 100644 (file)
index 0000000..e90b6c0
--- /dev/null
@@ -0,0 +1,29 @@
+/**
+ * Copyright (c) 2010-2023 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.mynice.internal.xml.dto;
+
+import com.thoughtworks.xstream.annotations.XStreamAlias;
+
+/**
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@XStreamAlias("Properties")
+public class Properties {
+    @XStreamAlias("DoorStatus")
+    public String doorStatus;
+    @XStreamAlias("Obstruct")
+    public String obstruct;
+    @XStreamAlias("T4_allowed")
+    public Property t4allowed;
+}
diff --git a/bundles/org.openhab.binding.mynice/src/main/java/org/openhab/binding/mynice/internal/xml/dto/Property.java b/bundles/org.openhab.binding.mynice/src/main/java/org/openhab/binding/mynice/internal/xml/dto/Property.java
new file mode 100644 (file)
index 0000000..8058928
--- /dev/null
@@ -0,0 +1,28 @@
+/**
+ * Copyright (c) 2010-2023 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.mynice.internal.xml.dto;
+
+import com.thoughtworks.xstream.annotations.XStreamAsAttribute;
+
+/**
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+public class Property {
+    @XStreamAsAttribute
+    public String type;
+    @XStreamAsAttribute
+    public String values;
+    @XStreamAsAttribute
+    public String perm;
+}
diff --git a/bundles/org.openhab.binding.mynice/src/main/java/org/openhab/binding/mynice/internal/xml/dto/Response.java b/bundles/org.openhab.binding.mynice/src/main/java/org/openhab/binding/mynice/internal/xml/dto/Response.java
new file mode 100644 (file)
index 0000000..f6ae5c4
--- /dev/null
@@ -0,0 +1,26 @@
+/**
+ * Copyright (c) 2010-2023 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.mynice.internal.xml.dto;
+
+import com.thoughtworks.xstream.annotations.XStreamAlias;
+
+/**
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+public class Response extends Event {
+    @XStreamAlias("Authentication")
+    public Authentication authentication;
+    @XStreamAlias("Interface")
+    public Interface intf;
+}
diff --git a/bundles/org.openhab.binding.mynice/src/main/java/org/openhab/binding/mynice/internal/xml/dto/T4Command.java b/bundles/org.openhab.binding.mynice/src/main/java/org/openhab/binding/mynice/internal/xml/dto/T4Command.java
new file mode 100644 (file)
index 0000000..3b4812d
--- /dev/null
@@ -0,0 +1,67 @@
+/**
+ * Copyright (c) 2010-2023 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.mynice.internal.xml.dto;
+
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * This enum lists all handled T4 commands
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public enum T4Command {
+    MDAx(1),
+    MDAy(2),
+    MDAz(3),
+    MDA0(4),
+    MDA1(5),
+    MDA2(6),
+    MDA3(7),
+    MDBi(11),
+    MDBj(12),
+    MDBk(13),
+    MDBl(14),
+    MDBm(15),
+    MDEw(16),
+    MDEx(17),
+    MDEy(18),
+    MDEz(19),
+    MDE0(20),
+    MDE1(21),
+    MDE2(22),
+    MDE3(23),
+    MDE4(24),
+    MDE5(25),
+    MDFh(26);
+
+    private int bitPosition;
+
+    private T4Command(int bitPosition) {
+        this.bitPosition = bitPosition;
+    }
+
+    public static T4Command fromCode(String commandCode) {
+        return Stream.of(T4Command.values()).filter(command -> command.name().equalsIgnoreCase(commandCode)).findFirst()
+                .orElseThrow(() -> new IllegalArgumentException("Unknown T4 command code (%s)".formatted(commandCode)));
+    }
+
+    public static List<T4Command> fromBitmask(int bitmask) {
+        return Stream.of(T4Command.values()).filter(command -> ((1 << command.bitPosition) & bitmask) != 0)
+                .collect(Collectors.toList());
+    }
+}
diff --git a/bundles/org.openhab.binding.mynice/src/main/resources/OH-INF/addon/addon.xml b/bundles/org.openhab.binding.mynice/src/main/resources/OH-INF/addon/addon.xml
new file mode 100644 (file)
index 0000000..537e9ce
--- /dev/null
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<addon:addon id="mynice" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:addon="https://openhab.org/schemas/addon/v1.0.0"
+       xsi:schemaLocation="https://openhab.org/schemas/addon/v1.0.0 https://openhab.org/schemas/addon-1.0.0.xsd">
+
+       <type>binding</type>
+       <name>MyNice Binding</name>
+       <description>This binding lets you connect to your IT4Wifi Nice device.</description>
+
+</addon:addon>
diff --git a/bundles/org.openhab.binding.mynice/src/main/resources/OH-INF/i18n/mynice.properties b/bundles/org.openhab.binding.mynice/src/main/resources/OH-INF/i18n/mynice.properties
new file mode 100644 (file)
index 0000000..fe20f9f
--- /dev/null
@@ -0,0 +1,78 @@
+# add-on
+
+addon.mynice.name = MyNice Binding
+addon.mynice.description = This binding lets you connect to your IT4Wifi Nice device.
+
+# thing types
+
+thing-type.mynice.it4wifi.label = IT4Wifi
+thing-type.mynice.it4wifi.description = This thing connects to your IT4Wifi module
+thing-type.mynice.sliding.label = Sliding Gate
+thing-type.mynice.sliding.description = A sliding gate
+thing-type.mynice.swing.label = Swing Gate
+thing-type.mynice.swing.description = A dual swing gate
+
+# thing types config
+
+thing-type.config.mynice.it4wifi.hostname.label = Hostname
+thing-type.config.mynice.it4wifi.hostname.description = Hostname or IP address of the IT4Wifi
+thing-type.config.mynice.it4wifi.macAddress.label = MAC Address
+thing-type.config.mynice.it4wifi.macAddress.description = The MAC address of the IT4Wifi
+thing-type.config.mynice.it4wifi.password.label = Pairing Key
+thing-type.config.mynice.it4wifi.password.description = Pairing Key needed to access the device, provided by the bridge itself
+thing-type.config.mynice.it4wifi.username.label = Username
+thing-type.config.mynice.it4wifi.username.description = User defined on the IT4Wifi for openHAB
+thing-type.config.mynice.sliding.id.label = ID
+thing-type.config.mynice.sliding.id.description = ID of the gate on the TP4 bus connected to the bridge.
+thing-type.config.mynice.swing.id.label = ID
+thing-type.config.mynice.swing.id.description = ID of the gate on the TP4 bus connected to the bridge
+
+# channel types
+
+channel-type.mynice.command.label = Command
+channel-type.mynice.command.description = Send a given command to the gate
+channel-type.mynice.command.state.option.stop = Stop
+channel-type.mynice.command.state.option.open = Open
+channel-type.mynice.command.state.option.close = Close
+channel-type.mynice.doorstatus.label = Gate Status
+channel-type.mynice.doorstatus.description = Position of the gate or state if moving
+channel-type.mynice.doorstatus.state.option.open = Open
+channel-type.mynice.doorstatus.state.option.closed = Closed
+channel-type.mynice.doorstatus.state.option.opening = Opening
+channel-type.mynice.doorstatus.state.option.closing = Closing
+channel-type.mynice.doorstatus.state.option.stopped = Stopped
+channel-type.mynice.moving.label = Moving
+channel-type.mynice.moving.description = Indicates if the device is currently operating a command
+channel-type.mynice.obstruct.label = Obstruction
+channel-type.mynice.obstruct.description = Something prevented normal operation of the gate by crossing the infra-red barrier
+channel-type.mynice.t4command.label = T4 Command
+channel-type.mynice.t4command.description = Send a T4 Command to the gate
+channel-type.mynice.t4command.state.option.MDAx = Step by Step
+channel-type.mynice.t4command.state.option.MDAy = Stop (as remote control)
+channel-type.mynice.t4command.state.option.MDAz = Open (as remote control)
+channel-type.mynice.t4command.state.option.MDA0 = Close (as remote control)
+channel-type.mynice.t4command.state.option.MDA1 = Partial opening 1
+channel-type.mynice.t4command.state.option.MDA2 = Partial opening 2
+channel-type.mynice.t4command.state.option.MDA3 = Partial opening 3
+channel-type.mynice.t4command.state.option.MDBi = Apartment Step by Step
+channel-type.mynice.t4command.state.option.MDBj = Step by Step high priority
+channel-type.mynice.t4command.state.option.MDBk = Open and block
+channel-type.mynice.t4command.state.option.MDBl = Close and block
+channel-type.mynice.t4command.state.option.MDBm = Block
+channel-type.mynice.t4command.state.option.MDEw = Release
+channel-type.mynice.t4command.state.option.MDEx = Courtesy light timer on
+channel-type.mynice.t4command.state.option.MDEy = Courtesy light on-off
+channel-type.mynice.t4command.state.option.MDEz = Step by Step master door
+channel-type.mynice.t4command.state.option.MDE0 = Open master door
+channel-type.mynice.t4command.state.option.MDE1 = Close master door
+channel-type.mynice.t4command.state.option.MDE2 = Step by Step slave door
+channel-type.mynice.t4command.state.option.MDE3 = Open slave door
+channel-type.mynice.t4command.state.option.MDE4 = Close slave door
+channel-type.mynice.t4command.state.option.MDE5 = Release and Open
+channel-type.mynice.t4command.state.option.MDFh = Release and Close
+
+# error messages
+
+conf-error-no-username = Please define a username for this thing
+conf-pending-validation = Please validate the user on the MyNice application
+error-handshake-limit = Maximum handshake attempts reached
diff --git a/bundles/org.openhab.binding.mynice/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.mynice/src/main/resources/OH-INF/thing/thing-types.xml
new file mode 100644 (file)
index 0000000..4809d4f
--- /dev/null
@@ -0,0 +1,166 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="mynice"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
+       xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
+
+       <bridge-type id="it4wifi">
+               <label>IT4Wifi</label>
+               <description>This thing connects to your IT4Wifi module</description>
+
+               <representation-property>macAddress</representation-property>
+
+               <config-description>
+                       <parameter name="username" type="text">
+                               <label>Username</label>
+                               <description>User defined on the IT4Wifi for openHAB</description>
+                       </parameter>
+                       <parameter name="hostname" type="text" required="true">
+                               <context>network-address</context>
+                               <label>Hostname</label>
+                               <description>Hostname or IP address of the IT4Wifi</description>
+                       </parameter>
+                       <parameter name="macAddress" type="text" pattern="([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}(\s*#.*)*"
+                               required="true">
+                               <label>MAC Address</label>
+                               <description>The MAC address of the IT4Wifi</description>
+                       </parameter>
+                       <parameter name="password" type="text">
+                               <context>password</context>
+                               <label>Pairing Key</label>
+                               <description>Pairing Key needed to access the device, provided by the bridge itself</description>
+                       </parameter>
+               </config-description>
+       </bridge-type>
+
+       <thing-type id="swing">
+               <supported-bridge-type-refs>
+                       <bridge-type-ref id="it4wifi"/>
+               </supported-bridge-type-refs>
+
+               <label>Swing Gate</label>
+               <description>A dual swing gate</description>
+
+               <channels>
+                       <channel id="status" typeId="doorstatus"/>
+                       <channel id="obstruct" typeId="obstruct"/>
+                       <channel id="moving" typeId="moving"/>
+                       <channel id="command" typeId="command"/>
+                       <channel id="t4command" typeId="t4command"/>
+               </channels>
+
+               <representation-property>id</representation-property>
+
+               <config-description>
+                       <parameter name="id" type="text" required="true">
+                               <label>ID</label>
+                               <description>ID of the gate on the TP4 bus connected to the bridge</description>
+                       </parameter>
+               </config-description>
+       </thing-type>
+
+       <thing-type id="sliding">
+               <supported-bridge-type-refs>
+                       <bridge-type-ref id="it4wifi"/>
+               </supported-bridge-type-refs>
+
+               <label>Sliding Gate</label>
+               <description>A sliding gate</description>
+
+               <channels>
+                       <channel id="status" typeId="doorstatus"/>
+                       <channel id="obstruct" typeId="obstruct"/>
+                       <channel id="moving" typeId="moving"/>
+                       <channel id="command" typeId="command"/>
+                       <channel id="t4command" typeId="t4command"/>
+               </channels>
+
+               <representation-property>id</representation-property>
+
+               <config-description>
+                       <parameter name="id" type="text" required="true">
+                               <label>ID</label>
+                               <description>ID of the gate on the TP4 bus connected to the bridge.</description>
+                       </parameter>
+               </config-description>
+       </thing-type>
+
+
+       <channel-type id="doorstatus">
+               <item-type>String</item-type>
+               <label>Gate Status</label>
+               <description>Position of the gate or state if moving</description>
+               <state readOnly="true">
+                       <options>
+                               <option value="open">Open</option>
+                               <option value="closed">Closed</option>
+                               <option value="opening">Opening</option>
+                               <option value="closing">Closing</option>
+                               <option value="stopped">Stopped</option>
+                       </options>
+               </state>
+       </channel-type>
+
+       <channel-type id="moving">
+               <item-type>Switch</item-type>
+               <label>Moving</label>
+               <description>Indicates if the device is currently operating a command</description>
+               <state readOnly="true"/>
+       </channel-type>
+
+       <channel-type id="obstruct">
+               <item-type>Switch</item-type>
+               <label>Obstruction</label>
+               <description>Something prevented normal operation of the gate by crossing the infra-red barrier</description>
+               <state readOnly="true"/>
+       </channel-type>
+
+       <channel-type id="command">
+               <item-type>String</item-type>
+               <label>Command</label>
+               <description>Send a given command to the gate</description>
+               <state readOnly="false">
+                       <options>
+                               <option value="stop">Stop</option>
+                               <option value="open">Open</option>
+                               <option value="close">Close</option>
+                       </options>
+               </state>
+               <autoUpdatePolicy>veto</autoUpdatePolicy>
+       </channel-type>
+
+       <channel-type id="t4command">
+               <item-type>String</item-type>
+               <label>T4 Command</label>
+               <description>Send a T4 Command to the gate</description>
+               <state readOnly="false">
+                       <options>
+                               <option value="MDAx">Step by Step</option>
+                               <option value="MDAy">Stop (as remote control)</option>
+                               <option value="MDAz">Open (as remote control)</option>
+                               <option value="MDA0">Close (as remote control)</option>
+                               <option value="MDA1">Partial opening 1</option>
+                               <option value="MDA2">Partial opening 2</option>
+                               <option value="MDA3">Partial opening 3</option>
+                               <option value="MDBi">Apartment Step by Step</option>
+                               <option value="MDBj">Step by Step high priority</option>
+                               <option value="MDBk">Open and block</option>
+                               <option value="MDBl">Close and block</option>
+                               <option value="MDBm">Block</option>
+                               <option value="MDEw">Release</option>
+                               <option value="MDEx">Courtesy light timer on</option>
+                               <option value="MDEy">Courtesy light on-off</option>
+                               <option value="MDEz">Step by Step master door</option>
+                               <option value="MDE0">Open master door</option>
+                               <option value="MDE1">Close master door</option>
+                               <option value="MDE2">Step by Step slave door</option>
+                               <option value="MDE3">Open slave door</option>
+                               <option value="MDE4">Close slave door</option>
+                               <option value="MDE5">Release and Open</option>
+                               <option value="MDFh">Release and Close</option>
+                       </options>
+               </state>
+               <autoUpdatePolicy>veto</autoUpdatePolicy>
+       </channel-type>
+
+</thing:thing-descriptions>
index e22d555988c616ee823a616167cb7e4bbfbda7a7..23bf367cd0e9eae859aebc7be7d6e2f657f7e4e3 100644 (file)
     <module>org.openhab.binding.mqtt.homie</module>
     <module>org.openhab.binding.mybmw</module>
     <module>org.openhab.binding.mycroft</module>
+    <module>org.openhab.binding.mynice</module>
     <module>org.openhab.binding.myq</module>
     <module>org.openhab.binding.mystrom</module>
     <module>org.openhab.binding.nanoleaf</module>