added migrated 2.x add-ons
Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
32
bundles/org.openhab.binding.gce/.classpath
Normal file
32
bundles/org.openhab.binding.gce/.classpath
Normal file
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="src" output="target/classes" path="src/main/java">
|
||||
<attributes>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
|
||||
<attributes>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
<attribute name="test" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="output" path="target/classes"/>
|
||||
</classpath>
|
||||
23
bundles/org.openhab.binding.gce/.project
Normal file
23
bundles/org.openhab.binding.gce/.project
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>org.openhab.binding.gce</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.m2e.core.maven2Builder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
||||
13
bundles/org.openhab.binding.gce/NOTICE
Normal file
13
bundles/org.openhab.binding.gce/NOTICE
Normal file
@@ -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
|
||||
192
bundles/org.openhab.binding.gce/README.md
Normal file
192
bundles/org.openhab.binding.gce/README.md
Normal file
@@ -0,0 +1,192 @@
|
||||
# GCE Binding
|
||||
|
||||
This binding aims to handle various GCE Electronics equipments.
|
||||
IPX800 is a 8 relay webserver from gce-electronics with a lot of possibilities:
|
||||
|
||||
* 8 Digital Input
|
||||
* 8 Relay (250V / 10A / channel)
|
||||
* 4 Analog Input
|
||||
* 8 Counters
|
||||
* Ability to cascade up to 3 extensions for a total of 32 inputs / 32 relay
|
||||
|
||||
Each IPX800 connected to openHAB must be configured with the setting 'Send data on status changed' on the website in M2M > TCP client.
|
||||
|
||||
To make it simple, IPX800 is a simple device that drives output and retrieves input.
|
||||
On input we generally connect push buttons (for instance house switchs), on ouputs we can connect light bulbs for instance.
|
||||
|
||||
Features of the binding:
|
||||
|
||||
* Multi ipx support
|
||||
* Direct TCP connection
|
||||
* Auto reconnect
|
||||
* Simple clic/Long press
|
||||
* Pulse mode support
|
||||
|
||||
## Binding Configuration
|
||||
|
||||
There is no configuration at binding level.
|
||||
|
||||
|
||||
## Thing Configuration
|
||||
|
||||
The IPX800v3 (ID : 'ipx800v3') accepts the following configuration parameters :
|
||||
|
||||
| Property | Default | Required | Description |
|
||||
|---------------------|---------|----------|-----------------------------|
|
||||
| hostname | | Yes | IP address or hostname. |
|
||||
| portNumber | 9870 | No | TCP client connection port. |
|
||||
| pullInterval* | 5000 | No | Refresh interval (in ms) |
|
||||
|
||||
The binding will query periodically the 'globalstatus.xml' page of the IPX to get fresh informations.
|
||||
This is especially usefull for Analog inputs and Counter as modification of these values on PLC side does not trigger any M2M message.
|
||||
|
||||
The thing provides four groups of channels.
|
||||
|
||||
### Digital Inputs
|
||||
|
||||
This represents the inputs of the PLC. Each can be open or closed.
|
||||
They are usually commuted by physical devices like pushbuttons, magnets...
|
||||
|
||||
#### Digital Input Channels (contacts)
|
||||
|
||||
Each input will have these associated channels:
|
||||
|
||||
| Group | Channel Name | Item Type | R/W | Description |
|
||||
|----------|------------------------|-------------|-----|-----------------------------------------------------------------------------|
|
||||
| contact | `portnumber` | Contact | R | Status of the actual port (OPEN, CLOSED) |
|
||||
| contact | `portnumber`-duration | Number:Time | R | Updated when the port status changes to the duration of the previous state. |
|
||||
|
||||
Associated events:
|
||||
|
||||
| Channel Type ID | Options | Description | Conf Dependency |
|
||||
|--------------------|-------------------|--------------------------------------------------|-----------------|
|
||||
| `portnumber`-event | | Triggered on or after a port status change | |
|
||||
| | PRESSED | Triggered when state changes from OPEN to CLOSED | |
|
||||
| | RELEASED | Triggered when state changes from CLOSED to OPEN | |
|
||||
| | LONG_PRESS | Triggered when RELEASED after a long period | longPressTime |
|
||||
| | SHORT_PRESS | Triggered when RELEASED before a long period | longPressTime |
|
||||
| | PULSE | Triggered during CLOSED state | pulsePeriod |
|
||||
|
||||
#### Configuration
|
||||
|
||||
| Property | Default | Unit | Description |
|
||||
|-----------------|---------|------|---------------------------------------------------------------------------------|
|
||||
| debouncePeriod | 0(*) | ms | Debounce time (ignores flappling within this time). No debounce is done if '0'. |
|
||||
| longPressTime | 0(*) | ms | Delay before triggering long press event. Ignored if '0'. |
|
||||
| pulsePeriod | 0(*) | ms | Period of pulse event triggering while the entry is closed. Ignored if '0'. |
|
||||
| pulseTimeout | 0(*) | ms | Period of time after pulsing will be stopped. None if '0'. |
|
||||
|
||||
* Values below 100ms should be avoided as the JVM could skip them and proceed in the same time slice.
|
||||
|
||||
|
||||
### Digital Outputs Channels (relays)
|
||||
|
||||
Each output will have these associated channels:
|
||||
|
||||
| Group | Channel Name | Item Type | R/W | Description |
|
||||
|----------|------------------------|-------------|-----|-----------------------------------------------------------------------------|
|
||||
| relay | `portnumber` | Switch | R/W | Status of the actual port (ON, OFF) |
|
||||
| relay | `portnumber`-duration | Number:Time | R | Updated when the port status changes to the duration of the previous state. |
|
||||
|
||||
#### Configuration
|
||||
|
||||
| Property | Default | Description |
|
||||
|-----------------|---------|--------------------------------------------------------------------------|
|
||||
| pulse | false | If set, the output will be in pulse mode, releasing it after the contact |
|
||||
|
||||
### Counters Channels
|
||||
|
||||
Each counter will have these associated channels:
|
||||
|
||||
| Group | Channel Name | Item Type | R/W | Description |
|
||||
|----------|--------------------------|-------------|-----|--------------------------------------------------------------------------------|
|
||||
| counter | `counternumber` | Number | R | Actual value of the counter |
|
||||
| counter | `counternumber`-duration | Number:Time | R | Updated when the counter status changes to the duration of the previous state. |
|
||||
|
||||
#### Configuration
|
||||
|
||||
This channel has no configuration setting.
|
||||
|
||||
### Analog Inputs Channels
|
||||
|
||||
Each analog port will have these associated channels:
|
||||
|
||||
| Group | Channel Name | Item Type | R/W | Description |
|
||||
|--------|-----------------------|--------------------------|-----|-----------------------------------------------------------------------------|
|
||||
| analog | `portnumber` | Number | R | Value of the port. |
|
||||
| analog | `portnumber`-duration | Number:Time | R | Updated when the port status changes to the duration of the previous state. |
|
||||
| analog | `portnumber`-voltage | Number:ElectricPotential | R | Electrical equivalency of the analogic value |
|
||||
|
||||
#### Configuration
|
||||
|
||||
| Property | Default | Description |
|
||||
|------------|---------|-------------------------------------------------------------------------------------|
|
||||
| hysteresis | 0 | If set, the channel will ignore status if change (+ or -) is less than hysteresis/2 |
|
||||
|
||||
|
||||
## Rule Actions
|
||||
|
||||
Multiple actions are supported by this binding. In classic rules these are accessible as shown in the example below:
|
||||
|
||||
Getting ipxActions variable in scripts
|
||||
|
||||
```
|
||||
val ipxActions = getActions("gce","gce:ipx800v3:43cc8d07")
|
||||
if(null === ipxActions) {
|
||||
logInfo("actions", "ipxActions not found, check thing ID")
|
||||
return
|
||||
} else {
|
||||
// do something with sunActions
|
||||
}
|
||||
```
|
||||
|
||||
### resetCounter(counterId)
|
||||
|
||||
Resets the value of the given counter to 0.
|
||||
|
||||
* `counterId` (Integer) - id of the counter.
|
||||
|
||||
|
||||
### reset(placeholder)
|
||||
|
||||
Restarts the PLC.
|
||||
|
||||
* `placeholder` (Integer) - This parameter is not used (can be null).
|
||||
|
||||
## Example
|
||||
|
||||
### Things
|
||||
|
||||
ipx800.things
|
||||
|
||||
```java
|
||||
|
||||
Thing gce:ipx800v3:ipx "IPX800" @ "diningroom" [hostname="192.168.0.144", portNumber=9870] {
|
||||
Channels:
|
||||
Type contact : contact#1 [ // Aimant Détection porte de garage ouverte
|
||||
debouncePeriod=2500,
|
||||
pulsePeriod=1000,
|
||||
pulseTimeout=60000
|
||||
]
|
||||
Type contact : contact#2 [ // Aimant Détection porte de garage fermée
|
||||
debouncePeriod=2500
|
||||
]
|
||||
Type relay : relay#8 [ // Actionneur porte de garage
|
||||
pulse=true
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
ipx800.items
|
||||
|
||||
```java
|
||||
|
||||
Group gIPXInputs "Inputs" <input>
|
||||
Contact input1 "Porte garage ouverte [%s]" <contact> (gIPXInputs) {channel="gce:ipx800v3:ipx:contact#1"}
|
||||
Contact input2 "Porte garage fermée [%s]" <contact> (gIPXInputs) {channel="gce:ipx800v3:ipx:contact#2"}
|
||||
|
||||
Group gIPXOutputs "Outputs" <output>
|
||||
Switch output3 "Chaudière" <furnace> (gIPXOutputs) {channel="gce:ipx800v3:ipx:relay#3"}
|
||||
Switch output4 "Lumière Porche" <light> (gIPXOutputs) {channel="gce:ipx800v3:ipx:relay#4"}
|
||||
|
||||
```
|
||||
17
bundles/org.openhab.binding.gce/pom.xml
Normal file
17
bundles/org.openhab.binding.gce/pom.xml
Normal file
@@ -0,0 +1,17 @@
|
||||
<?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/maven-v4_0_0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
|
||||
<version>3.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>org.openhab.binding.gce</artifactId>
|
||||
|
||||
<name>openHAB Add-ons :: Bundles :: GCE Binding</name>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<features name="org.openhab.binding.gce-${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-gce" description="GCE Binding" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.gce/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
||||
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.gce.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
/**
|
||||
* The {@link GCEBindingConstants} class defines common constants, which are used
|
||||
* across the whole binding.
|
||||
*
|
||||
* @author Gaël L'hopital - Initial Contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class GCEBindingConstants {
|
||||
|
||||
public static final String BINDING_ID = "gce";
|
||||
|
||||
// Bridge Type UID
|
||||
public static final ThingTypeUID IPXV3_THING_TYPE = new ThingTypeUID(BINDING_ID, "ipx800v3");
|
||||
|
||||
public static final String CHANNEL_LAST_STATE_DURATION = "duration";
|
||||
public static final String CHANNEL_VOLTAGE = "voltage";
|
||||
public static final String TRIGGER_CONTACT = "contact-trigger";
|
||||
|
||||
public static final String EVENT_PRESSED = "PRESSED";
|
||||
public static final String EVENT_RELEASED = "RELEASED";
|
||||
public static final String EVENT_SHORT_PRESS = "SHORT_PRESS";
|
||||
public static final String EVENT_LONG_PRESS = "LONG_PRESS";
|
||||
public static final String EVENT_PULSE = "PULSE";
|
||||
|
||||
// Adressable thing
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.gce.internal;
|
||||
|
||||
import static org.openhab.binding.gce.internal.GCEBindingConstants.IPXV3_THING_TYPE;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.gce.internal.handler.Ipx800v3Handler;
|
||||
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 GCEHandlerFactory} is responsible for creating things and
|
||||
* thing handlers.
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.gce")
|
||||
public class GCEHandlerFactory extends BaseThingHandlerFactory {
|
||||
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.singleton(IPXV3_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();
|
||||
return IPXV3_THING_TYPE.equals(thingTypeUID) ? new Ipx800v3Handler(thing) : null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.gce.internal.action;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The {@link IIpx800Actions} defines the interface for all thing actions supported by the binding.
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface IIpx800Actions {
|
||||
public void resetCounter(Integer counter);
|
||||
|
||||
public void reset(@Nullable Integer placeholder);
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.gce.internal.action;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.gce.internal.handler.Ipx800v3Handler;
|
||||
import org.openhab.core.automation.annotation.ActionInput;
|
||||
import org.openhab.core.automation.annotation.RuleAction;
|
||||
import org.openhab.core.thing.binding.ThingActions;
|
||||
import org.openhab.core.thing.binding.ThingActionsScope;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {Ipx800Actions } defines rule actions for the GCE binding.
|
||||
* <p>
|
||||
* <b>Note:</b>The static method <b>invokeMethodOf</b> handles the case where
|
||||
* the test <i>actions instanceof Ipx800Actions</i> fails. This test can fail
|
||||
* due to an issue in openHAB core v2.5.0 where the {@link Ipx800Actions} class
|
||||
* can be loaded by a different classloader than the <i>actions</i> instance.
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*/
|
||||
@ThingActionsScope(name = "gce")
|
||||
@NonNullByDefault
|
||||
public class Ipx800Actions implements ThingActions, IIpx800Actions {
|
||||
private final Logger logger = LoggerFactory.getLogger(Ipx800Actions.class);
|
||||
|
||||
protected @Nullable Ipx800v3Handler handler;
|
||||
|
||||
public Ipx800Actions() {
|
||||
logger.debug("IPX800 actions service instanciated");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setThingHandler(@Nullable ThingHandler handler) {
|
||||
if (handler instanceof Ipx800v3Handler) {
|
||||
this.handler = (Ipx800v3Handler) handler;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ThingHandler getThingHandler() {
|
||||
return this.handler;
|
||||
}
|
||||
|
||||
@Override
|
||||
@RuleAction(label = "GCE : Reset counter", description = "Resets to 0 value of a given counter")
|
||||
public void resetCounter(
|
||||
@ActionInput(name = "counter", label = "Counter", required = true, description = "Id of the counter", type = "java.lang.Integer") Integer counter) {
|
||||
logger.debug("IPX800 action 'resetCounter' called");
|
||||
Ipx800v3Handler theHandler = this.handler;
|
||||
if (theHandler != null) {
|
||||
theHandler.resetCounter(counter);
|
||||
} else {
|
||||
logger.warn("Method call resetCounter failed because IPX800 action service ThingHandler is null!");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@RuleAction(label = "GCE : Reset PLC", description = "Restarts the IPX800")
|
||||
public void reset(
|
||||
@ActionInput(name = "placeholder", label = "Placeholder", required = false, description = "This parameter is not used", type = "java.lang.Integer") @Nullable Integer placeholder) {
|
||||
logger.debug("IPX800 action 'reset' called");
|
||||
Ipx800v3Handler theHandler = this.handler;
|
||||
if (theHandler != null) {
|
||||
theHandler.reset();
|
||||
} else {
|
||||
logger.warn("Method call reset failed because IPX800 action service ThingHandler is null!");
|
||||
}
|
||||
}
|
||||
|
||||
public static void resetCounter(@Nullable ThingActions actions, Integer counter) {
|
||||
invokeMethodOf(actions).resetCounter(counter);
|
||||
}
|
||||
|
||||
public static void reset(@Nullable ThingActions actions, @Nullable Integer placeholder) {
|
||||
invokeMethodOf(actions).reset(placeholder);
|
||||
}
|
||||
|
||||
private static IIpx800Actions invokeMethodOf(@Nullable ThingActions actions) {
|
||||
if (actions == null) {
|
||||
throw new IllegalArgumentException("actions cannot be null");
|
||||
}
|
||||
if (actions.getClass().getName().equals(Ipx800Actions.class.getName())) {
|
||||
if (actions instanceof IIpx800Actions) {
|
||||
return (IIpx800Actions) actions;
|
||||
} else {
|
||||
return (IIpx800Actions) Proxy.newProxyInstance(IIpx800Actions.class.getClassLoader(),
|
||||
new Class[] { IIpx800Actions.class }, (Object proxy, Method method, Object[] args) -> {
|
||||
Method m = actions.getClass().getDeclaredMethod(method.getName(),
|
||||
method.getParameterTypes());
|
||||
return m.invoke(actions, args);
|
||||
});
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Actions is not an instance of Ipx800Actions");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.gce.internal.config;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link AnalogInputConfiguration} class holds configuration informations of
|
||||
* an ipx800 Analog Input port.
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class AnalogInputConfiguration {
|
||||
public long hysteresis;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.gce.internal.config;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
|
||||
/**
|
||||
* The {@link DigitalInputConfiguration} class holds configuration informations of
|
||||
* an ipx800 Digital Input port.
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class DigitalInputConfiguration extends Configuration {
|
||||
public long debouncePeriod = 0;
|
||||
public long longPressTime = 0;
|
||||
public long pulsePeriod = 0;
|
||||
public long pulseTimeout = 0;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.gce.internal.config;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link Ipx800Configuration} class holds configuration informations of
|
||||
* the ipx800v3 thing.
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Ipx800Configuration {
|
||||
public String hostname = "";
|
||||
public int portNumber = 9870;
|
||||
public int pullInterval = 5000;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.gce.internal.config;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
|
||||
/**
|
||||
* The {@link RelayOutputConfiguration} class holds configuration informations of
|
||||
* an ipx800 relay output.
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class RelayOutputConfiguration extends Configuration {
|
||||
public boolean pulse;
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.gce.internal.handler;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.PrintWriter;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketTimeoutException;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.gce.internal.model.M2MMessageParser;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link Ipx800DeviceConnector} is responsible for connecting,
|
||||
* reading, writing and disconnecting from the Ipx800.
|
||||
*
|
||||
* @author Seebag - Initial Contribution
|
||||
* @author Gaël L'hopital - Ported and adapted for OH2
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Ipx800DeviceConnector extends Thread {
|
||||
private final Logger logger = LoggerFactory.getLogger(Ipx800DeviceConnector.class);
|
||||
private static final int DEFAULT_SOCKET_TIMEOUT_MS = 5000;
|
||||
private static final int DEFAULT_RECONNECT_TIMEOUT_MS = 5000;
|
||||
private static final int MAX_KEEPALIVE_FAILURE = 3;
|
||||
private static final String ENDL = "\r\n";
|
||||
|
||||
private final String hostname;
|
||||
public final int portNumber;
|
||||
private @Nullable M2MMessageParser parser;
|
||||
|
||||
private @NonNullByDefault({}) Socket client;
|
||||
private @NonNullByDefault({}) BufferedReader in;
|
||||
private @NonNullByDefault({}) PrintWriter out;
|
||||
|
||||
private int failedKeepalive = 0;
|
||||
private boolean waitingKeepaliveResponse = false;
|
||||
|
||||
public Ipx800DeviceConnector(String hostname, int portNumber, ThingUID uid) {
|
||||
super("OH-binding-" + uid);
|
||||
this.hostname = hostname;
|
||||
this.portNumber = portNumber;
|
||||
setDaemon(true);
|
||||
}
|
||||
|
||||
public synchronized void send(String message) {
|
||||
logger.debug("Sending '{}' to Ipx800", message);
|
||||
out.write(message + ENDL);
|
||||
out.flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to the ipx800
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
private void connect() throws IOException {
|
||||
disconnect();
|
||||
logger.debug("Connecting {}:{}...", hostname, portNumber);
|
||||
client = new Socket(hostname, portNumber);
|
||||
client.setSoTimeout(DEFAULT_SOCKET_TIMEOUT_MS);
|
||||
client.getInputStream().skip(client.getInputStream().available());
|
||||
in = new BufferedReader(new InputStreamReader(client.getInputStream()));
|
||||
out = new PrintWriter(client.getOutputStream(), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnect the device
|
||||
*/
|
||||
private void disconnect() {
|
||||
logger.debug("Disconnecting");
|
||||
|
||||
if (in != null) {
|
||||
try {
|
||||
in.close();
|
||||
} catch (IOException ignore) {
|
||||
}
|
||||
this.in = null;
|
||||
}
|
||||
if (out != null) {
|
||||
out.close();
|
||||
this.out = null;
|
||||
}
|
||||
if (client != null) {
|
||||
try {
|
||||
client.close();
|
||||
} catch (IOException ignore) {
|
||||
}
|
||||
this.client = null;
|
||||
}
|
||||
logger.debug("Disconnected");
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the device thread
|
||||
*/
|
||||
public void destroyAndExit() {
|
||||
interrupt();
|
||||
disconnect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an arbitrary keepalive command which cause the IPX to send an update.
|
||||
* If we don't receive the update maxKeepAliveFailure time, the connection is closed and reopened
|
||||
*/
|
||||
private void sendKeepalive() {
|
||||
if (out != null) {
|
||||
if (waitingKeepaliveResponse) {
|
||||
failedKeepalive++;
|
||||
logger.debug("Sending keepalive, attempt {}", failedKeepalive);
|
||||
} else {
|
||||
failedKeepalive = 0;
|
||||
logger.debug("Sending keepalive");
|
||||
}
|
||||
out.println("GetIn01");
|
||||
out.flush();
|
||||
waitingKeepaliveResponse = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
waitingKeepaliveResponse = false;
|
||||
failedKeepalive = 0;
|
||||
connect();
|
||||
while (!interrupted()) {
|
||||
if (failedKeepalive > MAX_KEEPALIVE_FAILURE) {
|
||||
throw new IOException("Max keep alive attempts has been reached");
|
||||
}
|
||||
try {
|
||||
String command = in.readLine();
|
||||
waitingKeepaliveResponse = false;
|
||||
if (parser != null) {
|
||||
parser.unsolicitedUpdate(command);
|
||||
}
|
||||
} catch (SocketTimeoutException e) {
|
||||
handleException(e);
|
||||
}
|
||||
}
|
||||
disconnect();
|
||||
} catch (IOException e) {
|
||||
handleException(e);
|
||||
}
|
||||
try {
|
||||
Thread.sleep(DEFAULT_RECONNECT_TIMEOUT_MS);
|
||||
} catch (InterruptedException e) {
|
||||
destroyAndExit();
|
||||
}
|
||||
}
|
||||
|
||||
private void handleException(Exception e) {
|
||||
if (!interrupted()) {
|
||||
if (e instanceof SocketTimeoutException) {
|
||||
sendKeepalive();
|
||||
return;
|
||||
} else if (e instanceof IOException) {
|
||||
logger.warn("Communication error : '{}', will retry in {} ms", e, DEFAULT_RECONNECT_TIMEOUT_MS);
|
||||
}
|
||||
if (parser != null) {
|
||||
parser.errorOccurred(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setParser(M2MMessageParser parser) {
|
||||
this.parser = parser;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.gce.internal.handler;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* This interface defines interface to receive data from IPX800 controller.
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface Ipx800EventListener {
|
||||
|
||||
/**
|
||||
* Procedure for receive data from IPX800 controller.
|
||||
*
|
||||
* @param port Port (kind and number) receiving update
|
||||
* @param value value updated
|
||||
*/
|
||||
void dataReceived(String port, double value);
|
||||
|
||||
/**
|
||||
* Procedure for receiving information fatal error.
|
||||
*
|
||||
* @param e Error occurred.
|
||||
*/
|
||||
void errorOccurred(Exception e);
|
||||
}
|
||||
@@ -0,0 +1,392 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.gce.internal.handler;
|
||||
|
||||
import static org.openhab.binding.gce.internal.GCEBindingConstants.*;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
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.gce.internal.action.Ipx800Actions;
|
||||
import org.openhab.binding.gce.internal.config.AnalogInputConfiguration;
|
||||
import org.openhab.binding.gce.internal.config.DigitalInputConfiguration;
|
||||
import org.openhab.binding.gce.internal.config.Ipx800Configuration;
|
||||
import org.openhab.binding.gce.internal.config.RelayOutputConfiguration;
|
||||
import org.openhab.binding.gce.internal.model.M2MMessageParser;
|
||||
import org.openhab.binding.gce.internal.model.PortData;
|
||||
import org.openhab.binding.gce.internal.model.PortDefinition;
|
||||
import org.openhab.binding.gce.internal.model.StatusFileInterpreter;
|
||||
import org.openhab.binding.gce.internal.model.StatusFileInterpreter.StatusEntry;
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
import org.openhab.core.library.CoreItemFactory;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.OpenClosedType;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.library.unit.SmartHomeUnits;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.thing.ChannelGroupUID;
|
||||
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.thing.binding.ThingHandlerService;
|
||||
import org.openhab.core.thing.binding.builder.ChannelBuilder;
|
||||
import org.openhab.core.thing.binding.builder.ThingBuilder;
|
||||
import org.openhab.core.thing.type.ChannelKind;
|
||||
import org.openhab.core.thing.type.ChannelTypeUID;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link Ipx800v3Handler} is responsible for handling commands, which are
|
||||
* sent to one of the channels.
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Ipx800v3Handler extends BaseThingHandler implements Ipx800EventListener {
|
||||
private static final String PROPERTY_SEPARATOR = "-";
|
||||
private static final double ANALOG_SAMPLING = 0.000050354;
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(Ipx800v3Handler.class);
|
||||
|
||||
private @NonNullByDefault({}) Ipx800Configuration configuration;
|
||||
private @NonNullByDefault({}) Ipx800DeviceConnector connector;
|
||||
private @Nullable M2MMessageParser parser;
|
||||
private @NonNullByDefault({}) StatusFileInterpreter statusFile;
|
||||
private @Nullable ScheduledFuture<?> refreshJob;
|
||||
|
||||
private final Map<String, @Nullable PortData> portDatas = new HashMap<>();
|
||||
|
||||
private class LongPressEvaluator implements Runnable {
|
||||
private final ZonedDateTime referenceTime;
|
||||
private final String port;
|
||||
private final String eventChannelId;
|
||||
|
||||
public LongPressEvaluator(Channel channel, String port, PortData portData) {
|
||||
this.referenceTime = portData.getTimestamp();
|
||||
this.port = port;
|
||||
this.eventChannelId = channel.getUID().getId() + PROPERTY_SEPARATOR + TRIGGER_CONTACT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
PortData currentData = portDatas.get(port);
|
||||
if (currentData != null && currentData.getValue() == 1 && currentData.getTimestamp() == referenceTime) {
|
||||
triggerChannel(eventChannelId, EVENT_LONG_PRESS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Ipx800v3Handler(Thing thing) {
|
||||
super(thing);
|
||||
logger.debug("Create a IPX800 Handler for thing '{}'", getThing().getUID());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
|
||||
configuration = getConfigAs(Ipx800Configuration.class);
|
||||
|
||||
logger.debug("Initializing IPX800 handler for uid '{}'", getThing().getUID());
|
||||
|
||||
statusFile = new StatusFileInterpreter(configuration.hostname, this);
|
||||
|
||||
if (thing.getProperties().isEmpty()) {
|
||||
discoverAttributes();
|
||||
}
|
||||
|
||||
connector = new Ipx800DeviceConnector(configuration.hostname, configuration.portNumber, getThing().getUID());
|
||||
parser = new M2MMessageParser(connector, this);
|
||||
|
||||
updateStatus(ThingStatus.UNKNOWN);
|
||||
|
||||
refreshJob = scheduler.scheduleWithFixedDelay(statusFile::read, 3000, configuration.pullInterval,
|
||||
TimeUnit.MILLISECONDS);
|
||||
|
||||
connector.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
if (refreshJob != null) {
|
||||
refreshJob.cancel(true);
|
||||
refreshJob = null;
|
||||
}
|
||||
|
||||
if (connector != null) {
|
||||
connector.destroyAndExit();
|
||||
}
|
||||
parser = null;
|
||||
|
||||
portDatas.values().stream().forEach(portData -> {
|
||||
if (portData != null) {
|
||||
portData.destroy();
|
||||
}
|
||||
});
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
protected void discoverAttributes() {
|
||||
final Map<String, String> properties = new HashMap<>();
|
||||
|
||||
properties.put(Thing.PROPERTY_VENDOR, "GCE Electronics");
|
||||
properties.put(Thing.PROPERTY_FIRMWARE_VERSION, statusFile.getElement(StatusEntry.VERSION));
|
||||
properties.put(Thing.PROPERTY_MAC_ADDRESS, statusFile.getElement(StatusEntry.CONFIG_MAC));
|
||||
updateProperties(properties);
|
||||
|
||||
ThingBuilder thingBuilder = editThing();
|
||||
List<Channel> channels = new ArrayList<>(getThing().getChannels());
|
||||
|
||||
PortDefinition.asStream().forEach(portDefinition -> {
|
||||
int nbElements = statusFile.getMaxNumberofNodeType(portDefinition);
|
||||
for (int i = 0; i < nbElements; i++) {
|
||||
createChannels(portDefinition, i, channels);
|
||||
}
|
||||
});
|
||||
|
||||
thingBuilder.withChannels(channels);
|
||||
updateThing(thingBuilder.build());
|
||||
}
|
||||
|
||||
private void createChannels(PortDefinition portDefinition, int portIndex, List<Channel> channels) {
|
||||
String ndx = Integer.toString(portIndex + 1);
|
||||
String advancedChannelTypeName = portDefinition.toString()
|
||||
+ (portDefinition.isAdvanced(portIndex) ? "Advanced" : "");
|
||||
ChannelGroupUID groupUID = new ChannelGroupUID(thing.getUID(), portDefinition.toString());
|
||||
ChannelUID mainChannelUID = new ChannelUID(groupUID, ndx);
|
||||
ChannelTypeUID channelType = new ChannelTypeUID(BINDING_ID, advancedChannelTypeName);
|
||||
switch (portDefinition) {
|
||||
case ANALOG:
|
||||
channels.add(ChannelBuilder.create(mainChannelUID, CoreItemFactory.NUMBER)
|
||||
.withLabel("Analog Input " + ndx).withType(channelType).build());
|
||||
channels.add(ChannelBuilder
|
||||
.create(new ChannelUID(groupUID, ndx + "-voltage"), "Number:ElectricPotential")
|
||||
.withLabel("Voltage " + ndx).withType(new ChannelTypeUID(BINDING_ID, CHANNEL_VOLTAGE)).build());
|
||||
break;
|
||||
case CONTACT:
|
||||
channels.add(ChannelBuilder.create(mainChannelUID, CoreItemFactory.CONTACT).withLabel("Contact " + ndx)
|
||||
.withType(channelType).build());
|
||||
channels.add(ChannelBuilder.create(new ChannelUID(groupUID, ndx + "-event"), null)
|
||||
.withLabel("Contact " + ndx + " Event").withKind(ChannelKind.TRIGGER)
|
||||
.withType(new ChannelTypeUID(BINDING_ID, TRIGGER_CONTACT + (portIndex < 8 ? "" : "Advanced")))
|
||||
.build());
|
||||
break;
|
||||
case COUNTER:
|
||||
channels.add(ChannelBuilder.create(mainChannelUID, CoreItemFactory.NUMBER).withLabel("Counter " + ndx)
|
||||
.withType(channelType).build());
|
||||
break;
|
||||
case RELAY:
|
||||
channels.add(ChannelBuilder.create(mainChannelUID, CoreItemFactory.SWITCH).withLabel("Relay " + ndx)
|
||||
.withType(channelType).build());
|
||||
break;
|
||||
}
|
||||
channels.add(ChannelBuilder.create(new ChannelUID(groupUID, ndx + "-duration"), "Number:Time")
|
||||
.withLabel("Previous state duration " + ndx)
|
||||
.withType(new ChannelTypeUID(BINDING_ID, CHANNEL_LAST_STATE_DURATION)).build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void errorOccurred(Exception e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
|
||||
}
|
||||
|
||||
private boolean ignoreCondition(double newValue, PortData portData, Configuration configuration,
|
||||
PortDefinition portDefinition, ZonedDateTime now) {
|
||||
if (!portData.isInitializing()) { // Always accept if portData is not initialized
|
||||
double prevValue = portData.getValue();
|
||||
if (newValue == prevValue) { // Always reject if the value did not change
|
||||
return true;
|
||||
}
|
||||
if (portDefinition == PortDefinition.ANALOG) { // For analog values, check histeresis
|
||||
AnalogInputConfiguration config = configuration.as(AnalogInputConfiguration.class);
|
||||
long hysteresis = config.hysteresis / 2;
|
||||
if (newValue <= prevValue + hysteresis && newValue >= prevValue - hysteresis) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (portDefinition == PortDefinition.CONTACT) { // For contact values, check debounce
|
||||
DigitalInputConfiguration config = configuration.as(DigitalInputConfiguration.class);
|
||||
if (config.debouncePeriod != 0
|
||||
&& now.isBefore(portData.getTimestamp().plus(config.debouncePeriod, ChronoUnit.MILLIS))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dataReceived(String port, double value) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
Channel channel = thing.getChannel(PortDefinition.asChannelId(port));
|
||||
if (channel != null) {
|
||||
String channelId = channel.getUID().getId();
|
||||
String groupId = channel.getUID().getGroupId();
|
||||
PortData portData = portDatas.get(channelId);
|
||||
if (portData != null && groupId != null) {
|
||||
ZonedDateTime now = ZonedDateTime.now(ZoneId.systemDefault());
|
||||
long sinceLastChange = Duration.between(portData.getTimestamp(), now).toMillis();
|
||||
Configuration configuration = channel.getConfiguration();
|
||||
PortDefinition portDefinition = PortDefinition.fromGroupId(groupId);
|
||||
if (ignoreCondition(value, portData, configuration, portDefinition, now)) {
|
||||
logger.debug("Ignore condition met for port '{}' with data '{}'", port, value);
|
||||
return;
|
||||
}
|
||||
logger.debug("About to update port '{}' with data '{}'", port, value);
|
||||
State state = UnDefType.UNDEF;
|
||||
switch (portDefinition) {
|
||||
case COUNTER:
|
||||
state = new DecimalType(value);
|
||||
break;
|
||||
case RELAY:
|
||||
state = value == 1 ? OnOffType.ON : OnOffType.OFF;
|
||||
break;
|
||||
case ANALOG:
|
||||
state = new DecimalType(value);
|
||||
updateState(channelId + PROPERTY_SEPARATOR + CHANNEL_VOLTAGE,
|
||||
new QuantityType<>(value * ANALOG_SAMPLING, SmartHomeUnits.VOLT));
|
||||
break;
|
||||
case CONTACT:
|
||||
DigitalInputConfiguration config = configuration.as(DigitalInputConfiguration.class);
|
||||
portData.cancelPulsing();
|
||||
state = value == 1 ? OpenClosedType.CLOSED : OpenClosedType.OPEN;
|
||||
switch ((OpenClosedType) state) {
|
||||
case CLOSED:
|
||||
if (config.longPressTime != 0 && !portData.isInitializing()) {
|
||||
scheduler.schedule(new LongPressEvaluator(channel, port, portData),
|
||||
config.longPressTime, TimeUnit.MILLISECONDS);
|
||||
} else if (config.pulsePeriod != 0) {
|
||||
portData.setPulsing(scheduler.scheduleWithFixedDelay(() -> {
|
||||
triggerPushButtonChannel(channel, EVENT_PULSE);
|
||||
}, config.pulsePeriod, config.pulsePeriod, TimeUnit.MILLISECONDS));
|
||||
if (config.pulseTimeout != 0) {
|
||||
scheduler.schedule(portData::cancelPulsing, config.pulseTimeout,
|
||||
TimeUnit.MILLISECONDS);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case OPEN:
|
||||
if (!portData.isInitializing() && config.longPressTime != 0
|
||||
&& sinceLastChange < config.longPressTime) {
|
||||
triggerPushButtonChannel(channel, EVENT_SHORT_PRESS);
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (!portData.isInitializing()) {
|
||||
triggerPushButtonChannel(channel, value == 1 ? EVENT_PRESSED : EVENT_RELEASED);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
updateState(channelId, state);
|
||||
if (!portData.isInitializing()) {
|
||||
updateState(channelId + PROPERTY_SEPARATOR + CHANNEL_LAST_STATE_DURATION,
|
||||
new QuantityType<>(sinceLastChange / 1000, SmartHomeUnits.SECOND));
|
||||
}
|
||||
portData.setData(value, now);
|
||||
} else {
|
||||
logger.debug("Received data '{}' for not configured port '{}'", value, port);
|
||||
}
|
||||
} else {
|
||||
logger.debug("Received data '{}' for not configured channel '{}'", value, port);
|
||||
}
|
||||
}
|
||||
|
||||
protected void triggerPushButtonChannel(Channel channel, String event) {
|
||||
logger.debug("Triggering event '{}' on channel '{}'", event, channel.getUID());
|
||||
triggerChannel(channel.getUID().getId() + PROPERTY_SEPARATOR + TRIGGER_CONTACT, event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
logger.debug("Received channel: {}, command: {}", channelUID, command);
|
||||
|
||||
Channel channel = thing.getChannel(channelUID.getId());
|
||||
String groupId = channelUID.getGroupId();
|
||||
|
||||
if (channel == null || groupId == null) {
|
||||
return;
|
||||
}
|
||||
if (command instanceof OnOffType && isValidPortId(channelUID)
|
||||
&& PortDefinition.fromGroupId(groupId) == PortDefinition.RELAY) {
|
||||
RelayOutputConfiguration config = channel.getConfiguration().as(RelayOutputConfiguration.class);
|
||||
String id = channelUID.getIdWithoutGroup();
|
||||
if (parser != null) {
|
||||
parser.setOutput(id, (OnOffType) command == OnOffType.ON ? 1 : 0, config.pulse);
|
||||
}
|
||||
return;
|
||||
}
|
||||
logger.debug("Can not handle command '{}' on channel '{}'", command, channelUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelLinked(ChannelUID channelUID) {
|
||||
logger.debug("channelLinked: {}", channelUID);
|
||||
final String channelId = channelUID.getId();
|
||||
if (isValidPortId(channelUID)) {
|
||||
Channel channel = thing.getChannel(channelUID);
|
||||
if (channel != null) {
|
||||
PortData data = new PortData();
|
||||
portDatas.put(channelId, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isValidPortId(ChannelUID channelUID) {
|
||||
return channelUID.getIdWithoutGroup().chars().allMatch(Character::isDigit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelUnlinked(ChannelUID channelUID) {
|
||||
super.channelUnlinked(channelUID);
|
||||
PortData portData = portDatas.remove(channelUID.getId());
|
||||
if (portData != null) {
|
||||
portData.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
public void resetCounter(int counter) {
|
||||
if (parser != null) {
|
||||
parser.resetCounter(counter);
|
||||
}
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
if (parser != null) {
|
||||
parser.resetPLC();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Class<? extends ThingHandlerService>> getServices() {
|
||||
return Collections.singletonList(Ipx800Actions.class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.gce.internal.model;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.gce.internal.handler.Ipx800DeviceConnector;
|
||||
import org.openhab.binding.gce.internal.handler.Ipx800EventListener;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* This class handles message translation to and from the IPX.
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class M2MMessageParser {
|
||||
private static final String IO_DESCRIPTOR = "(\\d{32})";
|
||||
private static final Pattern IO_PATTERN = Pattern.compile(IO_DESCRIPTOR);
|
||||
private static final Pattern VALIDATION_PATTERN = Pattern
|
||||
.compile("I=" + IO_DESCRIPTOR + "&O=" + IO_DESCRIPTOR + "&([AC]\\d{1,2}=\\d+&)*[^I]*");
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(M2MMessageParser.class);
|
||||
private final Ipx800DeviceConnector connector;
|
||||
private final @Nullable Ipx800EventListener listener;
|
||||
|
||||
private String expectedResponse = "";
|
||||
|
||||
public M2MMessageParser(Ipx800DeviceConnector connector, @Nullable Ipx800EventListener listener) {
|
||||
this.connector = connector;
|
||||
this.listener = listener;
|
||||
connector.setParser(this);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param data
|
||||
*/
|
||||
public void unsolicitedUpdate(String data) {
|
||||
if (IO_PATTERN.matcher(data).matches()) {
|
||||
PortDefinition portDefinition = PortDefinition.fromM2MCommand(expectedResponse);
|
||||
decodeDataLine(portDefinition, data);
|
||||
} else if (VALIDATION_PATTERN.matcher(data).matches()) {
|
||||
for (String status : data.split("&")) {
|
||||
String statusPart[] = status.split("=");
|
||||
int portNumShift = 1;
|
||||
PortDefinition portDefinition = PortDefinition.fromPortName(statusPart[0].substring(0, 1));
|
||||
switch (portDefinition) {
|
||||
case CONTACT:
|
||||
case RELAY: {
|
||||
decodeDataLine(portDefinition, statusPart[1]);
|
||||
break;
|
||||
}
|
||||
case COUNTER:
|
||||
portNumShift = 0; // Align counters on 1 based array
|
||||
case ANALOG: {
|
||||
int portNumber = Integer.parseInt(statusPart[0].substring(1)) + portNumShift;
|
||||
setStatus(portDefinition.getPortName() + portNumber, Double.parseDouble(statusPart[1]));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (!expectedResponse.isEmpty()) {
|
||||
setStatus(expectedResponse, Double.parseDouble(data));
|
||||
}
|
||||
|
||||
expectedResponse = "";
|
||||
}
|
||||
|
||||
private void decodeDataLine(PortDefinition portDefinition, String data) {
|
||||
for (int count = 0; count < data.length(); count++) {
|
||||
setStatus(portDefinition.getPortName() + (count + 1), (double) data.charAt(count) - '0');
|
||||
}
|
||||
}
|
||||
|
||||
private void setStatus(String port, double value) {
|
||||
logger.debug("Received {} : {}", port, value);
|
||||
if (listener != null) {
|
||||
listener.dataReceived(port, value);
|
||||
}
|
||||
}
|
||||
|
||||
public void setExpectedResponse(String expectedResponse) {
|
||||
if (expectedResponse.endsWith("s")) { // GetInputs or GetOutputs
|
||||
this.expectedResponse = expectedResponse;
|
||||
} else { // GetAnx or GetCountx
|
||||
PortDefinition portType = PortDefinition.fromM2MCommand(expectedResponse);
|
||||
this.expectedResponse = expectedResponse.replaceAll(portType.getM2mCommand(), portType.getPortName());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set output of the device sending the corresponding command
|
||||
*
|
||||
* @param targetPort
|
||||
* @param targetValue
|
||||
*/
|
||||
public void setOutput(String targetPort, int targetValue, boolean pulse) {
|
||||
logger.debug("Sending {} to {}", targetValue, targetPort);
|
||||
String command = String.format("Set%02d%s%s", Integer.parseInt(targetPort), targetValue, pulse ? "p" : "");
|
||||
connector.send(command);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the counter value to 0
|
||||
*
|
||||
* @param targetCounter
|
||||
*/
|
||||
public void resetCounter(int targetCounter) {
|
||||
logger.debug("Resetting counter {} to 0", targetCounter);
|
||||
connector.send(String.format("ResetCount%d", targetCounter));
|
||||
}
|
||||
|
||||
public void errorOccurred(Exception e) {
|
||||
logger.warn("Error received from connector : {}", e.getMessage());
|
||||
if (listener != null) {
|
||||
listener.errorOccurred(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void resetPLC() {
|
||||
connector.send("Reset");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.gce.internal.model;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The {@link PortData} is responsible for holding data regarding current status of a port.
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class PortData {
|
||||
private double value = -1;
|
||||
private ZonedDateTime timestamp = ZonedDateTime.now();
|
||||
private @Nullable ScheduledFuture<?> pulsing;
|
||||
|
||||
public void cancelPulsing() {
|
||||
if (pulsing != null) {
|
||||
pulsing.cancel(true);
|
||||
}
|
||||
pulsing = null;
|
||||
}
|
||||
|
||||
public void destroy() {
|
||||
cancelPulsing();
|
||||
}
|
||||
|
||||
public void setData(double value, ZonedDateTime timestamp) {
|
||||
this.value = value;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public double getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public ZonedDateTime getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public void setPulsing(ScheduledFuture<?> pulsing) {
|
||||
this.pulsing = pulsing;
|
||||
}
|
||||
|
||||
public boolean isInitializing() {
|
||||
return value == -1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.gce.internal.model;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link PortDefinition} enum defines and handle port
|
||||
* definition constants
|
||||
*
|
||||
* @author Gaël L'hopital - Initial Contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public enum PortDefinition {
|
||||
COUNTER("count", "C", "GetCount", 8),
|
||||
ANALOG("analog", "A", "GetAn", 4),
|
||||
RELAY("led", "O", "GetOut", 8),
|
||||
CONTACT("btn", "I", "GetIn", 8);
|
||||
|
||||
private final String nodeName; // Name used in the status xml file
|
||||
private final String portName; // Name used by the M2M protocol
|
||||
private final String m2mCommand; // associated M2M command
|
||||
private final int quantity; // base number of ports
|
||||
|
||||
PortDefinition(String nodeName, String portName, String m2mCommand, int quantity) {
|
||||
this.nodeName = nodeName;
|
||||
this.portName = portName;
|
||||
this.m2mCommand = m2mCommand;
|
||||
this.quantity = quantity;
|
||||
}
|
||||
|
||||
public String getNodeName() {
|
||||
return nodeName;
|
||||
}
|
||||
|
||||
public String getPortName() {
|
||||
return portName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name().toLowerCase();
|
||||
}
|
||||
|
||||
public boolean isAdvanced(int id) {
|
||||
return id >= quantity;
|
||||
}
|
||||
|
||||
public String getM2mCommand() {
|
||||
return m2mCommand;
|
||||
}
|
||||
|
||||
public static Stream<PortDefinition> asStream() {
|
||||
return Stream.of(PortDefinition.values());
|
||||
}
|
||||
|
||||
public static PortDefinition fromM2MCommand(String m2mCommand) {
|
||||
return asStream().filter(v -> m2mCommand.startsWith(v.m2mCommand)).findFirst().get();
|
||||
}
|
||||
|
||||
public static PortDefinition fromPortName(String portName) {
|
||||
return asStream().filter(v -> portName.startsWith(v.portName)).findFirst().get();
|
||||
}
|
||||
|
||||
public static PortDefinition fromGroupId(String groupId) {
|
||||
return valueOf(groupId.toUpperCase());
|
||||
}
|
||||
|
||||
public static String asChannelId(String portDefinition) {
|
||||
String portKind = portDefinition.substring(0, 1);
|
||||
PortDefinition result = asStream().filter(v -> v.portName.startsWith(portKind)).findFirst().get();
|
||||
return result.toString() + "#" + portDefinition.substring(1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.gce.internal.model;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.gce.internal.handler.Ipx800EventListener;
|
||||
import org.openhab.core.io.net.http.HttpUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
/**
|
||||
* This class takes care of interpreting the status.xml file
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class StatusFileInterpreter {
|
||||
private static final String URL_TEMPLATE = "http://%s/globalstatus.xml";
|
||||
private final Logger logger = LoggerFactory.getLogger(StatusFileInterpreter.class);
|
||||
private final String hostname;
|
||||
private @Nullable Document doc;
|
||||
private final Ipx800EventListener listener;
|
||||
|
||||
public static enum StatusEntry {
|
||||
VERSION,
|
||||
CONFIG_MAC;
|
||||
}
|
||||
|
||||
public StatusFileInterpreter(String hostname, Ipx800EventListener listener) {
|
||||
this.hostname = hostname;
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
public void read() {
|
||||
try {
|
||||
DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
|
||||
String statusPage = HttpUtil.executeUrl("GET", String.format(URL_TEMPLATE, hostname), 5000);
|
||||
InputStream inputStream = new ByteArrayInputStream(statusPage.getBytes());
|
||||
Document document = builder.parse(inputStream);
|
||||
document.getDocumentElement().normalize();
|
||||
doc = document;
|
||||
pushDatas();
|
||||
inputStream.close();
|
||||
} catch (IOException | SAXException | ParserConfigurationException e) {
|
||||
logger.warn("Unable to read IPX800 status page : {}", e.getMessage());
|
||||
doc = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void pushDatas() {
|
||||
Element root = getRoot();
|
||||
if (root != null) {
|
||||
PortDefinition.asStream().forEach(portDefinition -> {
|
||||
List<Node> xmlNodes = getMatchingNodes(root.getChildNodes(), portDefinition.getNodeName());
|
||||
xmlNodes.forEach(xmlNode -> {
|
||||
String sPortNum = xmlNode.getNodeName().replace(portDefinition.getNodeName(), "");
|
||||
int portNum = Integer.parseInt(sPortNum) + 1;
|
||||
double value = Double.parseDouble(xmlNode.getTextContent().replace("dn", "1").replace("up", "0"));
|
||||
listener.dataReceived(String.format("%s%d", portDefinition.getPortName(), portNum), value);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public String getElement(StatusEntry entry) {
|
||||
Element root = getRoot();
|
||||
if (root != null) {
|
||||
return root.getElementsByTagName(entry.name().toLowerCase()).item(0).getTextContent();
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
private List<Node> getMatchingNodes(NodeList nodeList, String criteria) {
|
||||
return IntStream.range(0, nodeList.getLength()).boxed().map(nodeList::item)
|
||||
.filter(node -> node.getNodeName().startsWith(criteria))
|
||||
.sorted(Comparator.comparing(o -> o.getNodeName())).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public int getMaxNumberofNodeType(PortDefinition portDefinition) {
|
||||
Element root = getRoot();
|
||||
if (root != null) {
|
||||
List<Node> filteredNodes = getMatchingNodes(root.getChildNodes(), portDefinition.getNodeName());
|
||||
return filteredNodes.size();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private @Nullable Element getRoot() {
|
||||
if (doc == null) {
|
||||
read();
|
||||
}
|
||||
if (doc != null) {
|
||||
return doc.getDocumentElement();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<binding:binding id="gce" 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>GCE Electronics Binding</name>
|
||||
<description>Provides access to IPX800 PLC build by GCE.</description>
|
||||
<author>Gaël L'hopital</author>
|
||||
|
||||
</binding:binding>
|
||||
@@ -0,0 +1,63 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<config-description:config-descriptions
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0
|
||||
https://openhab.org/schemas/config-description-1.0.0.xsd">
|
||||
|
||||
<config-description uri="thing-type:gce:ipx800v3Config">
|
||||
<parameter name="hostname" type="text" required="true">
|
||||
<context>network-address</context>
|
||||
<label>Network Address</label>
|
||||
<description>Network/IP address of the IPX800 without http(s) prefix.</description>
|
||||
</parameter>
|
||||
<parameter name="portNumber" type="integer" required="false">
|
||||
<label>Port Number</label>
|
||||
<description>TCP client connection port.</description>
|
||||
<default>9870</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="pullInterval" type="integer" required="false" min="500" max="60000" unit="ms">
|
||||
<label>Pull Interval</label>
|
||||
<description>Delay for pulling Analog and Counters info (in milliseconds).</description>
|
||||
<default>5000</default>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
<config-description uri="channel-type:gce:contactConfig">
|
||||
<parameter name="debouncePeriod" type="integer" min="0" max="3000" unit="ms">
|
||||
<label>Debounce Time</label>
|
||||
<default>0</default>
|
||||
</parameter>
|
||||
<parameter name="longPressTime" type="integer" step="1000" min="0" max="5000" unit="ms">
|
||||
<label>Long Press Time</label>
|
||||
<description>Long press time in milliseconds.</description>
|
||||
<default>0</default>
|
||||
</parameter>
|
||||
<parameter name="pulsePeriod" type="integer" step="500" min="0" max="50000" unit="ms">
|
||||
<label>Pulse Period</label>
|
||||
<description>Pulse period in milliseconds.</description>
|
||||
<default>0</default>
|
||||
</parameter>
|
||||
<parameter name="pulseTimeout" type="integer" step="500" min="0" max="50000" unit="ms">
|
||||
<label>Pulse Timeout</label>
|
||||
<description>Maximum period for sending pulses in milliseconds.</description>
|
||||
<default>0</default>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
<config-description uri="channel-type:gce:relayConfig">
|
||||
<parameter name="pulse" type="boolean">
|
||||
<label>Pulse Output</label>
|
||||
<default>false</default>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
<config-description uri="channel-type:gce:analogConfig">
|
||||
<parameter name="hysteresis" type="integer" min="0">
|
||||
<label>Hysteresis</label>
|
||||
<default>0</default>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
</config-description:config-descriptions>
|
||||
@@ -0,0 +1,95 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="gce" 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">
|
||||
|
||||
<channel-type id="contact">
|
||||
<item-type>Contact</item-type>
|
||||
<label>Digital Input</label>
|
||||
<category>Contact</category>
|
||||
<state pattern="%s" readOnly="true"/>
|
||||
<config-description-ref uri="channel-type:gce:contactConfig"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="contactAdvanced" advanced="true">
|
||||
<item-type>Contact</item-type>
|
||||
<label>Digital Input</label>
|
||||
<category>Contact</category>
|
||||
<state pattern="%s" readOnly="true"/>
|
||||
<config-description-ref uri="channel-type:gce:contactConfig"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="relay">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Digital Output</label>
|
||||
<config-description-ref uri="channel-type:gce:relayConfig"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="relayAdvanced" advanced="true">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Digital Output</label>
|
||||
<config-description-ref uri="channel-type:gce:relayConfig"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="counter">
|
||||
<item-type>Number</item-type>
|
||||
<label>Counter</label>
|
||||
<state pattern="%d %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="analog">
|
||||
<item-type>Number</item-type>
|
||||
<label>Analog Input</label>
|
||||
<state readOnly="true" pattern="%d %unit%"/>
|
||||
<config-description-ref uri="channel-type:gce:analogConfig"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="analogAdvanced" advanced="true">
|
||||
<item-type>Number</item-type>
|
||||
<label>Analog Input</label>
|
||||
<config-description-ref uri="channel-type:gce:analogConfig"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="duration" advanced="true">
|
||||
<item-type>Number:Time</item-type>
|
||||
<label>Previous State Duration</label>
|
||||
<description>Duration of previous state before state change.</description>
|
||||
<state pattern="%d %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="contact-trigger">
|
||||
<kind>trigger</kind>
|
||||
<label>Push Button Trigger Channel</label>
|
||||
<event>
|
||||
<options>
|
||||
<option value="PRESSED">Pressed</option>
|
||||
<option value="RELEASED">Released</option>
|
||||
<option value="SHORT_PRESS">Short press</option>
|
||||
<option value="LONG_PRESS">Long press</option>
|
||||
<option value="PULSE">Pulse</option>
|
||||
</options>
|
||||
</event>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="contact-triggerAdvanced" advanced="true">
|
||||
<kind>trigger</kind>
|
||||
<label>Push Button Trigger Channel</label>
|
||||
<event>
|
||||
<options>
|
||||
<option value="PRESSED">Pressed</option>
|
||||
<option value="RELEASED">Released</option>
|
||||
<option value="SHORT_PRESS">Short press</option>
|
||||
<option value="LONG_PRESS">Long press</option>
|
||||
<option value="PULSE">Pulse</option>
|
||||
</options>
|
||||
</event>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="voltage" advanced="true">
|
||||
<item-type>Number:ElectricPotential</item-type>
|
||||
<label>Voltage</label>
|
||||
<description>Voltage</description>
|
||||
<state readOnly="true" pattern="%.2f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="gce" 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">
|
||||
|
||||
<thing-type id="ipx800v3">
|
||||
<label>IPX800v3</label>
|
||||
<description>The GCE IPX800v3 device</description>
|
||||
|
||||
<channel-groups>
|
||||
<channel-group id="contact" typeId="contacts"/>
|
||||
<channel-group id="relay" typeId="relays"/>
|
||||
<channel-group id="counter" typeId="counters"/>
|
||||
<channel-group id="analog" typeId="analogs"/>
|
||||
</channel-groups>
|
||||
|
||||
<config-description-ref uri="thing-type:gce:ipx800v3Config"/>
|
||||
</thing-type>
|
||||
|
||||
<channel-group-type id="contacts">
|
||||
<label>Digital Inputs</label>
|
||||
</channel-group-type>
|
||||
|
||||
<channel-group-type id="relays">
|
||||
<label>Digital Outputs</label>
|
||||
</channel-group-type>
|
||||
|
||||
<channel-group-type id="counters">
|
||||
<label>Counters</label>
|
||||
</channel-group-type>
|
||||
|
||||
<channel-group-type id="analogs">
|
||||
<label>Analog Inputs</label>
|
||||
</channel-group-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
Reference in New Issue
Block a user