From 2a34f76c06036a3c2ff5d54fe3519ee605dfc9a1 Mon Sep 17 00:00:00 2001 From: clinique Date: Thu, 27 Aug 2020 19:20:21 +0200 Subject: [PATCH 01/17] Rewriting PR bundles/org.openhab.binding.hpprinter/pom.xml bundles/org.openhab.binding.hue/pom.xml bundles/org.openhab.binding.hydrawise/pom.xml bundles/org.openhab.binding.hyperion/pom.xml bundles/org.openhab.binding.iaqualink/pom.xml bundles/org.openhab.binding.icloud/pom.xml bundles/org.openhab.binding.ihc/pom.xml bundles/org.openhab.binding.innogysmarthome/pom.xml bundles/org.openhab.binding.ipp/pom.xml bundles/org.openhab.binding.irtrans/pom.xml bundles/org.openhab.binding.jeelink/pom.xml bundles/org.openhab.binding.keba/pom.xml bundles/org.openhab.binding.km200/pom.xml bundles/org.openhab.binding.knx/pom.xml bundles/org.openhab.binding.kodi/pom.xml bundles/org.openhab.binding.konnected/pom.xml bundles/org.openhab.binding.kostalinverter/pom.xml bundles/org.openhab.binding.lametrictime/pom.xml bundles/org.openhab.binding.leapmotion/pom.xml bundles/org.openhab.binding.lghombot/pom.xml bundles/org.openhab.binding.lgtvserial/pom.xml bundles/org.openhab.binding.lgwebos/pom.xml bundles/org.openhab.binding.lifx/pom.xml bundles/org.openhab.binding.linuxinput/pom.xml bundles/org.openhab.binding.lirc/pom.xml bundles/org.openhab.binding.logreader/pom.xml bundles/org.openhab.binding.loxone/pom.xml bundles/org.openhab.binding.lutron/pom.xml bundles/org.openhab.binding.mail/pom.xml bundles/org.openhab.binding.max/pom.xml bundles/org.openhab.binding.mcp23017/pom.xml bundles/org.openhab.binding.melcloud/pom.xml bundles/org.openhab.binding.meteoblue/pom.xml bundles/org.openhab.binding.meteostick/pom.xml bundles/org.openhab.binding.miele/pom.xml bundles/org.openhab.binding.mihome/pom.xml bundles/org.openhab.binding.miio/pom.xml bundles/org.openhab.binding.milight/pom.xml bundles/org.openhab.binding.millheat/pom.xml bundles/org.openhab.binding.minecraft/pom.xml bundles/org.openhab.binding.modbus/pom.xml bundles/org.openhab.binding.mqtt.generic/pom.xml bundles/org.openhab.binding.mqtt.homeassistant/pom.xml bundles/org.openhab.binding.mqtt.homie/pom.xml bundles/org.openhab.binding.mqtt/pom.xml bundles/org.openhab.binding.nanoleaf/pom.xml bundles/org.openhab.binding.neato/pom.xml bundles/org.openhab.binding.neeo/pom.xml bundles/org.openhab.binding.neohub/pom.xml bundles/org.openhab.binding.nest/pom.xml bundles/org.openhab.binding.netatmo/pom.xml bundles/org.openhab.binding.network/pom.xml bundles/org.openhab.binding.networkupstools/pom.xml bundles/org.openhab.binding.nibeheatpump/pom.xml bundles/org.openhab.binding.nibeuplink/pom.xml bundles/org.openhab.binding.nikobus/pom.xml bundles/org.openhab.binding.nikohomecontrol/pom.xml bundles/org.openhab.binding.ntp/pom.xml bundles/org.openhab.binding.nuki/pom.xml bundles/org.openhab.binding.oceanic/pom.xml bundles/org.openhab.binding.omnikinverter/pom.xml bundles/org.openhab.binding.onebusaway/pom.xml bundles/org.openhab.binding.onewire/pom.xml bundles/org.openhab.binding.onewiregpio/pom.xml bundles/org.openhab.binding.onkyo/pom.xml bundles/org.openhab.binding.opengarage/pom.xml bundles/org.openhab.binding.opensprinkler/pom.xml bundles/org.openhab.binding.openuv/pom.xml bundles/org.openhab.binding.openweathermap/pom.xml bundles/org.openhab.binding.orvibo/pom.xml bundles/org.openhab.binding.paradoxalarm/pom.xml bundles/org.openhab.binding.pentair/pom.xml bundles/org.openhab.binding.phc/pom.xml bundles/org.openhab.binding.pioneeravr/pom.xml bundles/org.openhab.binding.pixometer/pom.xml bundles/org.openhab.binding.pjlinkdevice/pom.xml bundles/org.openhab.binding.plclogo/pom.xml bundles/org.openhab.binding.plugwise/pom.xml bundles/org.openhab.binding.powermax/pom.xml bundles/org.openhab.binding.pulseaudio/pom.xml bundles/org.openhab.binding.pushbullet/pom.xml bundles/org.openhab.binding.regoheatpump/pom.xml bundles/org.openhab.binding.rfxcom/pom.xml bundles/org.openhab.binding.rme/pom.xml bundles/org.openhab.binding.robonect/pom.xml bundles/org.openhab.binding.rotel/pom.xml bundles/org.openhab.binding.rotelra1x/pom.xml bundles/org.openhab.binding.russound/pom.xml bundles/org.openhab.binding.samsungtv/pom.xml bundles/org.openhab.binding.satel/pom.xml bundles/org.openhab.binding.seneye/pom.xml bundles/org.openhab.binding.sensebox/pom.xml bundles/org.openhab.binding.serialbutton/pom.xml bundles/org.openhab.binding.shelly/pom.xml bundles/org.openhab.binding.siemensrds/pom.xml bundles/org.openhab.binding.silvercrestwifisocket/pom.xml bundles/org.openhab.binding.sinope/pom.xml bundles/org.openhab.binding.sleepiq/pom.xml bundles/org.openhab.binding.smaenergymeter/pom.xml bundles/org.openhab.binding.smartmeter/pom.xml bundles/org.openhab.binding.snmp/pom.xml bundles/org.openhab.binding.solaredge/pom.xml bundles/org.openhab.binding.solarlog/pom.xml bundles/org.openhab.binding.somfytahoma/pom.xml bundles/org.openhab.binding.sonos/pom.xml bundles/org.openhab.binding.sonyaudio/pom.xml bundles/org.openhab.binding.sonyprojector/pom.xml bundles/org.openhab.binding.spotify/pom.xml bundles/org.openhab.binding.squeezebox/pom.xml bundles/org.openhab.binding.synopanalyzer/pom.xml bundles/org.openhab.binding.systeminfo/pom.xml bundles/org.openhab.binding.tado/pom.xml bundles/org.openhab.binding.tankerkoenig/pom.xml bundles/org.openhab.binding.telegram/pom.xml bundles/org.openhab.binding.tellstick/pom.xml bundles/org.openhab.binding.tesla/pom.xml bundles/org.openhab.binding.toon/pom.xml bundles/org.openhab.binding.tplinksmarthome/pom.xml bundles/org.openhab.binding.tradfri/pom.xml bundles/org.openhab.binding.unifi/pom.xml bundles/org.openhab.binding.urtsi/pom.xml bundles/org.openhab.binding.valloxmv/pom.xml bundles/org.openhab.binding.vektiva/pom.xml bundles/org.openhab.binding.velbus/pom.xml bundles/org.openhab.binding.vitotronic/pom.xml bundles/org.openhab.binding.volvooncall/pom.xml bundles/org.openhab.binding.weathercompany/pom.xml bundles/org.openhab.binding.weatherunderground/pom.xml bundles/org.openhab.binding.wemo/pom.xml bundles/org.openhab.binding.wifiled/pom.xml bundles/org.openhab.binding.windcentrale/pom.xml bundles/org.openhab.binding.xmltv/pom.xml bundles/org.openhab.binding.xmppclient/pom.xml bundles/org.openhab.binding.yamahareceiver/pom.xml bundles/org.openhab.binding.yeelight/pom.xml bundles/org.openhab.binding.zoneminder/pom.xml bundles/org.openhab.binding.zway/pom.xml bundles/org.openhab.extensionservice.marketplace.automation/pom.xml bundles/org.openhab.extensionservice.marketplace/pom.xml bundles/org.openhab.io.homekit/pom.xml bundles/org.openhab.io.hueemulation/pom.xml bundles/org.openhab.io.imperihome/pom.xml bundles/org.openhab.io.javasound/pom.xml bundles/org.openhab.io.mqttembeddedbroker/pom.xml bundles/org.openhab.io.neeo/pom.xml bundles/org.openhab.io.openhabcloud/pom.xml bundles/org.openhab.io.transport.modbus/pom.xml bundles/org.openhab.io.webaudio/pom.xml bundles/org.openhab.persistence.mapdb/pom.xml bundles/org.openhab.transform.bin2json/pom.xml bundles/org.openhab.transform.exec/pom.xml bundles/org.openhab.transform.javascript/pom.xml bundles/org.openhab.transform.jinja/pom.xml bundles/org.openhab.transform.jsonpath/pom.xml bundles/org.openhab.transform.map/pom.xml bundles/org.openhab.transform.regex/pom.xml bundles/org.openhab.transform.scale/pom.xml bundles/org.openhab.transform.xpath/pom.xml bundles/org.openhab.transform.xslt/pom.xml bundles/org.openhab.voice.googletts/pom.xml bundles/org.openhab.voice.mactts/pom.xml bundles/org.openhab.voice.marytts/pom.xml bundles/org.openhab.voice.picotts/pom.xml bundles/org.openhab.voice.pollytts/pom.xml bundles/org.openhab.voice.voicerss/pom.xml bundles/pom.xml features/openhab-addons-external/pom.xml features/openhab-addons/pom.xml features/pom.xml itests/org.openhab.binding.astro.tests/pom.xml itests/org.openhab.binding.avmfritz.tests/pom.xml itests/org.openhab.binding.feed.tests/pom.xml itests/org.openhab.binding.hue.tests/pom.xml itests/org.openhab.binding.max.tests/pom.xml itests/org.openhab.binding.nest.tests/pom.xml itests/org.openhab.binding.ntp.tests/pom.xml itests/org.openhab.binding.systeminfo.tests/pom.xml itests/org.openhab.binding.tradfri.tests/pom.xml itests/org.openhab.binding.wemo.tests/pom.xml itests/org.openhab.io.hueemulation.tests/pom.xml itests/org.openhab.persistence.mapdb.tests/pom.xml itests/pom.xml pom.xml --- bundles/org.openhab.binding.gce/.classpath | 32 ++ bundles/org.openhab.binding.gce/.project | 23 ++ bundles/org.openhab.binding.gce/NOTICE | 13 + bundles/org.openhab.binding.gce/README.md | 202 +++++++++++++ bundles/org.openhab.binding.gce/pom.xml | 16 + .../gce/internal/GCEBindingConstants.java | 52 ++++ .../gce/internal/GCEHandlerFactory.java | 54 ++++ .../gce/internal/action/IIpx800Actions.java | 27 ++ .../gce/internal/action/Ipx800Actions.java | 114 ++++++++ .../config/AnalogInputConfiguration.java | 26 ++ .../internal/config/CounterConfiguration.java | 27 ++ .../config/DigitalInputConfiguration.java | 30 ++ .../internal/config/Ipx800Configuration.java | 31 ++ .../config/RelayOutputConfiguration.java | 27 ++ .../handler/Ipx800DeviceConnector.java | 171 +++++++++++ .../internal/handler/Ipx800EventListener.java | 40 +++ .../internal/handler/Ipx800MessageParser.java | 167 +++++++++++ .../gce/internal/handler/Ipx800v3Handler.java | 273 ++++++++++++++++++ .../gce/internal/handler/PortData.java | 77 +++++ .../resources/ESH-INF/binding/binding.xml | 11 + .../ESH-INF/config/ipx800v3Config.xml | 46 +++ .../main/resources/ESH-INF/thing/channels.xml | 212 ++++++++++++++ .../main/resources/ESH-INF/thing/ipx800v3.xml | 19 ++ .../test/Ipx800DeviceConnectorTest.java | 67 +++++ 24 files changed, 1757 insertions(+) create mode 100644 bundles/org.openhab.binding.gce/.classpath create mode 100644 bundles/org.openhab.binding.gce/.project create mode 100644 bundles/org.openhab.binding.gce/NOTICE create mode 100644 bundles/org.openhab.binding.gce/README.md create mode 100644 bundles/org.openhab.binding.gce/pom.xml create mode 100644 bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/GCEBindingConstants.java create mode 100644 bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/GCEHandlerFactory.java create mode 100644 bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/action/IIpx800Actions.java create mode 100644 bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/action/Ipx800Actions.java create mode 100644 bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/config/AnalogInputConfiguration.java create mode 100644 bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/config/CounterConfiguration.java create mode 100644 bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/config/DigitalInputConfiguration.java create mode 100644 bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/config/Ipx800Configuration.java create mode 100644 bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/config/RelayOutputConfiguration.java create mode 100644 bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800DeviceConnector.java create mode 100644 bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800EventListener.java create mode 100644 bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800MessageParser.java create mode 100644 bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800v3Handler.java create mode 100644 bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/PortData.java create mode 100644 bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/binding/binding.xml create mode 100644 bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/config/ipx800v3Config.xml create mode 100644 bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/thing/channels.xml create mode 100644 bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/thing/ipx800v3.xml create mode 100644 bundles/org.openhab.binding.gce/src/test/java/org/openhab/binding/gce/internal/test/Ipx800DeviceConnectorTest.java diff --git a/bundles/org.openhab.binding.gce/.classpath b/bundles/org.openhab.binding.gce/.classpath new file mode 100644 index 0000000000000..a5d95095ccaaf --- /dev/null +++ b/bundles/org.openhab.binding.gce/.classpath @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bundles/org.openhab.binding.gce/.project b/bundles/org.openhab.binding.gce/.project new file mode 100644 index 0000000000000..6cf1cf4a2660c --- /dev/null +++ b/bundles/org.openhab.binding.gce/.project @@ -0,0 +1,23 @@ + + + org.openhab.binding.gce + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + + diff --git a/bundles/org.openhab.binding.gce/NOTICE b/bundles/org.openhab.binding.gce/NOTICE new file mode 100644 index 0000000000000..4c20ef446c1e4 --- /dev/null +++ b/bundles/org.openhab.binding.gce/NOTICE @@ -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/openhab2-addons diff --git a/bundles/org.openhab.binding.gce/README.md b/bundles/org.openhab.binding.gce/README.md new file mode 100644 index 0000000000000..37bc5ff8b8de9 --- /dev/null +++ b/bundles/org.openhab.binding.gce/README.md @@ -0,0 +1,202 @@ +# 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 drive output and retrieve input. On input we generally connect push button (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/double clic/Long press + * Virtual dimmer + * Pulse mode support + +## Binding Configuration + +There is no configuration at binding level. + + +## Thing Configuration + +The IPX800v3 accepts the following configuration parameters + +| Property | Default | Required | Description | +|-----------------------------------|---------|----------|----------------------------| +| hostname | | Yes | IP address or hostname | +| portNumber | 9870 | No | TCP client connection port | + +The thing provides three kind of channels + +### Digital Inputs + +| Property | Required | Description | +|-----------------------------------|----------|----------------------------| +| debouncePeriod | No | Debounce time (avoids entry flappling within this time) | +| longPressTime | No | Long Press Time | +| pulsePeriod | No | Pulse Period | +| pulseTimeout | No | Pulse Timeout | + + +### Digital Outputs (relays) + +### Counters + +### Analog Inputs + + +## Item Configuration + +### Syntax + +ipx800 items are described as below (italic items are optionnal) + +``` +ipx800="name:port:*options>to\_name:to\_port*" +``` + +| name | ipx name or extension alias as defined in the configuration | +| port | ipx port name as Tnn, with T port type (O : ouput, I : input, C : Counter, A : Analog) and nn port number | +| options | depending on items | + +* `>*to\_name:*to\_port` : redirection option, is used to drive directly one output using an input. to\_name is the optional name of the ipx800 to send to command. to\_port is the port to send the command to (if no to_name, the command will be send to the same ipx) + +### Item Types + +#### Output + +``` +Switch Output { ipx800="myipx:O01" } +Switch Output { ipx800="myipx:O01:p" } +``` + +Drive output directly from a openhab item. Option p put this ouput in pulse mode (sending SetNxxp at ipx800) + +#### Mirror + +``` +Switch InputMirror { ipx800="myipx:I08:m" } +``` + +State of this item will follow the input state. + +#### Normal astable switch + +``` +Switch InputNormal { ipx800="myipx:I08" } +``` + +On each rising edge of the input, item state will change. + +#### Simple Clic + +``` +Switch InputSimpleClic { ipx800="myipx:I08:d", milight="m1;3" } +``` + +When coupled with a double click, after a single rising/falling edge, will wait the double clic timeout before changing item state. + +#### Double click + +``` +Switch InputDoubleClic2 { ipx800="myipx:I08:D>myipx2:O03" } +``` + +Change item state after a double clic on the input. + +#### Virtual dimmer + +``` +Switch InputDimmer { ipx800="ipx1:I02\:v\:\" } +``` + +A long press will raise the value of this item each 500ms by . + +Once item value reaches 100, it will stick to this value. A new long press will restart the dimmer to 0. + +#### Simple Counter + +``` +Number SimpleCounter {ipx800="myipx:C01"} +``` + +Will reflect ipx800 counter value. + +#### Average counter + +``` +Number AverageCounter {ipx800="myipx\:C01\: a\:1\:m"} +``` + +Will compute the average based on the counter. This is very useful to use in conjunction to pulse based counter (water/gaz/electrical counter). + +8 different counter could be connected direclty to ipx800. + +With this kind of counter, all the power will be monitored easily (liters per minute, Kw...) + +This item will publish its state at least each period. + +Options : \\:\ + +| Step | unit of each counter increment (as defined by hardware counter) | +| Period | Base period to compute the average | + + +#### To be done + +* Long press + + +### Example + +``` +Switch Output { ipx800="myipx:O01" } +Switch InputNormal { ipx800="myipx:I08" } +Switch InputSimpleClic { ipx800="myipx:I08:d>O02" } +Switch InputSimpleClic { ipx800="myipx:I08:d", milight="m1;3" } +Switch InputDoubleClic { ipx800="myipx:I08:D", milight="m1;9" } +Switch InputDoubleClic2 { ipx800="myipx:I08:D>myipx2:O03" } +Switch InputDoubleClic3 { ipx800="myipx:I08:v>O04" } +Switch Output2 { ipx800="myipx:O02" } +Switch Output3 { ipx800="myipx:O03" } +Switch Output9 { ipx800="myipx2:O01" } + +Switch InputToOuput {ipx800="myipx:I08>O01"} + +Switch Mirror {ipx800="myipx:I06:m>O06" } + +Number PowerSimple {ipx800="myipx:C01"} +Number PowerAverage {ipx800="myipx:C01:a:1:m"} +``` + +## Architecture & developpment choices + +The goal of this binding is to connect openHAB to ipx800 while provinding extra functionnality to like double click (double change of input state will change the state of an openhab item), long press (long change of input), average power counter (count the average of pulse on a specified input)... + +To do this two things are needed : + +* Keep a cache of ipx800 device state -> Because the change of an ipx800 input doesn't directly change the state of an openhab item state. + +* Keep the bindingProvider configured with openhab item configuration -> When you change an ipx800 input, the provider need to know what kind of item is configured on openhab + +Ex: + +A double click item is configured on an input. + +A change occur on this input, the provider will just change its internal state to wait for the next clic +On the second change, the provider will change the state of the openhab item + +In the current architecture, for each openhab item setup in conf file (Ex : Switch InputDoubleClic { ipx800="ipx1:I02:D"}), an ipx800Item is configured in the binding (in this case : Ipx800DoubleClic). +To be able to handle multiple items on the same ipx800 input port, i add the handler layer, so each item is linked to these handlers. This layer can for instance handle SimpleClic, DoubleClic and virtual dimmer connected on the same ipx800 input port (Ipx800HandlerMulti). + +These features (double item state change, virtual dimmer,...) could have been implemented as rules, but it's easier to only configure an item. diff --git a/bundles/org.openhab.binding.gce/pom.xml b/bundles/org.openhab.binding.gce/pom.xml new file mode 100644 index 0000000000000..5d3717ab2a10e --- /dev/null +++ b/bundles/org.openhab.binding.gce/pom.xml @@ -0,0 +1,16 @@ + + + + 4.0.0 + + + org.openhab.addons.bundles + org.openhab.addons.reactor.bundles + 2.5.9-SNAPSHOT + + + org.openhab.binding.gce + + openHAB Add-ons :: Bundles :: GCE Binding + + diff --git a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/GCEBindingConstants.java b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/GCEBindingConstants.java new file mode 100644 index 0000000000000..20da0dccfebd8 --- /dev/null +++ b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/GCEBindingConstants.java @@ -0,0 +1,52 @@ +/** + * Copyright (c) 2010-2019 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 java.util.Collections; +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.smarthome.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"; + + // List of Bridge Type UIDs + public static final ThingTypeUID IPXV3_THING_TYPE = new ThingTypeUID(BINDING_ID, "ipx800v3"); + + // Module Properties + public static final String DIGITAL_INPUT = "I"; + public static final String ANALOG_INPUT = "A"; + public static final String COUNTER = "C"; + public static final String RELAY_OUTPUT = "O"; + + public static final String LAST_STATE_DURATION_CHANNEL_NAME = "duration"; + public static final String CHANNEL_TYPE_PUSH_BUTTON_TRIGGER = "pushButtonTrigger"; + + 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"; + + // List of adressable things + public static final Set SUPPORTED_THING_TYPES_UIDS = Collections.singleton(IPXV3_THING_TYPE); +} diff --git a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/GCEHandlerFactory.java b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/GCEHandlerFactory.java new file mode 100644 index 0000000000000..2d5e3c7b79f9a --- /dev/null +++ b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/GCEHandlerFactory.java @@ -0,0 +1,54 @@ +/** + * Copyright (c) 2010-2019 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.*; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.smarthome.core.thing.Thing; +import org.eclipse.smarthome.core.thing.ThingTypeUID; +import org.eclipse.smarthome.core.thing.binding.BaseThingHandlerFactory; +import org.eclipse.smarthome.core.thing.binding.ThingHandler; +import org.eclipse.smarthome.core.thing.binding.ThingHandlerFactory; +import org.openhab.binding.gce.internal.handler.Ipx800v3Handler; +import org.osgi.service.component.annotations.Component; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link GCEHandlerFactory} is responsible for creating things and + * thing handlers. + * + * @author Gaël L'hopital - Initial Implementation + */ +@NonNullByDefault +@Component(service = ThingHandlerFactory.class, configurationPid = "binding.gce") +public class GCEHandlerFactory extends BaseThingHandlerFactory { + private Logger logger = LoggerFactory.getLogger(GCEHandlerFactory.class); + + @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(IPXV3_THING_TYPE)) { + return new Ipx800v3Handler(thing); + } + logger.warn("ThingHandler not found for {}", thing.getThingTypeUID()); + return null; + } +} diff --git a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/action/IIpx800Actions.java b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/action/IIpx800Actions.java new file mode 100644 index 0000000000000..36d27bcc5cb57 --- /dev/null +++ b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/action/IIpx800Actions.java @@ -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.action; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * 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(int counter); + + public void reset(); +} diff --git a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/action/Ipx800Actions.java b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/action/Ipx800Actions.java new file mode 100644 index 0000000000000..7f54d2e82a91e --- /dev/null +++ b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/action/Ipx800Actions.java @@ -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.eclipse.smarthome.core.thing.binding.ThingActions; +import org.eclipse.smarthome.core.thing.binding.ThingActionsScope; +import org.eclipse.smarthome.core.thing.binding.ThingHandler; +import org.openhab.binding.gce.internal.handler.Ipx800v3Handler; +import org.openhab.core.automation.annotation.ActionInput; +import org.openhab.core.automation.annotation.RuleAction; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {Ipx800Actions } defines rule actions for the GCE binding. + *

