]> git.basschouten.com Git - openhab-addons.git/commitdiff
[dominoswiss] Initial contribution (#11585)
authorFrieso Aeschbacher <frieso.aeschbacher@gmail.com>
Sat, 11 Dec 2021 12:05:41 +0000 (13:05 +0100)
committerGitHub <noreply@github.com>
Sat, 11 Dec 2021 12:05:41 +0000 (13:05 +0100)
* Added Dominoswiss to CODEOWNERS and POMs

Signed-off-by: Frieso Aeschbacher <frieso.aeschbacher@gmail.com>
* Intitial contribution of Dominoswiss Binding

Signed-off-by: Frieso Aeschbacher <frieso.aeschbacher@gmail.com>
* Typo in pom.xml

Signed-off-by: Frieso Aeschbacher <frieso.aeschbacher@gmail.com>
* Fixed inputs from fwolter

Signed-off-by: Frieso Aeschbacher <frieso.aeschbacher@gmail.com>
* Fixed inputs from fwolter

Signed-off-by: Frieso Aeschbacher <frieso.aeschbacher@gmail.com>
* Fixed localWriter Issue

Signed-off-by: Frieso Aeschbacher <frieso.aeschbacher@gmail.com>
* Update bom/openhab-addons/pom.xml

Signed-off-by: Fabian Wolter <github@fabian-wolter.de>
Co-authored-by: Fabian Wolter <github@fabian-wolter.de>
16 files changed:
CODEOWNERS
bom/openhab-addons/pom.xml
bundles/org.openhab.binding.dominoswiss/NOTICE [new file with mode: 0644]
bundles/org.openhab.binding.dominoswiss/README.md [new file with mode: 0644]
bundles/org.openhab.binding.dominoswiss/pom.xml [new file with mode: 0644]
bundles/org.openhab.binding.dominoswiss/src/main/feature/feature.xml [new file with mode: 0644]
bundles/org.openhab.binding.dominoswiss/src/main/java/org/openhab/binding/dominoswiss/internal/BlindConfig.java [new file with mode: 0644]
bundles/org.openhab.binding.dominoswiss/src/main/java/org/openhab/binding/dominoswiss/internal/BlindHandler.java [new file with mode: 0644]
bundles/org.openhab.binding.dominoswiss/src/main/java/org/openhab/binding/dominoswiss/internal/DominoswissBindingConstants.java [new file with mode: 0644]
bundles/org.openhab.binding.dominoswiss/src/main/java/org/openhab/binding/dominoswiss/internal/DominoswissConfiguration.java [new file with mode: 0644]
bundles/org.openhab.binding.dominoswiss/src/main/java/org/openhab/binding/dominoswiss/internal/DominoswissHandlerFactory.java [new file with mode: 0644]
bundles/org.openhab.binding.dominoswiss/src/main/java/org/openhab/binding/dominoswiss/internal/EGateHandler.java [new file with mode: 0644]
bundles/org.openhab.binding.dominoswiss/src/main/resources/OH-INF/binding/binding.xml [new file with mode: 0644]
bundles/org.openhab.binding.dominoswiss/src/main/resources/OH-INF/i18n/dominoswiss_de_DE.properties [new file with mode: 0644]
bundles/org.openhab.binding.dominoswiss/src/main/resources/OH-INF/thing/thing-types.xml [new file with mode: 0644]
bundles/pom.xml

index 0c2d5cdee37d2b302b7e241be3f27157abbc4302..c09e2b9f1e3beebde8953ddb18e40317fd06cc7a 100644 (file)
@@ -70,6 +70,7 @@
 /bundles/org.openhab.binding.digitalstrom/ @MichaelOchel @msiegele
 /bundles/org.openhab.binding.dlinksmarthome/ @MikeJMajor
 /bundles/org.openhab.binding.dmx/ @openhab/add-ons-maintainers
+/bundles/org.openhab.binding.dominoswiss/ @Friesoch
 /bundles/org.openhab.binding.doorbird/ @mhilbush
 /bundles/org.openhab.binding.draytonwiser/ @andrew-schofield
 /bundles/org.openhab.binding.dscalarm/ @RSStephens
index ef92dd10895be9d6cf70d9289658e078e7e188e6..b443a50b6af3f30e4165d70f0e0e6f46e8e16f68 100644 (file)
       <artifactId>org.openhab.binding.dmx</artifactId>
       <version>${project.version}</version>
     </dependency>
+    <dependency>
+      <groupId>org.openhab.addons.bundles</groupId>
+      <artifactId>org.openhab.binding.dominoswiss</artifactId>
+      <version>${project.version}</version>
+    </dependency>
     <dependency>
       <groupId>org.openhab.addons.bundles</groupId>
       <artifactId>org.openhab.binding.doorbird</artifactId>
diff --git a/bundles/org.openhab.binding.dominoswiss/NOTICE b/bundles/org.openhab.binding.dominoswiss/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.dominoswiss/README.md b/bundles/org.openhab.binding.dominoswiss/README.md
new file mode 100644 (file)
index 0000000..fb2363d
--- /dev/null
@@ -0,0 +1,100 @@
+# Dominoswiss Binding
+
+This binding allows the control of rollershutters, using an eGate as gateway and Dominoswiss radio receivers. 
+The eGate-gateway is connected via ethernet to openHAB and sends its commands via radio to all rollershutters. 
+See https://www.brelag.com/ for more information.
+
+
+## Supported Things
+
+eGate: Dominoswiss eGate Server. The eGate is the gateway which sends the commands to the connected rollershutters. The bridge-type ID is egate.
+Blind: represents one rollershutter, that can be controlled via eGate. The thing-type ID is blind.
+
+
+## Discovery
+
+Unfortunately no automatic discovery is possible due to protocol restrictions. 
+Every rollershutter must be known by eGate and can be called by it's number of storage-place on eGate gateway.
+
+
+
+## Thing Configuration
+
+The bridge "eGate" has one channel "getconfig" which returns the config of this bridge. 
+The eGate is configured with both an `ipAddress` and a port.
+
+|Property|Default|Required|Description|
+|--------|-------|--------|-----------|
+|ipAddress|none|Yes|The IP or host name of the Dominoswiss EGate Serve|
+|port|1318|Yes|Port interface of the Dominoswiss EGate Server|
+
+```
+Bridge dominoswiss:egate:myeGate "My eGate Server" @ "Home" [ ipAddres="localhost", port=5700 ]
+```
+
+
+The thing blind represents one blind on the eGate. Each blind is represented by an id set on your eGate.
+  
+``` 
+Thing blind officeBlind "Office" @ "1stFloor" [ id="1"]
+```
+
+The blind-Thing has the following channels:
+
+|Channel Type ID|Item Type|Description|
+|---------------|---------|-----------|
+|pulseUp|Rollershutter|sends one pulse up to this blind.|
+|pulseDown|Rollershutter|sends one pulse down to this blind|
+|continuousUp|Rollershutter|sends a continuous up to this blind. The blind will automatically stop as it is fully up.|
+|continuousDown|Rollershutter|send a continous down to this blind. The blind will automatically stop as it is fully down.|
+|stop|Rollershutter|stop the action of the blind. The command will be imadiatly sent to the blind.|
+|shutter|Rollershutter|this is used to bind the channel to the shutter item. There are no further rules needed this channel will handel the up/down/stop commands. See example for usage.|
+|tilt|Rollershutter|same as shutter, this will handel all up/down/stop - tilt commands to be used with the shutter-item.| 
+|tiltUp|Rollershutter|sends 3 pulse-up commands to this blind to tilt the blind. If your blind needs more than 3 pulse-up, create a rule yourself with three pluse-up commands. Between each pulse-up you should wait 150ms to let the command be processed. 
+|tiltDown|Rollershutter|sends 3 pulse-down commands to this blind to tilt the blind. If your blind needs more than 3 pulse-down, create a rule yourself with three pluse-down commands. Between each pulse-down you should wait 150ms to let the command be processed. |
+
+## Full Example
+
+Sample things file:
+
+``` 
+Bridge dominoswiss:egate:myeGate "My eGate Server" @ "Home" [ ipAddres="localhost", port="5500" ]
+{
+    Thing blind officeBlind "Office" @ "1stFloor" [ id="1"]
+    Thing blind diningRoomBlind "Dining Room" @ "EG" [id="2"]
+    Thing blind kitchenBlind "Kitchen" @ "EG" [id="12"]
+}
+```
+
+Sample items file:
+
+```
+Rollershutter OfficeBlindShutter "Office blind" <rollershutter>  (g_blinds) { channel="dominoswiss:blind:myeGate:officeBlind:shutter"}
+
+Rollershutter OfficeBlindShutterTilt "Tilt Office" <rollershutter>  (g_blinds_tilt) { channel="dominoswiss:blind:meGgate:bueroBlind:tilt"}
+
+```
+
+Sample sitemap file
+
+```
+Switch  item=OfficeBlindShutter
+Switch  item=OfficeBlindShutterTilt
+```
+
+Sample rule file
+
+This example moves the blind of the office up as the sun passed 110 azimuth (so the sun is no longer shining directly into the office).
+
+```
+rule "OneSide up"
+when 
+    Item Azimuth changed 
+then 
+    val azimuth = Math::round((Azimuth.state as DecimalType).intValue)
+    if (azimuth == 110)
+    {
+        OfficeBlindShutter.sendCommand(UP)
+    }
+end
+```
diff --git a/bundles/org.openhab.binding.dominoswiss/pom.xml b/bundles/org.openhab.binding.dominoswiss/pom.xml
new file mode 100644 (file)
index 0000000..b5c5b3c
--- /dev/null
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://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>3.2.0-SNAPSHOT</version>
+  </parent>
+  <artifactId>org.openhab.binding.dominoswiss</artifactId>
+
+  <name>openHAB Add-ons :: Bundles :: Dominoswiss Binding</name>
+
+</project>
diff --git a/bundles/org.openhab.binding.dominoswiss/src/main/feature/feature.xml b/bundles/org.openhab.binding.dominoswiss/src/main/feature/feature.xml
new file mode 100644 (file)
index 0000000..61f2c28
--- /dev/null
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<features name="org.openhab.binding.dominoswiss-${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-dominoswiss" description="dominoswiss Binding" version="${project.version}">
+               <feature>openhab-runtime-base</feature>
+               <bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.dominoswiss/${project.version}</bundle>
+       </feature>
+</features>
diff --git a/bundles/org.openhab.binding.dominoswiss/src/main/java/org/openhab/binding/dominoswiss/internal/BlindConfig.java b/bundles/org.openhab.binding.dominoswiss/src/main/java/org/openhab/binding/dominoswiss/internal/BlindConfig.java
new file mode 100644 (file)
index 0000000..0deb53a
--- /dev/null
@@ -0,0 +1,29 @@
+/**
+ * Copyright (c) 2010-2021 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.dominoswiss.internal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * Configuration of a blind.
+ *
+ * @author Frieso Aeschbacher - Initial contribution
+ */
+@NonNullByDefault
+public class BlindConfig {
+
+    /*
+     * The ID of that blind
+     */
+    public String id = "";
+}
diff --git a/bundles/org.openhab.binding.dominoswiss/src/main/java/org/openhab/binding/dominoswiss/internal/BlindHandler.java b/bundles/org.openhab.binding.dominoswiss/src/main/java/org/openhab/binding/dominoswiss/internal/BlindHandler.java
new file mode 100644 (file)
index 0000000..a2a1a14
--- /dev/null
@@ -0,0 +1,186 @@
+/**
+ * Copyright (c) 2010-2021 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.dominoswiss.internal;
+
+import static org.openhab.binding.dominoswiss.internal.DominoswissBindingConstants.*;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+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.ThingStatusDetail;
+import org.openhab.core.thing.binding.BaseThingHandler;
+import org.openhab.core.types.Command;
+import org.openhab.core.types.RefreshType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link BlindHandler} is responsible for handling commands, which are
+ * sent to one of the channels.The class defines common constants, which are
+ * used across the whole binding
+ *
+ * @author Frieso Aeschbacher - Initial contribution
+ */
+@NonNullByDefault
+public class BlindHandler extends BaseThingHandler {
+
+    private Logger logger = LoggerFactory.getLogger(BlindHandler.class);
+
+    private @Nullable EGateHandler dominoswissHandler;
+
+    private String id = "";
+
+    public BlindHandler(Thing thing) {
+        super(thing);
+    }
+
+    @Override
+    public void handleCommand(ChannelUID channelUID, Command command) {
+        logger.debug("Blind got command: {} and ChannelUID: {} ", command.toFullString(),
+                channelUID.getIdWithoutGroup());
+        Bridge bridge = getBridge();
+        EGateHandler localDominoswissHandler = dominoswissHandler;
+        if (bridge != null) {
+            localDominoswissHandler = (EGateHandler) bridge.getHandler();
+        }
+        if (localDominoswissHandler == null) {
+            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED, "EGate not available");
+            logger.debug("Blind thing {} has no server configured, ignoring command: {}", getThing().getUID(), command);
+            return;
+        }
+
+        // Some of the code below is not designed to handle REFRESH
+        if (command == RefreshType.REFRESH) {
+            return;
+        }
+        switch (channelUID.getIdWithoutGroup()) {
+            case CHANNEL_PULSEUP:
+                if (command instanceof Number) {
+                    localDominoswissHandler.pulseUp(id);
+                }
+                break;
+            case CHANNEL_PULSEDOWN:
+                if (command instanceof Number) {
+                    localDominoswissHandler.pulseDown(id);
+                }
+                break;
+            case CHANNEL_CONTINOUSUP:
+                if (command instanceof Number) {
+                    localDominoswissHandler.continuousUp(id);
+                }
+                break;
+            case CHANNEL_CONTINOUSDOWN:
+                if (command instanceof Number) {
+                    localDominoswissHandler.continuousDown(id);
+                }
+                break;
+            case CHANNEL_STOP:
+                if (command instanceof Number) {
+                    localDominoswissHandler.stop(id);
+                }
+                break;
+            case UP:
+                if (command instanceof Number) {
+                    localDominoswissHandler.continuousUp(id);
+                }
+                break;
+            case DOWN:
+                if (command instanceof Number) {
+                    localDominoswissHandler.continuousDown(id);
+                }
+                break;
+            case SHUTTER:
+                if (command.toFullString() == DOWN) {
+                    localDominoswissHandler.continuousDown(id);
+                } else if (command.toFullString() == UP) {
+                    localDominoswissHandler.continuousUp(id);
+                } else if (command.toFullString() == CHANNEL_STOP) {
+                    localDominoswissHandler.stop(id);
+                } else {
+                    logger.debug("Blind got command but nothing executed: {}  and ChannelUID: {}",
+                            command.toFullString(), channelUID.getIdWithoutGroup());
+                }
+
+            case TILTDOWN:
+                if (command instanceof Number) {
+                    try {
+                        localDominoswissHandler.tiltDown(id);
+                    } catch (InterruptedException e) {
+                        logger.debug("EGate tiltDown error: {} ", e.toString());
+                    }
+                }
+                break;
+
+            case TILTUP:
+                if (command instanceof Number) {
+                    try {
+                        localDominoswissHandler.tiltUp(id);
+                    } catch (InterruptedException e) {
+                        logger.debug("EGate tiltUP error: {} ", e.toString());
+                    }
+                }
+                break;
+
+            case SHUTTERTILT:
+                if (command.toFullString() == UP) {
+                    localDominoswissHandler.pulseUp(id);
+                } else if (command.toFullString() == DOWN) {
+                    localDominoswissHandler.pulseDown(id);
+                } else if (command.toFullString() == CHANNEL_STOP) {
+                    localDominoswissHandler.stop(id);
+                } else {
+                    logger.debug("Blind got command but nothing executed: {}  and ChannelUID: {}",
+                            command.toFullString(), channelUID.getIdWithoutGroup());
+                }
+
+            default:
+                break;
+        }
+    }
+
+    @Override
+    public void initialize() {
+        this.id = getConfig().as(BlindConfig.class).id;
+        Bridge bridge = getBridge();
+        if (bridge != null) {
+            dominoswissHandler = (EGateHandler) bridge.getHandler();
+            EGateHandler localDominoswissHandler = dominoswissHandler;
+            if (localDominoswissHandler != null) {
+                localDominoswissHandler.registerBlind(this.id, getThing().getUID());
+                try {
+                    ThingStatus bridgeStatus = bridge.getStatus();
+                    if (bridgeStatus == ThingStatus.ONLINE && getThing().getStatus() != ThingStatus.ONLINE) {
+                        updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE);
+                        localDominoswissHandler = (EGateHandler) bridge.getHandler();
+                    } else if (bridgeStatus == ThingStatus.OFFLINE) {
+                        updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
+                    }
+                } catch (Exception e) {
+                    logger.debug("Could not update ThingStatus ", e);
+                    updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, e.toString());
+
+                }
+            }
+        }
+    }
+
+    /*
+     * Gets the ID of this Blind
+     */
+    public String getID() {
+        return this.id;
+    }
+}
diff --git a/bundles/org.openhab.binding.dominoswiss/src/main/java/org/openhab/binding/dominoswiss/internal/DominoswissBindingConstants.java b/bundles/org.openhab.binding.dominoswiss/src/main/java/org/openhab/binding/dominoswiss/internal/DominoswissBindingConstants.java
new file mode 100644 (file)
index 0000000..4c3d513
--- /dev/null
@@ -0,0 +1,49 @@
+/**
+ * Copyright (c) 2010-2021 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.dominoswiss.internal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.core.thing.ThingTypeUID;
+
+/**
+ * The {@link DominoswissBindingConstants} class defines common constants, which are
+ * used across the whole binding.
+ *
+ * @author Frieso Aeschbacher - Initial contribution
+ */
+@NonNullByDefault
+public class DominoswissBindingConstants {
+
+    private static final String BINDING_ID = "dominoswiss";
+
+    // List of all Thing Type UIDs
+    public static final ThingTypeUID DOMINOSWISSBLINDS_THING_TYPE = new ThingTypeUID(BINDING_ID, "blind");
+    public static final ThingTypeUID DOMINOSWISSEGATE_THING_TYPE = new ThingTypeUID(BINDING_ID, "egate");
+
+    // List of all Channel ids
+    public static final String CHANNEL_PULSEUP = "pulseUp";
+    public static final String CHANNEL_PULSEDOWN = "pulseDown";
+    public static final String CHANNEL_CONTINOUSUP = "continousUp";
+    public static final String CHANNEL_CONTINOUSDOWN = "continousDown";
+    public static final String CHANNEL_STOP = "STOP";
+    public static final String UP = "UP";
+    public static final String DOWN = "DOWN";
+    public static final String SHUTTER = "shutter";
+    public static final String TILTUP = "tiltUp";
+    public static final String TILTDOWN = "tiltDown";
+    public static final String SHUTTERTILT = "shutterTilt";
+
+    public static final String GETCONFIG = "getConfig";
+
+    public static final String CR = "\r";
+}
diff --git a/bundles/org.openhab.binding.dominoswiss/src/main/java/org/openhab/binding/dominoswiss/internal/DominoswissConfiguration.java b/bundles/org.openhab.binding.dominoswiss/src/main/java/org/openhab/binding/dominoswiss/internal/DominoswissConfiguration.java
new file mode 100644 (file)
index 0000000..6ab19ed
--- /dev/null
@@ -0,0 +1,38 @@
+/**
+ * Copyright (c) 2010-2021 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.dominoswiss.internal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * The {@link DominoswissConfiguration} class contains fields mapping thing configuration parameters.
+ *
+ * @author Frieso Aeschbacher - Initial contribution
+ */
+@NonNullByDefault
+public class DominoswissConfiguration {
+
+    /**
+     * Server ip address
+     */
+    public String ipAddress = "localhost";
+    /**
+     * Server web port for REST calls
+     */
+    public int port = 1318;
+
+    /**
+     * Language for TTS has to be fix to EN as only English commands are allowed
+     */
+    public final String language = "EN";
+}
diff --git a/bundles/org.openhab.binding.dominoswiss/src/main/java/org/openhab/binding/dominoswiss/internal/DominoswissHandlerFactory.java b/bundles/org.openhab.binding.dominoswiss/src/main/java/org/openhab/binding/dominoswiss/internal/DominoswissHandlerFactory.java
new file mode 100644 (file)
index 0000000..5b38d19
--- /dev/null
@@ -0,0 +1,60 @@
+/**
+ * Copyright (c) 2010-2021 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.dominoswiss.internal;
+
+import static org.openhab.binding.dominoswiss.internal.DominoswissBindingConstants.*;
+
+import java.util.Set;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+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 DominoswissHandlerFactory} is responsible for creating things and thing
+ * handlers.
+ *
+ * @author Frieso Aeschbacher - Initial contribution
+ */
+@NonNullByDefault
+@Component(configurationPid = "binding.dominoswiss", service = ThingHandlerFactory.class)
+public class DominoswissHandlerFactory extends BaseThingHandlerFactory {
+
+    private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(DOMINOSWISSEGATE_THING_TYPE,
+            DOMINOSWISSBLINDS_THING_TYPE);
+
+    @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 (thingTypeUID.equals(DOMINOSWISSEGATE_THING_TYPE)) {
+            return new EGateHandler((Bridge) thing);
+        }
+
+        if (thingTypeUID.equals(DOMINOSWISSBLINDS_THING_TYPE)) {
+            return new BlindHandler(thing);
+        }
+        return null;
+    }
+}
diff --git a/bundles/org.openhab.binding.dominoswiss/src/main/java/org/openhab/binding/dominoswiss/internal/EGateHandler.java b/bundles/org.openhab.binding.dominoswiss/src/main/java/org/openhab/binding/dominoswiss/internal/EGateHandler.java
new file mode 100644 (file)
index 0000000..7cbc66b
--- /dev/null
@@ -0,0 +1,316 @@
+/**
+ * Copyright (c) 2010-2021 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.dominoswiss.internal;
+
+import static org.openhab.binding.dominoswiss.internal.DominoswissBindingConstants.*;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Future;
+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.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.ThingStatusDetail;
+import org.openhab.core.thing.ThingUID;
+import org.openhab.core.thing.binding.BaseBridgeHandler;
+import org.openhab.core.types.Command;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link EgateHandler} is responsible for handling commands, which are
+ * sent to one of the channels.
+ *
+ * @author Frieso Aeschbacher - Initial contribution
+ */
+@NonNullByDefault
+public class EGateHandler extends BaseBridgeHandler {
+
+    private final Logger logger = LoggerFactory.getLogger(EGateHandler.class);
+    private @Nullable Socket egateSocket;
+
+    private int port;
+    private @Nullable String host;
+    private static final int SOCKET_TIMEOUT_SEC = 250;
+    private final Object lock = new Object();
+    private @Nullable BufferedWriter writer;
+    private @Nullable BufferedReader reader;
+    private @Nullable Future<?> refreshJob;
+    private Map<String, ThingUID> registeredBlinds;
+    private @Nullable ScheduledFuture<?> pollingJob;
+
+    public EGateHandler(Bridge thing) {
+        super(thing);
+        registeredBlinds = new HashMap<String, ThingUID>();
+    }
+
+    @Override
+    public void handleCommand(ChannelUID channelUID, Command command) {
+        if (channelUID.getId().equals(GETCONFIG)) {
+            sendCommand("EthernetGet;\r");
+        }
+    }
+
+    @Override
+    public void initialize() {
+        DominoswissConfiguration config;
+        config = this.getConfigAs(DominoswissConfiguration.class);
+        host = config.ipAddress;
+        port = config.port;
+
+        if (host != null && port > 0) {
+            // Create a socket to eGate
+            try (Socket localEgateSocket = new Socket(host, port)) {
+                writer = new BufferedWriter(new OutputStreamWriter(localEgateSocket.getOutputStream()));
+                egateSocket = localEgateSocket;
+            } catch (IOException e) {
+                logger.debug("IOException in initialize: {} host {} port {}", e.toString(), host, port);
+                updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.toString());
+                egateSocket = null;
+            }
+            pollingJob = scheduler.scheduleWithFixedDelay(this::pollingConfig, 0, 30, TimeUnit.SECONDS);
+        } else {
+            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
+                    "Cannot connect to dominoswiss eGate gateway. host IP address or port are not set.");
+        }
+    }
+
+    @Override
+    public void dispose() {
+        try {
+            Socket localEgateSocket = egateSocket;
+            if (localEgateSocket != null) {
+                localEgateSocket.close();
+            }
+            Future<?> localRefreshJob = refreshJob;
+            if (localRefreshJob != null) {
+                localRefreshJob.cancel(true);
+            }
+            BufferedReader localReader = reader;
+            if (localReader != null) {
+                localReader.close();
+            }
+
+            BufferedWriter localWriter = writer;
+            if (localWriter != null) {
+                localWriter.close();
+            }
+            ScheduledFuture<?> localPollingJob = pollingJob;
+            if (localPollingJob != null) {
+                localPollingJob.cancel(true);
+                localPollingJob = null;
+            }
+            logger.debug("EGate Handler connection closed, disposing");
+        } catch (IOException e) {
+            logger.debug("EGate Handler Error on dispose: {} ", e.toString());
+        }
+    }
+
+    public synchronized boolean isConnected() {
+        Socket localEGateSocket = egateSocket;
+        if (localEGateSocket == null) {
+            return false;
+        }
+
+        // NOTE: isConnected() returns true once a connection is made and will
+        // always return true even after the socket is closed
+        // http://stackoverflow.com/questions/10163358/
+        return localEGateSocket.isConnected() && !localEGateSocket.isClosed();
+    }
+
+    /**
+     * Possible Instructions are:
+     * FssTransmit 1 Kommandoabsetzung (Controller > eGate > Dominoswiss)
+     * FssReceive 2 Empfangenes Funkpaket (Dominoswiss > eGate > Controller)
+     *
+     * @throws InterruptedException
+     *
+     */
+
+    public void tiltUp(String id) throws InterruptedException {
+        for (int i = 0; i < 3; i++) {
+            pulseUp(id);
+            Thread.sleep(150); // sleep to not confuse the blinds
+
+        }
+    }
+
+    public void tiltDown(String id) throws InterruptedException {
+        for (int i = 0; i < 3; i++) {
+            pulseDown(id);
+            Thread.sleep(150);// sleep to not confuse the blinds
+        }
+    }
+
+    public void pulseUp(String id) {
+        sendCommand("Instruction=1;ID=" + id + ";Command=1;Priority=1;CheckNr=3415347;" + CR);
+    }
+
+    public void pulseDown(String id) {
+        sendCommand("Instruction=1;ID=" + id + ";Command=2;Priority=1;CheckNr=2764516;" + CR);
+    }
+
+    public void continuousUp(String id) {
+        sendCommand("Instruction=1;ID=" + id + ";Command=3;Priority=1;CheckNr=2867016;" + CR, 20000);
+    }
+
+    public void continuousDown(String id) {
+        sendCommand("Instruction=1;ID=" + id + ";Command=4;Priority=1;CheckNr=973898;" + CR, 20000);
+    }
+
+    public void stop(String id) {
+        sendCommand("Instruction=1;ID=" + id + ";Command=5;Priority=1;CheckNr=5408219;" + CR);
+    }
+
+    public void registerBlind(String id, ThingUID uid) {
+        logger.debug("Registring Blind id {} with thingUID {}", id, uid);
+        registeredBlinds.put(id, uid);
+    }
+
+    /**
+     * Send a command to the eGate Server.
+     */
+
+    private void sendCommand(String command) {
+        sendCommand(command, SOCKET_TIMEOUT_SEC);
+    }
+
+    private synchronized void sendCommand(String command, int timeout) {
+        logger.debug("EGate got command: {}", command);
+        Socket localEGateSocket = egateSocket;
+        BufferedWriter localWriter = writer;
+        if (localEGateSocket == null || localWriter == null) {
+            return;
+        }
+        if (!isConnected()) {
+            logger.debug("no connection to Dominoswiss eGate server when trying to send command, returning...");
+            return;
+        }
+
+        // Send plain string to eGate Server,
+        try {
+            localEGateSocket.setSoTimeout(SOCKET_TIMEOUT_SEC);
+            localWriter.write(command);
+            localWriter.flush();
+        } catch (IOException e) {
+            logger.debug("Error while sending command {} to Dominoswiss eGate Server {} ", command, e.toString());
+        }
+    }
+
+    private void pollingConfig() {
+        if (!isConnected()) {
+            Socket localEGateSocket = egateSocket;
+            BufferedWriter localWriter = writer;
+            if (localEGateSocket == null || localWriter == null) {
+                return;
+            }
+            synchronized (lock) {
+                try {
+                    localEGateSocket.connect(new InetSocketAddress(host, port));
+                    localEGateSocket.setSoTimeout(SOCKET_TIMEOUT_SEC);
+                    localWriter.write("SilenceModeSet;Value=0;" + CR);
+                    localWriter.flush();
+                } catch (IOException e) {
+                    logger.debug("IOException in pollingConfig: {} host {} port {}", e.toString(), host, port);
+                    try {
+                        localEGateSocket.close();
+                        egateSocket = null;
+                        updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.toString());
+                    } catch (IOException e1) {
+                        logger.debug("EGate Socket not closed {}", e1.toString());
+                    }
+                    egateSocket = null;
+                }
+                if (egateSocket != null) {
+                    updateStatus(ThingStatus.ONLINE);
+                    startAutomaticRefresh();
+                    logger.debug("EGate Handler started automatic refresh, status: {} ",
+                            getThing().getStatus().toString());
+                } else {
+                    updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
+                }
+            }
+        }
+    }
+
+    private void startAutomaticRefresh() {
+        Runnable runnable = () -> {
+            try {
+
+                Socket localSocket = egateSocket;
+                if (localSocket == null) {
+                    return;
+                }
+                BufferedReader localReader = reader;
+                if (localReader == null) {
+                    reader = new BufferedReader(new InputStreamReader(localSocket.getInputStream()));
+                }
+                if (localReader != null && localReader.ready()) {
+                    String input = localReader.readLine();
+                    logger.debug("Reader got from EGATE: {}", input);
+                    onData(input);
+                }
+            } catch (IOException e) {
+                updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
+                        "Error while reading command from Dominoswiss eGate Server " + e.toString());
+            }
+        };
+        refreshJob = scheduler.submit(runnable);
+    }
+
+    /**
+     * Finds and returns a child thing for a given UID of this bridge.
+     *
+     * @param uid uid of the child thing
+     * @return child thing with the given uid or null if thing was not found
+     */
+    public @Nullable Thing getThingByUID(ThingUID uid) {
+        Bridge bridge = getThing();
+
+        List<Thing> things = bridge.getThings();
+
+        for (Thing thing : things) {
+            if (thing.getUID().equals(uid)) {
+                return thing;
+            }
+        }
+
+        return null;
+    }
+
+    protected void onData(String input) {
+        // Instruction=2;ID=19;Command=1;Value=0;Priority=0;
+        Map<String, String> map = new HashMap<String, String>();
+        // split on ;
+        String[] parts = input.split(";");
+        if (parts.length >= 2) {
+            for (int i = 0; i < parts.length; i += 2) {
+                map.put(parts[i], parts[i + 1]);
+            }
+        }
+    }
+}
diff --git a/bundles/org.openhab.binding.dominoswiss/src/main/resources/OH-INF/binding/binding.xml b/bundles/org.openhab.binding.dominoswiss/src/main/resources/OH-INF/binding/binding.xml
new file mode 100644 (file)
index 0000000..0c9d588
--- /dev/null
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<binding:binding id="dominoswiss" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
+       xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
+
+       <name>Dominoswiss Binding</name>
+       <description>The Dominoswiss Binding interacts with the Dominoswiss eGate G1 Gateway to control blinds</description>
+</binding:binding>
diff --git a/bundles/org.openhab.binding.dominoswiss/src/main/resources/OH-INF/i18n/dominoswiss_de_DE.properties b/bundles/org.openhab.binding.dominoswiss/src/main/resources/OH-INF/i18n/dominoswiss_de_DE.properties
new file mode 100644 (file)
index 0000000..73b2a40
--- /dev/null
@@ -0,0 +1,3 @@
+# binding
+binding.dominoswiss.name = Dominoswiss
+binding.dominoswiss.description = Dominoswiss Binding zur Kontrolle der Jalousien  
diff --git a/bundles/org.openhab.binding.dominoswiss/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.dominoswiss/src/main/resources/OH-INF/thing/thing-types.xml
new file mode 100644 (file)
index 0000000..2b06be3
--- /dev/null
@@ -0,0 +1,114 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="dominoswiss"
+       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="egate">
+               <label>Dominoswiss EGate Server</label>
+               <description>This is a Dominoswiss EGate Server instance.</description>
+               <config-description>
+                       <parameter name="ipAddress" type="text" required="true">
+                               <label>IP or Host Name</label>
+                               <context>network-address</context>
+                               <description>The IP or host name of the Dominoswiss EGate Server (192.168.1.100, localhost)</description>
+                       </parameter>
+                       <parameter name="port" type="integer" required="true" min="1" max="65335">
+                               <label>Web Port</label>
+                               <description>Port interface of the Dominoswiss EGate Server, default 1318</description>
+                               <default>1318</default>
+                       </parameter>
+               </config-description>
+       </bridge-type>
+
+       <thing-type id="blind">
+               <supported-bridge-type-refs>
+                       <bridge-type-ref id="egate"/>
+               </supported-bridge-type-refs>
+               <label>Blind</label>
+               <description>Provides various control commands for Dominoswiss receivers</description>
+               <channels>
+                       <channel id="pulseUp" typeId="pulseUp"/>
+                       <channel id="pulseDown" typeId="pulseDown"/>
+                       <channel id="continuousUp" typeId="continuousUp"/>
+                       <channel id="continuousDown" typeId="continuousDown"/>
+                       <channel id="stop" typeId="stop"/>
+                       <channel id="shutter" typeId="shutter"/>
+                       <channel id="shutterTilt" typeId="shutterTilt"/>
+                       <channel id="tiltUp" typeId="tiltUp"/>
+                       <channel id="tiltDown" typeId="tiltDown"/>
+               </channels>
+               <properties>
+                       <property name="vendor">Dominoswiss</property>
+               </properties>
+               <config-description>
+                       <parameter name="id" type="text" required="true">
+                               <label>ID Address</label>
+                               <description>Blinds are identified by their ID address</description>
+                       </parameter>
+               </config-description>
+       </thing-type>
+
+       <channel-type id="pulseUp">
+               <item-type>Rollershutter</item-type>
+               <label>Pulse Up</label>
+               <description>Send one pulse up</description>
+               <state readOnly="true"/>
+       </channel-type>
+
+       <channel-type id="pulseDown">
+               <item-type>Rollershutter</item-type>
+               <label>Pulse Down</label>
+               <description>Send one pulse down</description>
+               <state readOnly="true"/>
+       </channel-type>
+
+       <channel-type id="continuousUp">
+               <item-type>Rollershutter</item-type>
+               <label>Continuous Up</label>
+               <description>Send continuous up command to blind</description>
+               <state readOnly="true"/>
+       </channel-type>
+
+       <channel-type id="continuousDown">
+               <item-type>Rollershutter</item-type>
+               <label>Continuous Down</label>
+               <description>Send continuous down command to blind</description>
+               <state readOnly="true"/>
+       </channel-type>
+
+       <channel-type id="stop">
+               <item-type>Rollershutter</item-type>
+               <label>Stop</label>
+               <description>Send stop impulse to stop the blinds</description>
+               <state readOnly="true"/>
+       </channel-type>
+
+       <channel-type id="shutter">
+               <item-type>Rollershutter</item-type>
+               <label>Shutter</label>
+               <description>Handle the commands up/down/stop </description>
+               <state readOnly="true"/>
+       </channel-type>
+
+       <channel-type id="shutterTilt">
+               <item-type>Rollershutter</item-type>
+               <label>Shutter Tilt Up Down</label>
+               <description>Handle the commands tiltUp/tiltDown/stop </description>
+               <state readOnly="true"/>
+       </channel-type>
+
+       <channel-type id="tiltUp">
+               <item-type>Rollershutter</item-type>
+               <label>Tilt Up</label>
+               <description>Handle the command for 3 tilts up </description>
+               <state readOnly="true"/>
+       </channel-type>
+
+       <channel-type id="tiltDown">
+               <item-type>Rollershutter</item-type>
+               <label>Tilt Down</label>
+               <description>Handle the command for 3 tilts down </description>
+               <state readOnly="true"/>
+       </channel-type>
+
+</thing:thing-descriptions>
index 82499a358d826b5ea436def2a48e87e961872b08..36f531048665ad217288f0fc2c9908ab09d4f06a 100644 (file)
     <module>org.openhab.binding.digitalstrom</module>
     <module>org.openhab.binding.dlinksmarthome</module>
     <module>org.openhab.binding.dmx</module>
+    <module>org.openhab.binding.dominoswiss</module>
     <module>org.openhab.binding.doorbird</module>
     <module>org.openhab.binding.draytonwiser</module>
     <module>org.openhab.binding.dscalarm</module>