+ * Note:The static method invokeMethodOf handles the case where + * the test actions instanceof AstroActions 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 actions 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") int counter) { + logger.debug("IPX800 action 'resetCounter' called"); + Ipx800v3Handler theHandler = this.handler; + if (theHandler != null) { + theHandler.resetCounter(counter); + } else { + logger.info("IPX800 Action service ThingHandler is null!"); + } + } + + @Override + @RuleAction(label = "GCE : Reset PLC", description = "Restarts the IPX800") + public void reset() { + logger.debug("IPX800 action 'reset' called"); + Ipx800v3Handler theHandler = this.handler; + if (theHandler != null) { + theHandler.reset(); + } else { + logger.info("IPX800 Action service ThingHandler is null!"); + } + } + + public static void resetCounter(@Nullable ThingActions actions, int counter) { + invokeMethodOf(actions).resetCounter(counter); + } + + public static void reset(@Nullable ThingActions actions) { + invokeMethodOf(actions).reset(); + } + + 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"); + } + +} diff --git a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/config/AnalogInputConfiguration.java b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/config/AnalogInputConfiguration.java new file mode 100644 index 0000000000000..c5521adb1b963 --- /dev/null +++ b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/config/AnalogInputConfiguration.java @@ -0,0 +1,26 @@ +/** + * Copyright (c) 2010-2019 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 Implementation + */ +@NonNullByDefault +public class AnalogInputConfiguration extends CounterConfiguration { + public Long histeresis = 0l; +} diff --git a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/config/CounterConfiguration.java b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/config/CounterConfiguration.java new file mode 100644 index 0000000000000..ad447e718e725 --- /dev/null +++ b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/config/CounterConfiguration.java @@ -0,0 +1,27 @@ +/** + * Copyright (c) 2010-2019 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.eclipse.smarthome.config.core.Configuration; + +/** + * The {@link CounterConfiguration} class holds configuration informations of + * an ipx800 counter. + * + * @author Gaël L'hopital - Initial Implementation + */ +@NonNullByDefault +public class CounterConfiguration extends Configuration { + public int pullFrequency = 5000; +} diff --git a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/config/DigitalInputConfiguration.java b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/config/DigitalInputConfiguration.java new file mode 100644 index 0000000000000..9f86ac0de0fb4 --- /dev/null +++ b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/config/DigitalInputConfiguration.java @@ -0,0 +1,30 @@ +/** + * Copyright (c) 2010-2019 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.eclipse.smarthome.config.core.Configuration; + +/** + * The {@link DigitalInputConfiguration} class holds configuration informations of + * an ipx800 Digital Input port. + * + * @author Gaël L'hopital - Initial Implementation + */ +@NonNullByDefault +public class DigitalInputConfiguration extends Configuration { + public long debouncePeriod = 0; + public long longPressTime = 0; + public long pulsePeriod = 0; + public long pulseTimeout = 0; +} diff --git a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/config/Ipx800Configuration.java b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/config/Ipx800Configuration.java new file mode 100644 index 0000000000000..5e8dd0608c644 --- /dev/null +++ b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/config/Ipx800Configuration.java @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2010-2019 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 implementation + */ +@NonNullByDefault +public class Ipx800Configuration { + public String hostname = ""; + public int portNumber = 9870; + public int reconnectTimeout = 5000; + public int sendTimeout = 1000; + public int maxKeepAliveFailure = 1; + public int keepAliveTimeout = 30000; +} diff --git a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/config/RelayOutputConfiguration.java b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/config/RelayOutputConfiguration.java new file mode 100644 index 0000000000000..2417f8d433c1e --- /dev/null +++ b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/config/RelayOutputConfiguration.java @@ -0,0 +1,27 @@ +/** + * Copyright (c) 2010-2019 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.eclipse.smarthome.config.core.Configuration; + +/** + * The {@link RelayOutputConfiguration} class holds configuration informations of + * an ipx800 relay output. + * + * @author Gaël L'hopital - Initial Implementation + */ +@NonNullByDefault +public class RelayOutputConfiguration extends Configuration { + public Boolean pulse = Boolean.FALSE; +} diff --git a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800DeviceConnector.java b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800DeviceConnector.java new file mode 100644 index 0000000000000..83d47b35f8ff4 --- /dev/null +++ b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800DeviceConnector.java @@ -0,0 +1,171 @@ +/** + * Copyright (c) 2010-2019 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 java.util.Optional; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.gce.internal.config.Ipx800Configuration; +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 on OH1 + * @author Gaël L'hopital - Ported and adapted for OH2 + */ +@NonNullByDefault +public class Ipx800DeviceConnector extends Thread { + private static final Logger logger = LoggerFactory.getLogger(Ipx800DeviceConnector.class); + private final static String ENDL = "\r\n"; + + private final Ipx800Configuration config; + private Optional parser = Optional.empty(); + + private boolean interrupted = false; + private boolean connected = false; + + private @NonNullByDefault({}) Socket client; + private @NonNullByDefault({}) BufferedReader in; + private @NonNullByDefault({}) PrintWriter out; + + private int failedKeepalive = 0; + private boolean waitingKeepaliveResponse = false; + + /** + * + * @param config + */ + public Ipx800DeviceConnector(Ipx800Configuration config) { + this.config = config; + } + + 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 {}:{}...", config.hostname, config.portNumber); + client = new Socket(config.hostname, config.portNumber); + client.setSoTimeout(config.keepAliveTimeout); + client.getInputStream().skip(client.getInputStream().available()); + in = new BufferedReader(new InputStreamReader(client.getInputStream())); + out = new PrintWriter(client.getOutputStream(), true); + connected = true; + logger.debug("Connected to {}:{}", config.hostname, config.portNumber); + } + + /** + * Disconnect the device + */ + private void disconnect() { + if (connected) { + logger.debug("Disconnecting"); + try { + client.close(); + } catch (IOException e) { + logger.warn("Unable to disconnect {}", e.getMessage()); + } + connected = false; + logger.debug("Disconnected"); + } + } + + /** + * Stop the device thread + */ + public void destroyAndExit() { + interrupted = true; + 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 (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() { + interrupted = false; + while (!interrupted) { + try { + waitingKeepaliveResponse = false; + failedKeepalive = 0; + connect(); + while (!interrupted) { + if (failedKeepalive > config.maxKeepAliveFailure) { + throw new IOException("Max keep alive attempts has been reached"); + } + try { + String command = in.readLine(); + waitingKeepaliveResponse = false; + parser.ifPresent(parser -> parser.unsollicitedUpdate(command)); + } catch (SocketTimeoutException e) { + handleException(e); + } + } + disconnect(); + } catch (IOException e) { + handleException(e); + } + try { + Thread.sleep(config.reconnectTimeout); + } catch (InterruptedException e) { + handleException(e); + } + } + } + + private void handleException(Exception e) { + if (e instanceof SocketTimeoutException) { + sendKeepalive(); + return; + } else if (e instanceof IOException) { + logger.info("Communication error : '{}', will retry in {} ms", e.getMessage(), config.reconnectTimeout); + } + parser.ifPresent(parser -> parser.errorOccurred(e)); + logger.debug(e.getMessage()); + } + + public void setParser(Ipx800MessageParser parser) { + this.parser = Optional.of(parser); + } +} diff --git a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800EventListener.java b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800EventListener.java new file mode 100644 index 0000000000000..6e5e19a8de015 --- /dev/null +++ b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800EventListener.java @@ -0,0 +1,40 @@ +/** + * Copyright (c) 2010-2019 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); + +} diff --git a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800MessageParser.java b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800MessageParser.java new file mode 100644 index 0000000000000..c43a4262c176b --- /dev/null +++ b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800MessageParser.java @@ -0,0 +1,167 @@ +/** + * Copyright (c) 2010-2019 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.util.Optional; +import java.util.regex.Pattern; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@NonNullByDefault +public class Ipx800MessageParser { + private static final Logger logger = LoggerFactory.getLogger(Ipx800DeviceConnector.class); + 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 String expectedResponse = ""; + private final Ipx800DeviceConnector connector; + + private final Optional listener; + + public Ipx800MessageParser(Ipx800DeviceConnector connector, @Nullable Ipx800EventListener ipx800EventListener) { + this.connector = connector; + connector.setParser(this); + if (ipx800EventListener != null) { + this.listener = Optional.of(ipx800EventListener); + } else { + this.listener = Optional.empty(); + } + } + + /** + * 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); + int port = Integer.parseInt(targetPort); + String command = String.format("Set%02d%d%s", port, targetValue, pulse ? "p" : ""); + connector.send(command); + } + + /** + * + * @param data + */ + public void unsollicitedUpdate(String data) { + if (IO_PATTERN.matcher(data).matches()) { + String portKind = "GetOutputs".equalsIgnoreCase(expectedResponse) ? RELAY_OUTPUT + : "GetInputs".equalsIgnoreCase(expectedResponse) ? DIGITAL_INPUT : null; + if (portKind != null) { + for (int count = 0; count < data.length(); count++) { + setStatus(portKind + String.valueOf(count + 1), new Double(data.charAt(count) - '0')); + } + } + } else if (VALIDATION_PATTERN.matcher(data).matches()) { + for (String status : data.split("&")) { + String statusPart[] = status.split("="); + String portKind = statusPart[0].substring(0, 1); + int portNumShift = 0; + switch (portKind) { + case DIGITAL_INPUT: + case RELAY_OUTPUT: { + for (int count = 0; count < statusPart[1].length(); count++) { + setStatus(portKind + String.valueOf(count + 1), + new Double(statusPart[1].charAt(count) - '0')); + } + break; + } + case COUNTER: + portNumShift = -1; // Align counters on 1 based array + case ANALOG_INPUT: { + int portNumber = Integer.parseInt(statusPart[0].substring(1)); + setStatus(portKind + String.valueOf(portNumber + portNumShift + 1), + Double.parseDouble(statusPart[1])); + } + } + } + } else if (!"".equals(expectedResponse)) { + setStatus(expectedResponse, Double.parseDouble(data)); + } + + expectedResponse = ""; + } + + private void setStatus(String port, Double value) { + logger.debug("Received {} : {}", port, value.toString()); + listener.ifPresent(l -> l.dataReceived(port, value)); + } + + public void setExpectedResponse(String expectedResponse) { + if (expectedResponse.endsWith("s")) { // GetInputs or GetOutputs + this.expectedResponse = expectedResponse; + } else { // GetAnx or GetCountx + this.expectedResponse = expectedResponse.replaceAll("GetAn", ANALOG_INPUT).replaceAll("GetCount", COUNTER) + .replaceAll("GetIn", DIGITAL_INPUT).replaceAll("GetOut", RELAY_OUTPUT); + } + } + + /** + * 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)); + try { + Thread.sleep(200); + String request = String.format("GetCount%d", targetCounter); + setExpectedResponse(request); + connector.send(request); + } catch (InterruptedException e) { + errorOccurred(e); + } + } + + public void errorOccurred(Exception e) { + logger.warn("Error received from connector : {}", e.getMessage()); + listener.ifPresent(l -> l.errorOccurred(e)); + } + + public synchronized void getValue(String channelId) { + logger.debug("Requested value for {}", channelId); + if ("".equals(expectedResponse)) { // Do not send a request is something is expected + String[] elements = channelId.split("#"); + String command = "Get" + elements[0].replaceAll(ANALOG_INPUT, "An").replaceAll(COUNTER, "Count") + .replaceAll(DIGITAL_INPUT, "An").replaceAll(RELAY_OUTPUT, "Out") + elements[1]; + setExpectedResponse(command); + connector.send(command); + } + } + + public void getOutputs() { + String command = "GetOutputs"; + setExpectedResponse(command); + connector.send(command); + } + + public void getInputs() { + String command = "GetInputs"; + setExpectedResponse(command); + connector.send(command); + } + + public void reset() { + connector.send("Reset"); + } +} diff --git a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800v3Handler.java b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800v3Handler.java new file mode 100644 index 0000000000000..eb067d9fff9c2 --- /dev/null +++ b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800v3Handler.java @@ -0,0 +1,273 @@ +/** + * Copyright (c) 2010-2019 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.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.smarthome.config.core.Configuration; +import org.eclipse.smarthome.core.library.types.DecimalType; +import org.eclipse.smarthome.core.library.types.OnOffType; +import org.eclipse.smarthome.core.library.types.OpenClosedType; +import org.eclipse.smarthome.core.library.types.QuantityType; +import org.eclipse.smarthome.core.library.unit.SmartHomeUnits; +import org.eclipse.smarthome.core.thing.Channel; +import org.eclipse.smarthome.core.thing.ChannelUID; +import org.eclipse.smarthome.core.thing.Thing; +import org.eclipse.smarthome.core.thing.ThingStatus; +import org.eclipse.smarthome.core.thing.ThingStatusDetail; +import org.eclipse.smarthome.core.thing.binding.BaseThingHandler; +import org.eclipse.smarthome.core.types.Command; +import org.eclipse.smarthome.core.types.State; +import org.eclipse.smarthome.core.types.UnDefType; +import org.openhab.binding.gce.internal.config.AnalogInputConfiguration; +import org.openhab.binding.gce.internal.config.CounterConfiguration; +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.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 final Logger logger = LoggerFactory.getLogger(Ipx800v3Handler.class); + + private @NonNullByDefault({}) Ipx800Configuration configuration; + private @NonNullByDefault({}) Ipx800DeviceConnector connector; + private Optional parser = Optional.empty(); + + private final Map 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 + CHANNEL_TYPE_PUSH_BUTTON_TRIGGER; + } + + @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() { + updateStatus(ThingStatus.OFFLINE); + + configuration = getConfigAs(Ipx800Configuration.class); + + logger.debug("Initializing IPX800 handler for uid '{}'", getThing().getUID()); + scheduler.execute(this::doInitialization); + } + + @Override + public void dispose() { + if (connector != null) { + connector.destroyAndExit(); + } + super.dispose(); + } + + protected void doInitialization() { + logger.debug("Initialize IPX800 input blocks handler."); + + connector = new Ipx800DeviceConnector(configuration); + parser = Optional.of(new Ipx800MessageParser(connector, this)); + updateStatus(ThingStatus.ONLINE); + connector.run(); + } + + @Override + public void errorOccurred(Exception e) { + logger.warn(e.getMessage()); + if (e instanceof InterruptedException) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); + } + } + + private @Nullable Channel getChannelForPort(String port) { + String portKind = port.substring(0, 1); + String portNum = port.replace(portKind, ""); + return thing.getChannel(portKind + "#" + portNum); + } + + @Override + public void dataReceived(String port, Double value) { + Channel channel = getChannelForPort(port); + if (channel != null) { + PortData portData = portDatas.get(channel.getUID().getId()); + if (portData != null) { + if (value.equals(portData.getValue())) { + return; + } + + ZonedDateTime now = ZonedDateTime.now(ZoneId.systemDefault()); + long sinceLastChange = Duration.between(portData.getTimestamp(), now).toMillis(); + Configuration configuration = channel.getConfiguration(); + State state = UnDefType.UNDEF; + String groupId = channel.getUID().getGroupId(); + if (groupId != null) { + switch (groupId) { + case COUNTER: + state = new DecimalType(value); + break; + case ANALOG_INPUT: + AnalogInputConfiguration config = configuration.as(AnalogInputConfiguration.class); + long histeresis = config.histeresis / 2; + if (portData.isInitializing() || value > portData.getValue() + histeresis + || value < portData.getValue() - histeresis) { + state = new DecimalType(value); + } else { + return; + } + break; + case DIGITAL_INPUT: + DigitalInputConfiguration config2 = configuration.as(DigitalInputConfiguration.class); + if (config2.debouncePeriod != 0 && now.isBefore( + portData.getTimestamp().plus(config2.debouncePeriod, ChronoUnit.MILLIS))) { + return; + } + portData.cancelPulsing(); + if (value == 1) { + state = OpenClosedType.CLOSED; + if (portData.getValue() != -1) { + triggerPushButtonChannel(channel, EVENT_PRESSED); + } + if (config2.longPressTime != 0 && !portData.isInitializing()) { + scheduler.schedule(new LongPressEvaluator(channel, port, portData), + config2.longPressTime, TimeUnit.MILLISECONDS); + } else if (config2.pulsePeriod != 0) { + portData.setPulsing(scheduler.scheduleAtFixedRate(() -> { + triggerPushButtonChannel(channel, EVENT_PULSE); + }, config2.pulsePeriod, config2.pulsePeriod, TimeUnit.MILLISECONDS)); + if (config2.pulseTimeout != 0) { + scheduler.schedule(() -> { + portData.cancelPulsing(); + }, config2.pulseTimeout, TimeUnit.MILLISECONDS); + } + } + } else { + state = OpenClosedType.OPEN; + if (!portData.isInitializing()) { + triggerPushButtonChannel(channel, EVENT_RELEASED); + if (config2.longPressTime != 0 && sinceLastChange < config2.longPressTime) { + triggerPushButtonChannel(channel, EVENT_SHORT_PRESS); + } + } + } + break; + case RELAY_OUTPUT: + state = value == 1 ? OnOffType.ON : OnOffType.OFF; + break; + } + } + updateState(channel.getUID().getId(), state); + if (!portData.isInitializing()) { + updateState(channel.getUID().getId() + PROPERTY_SEPARATOR + LAST_STATE_DURATION_CHANNEL_NAME, + 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 + CHANNEL_TYPE_PUSH_BUTTON_TRIGGER, event); + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + logger.debug("Received channel: {}, command: {}", channelUID, command); + + String channelName = channelUID.getIdWithoutGroup(); + Channel channel = thing.getChannel(channelUID.getId()); + if (channelName.chars().allMatch(Character::isDigit) && RELAY_OUTPUT.equalsIgnoreCase(channelUID.getGroupId()) + && command instanceof OnOffType && channel != null) { + RelayOutputConfiguration config = channel.getConfiguration().as(RelayOutputConfiguration.class); + parser.ifPresent(p -> p.setOutput(channelName, (OnOffType) command == OnOffType.ON ? 1 : 0, config.pulse)); + return; + } + logger.info("Can not handle command '{}' on channel '{}'", command, channelUID); + } + + @Override + public void channelLinked(ChannelUID channelUID) { + logger.debug("channelLinked: {}", channelUID); + final String channelId = channelUID.getId(); + if (channelUID.getIdWithoutGroup().chars().allMatch(Character::isDigit)) { + Channel channel = thing.getChannel(channelUID); + if (channel != null) { + Configuration configuration = channel.getConfiguration(); + PortData data = new PortData(); + if (configuration.get("pullFrequency") != null) { + int pullFrequency = configuration.as(CounterConfiguration.class).pullFrequency; + data.setPullJob(scheduler.scheduleAtFixedRate(() -> { + parser.ifPresent(p -> p.getValue(channelId)); + }, pullFrequency, pullFrequency, TimeUnit.MILLISECONDS)); + } + portDatas.put(channelId, data); + } + } + } + + @Override + public void channelUnlinked(ChannelUID channelUID) { + super.channelUnlinked(channelUID); + portDatas.remove(channelUID.getId()); + } + + public void resetCounter(int counter) { + parser.ifPresent(p -> p.resetCounter(counter)); + } + + public void reset() { + parser.ifPresent(Ipx800MessageParser::reset); + } + +} diff --git a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/PortData.java b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/PortData.java new file mode 100644 index 0000000000000..eb903d608ae3e --- /dev/null +++ b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/PortData.java @@ -0,0 +1,77 @@ +/** + * 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.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; + private @Nullable ScheduledFuture pullJob; + + public void cancelPulsing() { + if (pulsing != null) { + pulsing.cancel(true); + } + pulsing = null; + } + + @Override + protected void finalize() { + cancelPulsing(); + if (pullJob != null) { + pullJob.cancel(true); + } + pullJob = null; + } + + public void setData(double value, ZonedDateTime timestamp) { + this.value = value; + this.timestamp = timestamp; + } + + public Double getValue() { + return value; + } + + public ZonedDateTime getTimestamp() { + return timestamp; + } + + public @Nullable ScheduledFuture getPulsing() { + return pulsing; + } + + public void setPulsing(ScheduledFuture pulsing) { + this.pulsing = pulsing; + } + + public void setPullJob(ScheduledFuture pullJob) { + this.pullJob = pullJob; + } + + public boolean isInitializing() { + return value == -1; + } +} diff --git a/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/binding/binding.xml b/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/binding/binding.xml new file mode 100644 index 0000000000000..df26f91874677 --- /dev/null +++ b/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/binding/binding.xml @@ -0,0 +1,11 @@ + + + + GCE Electronics Binding + Provides access to IPX800 PLC build by GCE. + Gaël L'hopital + + diff --git a/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/config/ipx800v3Config.xml b/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/config/ipx800v3Config.xml new file mode 100644 index 0000000000000..49644483bd25a --- /dev/null +++ b/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/config/ipx800v3Config.xml @@ -0,0 +1,46 @@ + + + + + + network-address + + Network/IP address of the IPX800 without http(s) prefix + + + + TCP client connection port + 9870 + true + + + + Time before reconnecting in case of failure (in milliseconds). + 5000 + true + + + + Time before assuming send failure (in milliseconds). + 1000 + true + + + + Max keep alive failures before reconnecting (tentatives, 1-10) + 1 + true + + + + Max keep timeout (in milliseconds) + 30000 + true + + + diff --git a/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/thing/channels.xml b/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/thing/channels.xml new file mode 100644 index 0000000000000..77ed48c9f8615 --- /dev/null +++ b/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/thing/channels.xml @@ -0,0 +1,212 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Contact + + Contact + + + + + 0 + + + + Long press time in milliseconds. + 0 + + + + Pulse period in milliseconds. + 0 + + + + Maximum period for sending pulses in milliseconds. + 0 + + + + + + Switch + + + + + false + + + + + + Number + + + + + Delay for pulling Analog and Counters info (in milliseconds). + 5000 + + + + + + Number + + + + + 0 + + + + Delay for pulling Analog and Counters info (in milliseconds). + 5000 + + + + + + Number:Time + + Duration of previous state before state change. + + + + + trigger + + + + + + + + + + + + + diff --git a/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/thing/ipx800v3.xml b/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/thing/ipx800v3.xml new file mode 100644 index 0000000000000..16a778fa78590 --- /dev/null +++ b/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/thing/ipx800v3.xml @@ -0,0 +1,19 @@ + + + + + + The GCE IPX800v3 device + + + + + + + + + + + diff --git a/bundles/org.openhab.binding.gce/src/test/java/org/openhab/binding/gce/internal/test/Ipx800DeviceConnectorTest.java b/bundles/org.openhab.binding.gce/src/test/java/org/openhab/binding/gce/internal/test/Ipx800DeviceConnectorTest.java new file mode 100644 index 0000000000000..7ab2ae56e3d93 --- /dev/null +++ b/bundles/org.openhab.binding.gce/src/test/java/org/openhab/binding/gce/internal/test/Ipx800DeviceConnectorTest.java @@ -0,0 +1,67 @@ +/** + * Copyright (c) 2010-2019 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.test; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.Test; +import org.openhab.binding.gce.internal.config.Ipx800Configuration; +import org.openhab.binding.gce.internal.handler.Ipx800DeviceConnector; +import org.openhab.binding.gce.internal.handler.Ipx800MessageParser; + +/** + * + * @author Seebag + * @since 1.8.0 + * + */ +@NonNullByDefault +public class Ipx800DeviceConnectorTest { + @Test + public void validateDeviceListModel() { + // Example of + // I=00000000000000000000000000000000&O=10000000000000000000000000000000&\ + // A0=0&A1=0&A2=0&A3=0&A4=0&A5=0&A6=0&A7=0&A8=0&A9=0&A10=0&A11=0&A12=0&A13=0&A14=0&A15=0&C1=47&C2=0&C3=0&C4=0&C5=0&C6=0&C7=0&C8=0 + // Command : + // I=01000000000000000000000000000000&O=01000000000000000000000000000000&A0=0&A1=0&A2=0&A3=0&A4=0&A5=0&A6=0&A7=0&A8=0&A9=0&A10=0&A11=0&A12=0&A13=0&A14=0&A15=0&C1=2064&C2=1&C3=3&C4=4&C5=5&C6=6&C7=7&C8=8 + // Command : + // I=01000000000000000000000000000000&O=00000000000000000000000000000000&A0=0&A1=0&A2=0&A3=0&A4=0&A5=0&A6=0&A7=0&A8=0&A9=0&A10=0&A11=0&A12=0&A13=0&A14=0&A15=0&C1=2064&C2=1&C3=3&C4=4&C5=5&C6=6&C7=7&C8=8 + // Command : + // I=01000000000000000000000000000000&O=00000001000000000000000000000000&A0=0&A1=0&A2=0&A3=0&A4=0&A5=0&A6=0&A7=0&A8=0&A9=0&A10=0&A11=0&A12=0&A13=0&A14=0&A15=0&C1=2064&C2=1&C3=3&C4=4&C5=5&C6=6&C7=7&C8=8 + // Command : + // I=00000000000000000000000000000000&O=00000001000000000000000000000000&A0=0&A1=0&A2=0&A3=0&A4=0&A5=0&A6=0&A7=0&A8=0&A9=0&A10=0&A11=0&A12=0&A13=0&A14=0&A15=0&C1=2064&C2=1&C3=3&C4=4&C5=5&C6=6&C7=7&C8=8 + // Command : + // I=01000000000000000000000000000000&O=00000000000000000000000000000000&A0=0&A1=0&A2=0&A3=0&A4=0&A5=0&A6=0&A7=0&A8=0&A9=0&A10=0&A11=0&A12=0&A13=0&A14=0&A15=0&C1=2064&C2=1&C3=3&C4=4&C5=5&C6=6&C7=7&C8=8 + // Command : + // I=00000000000000000000000000000000&O=00000000000000000000000000000000&A0=0&A1=0&A2=0&A3=0&A4=0&A5=0&A6=0&A7=0&A8=0&A9=0&A10=0&A11=0&A12=0&A13=0&A14=0&A15=0&C1=2064&C2=1&C3=3&C4=4&C5=5&C6=6&C7=7&C8=8 + // Command : + // I=10000000000000000000000000000000&O=00000000000000000000000000000000&A0=0&A1=0&A2=0&A3=0&A4=0&A5=0&A6=0&A7=0&A8=0&A9=0&A10=0&A11=0&A12=0&A13=0&A14=0&A15=0&C1=2064&C2=1&C3=3&C4=4&C5=5&C6=6&C7=7&C8=8 + + Ipx800Configuration configuration = new Ipx800Configuration(); + Ipx800DeviceConnector connector = new Ipx800DeviceConnector(configuration); + Ipx800MessageParser parser = new Ipx800MessageParser(connector, null); + parser.setExpectedResponse("GetOutputs"); + parser.unsollicitedUpdate("01010010100101010101010101010101"); + parser.setExpectedResponse("GetInputs"); + parser.unsollicitedUpdate("01000000234005670000000000000000"); + parser.unsollicitedUpdate( + "I=01000000000000000000000000000000&O=01000000000000000000000000000000&A0=0&A1=0&A2=0&A3=0&A4=0&A5=0&A6=0&A7=0&A8=0&A9=0&A10=0&A11=0&A12=0&A13=0&A14=0&A15=0&C1=2064&C2=1&C3=3&C4=4&C5=5&C6=6&C7=7&C8=8"); + parser.setExpectedResponse("GetAn1"); + parser.unsollicitedUpdate("234"); + parser.setExpectedResponse("GetCount12"); + parser.unsollicitedUpdate("4294967295"); // Test maximum value + parser.setExpectedResponse("GetIn5"); + parser.unsollicitedUpdate("1"); + parser.setExpectedResponse("GetOut3"); + parser.unsollicitedUpdate("0"); + } +} From a18b8d8db6fa07907c2330eb2d0b94a657e28c0e Mon Sep 17 00:00:00 2001 From: clinique Date: Thu, 27 Aug 2020 19:31:57 +0200 Subject: [PATCH 02/17] Adding missing files Signed-off-by: clinique --- .../org.openhab.binding.gce/src/main/feature/feature.xml | 9 +++++++++ bundles/pom.xml | 1 + 2 files changed, 10 insertions(+) create mode 100644 bundles/org.openhab.binding.gce/src/main/feature/feature.xml diff --git a/bundles/org.openhab.binding.gce/src/main/feature/feature.xml b/bundles/org.openhab.binding.gce/src/main/feature/feature.xml new file mode 100644 index 0000000000000..99398041f8d39 --- /dev/null +++ b/bundles/org.openhab.binding.gce/src/main/feature/feature.xml @@ -0,0 +1,9 @@ + + + mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features + + + openhab-runtime-base + mvn:org.openhab.addons.bundles/org.openhab.binding.gce/${project.version} + + diff --git a/bundles/pom.xml b/bundles/pom.xml index 18e000ff456d1..7c97447912bc1 100644 --- a/bundles/pom.xml +++ b/bundles/pom.xml @@ -103,6 +103,7 @@ org.openhab.binding.fsinternetradio org.openhab.binding.ftpupload org.openhab.binding.gardena + org.openhab.binding.gce org.openhab.binding.goecharger org.openhab.binding.globalcache org.openhab.binding.gpstracker From 610cfc08023379e756b4399725742b612ac5c9b3 Mon Sep 17 00:00:00 2001 From: clinique Date: Fri, 28 Aug 2020 14:01:29 +0200 Subject: [PATCH 03/17] Code review and adressing travis findings Signed-off-by: clinique --- .../src/main/feature/feature.xml | 2 +- .../gce/internal/GCEBindingConstants.java | 2 +- .../gce/internal/GCEHandlerFactory.java | 6 +- .../gce/internal/action/Ipx800Actions.java | 4 +- .../config/AnalogInputConfiguration.java | 4 +- .../internal/config/CounterConfiguration.java | 2 +- .../config/DigitalInputConfiguration.java | 2 +- .../internal/config/Ipx800Configuration.java | 2 +- .../config/RelayOutputConfiguration.java | 4 +- .../handler/Ipx800DeviceConnector.java | 11 ++-- .../internal/handler/Ipx800EventListener.java | 4 +- .../internal/handler/Ipx800MessageParser.java | 11 +++- .../gce/internal/handler/Ipx800v3Handler.java | 57 +++++++++++-------- .../gce/internal/handler/PortData.java | 5 +- .../ESH-INF/config/ipx800v3Config.xml | 2 +- .../main/resources/ESH-INF/thing/channels.xml | 4 +- .../test/Ipx800DeviceConnectorTest.java | 4 +- 17 files changed, 66 insertions(+), 60 deletions(-) diff --git a/bundles/org.openhab.binding.gce/src/main/feature/feature.xml b/bundles/org.openhab.binding.gce/src/main/feature/feature.xml index 99398041f8d39..57f2cb8f071cc 100644 --- a/bundles/org.openhab.binding.gce/src/main/feature/feature.xml +++ b/bundles/org.openhab.binding.gce/src/main/feature/feature.xml @@ -2,7 +2,7 @@ mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features - + openhab-runtime-base mvn:org.openhab.addons.bundles/org.openhab.binding.gce/${project.version} diff --git a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/GCEBindingConstants.java b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/GCEBindingConstants.java index 20da0dccfebd8..84c43365058b7 100644 --- a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/GCEBindingConstants.java +++ b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/GCEBindingConstants.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2019 Contributors to the openHAB project + * Copyright (c) 2010-2020 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/GCEHandlerFactory.java b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/GCEHandlerFactory.java index 2d5e3c7b79f9a..a2064be010b61 100644 --- a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/GCEHandlerFactory.java +++ b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/GCEHandlerFactory.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2019 Contributors to the openHAB project + * Copyright (c) 2010-2020 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. @@ -23,8 +23,6 @@ import org.eclipse.smarthome.core.thing.binding.ThingHandlerFactory; import org.openhab.binding.gce.internal.handler.Ipx800v3Handler; import org.osgi.service.component.annotations.Component; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * The {@link GCEHandlerFactory} is responsible for creating things and @@ -35,7 +33,6 @@ @NonNullByDefault @Component(service = ThingHandlerFactory.class, configurationPid = "binding.gce") public class GCEHandlerFactory extends BaseThingHandlerFactory { - private Logger logger = LoggerFactory.getLogger(GCEHandlerFactory.class); @Override public boolean supportsThingType(ThingTypeUID thingTypeUID) { @@ -48,7 +45,6 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) { if (thingTypeUID.equals(IPXV3_THING_TYPE)) { return new Ipx800v3Handler(thing); } - logger.warn("ThingHandler not found for {}", thing.getThingTypeUID()); return null; } } diff --git a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/action/Ipx800Actions.java b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/action/Ipx800Actions.java index 7f54d2e82a91e..ae24a7a656d83 100644 --- a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/action/Ipx800Actions.java +++ b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/action/Ipx800Actions.java @@ -68,7 +68,7 @@ public void resetCounter( if (theHandler != null) { theHandler.resetCounter(counter); } else { - logger.info("IPX800 Action service ThingHandler is null!"); + logger.info("Method call resetCounter failed because IPX800 action service ThingHandler is null!"); } } @@ -80,7 +80,7 @@ public void reset() { if (theHandler != null) { theHandler.reset(); } else { - logger.info("IPX800 Action service ThingHandler is null!"); + logger.info("Method call reset failed because IPX800 action service ThingHandler is null!"); } } diff --git a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/config/AnalogInputConfiguration.java b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/config/AnalogInputConfiguration.java index c5521adb1b963..d67088b995d70 100644 --- a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/config/AnalogInputConfiguration.java +++ b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/config/AnalogInputConfiguration.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2019 Contributors to the openHAB project + * Copyright (c) 2010-2020 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. @@ -22,5 +22,5 @@ */ @NonNullByDefault public class AnalogInputConfiguration extends CounterConfiguration { - public Long histeresis = 0l; + public long histeresis; } diff --git a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/config/CounterConfiguration.java b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/config/CounterConfiguration.java index ad447e718e725..e76734aed9df9 100644 --- a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/config/CounterConfiguration.java +++ b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/config/CounterConfiguration.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2019 Contributors to the openHAB project + * Copyright (c) 2010-2020 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/config/DigitalInputConfiguration.java b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/config/DigitalInputConfiguration.java index 9f86ac0de0fb4..7b3e838b3e917 100644 --- a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/config/DigitalInputConfiguration.java +++ b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/config/DigitalInputConfiguration.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2019 Contributors to the openHAB project + * Copyright (c) 2010-2020 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/config/Ipx800Configuration.java b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/config/Ipx800Configuration.java index 5e8dd0608c644..005a648069be0 100644 --- a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/config/Ipx800Configuration.java +++ b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/config/Ipx800Configuration.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2019 Contributors to the openHAB project + * Copyright (c) 2010-2020 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/config/RelayOutputConfiguration.java b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/config/RelayOutputConfiguration.java index 2417f8d433c1e..dee0c7f6e8b17 100644 --- a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/config/RelayOutputConfiguration.java +++ b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/config/RelayOutputConfiguration.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2019 Contributors to the openHAB project + * Copyright (c) 2010-2020 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. @@ -23,5 +23,5 @@ */ @NonNullByDefault public class RelayOutputConfiguration extends Configuration { - public Boolean pulse = Boolean.FALSE; + public boolean pulse; } diff --git a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800DeviceConnector.java b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800DeviceConnector.java index 83d47b35f8ff4..fdeb3379f072f 100644 --- a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800DeviceConnector.java +++ b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800DeviceConnector.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2019 Contributors to the openHAB project + * Copyright (c) 2010-2020 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. @@ -34,7 +34,7 @@ */ @NonNullByDefault public class Ipx800DeviceConnector extends Thread { - private static final Logger logger = LoggerFactory.getLogger(Ipx800DeviceConnector.class); + private final Logger logger = LoggerFactory.getLogger(Ipx800DeviceConnector.class); private final static String ENDL = "\r\n"; private final Ipx800Configuration config; @@ -50,10 +50,6 @@ public class Ipx800DeviceConnector extends Thread { private int failedKeepalive = 0; private boolean waitingKeepaliveResponse = false; - /** - * - * @param config - */ public Ipx800DeviceConnector(Ipx800Configuration config) { this.config = config; } @@ -149,7 +145,8 @@ public void run() { try { Thread.sleep(config.reconnectTimeout); } catch (InterruptedException e) { - handleException(e); + interrupted = true; + Thread.currentThread().interrupt(); } } } diff --git a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800EventListener.java b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800EventListener.java index 6e5e19a8de015..caa7f84aab08a 100644 --- a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800EventListener.java +++ b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800EventListener.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2019 Contributors to the openHAB project + * Copyright (c) 2010-2020 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. @@ -25,7 +25,7 @@ public interface Ipx800EventListener { /** * Procedure for receive data from IPX800 controller. * - * @param port Port (kind and number) receiving update + * @param port Port (kind and number) receiving update * @param value value updated */ void dataReceived(String port, Double value); diff --git a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800MessageParser.java b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800MessageParser.java index c43a4262c176b..388c2bb890c29 100644 --- a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800MessageParser.java +++ b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800MessageParser.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2019 Contributors to the openHAB project + * Copyright (c) 2010-2020 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. @@ -22,9 +22,14 @@ 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 Ipx800MessageParser { - private static final Logger logger = LoggerFactory.getLogger(Ipx800DeviceConnector.class); + private final Logger logger = LoggerFactory.getLogger(Ipx800DeviceConnector.class); 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 @@ -129,7 +134,7 @@ public void resetCounter(int targetCounter) { setExpectedResponse(request); connector.send(request); } catch (InterruptedException e) { - errorOccurred(e); + Thread.currentThread().interrupt(); } } diff --git a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800v3Handler.java b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800v3Handler.java index eb067d9fff9c2..d103defeaa574 100644 --- a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800v3Handler.java +++ b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800v3Handler.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2019 Contributors to the openHAB project + * Copyright (c) 2010-2020 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. @@ -98,7 +98,15 @@ public void initialize() { configuration = getConfigAs(Ipx800Configuration.class); logger.debug("Initializing IPX800 handler for uid '{}'", getThing().getUID()); - scheduler.execute(this::doInitialization); + + connector = new Ipx800DeviceConnector(configuration); + connector.setName("OH-binding-" + getThing().getUID()); + connector.setDaemon(true); + + parser = Optional.of(new Ipx800MessageParser(connector, this)); + + updateStatus(ThingStatus.ONLINE); + connector.start(); } @Override @@ -106,24 +114,19 @@ public void dispose() { if (connector != null) { connector.destroyAndExit(); } - super.dispose(); - } - protected void doInitialization() { - logger.debug("Initialize IPX800 input blocks handler."); + portDatas.values().stream().forEach(portData -> { + if (portData != null) { + portData.destroy(); + } + }); - connector = new Ipx800DeviceConnector(configuration); - parser = Optional.of(new Ipx800MessageParser(connector, this)); - updateStatus(ThingStatus.ONLINE); - connector.run(); + super.dispose(); } @Override public void errorOccurred(Exception e) { - logger.warn(e.getMessage()); - if (e instanceof InterruptedException) { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); - } + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); } private @Nullable Channel getChannelForPort(String port) { @@ -178,13 +181,12 @@ public void dataReceived(String port, Double value) { scheduler.schedule(new LongPressEvaluator(channel, port, portData), config2.longPressTime, TimeUnit.MILLISECONDS); } else if (config2.pulsePeriod != 0) { - portData.setPulsing(scheduler.scheduleAtFixedRate(() -> { + portData.setPulsing(scheduler.scheduleWithFixedDelay(() -> { triggerPushButtonChannel(channel, EVENT_PULSE); }, config2.pulsePeriod, config2.pulsePeriod, TimeUnit.MILLISECONDS)); if (config2.pulseTimeout != 0) { - scheduler.schedule(() -> { - portData.cancelPulsing(); - }, config2.pulseTimeout, TimeUnit.MILLISECONDS); + scheduler.schedule(portData::cancelPulsing, config2.pulseTimeout, + TimeUnit.MILLISECONDS); } } } else { @@ -225,12 +227,12 @@ protected void triggerPushButtonChannel(Channel channel, String event) { public void handleCommand(ChannelUID channelUID, Command command) { logger.debug("Received channel: {}, command: {}", channelUID, command); - String channelName = channelUID.getIdWithoutGroup(); Channel channel = thing.getChannel(channelUID.getId()); - if (channelName.chars().allMatch(Character::isDigit) && RELAY_OUTPUT.equalsIgnoreCase(channelUID.getGroupId()) + if (isValidPortId(channelUID) && RELAY_OUTPUT.equalsIgnoreCase(channelUID.getGroupId()) && command instanceof OnOffType && channel != null) { RelayOutputConfiguration config = channel.getConfiguration().as(RelayOutputConfiguration.class); - parser.ifPresent(p -> p.setOutput(channelName, (OnOffType) command == OnOffType.ON ? 1 : 0, config.pulse)); + parser.ifPresent(p -> p.setOutput(channelUID.getIdWithoutGroup(), + (OnOffType) command == OnOffType.ON ? 1 : 0, config.pulse)); return; } logger.info("Can not handle command '{}' on channel '{}'", command, channelUID); @@ -240,14 +242,14 @@ public void handleCommand(ChannelUID channelUID, Command command) { public void channelLinked(ChannelUID channelUID) { logger.debug("channelLinked: {}", channelUID); final String channelId = channelUID.getId(); - if (channelUID.getIdWithoutGroup().chars().allMatch(Character::isDigit)) { + if (isValidPortId(channelUID)) { Channel channel = thing.getChannel(channelUID); if (channel != null) { Configuration configuration = channel.getConfiguration(); PortData data = new PortData(); if (configuration.get("pullFrequency") != null) { int pullFrequency = configuration.as(CounterConfiguration.class).pullFrequency; - data.setPullJob(scheduler.scheduleAtFixedRate(() -> { + data.setPullJob(scheduler.scheduleWithFixedDelay(() -> { parser.ifPresent(p -> p.getValue(channelId)); }, pullFrequency, pullFrequency, TimeUnit.MILLISECONDS)); } @@ -256,10 +258,17 @@ public void channelLinked(ChannelUID channelUID) { } } + private boolean isValidPortId(ChannelUID channelUID) { + return channelUID.getIdWithoutGroup().chars().allMatch(Character::isDigit); + } + @Override public void channelUnlinked(ChannelUID channelUID) { super.channelUnlinked(channelUID); - portDatas.remove(channelUID.getId()); + PortData portData = portDatas.remove(channelUID.getId()); + if (portData != null) { + portData.destroy(); + } } public void resetCounter(int counter) { diff --git a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/PortData.java b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/PortData.java index eb903d608ae3e..4c962af018409 100644 --- a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/PortData.java +++ b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/PortData.java @@ -37,8 +37,7 @@ public void cancelPulsing() { pulsing = null; } - @Override - protected void finalize() { + public void destroy() { cancelPulsing(); if (pullJob != null) { pullJob.cancel(true); @@ -51,7 +50,7 @@ public void setData(double value, ZonedDateTime timestamp) { this.timestamp = timestamp; } - public Double getValue() { + public double getValue() { return value; } diff --git a/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/config/ipx800v3Config.xml b/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/config/ipx800v3Config.xml index 49644483bd25a..96f90cda84b5a 100644 --- a/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/config/ipx800v3Config.xml +++ b/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/config/ipx800v3Config.xml @@ -11,7 +11,7 @@ Network/IP address of the IPX800 without http(s) prefix - + TCP client connection port 9870 true diff --git a/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/thing/channels.xml b/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/thing/channels.xml index 77ed48c9f8615..82151a0fbc5ed 100644 --- a/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/thing/channels.xml +++ b/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/thing/channels.xml @@ -128,7 +128,7 @@ - + 0 @@ -154,7 +154,7 @@ - + false diff --git a/bundles/org.openhab.binding.gce/src/test/java/org/openhab/binding/gce/internal/test/Ipx800DeviceConnectorTest.java b/bundles/org.openhab.binding.gce/src/test/java/org/openhab/binding/gce/internal/test/Ipx800DeviceConnectorTest.java index 7ab2ae56e3d93..1e9d634899671 100644 --- a/bundles/org.openhab.binding.gce/src/test/java/org/openhab/binding/gce/internal/test/Ipx800DeviceConnectorTest.java +++ b/bundles/org.openhab.binding.gce/src/test/java/org/openhab/binding/gce/internal/test/Ipx800DeviceConnectorTest.java @@ -20,8 +20,8 @@ /** * - * @author Seebag - * @since 1.8.0 + * @author Seebag - Initial contribution on OH1 + * @author Gaël L'hopital - Ported and adapted for OH2 * */ @NonNullByDefault From caf8b0f5cee4351094cd386e36bbb1d7c3927e35 Mon Sep 17 00:00:00 2001 From: clinique Date: Fri, 28 Aug 2020 16:10:12 +0200 Subject: [PATCH 04/17] Statisfaction of Travis Signed-off-by: clinique --- .../gce/internal/GCEHandlerFactory.java | 2 +- .../config/AnalogInputConfiguration.java | 2 +- .../internal/config/CounterConfiguration.java | 2 +- .../config/DigitalInputConfiguration.java | 2 +- .../internal/config/Ipx800Configuration.java | 2 +- .../config/RelayOutputConfiguration.java | 2 +- .../internal/handler/Ipx800MessageParser.java | 2 +- .../main/resources/ESH-INF/thing/channels.xml | 404 +++++++++--------- .../main/resources/ESH-INF/thing/ipx800v3.xml | 26 +- .../test/Ipx800DeviceConnectorTest.java | 4 +- 10 files changed, 224 insertions(+), 224 deletions(-) diff --git a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/GCEHandlerFactory.java b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/GCEHandlerFactory.java index a2064be010b61..d261b9ab53f45 100644 --- a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/GCEHandlerFactory.java +++ b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/GCEHandlerFactory.java @@ -28,7 +28,7 @@ * The {@link GCEHandlerFactory} is responsible for creating things and * thing handlers. * - * @author Gaël L'hopital - Initial Implementation + * @author Gaël L'hopital - Initial contribution */ @NonNullByDefault @Component(service = ThingHandlerFactory.class, configurationPid = "binding.gce") diff --git a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/config/AnalogInputConfiguration.java b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/config/AnalogInputConfiguration.java index d67088b995d70..05c002f5e33f9 100644 --- a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/config/AnalogInputConfiguration.java +++ b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/config/AnalogInputConfiguration.java @@ -18,7 +18,7 @@ * The {@link AnalogInputConfiguration} class holds configuration informations of * an ipx800 Analog Input port. * - * @author Gaël L'hopital - Initial Implementation + * @author Gaël L'hopital - Initial contribution */ @NonNullByDefault public class AnalogInputConfiguration extends CounterConfiguration { diff --git a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/config/CounterConfiguration.java b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/config/CounterConfiguration.java index e76734aed9df9..17f9ed75f74c4 100644 --- a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/config/CounterConfiguration.java +++ b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/config/CounterConfiguration.java @@ -19,7 +19,7 @@ * The {@link CounterConfiguration} class holds configuration informations of * an ipx800 counter. * - * @author Gaël L'hopital - Initial Implementation + * @author Gaël L'hopital - Initial contribution */ @NonNullByDefault public class CounterConfiguration extends Configuration { diff --git a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/config/DigitalInputConfiguration.java b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/config/DigitalInputConfiguration.java index 7b3e838b3e917..6394d447cd2ee 100644 --- a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/config/DigitalInputConfiguration.java +++ b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/config/DigitalInputConfiguration.java @@ -19,7 +19,7 @@ * The {@link DigitalInputConfiguration} class holds configuration informations of * an ipx800 Digital Input port. * - * @author Gaël L'hopital - Initial Implementation + * @author Gaël L'hopital - Initial contribution */ @NonNullByDefault public class DigitalInputConfiguration extends Configuration { diff --git a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/config/Ipx800Configuration.java b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/config/Ipx800Configuration.java index 005a648069be0..3c0ae082a76e1 100644 --- a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/config/Ipx800Configuration.java +++ b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/config/Ipx800Configuration.java @@ -18,7 +18,7 @@ * The {@link Ipx800Configuration} class holds configuration informations of * the ipx800v3 thing. * - * @author Gaël L'hopital - Initial implementation + * @author Gaël L'hopital - Initial contribution */ @NonNullByDefault public class Ipx800Configuration { diff --git a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/config/RelayOutputConfiguration.java b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/config/RelayOutputConfiguration.java index dee0c7f6e8b17..77cc5a3b8f67e 100644 --- a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/config/RelayOutputConfiguration.java +++ b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/config/RelayOutputConfiguration.java @@ -19,7 +19,7 @@ * The {@link RelayOutputConfiguration} class holds configuration informations of * an ipx800 relay output. * - * @author Gaël L'hopital - Initial Implementation + * @author Gaël L'hopital - Initial contribution */ @NonNullByDefault public class RelayOutputConfiguration extends Configuration { diff --git a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800MessageParser.java b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800MessageParser.java index 388c2bb890c29..e4ea7efb4beda 100644 --- a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800MessageParser.java +++ b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800MessageParser.java @@ -29,7 +29,7 @@ */ @NonNullByDefault public class Ipx800MessageParser { - private final Logger logger = LoggerFactory.getLogger(Ipx800DeviceConnector.class); + private final Logger logger = LoggerFactory.getLogger(Ipx800MessageParser.class); 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 diff --git a/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/thing/channels.xml b/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/thing/channels.xml index 82151a0fbc5ed..dcdcb37bdee50 100644 --- a/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/thing/channels.xml +++ b/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/thing/channels.xml @@ -5,208 +5,208 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Contact - - Contact - - - - - 0 - - - - Long press time in milliseconds. - 0 - - - - Pulse period in milliseconds. - 0 - - - - Maximum period for sending pulses in milliseconds. - 0 - - - - - - Switch - - - - - false - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Contact + + Contact + + + + + 0 + + + + Long press time in milliseconds. + 0 + + + + Pulse period in milliseconds. + 0 + + + + Maximum period for sending pulses in milliseconds. + 0 + + + + + + Switch + + + + + false + + + - - Number - - - - - Delay for pulling Analog and Counters info (in milliseconds). - 5000 - - - - - - Number - - - - - 0 - - - - Delay for pulling Analog and Counters info (in milliseconds). - 5000 - - - - - - Number:Time - - Duration of previous state before state change. - - - - - trigger - - - - - - - - - - - + + Number + + + + + Delay for pulling Analog and Counters info (in milliseconds). + 5000 + + + + + + Number + + + + + 0 + + + + Delay for pulling Analog and Counters info (in milliseconds). + 5000 + + + + + + Number:Time + + Duration of previous state before state change. + + + + + trigger + + + + + + + + + + + diff --git a/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/thing/ipx800v3.xml b/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/thing/ipx800v3.xml index 16a778fa78590..318d3474c814b 100644 --- a/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/thing/ipx800v3.xml +++ b/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/thing/ipx800v3.xml @@ -3,17 +3,17 @@ 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"> - - - The GCE IPX800v3 device - - - - - - - - - - + + + The GCE IPX800v3 device + + + + + + + + + + diff --git a/bundles/org.openhab.binding.gce/src/test/java/org/openhab/binding/gce/internal/test/Ipx800DeviceConnectorTest.java b/bundles/org.openhab.binding.gce/src/test/java/org/openhab/binding/gce/internal/test/Ipx800DeviceConnectorTest.java index 1e9d634899671..598a02828852c 100644 --- a/bundles/org.openhab.binding.gce/src/test/java/org/openhab/binding/gce/internal/test/Ipx800DeviceConnectorTest.java +++ b/bundles/org.openhab.binding.gce/src/test/java/org/openhab/binding/gce/internal/test/Ipx800DeviceConnectorTest.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2019 Contributors to the openHAB project + * Copyright (c) 2010-2020 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. @@ -20,7 +20,7 @@ /** * - * @author Seebag - Initial contribution on OH1 + * @author Seebag - Initial contribution * @author Gaël L'hopital - Ported and adapted for OH2 * */ From 62be7004e578d0793d9a9bbae1fadb6e69be93c5 Mon Sep 17 00:00:00 2001 From: clinique Date: Fri, 28 Aug 2020 16:55:40 +0200 Subject: [PATCH 05/17] SAT corrections Signed-off-by: clinique --- bundles/org.openhab.binding.gce/README.md | 172 ++++-------------- .../internal/config/Ipx800Configuration.java | 4 - .../handler/Ipx800DeviceConnector.java | 26 +-- .../gce/internal/handler/Ipx800v3Handler.java | 2 +- .../ESH-INF/config/ipx800v3Config.xml | 48 ++--- .../main/resources/ESH-INF/thing/channels.xml | 54 +++--- .../test/Ipx800DeviceConnectorTest.java | 4 +- 7 files changed, 89 insertions(+), 221 deletions(-) diff --git a/bundles/org.openhab.binding.gce/README.md b/bundles/org.openhab.binding.gce/README.md index 37bc5ff8b8de9..2f06648ab6d56 100644 --- a/bundles/org.openhab.binding.gce/README.md +++ b/bundles/org.openhab.binding.gce/README.md @@ -11,15 +11,15 @@ IPX800 is a 8 relay webserver from gce-electronics with a lot of possibilities: 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 drive output and retrieve input. On input we generally connect push button (for instance house switchs), on ouputs we can connect light bulbs for instance. +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/double clic/Long press - * Virtual dimmer + * Simple clic/Long press * Pulse mode support ## Binding Configuration @@ -31,126 +31,59 @@ There is no configuration at binding level. The IPX800v3 accepts the following configuration parameters -| Property | Default | Required | Description | -|-----------------------------------|---------|----------|----------------------------| -| hostname | | Yes | IP address or hostname | -| portNumber | 9870 | No | TCP client connection port | +| Property | Default | Required | Description | +|---------------------|---------|----------|-----------------------------| +| hostname | | Yes | IP address or hostname. | +| portNumber | 9870 | No | TCP client connection port. | -The thing provides three kind of channels +The thing provides four kinds of channels. ### Digital Inputs -| Property | Required | Description | -|-----------------------------------|----------|----------------------------| -| debouncePeriod | No | Debounce time (avoids entry flappling within this time) | -| longPressTime | No | Long Press Time | -| pulsePeriod | No | Pulse Period | -| pulseTimeout | No | Pulse Timeout | +#### Configuration + +| Property | Default | Description | +|-----------------|---------|---------------------------------------------------------------------------------| +| debouncePeriod | 0 | Debounce time (ignores flappling within this time). No debounce is done if '0'. | +| longPressTime | 0 | Delay (in ms) before triggering long press event. Ignored if '0'. | +| pulsePeriod | 0 | Period of pulse event triggering while the entry is closed. Ignored if '0'. | +| pulseTimeout | 0 | Period of time after pulsing will be stopped. None if '0'. | ### Digital Outputs (relays) +#### Configuration + +| Property | Default | Description | +|-----------------|---------|---------------------------------------------------------------------------------| +| pulse | false | | + ### Counters -### Analog Inputs +#### Configuration +| Property | Default | Description | +|-----------------|---------|---------------------------------------------------------------------------------| +| pullFrequency | 5000 | Counter value refreshing frequency (in ms). | -## Item Configuration +### Analog Inputs -### Syntax +#### Configuration -ipx800 items are described as below (italic items are optionnal) +| Property | Default | Description | +|-----------------|---------|----------------------------------------------------------------------------------------| +| pullFrequency | 5000 | Counter value refreshing frequency (in ms). | +| histeresis | 0 | Threshold that must be reached between two refreshes to trigger an update of the value | -``` -ipx800="name:port:*options>to\_name:to\_port*" -``` +## Item Configuration -| name | ipx name or extension alias as defined in the configuration | -| port | ipx port name as Tnn, with T port type (O : ouput, I : input, C : Counter, A : Analog) and nn port number | -| options | depending on items | +### Syntax -* `>*to\_name:*to\_port` : redirection option, is used to drive directly one output using an input. to\_name is the optional name of the ipx800 to send to command. to\_port is the port to send the command to (if no to_name, the command will be send to the same ipx) ### Item Types #### Output -``` -Switch Output { ipx800="myipx:O01" } -Switch Output { ipx800="myipx:O01:p" } -``` - -Drive output directly from a openhab item. Option p put this ouput in pulse mode (sending SetNxxp at ipx800) - -#### Mirror - -``` -Switch InputMirror { ipx800="myipx:I08:m" } -``` - -State of this item will follow the input state. - -#### Normal astable switch - -``` -Switch InputNormal { ipx800="myipx:I08" } -``` - -On each rising edge of the input, item state will change. - -#### Simple Clic - -``` -Switch InputSimpleClic { ipx800="myipx:I08:d", milight="m1;3" } -``` - -When coupled with a double click, after a single rising/falling edge, will wait the double clic timeout before changing item state. - -#### Double click - -``` -Switch InputDoubleClic2 { ipx800="myipx:I08:D>myipx2:O03" } -``` - -Change item state after a double clic on the input. - -#### Virtual dimmer - -``` -Switch InputDimmer { ipx800="ipx1:I02\:v\:\" } -``` - -A long press will raise the value of this item each 500ms by . - -Once item value reaches 100, it will stick to this value. A new long press will restart the dimmer to 0. - -#### Simple Counter - -``` -Number SimpleCounter {ipx800="myipx:C01"} -``` - -Will reflect ipx800 counter value. - -#### Average counter - -``` -Number AverageCounter {ipx800="myipx\:C01\: a\:1\:m"} -``` - -Will compute the average based on the counter. This is very useful to use in conjunction to pulse based counter (water/gaz/electrical counter). - -8 different counter could be connected direclty to ipx800. - -With this kind of counter, all the power will be monitored easily (liters per minute, Kw...) - -This item will publish its state at least each period. - -Options : \\:\ - -| Step | unit of each counter increment (as defined by hardware counter) | -| Period | Base period to compute the average | - #### To be done @@ -160,43 +93,6 @@ Options : \\:\ ### Example ``` -Switch Output { ipx800="myipx:O01" } -Switch InputNormal { ipx800="myipx:I08" } -Switch InputSimpleClic { ipx800="myipx:I08:d>O02" } -Switch InputSimpleClic { ipx800="myipx:I08:d", milight="m1;3" } -Switch InputDoubleClic { ipx800="myipx:I08:D", milight="m1;9" } -Switch InputDoubleClic2 { ipx800="myipx:I08:D>myipx2:O03" } -Switch InputDoubleClic3 { ipx800="myipx:I08:v>O04" } -Switch Output2 { ipx800="myipx:O02" } -Switch Output3 { ipx800="myipx:O03" } -Switch Output9 { ipx800="myipx2:O01" } - -Switch InputToOuput {ipx800="myipx:I08>O01"} - -Switch Mirror {ipx800="myipx:I06:m>O06" } - -Number PowerSimple {ipx800="myipx:C01"} -Number PowerAverage {ipx800="myipx:C01:a:1:m"} ``` -## Architecture & developpment choices - -The goal of this binding is to connect openHAB to ipx800 while provinding extra functionnality to like double click (double change of input state will change the state of an openhab item), long press (long change of input), average power counter (count the average of pulse on a specified input)... - -To do this two things are needed : - -* Keep a cache of ipx800 device state -> Because the change of an ipx800 input doesn't directly change the state of an openhab item state. - -* Keep the bindingProvider configured with openhab item configuration -> When you change an ipx800 input, the provider need to know what kind of item is configured on openhab - -Ex: - -A double click item is configured on an input. - -A change occur on this input, the provider will just change its internal state to wait for the next clic -On the second change, the provider will change the state of the openhab item - -In the current architecture, for each openhab item setup in conf file (Ex : Switch InputDoubleClic { ipx800="ipx1:I02:D"}), an ipx800Item is configured in the binding (in this case : Ipx800DoubleClic). -To be able to handle multiple items on the same ipx800 input port, i add the handler layer, so each item is linked to these handlers. This layer can for instance handle SimpleClic, DoubleClic and virtual dimmer connected on the same ipx800 input port (Ipx800HandlerMulti). -These features (double item state change, virtual dimmer,...) could have been implemented as rules, but it's easier to only configure an item. diff --git a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/config/Ipx800Configuration.java b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/config/Ipx800Configuration.java index 3c0ae082a76e1..263ed148caa5e 100644 --- a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/config/Ipx800Configuration.java +++ b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/config/Ipx800Configuration.java @@ -24,8 +24,4 @@ public class Ipx800Configuration { public String hostname = ""; public int portNumber = 9870; - public int reconnectTimeout = 5000; - public int sendTimeout = 1000; - public int maxKeepAliveFailure = 1; - public int keepAliveTimeout = 30000; } diff --git a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800DeviceConnector.java b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800DeviceConnector.java index fdeb3379f072f..01b7e5bb5a64d 100644 --- a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800DeviceConnector.java +++ b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800DeviceConnector.java @@ -21,7 +21,6 @@ import java.util.Optional; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.gce.internal.config.Ipx800Configuration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -35,9 +34,13 @@ @NonNullByDefault public class Ipx800DeviceConnector extends Thread { private final Logger logger = LoggerFactory.getLogger(Ipx800DeviceConnector.class); + private static final int DEFAULT_SOCKET_TIMEOUT = 5000; + private static final int DEFAULT_RECONNECT_TIMEOUT = 5000; + private static final int MAX_KEEPALIVE_FAILURE = 3; private final static String ENDL = "\r\n"; - private final Ipx800Configuration config; + private final String hostname; + public final int portNumber; private Optional parser = Optional.empty(); private boolean interrupted = false; @@ -50,8 +53,9 @@ public class Ipx800DeviceConnector extends Thread { private int failedKeepalive = 0; private boolean waitingKeepaliveResponse = false; - public Ipx800DeviceConnector(Ipx800Configuration config) { - this.config = config; + public Ipx800DeviceConnector(String hostname, int portNumber) { + this.hostname = hostname; + this.portNumber = portNumber; } public synchronized void send(String message) { @@ -67,14 +71,14 @@ public synchronized void send(String message) { */ private void connect() throws IOException { disconnect(); - logger.debug("Connecting {}:{}...", config.hostname, config.portNumber); - client = new Socket(config.hostname, config.portNumber); - client.setSoTimeout(config.keepAliveTimeout); + logger.debug("Connecting {}:{}...", hostname, portNumber); + client = new Socket(hostname, portNumber); + client.setSoTimeout(DEFAULT_SOCKET_TIMEOUT); client.getInputStream().skip(client.getInputStream().available()); in = new BufferedReader(new InputStreamReader(client.getInputStream())); out = new PrintWriter(client.getOutputStream(), true); connected = true; - logger.debug("Connected to {}:{}", config.hostname, config.portNumber); + logger.debug("Connected to {}:{}", hostname, portNumber); } /** @@ -127,7 +131,7 @@ public void run() { failedKeepalive = 0; connect(); while (!interrupted) { - if (failedKeepalive > config.maxKeepAliveFailure) { + if (failedKeepalive > MAX_KEEPALIVE_FAILURE) { throw new IOException("Max keep alive attempts has been reached"); } try { @@ -143,7 +147,7 @@ public void run() { handleException(e); } try { - Thread.sleep(config.reconnectTimeout); + Thread.sleep(DEFAULT_RECONNECT_TIMEOUT); } catch (InterruptedException e) { interrupted = true; Thread.currentThread().interrupt(); @@ -156,7 +160,7 @@ private void handleException(Exception e) { sendKeepalive(); return; } else if (e instanceof IOException) { - logger.info("Communication error : '{}', will retry in {} ms", e.getMessage(), config.reconnectTimeout); + logger.info("Communication error : '{}', will retry in {} ms", e, DEFAULT_RECONNECT_TIMEOUT); } parser.ifPresent(parser -> parser.errorOccurred(e)); logger.debug(e.getMessage()); diff --git a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800v3Handler.java b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800v3Handler.java index d103defeaa574..5b18b051731f3 100644 --- a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800v3Handler.java +++ b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800v3Handler.java @@ -99,7 +99,7 @@ public void initialize() { logger.debug("Initializing IPX800 handler for uid '{}'", getThing().getUID()); - connector = new Ipx800DeviceConnector(configuration); + connector = new Ipx800DeviceConnector(configuration.hostname, configuration.portNumber); connector.setName("OH-binding-" + getThing().getUID()); connector.setDaemon(true); diff --git a/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/config/ipx800v3Config.xml b/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/config/ipx800v3Config.xml index 96f90cda84b5a..e7208f2c45005 100644 --- a/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/config/ipx800v3Config.xml +++ b/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/config/ipx800v3Config.xml @@ -5,42 +5,16 @@ https://openhab.org/schemas/config-description-1.0.0.xsd"> - - network-address - - Network/IP address of the IPX800 without http(s) prefix - - - - TCP client connection port - 9870 - true - - - - Time before reconnecting in case of failure (in milliseconds). - 5000 - true - - - - Time before assuming send failure (in milliseconds). - 1000 - true - - - - Max keep alive failures before reconnecting (tentatives, 1-10) - 1 - true - - - - Max keep timeout (in milliseconds) - 30000 - true - + + network-address + + Network/IP address of the IPX800 without http(s) prefix. + + + + TCP client connection port. + 9870 + true + diff --git a/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/thing/channels.xml b/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/thing/channels.xml index dcdcb37bdee50..ff54350317b96 100644 --- a/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/thing/channels.xml +++ b/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/thing/channels.xml @@ -9,70 +9,70 @@ - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -102,25 +102,25 @@ - + - + - + - + - + - + Contact @@ -148,7 +148,7 @@ - + Switch @@ -171,7 +171,7 @@ - + Number @@ -194,7 +194,7 @@ Duration of previous state before state change. - + trigger diff --git a/bundles/org.openhab.binding.gce/src/test/java/org/openhab/binding/gce/internal/test/Ipx800DeviceConnectorTest.java b/bundles/org.openhab.binding.gce/src/test/java/org/openhab/binding/gce/internal/test/Ipx800DeviceConnectorTest.java index 598a02828852c..d181baa2ea531 100644 --- a/bundles/org.openhab.binding.gce/src/test/java/org/openhab/binding/gce/internal/test/Ipx800DeviceConnectorTest.java +++ b/bundles/org.openhab.binding.gce/src/test/java/org/openhab/binding/gce/internal/test/Ipx800DeviceConnectorTest.java @@ -14,7 +14,6 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.junit.Test; -import org.openhab.binding.gce.internal.config.Ipx800Configuration; import org.openhab.binding.gce.internal.handler.Ipx800DeviceConnector; import org.openhab.binding.gce.internal.handler.Ipx800MessageParser; @@ -46,8 +45,7 @@ public void validateDeviceListModel() { // Command : // I=10000000000000000000000000000000&O=00000000000000000000000000000000&A0=0&A1=0&A2=0&A3=0&A4=0&A5=0&A6=0&A7=0&A8=0&A9=0&A10=0&A11=0&A12=0&A13=0&A14=0&A15=0&C1=2064&C2=1&C3=3&C4=4&C5=5&C6=6&C7=7&C8=8 - Ipx800Configuration configuration = new Ipx800Configuration(); - Ipx800DeviceConnector connector = new Ipx800DeviceConnector(configuration); + Ipx800DeviceConnector connector = new Ipx800DeviceConnector("", 9870); Ipx800MessageParser parser = new Ipx800MessageParser(connector, null); parser.setExpectedResponse("GetOutputs"); parser.unsollicitedUpdate("01010010100101010101010101010101"); From 76bd7c56ba84fd3a6466c62b4e9c9d900d54b1ff Mon Sep 17 00:00:00 2001 From: clinique Date: Fri, 28 Aug 2020 17:04:27 +0200 Subject: [PATCH 06/17] This one should be good Signed-off-by: clinique --- CODEOWNERS | 1 + .../binding/gce/internal/handler/Ipx800DeviceConnector.java | 1 - .../src/main/resources/ESH-INF/thing/channels.xml | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index d4e47dd271368..42b05f85432cb 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -70,6 +70,7 @@ /bundles/org.openhab.binding.fsinternetradio/ @paphko /bundles/org.openhab.binding.ftpupload/ @paulianttila /bundles/org.openhab.binding.gardena/ @gerrieg +/bundles/org.openhab.binding.gce/ @clinique /bundles/org.openhab.binding.globalcache/ @mhilbush /bundles/org.openhab.binding.gpstracker/ @gbicskei /bundles/org.openhab.binding.gree/ @markus7017 diff --git a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800DeviceConnector.java b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800DeviceConnector.java index 01b7e5bb5a64d..8787861b6987b 100644 --- a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800DeviceConnector.java +++ b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800DeviceConnector.java @@ -163,7 +163,6 @@ private void handleException(Exception e) { logger.info("Communication error : '{}', will retry in {} ms", e, DEFAULT_RECONNECT_TIMEOUT); } parser.ifPresent(parser -> parser.errorOccurred(e)); - logger.debug(e.getMessage()); } public void setParser(Ipx800MessageParser parser) { diff --git a/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/thing/channels.xml b/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/thing/channels.xml index ff54350317b96..821057ed71400 100644 --- a/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/thing/channels.xml +++ b/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/thing/channels.xml @@ -92,7 +92,7 @@ - + From 6d71dd7e81053b3bf57eefbc5f1fd97073887ba9 Mon Sep 17 00:00:00 2001 From: clinique Date: Mon, 31 Aug 2020 10:23:05 +0200 Subject: [PATCH 07/17] Code review progress Signed-off-by: clinique --- bundles/org.openhab.binding.gce/NOTICE | 2 +- bundles/org.openhab.binding.gce/README.md | 31 +-- bundles/org.openhab.binding.gce/pom.xml | 3 +- .../gce/internal/action/Ipx800Actions.java | 5 +- .../config/AnalogInputConfiguration.java | 2 +- .../internal/config/CounterConfiguration.java | 2 +- .../handler/Ipx800DeviceConnector.java | 25 +-- .../internal/handler/Ipx800EventListener.java | 3 +- .../internal/handler/Ipx800MessageParser.java | 18 +- .../gce/internal/handler/Ipx800v3Handler.java | 23 +- .../resources/ESH-INF/binding/binding.xml | 7 +- .../ESH-INF/config/ipx800v3Config.xml | 25 ++- .../main/resources/ESH-INF/thing/channels.xml | 212 ++++++++++-------- .../main/resources/ESH-INF/thing/ipx800v3.xml | 10 +- .../test/Ipx800DeviceConnectorTest.java | 14 +- 15 files changed, 203 insertions(+), 179 deletions(-) diff --git a/bundles/org.openhab.binding.gce/NOTICE b/bundles/org.openhab.binding.gce/NOTICE index 4c20ef446c1e4..38d625e349232 100644 --- a/bundles/org.openhab.binding.gce/NOTICE +++ b/bundles/org.openhab.binding.gce/NOTICE @@ -10,4 +10,4 @@ https://www.eclipse.org/legal/epl-2.0/. == Source Code -https://github.com/openhab/openhab2-addons +https://github.com/openhab/openhab-addons diff --git a/bundles/org.openhab.binding.gce/README.md b/bundles/org.openhab.binding.gce/README.md index 2f06648ab6d56..62b92f61cc8f8 100644 --- a/bundles/org.openhab.binding.gce/README.md +++ b/bundles/org.openhab.binding.gce/README.md @@ -29,26 +29,27 @@ There is no configuration at binding level. ## Thing Configuration -The IPX800v3 accepts the following configuration parameters +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. | -The thing provides four kinds of channels. +The thing provides four groups of channels. ### Digital Inputs #### Configuration -| Property | Default | Description | -|-----------------|---------|---------------------------------------------------------------------------------| -| debouncePeriod | 0 | Debounce time (ignores flappling within this time). No debounce is done if '0'. | -| longPressTime | 0 | Delay (in ms) before triggering long press event. Ignored if '0'. | -| pulsePeriod | 0 | Period of pulse event triggering while the entry is closed. Ignored if '0'. | -| pulseTimeout | 0 | Period of time after pulsing will be stopped. None if '0'. | +| 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 (relays) @@ -62,18 +63,18 @@ The thing provides four kinds of channels. #### Configuration -| Property | Default | Description | -|-----------------|---------|---------------------------------------------------------------------------------| -| pullFrequency | 5000 | Counter value refreshing frequency (in ms). | +| Property | Default | Description | +|--------------|---------|---------------------------------------------------------------------------------| +| pullInterval | 5000 | Counter value refreshing frequency (in ms). | ### Analog Inputs #### Configuration -| Property | Default | Description | -|-----------------|---------|----------------------------------------------------------------------------------------| -| pullFrequency | 5000 | Counter value refreshing frequency (in ms). | -| histeresis | 0 | Threshold that must be reached between two refreshes to trigger an update of the value | +| Property | Default | Unit | Description | +|--------------|---------|------|-----------------------------------------------------------------------------------------| +| pullInterval | 5000 | ms | Counter value refreshing frequency. | +| hysteresis | 0 | | Threshold that must be reached between two refreshes to trigger an update of the value. | ## Item Configuration diff --git a/bundles/org.openhab.binding.gce/pom.xml b/bundles/org.openhab.binding.gce/pom.xml index 5d3717ab2a10e..f92601cdc09dd 100644 --- a/bundles/org.openhab.binding.gce/pom.xml +++ b/bundles/org.openhab.binding.gce/pom.xml @@ -1,5 +1,6 @@ - + 4.0.0 diff --git a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/action/Ipx800Actions.java b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/action/Ipx800Actions.java index ae24a7a656d83..371f1a9d52a2b 100644 --- a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/action/Ipx800Actions.java +++ b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/action/Ipx800Actions.java @@ -68,7 +68,7 @@ public void resetCounter( if (theHandler != null) { theHandler.resetCounter(counter); } else { - logger.info("Method call resetCounter failed because IPX800 action service ThingHandler is null!"); + logger.warn("Method call resetCounter failed because IPX800 action service ThingHandler is null!"); } } @@ -80,7 +80,7 @@ public void reset() { if (theHandler != null) { theHandler.reset(); } else { - logger.info("Method call reset failed because IPX800 action service ThingHandler is null!"); + logger.warn("Method call reset failed because IPX800 action service ThingHandler is null!"); } } @@ -110,5 +110,4 @@ private static IIpx800Actions invokeMethodOf(@Nullable ThingActions actions) { } throw new IllegalArgumentException("Actions is not an instance of Ipx800Actions"); } - } diff --git a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/config/AnalogInputConfiguration.java b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/config/AnalogInputConfiguration.java index 05c002f5e33f9..90d44a4a46e7d 100644 --- a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/config/AnalogInputConfiguration.java +++ b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/config/AnalogInputConfiguration.java @@ -22,5 +22,5 @@ */ @NonNullByDefault public class AnalogInputConfiguration extends CounterConfiguration { - public long histeresis; + public long hysteresis; } diff --git a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/config/CounterConfiguration.java b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/config/CounterConfiguration.java index 17f9ed75f74c4..4458f8cca6446 100644 --- a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/config/CounterConfiguration.java +++ b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/config/CounterConfiguration.java @@ -23,5 +23,5 @@ */ @NonNullByDefault public class CounterConfiguration extends Configuration { - public int pullFrequency = 5000; + public int pullInterval = 5000; } diff --git a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800DeviceConnector.java b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800DeviceConnector.java index 8787861b6987b..5d02d87d3bc61 100644 --- a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800DeviceConnector.java +++ b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800DeviceConnector.java @@ -34,16 +34,15 @@ @NonNullByDefault public class Ipx800DeviceConnector extends Thread { private final Logger logger = LoggerFactory.getLogger(Ipx800DeviceConnector.class); - private static final int DEFAULT_SOCKET_TIMEOUT = 5000; - private static final int DEFAULT_RECONNECT_TIMEOUT = 5000; + 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 final static String ENDL = "\r\n"; + private static final String ENDL = "\r\n"; private final String hostname; public final int portNumber; private Optional parser = Optional.empty(); - private boolean interrupted = false; private boolean connected = false; private @NonNullByDefault({}) Socket client; @@ -73,7 +72,7 @@ private void connect() throws IOException { disconnect(); logger.debug("Connecting {}:{}...", hostname, portNumber); client = new Socket(hostname, portNumber); - client.setSoTimeout(DEFAULT_SOCKET_TIMEOUT); + 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); @@ -88,6 +87,8 @@ private void disconnect() { if (connected) { logger.debug("Disconnecting"); try { + in.close(); + out.close(); client.close(); } catch (IOException e) { logger.warn("Unable to disconnect {}", e.getMessage()); @@ -101,7 +102,7 @@ private void disconnect() { * Stop the device thread */ public void destroyAndExit() { - interrupted = true; + Thread.currentThread().interrupt(); disconnect(); } @@ -124,20 +125,19 @@ private void sendKeepalive() { @Override public void run() { - interrupted = false; - while (!interrupted) { + while (!interrupted()) { try { waitingKeepaliveResponse = false; failedKeepalive = 0; connect(); - while (!interrupted) { + while (!interrupted()) { if (failedKeepalive > MAX_KEEPALIVE_FAILURE) { throw new IOException("Max keep alive attempts has been reached"); } try { String command = in.readLine(); waitingKeepaliveResponse = false; - parser.ifPresent(parser -> parser.unsollicitedUpdate(command)); + parser.ifPresent(parser -> parser.unsolicitedUpdate(command)); } catch (SocketTimeoutException e) { handleException(e); } @@ -147,9 +147,8 @@ public void run() { handleException(e); } try { - Thread.sleep(DEFAULT_RECONNECT_TIMEOUT); + Thread.sleep(DEFAULT_RECONNECT_TIMEOUT_MS); } catch (InterruptedException e) { - interrupted = true; Thread.currentThread().interrupt(); } } @@ -160,7 +159,7 @@ private void handleException(Exception e) { sendKeepalive(); return; } else if (e instanceof IOException) { - logger.info("Communication error : '{}', will retry in {} ms", e, DEFAULT_RECONNECT_TIMEOUT); + logger.warn("Communication error : '{}', will retry in {} ms", e, DEFAULT_RECONNECT_TIMEOUT_MS); } parser.ifPresent(parser -> parser.errorOccurred(e)); } diff --git a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800EventListener.java b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800EventListener.java index caa7f84aab08a..efd2292ca3c59 100644 --- a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800EventListener.java +++ b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800EventListener.java @@ -28,7 +28,7 @@ public interface Ipx800EventListener { * @param port Port (kind and number) receiving update * @param value value updated */ - void dataReceived(String port, Double value); + void dataReceived(String port, double value); /** * Procedure for receiving information fatal error. @@ -36,5 +36,4 @@ public interface Ipx800EventListener { * @param e Error occurred. */ void errorOccurred(Exception e); - } diff --git a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800MessageParser.java b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800MessageParser.java index e4ea7efb4beda..67b3cb8d33618 100644 --- a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800MessageParser.java +++ b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800MessageParser.java @@ -67,13 +67,13 @@ public void setOutput(String targetPort, int targetValue, boolean pulse) { * * @param data */ - public void unsollicitedUpdate(String data) { + public void unsolicitedUpdate(String data) { if (IO_PATTERN.matcher(data).matches()) { String portKind = "GetOutputs".equalsIgnoreCase(expectedResponse) ? RELAY_OUTPUT : "GetInputs".equalsIgnoreCase(expectedResponse) ? DIGITAL_INPUT : null; if (portKind != null) { for (int count = 0; count < data.length(); count++) { - setStatus(portKind + String.valueOf(count + 1), new Double(data.charAt(count) - '0')); + setStatus(portKind + (count + 1), (double) data.charAt(count) - '0'); } } } else if (VALIDATION_PATTERN.matcher(data).matches()) { @@ -85,8 +85,7 @@ public void unsollicitedUpdate(String data) { case DIGITAL_INPUT: case RELAY_OUTPUT: { for (int count = 0; count < statusPart[1].length(); count++) { - setStatus(portKind + String.valueOf(count + 1), - new Double(statusPart[1].charAt(count) - '0')); + setStatus(portKind + (count + 1), (double) statusPart[1].charAt(count) - '0'); } break; } @@ -94,20 +93,19 @@ public void unsollicitedUpdate(String data) { portNumShift = -1; // Align counters on 1 based array case ANALOG_INPUT: { int portNumber = Integer.parseInt(statusPart[0].substring(1)); - setStatus(portKind + String.valueOf(portNumber + portNumShift + 1), - Double.parseDouble(statusPart[1])); + setStatus(portKind + (portNumber + portNumShift + 1), Double.parseDouble(statusPart[1])); } } } - } else if (!"".equals(expectedResponse)) { + } else if (!expectedResponse.isEmpty()) { setStatus(expectedResponse, Double.parseDouble(data)); } expectedResponse = ""; } - private void setStatus(String port, Double value) { - logger.debug("Received {} : {}", port, value.toString()); + private void setStatus(String port, double value) { + logger.debug("Received {} : {}", port, value); listener.ifPresent(l -> l.dataReceived(port, value)); } @@ -145,7 +143,7 @@ public void errorOccurred(Exception e) { public synchronized void getValue(String channelId) { logger.debug("Requested value for {}", channelId); - if ("".equals(expectedResponse)) { // Do not send a request is something is expected + if (expectedResponse.isEmpty()) { // Do not send a request is something is expected String[] elements = channelId.split("#"); String command = "Get" + elements[0].replaceAll(ANALOG_INPUT, "An").replaceAll(COUNTER, "Count") .replaceAll(DIGITAL_INPUT, "An").replaceAll(RELAY_OUTPUT, "Out") + elements[1]; diff --git a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800v3Handler.java b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800v3Handler.java index 5b18b051731f3..0c2823d4b6990 100644 --- a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800v3Handler.java +++ b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800v3Handler.java @@ -93,7 +93,6 @@ public Ipx800v3Handler(Thing thing) { @Override public void initialize() { - updateStatus(ThingStatus.OFFLINE); configuration = getConfigAs(Ipx800Configuration.class); @@ -105,7 +104,7 @@ public void initialize() { parser = Optional.of(new Ipx800MessageParser(connector, this)); - updateStatus(ThingStatus.ONLINE); + updateStatus(ThingStatus.UNKNOWN); connector.start(); } @@ -136,12 +135,13 @@ public void errorOccurred(Exception e) { } @Override - public void dataReceived(String port, Double value) { + public void dataReceived(String port, double value) { + updateStatus(ThingStatus.ONLINE); Channel channel = getChannelForPort(port); if (channel != null) { PortData portData = portDatas.get(channel.getUID().getId()); if (portData != null) { - if (value.equals(portData.getValue())) { + if (value == portData.getValue()) { return; } @@ -157,9 +157,9 @@ public void dataReceived(String port, Double value) { break; case ANALOG_INPUT: AnalogInputConfiguration config = configuration.as(AnalogInputConfiguration.class); - long histeresis = config.histeresis / 2; - if (portData.isInitializing() || value > portData.getValue() + histeresis - || value < portData.getValue() - histeresis) { + long hysteresis = config.hysteresis / 2; + if (portData.isInitializing() || value > portData.getValue() + hysteresis + || value < portData.getValue() - hysteresis) { state = new DecimalType(value); } else { return; @@ -235,7 +235,7 @@ public void handleCommand(ChannelUID channelUID, Command command) { (OnOffType) command == OnOffType.ON ? 1 : 0, config.pulse)); return; } - logger.info("Can not handle command '{}' on channel '{}'", command, channelUID); + logger.debug("Can not handle command '{}' on channel '{}'", command, channelUID); } @Override @@ -247,11 +247,11 @@ public void channelLinked(ChannelUID channelUID) { if (channel != null) { Configuration configuration = channel.getConfiguration(); PortData data = new PortData(); - if (configuration.get("pullFrequency") != null) { - int pullFrequency = configuration.as(CounterConfiguration.class).pullFrequency; + if (configuration.get("pullInterval") != null) { + int pullInterval = configuration.as(CounterConfiguration.class).pullInterval; data.setPullJob(scheduler.scheduleWithFixedDelay(() -> { parser.ifPresent(p -> p.getValue(channelId)); - }, pullFrequency, pullFrequency, TimeUnit.MILLISECONDS)); + }, pullInterval, pullInterval, TimeUnit.MILLISECONDS)); } portDatas.put(channelId, data); } @@ -278,5 +278,4 @@ public void resetCounter(int counter) { public void reset() { parser.ifPresent(Ipx800MessageParser::reset); } - } diff --git a/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/binding/binding.xml b/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/binding/binding.xml index df26f91874677..0fcd7050f5980 100644 --- a/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/binding/binding.xml +++ b/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/binding/binding.xml @@ -1,8 +1,7 @@ - + GCE Electronics Binding Provides access to IPX800 PLC build by GCE. diff --git a/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/config/ipx800v3Config.xml b/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/config/ipx800v3Config.xml index e7208f2c45005..f7f3e407cc8b6 100644 --- a/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/config/ipx800v3Config.xml +++ b/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/config/ipx800v3Config.xml @@ -1,20 +1,21 @@ - - - network-address - - Network/IP address of the IPX800 without http(s) prefix. - - - - TCP client connection port. - 9870 - true - + + network-address + + Network/IP address of the IPX800 without http(s) prefix. + + + + TCP client connection port. + 9870 + true + diff --git a/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/thing/channels.xml b/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/thing/channels.xml index 821057ed71400..c1737e2a015df 100644 --- a/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/thing/channels.xml +++ b/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/thing/channels.xml @@ -8,116 +8,144 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + + - - + + + - - + + + - - + + + - - + + + - - + + + - - + + + - - + + + - - - - - - - - - - - - + + + + + + + + + + + + + + + + @@ -164,8 +192,8 @@ Number - - + + Delay for pulling Analog and Counters info (in milliseconds). 5000 @@ -176,12 +204,12 @@ Number - - + + 0 - - + + Delay for pulling Analog and Counters info (in milliseconds). 5000 @@ -192,7 +220,7 @@ Number:Time Duration of previous state before state change. - + diff --git a/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/thing/ipx800v3.xml b/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/thing/ipx800v3.xml index 318d3474c814b..cde1aa0bc6038 100644 --- a/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/thing/ipx800v3.xml +++ b/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/thing/ipx800v3.xml @@ -1,19 +1,19 @@ - The GCE IPX800v3 device - + - - + + diff --git a/bundles/org.openhab.binding.gce/src/test/java/org/openhab/binding/gce/internal/test/Ipx800DeviceConnectorTest.java b/bundles/org.openhab.binding.gce/src/test/java/org/openhab/binding/gce/internal/test/Ipx800DeviceConnectorTest.java index d181baa2ea531..df5c355af6d9b 100644 --- a/bundles/org.openhab.binding.gce/src/test/java/org/openhab/binding/gce/internal/test/Ipx800DeviceConnectorTest.java +++ b/bundles/org.openhab.binding.gce/src/test/java/org/openhab/binding/gce/internal/test/Ipx800DeviceConnectorTest.java @@ -48,18 +48,18 @@ public void validateDeviceListModel() { Ipx800DeviceConnector connector = new Ipx800DeviceConnector("", 9870); Ipx800MessageParser parser = new Ipx800MessageParser(connector, null); parser.setExpectedResponse("GetOutputs"); - parser.unsollicitedUpdate("01010010100101010101010101010101"); + parser.unsolicitedUpdate("01010010100101010101010101010101"); parser.setExpectedResponse("GetInputs"); - parser.unsollicitedUpdate("01000000234005670000000000000000"); - parser.unsollicitedUpdate( + parser.unsolicitedUpdate("01000000234005670000000000000000"); + parser.unsolicitedUpdate( "I=01000000000000000000000000000000&O=01000000000000000000000000000000&A0=0&A1=0&A2=0&A3=0&A4=0&A5=0&A6=0&A7=0&A8=0&A9=0&A10=0&A11=0&A12=0&A13=0&A14=0&A15=0&C1=2064&C2=1&C3=3&C4=4&C5=5&C6=6&C7=7&C8=8"); parser.setExpectedResponse("GetAn1"); - parser.unsollicitedUpdate("234"); + parser.unsolicitedUpdate("234"); parser.setExpectedResponse("GetCount12"); - parser.unsollicitedUpdate("4294967295"); // Test maximum value + parser.unsolicitedUpdate("4294967295"); // Test maximum value parser.setExpectedResponse("GetIn5"); - parser.unsollicitedUpdate("1"); + parser.unsolicitedUpdate("1"); parser.setExpectedResponse("GetOut3"); - parser.unsollicitedUpdate("0"); + parser.unsolicitedUpdate("0"); } } From 2d2e52f3d753e138c7c634fb98f4517579d1f3d0 Mon Sep 17 00:00:00 2001 From: clinique Date: Mon, 31 Aug 2020 11:32:56 +0200 Subject: [PATCH 08/17] Debugging actions Signed-off-by: clinique --- bundles/org.openhab.binding.gce/README.md | 30 ++++++++++++++++++- .../gce/internal/action/IIpx800Actions.java | 5 ++-- .../gce/internal/action/Ipx800Actions.java | 12 ++++---- .../gce/internal/handler/Ipx800v3Handler.java | 9 ++++++ 4 files changed, 48 insertions(+), 8 deletions(-) diff --git a/bundles/org.openhab.binding.gce/README.md b/bundles/org.openhab.binding.gce/README.md index 62b92f61cc8f8..72182f7fad270 100644 --- a/bundles/org.openhab.binding.gce/README.md +++ b/bundles/org.openhab.binding.gce/README.md @@ -91,7 +91,35 @@ The thing provides four groups of channels. * Long press -### Example +## 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). + ``` ``` diff --git a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/action/IIpx800Actions.java b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/action/IIpx800Actions.java index 36d27bcc5cb57..03cd5d15edc8f 100644 --- a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/action/IIpx800Actions.java +++ b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/action/IIpx800Actions.java @@ -13,6 +13,7 @@ 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. @@ -21,7 +22,7 @@ */ @NonNullByDefault public interface IIpx800Actions { - public void resetCounter(int counter); + public void resetCounter(Integer counter); - public void reset(); + public void reset(@Nullable Integer placeholder); } diff --git a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/action/Ipx800Actions.java b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/action/Ipx800Actions.java index 371f1a9d52a2b..c72797794cb4c 100644 --- a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/action/Ipx800Actions.java +++ b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/action/Ipx800Actions.java @@ -62,7 +62,7 @@ public void setThingHandler(@Nullable ThingHandler 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") int counter) { + @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) { @@ -74,7 +74,8 @@ public void resetCounter( @Override @RuleAction(label = "GCE : Reset PLC", description = "Restarts the IPX800") - public void reset() { + 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) { @@ -84,12 +85,12 @@ public void reset() { } } - public static void resetCounter(@Nullable ThingActions actions, int counter) { + public static void resetCounter(@Nullable ThingActions actions, Integer counter) { invokeMethodOf(actions).resetCounter(counter); } - public static void reset(@Nullable ThingActions actions) { - invokeMethodOf(actions).reset(); + public static void reset(@Nullable ThingActions actions, @Nullable Integer placeholder) { + invokeMethodOf(actions).reset(placeholder); } private static IIpx800Actions invokeMethodOf(@Nullable ThingActions actions) { @@ -110,4 +111,5 @@ private static IIpx800Actions invokeMethodOf(@Nullable ThingActions actions) { } throw new IllegalArgumentException("Actions is not an instance of Ipx800Actions"); } + } diff --git a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800v3Handler.java b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800v3Handler.java index 0c2823d4b6990..28b2deb950b93 100644 --- a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800v3Handler.java +++ b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800v3Handler.java @@ -18,6 +18,8 @@ import java.time.ZoneId; import java.time.ZonedDateTime; import java.time.temporal.ChronoUnit; +import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Optional; @@ -37,9 +39,11 @@ import org.eclipse.smarthome.core.thing.ThingStatus; import org.eclipse.smarthome.core.thing.ThingStatusDetail; import org.eclipse.smarthome.core.thing.binding.BaseThingHandler; +import org.eclipse.smarthome.core.thing.binding.ThingHandlerService; import org.eclipse.smarthome.core.types.Command; import org.eclipse.smarthome.core.types.State; import org.eclipse.smarthome.core.types.UnDefType; +import org.openhab.binding.gce.internal.action.Ipx800Actions; import org.openhab.binding.gce.internal.config.AnalogInputConfiguration; import org.openhab.binding.gce.internal.config.CounterConfiguration; import org.openhab.binding.gce.internal.config.DigitalInputConfiguration; @@ -278,4 +282,9 @@ public void resetCounter(int counter) { public void reset() { parser.ifPresent(Ipx800MessageParser::reset); } + + @Override + public Collection> getServices() { + return Collections.singletonList(Ipx800Actions.class); + } } From 1c60f55acff8403f88b5e279ea61b29b30aeea01 Mon Sep 17 00:00:00 2001 From: clinique Date: Fri, 11 Sep 2020 18:10:28 +0200 Subject: [PATCH 09/17] Cleaning the code in order to prepare merge. Signed-off-by: clinique --- bundles/org.openhab.binding.gce/README.md | 78 +++-- .../gce/internal/GCEBindingConstants.java | 20 +- .../gce/internal/GCEHandlerFactory.java | 13 +- .../gce/internal/action/Ipx800Actions.java | 4 +- .../config/AnalogInputConfiguration.java | 2 +- .../internal/config/CounterConfiguration.java | 27 -- .../internal/config/Ipx800Configuration.java | 1 + .../handler/Ipx800DeviceConnector.java | 127 ++++---- .../internal/handler/Ipx800MessageParser.java | 170 ----------- .../gce/internal/handler/Ipx800v3Handler.java | 270 ++++++++++++------ .../binding/gce/model/M2MMessageParser.java | 136 +++++++++ .../{internal/handler => model}/PortData.java | 15 +- .../binding/gce/model/PortDefinition.java | 87 ++++++ .../gce/model/StatusFileInterpreter.java | 125 ++++++++ .../resources/ESH-INF/config/gceConfig.xml | 63 ++++ .../ESH-INF/config/ipx800v3Config.xml | 21 -- .../main/resources/ESH-INF/thing/channels.xml | 247 ++++------------ .../main/resources/ESH-INF/thing/ipx800v3.xml | 25 +- .../test/Ipx800DeviceConnectorTest.java | 4 +- 19 files changed, 817 insertions(+), 618 deletions(-) delete mode 100644 bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/config/CounterConfiguration.java delete mode 100644 bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800MessageParser.java create mode 100644 bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/model/M2MMessageParser.java rename bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/{internal/handler => model}/PortData.java (80%) create mode 100644 bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/model/PortDefinition.java create mode 100644 bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/model/StatusFileInterpreter.java create mode 100644 bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/config/gceConfig.xml delete mode 100644 bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/config/ipx800v3Config.xml diff --git a/bundles/org.openhab.binding.gce/README.md b/bundles/org.openhab.binding.gce/README.md index 72182f7fad270..26d96afcf2bed 100644 --- a/bundles/org.openhab.binding.gce/README.md +++ b/bundles/org.openhab.binding.gce/README.md @@ -35,11 +35,37 @@ The IPX800v3 (ID : 'ipx800v3') accepts the following configuration parameters : |---------------------|---------|----------|-----------------------------| | 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 | @@ -49,46 +75,54 @@ The thing provides four groups of channels. | 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. +* Values below 100ms should be avoided as the JVM could skip them and proceed in the same time slice. -### Digital Outputs (relays) -#### Configuration +### Digital Outputs Channels (relays) -| Property | Default | Description | -|-----------------|---------|---------------------------------------------------------------------------------| -| pulse | false | | +Each output will have these associated channels: -### Counters +| 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 | -|--------------|---------|---------------------------------------------------------------------------------| -| pullInterval | 5000 | Counter value refreshing frequency (in ms). | +| Property | Default | Description | +|-----------------|---------|--------------------------------------------------------------------------| +| pulse | false | If set, the output will be in pulse mode, releasing it after the contact | -### Analog Inputs +### Counters Channels -#### Configuration +Each counter will have these associated channels: -| Property | Default | Unit | Description | -|--------------|---------|------|-----------------------------------------------------------------------------------------| -| pullInterval | 5000 | ms | Counter value refreshing frequency. | -| hysteresis | 0 | | Threshold that must be reached between two refreshes to trigger an update of the value. | +| 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. | -## Item Configuration +#### Configuration -### Syntax +This channel has no configuration setting. +### Analog Inputs Channels -### Item Types +Each analog port will have these associated channels: -#### Output +| 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 -#### To be done +| Property | Default | Description | +|------------|---------|--------------------------------------------------------------------------| +| histeresis | 0 | If set, the channel will ignore status changes below state + histeresis/2| +| | | or higher than state - histeresis sur 2 | -* Long press ## Rule Actions diff --git a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/GCEBindingConstants.java b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/GCEBindingConstants.java index 84c43365058b7..1c0449cb2a24a 100644 --- a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/GCEBindingConstants.java +++ b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/GCEBindingConstants.java @@ -12,9 +12,6 @@ */ package org.openhab.binding.gce.internal; -import java.util.Collections; -import java.util.Set; - import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.smarthome.core.thing.ThingTypeUID; @@ -29,17 +26,12 @@ public class GCEBindingConstants { public static final String BINDING_ID = "gce"; - // List of Bridge Type UIDs + // Bridge Type UID public static final ThingTypeUID IPXV3_THING_TYPE = new ThingTypeUID(BINDING_ID, "ipx800v3"); - // Module Properties - public static final String DIGITAL_INPUT = "I"; - public static final String ANALOG_INPUT = "A"; - public static final String COUNTER = "C"; - public static final String RELAY_OUTPUT = "O"; - - public static final String LAST_STATE_DURATION_CHANNEL_NAME = "duration"; - public static final String CHANNEL_TYPE_PUSH_BUTTON_TRIGGER = "pushButtonTrigger"; + 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"; @@ -47,6 +39,6 @@ public class GCEBindingConstants { public static final String EVENT_LONG_PRESS = "LONG_PRESS"; public static final String EVENT_PULSE = "PULSE"; - // List of adressable things - public static final Set SUPPORTED_THING_TYPES_UIDS = Collections.singleton(IPXV3_THING_TYPE); + // Adressable thing + } diff --git a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/GCEHandlerFactory.java b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/GCEHandlerFactory.java index d261b9ab53f45..e637d8b76c620 100644 --- a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/GCEHandlerFactory.java +++ b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/GCEHandlerFactory.java @@ -12,7 +12,10 @@ */ package org.openhab.binding.gce.internal; -import static org.openhab.binding.gce.internal.GCEBindingConstants.*; +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; @@ -33,18 +36,16 @@ @NonNullByDefault @Component(service = ThingHandlerFactory.class, configurationPid = "binding.gce") public class GCEHandlerFactory extends BaseThingHandlerFactory { + private static final Set SUPPORTED_THING_TYPES_UIDS = Collections.singleton(IPXV3_THING_TYPE); @Override public boolean supportsThingType(ThingTypeUID thingTypeUID) { - return (SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID)); + return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID); } @Override protected @Nullable ThingHandler createHandler(Thing thing) { ThingTypeUID thingTypeUID = thing.getThingTypeUID(); - if (thingTypeUID.equals(IPXV3_THING_TYPE)) { - return new Ipx800v3Handler(thing); - } - return null; + return IPXV3_THING_TYPE.equals(thingTypeUID) ? new Ipx800v3Handler(thing) : null; } } diff --git a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/action/Ipx800Actions.java b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/action/Ipx800Actions.java index c72797794cb4c..dabbea8774024 100644 --- a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/action/Ipx800Actions.java +++ b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/action/Ipx800Actions.java @@ -30,7 +30,7 @@ * The {Ipx800Actions } defines rule actions for the GCE binding. *

* Note:The static method invokeMethodOf handles the case where - * the test actions instanceof AstroActions fails. This test can fail + * the test actions instanceof Ipx800Actions 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 actions instance. * @@ -39,8 +39,8 @@ @ThingActionsScope(name = "gce") @NonNullByDefault public class Ipx800Actions implements ThingActions, IIpx800Actions { - private final Logger logger = LoggerFactory.getLogger(Ipx800Actions.class); + protected @Nullable Ipx800v3Handler handler; public Ipx800Actions() { diff --git a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/config/AnalogInputConfiguration.java b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/config/AnalogInputConfiguration.java index 90d44a4a46e7d..aafab96a0872f 100644 --- a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/config/AnalogInputConfiguration.java +++ b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/config/AnalogInputConfiguration.java @@ -21,6 +21,6 @@ * @author Gaël L'hopital - Initial contribution */ @NonNullByDefault -public class AnalogInputConfiguration extends CounterConfiguration { +public class AnalogInputConfiguration { public long hysteresis; } diff --git a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/config/CounterConfiguration.java b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/config/CounterConfiguration.java deleted file mode 100644 index 4458f8cca6446..0000000000000 --- a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/config/CounterConfiguration.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * 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.eclipse.smarthome.config.core.Configuration; - -/** - * The {@link CounterConfiguration} class holds configuration informations of - * an ipx800 counter. - * - * @author Gaël L'hopital - Initial contribution - */ -@NonNullByDefault -public class CounterConfiguration extends Configuration { - public int pullInterval = 5000; -} diff --git a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/config/Ipx800Configuration.java b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/config/Ipx800Configuration.java index 263ed148caa5e..aae10b3bb3279 100644 --- a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/config/Ipx800Configuration.java +++ b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/config/Ipx800Configuration.java @@ -24,4 +24,5 @@ public class Ipx800Configuration { public String hostname = ""; public int portNumber = 9870; + public int pullInterval = 5000; } diff --git a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800DeviceConnector.java b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800DeviceConnector.java index 5d02d87d3bc61..9b7c996a8d5bd 100644 --- a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800DeviceConnector.java +++ b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800DeviceConnector.java @@ -18,9 +18,11 @@ import java.io.PrintWriter; import java.net.Socket; import java.net.SocketTimeoutException; -import java.util.Optional; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.smarthome.core.thing.ThingUID; +import org.openhab.binding.gce.model.M2MMessageParser; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -28,7 +30,7 @@ * The {@link Ipx800DeviceConnector} is responsible for connecting, * reading, writing and disconnecting from the Ipx800. * - * @author Seebag - Initial contribution on OH1 + * @author Seebag - Initial Contribution * @author Gaël L'hopital - Ported and adapted for OH2 */ @NonNullByDefault @@ -41,9 +43,7 @@ public class Ipx800DeviceConnector extends Thread { private final String hostname; public final int portNumber; - private Optional parser = Optional.empty(); - - private boolean connected = false; + private @Nullable M2MMessageParser parser; private @NonNullByDefault({}) Socket client; private @NonNullByDefault({}) BufferedReader in; @@ -52,9 +52,11 @@ public class Ipx800DeviceConnector extends Thread { private int failedKeepalive = 0; private boolean waitingKeepaliveResponse = false; - public Ipx800DeviceConnector(String hostname, int portNumber) { + 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) { @@ -76,33 +78,40 @@ private void connect() throws IOException { client.getInputStream().skip(client.getInputStream().available()); in = new BufferedReader(new InputStreamReader(client.getInputStream())); out = new PrintWriter(client.getOutputStream(), true); - connected = true; - logger.debug("Connected to {}:{}", hostname, portNumber); } /** * Disconnect the device */ private void disconnect() { - if (connected) { - logger.debug("Disconnecting"); + logger.debug("Disconnecting"); + + if (in != null) { try { in.close(); - out.close(); + } catch (IOException ignore) { + } + this.in = null; + } + if (out != null) { + out.close(); + this.out = null; + } + if (client != null) { + try { client.close(); - } catch (IOException e) { - logger.warn("Unable to disconnect {}", e.getMessage()); + } catch (IOException ignore) { } - connected = false; - logger.debug("Disconnected"); + this.client = null; } + logger.debug("Disconnected"); } /** * Stop the device thread */ public void destroyAndExit() { - Thread.currentThread().interrupt(); + interrupt(); disconnect(); } @@ -111,60 +120,66 @@ public void destroyAndExit() { * If we don't receive the update maxKeepAliveFailure time, the connection is closed and reopened */ private void sendKeepalive() { - if (waitingKeepaliveResponse) { - failedKeepalive++; - logger.debug("Sending keepalive, attempt {}", failedKeepalive); - } else { - failedKeepalive = 0; - logger.debug("Sending keepalive"); + 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; } - out.println("GetIn01"); - out.flush(); - waitingKeepaliveResponse = true; } @Override public void run() { - while (!interrupted()) { - 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; - parser.ifPresent(parser -> parser.unsolicitedUpdate(command)); - } catch (SocketTimeoutException e) { - handleException(e); + 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) { - Thread.currentThread().interrupt(); } + disconnect(); + } catch (IOException e) { + handleException(e); + } + try { + Thread.sleep(DEFAULT_RECONNECT_TIMEOUT_MS); + } catch (InterruptedException e) { + destroyAndExit(); } } private void handleException(Exception e) { - 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 (!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); + } } - parser.ifPresent(parser -> parser.errorOccurred(e)); } - public void setParser(Ipx800MessageParser parser) { - this.parser = Optional.of(parser); + public void setParser(M2MMessageParser parser) { + this.parser = parser; } } diff --git a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800MessageParser.java b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800MessageParser.java deleted file mode 100644 index 67b3cb8d33618..0000000000000 --- a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800MessageParser.java +++ /dev/null @@ -1,170 +0,0 @@ -/** - * 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.util.Optional; -import java.util.regex.Pattern; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -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 Ipx800MessageParser { - private final Logger logger = LoggerFactory.getLogger(Ipx800MessageParser.class); - 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 String expectedResponse = ""; - private final Ipx800DeviceConnector connector; - - private final Optional listener; - - public Ipx800MessageParser(Ipx800DeviceConnector connector, @Nullable Ipx800EventListener ipx800EventListener) { - this.connector = connector; - connector.setParser(this); - if (ipx800EventListener != null) { - this.listener = Optional.of(ipx800EventListener); - } else { - this.listener = Optional.empty(); - } - } - - /** - * 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); - int port = Integer.parseInt(targetPort); - String command = String.format("Set%02d%d%s", port, targetValue, pulse ? "p" : ""); - connector.send(command); - } - - /** - * - * @param data - */ - public void unsolicitedUpdate(String data) { - if (IO_PATTERN.matcher(data).matches()) { - String portKind = "GetOutputs".equalsIgnoreCase(expectedResponse) ? RELAY_OUTPUT - : "GetInputs".equalsIgnoreCase(expectedResponse) ? DIGITAL_INPUT : null; - if (portKind != null) { - for (int count = 0; count < data.length(); count++) { - setStatus(portKind + (count + 1), (double) data.charAt(count) - '0'); - } - } - } else if (VALIDATION_PATTERN.matcher(data).matches()) { - for (String status : data.split("&")) { - String statusPart[] = status.split("="); - String portKind = statusPart[0].substring(0, 1); - int portNumShift = 0; - switch (portKind) { - case DIGITAL_INPUT: - case RELAY_OUTPUT: { - for (int count = 0; count < statusPart[1].length(); count++) { - setStatus(portKind + (count + 1), (double) statusPart[1].charAt(count) - '0'); - } - break; - } - case COUNTER: - portNumShift = -1; // Align counters on 1 based array - case ANALOG_INPUT: { - int portNumber = Integer.parseInt(statusPart[0].substring(1)); - setStatus(portKind + (portNumber + portNumShift + 1), Double.parseDouble(statusPart[1])); - } - } - } - } else if (!expectedResponse.isEmpty()) { - setStatus(expectedResponse, Double.parseDouble(data)); - } - - expectedResponse = ""; - } - - private void setStatus(String port, double value) { - logger.debug("Received {} : {}", port, value); - listener.ifPresent(l -> l.dataReceived(port, value)); - } - - public void setExpectedResponse(String expectedResponse) { - if (expectedResponse.endsWith("s")) { // GetInputs or GetOutputs - this.expectedResponse = expectedResponse; - } else { // GetAnx or GetCountx - this.expectedResponse = expectedResponse.replaceAll("GetAn", ANALOG_INPUT).replaceAll("GetCount", COUNTER) - .replaceAll("GetIn", DIGITAL_INPUT).replaceAll("GetOut", RELAY_OUTPUT); - } - } - - /** - * 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)); - try { - Thread.sleep(200); - String request = String.format("GetCount%d", targetCounter); - setExpectedResponse(request); - connector.send(request); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - } - - public void errorOccurred(Exception e) { - logger.warn("Error received from connector : {}", e.getMessage()); - listener.ifPresent(l -> l.errorOccurred(e)); - } - - public synchronized void getValue(String channelId) { - logger.debug("Requested value for {}", channelId); - if (expectedResponse.isEmpty()) { // Do not send a request is something is expected - String[] elements = channelId.split("#"); - String command = "Get" + elements[0].replaceAll(ANALOG_INPUT, "An").replaceAll(COUNTER, "Count") - .replaceAll(DIGITAL_INPUT, "An").replaceAll(RELAY_OUTPUT, "Out") + elements[1]; - setExpectedResponse(command); - connector.send(command); - } - } - - public void getOutputs() { - String command = "GetOutputs"; - setExpectedResponse(command); - connector.send(command); - } - - public void getInputs() { - String command = "GetInputs"; - setExpectedResponse(command); - connector.send(command); - } - - public void reset() { - connector.send("Reset"); - } -} diff --git a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800v3Handler.java b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800v3Handler.java index 28b2deb950b93..70650e31f6a46 100644 --- a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800v3Handler.java +++ b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800v3Handler.java @@ -18,37 +18,49 @@ 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.Optional; +import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.eclipse.smarthome.config.core.Configuration; +import org.eclipse.smarthome.core.library.CoreItemFactory; import org.eclipse.smarthome.core.library.types.DecimalType; import org.eclipse.smarthome.core.library.types.OnOffType; import org.eclipse.smarthome.core.library.types.OpenClosedType; import org.eclipse.smarthome.core.library.types.QuantityType; import org.eclipse.smarthome.core.library.unit.SmartHomeUnits; import org.eclipse.smarthome.core.thing.Channel; +import org.eclipse.smarthome.core.thing.ChannelGroupUID; import org.eclipse.smarthome.core.thing.ChannelUID; import org.eclipse.smarthome.core.thing.Thing; import org.eclipse.smarthome.core.thing.ThingStatus; import org.eclipse.smarthome.core.thing.ThingStatusDetail; import org.eclipse.smarthome.core.thing.binding.BaseThingHandler; import org.eclipse.smarthome.core.thing.binding.ThingHandlerService; +import org.eclipse.smarthome.core.thing.binding.builder.ChannelBuilder; +import org.eclipse.smarthome.core.thing.binding.builder.ThingBuilder; +import org.eclipse.smarthome.core.thing.type.ChannelKind; +import org.eclipse.smarthome.core.thing.type.ChannelTypeUID; import org.eclipse.smarthome.core.types.Command; import org.eclipse.smarthome.core.types.State; import org.eclipse.smarthome.core.types.UnDefType; import org.openhab.binding.gce.internal.action.Ipx800Actions; import org.openhab.binding.gce.internal.config.AnalogInputConfiguration; -import org.openhab.binding.gce.internal.config.CounterConfiguration; 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.model.M2MMessageParser; +import org.openhab.binding.gce.model.PortData; +import org.openhab.binding.gce.model.PortDefinition; +import org.openhab.binding.gce.model.StatusFileInterpreter; +import org.openhab.binding.gce.model.StatusFileInterpreter.StatusEntry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -61,12 +73,15 @@ @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 Optional parser = Optional.empty(); + private @Nullable M2MMessageParser parser; + private @NonNullByDefault({}) StatusFileInterpreter statusFile; + private @Nullable ScheduledFuture refreshJob; private final Map portDatas = new HashMap<>(); @@ -78,7 +93,7 @@ private class LongPressEvaluator implements Runnable { public LongPressEvaluator(Channel channel, String port, PortData portData) { this.referenceTime = portData.getTimestamp(); this.port = port; - this.eventChannelId = channel.getUID().getId() + PROPERTY_SEPARATOR + CHANNEL_TYPE_PUSH_BUTTON_TRIGGER; + this.eventChannelId = channel.getUID().getId() + PROPERTY_SEPARATOR + TRIGGER_CONTACT; } @Override @@ -102,115 +117,200 @@ public void initialize() { logger.debug("Initializing IPX800 handler for uid '{}'", getThing().getUID()); - connector = new Ipx800DeviceConnector(configuration.hostname, configuration.portNumber); - connector.setName("OH-binding-" + getThing().getUID()); - connector.setDaemon(true); + statusFile = new StatusFileInterpreter(configuration.hostname, this); - parser = Optional.of(new Ipx800MessageParser(connector, 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 properties = new HashMap<>(); + + properties.put(Thing.PROPERTY_VENDOR, "GC 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 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 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 @Nullable Channel getChannelForPort(String port) { - String portKind = port.substring(0, 1); - String portNum = port.replace(portKind, ""); - return thing.getChannel(portKind + "#" + portNum); + 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 = getChannelForPort(port); + Channel channel = thing.getChannel(PortDefinition.asChannelId(port)); if (channel != null) { - PortData portData = portDatas.get(channel.getUID().getId()); - if (portData != null) { - if (value == portData.getValue()) { - return; - } - + 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; - String groupId = channel.getUID().getGroupId(); - if (groupId != null) { - switch (groupId) { - case COUNTER: - state = new DecimalType(value); - break; - case ANALOG_INPUT: - AnalogInputConfiguration config = configuration.as(AnalogInputConfiguration.class); - long hysteresis = config.hysteresis / 2; - if (portData.isInitializing() || value > portData.getValue() + hysteresis - || value < portData.getValue() - hysteresis) { - state = new DecimalType(value); - } else { - return; - } - break; - case DIGITAL_INPUT: - DigitalInputConfiguration config2 = configuration.as(DigitalInputConfiguration.class); - if (config2.debouncePeriod != 0 && now.isBefore( - portData.getTimestamp().plus(config2.debouncePeriod, ChronoUnit.MILLIS))) { - return; - } - portData.cancelPulsing(); - if (value == 1) { - state = OpenClosedType.CLOSED; - if (portData.getValue() != -1) { - triggerPushButtonChannel(channel, EVENT_PRESSED); - } - if (config2.longPressTime != 0 && !portData.isInitializing()) { + 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), - config2.longPressTime, TimeUnit.MILLISECONDS); - } else if (config2.pulsePeriod != 0) { + config.longPressTime, TimeUnit.MILLISECONDS); + } else if (config.pulsePeriod != 0) { portData.setPulsing(scheduler.scheduleWithFixedDelay(() -> { triggerPushButtonChannel(channel, EVENT_PULSE); - }, config2.pulsePeriod, config2.pulsePeriod, TimeUnit.MILLISECONDS)); - if (config2.pulseTimeout != 0) { - scheduler.schedule(portData::cancelPulsing, config2.pulseTimeout, + }, config.pulsePeriod, config.pulsePeriod, TimeUnit.MILLISECONDS)); + if (config.pulseTimeout != 0) { + scheduler.schedule(portData::cancelPulsing, config.pulseTimeout, TimeUnit.MILLISECONDS); } } - } else { - state = OpenClosedType.OPEN; - if (!portData.isInitializing()) { - triggerPushButtonChannel(channel, EVENT_RELEASED); - if (config2.longPressTime != 0 && sinceLastChange < config2.longPressTime) { - triggerPushButtonChannel(channel, EVENT_SHORT_PRESS); - } + break; + case OPEN: + if (!portData.isInitializing() && config.longPressTime != 0 + && sinceLastChange < config.longPressTime) { + triggerPushButtonChannel(channel, EVENT_SHORT_PRESS); } - } - break; - case RELAY_OUTPUT: - state = value == 1 ? OnOffType.ON : OnOffType.OFF; - break; - } + break; + } + if (!portData.isInitializing()) { + triggerPushButtonChannel(channel, value == 1 ? EVENT_PRESSED : EVENT_RELEASED); + } + break; } - updateState(channel.getUID().getId(), state); + + updateState(channelId, state); if (!portData.isInitializing()) { - updateState(channel.getUID().getId() + PROPERTY_SEPARATOR + LAST_STATE_DURATION_CHANNEL_NAME, + updateState(channelId + PROPERTY_SEPARATOR + CHANNEL_LAST_STATE_DURATION, new QuantityType<>(sinceLastChange / 1000, SmartHomeUnits.SECOND)); } portData.setData(value, now); @@ -224,7 +324,7 @@ public void dataReceived(String port, double value) { protected void triggerPushButtonChannel(Channel channel, String event) { logger.debug("Triggering event '{}' on channel '{}'", event, channel.getUID()); - triggerChannel(channel.getUID().getId() + PROPERTY_SEPARATOR + CHANNEL_TYPE_PUSH_BUTTON_TRIGGER, event); + triggerChannel(channel.getUID().getId() + PROPERTY_SEPARATOR + TRIGGER_CONTACT, event); } @Override @@ -232,11 +332,18 @@ public void handleCommand(ChannelUID channelUID, Command command) { logger.debug("Received channel: {}, command: {}", channelUID, command); Channel channel = thing.getChannel(channelUID.getId()); - if (isValidPortId(channelUID) && RELAY_OUTPUT.equalsIgnoreCase(channelUID.getGroupId()) - && command instanceof OnOffType && channel != null) { + 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); - parser.ifPresent(p -> p.setOutput(channelUID.getIdWithoutGroup(), - (OnOffType) command == OnOffType.ON ? 1 : 0, config.pulse)); + 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); @@ -249,14 +356,7 @@ public void channelLinked(ChannelUID channelUID) { if (isValidPortId(channelUID)) { Channel channel = thing.getChannel(channelUID); if (channel != null) { - Configuration configuration = channel.getConfiguration(); PortData data = new PortData(); - if (configuration.get("pullInterval") != null) { - int pullInterval = configuration.as(CounterConfiguration.class).pullInterval; - data.setPullJob(scheduler.scheduleWithFixedDelay(() -> { - parser.ifPresent(p -> p.getValue(channelId)); - }, pullInterval, pullInterval, TimeUnit.MILLISECONDS)); - } portDatas.put(channelId, data); } } @@ -276,11 +376,15 @@ public void channelUnlinked(ChannelUID channelUID) { } public void resetCounter(int counter) { - parser.ifPresent(p -> p.resetCounter(counter)); + if (parser != null) { + parser.resetCounter(counter); + } } public void reset() { - parser.ifPresent(Ipx800MessageParser::reset); + if (parser != null) { + parser.resetPLC(); + } } @Override diff --git a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/model/M2MMessageParser.java b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/model/M2MMessageParser.java new file mode 100644 index 0000000000000..dde7215b100b8 --- /dev/null +++ b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/model/M2MMessageParser.java @@ -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.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"); + } +} diff --git a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/PortData.java b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/model/PortData.java similarity index 80% rename from bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/PortData.java rename to bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/model/PortData.java index 4c962af018409..536bf1f60b5d7 100644 --- a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/PortData.java +++ b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/model/PortData.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.gce.internal.handler; +package org.openhab.binding.gce.model; import java.time.ZonedDateTime; import java.util.concurrent.ScheduledFuture; @@ -28,7 +28,6 @@ public class PortData { private double value = -1; private ZonedDateTime timestamp = ZonedDateTime.now(); private @Nullable ScheduledFuture pulsing; - private @Nullable ScheduledFuture pullJob; public void cancelPulsing() { if (pulsing != null) { @@ -39,10 +38,6 @@ public void cancelPulsing() { public void destroy() { cancelPulsing(); - if (pullJob != null) { - pullJob.cancel(true); - } - pullJob = null; } public void setData(double value, ZonedDateTime timestamp) { @@ -58,18 +53,10 @@ public ZonedDateTime getTimestamp() { return timestamp; } - public @Nullable ScheduledFuture getPulsing() { - return pulsing; - } - public void setPulsing(ScheduledFuture pulsing) { this.pulsing = pulsing; } - public void setPullJob(ScheduledFuture pullJob) { - this.pullJob = pullJob; - } - public boolean isInitializing() { return value == -1; } diff --git a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/model/PortDefinition.java b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/model/PortDefinition.java new file mode 100644 index 0000000000000..5a302463593a5 --- /dev/null +++ b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/model/PortDefinition.java @@ -0,0 +1,87 @@ +/** + * 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.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 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); + } + +} diff --git a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/model/StatusFileInterpreter.java b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/model/StatusFileInterpreter.java new file mode 100644 index 0000000000000..5ebabd6ddfbe2 --- /dev/null +++ b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/model/StatusFileInterpreter.java @@ -0,0 +1,125 @@ +/** + * 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.model; + +import java.io.IOException; +import java.net.URL; +import java.net.URLConnection; +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.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(); + URL url = new URL(String.format(URL_TEMPLATE, hostname)); + + URLConnection conn = url.openConnection(); + Document document = builder.parse(conn.getInputStream()); + document.getDocumentElement().normalize(); + doc = document; + pushDatas(); + } 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 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 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 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; + } +} diff --git a/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/config/gceConfig.xml b/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/config/gceConfig.xml new file mode 100644 index 0000000000000..620e0ce8cf4ef --- /dev/null +++ b/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/config/gceConfig.xml @@ -0,0 +1,63 @@ + + + + + + network-address + + Network/IP address of the IPX800 without http(s) prefix. + + + + TCP client connection port. + 9870 + true + + + + Delay for pulling Analog and Counters info (in milliseconds). + 5000 + + + + + + + 0 + + + + Long press time in milliseconds. + 0 + + + + Pulse period in milliseconds. + 0 + + + + Maximum period for sending pulses in milliseconds. + 0 + + + + + + + false + + + + + + + 0 + + + + diff --git a/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/config/ipx800v3Config.xml b/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/config/ipx800v3Config.xml deleted file mode 100644 index f7f3e407cc8b6..0000000000000 --- a/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/config/ipx800v3Config.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - network-address - - Network/IP address of the IPX800 without http(s) prefix. - - - - TCP client connection port. - 9870 - true - - - diff --git a/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/thing/channels.xml b/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/thing/channels.xml index c1737e2a015df..2745ee3b8e22d 100644 --- a/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/thing/channels.xml +++ b/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/thing/channels.xml @@ -3,217 +3,51 @@ 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"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Contact Contact - - - - - 0 - - - - Long press time in milliseconds. - 0 - - - - Pulse period in milliseconds. - 0 - - - - Maximum period for sending pulses in milliseconds. - 0 - - + + + + + + Contact + + Contact + + - + + Switch + + + + + Switch - - - - false - - + Number - - - - Delay for pulling Analog and Counters info (in milliseconds). - 5000 - - + - + Number - - - - 0 - - - - Delay for pulling Analog and Counters info (in milliseconds). - 5000 - - + + + + + + Number + + @@ -223,7 +57,21 @@ - + + trigger + + + + + + + + + + + + + trigger @@ -236,5 +84,12 @@ + + + Number:ElectricPotential + + Voltage + + diff --git a/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/thing/ipx800v3.xml b/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/thing/ipx800v3.xml index cde1aa0bc6038..0136fdde1c385 100644 --- a/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/thing/ipx800v3.xml +++ b/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/thing/ipx800v3.xml @@ -8,12 +8,29 @@ The GCE IPX800v3 device - - - - + + + + + + + + + + + + + + + + + + + + + diff --git a/bundles/org.openhab.binding.gce/src/test/java/org/openhab/binding/gce/internal/test/Ipx800DeviceConnectorTest.java b/bundles/org.openhab.binding.gce/src/test/java/org/openhab/binding/gce/internal/test/Ipx800DeviceConnectorTest.java index df5c355af6d9b..80278717ba6ff 100644 --- a/bundles/org.openhab.binding.gce/src/test/java/org/openhab/binding/gce/internal/test/Ipx800DeviceConnectorTest.java +++ b/bundles/org.openhab.binding.gce/src/test/java/org/openhab/binding/gce/internal/test/Ipx800DeviceConnectorTest.java @@ -15,7 +15,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.junit.Test; import org.openhab.binding.gce.internal.handler.Ipx800DeviceConnector; -import org.openhab.binding.gce.internal.handler.Ipx800MessageParser; +import org.openhab.binding.gce.model.M2MMessageParser; /** * @@ -46,7 +46,7 @@ public void validateDeviceListModel() { // I=10000000000000000000000000000000&O=00000000000000000000000000000000&A0=0&A1=0&A2=0&A3=0&A4=0&A5=0&A6=0&A7=0&A8=0&A9=0&A10=0&A11=0&A12=0&A13=0&A14=0&A15=0&C1=2064&C2=1&C3=3&C4=4&C5=5&C6=6&C7=7&C8=8 Ipx800DeviceConnector connector = new Ipx800DeviceConnector("", 9870); - Ipx800MessageParser parser = new Ipx800MessageParser(connector, null); + M2MMessageParser parser = new M2MMessageParser(connector, null); parser.setExpectedResponse("GetOutputs"); parser.unsolicitedUpdate("01010010100101010101010101010101"); parser.setExpectedResponse("GetInputs"); From 3b40994c864653d7c70cc6260c7267ffa8a39ad0 Mon Sep 17 00:00:00 2001 From: clinique Date: Fri, 11 Sep 2020 19:30:28 +0200 Subject: [PATCH 10/17] Spotless corrections Signed-off-by: clinique --- .../binding/gce/internal/GCEBindingConstants.java | 1 - .../binding/gce/internal/action/Ipx800Actions.java | 1 - .../binding/gce/internal/handler/Ipx800v3Handler.java | 1 - .../org/openhab/binding/gce/model/PortDefinition.java | 1 - .../src/main/resources/ESH-INF/config/gceConfig.xml | 8 ++++---- .../src/main/resources/ESH-INF/thing/channels.xml | 10 +++++----- .../src/main/resources/ESH-INF/thing/ipx800v3.xml | 10 +++++----- 7 files changed, 14 insertions(+), 18 deletions(-) diff --git a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/GCEBindingConstants.java b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/GCEBindingConstants.java index 1c0449cb2a24a..e23b1dbd8ac98 100644 --- a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/GCEBindingConstants.java +++ b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/GCEBindingConstants.java @@ -40,5 +40,4 @@ public class GCEBindingConstants { public static final String EVENT_PULSE = "PULSE"; // Adressable thing - } diff --git a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/action/Ipx800Actions.java b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/action/Ipx800Actions.java index dabbea8774024..cfc07b3ee9697 100644 --- a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/action/Ipx800Actions.java +++ b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/action/Ipx800Actions.java @@ -111,5 +111,4 @@ private static IIpx800Actions invokeMethodOf(@Nullable ThingActions actions) { } throw new IllegalArgumentException("Actions is not an instance of Ipx800Actions"); } - } diff --git a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800v3Handler.java b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800v3Handler.java index 70650e31f6a46..6a048ecf89cf2 100644 --- a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800v3Handler.java +++ b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800v3Handler.java @@ -212,7 +212,6 @@ private void createChannels(PortDefinition portDefinition, int portIndex, List v.portName.startsWith(portKind)).findFirst().get(); return result.toString() + "#" + portDefinition.substring(1); } - } diff --git a/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/config/gceConfig.xml b/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/config/gceConfig.xml index 620e0ce8cf4ef..4f3b11b4afefd 100644 --- a/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/config/gceConfig.xml +++ b/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/config/gceConfig.xml @@ -23,7 +23,7 @@ 5000 - + @@ -45,19 +45,19 @@ 0 - + false - + 0 - + diff --git a/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/thing/channels.xml b/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/thing/channels.xml index 2745ee3b8e22d..4821a254463fa 100644 --- a/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/thing/channels.xml +++ b/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/thing/channels.xml @@ -10,7 +10,7 @@ - + Contact @@ -24,7 +24,7 @@ - + Switch @@ -43,7 +43,7 @@ - + Number @@ -70,7 +70,7 @@ - + trigger @@ -84,7 +84,7 @@ - + Number:ElectricPotential diff --git a/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/thing/ipx800v3.xml b/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/thing/ipx800v3.xml index 0136fdde1c385..2b1156ffaf258 100644 --- a/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/thing/ipx800v3.xml +++ b/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/thing/ipx800v3.xml @@ -16,21 +16,21 @@ - + - + - + - + - + From 954a22c8d07e92736930f3871fdd2d91559ec82a Mon Sep 17 00:00:00 2001 From: clinique Date: Fri, 11 Sep 2020 20:01:49 +0200 Subject: [PATCH 11/17] Test correction Signed-off-by: clinique --- bundles/org.openhab.binding.gce/README.md | 7 ++++--- .../gce/internal/test/Ipx800DeviceConnectorTest.java | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/bundles/org.openhab.binding.gce/README.md b/bundles/org.openhab.binding.gce/README.md index 26d96afcf2bed..ace919d679400 100644 --- a/bundles/org.openhab.binding.gce/README.md +++ b/bundles/org.openhab.binding.gce/README.md @@ -37,14 +37,15 @@ The IPX800v3 (ID : 'ipx800v3') accepts the following configuration parameters : | 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 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... +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) diff --git a/bundles/org.openhab.binding.gce/src/test/java/org/openhab/binding/gce/internal/test/Ipx800DeviceConnectorTest.java b/bundles/org.openhab.binding.gce/src/test/java/org/openhab/binding/gce/internal/test/Ipx800DeviceConnectorTest.java index 80278717ba6ff..8347f4aca1196 100644 --- a/bundles/org.openhab.binding.gce/src/test/java/org/openhab/binding/gce/internal/test/Ipx800DeviceConnectorTest.java +++ b/bundles/org.openhab.binding.gce/src/test/java/org/openhab/binding/gce/internal/test/Ipx800DeviceConnectorTest.java @@ -45,7 +45,7 @@ public void validateDeviceListModel() { // Command : // I=10000000000000000000000000000000&O=00000000000000000000000000000000&A0=0&A1=0&A2=0&A3=0&A4=0&A5=0&A6=0&A7=0&A8=0&A9=0&A10=0&A11=0&A12=0&A13=0&A14=0&A15=0&C1=2064&C2=1&C3=3&C4=4&C5=5&C6=6&C7=7&C8=8 - Ipx800DeviceConnector connector = new Ipx800DeviceConnector("", 9870); + Ipx800DeviceConnector connector = new Ipx800DeviceConnector("", 9870, "test"); M2MMessageParser parser = new M2MMessageParser(connector, null); parser.setExpectedResponse("GetOutputs"); parser.unsolicitedUpdate("01010010100101010101010101010101"); From c3f7a2e2f4ec08eea875fd66bb3a4d44f9774a03 Mon Sep 17 00:00:00 2001 From: clinique Date: Fri, 11 Sep 2020 21:09:10 +0200 Subject: [PATCH 12/17] Correcting tests Signed-off-by: clinique --- .../binding/gce/internal/test/Ipx800DeviceConnectorTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bundles/org.openhab.binding.gce/src/test/java/org/openhab/binding/gce/internal/test/Ipx800DeviceConnectorTest.java b/bundles/org.openhab.binding.gce/src/test/java/org/openhab/binding/gce/internal/test/Ipx800DeviceConnectorTest.java index 8347f4aca1196..46dbc181f63a2 100644 --- a/bundles/org.openhab.binding.gce/src/test/java/org/openhab/binding/gce/internal/test/Ipx800DeviceConnectorTest.java +++ b/bundles/org.openhab.binding.gce/src/test/java/org/openhab/binding/gce/internal/test/Ipx800DeviceConnectorTest.java @@ -13,6 +13,7 @@ package org.openhab.binding.gce.internal.test; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.smarthome.core.thing.ThingUID; import org.junit.Test; import org.openhab.binding.gce.internal.handler.Ipx800DeviceConnector; import org.openhab.binding.gce.model.M2MMessageParser; @@ -45,7 +46,7 @@ public void validateDeviceListModel() { // Command : // I=10000000000000000000000000000000&O=00000000000000000000000000000000&A0=0&A1=0&A2=0&A3=0&A4=0&A5=0&A6=0&A7=0&A8=0&A9=0&A10=0&A11=0&A12=0&A13=0&A14=0&A15=0&C1=2064&C2=1&C3=3&C4=4&C5=5&C6=6&C7=7&C8=8 - Ipx800DeviceConnector connector = new Ipx800DeviceConnector("", 9870, "test"); + Ipx800DeviceConnector connector = new Ipx800DeviceConnector("", 9870, new ThingUID("test")); M2MMessageParser parser = new M2MMessageParser(connector, null); parser.setExpectedResponse("GetOutputs"); parser.unsolicitedUpdate("01010010100101010101010101010101"); From 21386d89846169c3eb8bb2eaeae6cc1f131f7168 Mon Sep 17 00:00:00 2001 From: clinique Date: Fri, 11 Sep 2020 21:37:46 +0200 Subject: [PATCH 13/17] REmoving pointless tests Signed-off-by: clinique --- .../test/Ipx800DeviceConnectorTest.java | 66 ------------------- 1 file changed, 66 deletions(-) delete mode 100644 bundles/org.openhab.binding.gce/src/test/java/org/openhab/binding/gce/internal/test/Ipx800DeviceConnectorTest.java diff --git a/bundles/org.openhab.binding.gce/src/test/java/org/openhab/binding/gce/internal/test/Ipx800DeviceConnectorTest.java b/bundles/org.openhab.binding.gce/src/test/java/org/openhab/binding/gce/internal/test/Ipx800DeviceConnectorTest.java deleted file mode 100644 index 46dbc181f63a2..0000000000000 --- a/bundles/org.openhab.binding.gce/src/test/java/org/openhab/binding/gce/internal/test/Ipx800DeviceConnectorTest.java +++ /dev/null @@ -1,66 +0,0 @@ -/** - * 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.test; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.smarthome.core.thing.ThingUID; -import org.junit.Test; -import org.openhab.binding.gce.internal.handler.Ipx800DeviceConnector; -import org.openhab.binding.gce.model.M2MMessageParser; - -/** - * - * @author Seebag - Initial contribution - * @author Gaël L'hopital - Ported and adapted for OH2 - * - */ -@NonNullByDefault -public class Ipx800DeviceConnectorTest { - @Test - public void validateDeviceListModel() { - // Example of - // I=00000000000000000000000000000000&O=10000000000000000000000000000000&\ - // A0=0&A1=0&A2=0&A3=0&A4=0&A5=0&A6=0&A7=0&A8=0&A9=0&A10=0&A11=0&A12=0&A13=0&A14=0&A15=0&C1=47&C2=0&C3=0&C4=0&C5=0&C6=0&C7=0&C8=0 - // Command : - // I=01000000000000000000000000000000&O=01000000000000000000000000000000&A0=0&A1=0&A2=0&A3=0&A4=0&A5=0&A6=0&A7=0&A8=0&A9=0&A10=0&A11=0&A12=0&A13=0&A14=0&A15=0&C1=2064&C2=1&C3=3&C4=4&C5=5&C6=6&C7=7&C8=8 - // Command : - // I=01000000000000000000000000000000&O=00000000000000000000000000000000&A0=0&A1=0&A2=0&A3=0&A4=0&A5=0&A6=0&A7=0&A8=0&A9=0&A10=0&A11=0&A12=0&A13=0&A14=0&A15=0&C1=2064&C2=1&C3=3&C4=4&C5=5&C6=6&C7=7&C8=8 - // Command : - // I=01000000000000000000000000000000&O=00000001000000000000000000000000&A0=0&A1=0&A2=0&A3=0&A4=0&A5=0&A6=0&A7=0&A8=0&A9=0&A10=0&A11=0&A12=0&A13=0&A14=0&A15=0&C1=2064&C2=1&C3=3&C4=4&C5=5&C6=6&C7=7&C8=8 - // Command : - // I=00000000000000000000000000000000&O=00000001000000000000000000000000&A0=0&A1=0&A2=0&A3=0&A4=0&A5=0&A6=0&A7=0&A8=0&A9=0&A10=0&A11=0&A12=0&A13=0&A14=0&A15=0&C1=2064&C2=1&C3=3&C4=4&C5=5&C6=6&C7=7&C8=8 - // Command : - // I=01000000000000000000000000000000&O=00000000000000000000000000000000&A0=0&A1=0&A2=0&A3=0&A4=0&A5=0&A6=0&A7=0&A8=0&A9=0&A10=0&A11=0&A12=0&A13=0&A14=0&A15=0&C1=2064&C2=1&C3=3&C4=4&C5=5&C6=6&C7=7&C8=8 - // Command : - // I=00000000000000000000000000000000&O=00000000000000000000000000000000&A0=0&A1=0&A2=0&A3=0&A4=0&A5=0&A6=0&A7=0&A8=0&A9=0&A10=0&A11=0&A12=0&A13=0&A14=0&A15=0&C1=2064&C2=1&C3=3&C4=4&C5=5&C6=6&C7=7&C8=8 - // Command : - // I=10000000000000000000000000000000&O=00000000000000000000000000000000&A0=0&A1=0&A2=0&A3=0&A4=0&A5=0&A6=0&A7=0&A8=0&A9=0&A10=0&A11=0&A12=0&A13=0&A14=0&A15=0&C1=2064&C2=1&C3=3&C4=4&C5=5&C6=6&C7=7&C8=8 - - Ipx800DeviceConnector connector = new Ipx800DeviceConnector("", 9870, new ThingUID("test")); - M2MMessageParser parser = new M2MMessageParser(connector, null); - parser.setExpectedResponse("GetOutputs"); - parser.unsolicitedUpdate("01010010100101010101010101010101"); - parser.setExpectedResponse("GetInputs"); - parser.unsolicitedUpdate("01000000234005670000000000000000"); - parser.unsolicitedUpdate( - "I=01000000000000000000000000000000&O=01000000000000000000000000000000&A0=0&A1=0&A2=0&A3=0&A4=0&A5=0&A6=0&A7=0&A8=0&A9=0&A10=0&A11=0&A12=0&A13=0&A14=0&A15=0&C1=2064&C2=1&C3=3&C4=4&C5=5&C6=6&C7=7&C8=8"); - parser.setExpectedResponse("GetAn1"); - parser.unsolicitedUpdate("234"); - parser.setExpectedResponse("GetCount12"); - parser.unsolicitedUpdate("4294967295"); // Test maximum value - parser.setExpectedResponse("GetIn5"); - parser.unsolicitedUpdate("1"); - parser.setExpectedResponse("GetOut3"); - parser.unsolicitedUpdate("0"); - } -} From d8fa6cad4d90e9f20ed61f19f8e19bd1f127acc5 Mon Sep 17 00:00:00 2001 From: clinique Date: Sat, 12 Sep 2020 09:24:57 +0200 Subject: [PATCH 14/17] Code review corrections Signed-off-by: clinique --- bundles/org.openhab.binding.gce/README.md | 10 ++++------ .../gce/internal/handler/Ipx800DeviceConnector.java | 2 +- .../gce/internal/handler/Ipx800v3Handler.java | 12 ++++++------ .../gce/{ => internal}/model/M2MMessageParser.java | 2 +- .../binding/gce/{ => internal}/model/PortData.java | 2 +- .../gce/{ => internal}/model/PortDefinition.java | 2 +- .../{ => internal}/model/StatusFileInterpreter.java | 3 ++- 7 files changed, 16 insertions(+), 17 deletions(-) rename bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/{ => internal}/model/M2MMessageParser.java (99%) rename bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/{ => internal}/model/PortData.java (96%) rename bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/{ => internal}/model/PortDefinition.java (98%) rename bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/{ => internal}/model/StatusFileInterpreter.java (97%) diff --git a/bundles/org.openhab.binding.gce/README.md b/bundles/org.openhab.binding.gce/README.md index ace919d679400..c3f2f8ed95b90 100644 --- a/bundles/org.openhab.binding.gce/README.md +++ b/bundles/org.openhab.binding.gce/README.md @@ -4,7 +4,7 @@ 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) +* 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 @@ -119,11 +119,9 @@ Each analog port will have these associated channels: #### Configuration -| Property | Default | Description | -|------------|---------|--------------------------------------------------------------------------| -| histeresis | 0 | If set, the channel will ignore status changes below state + histeresis/2| -| | | or higher than state - histeresis sur 2 | - +| Property | Default | Description | +|------------|---------|-------------------------------------------------------------------------------------| +| hysteresis | 0 | If set, the channel will ignore status if change (+ or -) is less than hysteresis/2 | ## Rule Actions diff --git a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800DeviceConnector.java b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800DeviceConnector.java index 9b7c996a8d5bd..771ee566f3aa9 100644 --- a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800DeviceConnector.java +++ b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800DeviceConnector.java @@ -22,7 +22,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.eclipse.smarthome.core.thing.ThingUID; -import org.openhab.binding.gce.model.M2MMessageParser; +import org.openhab.binding.gce.internal.model.M2MMessageParser; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800v3Handler.java b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800v3Handler.java index 6a048ecf89cf2..6b1bc3b650e85 100644 --- a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800v3Handler.java +++ b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800v3Handler.java @@ -56,11 +56,11 @@ 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.model.M2MMessageParser; -import org.openhab.binding.gce.model.PortData; -import org.openhab.binding.gce.model.PortDefinition; -import org.openhab.binding.gce.model.StatusFileInterpreter; -import org.openhab.binding.gce.model.StatusFileInterpreter.StatusEntry; +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.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -158,7 +158,7 @@ public void dispose() { protected void discoverAttributes() { final Map properties = new HashMap<>(); - properties.put(Thing.PROPERTY_VENDOR, "GC Electronics"); + 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); diff --git a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/model/M2MMessageParser.java b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/model/M2MMessageParser.java similarity index 99% rename from bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/model/M2MMessageParser.java rename to bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/model/M2MMessageParser.java index dde7215b100b8..89a0c378288f3 100644 --- a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/model/M2MMessageParser.java +++ b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/model/M2MMessageParser.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.gce.model; +package org.openhab.binding.gce.internal.model; import java.util.regex.Pattern; diff --git a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/model/PortData.java b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/model/PortData.java similarity index 96% rename from bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/model/PortData.java rename to bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/model/PortData.java index 536bf1f60b5d7..4b4f923705aaa 100644 --- a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/model/PortData.java +++ b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/model/PortData.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.gce.model; +package org.openhab.binding.gce.internal.model; import java.time.ZonedDateTime; import java.util.concurrent.ScheduledFuture; diff --git a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/model/PortDefinition.java b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/model/PortDefinition.java similarity index 98% rename from bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/model/PortDefinition.java rename to bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/model/PortDefinition.java index a9f1afad36acd..1d9286dababc7 100644 --- a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/model/PortDefinition.java +++ b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/model/PortDefinition.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.gce.model; +package org.openhab.binding.gce.internal.model; import java.util.stream.Stream; diff --git a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/model/StatusFileInterpreter.java b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/model/StatusFileInterpreter.java similarity index 97% rename from bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/model/StatusFileInterpreter.java rename to bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/model/StatusFileInterpreter.java index 5ebabd6ddfbe2..f1134d78510d8 100644 --- a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/model/StatusFileInterpreter.java +++ b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/model/StatusFileInterpreter.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.gce.model; +package org.openhab.binding.gce.internal.model; import java.io.IOException; import java.net.URL; @@ -68,6 +68,7 @@ public void read() { document.getDocumentElement().normalize(); doc = document; pushDatas(); + conn.getInputStream().close(); } catch (IOException | SAXException | ParserConfigurationException e) { logger.warn("Unable to read IPX800 status page : {}", e.getMessage()); doc = null; From 672267acff2a87fdf680c02ff5422c1796d040f3 Mon Sep 17 00:00:00 2001 From: clinique Date: Sat, 12 Sep 2020 15:10:30 +0200 Subject: [PATCH 15/17] Forgot bom/openhab-addons/pom.xml Signed-off-by: clinique --- bom/openhab-addons/pom.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/bom/openhab-addons/pom.xml b/bom/openhab-addons/pom.xml index d3641115fc4a5..b3f63fd758cdb 100644 --- a/bom/openhab-addons/pom.xml +++ b/bom/openhab-addons/pom.xml @@ -339,6 +339,11 @@ org.openhab.binding.gardena ${project.version} + + org.openhab.addons.bundles + org.openhab.binding.gce + ${project.version} + org.openhab.addons.bundles org.openhab.binding.goecharger From 4394d2f80f209b483d0f36decda7b656a2a6e5c3 Mon Sep 17 00:00:00 2001 From: clinique Date: Tue, 15 Sep 2020 11:34:31 +0200 Subject: [PATCH 16/17] Code review corrections Signed-off-by: clinique --- bundles/org.openhab.binding.gce/README.md | 37 +++++++++++++++++++ .../gce/internal/handler/Ipx800v3Handler.java | 5 +-- .../internal/model/StatusFileInterpreter.java | 14 +++---- .../main/resources/ESH-INF/thing/ipx800v3.xml | 16 ++++---- 4 files changed, 54 insertions(+), 18 deletions(-) diff --git a/bundles/org.openhab.binding.gce/README.md b/bundles/org.openhab.binding.gce/README.md index c3f2f8ed95b90..2738cd3fb34df 100644 --- a/bundles/org.openhab.binding.gce/README.md +++ b/bundles/org.openhab.binding.gce/README.md @@ -153,6 +153,43 @@ 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" +Contact input1 "Porte garage ouverte [%s]" (gIPXInputs) {channel="gce:ipx800v3:ipx:contact#1"} +Contact input2 "Porte garage fermée [%s]" (gIPXInputs) {channel="gce:ipx800v3:ipx:contact#2"} + +Group gIPXOutputs "Outputs" +Switch output3 "Chaudière" (gIPXOutputs) {channel="gce:ipx800v3:ipx:relay#3"} +Switch output4 "Lumière Porche" (gIPXOutputs) {channel="gce:ipx800v3:ipx:relay#4"} + +``` ``` ``` diff --git a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800v3Handler.java b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800v3Handler.java index 6b1bc3b650e85..317a2ec088963 100644 --- a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800v3Handler.java +++ b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/handler/Ipx800v3Handler.java @@ -128,9 +128,8 @@ public void initialize() { updateStatus(ThingStatus.UNKNOWN); - refreshJob = scheduler.scheduleWithFixedDelay(() -> { - statusFile.read(); - }, 3000, configuration.pullInterval, TimeUnit.MILLISECONDS); + refreshJob = scheduler.scheduleWithFixedDelay(statusFile::read, 3000, configuration.pullInterval, + TimeUnit.MILLISECONDS); connector.start(); } diff --git a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/model/StatusFileInterpreter.java b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/model/StatusFileInterpreter.java index f1134d78510d8..07a5aa7032ab1 100644 --- a/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/model/StatusFileInterpreter.java +++ b/bundles/org.openhab.binding.gce/src/main/java/org/openhab/binding/gce/internal/model/StatusFileInterpreter.java @@ -12,9 +12,9 @@ */ package org.openhab.binding.gce.internal.model; +import java.io.ByteArrayInputStream; import java.io.IOException; -import java.net.URL; -import java.net.URLConnection; +import java.io.InputStream; import java.util.Comparator; import java.util.List; import java.util.stream.Collectors; @@ -26,6 +26,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.smarthome.io.net.http.HttpUtil; import org.openhab.binding.gce.internal.handler.Ipx800EventListener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -61,14 +62,13 @@ public StatusFileInterpreter(String hostname, Ipx800EventListener listener) { public void read() { try { DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); - URL url = new URL(String.format(URL_TEMPLATE, hostname)); - - URLConnection conn = url.openConnection(); - Document document = builder.parse(conn.getInputStream()); + 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(); - conn.getInputStream().close(); + inputStream.close(); } catch (IOException | SAXException | ParserConfigurationException e) { logger.warn("Unable to read IPX800 status page : {}", e.getMessage()); doc = null; diff --git a/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/thing/ipx800v3.xml b/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/thing/ipx800v3.xml index 2b1156ffaf258..21fecb448f505 100644 --- a/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/thing/ipx800v3.xml +++ b/bundles/org.openhab.binding.gce/src/main/resources/ESH-INF/thing/ipx800v3.xml @@ -8,28 +8,28 @@ The GCE IPX800v3 device - - - - + + + + - + - + - + - + From 50c2b411aa0a1c28e9a6a542757bd09991b793e6 Mon Sep 17 00:00:00 2001 From: Hilbrand Bouwkamp Date: Tue, 15 Sep 2020 13:08:34 +0200 Subject: [PATCH 17/17] Removed empty block --- bundles/org.openhab.binding.gce/README.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/bundles/org.openhab.binding.gce/README.md b/bundles/org.openhab.binding.gce/README.md index 2738cd3fb34df..0e57097f1cee7 100644 --- a/bundles/org.openhab.binding.gce/README.md +++ b/bundles/org.openhab.binding.gce/README.md @@ -190,8 +190,3 @@ Switch output3 "Chaudière" (gIPXOutputs) {channel="gce:ipx800v3:ipx:r Switch output4 "Lumière Porche" (gIPXOutputs) {channel="gce:ipx800v3:ipx:relay#4"} ``` - -``` -``` - -