added migrated 2.x add-ons
Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
33
bundles/org.openhab.binding.plclogo/.classpath
Normal file
33
bundles/org.openhab.binding.plclogo/.classpath
Normal file
@@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="src" output="target/classes" path="src/main/java">
|
||||
<attributes>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="lib" path="lib/Moka7-1.0.2_io_patch.jar"/>
|
||||
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
|
||||
<attributes>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
<attribute name="test" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="output" path="target/classes"/>
|
||||
</classpath>
|
||||
23
bundles/org.openhab.binding.plclogo/.project
Normal file
23
bundles/org.openhab.binding.plclogo/.project
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>org.openhab.binding.plclogo</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.m2e.core.maven2Builder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
||||
21
bundles/org.openhab.binding.plclogo/NOTICE
Normal file
21
bundles/org.openhab.binding.plclogo/NOTICE
Normal file
@@ -0,0 +1,21 @@
|
||||
This content is produced and maintained by the openHAB project.
|
||||
|
||||
* Project home: https://www.openhab.org
|
||||
|
||||
== Declared Project Licenses
|
||||
|
||||
This program and the accompanying materials are made available under the terms
|
||||
of the Eclipse Public License 2.0 which is available at
|
||||
https://www.eclipse.org/legal/epl-2.0/.
|
||||
|
||||
== Source Code
|
||||
|
||||
https://github.com/openhab/openhab-addons
|
||||
|
||||
== Third-party Content
|
||||
|
||||
Moka7
|
||||
* License: EPL 1.0 License
|
||||
* Project: http://snap7.sourceforge.net/moka7.html
|
||||
* Source: https://sourceforge.net/p/snap7/discussion/bugfix/thread/0cd95d96
|
||||
for a send of single bit was integrated into this copy of the library
|
||||
387
bundles/org.openhab.binding.plclogo/README.md
Normal file
387
bundles/org.openhab.binding.plclogo/README.md
Normal file
@@ -0,0 +1,387 @@
|
||||
# PLCLogo Binding
|
||||
|
||||
This binding provides native support of Siemens LOGO! PLC devices.
|
||||
Communication with LOGO! is done via Moka7 library.
|
||||
Currently only two devices are supported: `0BA7` (LOGO! 7) and `0BA8` (LOGO! 8).
|
||||
Additionally multiple devices are supported.
|
||||
Different families of LOGO! devices should work also, but was not tested now due to lack of hardware.
|
||||
Binding works nicely at least 100ms polling rate, if network connection is stable.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- Changing of block parameter while running the binding may kill your LOGO!, so that program flashing via LOGO! SoftComort
|
||||
will be required. Furthermore programs within LOGO! SoftComfort and LOGO! itself will differ, so that online simulation
|
||||
will not work anymore without program synchronisation.
|
||||
|
||||
- Flashing the LOGO! while running the binding may crash the network interface of your LOGO!. Before flashing the LOGO!
|
||||
with LOGO! SoftComfort stop openHAB service. If network interface is crashed, no reader could be created for this
|
||||
device. See troubleshooting section below how to recover.
|
||||
|
||||
## Discovery
|
||||
|
||||
Siemens LOGO! devices can be manually discovered by sending a request to every IP on the network.
|
||||
This functionality should be used with caution, because it produces heavy load to the operating hardware.
|
||||
For this reason, the binding does not do an automatic background discovery, but discovery can be triggered manually.
|
||||
|
||||
## Bridge configuration
|
||||
|
||||
Every Siemens LOGO! PLC is configured as bridge:
|
||||
|
||||
```
|
||||
Bridge plclogo:device:<DeviceId> [ address="<ip>", family="<0BA7/0BA8>", localTSAP="0x<number>", remoteTSAP="0x<number>", refresh=<number> ]
|
||||
```
|
||||
|
||||
| Parameter | Type | Required | Default | Description |
|
||||
| ---------- | :-----: | :--------: | :-------: | ---------------------------------------------------------------- |
|
||||
| address | String | Yes | | IP address of the LOGO! PLC. |
|
||||
| family | String | Yes | | LOGO! family to communicate with. Can be `0BA7` or `0BA8` now. |
|
||||
| localTSAP | String | Yes | | TSAP (as hex) is used by the local instance. Check configuration |
|
||||
| | | | | in LOGO!Soft Comfort. Common used value is `0x3000`. |
|
||||
| remoteTSAP | String | Yes | | TSAP (as hex) of the remote LOGO! PLC, as configured by |
|
||||
| | | | | LOGO!Soft Comfort. Common used value is `0x2000`. |
|
||||
| refresh | Integer | No | 100ms | Polling interval, in milliseconds. Is used for query the LOGO!. |
|
||||
|
||||
Be sure not to use the same values for localTSAP and remoteTSAP, if configure more than one LOGO!
|
||||
|
||||
## Thing configuration
|
||||
|
||||
Binding supports four types of things: digital, analog, memory and datetime.
|
||||
|
||||
### Digital Things
|
||||
|
||||
The configuration pattern for digital things is:
|
||||
|
||||
```
|
||||
Thing digital <ThingId> "Label" @ "Location" [ kind="<kind>", force=<true/false> ]
|
||||
```
|
||||
|
||||
| Parameter | Type | Required | Default | Description |
|
||||
| --------- | :-----: | :--------: | :-------: | ------------------------------------------------------------ |
|
||||
| kind | String | Yes | | Blocks kind |
|
||||
| force | Boolean | No | false | Send current value to openHAB, independent if changed or not |
|
||||
|
||||
Follow block kinds are allowed for digital things:
|
||||
|
||||
| Type | `0BA7` | `0BA8` |
|
||||
| -------------- | :----: | ------ |
|
||||
| Input | `I` | `I` |
|
||||
| Output | `Q` | `Q` |
|
||||
| Marker | `M` | `M` |
|
||||
| Network input | | `NI` |
|
||||
| Network output | | `NQ` |
|
||||
|
||||
### Analog Things
|
||||
|
||||
The configuration pattern for analog things is:
|
||||
|
||||
```
|
||||
Thing analog <ThingId> "Label" @ "Location" [ kind="<kind>", threshold=<number>, force=<true/false> ]
|
||||
```
|
||||
|
||||
| Parameter | Type | Required | Default | Description |
|
||||
| --------- | :-----: | :--------: | :-------: | ------------------------------------------------------------- |
|
||||
| kind | String | Yes | | Blocks kind |
|
||||
| threshold | Integer | No | 0 | Send current value to openHAB, if changed more than threshold |
|
||||
| force | Boolean | No | false | Send current value to openHAB, independent if changed or not |
|
||||
|
||||
Follow block kinds are allowed for analog things:
|
||||
|
||||
| Type | `0BA7` | `0BA8` |
|
||||
| -------------- | :----: | ------ |
|
||||
| Input | `AI` | `AI` |
|
||||
| Output | `AQ` | `AQ` |
|
||||
| Marker | `AM` | `AM` |
|
||||
| Network input | | `NAI` |
|
||||
| Network output | | `NAQ` |
|
||||
|
||||
### Memory Things
|
||||
|
||||
The configuration pattern for analog things is:
|
||||
|
||||
```
|
||||
Thing memory <ThingId> "Label" @ "Location" [ block="<name>", threshold=<number>, force=<true/false> ]
|
||||
```
|
||||
|
||||
Follow block names are allowed for memory things:
|
||||
|
||||
| Type | `0BA7` | `0BA8` |
|
||||
| ----- | :---------------: | ----------------- |
|
||||
| Bit | `VB[0-850].[0-7]` | `VB[0-850].[0-7]` |
|
||||
| Byte | `VB[0-850]` | `VB[0-850]` |
|
||||
| Word | `VW[0-849]` | `VW[0-849]` |
|
||||
| DWord | `VD[0-847]` | `VD[0-847]` |
|
||||
|
||||
Parameter `threshold` will be taken into account for Byte, Word and DWord, i.e Number items, only.
|
||||
|
||||
### DateTime Things
|
||||
|
||||
The configuration pattern for datetime things is:
|
||||
|
||||
```
|
||||
Thing datetime <ThingId> "Label" @ "Location" [ block="<name>", type=<type>, force=<true/false> ]
|
||||
```
|
||||
|
||||
Follow block names are allowed for datetime things:
|
||||
|
||||
| Type | `0BA7` | `0BA8` |
|
||||
| ----- | :---------: | ----------- |
|
||||
| Word | `VW[0-849]` | `VW[0-849]` |
|
||||
|
||||
If parameter `type` is `"date"`, then the binding will try to interpret incoming data as calendar date.
|
||||
The time this case will be taken from openHAB host.
|
||||
If `type` is set to `"time"`, then incoming data will be tried to interpret as time of day.
|
||||
The date this case will be taken from openHAB host.
|
||||
|
||||
### Pulse Things
|
||||
|
||||
The configuration pattern for pulse things is:
|
||||
|
||||
```
|
||||
Thing pulse <ThingId> "Label" @ "Location" [ block="<name>", observe="<name>", pulse=<number> ]
|
||||
```
|
||||
|
||||
Follow block names are allowed for pulse things:
|
||||
|
||||
| Type | `0BA7` | `0BA8` |
|
||||
| ----- | :---------------: | ----------------- |
|
||||
| Bit | `VB[0-850].[0-7]` | `VB[0-850].[0-7]` |
|
||||
|
||||
Follow observed block names are allowed for pulse things:
|
||||
|
||||
| Type | `0BA7` | `0BA8` |
|
||||
| ----- | :---------------: | ----------------- |
|
||||
| Bit | `VB[0-850].[0-7]` | `VB[0-850].[0-7]` |
|
||||
| Bit | `I[1-24]` | `I[1-24]` |
|
||||
| Bit | `Q[1-16]` | `Q[1-20]` |
|
||||
| Bit | `M[1-27]` | `M[1-64]` |
|
||||
| Bit | | `NI[1-64]` |
|
||||
| Bit | | `NQ[1-64]` |
|
||||
|
||||
If `observe` is not set or set equal `block`, simply pulse with length `pulse` milliseconds is send to `block`.
|
||||
If `observe` is set and differ from `block`, binding will wait for value change on `observe` and send then a pulse with length `pulse` milliseconds to block.
|
||||
Please note, update rate for change detection depends on bridge refresh value.
|
||||
For both use cases: if `block` was `0` then `1` is send and vice versa.
|
||||
|
||||
## Channels
|
||||
|
||||
### Bridge
|
||||
|
||||
Each device have currently three channels `diagnostic`, `rtc` and `weekday`:
|
||||
|
||||
```
|
||||
channel="plclogo:device:<DeviceId>:diagnostic"
|
||||
channel="plclogo:device:<DeviceId>:rtc"
|
||||
channel="plclogo:device:<DeviceId>:weekday"
|
||||
```
|
||||
|
||||
Channels `diagnostic` and `weekday` supports `String` items. Channel `diagnostic` contains the last diagnostic message reported by LOGO!.
|
||||
Channel `weekday` contains current day of the week.
|
||||
The value is provided by LOGO!.
|
||||
Channel `rtc` supports `DateTime` items only. Since Siemens `0BA7` (LOGO! 7) devices will not transfer any useful data for this channel, local time of openHAB host will be used.
|
||||
Rather for Siemens `0BA8` (LOGO! 8) devices, the data will be read from PLC.
|
||||
Since the smallest resolution provided by LOGO! is one second, `rtc` channel will be tried to update with the same rate.
|
||||
|
||||
### Digital
|
||||
|
||||
Format pattern for digital channels is
|
||||
|
||||
```
|
||||
channel="plclogo:digital:<DeviceId>:<ThingId>:<Channel>"
|
||||
```
|
||||
|
||||
Dependent on configured LOGO! PLC and thing kind, follow channels are available:
|
||||
|
||||
| Kind | `0BA7` | `0BA8` | Item |
|
||||
| ---- | :-------: | :--------: | --------- |
|
||||
| `I` | `I[1-24]` | `I[1-24]` | `Contact` |
|
||||
| `Q` | `Q[1-16]` | `Q[1-20]` | `Switch` |
|
||||
| `M` | `M[1-27]` | `M[1-64]` | `Switch` |
|
||||
| `NI` | | `NI[1-64]` | `Contact` |
|
||||
| `NQ` | | `NQ[1-64]` | `Switch` |
|
||||
|
||||
### Analog
|
||||
|
||||
Format pattern for analog channels is
|
||||
|
||||
```
|
||||
channel="plclogo:analog:<DeviceId>:<ThingId>:<Channel>"
|
||||
```
|
||||
|
||||
Dependent on configured LOGO! PLC and thing kind, follow channels are available:
|
||||
|
||||
| Kind | `0BA7` | `0BA8` | Item |
|
||||
| ----- | :--------: | :---------: | -------- |
|
||||
| `AI` | `AI[1-8]` | `AI[1-8]` | `Number` |
|
||||
| `AQ` | `AQ[1-2]` | `AQ[1-8]` | `Number` |
|
||||
| `AM` | `AM[1-16]` | `AM[1-64]` | `Number` |
|
||||
| `NAI` | | `NAI[1-32]` | `Number` |
|
||||
| `NAQ` | | `NAQ[1-16]` | `Number` |
|
||||
|
||||
### Memory
|
||||
|
||||
Format pattern for memory channels for bit values is
|
||||
|
||||
```
|
||||
channel="plclogo:memory:<DeviceId>:<ThingId>:<state/value>"
|
||||
```
|
||||
|
||||
Dependent on configured LOGO! PLC and thing kind, follow channels are available:
|
||||
|
||||
| Kind | `0BA7` | `0BA8` | Item |
|
||||
| ----------------- | :-----: | :-----: | -------- |
|
||||
| `VB[0-850].[0-7]` | `state` | `state` | `Switch` |
|
||||
| `VB[0-850]` | `value` | `value` | `Number` |
|
||||
| `VW[0-849]` | `value` | `value` | `Number` |
|
||||
| `VD[0-847]` | `value` | `value` | `Number` |
|
||||
|
||||
### DateTime
|
||||
|
||||
Format pattern depends for date/time channels is
|
||||
|
||||
```
|
||||
channel="plclogo:datetime:<DeviceId>:<ThingId>:<date/time>"
|
||||
```
|
||||
|
||||
Dependent on configured LOGO! PLC and thing kind, follow channels are available:
|
||||
|
||||
| Kind | `0BA7` | `0BA8` | Item |
|
||||
| ----------- | :-----: | :-----: | ---------- |
|
||||
| `VW[0-849]` | `date` | `date` | `DateTime` |
|
||||
| `VW[0-849]` | `time` | `time` | `DateTime` |
|
||||
| `VW[0-849]` | `value` | `value` | `Number` |
|
||||
|
||||
Channel `date` is available, if thing is configured as `"date"`.
|
||||
Is thing configured as `"time"`, then channel `time` is provided.
|
||||
Raw block data is provided via `value` channel, independed from thing configuration:
|
||||
|
||||
```
|
||||
channel="plclogo:datetime:<DeviceId>:<ThingId>:value"
|
||||
```
|
||||
|
||||
### Pulse
|
||||
|
||||
Format pattern depends for pulse channels is
|
||||
|
||||
```
|
||||
channel="plclogo:pulse:<DeviceId>:<ThingId>:state"
|
||||
```
|
||||
|
||||
Additionally the state of observed block data is provided via `observed` channel
|
||||
|
||||
```
|
||||
channel="plclogo:pulse:<DeviceId>:<ThingId>:observed"
|
||||
```
|
||||
|
||||
Dependent on configured LOGO! PLC and thing kind, follow channels are available:
|
||||
|
||||
| Kind | `0BA7` | `0BA8` | Item |
|
||||
| ----------------- | :--------: | :--------: | --------- |
|
||||
| `VB[0-850].[0-7]` | `state` | `state` | `Switch` |
|
||||
| `VB[0-850].[0-7]` | `observed` | `observed` | `Switch` |
|
||||
| `I[1-24]` | `observed` | `observed` | `Contact` |
|
||||
| `Q[1-16/20]` | `observed` | `observed` | `Switch` |
|
||||
| `M[1-27/64]` | `observed` | `observed` | `Switch` |
|
||||
| `NI[1-64]` | | `observed` | `Contact` |
|
||||
| `NQ[1-64]` | | `observed` | `Switch` |
|
||||
|
||||
## Examples
|
||||
|
||||
Configuration of one Siemens LOGO!
|
||||
|
||||
logo.things:
|
||||
|
||||
```
|
||||
Bridge plclogo:device:Logo [ address="192.168.0.1", family="0BA8", localTSAP="0x3000", remoteTSAP="0x2000", refresh=100 ]
|
||||
{
|
||||
Thing digital Inputs [ kind="I" ]
|
||||
Thing digital Outputs [ kind="Q" ]
|
||||
Thing memory VW100 [ block="VW100", threshold=1, force=true ]
|
||||
Thing datetime VW102 [ block="VW102", type="time" ]
|
||||
Thing datetime VW150 [ block="VW150", type="date" ]
|
||||
Thing pulse VB0_1 [ block="VB0.1", observe="Q1", pulse=500 ]
|
||||
}
|
||||
```
|
||||
|
||||
logo.items:
|
||||
|
||||
```
|
||||
Contact LogoI1 { channel="plclogo:digital:Logo:Inputs:I1" }
|
||||
Contact LogoI2 { channel="plclogo:digital:Logo:Inputs:I2" }
|
||||
Switch LogoQ1 { channel="plclogo:digital:Logo:Outputs:Q1" }
|
||||
Switch LogoQ2 { channel="plclogo:digital:Logo:Outputs:Q2" }
|
||||
Number Position { channel="plclogo:memory:Logo:VW100:value" }
|
||||
|
||||
DateTime LogoTime { channel="plclogo:datetime:Logo:VW102:time" }
|
||||
DateTime LogoDate { channel="plclogo:datetime:Logo:VW150:date" }
|
||||
|
||||
Switch LogoVB1_S { channel="plclogo:pulse:Logo:VB0_1:state"}
|
||||
Switch LogoVB1_O { channel="plclogo:pulse:Logo:VB0_1:observed"}
|
||||
|
||||
String Diagnostic { channel="plclogo:device:Logo:diagnostic"}
|
||||
DateTime RTC { channel="plclogo:device:Logo:rtc"}
|
||||
String DayOfWeek { channel="plclogo:device:Logo:weekday"}
|
||||
```
|
||||
|
||||
Configuration of two Siemens LOGO!
|
||||
|
||||
logo.things:
|
||||
|
||||
```
|
||||
Bridge plclogo:device:Logo1 [ address="192.168.0.1", family="0BA8", localTSAP="0x3000", remoteTSAP="0x2000", refresh=100 ]
|
||||
{
|
||||
Thing digital Inputs [ kind="I" ]
|
||||
Thing digital Outputs [ kind="Q" ]
|
||||
Thing memory VW100 [ block="VW100", threshold=1 ]
|
||||
Thing pulse VB0_0 [ block="VB0.0", observe="NI1", pulse=250 ]
|
||||
}
|
||||
Bridge plclogo:device:Logo2 [ address="192.168.0.2", family="0BA8", localTSAP="0x3100", remoteTSAP="0x2000", refresh=100 ]
|
||||
{
|
||||
Thing digital Inputs [ kind="I" ]
|
||||
Thing digital Outputs [ kind="Q" ]
|
||||
Thing memory VD102 [ block="VD102", threshold=1 ]
|
||||
Thing pulse VB0_1 [ block="VB0.1", observe="VB0.1", pulse=500 ]
|
||||
}
|
||||
```
|
||||
|
||||
logo.items:
|
||||
|
||||
```
|
||||
Contact Logo1_I1 { channel="plclogo:digital:Logo1:Inputs:I1" }
|
||||
Contact Logo1_I2 { channel="plclogo:digital:Logo1:Inputs:I2" }
|
||||
Switch Logo1_Q1 { channel="plclogo:digital:Logo1:Outputs:Q1" }
|
||||
Switch Logo1_Q2 { channel="plclogo:digital:Logo1:Outputs:Q2" }
|
||||
Number Logo1_VW100 { channel="plclogo:memory:Logo1:VW100:value" }
|
||||
Switch Logo1_VB0_S { channel="plclogo:pulse:Logo1:VB0_0:state"}
|
||||
Contact Logo1_VB0_O { channel="plclogo:pulse:Logo1:VB0_0:observed"}
|
||||
DateTime Logo1_RTC { channel="plclogo:device:Logo1:rtc"}
|
||||
|
||||
Contact Logo2_I1 { channel="plclogo:digital:Logo2:Inputs:I1" }
|
||||
Contact Logo2_I2 { channel="plclogo:digital:Logo2:Inputs:I2" }
|
||||
Switch Logo2_Q1 { channel="plclogo:digital:Logo2:Outputs:Q1" }
|
||||
Switch Logo2_Q2 { channel="plclogo:digital:Logo2:Outputs:Q2" }
|
||||
Number Logo2_VD102 { channel="plclogo:memory:Logo2:VD102:value" }
|
||||
Switch Logo2_VB1_S { channel="plclogo:pulse:Logo2:VB0_1:state"}
|
||||
Switch Logo2_VB1_O { channel="plclogo:pulse:Logo2:VB0_1:observed"}
|
||||
DateTime Logo2_RTC { channel="plclogo:device:Logo2:rtc"}
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**LOGO! bridge will not go online**
|
||||
|
||||
Be sure to have only one bridge for each LOGO! device.
|
||||
|
||||
**Log shows reader was created but no communication with LOGO! possible**
|
||||
|
||||
Check TSAP values: localTSAP and remoteTSAP should not be the same.
|
||||
You have to choose different addresses.
|
||||
|
||||
**openHAB is starting without errors but no reader was created for the LOGO!**
|
||||
|
||||
If all configuration parameters were checked and fine, it maybe possible that the network interface of the LOGO! is crashed.
|
||||
To recover stop openHAB, cold boot your LOGO! (power off/on) and reflash the program with LOGO! SoftComfort.
|
||||
Then restart openHAB and check logging for a created reader.
|
||||
|
||||
**RTC value differs from the value shown in LOGO! (0BA7)**
|
||||
|
||||
This is no bug! Since there is no way to read the RTC from a 0BA7, the binding simply returns the local time of openHAB host.
|
||||
BIN
bundles/org.openhab.binding.plclogo/lib/Moka7-1.0.2_io_patch.jar
Normal file
BIN
bundles/org.openhab.binding.plclogo/lib/Moka7-1.0.2_io_patch.jar
Normal file
Binary file not shown.
3792
bundles/org.openhab.binding.plclogo/lib/bit_io.patch
Normal file
3792
bundles/org.openhab.binding.plclogo/lib/bit_io.patch
Normal file
File diff suppressed because it is too large
Load Diff
17
bundles/org.openhab.binding.plclogo/pom.xml
Normal file
17
bundles/org.openhab.binding.plclogo/pom.xml
Normal file
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
|
||||
<version>3.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>org.openhab.binding.plclogo</artifactId>
|
||||
|
||||
<name>openHAB Add-ons :: Bundles :: PLCLogo Binding</name>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<features name="org.openhab.binding.plclogo-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
|
||||
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
|
||||
|
||||
<feature name="openhab-binding-plclogo" description="PLCLogo Binding" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.plclogo/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
||||
@@ -0,0 +1,190 @@
|
||||
/**
|
||||
* 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.plclogo.internal;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
/**
|
||||
* The {@link PLCLogoBindingConstants} class defines common constants, which are
|
||||
* used across the whole binding.
|
||||
*
|
||||
* @author Alexander Falkenstern - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class PLCLogoBindingConstants {
|
||||
|
||||
public static final String BINDING_ID = "plclogo";
|
||||
|
||||
// List of all thing type UIDs
|
||||
public static final ThingTypeUID THING_TYPE_DEVICE = new ThingTypeUID(BINDING_ID, "device");
|
||||
public static final ThingTypeUID THING_TYPE_ANALOG = new ThingTypeUID(BINDING_ID, "analog");
|
||||
public static final ThingTypeUID THING_TYPE_MEMORY = new ThingTypeUID(BINDING_ID, "memory");
|
||||
public static final ThingTypeUID THING_TYPE_DIGITAL = new ThingTypeUID(BINDING_ID, "digital");
|
||||
public static final ThingTypeUID THING_TYPE_DATETIME = new ThingTypeUID(BINDING_ID, "datetime");
|
||||
public static final ThingTypeUID THING_TYPE_PULSE = new ThingTypeUID(BINDING_ID, "pulse");
|
||||
|
||||
// Something goes wrong...
|
||||
public static final String NOT_SUPPORTED = "NOT SUPPORTED";
|
||||
|
||||
// List of all channels
|
||||
public static final String STATE_CHANNEL = "state";
|
||||
public static final String OBSERVE_CHANNEL = "observed";
|
||||
public static final String VALUE_CHANNEL = "value";
|
||||
public static final String RTC_CHANNEL = "rtc";
|
||||
public static final String DAIGNOSTICS_CHANNEL = "diagnostic";
|
||||
public static final String DAY_OF_WEEK_CHANNEL = "weekday";
|
||||
|
||||
// List of all channel properties
|
||||
public static final String BLOCK_PROPERTY = "block";
|
||||
|
||||
// List of all item types
|
||||
public static final String ANALOG_ITEM = "Number";
|
||||
public static final String DATE_TIME_ITEM = "DateTime";
|
||||
public static final String DIGITAL_INPUT_ITEM = "Contact";
|
||||
public static final String DIGITAL_OUTPUT_ITEM = "Switch";
|
||||
public static final String INFORMATION_ITEM = "String";
|
||||
|
||||
// LOGO! family definitions
|
||||
public static final String LOGO_0BA7 = "0BA7";
|
||||
public static final String LOGO_0BA8 = "0BA8";
|
||||
|
||||
// LOGO! block definitions
|
||||
public static final String MEMORY_BYTE = "VB"; // Bit or Byte memory
|
||||
public static final String MEMORY_WORD = "VW"; // Word memory
|
||||
public static final String MEMORY_DWORD = "VD"; // DWord memory
|
||||
public static final String MEMORY_SIZE = "SIZE"; // Size of memory
|
||||
|
||||
public static final String I_DIGITAL = "I"; // Physical digital input
|
||||
public static final String Q_DIGITAL = "Q"; // Physical digital output
|
||||
public static final String M_DIGITAL = "M"; // Program digital marker
|
||||
public static final String NI_DIGITAL = "NI"; // Network digital input
|
||||
public static final String NQ_DIGITAL = "NQ"; // Network digital output
|
||||
|
||||
public static final String I_ANALOG = "AI"; // Physical analog input
|
||||
public static final String Q_ANALOG = "AQ"; // Physical analog output
|
||||
public static final String M_ANALOG = "AM"; // Program analog marker
|
||||
public static final String NI_ANALOG = "NAI"; // Network analog input
|
||||
public static final String NQ_ANALOG = "NAQ"; // Network analog output
|
||||
|
||||
private static final Map<Integer, @Nullable String> LOGO_STATES_0BA7;
|
||||
static {
|
||||
Map<Integer, String> buffer = new HashMap<>();
|
||||
// buffer.put(???, "Network access error"); // Netzwerkzugriffsfehler
|
||||
// buffer.put(???, "Expansion module bus error"); // Erweiterungsmodul-Busfehler
|
||||
// buffer.put(???, "SD card read/write error"); // Fehler beim Lesen oder Schreiben der SD-Karte
|
||||
// buffer.put(???, "SD card write protection"); // Schreibschutz der SD-Karte
|
||||
LOGO_STATES_0BA7 = Collections.unmodifiableMap(buffer);
|
||||
}
|
||||
|
||||
private static final Map<Integer, @Nullable String> LOGO_STATES_0BA8;
|
||||
static {
|
||||
Map<Integer, String> buffer = new HashMap<>();
|
||||
buffer.put(1, "Ethernet link error"); // Netzwerk Verbindungsfehler
|
||||
buffer.put(2, "Expansion module changed"); // Ausgetauschtes Erweiterungsmodul
|
||||
buffer.put(4, "SD card read/write error"); // Fehler beim Lesen oder Schreiben der SD-Karte
|
||||
buffer.put(8, "SD Card does not exist"); // "SD-Karte nicht vorhanden"
|
||||
buffer.put(16, "SD Card is full"); // SD-Karte voll
|
||||
// buffer.put(???, "Network S7 Tcp Error"); //
|
||||
LOGO_STATES_0BA8 = Collections.unmodifiableMap(buffer);
|
||||
}
|
||||
|
||||
public static final Map<String, @Nullable Map<Integer, @Nullable String>> LOGO_STATES;
|
||||
static {
|
||||
Map<String, @Nullable Map<Integer, @Nullable String>> buffer = new HashMap<>();
|
||||
buffer.put(LOGO_0BA7, LOGO_STATES_0BA7);
|
||||
buffer.put(LOGO_0BA8, LOGO_STATES_0BA8);
|
||||
LOGO_STATES = Collections.unmodifiableMap(buffer);
|
||||
}
|
||||
|
||||
public static final class Layout {
|
||||
public final int address;
|
||||
public final int length;
|
||||
|
||||
public Layout(int address, int length) {
|
||||
this.address = address;
|
||||
this.length = length;
|
||||
}
|
||||
}
|
||||
|
||||
public static final Map<String, @Nullable Layout> LOGO_CHANNELS;
|
||||
static {
|
||||
Map<String, @Nullable Layout> buffer = new HashMap<>();
|
||||
buffer.put(DAIGNOSTICS_CHANNEL, new Layout(984, 1)); // Diagnostics starts at 984 for 1 byte
|
||||
buffer.put(RTC_CHANNEL, new Layout(985, 6)); // RTC starts at 985 for 6 bytes: year month day hour minute second
|
||||
buffer.put(DAY_OF_WEEK_CHANNEL, new Layout(998, 1)); // Diagnostics starts at 998 for 1 byte
|
||||
LOGO_CHANNELS = Collections.unmodifiableMap(buffer);
|
||||
}
|
||||
|
||||
public static final Map<Integer, @Nullable String> DAY_OF_WEEK;
|
||||
static {
|
||||
Map<Integer, @Nullable String> buffer = new HashMap<>();
|
||||
buffer.put(1, "SUNDAY");
|
||||
buffer.put(2, "MONDAY");
|
||||
buffer.put(3, "TUEsDAY");
|
||||
buffer.put(4, "WEDNESDAY");
|
||||
buffer.put(5, "THURSDAY");
|
||||
buffer.put(6, "FRIDAY");
|
||||
buffer.put(7, "SATURDAY");
|
||||
DAY_OF_WEEK = Collections.unmodifiableMap(buffer);
|
||||
}
|
||||
|
||||
private static final Map<String, @Nullable Layout> LOGO_MEMORY_0BA7;
|
||||
static {
|
||||
Map<String, @Nullable Layout> buffer = new HashMap<>();
|
||||
buffer.put(MEMORY_BYTE, new Layout(0, 850));
|
||||
buffer.put(MEMORY_DWORD, new Layout(0, 850));
|
||||
buffer.put(MEMORY_WORD, new Layout(0, 850));
|
||||
buffer.put(I_DIGITAL, new Layout(923, 3)); // Digital inputs starts at 923 for 3 bytes
|
||||
buffer.put(Q_DIGITAL, new Layout(942, 2)); // Digital outputs starts at 942 for 2 bytes
|
||||
buffer.put(M_DIGITAL, new Layout(948, 4)); // Digital markers starts at 948 for 4 bytes
|
||||
buffer.put(I_ANALOG, new Layout(926, 16)); // Analog inputs starts at 926 for 16 bytes -> 8 words
|
||||
buffer.put(Q_ANALOG, new Layout(944, 4)); // Analog outputs starts at 944 for 4 bytes -> 2 words
|
||||
buffer.put(M_ANALOG, new Layout(952, 32)); // Analog markers starts at 952 for 32 bytes -> 16 words
|
||||
buffer.put(MEMORY_SIZE, new Layout(0, 984)); // Size of memory block for LOGO! 7
|
||||
LOGO_MEMORY_0BA7 = Collections.unmodifiableMap(buffer);
|
||||
}
|
||||
|
||||
private static final Map<String, @Nullable Layout> LOGO_MEMORY_0BA8;
|
||||
static {
|
||||
Map<String, @Nullable Layout> buffer = new HashMap<>();
|
||||
buffer.put(MEMORY_BYTE, new Layout(0, 850));
|
||||
buffer.put(MEMORY_DWORD, new Layout(0, 850));
|
||||
buffer.put(MEMORY_WORD, new Layout(0, 850));
|
||||
buffer.put(I_DIGITAL, new Layout(1024, 8)); // Digital inputs starts at 1024 for 8 bytes
|
||||
buffer.put(Q_DIGITAL, new Layout(1064, 8)); // Digital outputs starts at 1064 for 8 bytes
|
||||
buffer.put(M_DIGITAL, new Layout(1104, 14)); // Digital markers starts at 1104 for 14 bytes
|
||||
buffer.put(I_ANALOG, new Layout(1032, 32)); // Analog inputs starts at 1032 for 32 bytes -> 16 words
|
||||
buffer.put(Q_ANALOG, new Layout(1072, 32)); // Analog outputs starts at 1072 for 32 bytes -> 16 words
|
||||
buffer.put(M_ANALOG, new Layout(1118, 128)); // Analog markers starts at 1118 for 128 bytes -> 64 words
|
||||
buffer.put(NI_DIGITAL, new Layout(1246, 16)); // Network inputs starts at 1246 for 16 bytes
|
||||
buffer.put(NI_ANALOG, new Layout(1262, 128)); // Network analog inputs starts at 1262 for 128 bytes -> 64 words
|
||||
buffer.put(NQ_DIGITAL, new Layout(1390, 16)); // Network outputs starts at 1390 for 16 bytes
|
||||
buffer.put(NQ_ANALOG, new Layout(1406, 64)); // Network analog inputs starts at 1406 for 64 bytes -> 32 words
|
||||
buffer.put(MEMORY_SIZE, new Layout(0, 1470)); // Size of memory block for LOGO! 8
|
||||
LOGO_MEMORY_0BA8 = Collections.unmodifiableMap(buffer);
|
||||
}
|
||||
|
||||
public static final Map<String, @Nullable Map<String, @Nullable Layout>> LOGO_MEMORY_BLOCK;
|
||||
static {
|
||||
Map<String, @Nullable Map<String, @Nullable Layout>> buffer = new HashMap<>();
|
||||
buffer.put(LOGO_0BA7, LOGO_MEMORY_0BA7);
|
||||
buffer.put(LOGO_0BA8, LOGO_MEMORY_0BA8);
|
||||
LOGO_MEMORY_BLOCK = Collections.unmodifiableMap(buffer);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
/**
|
||||
* 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.plclogo.internal;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import Moka7.S7;
|
||||
import Moka7.S7Client;
|
||||
|
||||
/**
|
||||
* The {@link PLCLogoClient} is thread safe LOGO! client.
|
||||
*
|
||||
* @author Alexander Falkenstern - Initial contribution
|
||||
*/
|
||||
public class PLCLogoClient extends S7Client {
|
||||
|
||||
private static final int MAX_RETRY_NUMBER = 10;
|
||||
private final Logger logger = LoggerFactory.getLogger(PLCLogoClient.class);
|
||||
|
||||
private String plcIPAddress = "INVALID_IP";
|
||||
|
||||
/**
|
||||
* Connects a client to a PLC
|
||||
*/
|
||||
@Override
|
||||
public synchronized int Connect() {
|
||||
return super.Connect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Connects a client to a PLC with specified parameters
|
||||
*
|
||||
* @param Address IP address of PLC
|
||||
* @param LocalTSAP Local TSAP for the connection
|
||||
* @param RemoteTSAP Remote TSAP for the connection
|
||||
* @return Zero on success, error code otherwise
|
||||
*/
|
||||
public synchronized int Connect(String Address, int LocalTSAP, int RemoteTSAP) {
|
||||
SetConnectionParams(Address, LocalTSAP, RemoteTSAP);
|
||||
return super.Connect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set connection parameters
|
||||
*
|
||||
* @param Address IP address of PLC
|
||||
* @param LocalTSAP Local TSAP for the connection
|
||||
* @param RemoteTSAP Remote TSAP for the connection
|
||||
*/
|
||||
@Override
|
||||
public void SetConnectionParams(String Address, int LocalTSAP, int RemoteTSAP) {
|
||||
plcIPAddress = Address; // Store ip address for logging
|
||||
super.SetConnectionParams(Address, LocalTSAP, RemoteTSAP);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnects a client from a PLC
|
||||
*/
|
||||
@Override
|
||||
public synchronized void Disconnect() {
|
||||
super.Disconnect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a data area from a PLC
|
||||
*
|
||||
* @param Area S7 Area ID. Can be S7AreaPE, S7AreaPA, S7AreaMK, S7AreaDB, S7AreaCT or S7AreaTM
|
||||
* @param DBNumber S7 data block number
|
||||
* @param Start First position within data block read from
|
||||
* @param Amount Number of words to read
|
||||
* @param WordLength Length of single word. Can be S7WLBit, S7WLByte, S7WLCounter or S7WLTimer
|
||||
* @param Data Buffer to read into
|
||||
* @return Zero on success, error code otherwise
|
||||
*/
|
||||
@Override
|
||||
public synchronized int ReadArea(int Area, int DBNumber, int Start, int Amount, int WordLength, byte[] Data) {
|
||||
if (LastError != 0) {
|
||||
logger.debug("Reconnect during read from {}: {}", plcIPAddress, ErrorText(LastError));
|
||||
Disconnect();
|
||||
}
|
||||
if (!Connected) {
|
||||
Connect();
|
||||
}
|
||||
|
||||
final int packet = Math.min(Amount, 1024);
|
||||
int offset = packet;
|
||||
|
||||
int retry = 0;
|
||||
int result = -1;
|
||||
do {
|
||||
// read first portion directly to data
|
||||
result = super.ReadArea(Area, DBNumber, Start, packet, WordLength, Data);
|
||||
while ((result == 0) && (offset < Amount)) {
|
||||
byte buffer[] = new byte[Math.min(Amount - offset, packet)];
|
||||
result = super.ReadArea(Area, DBNumber, offset, buffer.length, WordLength, buffer);
|
||||
System.arraycopy(buffer, 0, Data, offset, buffer.length);
|
||||
offset = offset + buffer.length;
|
||||
}
|
||||
|
||||
if (retry == MAX_RETRY_NUMBER) {
|
||||
logger.info("Giving up reading from {} after {} retries.", plcIPAddress, MAX_RETRY_NUMBER);
|
||||
break;
|
||||
}
|
||||
|
||||
if (result != 0) {
|
||||
logger.info("Reconnect during read from {}: {}", plcIPAddress, ErrorText(result));
|
||||
retry = retry + 1;
|
||||
Disconnect();
|
||||
Connect();
|
||||
}
|
||||
} while (result != 0);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a data block area from a PLC
|
||||
*
|
||||
* @param DBNumber S7 data block number
|
||||
* @param Start First position within data block read from
|
||||
* @param Amount Number of words to read
|
||||
* @param WordLength Length of single word. Can be S7WLBit, S7WLByte, S7WLCounter or S7WLTimer
|
||||
* @param Data Buffer to read into
|
||||
* @return Zero on success, error code otherwise
|
||||
*/
|
||||
public int readDBArea(int DBNumber, int Start, int Amount, int WordLength, byte[] Data) {
|
||||
return ReadArea(S7.S7AreaDB, DBNumber, Start, Amount, WordLength, Data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a data area into a PLC
|
||||
*
|
||||
* @param Area S7 Area ID. Can be S7AreaPE, S7AreaPA, S7AreaMK, S7AreaDB, S7AreaCT or S7AreaTM
|
||||
* @param DBNumber S7 data block number
|
||||
* @param Start First position within data block write into
|
||||
* @param Amount Number of words to write
|
||||
* @param WordLength Length of single word. Can be S7WLBit, S7WLByte, S7WLCounter or S7WLTimer
|
||||
* @param Data Buffer to write from
|
||||
* @return Zero on success, error code otherwise
|
||||
*/
|
||||
@Override
|
||||
public synchronized int WriteArea(int Area, int DBNumber, int Start, int Amount, int WordLength, byte[] Data) {
|
||||
if (LastError != 0) {
|
||||
logger.debug("Reconnect during write to {}: {}", plcIPAddress, ErrorText(LastError));
|
||||
Disconnect();
|
||||
}
|
||||
if (!Connected) {
|
||||
Connect();
|
||||
}
|
||||
|
||||
int retry = 0;
|
||||
int result = -1;
|
||||
do {
|
||||
result = super.WriteArea(Area, DBNumber, Start, Amount, WordLength, Data);
|
||||
|
||||
if (retry == MAX_RETRY_NUMBER) {
|
||||
logger.info("Giving up writing to {} after {} retries.", plcIPAddress, MAX_RETRY_NUMBER);
|
||||
break;
|
||||
}
|
||||
|
||||
if (result != 0) {
|
||||
logger.info("Reconnect during write to {}: {}", plcIPAddress, ErrorText(result));
|
||||
retry = retry + 1;
|
||||
Disconnect();
|
||||
Connect();
|
||||
}
|
||||
} while (result != 0);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a data block area into a PLC
|
||||
*
|
||||
* @param DBNumber S7 data block number
|
||||
* @param Start First position within data block write into
|
||||
* @param Amount Number of words to write
|
||||
* @param WordLength Length of single word. Can be S7WLBit, S7WLByte, S7WLCounter or S7WLTimer
|
||||
* @param Data Buffer to write from
|
||||
* @return Zero on success, error code otherwise
|
||||
*/
|
||||
public int writeDBArea(int DBNumber, int Start, int Amount, int WordLength, byte[] Data) {
|
||||
return WriteArea(S7.S7AreaDB, DBNumber, Start, Amount, WordLength, Data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns, if client is already connected or not
|
||||
*
|
||||
* @return True, if client is connected and false otherwise
|
||||
*/
|
||||
public synchronized boolean isConnected() {
|
||||
return Connected;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* 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.plclogo.internal;
|
||||
|
||||
import static org.openhab.binding.plclogo.internal.PLCLogoBindingConstants.*;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.plclogo.internal.handler.PLCAnalogHandler;
|
||||
import org.openhab.binding.plclogo.internal.handler.PLCBridgeHandler;
|
||||
import org.openhab.binding.plclogo.internal.handler.PLCDateTimeHandler;
|
||||
import org.openhab.binding.plclogo.internal.handler.PLCDigitalHandler;
|
||||
import org.openhab.binding.plclogo.internal.handler.PLCMemoryHandler;
|
||||
import org.openhab.binding.plclogo.internal.handler.PLCPulseHandler;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerFactory;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
|
||||
/**
|
||||
* The {@link PLCLogoHandlerFactory} is responsible for creating things and
|
||||
* thing handlers supported by PLCLogo binding.
|
||||
*
|
||||
* @author Alexander Falkenstern - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.plclogo")
|
||||
public class PLCLogoHandlerFactory extends BaseThingHandlerFactory {
|
||||
|
||||
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS;
|
||||
static {
|
||||
Set<ThingTypeUID> buffer = new HashSet<>();
|
||||
buffer.add(THING_TYPE_DEVICE);
|
||||
buffer.add(THING_TYPE_MEMORY);
|
||||
buffer.add(THING_TYPE_ANALOG);
|
||||
buffer.add(THING_TYPE_DIGITAL);
|
||||
buffer.add(THING_TYPE_DATETIME);
|
||||
buffer.add(THING_TYPE_PULSE);
|
||||
SUPPORTED_THING_TYPES_UIDS = Collections.unmodifiableSet(buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public PLCLogoHandlerFactory() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
||||
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable ThingHandler createHandler(Thing thing) {
|
||||
if (THING_TYPE_DEVICE.equals(thing.getThingTypeUID()) && (thing instanceof Bridge)) {
|
||||
return new PLCBridgeHandler((Bridge) thing);
|
||||
} else if (THING_TYPE_ANALOG.equals(thing.getThingTypeUID())) {
|
||||
return new PLCAnalogHandler(thing);
|
||||
} else if (THING_TYPE_DIGITAL.equals(thing.getThingTypeUID())) {
|
||||
return new PLCDigitalHandler(thing);
|
||||
} else if (THING_TYPE_DATETIME.equals(thing.getThingTypeUID())) {
|
||||
return new PLCDateTimeHandler(thing);
|
||||
} else if (THING_TYPE_MEMORY.equals(thing.getThingTypeUID())) {
|
||||
return new PLCMemoryHandler(thing);
|
||||
} else if (THING_TYPE_PULSE.equals(thing.getThingTypeUID())) {
|
||||
return new PLCPulseHandler(thing);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* 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.plclogo.internal.config;
|
||||
|
||||
import static org.openhab.binding.plclogo.internal.PLCLogoBindingConstants.ANALOG_ITEM;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link PLCAnalogConfiguration} is a class for configuration
|
||||
* of Siemens LOGO! PLC analog input/outputs blocks.
|
||||
*
|
||||
* @author Alexander Falkenstern - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class PLCAnalogConfiguration extends PLCDigitalConfiguration {
|
||||
|
||||
private Integer threshold = 0;
|
||||
|
||||
/**
|
||||
* Get Siemens LOGO! blocks update threshold.
|
||||
*
|
||||
* @return Configured Siemens LOGO! update threshold
|
||||
*/
|
||||
public Integer getThreshold() {
|
||||
return threshold;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Siemens LOGO! blocks update threshold.
|
||||
*
|
||||
* @param force Force update of Siemens LOGO! blocks
|
||||
*/
|
||||
public void setThreshold(final Integer threshold) {
|
||||
this.threshold = threshold;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getChannelType() {
|
||||
return ANALOG_ITEM;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* 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.plclogo.internal.config;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link PLCCommonConfiguration} is a base class for configuration
|
||||
* of Siemens LOGO! PLC blocks.
|
||||
*
|
||||
* @author Alexander Falkenstern - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
abstract class PLCCommonConfiguration {
|
||||
|
||||
private Boolean force = false;
|
||||
|
||||
/**
|
||||
* Returns if Siemens LOGO! channels update must be forced.
|
||||
*
|
||||
* @return True, if channels update to be forced and false otherwise
|
||||
*/
|
||||
public Boolean isUpdateForced() {
|
||||
return force;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Siemens LOGO! channels update must be forced.
|
||||
*
|
||||
* @param force Force update of Siemens LOGO! block
|
||||
*/
|
||||
public void setForceUpdate(final Boolean force) {
|
||||
this.force = force;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return channel type accepted by thing.
|
||||
* Can be Contact, Switch, Number, DateTime or String
|
||||
*
|
||||
* @return Accepted channel type
|
||||
*/
|
||||
public abstract String getChannelType();
|
||||
|
||||
/**
|
||||
* Get configured Siemens LOGO! blocks kind.
|
||||
* Can be I, Q, M, NI or NQ for digital blocks, AI, AM,
|
||||
* AQ, NAI or NAQ for analog and VB, VW or VD for memory
|
||||
*
|
||||
* @return Configured Siemens LOGO! blocks kind
|
||||
*/
|
||||
public abstract String getBlockKind();
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* 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.plclogo.internal.config;
|
||||
|
||||
import static org.openhab.binding.plclogo.internal.PLCLogoBindingConstants.DATE_TIME_ITEM;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link PLCDateTimeConfiguration} holds configuration of Siemens LOGO! PLC
|
||||
* analog input/outputs blocks.
|
||||
*
|
||||
* @author Alexander Falkenstern - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class PLCDateTimeConfiguration extends PLCCommonConfiguration {
|
||||
|
||||
private String block = "";
|
||||
private String type = "";
|
||||
|
||||
/**
|
||||
* Get configured Siemens LOGO! block name.
|
||||
*
|
||||
* @return Configured Siemens LOGO! block name
|
||||
*/
|
||||
public String getBlockName() {
|
||||
return block;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Siemens LOGO! block name.
|
||||
*
|
||||
* @param name Siemens LOGO! block name
|
||||
*/
|
||||
public void setBlockName(final String name) {
|
||||
this.block = name.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get configured Siemens LOGO! block name.
|
||||
*
|
||||
* @return Configured Siemens LOGO! block name
|
||||
*/
|
||||
public String getBlockType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Siemens LOGO! block name.
|
||||
*
|
||||
* @param name Siemens LOGO! output block name
|
||||
*/
|
||||
public void setBlockType(final String type) {
|
||||
this.type = type.trim();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getChannelType() {
|
||||
return DATE_TIME_ITEM;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBlockKind() {
|
||||
return block.substring(0, 2);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.plclogo.internal.config;
|
||||
|
||||
import static org.openhab.binding.plclogo.internal.PLCLogoBindingConstants.*;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link PLCDigitalConfiguration} is a base class for configuration
|
||||
* of Siemens LOGO! PLC digital input/outputs blocks.
|
||||
*
|
||||
* @author Alexander Falkenstern - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class PLCDigitalConfiguration extends PLCCommonConfiguration {
|
||||
|
||||
private String kind = "";
|
||||
|
||||
@Override
|
||||
public String getBlockKind() {
|
||||
return kind;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Siemens LOGO! blocks kind.
|
||||
* Can be I, Q, M, NI or NQ for digital blocks and
|
||||
* AI, AM, AQ, NAI or NAQ for analog
|
||||
*
|
||||
* @param kind Siemens LOGO! blocks kind
|
||||
*/
|
||||
public void setBlockKind(final String kind) {
|
||||
this.kind = kind.trim();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getChannelType() {
|
||||
boolean isInput = kind.equalsIgnoreCase(I_DIGITAL) || kind.equalsIgnoreCase(NI_DIGITAL);
|
||||
return isInput ? DIGITAL_INPUT_ITEM : DIGITAL_OUTPUT_ITEM;
|
||||
}
|
||||
}
|
||||
@@ -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.plclogo.internal.config;
|
||||
|
||||
import static org.openhab.binding.plclogo.internal.PLCLogoBindingConstants.NOT_SUPPORTED;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.plclogo.internal.PLCLogoBindingConstants;
|
||||
|
||||
/**
|
||||
* The {@link PLCLogoBridgeConfiguration} hold configuration of Siemens LOGO! PLCs.
|
||||
*
|
||||
* @author Alexander Falkenstern - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class PLCLogoBridgeConfiguration {
|
||||
|
||||
private String address = "";
|
||||
private String family = NOT_SUPPORTED;
|
||||
private String localTSAP = "0x3000";
|
||||
private String remoteTSAP = "0x2000";
|
||||
private Integer refresh = 100;
|
||||
|
||||
/**
|
||||
* Get configured Siemens LOGO! device IP address.
|
||||
*
|
||||
* @return Configured Siemens LOGO! IP address
|
||||
*/
|
||||
public String getAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set IP address for Siemens LOGO! device.
|
||||
*
|
||||
* @param address IP address of Siemens LOGO! device
|
||||
*/
|
||||
public void setAddress(final String address) {
|
||||
this.address = address.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get configured Siemens LOGO! device family.
|
||||
*
|
||||
* @see PLCLogoBindingConstants#LOGO_0BA7
|
||||
* @see PLCLogoBindingConstants#LOGO_0BA8
|
||||
* @return Configured Siemens LOGO! device family
|
||||
*/
|
||||
public String getFamily() {
|
||||
return family;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Siemens LOGO! device family.
|
||||
*
|
||||
* @param family Family of Siemens LOGO! device
|
||||
* @see PLCLogoBindingConstants#LOGO_0BA7
|
||||
* @see PLCLogoBindingConstants#LOGO_0BA8
|
||||
*/
|
||||
public void setFamily(final String family) {
|
||||
this.family = family.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get configured local TSAP of Siemens LOGO! device.
|
||||
*
|
||||
* @return Configured local TSAP of Siemens LOGO!
|
||||
*/
|
||||
public @Nullable Integer getLocalTSAP() {
|
||||
Integer result = null;
|
||||
if (localTSAP.startsWith("0x")) {
|
||||
result = Integer.decode(localTSAP);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set local TSAP of Siemens LOGO! device.
|
||||
*
|
||||
* @param tsap Local TSAP of Siemens LOGO! device
|
||||
*/
|
||||
public void setLocalTSAP(final String tsap) {
|
||||
this.localTSAP = tsap.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get configured remote TSAP of Siemens LOGO! device.
|
||||
*
|
||||
* @return Configured local TSAP of Siemens LOGO!
|
||||
*/
|
||||
public @Nullable Integer getRemoteTSAP() {
|
||||
Integer result = null;
|
||||
if (remoteTSAP.startsWith("0x")) {
|
||||
result = Integer.decode(remoteTSAP);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set remote TSAP of Siemens LOGO! device.
|
||||
*
|
||||
* @param tsap Remote TSAP of Siemens LOGO! device
|
||||
*/
|
||||
public void setRemoteTSAP(final String tsap) {
|
||||
this.remoteTSAP = tsap.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get configured refresh rate of Siemens LOGO! device blocks in milliseconds.
|
||||
*
|
||||
* @return Configured refresh rate of Siemens LOGO! device blocks
|
||||
*/
|
||||
public Integer getRefreshRate() {
|
||||
return refresh;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set refresh rate of Siemens LOGO! device blocks in milliseconds.
|
||||
*
|
||||
* @param rate Refresh rate of Siemens LOGO! device blocks
|
||||
*/
|
||||
public void setRefreshRate(final Integer rate) {
|
||||
this.refresh = rate;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/**
|
||||
* 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.plclogo.internal.config;
|
||||
|
||||
import static org.openhab.binding.plclogo.internal.PLCLogoBindingConstants.*;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link PLCMemoryConfiguration} is a class for configuration
|
||||
* of Siemens LOGO! PLC memory input/outputs blocks.
|
||||
*
|
||||
* @author Alexander Falkenstern - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class PLCMemoryConfiguration extends PLCCommonConfiguration {
|
||||
|
||||
private String block = "";
|
||||
private Integer threshold = 0;
|
||||
|
||||
/**
|
||||
* Get configured Siemens LOGO! memory block name.
|
||||
*
|
||||
* @return Configured Siemens LOGO! memory block name
|
||||
*/
|
||||
public String getBlockName() {
|
||||
return block;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Siemens LOGO! memory block name.
|
||||
*
|
||||
* @param name Siemens LOGO! memory block name
|
||||
*/
|
||||
public void setBlockName(final String name) {
|
||||
this.block = name.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Siemens LOGO! blocks update threshold.
|
||||
*
|
||||
* @return Configured Siemens LOGO! update threshold
|
||||
*/
|
||||
public Integer getThreshold() {
|
||||
return threshold;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Siemens LOGO! blocks update threshold.
|
||||
*
|
||||
* @param force Force update of Siemens LOGO! blocks
|
||||
*/
|
||||
public void setThreshold(final Integer threshold) {
|
||||
this.threshold = threshold;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getChannelType() {
|
||||
final String kind = getBlockKind();
|
||||
return kind.equalsIgnoreCase(MEMORY_BYTE) && block.contains(".") ? DIGITAL_OUTPUT_ITEM : ANALOG_ITEM;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBlockKind() {
|
||||
return getBlockKind(block);
|
||||
}
|
||||
|
||||
protected static String getBlockKind(final String name) {
|
||||
String kind = "Unknown";
|
||||
if (Character.isDigit(name.charAt(1))) {
|
||||
kind = name.substring(0, 1);
|
||||
} else if (Character.isDigit(name.charAt(2))) {
|
||||
kind = name.substring(0, 2);
|
||||
} else if (Character.isDigit(name.charAt(3))) {
|
||||
kind = name.substring(0, 3);
|
||||
}
|
||||
return kind;
|
||||
}
|
||||
}
|
||||
@@ -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.plclogo.internal.config;
|
||||
|
||||
import static org.openhab.binding.plclogo.internal.PLCLogoBindingConstants.*;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The {@link PLCPulseConfiguration} is a class for configuration
|
||||
* of Siemens LOGO! PLC memory input/outputs blocks.
|
||||
*
|
||||
* @author Alexander Falkenstern - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class PLCPulseConfiguration extends PLCMemoryConfiguration {
|
||||
|
||||
private @Nullable String observe;
|
||||
private Integer pulse = 150;
|
||||
|
||||
/**
|
||||
* Get observed Siemens LOGO! block name or memory address.
|
||||
*
|
||||
* @return Observed Siemens LOGO! block name or memory address
|
||||
*/
|
||||
public String getObservedBlock() {
|
||||
String result = observe;
|
||||
if (result == null) {
|
||||
result = getBlockName();
|
||||
observe = result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Siemens LOGO! block name or memory address to observe.
|
||||
*
|
||||
* @param name Siemens LOGO! memory block name or memory address
|
||||
*/
|
||||
public void setObservedBlock(final String name) {
|
||||
this.observe = name;
|
||||
}
|
||||
|
||||
public String getObservedChannelType() {
|
||||
String kind = getObservedBlockKind();
|
||||
boolean isInput = kind.equalsIgnoreCase(I_DIGITAL) || kind.equalsIgnoreCase(NI_DIGITAL);
|
||||
return isInput ? DIGITAL_INPUT_ITEM : DIGITAL_OUTPUT_ITEM;
|
||||
}
|
||||
|
||||
public String getObservedBlockKind() {
|
||||
String result = observe;
|
||||
if (result == null) {
|
||||
result = getBlockName();
|
||||
observe = result;
|
||||
}
|
||||
return getBlockKind(result);
|
||||
}
|
||||
|
||||
public Integer getPulseLength() {
|
||||
return pulse;
|
||||
}
|
||||
|
||||
public void setPulseLength(Integer pulse) {
|
||||
this.pulse = pulse;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
/**
|
||||
* 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.plclogo.internal.discovery;
|
||||
|
||||
import static org.openhab.binding.plclogo.internal.PLCLogoBindingConstants.THING_TYPE_DEVICE;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.Inet4Address;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InterfaceAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.net.SocketException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import org.apache.commons.net.util.SubnetUtils;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.config.discovery.AbstractDiscoveryService;
|
||||
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
||||
import org.openhab.core.config.discovery.DiscoveryService;
|
||||
import org.openhab.core.model.script.actions.Ping;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link PLCDiscoveryService} is responsible for discovering devices on
|
||||
* the current Network. It uses every Network Interface which is connected to a network.
|
||||
* Based on network binding discovery service.
|
||||
*
|
||||
* @author Alexander Falkenstern - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(service = DiscoveryService.class)
|
||||
public class PLCDiscoveryService extends AbstractDiscoveryService {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(PLCDiscoveryService.class);
|
||||
private static final Set<ThingTypeUID> THING_TYPES_UIDS = Collections.singleton(THING_TYPE_DEVICE);
|
||||
|
||||
private static final String LOGO_HOST = "address";
|
||||
private static final int LOGO_PORT = 102;
|
||||
|
||||
private static final int CONNECTION_TIMEOUT = 500;
|
||||
private static final int DISCOVERY_TIMEOUT = 30;
|
||||
|
||||
private class Runner implements Runnable {
|
||||
private final ReentrantLock lock = new ReentrantLock();
|
||||
private String host;
|
||||
|
||||
public Runner(final String address) {
|
||||
this.host = address;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
if (Ping.checkVitality(host, LOGO_PORT, CONNECTION_TIMEOUT)) {
|
||||
logger.debug("LOGO! device found at: {}.", host);
|
||||
|
||||
ThingUID thingUID = new ThingUID(THING_TYPE_DEVICE, host.replace('.', '_'));
|
||||
DiscoveryResultBuilder builder = DiscoveryResultBuilder.create(thingUID);
|
||||
builder.withProperty(LOGO_HOST, host);
|
||||
builder.withLabel(host);
|
||||
|
||||
lock.lock();
|
||||
try {
|
||||
thingDiscovered(builder.build());
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
} catch (IOException exception) {
|
||||
logger.debug("LOGO! device not found at: {}.", host);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public PLCDiscoveryService() {
|
||||
super(THING_TYPES_UIDS, DISCOVERY_TIMEOUT);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startScan() {
|
||||
stopScan();
|
||||
|
||||
logger.debug("Start scan for LOGO! bridge");
|
||||
|
||||
Enumeration<NetworkInterface> devices = null;
|
||||
try {
|
||||
devices = NetworkInterface.getNetworkInterfaces();
|
||||
} catch (SocketException exception) {
|
||||
logger.warn("LOGO! bridge discovering: {}.", exception.toString());
|
||||
}
|
||||
|
||||
Set<String> addresses = new TreeSet<>();
|
||||
while ((devices != null) && devices.hasMoreElements()) {
|
||||
NetworkInterface device = devices.nextElement();
|
||||
try {
|
||||
if (!device.isUp() || device.isLoopback()) {
|
||||
continue;
|
||||
}
|
||||
} catch (SocketException exception) {
|
||||
logger.warn("LOGO! bridge discovering: {}.", exception.toString());
|
||||
}
|
||||
for (InterfaceAddress iface : device.getInterfaceAddresses()) {
|
||||
InetAddress address = iface.getAddress();
|
||||
if (address instanceof Inet4Address) {
|
||||
String prefix = String.valueOf(iface.getNetworkPrefixLength());
|
||||
SubnetUtils utilities = new SubnetUtils(address.getHostAddress() + "/" + prefix);
|
||||
addresses.addAll(Arrays.asList(utilities.getInfo().getAllAddresses()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
|
||||
for (String address : addresses) {
|
||||
try {
|
||||
executor.execute(new Runner(address));
|
||||
} catch (RejectedExecutionException exception) {
|
||||
logger.warn("LOGO! bridge discovering: {}.", exception.toString());
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
executor.awaitTermination(CONNECTION_TIMEOUT * addresses.size(), TimeUnit.MILLISECONDS);
|
||||
} catch (InterruptedException exception) {
|
||||
logger.warn("LOGO! bridge discovering: {}.", exception.toString());
|
||||
}
|
||||
executor.shutdown();
|
||||
|
||||
stopScan();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected synchronized void stopScan() {
|
||||
logger.debug("Stop scan for LOGO! bridge");
|
||||
super.stopScan();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,286 @@
|
||||
/**
|
||||
* 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.plclogo.internal.handler;
|
||||
|
||||
import static org.openhab.binding.plclogo.internal.PLCLogoBindingConstants.*;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.plclogo.internal.PLCLogoClient;
|
||||
import org.openhab.binding.plclogo.internal.config.PLCAnalogConfiguration;
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.binding.builder.ChannelBuilder;
|
||||
import org.openhab.core.thing.binding.builder.ThingBuilder;
|
||||
import org.openhab.core.thing.type.ChannelTypeUID;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.openhab.core.types.State;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import Moka7.S7;
|
||||
import Moka7.S7Client;
|
||||
|
||||
/**
|
||||
* The {@link PLCAnalogHandler} is responsible for handling commands, which are
|
||||
* sent to one of the channels.
|
||||
*
|
||||
* @author Alexander Falkenstern - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class PLCAnalogHandler extends PLCCommonHandler {
|
||||
|
||||
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_ANALOG);
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(PLCAnalogHandler.class);
|
||||
private AtomicReference<PLCAnalogConfiguration> config = new AtomicReference<>();
|
||||
|
||||
private static final Map<String, @Nullable Integer> LOGO_BLOCKS_0BA7;
|
||||
static {
|
||||
Map<String, @Nullable Integer> buffer = new HashMap<>();
|
||||
buffer.put(I_ANALOG, 8); // 8 analog inputs
|
||||
buffer.put(Q_ANALOG, 2); // 2 analog outputs
|
||||
buffer.put(M_ANALOG, 16); // 16 analog markers
|
||||
LOGO_BLOCKS_0BA7 = Collections.unmodifiableMap(buffer);
|
||||
}
|
||||
|
||||
private static final Map<String, @Nullable Integer> LOGO_BLOCKS_0BA8;
|
||||
static {
|
||||
Map<String, @Nullable Integer> buffer = new HashMap<>();
|
||||
buffer.put(I_ANALOG, 8); // 8 analog inputs
|
||||
buffer.put(Q_ANALOG, 8); // 8 analog outputs
|
||||
buffer.put(M_ANALOG, 64); // 64 analog markers
|
||||
buffer.put(NI_ANALOG, 32); // 32 network analog inputs
|
||||
buffer.put(NQ_ANALOG, 16); // 16 network analog outputs
|
||||
LOGO_BLOCKS_0BA8 = Collections.unmodifiableMap(buffer);
|
||||
}
|
||||
|
||||
private static final Map<String, @Nullable Map<String, @Nullable Integer>> LOGO_BLOCK_NUMBER;
|
||||
static {
|
||||
Map<String, @Nullable Map<String, @Nullable Integer>> buffer = new HashMap<>();
|
||||
buffer.put(LOGO_0BA7, LOGO_BLOCKS_0BA7);
|
||||
buffer.put(LOGO_0BA8, LOGO_BLOCKS_0BA8);
|
||||
LOGO_BLOCK_NUMBER = Collections.unmodifiableMap(buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public PLCAnalogHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
if (!isThingOnline()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Channel channel = getThing().getChannel(channelUID.getId());
|
||||
String name = getBlockFromChannel(channel);
|
||||
if (!isValid(name) || (channel == null)) {
|
||||
logger.debug("Can not update channel {}, block {}.", channelUID, name);
|
||||
return;
|
||||
}
|
||||
|
||||
int address = getAddress(name);
|
||||
PLCLogoClient client = getLogoClient();
|
||||
if ((address != INVALID) && (client != null)) {
|
||||
if (command instanceof RefreshType) {
|
||||
int base = getBase(name);
|
||||
byte[] buffer = new byte[getBufferLength()];
|
||||
int result = client.readDBArea(1, base, buffer.length, S7Client.S7WLByte, buffer);
|
||||
if (result == 0) {
|
||||
updateChannel(channel, S7.GetShortAt(buffer, address - base));
|
||||
} else {
|
||||
logger.debug("Can not read data from LOGO!: {}.", S7Client.ErrorText(result));
|
||||
}
|
||||
} else if (command instanceof DecimalType) {
|
||||
byte[] buffer = new byte[2];
|
||||
String type = channel.getAcceptedItemType();
|
||||
if (ANALOG_ITEM.equalsIgnoreCase(type)) {
|
||||
S7.SetShortAt(buffer, 0, ((DecimalType) command).intValue());
|
||||
} else {
|
||||
logger.debug("Channel {} will not accept {} items.", channelUID, type);
|
||||
}
|
||||
int result = client.writeDBArea(1, address, buffer.length, S7Client.S7WLByte, buffer);
|
||||
if (result != 0) {
|
||||
logger.debug("Can not write data to LOGO!: {}.", S7Client.ErrorText(result));
|
||||
}
|
||||
} else {
|
||||
logger.debug("Channel {} received not supported command {}.", channelUID, command);
|
||||
}
|
||||
} else {
|
||||
logger.info("Invalid channel {} or client {} found.", channelUID, client);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setData(final byte[] data) {
|
||||
if (!isThingOnline()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.length != getBufferLength()) {
|
||||
logger.info("Received and configured data sizes does not match.");
|
||||
return;
|
||||
}
|
||||
|
||||
List<Channel> channels = thing.getChannels();
|
||||
if (channels.size() != getNumberOfChannels()) {
|
||||
logger.info("Received and configured channel sizes does not match.");
|
||||
return;
|
||||
}
|
||||
|
||||
Boolean force = config.get().isUpdateForced();
|
||||
Integer threshold = config.get().getThreshold();
|
||||
for (Channel channel : channels) {
|
||||
ChannelUID channelUID = channel.getUID();
|
||||
String name = getBlockFromChannel(channel);
|
||||
|
||||
int address = getAddress(name);
|
||||
if (address != INVALID) {
|
||||
DecimalType state = (DecimalType) getOldValue(name);
|
||||
int value = S7.GetShortAt(data, address - getBase(name));
|
||||
if ((state == null) || (Math.abs(value - state.intValue()) > threshold) || force) {
|
||||
updateChannel(channel, value);
|
||||
}
|
||||
if (logger.isTraceEnabled()) {
|
||||
int index = address - getBase(name);
|
||||
logger.trace("Channel {} received [{}, {}].", channelUID, data[index], data[index + 1]);
|
||||
}
|
||||
} else {
|
||||
logger.info("Invalid channel {} found.", channelUID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateState(ChannelUID channelUID, State state) {
|
||||
super.updateState(channelUID, state);
|
||||
|
||||
Channel channel = thing.getChannel(channelUID.getId());
|
||||
setOldValue(getBlockFromChannel(channel), state);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateConfiguration(Configuration configuration) {
|
||||
super.updateConfiguration(configuration);
|
||||
config.set(getConfigAs(PLCAnalogConfiguration.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isValid(final String name) {
|
||||
if (3 <= name.length() && (name.length() <= 5)) {
|
||||
String kind = getBlockKind();
|
||||
if (Character.isDigit(name.charAt(2)) || Character.isDigit(name.charAt(3))) {
|
||||
boolean valid = I_ANALOG.equalsIgnoreCase(kind) || NI_ANALOG.equalsIgnoreCase(kind);
|
||||
valid = valid || Q_ANALOG.equalsIgnoreCase(kind) || NQ_ANALOG.equalsIgnoreCase(kind);
|
||||
return name.startsWith(kind) && (valid || M_ANALOG.equalsIgnoreCase(kind));
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getBlockKind() {
|
||||
return config.get().getBlockKind();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getNumberOfChannels() {
|
||||
String kind = getBlockKind();
|
||||
String family = getLogoFamily();
|
||||
logger.debug("Get block number of {} LOGO! for {} blocks.", family, kind);
|
||||
|
||||
Map<?, @Nullable Integer> blocks = LOGO_BLOCK_NUMBER.get(family);
|
||||
Integer number = (blocks != null) ? blocks.get(kind) : null;
|
||||
return (number != null) ? number.intValue() : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getAddress(final String name) {
|
||||
int address = super.getAddress(name);
|
||||
if (address != INVALID) {
|
||||
address = getBase(name) + (address - 1) * 2;
|
||||
} else {
|
||||
logger.info("Wrong configurated LOGO! block {} found.", name);
|
||||
}
|
||||
return address;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doInitialization() {
|
||||
Thing thing = getThing();
|
||||
logger.debug("Initialize LOGO! analog input blocks handler.");
|
||||
|
||||
config.set(getConfigAs(PLCAnalogConfiguration.class));
|
||||
|
||||
super.doInitialization();
|
||||
if (ThingStatus.OFFLINE != thing.getStatus()) {
|
||||
String kind = getBlockKind();
|
||||
String text = I_ANALOG.equalsIgnoreCase(kind) || NI_ANALOG.equalsIgnoreCase(kind) ? "input" : "output";
|
||||
|
||||
ThingBuilder tBuilder = editThing();
|
||||
|
||||
String label = thing.getLabel();
|
||||
if (label == null) {
|
||||
Bridge bridge = getBridge();
|
||||
label = (bridge == null) || (bridge.getLabel() == null) ? "Siemens Logo!" : bridge.getLabel();
|
||||
label += (": analog " + text + "s");
|
||||
}
|
||||
tBuilder.withLabel(label);
|
||||
|
||||
String type = config.get().getChannelType();
|
||||
for (int i = 0; i < getNumberOfChannels(); i++) {
|
||||
String name = kind + String.valueOf(i + 1);
|
||||
ChannelUID uid = new ChannelUID(thing.getUID(), name);
|
||||
ChannelBuilder cBuilder = ChannelBuilder.create(uid, type);
|
||||
cBuilder.withType(new ChannelTypeUID(BINDING_ID, type.toLowerCase()));
|
||||
cBuilder.withLabel(name);
|
||||
cBuilder.withDescription("Analog " + text + " block " + name);
|
||||
cBuilder.withProperties(Collections.singletonMap(BLOCK_PROPERTY, name));
|
||||
tBuilder.withChannel(cBuilder.build());
|
||||
setOldValue(name, null);
|
||||
}
|
||||
|
||||
updateThing(tBuilder.build());
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateChannel(final Channel channel, int value) {
|
||||
ChannelUID channelUID = channel.getUID();
|
||||
String type = channel.getAcceptedItemType();
|
||||
if (ANALOG_ITEM.equalsIgnoreCase(type)) {
|
||||
updateState(channelUID, new DecimalType(value));
|
||||
logger.debug("Channel {} accepting {} was set to {}.", channelUID, type, value);
|
||||
} else {
|
||||
logger.debug("Channel {} will not accept {} items.", channelUID, type);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,377 @@
|
||||
/**
|
||||
* 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.plclogo.internal.handler;
|
||||
|
||||
import static org.openhab.binding.plclogo.internal.PLCLogoBindingConstants.*;
|
||||
|
||||
import java.time.DateTimeException;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.plclogo.internal.PLCLogoBindingConstants.Layout;
|
||||
import org.openhab.binding.plclogo.internal.PLCLogoClient;
|
||||
import org.openhab.binding.plclogo.internal.config.PLCLogoBridgeConfiguration;
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
import org.openhab.core.library.types.DateTimeType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.binding.BaseBridgeHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import Moka7.S7Client;
|
||||
|
||||
/**
|
||||
* The {@link PLCBridgeHandler} is responsible for handling commands, which are
|
||||
* sent to one of the channels.
|
||||
*
|
||||
* @author Alexander Falkenstern - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class PLCBridgeHandler extends BaseBridgeHandler {
|
||||
|
||||
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_DEVICE);
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(PLCBridgeHandler.class);
|
||||
|
||||
private Map<ChannelUID, @Nullable String> oldValues = new HashMap<>();
|
||||
|
||||
@Nullable
|
||||
private volatile PLCLogoClient client; // S7 client used for communication with Logo!
|
||||
private final Set<PLCCommonHandler> handlers = new HashSet<>();
|
||||
private AtomicReference<PLCLogoBridgeConfiguration> config = new AtomicReference<>();
|
||||
|
||||
@Nullable
|
||||
private ScheduledFuture<?> rtcJob;
|
||||
private AtomicReference<ZonedDateTime> rtc = new AtomicReference<>(ZonedDateTime.now());
|
||||
private final Runnable rtcReader = new Runnable() {
|
||||
private final List<Channel> channels = getThing().getChannels();
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
for (Channel channel : channels) {
|
||||
handleCommand(channel.getUID(), RefreshType.REFRESH);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@Nullable
|
||||
private ScheduledFuture<?> readerJob;
|
||||
private final Runnable dataReader = new Runnable() {
|
||||
// Buffer for block data read operation
|
||||
private final byte[] buffer = new byte[2048];
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
PLCLogoClient localClient = client;
|
||||
Map<?, @Nullable Layout> memory = LOGO_MEMORY_BLOCK.get(getLogoFamily());
|
||||
Layout layout = (memory != null) ? memory.get(MEMORY_SIZE) : null;
|
||||
if ((layout != null) && (localClient != null)) {
|
||||
try {
|
||||
int result = localClient.readDBArea(1, 0, layout.length, S7Client.S7WLByte, buffer);
|
||||
if (result == 0) {
|
||||
synchronized (handlers) {
|
||||
for (PLCCommonHandler handler : handlers) {
|
||||
int length = handler.getBufferLength();
|
||||
int address = handler.getStartAddress();
|
||||
if ((length > 0) && (address != PLCCommonHandler.INVALID)) {
|
||||
handler.setData(Arrays.copyOfRange(buffer, address, address + length));
|
||||
} else {
|
||||
logger.debug("Invalid handler {} found.", handler.getClass().getSimpleName());
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logger.debug("Can not read data from LOGO!: {}.", S7Client.ErrorText(result));
|
||||
}
|
||||
} catch (Exception exception) {
|
||||
logger.error("Reader thread got exception: {}.", exception.getMessage());
|
||||
}
|
||||
} else {
|
||||
logger.debug("Either memory block {} or LOGO! client {} is invalid.", memory, localClient);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public PLCBridgeHandler(Bridge bridge) {
|
||||
super(bridge);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
logger.debug("Handle command {} on channel {}", command, channelUID);
|
||||
|
||||
Thing thing = getThing();
|
||||
if (ThingStatus.ONLINE != thing.getStatus()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(command instanceof RefreshType)) {
|
||||
logger.debug("Not supported command {} received.", command);
|
||||
return;
|
||||
}
|
||||
|
||||
PLCLogoClient localClient = client;
|
||||
String channelId = channelUID.getId();
|
||||
Channel channel = thing.getChannel(channelId);
|
||||
Layout layout = LOGO_CHANNELS.get(channelId);
|
||||
if ((localClient != null) && (channel != null) && (layout != null)) {
|
||||
byte[] buffer = new byte[layout.length];
|
||||
Arrays.fill(buffer, (byte) 0);
|
||||
int result = localClient.readDBArea(1, layout.address, buffer.length, S7Client.S7WLByte, buffer);
|
||||
if (result == 0) {
|
||||
if (RTC_CHANNEL.equals(channelId)) {
|
||||
ZonedDateTime clock = ZonedDateTime.now();
|
||||
if (!LOGO_0BA7.equalsIgnoreCase(getLogoFamily())) {
|
||||
try {
|
||||
int year = clock.getYear() / 100;
|
||||
clock = clock.withYear(100 * year + buffer[0]);
|
||||
clock = clock.withMonth(buffer[1]);
|
||||
clock = clock.withDayOfMonth(buffer[2]);
|
||||
clock = clock.withHour(buffer[3]);
|
||||
clock = clock.withMinute(buffer[4]);
|
||||
clock = clock.withSecond(buffer[5]);
|
||||
} catch (DateTimeException exception) {
|
||||
clock = ZonedDateTime.now();
|
||||
logger.info("Return local server time: {}.", exception.getMessage());
|
||||
}
|
||||
}
|
||||
rtc.set(clock);
|
||||
updateState(channelUID, new DateTimeType(clock));
|
||||
} else if (DAIGNOSTICS_CHANNEL.equals(channelId)) {
|
||||
Map<Integer, @Nullable String> states = LOGO_STATES.get(getLogoFamily());
|
||||
if (states != null) {
|
||||
for (Integer key : states.keySet()) {
|
||||
String message = states.get(buffer[0] & key.intValue());
|
||||
synchronized (oldValues) {
|
||||
if ((message != null) && (oldValues.get(channelUID) != message)) {
|
||||
updateState(channelUID, new StringType(message));
|
||||
oldValues.put(channelUID, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (DAY_OF_WEEK_CHANNEL.equals(channelId)) {
|
||||
String value = DAY_OF_WEEK.get(Integer.valueOf(buffer[0]));
|
||||
synchronized (oldValues) {
|
||||
if ((value != null) && (oldValues.get(channelUID) != value)) {
|
||||
updateState(channelUID, new StringType(value));
|
||||
oldValues.put(channelUID, value);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logger.info("Invalid channel {} or client {} found.", channelUID, client);
|
||||
}
|
||||
|
||||
if (logger.isTraceEnabled()) {
|
||||
String raw = Arrays.toString(buffer);
|
||||
String type = channel.getAcceptedItemType();
|
||||
logger.trace("Channel {} accepting {} received {}.", channelUID, type, raw);
|
||||
}
|
||||
} else {
|
||||
logger.debug("Can not read data from LOGO!: {}.", S7Client.ErrorText(result));
|
||||
}
|
||||
} else {
|
||||
logger.info("Invalid channel {} or client {} found.", channelUID, client);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
logger.debug("Initialize LOGO! bridge handler.");
|
||||
|
||||
synchronized (oldValues) {
|
||||
oldValues.clear();
|
||||
}
|
||||
config.set(getConfigAs(PLCLogoBridgeConfiguration.class));
|
||||
|
||||
boolean configured = (config.get().getLocalTSAP() != null);
|
||||
configured = configured && (config.get().getRemoteTSAP() != null);
|
||||
|
||||
if (configured) {
|
||||
if (client == null) {
|
||||
client = new PLCLogoClient();
|
||||
}
|
||||
configured = connect();
|
||||
} else {
|
||||
String message = "Can not initialize LOGO!. Please, check ip address / TSAP settings.";
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, message);
|
||||
}
|
||||
|
||||
if (configured) {
|
||||
String host = config.get().getAddress();
|
||||
if (readerJob == null) {
|
||||
Integer interval = config.get().getRefreshRate();
|
||||
logger.info("Creating new reader job for {} with interval {} ms.", host, interval);
|
||||
readerJob = scheduler.scheduleWithFixedDelay(dataReader, 100, interval, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
if (rtcJob == null) {
|
||||
logger.info("Creating new RTC job for {} with interval 1 s.", host);
|
||||
rtcJob = scheduler.scheduleAtFixedRate(rtcReader, 100, 1000, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
} else {
|
||||
String message = "Can not initialize LOGO!. Please, check network connection.";
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, message);
|
||||
client = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
logger.debug("Dispose LOGO! bridge handler.");
|
||||
super.dispose();
|
||||
|
||||
if (rtcJob != null) {
|
||||
rtcJob.cancel(false);
|
||||
rtcJob = null;
|
||||
logger.info("Destroy RTC job for {}.", config.get().getAddress());
|
||||
}
|
||||
|
||||
if (readerJob != null) {
|
||||
readerJob.cancel(false);
|
||||
readerJob = null;
|
||||
logger.info("Destroy reader job for {}.", config.get().getAddress());
|
||||
}
|
||||
|
||||
if (disconnect()) {
|
||||
client = null;
|
||||
}
|
||||
|
||||
synchronized (oldValues) {
|
||||
oldValues.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void childHandlerInitialized(ThingHandler childHandler, Thing childThing) {
|
||||
super.childHandlerInitialized(childHandler, childThing);
|
||||
if (childHandler instanceof PLCCommonHandler) {
|
||||
PLCCommonHandler handler = (PLCCommonHandler) childHandler;
|
||||
synchronized (handlers) {
|
||||
if (!handlers.contains(handler)) {
|
||||
handlers.add(handler);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void childHandlerDisposed(ThingHandler childHandler, Thing childThing) {
|
||||
if (childHandler instanceof PLCCommonHandler) {
|
||||
PLCCommonHandler handler = (PLCCommonHandler) childHandler;
|
||||
synchronized (handlers) {
|
||||
if (handlers.contains(handler)) {
|
||||
handlers.remove(handler);
|
||||
}
|
||||
}
|
||||
}
|
||||
super.childHandlerDisposed(childHandler, childThing);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns Siemens LOGO! communication client
|
||||
*
|
||||
* @return Configured Siemens LOGO! client
|
||||
*/
|
||||
public @Nullable PLCLogoClient getLogoClient() {
|
||||
return client;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns configured Siemens LOGO! family: 0BA7 or 0BA8
|
||||
*
|
||||
* @return Configured Siemens LOGO! family
|
||||
*/
|
||||
public String getLogoFamily() {
|
||||
return config.get().getFamily();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns RTC was fetched last from Siemens LOGO!
|
||||
*
|
||||
* @return Siemens LOGO! RTC
|
||||
*/
|
||||
public ZonedDateTime getLogoRTC() {
|
||||
return rtc.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateConfiguration(Configuration configuration) {
|
||||
super.updateConfiguration(configuration);
|
||||
config.set(getConfigAs(PLCLogoBridgeConfiguration.class));
|
||||
}
|
||||
|
||||
/**
|
||||
* Read connection parameter and connect to Siemens LOGO!
|
||||
*
|
||||
* @return True, if connected and false otherwise
|
||||
*/
|
||||
private boolean connect() {
|
||||
boolean result = false;
|
||||
|
||||
PLCLogoClient localClient = client;
|
||||
if (localClient != null) {
|
||||
Integer local = config.get().getLocalTSAP();
|
||||
Integer remote = config.get().getRemoteTSAP();
|
||||
if (!localClient.isConnected() && (local != null) && (remote != null)) {
|
||||
localClient.Connect(config.get().getAddress(), local.intValue(), remote.intValue());
|
||||
}
|
||||
result = localClient.isConnected();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnect from Siemens LOGO!
|
||||
*
|
||||
* @return True, if disconnected and false otherwise
|
||||
*/
|
||||
private boolean disconnect() {
|
||||
boolean result = false;
|
||||
|
||||
PLCLogoClient localClient = client;
|
||||
if (localClient != null) {
|
||||
if (localClient.isConnected()) {
|
||||
localClient.Disconnect();
|
||||
}
|
||||
result = !localClient.isConnected();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,285 @@
|
||||
/**
|
||||
* 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.plclogo.internal.handler;
|
||||
|
||||
import static org.openhab.binding.plclogo.internal.PLCLogoBindingConstants.*;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.plclogo.internal.PLCLogoBindingConstants;
|
||||
import org.openhab.binding.plclogo.internal.PLCLogoBindingConstants.Layout;
|
||||
import org.openhab.binding.plclogo.internal.PLCLogoClient;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||
import org.openhab.core.thing.binding.BridgeHandler;
|
||||
import org.openhab.core.thing.binding.builder.ThingBuilder;
|
||||
import org.openhab.core.types.State;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link PLCCommonHandler} is responsible for handling commands, which are
|
||||
* sent to one of the channels.
|
||||
*
|
||||
* @author Alexander Falkenstern - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public abstract class PLCCommonHandler extends BaseThingHandler {
|
||||
|
||||
public static final int INVALID = Integer.MAX_VALUE;
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(PLCCommonHandler.class);
|
||||
|
||||
private Map<String, @Nullable State> oldValues = new HashMap<>();
|
||||
|
||||
private @Nullable PLCLogoClient client;
|
||||
private String family = NOT_SUPPORTED;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public PLCCommonHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
synchronized (oldValues) {
|
||||
oldValues.clear();
|
||||
}
|
||||
scheduler.execute(this::doInitialization);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
logger.debug("Dispose LOGO! common block handler.");
|
||||
super.dispose();
|
||||
|
||||
ThingBuilder tBuilder = editThing();
|
||||
for (Channel channel : getThing().getChannels()) {
|
||||
tBuilder.withoutChannel(channel.getUID());
|
||||
}
|
||||
updateThing(tBuilder.build());
|
||||
|
||||
synchronized (oldValues) {
|
||||
oldValues.clear();
|
||||
}
|
||||
|
||||
family = NOT_SUPPORTED;
|
||||
client = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return data buffer start address to read/write dependent on configured Logo! family.
|
||||
*
|
||||
* @return Start address of data buffer
|
||||
*/
|
||||
public int getStartAddress() {
|
||||
String kind = getBlockKind();
|
||||
String family = getLogoFamily();
|
||||
logger.debug("Get start address of {} LOGO! for {} blocks.", family, kind);
|
||||
|
||||
Map<?, @Nullable Layout> memory = LOGO_MEMORY_BLOCK.get(family);
|
||||
Layout layout = (memory != null) ? memory.get(kind) : null;
|
||||
return layout != null ? layout.address : INVALID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return data buffer length to read/write dependent on configured Logo! family.
|
||||
*
|
||||
* @return Length of data buffer in bytes
|
||||
*/
|
||||
public int getBufferLength() {
|
||||
String kind = getBlockKind();
|
||||
String family = getLogoFamily();
|
||||
logger.debug("Get data buffer length of {} LOGO! for {} blocks.", family, kind);
|
||||
|
||||
Map<?, @Nullable Layout> memory = LOGO_MEMORY_BLOCK.get(family);
|
||||
Layout layout = (memory != null) ? memory.get(kind) : null;
|
||||
return layout != null ? layout.length : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update value channel of current thing with new data.
|
||||
*
|
||||
* @param data Data value to update with
|
||||
*/
|
||||
public abstract void setData(final byte[] data);
|
||||
|
||||
/**
|
||||
* Checks if block name is valid.
|
||||
*
|
||||
* @param name Name of the LOGO! block to check
|
||||
* @return True, if the name is valid and false otherwise
|
||||
*/
|
||||
protected abstract boolean isValid(final String name);
|
||||
|
||||
/**
|
||||
* Returns configured block kind.
|
||||
*
|
||||
* @return Configured block kind
|
||||
*/
|
||||
protected abstract String getBlockKind();
|
||||
|
||||
/**
|
||||
* Return number of channels dependent on configured Logo! family.
|
||||
*
|
||||
* @return Number of channels
|
||||
*/
|
||||
protected abstract int getNumberOfChannels();
|
||||
|
||||
/**
|
||||
* Calculate address for the block with given name.
|
||||
*
|
||||
* @param name Name of the LOGO! block
|
||||
* @return Calculated address
|
||||
*/
|
||||
protected int getAddress(final String name) {
|
||||
int address = INVALID;
|
||||
|
||||
logger.debug("Get address of {} LOGO! for block {} .", getLogoFamily(), name);
|
||||
|
||||
int base = getBase(name);
|
||||
if (isValid(name) && (base != INVALID)) {
|
||||
String block = name.split("\\.")[0];
|
||||
if (Character.isDigit(block.charAt(1))) {
|
||||
address = Integer.parseInt(block.substring(1));
|
||||
} else if (Character.isDigit(block.charAt(2))) {
|
||||
address = Integer.parseInt(block.substring(2));
|
||||
} else if (Character.isDigit(block.charAt(3))) {
|
||||
address = Integer.parseInt(block.substring(3));
|
||||
}
|
||||
} else {
|
||||
logger.info("Wrong configurated LOGO! block {} found.", name);
|
||||
}
|
||||
|
||||
return address;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate address offset for given block name.
|
||||
*
|
||||
* @param name Name of the data block
|
||||
* @return Calculated address offset
|
||||
*/
|
||||
protected int getBase(final String name) {
|
||||
Layout layout = null;
|
||||
String family = getLogoFamily();
|
||||
|
||||
logger.debug("Get base address of {} LOGO! for block {} .", family, name);
|
||||
|
||||
String block = name.split("\\.")[0];
|
||||
Map<?, @Nullable Layout> memory = LOGO_MEMORY_BLOCK.get(family);
|
||||
if (isValid(name) && !block.isEmpty() && (memory != null)) {
|
||||
if (Character.isDigit(block.charAt(1))) {
|
||||
layout = memory.get(block.substring(0, 1));
|
||||
} else if (Character.isDigit(block.charAt(2))) {
|
||||
layout = memory.get(block.substring(0, 2));
|
||||
} else if (Character.isDigit(block.charAt(3))) {
|
||||
layout = memory.get(block.substring(0, 3));
|
||||
}
|
||||
}
|
||||
|
||||
return layout != null ? layout.address : INVALID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if thing handler is valid and online.
|
||||
*
|
||||
* @return True, if handler is valid and false otherwise
|
||||
*/
|
||||
protected boolean isThingOnline() {
|
||||
Bridge bridge = getBridge();
|
||||
if (bridge != null) {
|
||||
Thing thing = getThing();
|
||||
return ((ThingStatus.ONLINE == bridge.getStatus()) && (ThingStatus.ONLINE == thing.getStatus()));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected @Nullable State getOldValue(final String name) {
|
||||
synchronized (oldValues) {
|
||||
return oldValues.get(name);
|
||||
}
|
||||
}
|
||||
|
||||
protected void setOldValue(final String name, final @Nullable State value) {
|
||||
synchronized (oldValues) {
|
||||
if (!NOT_SUPPORTED.equalsIgnoreCase(name)) {
|
||||
oldValues.put(name, value);
|
||||
} else {
|
||||
logger.info("Wrong configurated LOGO! block {} found.", name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns configured LOGO! communication client.
|
||||
*
|
||||
* @return Configured LOGO! client
|
||||
*/
|
||||
protected @Nullable PLCLogoClient getLogoClient() {
|
||||
return client;
|
||||
}
|
||||
|
||||
protected @Nullable PLCBridgeHandler getBridgeHandler() {
|
||||
Bridge bridge = getBridge();
|
||||
if (bridge != null) {
|
||||
BridgeHandler handler = bridge.getHandler();
|
||||
if ((handler != null) && (handler instanceof PLCBridgeHandler)) {
|
||||
return (PLCBridgeHandler) handler;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns configured LOGO! family.
|
||||
*
|
||||
* @see PLCLogoBindingConstants#LOGO_0BA7
|
||||
* @see PLCLogoBindingConstants#LOGO_0BA8
|
||||
* @return Configured LOGO! family
|
||||
*/
|
||||
protected String getLogoFamily() {
|
||||
return family;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform thing initialization.
|
||||
*/
|
||||
protected void doInitialization() {
|
||||
PLCBridgeHandler handler = getBridgeHandler();
|
||||
if (handler != null) {
|
||||
family = handler.getLogoFamily();
|
||||
client = handler.getLogoClient();
|
||||
if ((client == null) || NOT_SUPPORTED.equalsIgnoreCase(family)) {
|
||||
String message = "Can not initialize LOGO! block handler.";
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, message);
|
||||
|
||||
Thing thing = getThing();
|
||||
logger.warn("Can not initialize thing {} for LOGO! {}.", thing.getUID(), thing.getBridgeUID());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected static String getBlockFromChannel(final @Nullable Channel channel) {
|
||||
return channel == null ? NOT_SUPPORTED : channel.getProperties().get(BLOCK_PROPERTY);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,275 @@
|
||||
/**
|
||||
* 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.plclogo.internal.handler;
|
||||
|
||||
import static org.openhab.binding.plclogo.internal.PLCLogoBindingConstants.*;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.plclogo.internal.PLCLogoClient;
|
||||
import org.openhab.binding.plclogo.internal.config.PLCDateTimeConfiguration;
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
import org.openhab.core.library.types.DateTimeType;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.binding.builder.ChannelBuilder;
|
||||
import org.openhab.core.thing.binding.builder.ThingBuilder;
|
||||
import org.openhab.core.thing.type.ChannelTypeUID;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.openhab.core.types.State;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import Moka7.S7;
|
||||
import Moka7.S7Client;
|
||||
|
||||
/**
|
||||
* The {@link PLCDateTimeHandler} is responsible for handling commands, which are
|
||||
* sent to one of the channels.
|
||||
*
|
||||
* @author Alexander Falkenstern - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class PLCDateTimeHandler extends PLCCommonHandler {
|
||||
|
||||
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_DATETIME);
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(PLCDateTimeHandler.class);
|
||||
private AtomicReference<PLCDateTimeConfiguration> config = new AtomicReference<>();
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public PLCDateTimeHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
if (!isThingOnline()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Channel channel = getThing().getChannel(channelUID.getId());
|
||||
String name = config.get().getBlockName();
|
||||
if (!isValid(name) || (channel == null)) {
|
||||
logger.debug("Can not update channel {}, block {}.", channelUID, name);
|
||||
return;
|
||||
}
|
||||
|
||||
int address = getAddress(name);
|
||||
PLCLogoClient client = getLogoClient();
|
||||
if ((address != INVALID) && (client != null)) {
|
||||
if (command instanceof RefreshType) {
|
||||
byte[] buffer = new byte[getBufferLength()];
|
||||
int result = client.readDBArea(1, 0, buffer.length, S7Client.S7WLByte, buffer);
|
||||
if (result == 0) {
|
||||
updateChannel(channel, S7.GetShortAt(buffer, address));
|
||||
} else {
|
||||
logger.debug("Can not read data from LOGO!: {}.", S7Client.ErrorText(result));
|
||||
}
|
||||
} else if (command instanceof DateTimeType) {
|
||||
byte[] buffer = new byte[2];
|
||||
String type = channel.getAcceptedItemType();
|
||||
if (DATE_TIME_ITEM.equalsIgnoreCase(type)) {
|
||||
ZonedDateTime datetime = ((DateTimeType) command).getZonedDateTime();
|
||||
if ("Time".equalsIgnoreCase(channelUID.getId())) {
|
||||
buffer[0] = S7.ByteToBCD(datetime.getHour());
|
||||
buffer[1] = S7.ByteToBCD(datetime.getMinute());
|
||||
} else if ("Date".equalsIgnoreCase(channelUID.getId())) {
|
||||
buffer[0] = S7.ByteToBCD(datetime.getMonthValue());
|
||||
buffer[1] = S7.ByteToBCD(datetime.getDayOfMonth());
|
||||
}
|
||||
} else {
|
||||
logger.debug("Channel {} will not accept {} items.", channelUID, type);
|
||||
}
|
||||
int result = client.writeDBArea(1, address, buffer.length, S7Client.S7WLByte, buffer);
|
||||
if (result != 0) {
|
||||
logger.debug("Can not write data to LOGO!: {}.", S7Client.ErrorText(result));
|
||||
}
|
||||
} else {
|
||||
logger.debug("Channel {} received not supported command {}.", channelUID, command);
|
||||
}
|
||||
} else {
|
||||
logger.info("Invalid channel {} or client {} found.", channelUID, client);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setData(final byte[] data) {
|
||||
if (!isThingOnline()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.length != getBufferLength()) {
|
||||
logger.info("Received and configured data sizes does not match.");
|
||||
return;
|
||||
}
|
||||
|
||||
List<Channel> channels = getThing().getChannels();
|
||||
if (channels.size() != getNumberOfChannels()) {
|
||||
logger.info("Received and configured channel sizes does not match.");
|
||||
return;
|
||||
}
|
||||
|
||||
String name = config.get().getBlockName();
|
||||
Boolean force = config.get().isUpdateForced();
|
||||
for (Channel channel : channels) {
|
||||
int address = getAddress(name);
|
||||
if (address != INVALID) {
|
||||
DecimalType state = (DecimalType) getOldValue(name);
|
||||
int value = S7.GetShortAt(data, address);
|
||||
if ((state == null) || (value != state.intValue()) || force) {
|
||||
updateChannel(channel, value);
|
||||
}
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Channel {} received [{}, {}].", channel.getUID(), data[address], data[address + 1]);
|
||||
}
|
||||
} else {
|
||||
logger.info("Invalid channel {} found.", channel.getUID());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateState(ChannelUID channelUID, State state) {
|
||||
super.updateState(channelUID, state);
|
||||
if (state instanceof DecimalType) {
|
||||
setOldValue(config.get().getBlockName(), state);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateConfiguration(Configuration configuration) {
|
||||
super.updateConfiguration(configuration);
|
||||
config.set(getConfigAs(PLCDateTimeConfiguration.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isValid(final String name) {
|
||||
if (3 <= name.length() && (name.length() <= 5)) {
|
||||
String kind = getBlockKind();
|
||||
if (Character.isDigit(name.charAt(2))) {
|
||||
return name.startsWith(kind) && MEMORY_WORD.equalsIgnoreCase(kind);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getBlockKind() {
|
||||
return config.get().getBlockKind();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getNumberOfChannels() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doInitialization() {
|
||||
Thing thing = getThing();
|
||||
logger.debug("Initialize LOGO! date/time handler.");
|
||||
|
||||
config.set(getConfigAs(PLCDateTimeConfiguration.class));
|
||||
|
||||
super.doInitialization();
|
||||
if (ThingStatus.OFFLINE != thing.getStatus()) {
|
||||
String block = config.get().getBlockType();
|
||||
String text = "Time".equalsIgnoreCase(block) ? "Time" : "Date";
|
||||
|
||||
ThingBuilder tBuilder = editThing();
|
||||
|
||||
String label = thing.getLabel();
|
||||
if (label == null) {
|
||||
Bridge bridge = getBridge();
|
||||
label = (bridge == null) || (bridge.getLabel() == null) ? "Siemens Logo!" : bridge.getLabel();
|
||||
label += (": " + text.toLowerCase() + " in/output");
|
||||
}
|
||||
tBuilder.withLabel(label);
|
||||
|
||||
String name = config.get().getBlockName();
|
||||
String type = config.get().getChannelType();
|
||||
ChannelUID uid = new ChannelUID(thing.getUID(), "Time".equalsIgnoreCase(block) ? "time" : "date");
|
||||
ChannelBuilder cBuilder = ChannelBuilder.create(uid, type);
|
||||
cBuilder.withType(new ChannelTypeUID(BINDING_ID, type.toLowerCase()));
|
||||
cBuilder.withLabel(name);
|
||||
cBuilder.withDescription(text + " block parameter " + name);
|
||||
cBuilder.withProperties(Collections.singletonMap(BLOCK_PROPERTY, name));
|
||||
tBuilder.withChannel(cBuilder.build());
|
||||
|
||||
cBuilder = ChannelBuilder.create(new ChannelUID(thing.getUID(), VALUE_CHANNEL), ANALOG_ITEM);
|
||||
cBuilder.withType(new ChannelTypeUID(BINDING_ID, ANALOG_ITEM.toLowerCase()));
|
||||
cBuilder.withLabel(name);
|
||||
cBuilder.withDescription(text + " block parameter " + name);
|
||||
cBuilder.withProperties(Collections.singletonMap(BLOCK_PROPERTY, name));
|
||||
tBuilder.withChannel(cBuilder.build());
|
||||
setOldValue(name, null);
|
||||
|
||||
updateThing(tBuilder.build());
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateChannel(final Channel channel, int value) {
|
||||
ChannelUID channelUID = channel.getUID();
|
||||
PLCBridgeHandler handler = getBridgeHandler();
|
||||
if (handler == null) {
|
||||
String name = config.get().getBlockName();
|
||||
logger.debug("Can not update channel {}, block {}.", channelUID, name);
|
||||
return;
|
||||
}
|
||||
|
||||
String type = channel.getAcceptedItemType();
|
||||
if (DATE_TIME_ITEM.equalsIgnoreCase(type)) {
|
||||
String channelId = channelUID.getId();
|
||||
ZonedDateTime datetime = ZonedDateTime.from(handler.getLogoRTC());
|
||||
|
||||
byte[] data = new byte[2];
|
||||
S7.SetShortAt(data, 0, value);
|
||||
if ("Time".equalsIgnoreCase(channelId)) {
|
||||
if ((value < 0x0) || (value > 0x2359)) {
|
||||
logger.debug("Channel {} got garbage time {}.", channelUID, Long.toHexString(value));
|
||||
}
|
||||
datetime = datetime.withHour(S7.BCDtoByte(data[0]));
|
||||
datetime = datetime.withMinute(S7.BCDtoByte(data[1]));
|
||||
} else if ("Date".equalsIgnoreCase(channelId)) {
|
||||
if ((value < 0x0101) || (value > 0x1231)) {
|
||||
logger.debug("Channel {} got garbage date {}.", channelUID, Long.toHexString(value));
|
||||
}
|
||||
datetime = datetime.withMonth(S7.BCDtoByte(data[0]));
|
||||
datetime = datetime.withDayOfMonth(S7.BCDtoByte(data[1]));
|
||||
} else {
|
||||
logger.debug("Channel {} has wrong id {}.", channelUID, channelId);
|
||||
}
|
||||
updateState(channelUID, new DateTimeType(datetime));
|
||||
logger.debug("Channel {} accepting {} was set to {}.", channelUID, type, datetime);
|
||||
} else if (ANALOG_ITEM.equalsIgnoreCase(type)) {
|
||||
updateState(channelUID, new DecimalType(value));
|
||||
logger.debug("Channel {} accepting {} was set to {}.", channelUID, type, value);
|
||||
} else {
|
||||
logger.debug("Channel {} will not accept {} items.", channelUID, type);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,324 @@
|
||||
/**
|
||||
* 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.plclogo.internal.handler;
|
||||
|
||||
import static org.openhab.binding.plclogo.internal.PLCLogoBindingConstants.*;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.plclogo.internal.PLCLogoClient;
|
||||
import org.openhab.binding.plclogo.internal.config.PLCDigitalConfiguration;
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.OpenClosedType;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.binding.builder.ChannelBuilder;
|
||||
import org.openhab.core.thing.binding.builder.ThingBuilder;
|
||||
import org.openhab.core.thing.type.ChannelTypeUID;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.openhab.core.types.State;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import Moka7.S7;
|
||||
import Moka7.S7Client;
|
||||
|
||||
/**
|
||||
* The {@link PLCDigitalHandler} is responsible for handling commands, which are
|
||||
* sent to one of the channels.
|
||||
*
|
||||
* @author Alexander Falkenstern - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class PLCDigitalHandler extends PLCCommonHandler {
|
||||
|
||||
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_DIGITAL);
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(PLCDigitalHandler.class);
|
||||
private AtomicReference<PLCDigitalConfiguration> config = new AtomicReference<>();
|
||||
|
||||
private static final Map<String, @Nullable Integer> LOGO_BLOCKS_0BA7;
|
||||
static {
|
||||
Map<String, @Nullable Integer> buffer = new HashMap<>();
|
||||
buffer.put(I_DIGITAL, 24); // 24 digital inputs
|
||||
buffer.put(Q_DIGITAL, 16); // 16 digital outputs
|
||||
buffer.put(M_DIGITAL, 27); // 27 digital markers
|
||||
LOGO_BLOCKS_0BA7 = Collections.unmodifiableMap(buffer);
|
||||
}
|
||||
|
||||
private static final Map<String, @Nullable Integer> LOGO_BLOCKS_0BA8;
|
||||
static {
|
||||
Map<String, @Nullable Integer> buffer = new HashMap<>();
|
||||
buffer.put(I_DIGITAL, 24); // 24 digital inputs
|
||||
buffer.put(Q_DIGITAL, 20); // 20 digital outputs
|
||||
buffer.put(M_DIGITAL, 64); // 64 digital markers
|
||||
buffer.put(NI_DIGITAL, 64); // 64 network inputs
|
||||
buffer.put(NQ_DIGITAL, 64); // 64 network outputs
|
||||
LOGO_BLOCKS_0BA8 = Collections.unmodifiableMap(buffer);
|
||||
}
|
||||
|
||||
private static final Map<String, @Nullable Map<String, @Nullable Integer>> LOGO_BLOCK_NUMBER;
|
||||
static {
|
||||
Map<String, @Nullable Map<String, @Nullable Integer>> buffer = new HashMap<>();
|
||||
buffer.put(LOGO_0BA7, LOGO_BLOCKS_0BA7);
|
||||
buffer.put(LOGO_0BA8, LOGO_BLOCKS_0BA8);
|
||||
LOGO_BLOCK_NUMBER = Collections.unmodifiableMap(buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public PLCDigitalHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
if (!isThingOnline()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Channel channel = getThing().getChannel(channelUID.getId());
|
||||
String name = getBlockFromChannel(channel);
|
||||
if (!isValid(name) || (channel == null)) {
|
||||
logger.debug("Can not update channel {}, block {}.", channelUID, name);
|
||||
return;
|
||||
}
|
||||
|
||||
int bit = getBit(name);
|
||||
int address = getAddress(name);
|
||||
PLCLogoClient client = getLogoClient();
|
||||
if ((address != INVALID) && (bit != INVALID) && (client != null)) {
|
||||
if (command instanceof RefreshType) {
|
||||
int base = getBase(name);
|
||||
byte[] buffer = new byte[getBufferLength()];
|
||||
int result = client.readDBArea(1, base, buffer.length, S7Client.S7WLByte, buffer);
|
||||
if (result == 0) {
|
||||
updateChannel(channel, S7.GetBitAt(buffer, address - base, bit));
|
||||
} else {
|
||||
logger.debug("Can not read data from LOGO!: {}.", S7Client.ErrorText(result));
|
||||
}
|
||||
} else if ((command instanceof OpenClosedType) || (command instanceof OnOffType)) {
|
||||
byte[] buffer = new byte[1];
|
||||
String type = channel.getAcceptedItemType();
|
||||
if (DIGITAL_INPUT_ITEM.equalsIgnoreCase(type)) {
|
||||
S7.SetBitAt(buffer, 0, 0, ((OpenClosedType) command) == OpenClosedType.CLOSED);
|
||||
} else if (DIGITAL_OUTPUT_ITEM.equalsIgnoreCase(type)) {
|
||||
S7.SetBitAt(buffer, 0, 0, ((OnOffType) command) == OnOffType.ON);
|
||||
} else {
|
||||
logger.debug("Channel {} will not accept {} items.", channelUID, type);
|
||||
}
|
||||
int result = client.writeDBArea(1, 8 * address + bit, buffer.length, S7Client.S7WLBit, buffer);
|
||||
if (result != 0) {
|
||||
logger.debug("Can not write data to LOGO!: {}.", S7Client.ErrorText(result));
|
||||
}
|
||||
} else {
|
||||
logger.debug("Channel {} received not supported command {}.", channelUID, command);
|
||||
}
|
||||
} else {
|
||||
logger.info("Invalid channel {} or client {} found.", channelUID, client);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setData(final byte[] data) {
|
||||
if (!isThingOnline()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.length != getBufferLength()) {
|
||||
logger.info("Received and configured data sizes does not match.");
|
||||
return;
|
||||
}
|
||||
|
||||
List<Channel> channels = thing.getChannels();
|
||||
if (channels.size() != getNumberOfChannels()) {
|
||||
logger.info("Received and configured channel sizes does not match.");
|
||||
return;
|
||||
}
|
||||
|
||||
Boolean force = config.get().isUpdateForced();
|
||||
for (Channel channel : channels) {
|
||||
ChannelUID channelUID = channel.getUID();
|
||||
String name = getBlockFromChannel(channel);
|
||||
|
||||
int bit = getBit(name);
|
||||
int address = getAddress(name);
|
||||
if ((address != INVALID) && (bit != INVALID)) {
|
||||
DecimalType state = (DecimalType) getOldValue(name);
|
||||
boolean value = S7.GetBitAt(data, address - getBase(name), bit);
|
||||
if ((state == null) || ((value ? 1 : 0) != state.intValue()) || force) {
|
||||
updateChannel(channel, value);
|
||||
}
|
||||
if (logger.isTraceEnabled()) {
|
||||
int buffer = (data[address - getBase(name)] & 0xFF) + 0x100;
|
||||
logger.trace("Channel {} received [{}].", channelUID, Integer.toBinaryString(buffer).substring(1));
|
||||
}
|
||||
} else {
|
||||
logger.info("Invalid channel {} found.", channelUID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateState(ChannelUID channelUID, State state) {
|
||||
super.updateState(channelUID, state);
|
||||
DecimalType value = state.as(DecimalType.class);
|
||||
if (state instanceof OpenClosedType) {
|
||||
OpenClosedType type = (OpenClosedType) state;
|
||||
value = new DecimalType(type == OpenClosedType.CLOSED ? 1 : 0);
|
||||
}
|
||||
|
||||
Channel channel = thing.getChannel(channelUID.getId());
|
||||
setOldValue(getBlockFromChannel(channel), value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateConfiguration(Configuration configuration) {
|
||||
super.updateConfiguration(configuration);
|
||||
config.set(getConfigAs(PLCDigitalConfiguration.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isValid(final String name) {
|
||||
if (2 <= name.length() && (name.length() <= 4)) {
|
||||
String kind = getBlockKind();
|
||||
if (Character.isDigit(name.charAt(1)) || Character.isDigit(name.charAt(2))) {
|
||||
boolean valid = I_DIGITAL.equalsIgnoreCase(kind) || NI_DIGITAL.equalsIgnoreCase(kind);
|
||||
valid = valid || Q_DIGITAL.equalsIgnoreCase(kind) || NQ_DIGITAL.equalsIgnoreCase(kind);
|
||||
return name.startsWith(kind) && (valid || M_DIGITAL.equalsIgnoreCase(kind));
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getBlockKind() {
|
||||
return config.get().getBlockKind();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getNumberOfChannels() {
|
||||
String kind = getBlockKind();
|
||||
String family = getLogoFamily();
|
||||
logger.debug("Get block number of {} LOGO! for {} blocks.", family, kind);
|
||||
|
||||
Map<?, @Nullable Integer> blocks = LOGO_BLOCK_NUMBER.get(family);
|
||||
Integer number = (blocks != null) ? blocks.get(kind) : null;
|
||||
return (number != null) ? number.intValue() : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getAddress(final String name) {
|
||||
int address = super.getAddress(name);
|
||||
if (address != INVALID) {
|
||||
address = getBase(name) + (address - 1) / 8;
|
||||
} else {
|
||||
logger.info("Wrong configurated LOGO! block {} found.", name);
|
||||
}
|
||||
return address;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doInitialization() {
|
||||
Thing thing = getThing();
|
||||
logger.debug("Initialize LOGO! digital input blocks handler.");
|
||||
|
||||
config.set(getConfigAs(PLCDigitalConfiguration.class));
|
||||
|
||||
super.doInitialization();
|
||||
if (ThingStatus.OFFLINE != thing.getStatus()) {
|
||||
String kind = getBlockKind();
|
||||
String type = config.get().getChannelType();
|
||||
String text = DIGITAL_INPUT_ITEM.equalsIgnoreCase(type) ? "input" : "output";
|
||||
|
||||
ThingBuilder tBuilder = editThing();
|
||||
|
||||
String label = thing.getLabel();
|
||||
if (label == null) {
|
||||
Bridge bridge = getBridge();
|
||||
label = (bridge == null) || (bridge.getLabel() == null) ? "Siemens Logo!" : bridge.getLabel();
|
||||
label += (": digital " + text + "s");
|
||||
}
|
||||
tBuilder.withLabel(label);
|
||||
|
||||
for (int i = 0; i < getNumberOfChannels(); i++) {
|
||||
String name = kind + String.valueOf(i + 1);
|
||||
ChannelUID uid = new ChannelUID(thing.getUID(), name);
|
||||
ChannelBuilder cBuilder = ChannelBuilder.create(uid, type);
|
||||
cBuilder.withType(new ChannelTypeUID(BINDING_ID, type.toLowerCase()));
|
||||
cBuilder.withLabel(name);
|
||||
cBuilder.withDescription("Digital " + text + " block " + name);
|
||||
cBuilder.withProperties(Collections.singletonMap(BLOCK_PROPERTY, name));
|
||||
tBuilder.withChannel(cBuilder.build());
|
||||
setOldValue(name, null);
|
||||
}
|
||||
|
||||
updateThing(tBuilder.build());
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate bit within address for block with given name.
|
||||
*
|
||||
* @param name Name of the LOGO! block
|
||||
* @return Calculated bit
|
||||
*/
|
||||
private int getBit(final String name) {
|
||||
int bit = INVALID;
|
||||
|
||||
logger.debug("Get bit of {} LOGO! for block {} .", getLogoFamily(), name);
|
||||
|
||||
if (isValid(name) && (getAddress(name) != INVALID)) {
|
||||
if (Character.isDigit(name.charAt(1))) {
|
||||
bit = Integer.parseInt(name.substring(1));
|
||||
} else if (Character.isDigit(name.charAt(2))) {
|
||||
bit = Integer.parseInt(name.substring(2));
|
||||
}
|
||||
bit = (bit - 1) % 8;
|
||||
} else {
|
||||
logger.info("Wrong configurated LOGO! block {} found.", name);
|
||||
}
|
||||
|
||||
return bit;
|
||||
}
|
||||
|
||||
private void updateChannel(final Channel channel, boolean value) {
|
||||
ChannelUID channelUID = channel.getUID();
|
||||
String type = channel.getAcceptedItemType();
|
||||
if (DIGITAL_INPUT_ITEM.equalsIgnoreCase(type)) {
|
||||
updateState(channelUID, value ? OpenClosedType.CLOSED : OpenClosedType.OPEN);
|
||||
logger.debug("Channel {} accepting {} was set to {}.", channelUID, type, value);
|
||||
} else if (DIGITAL_OUTPUT_ITEM.equalsIgnoreCase(type)) {
|
||||
updateState(channelUID, value ? OnOffType.ON : OnOffType.OFF);
|
||||
logger.debug("Channel {} accepting {} was set to {}.", channelUID, type, value);
|
||||
} else {
|
||||
logger.debug("Channel {} will not accept {} items.", channelUID, type);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,325 @@
|
||||
/**
|
||||
* 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.plclogo.internal.handler;
|
||||
|
||||
import static org.openhab.binding.plclogo.internal.PLCLogoBindingConstants.*;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.plclogo.internal.PLCLogoClient;
|
||||
import org.openhab.binding.plclogo.internal.config.PLCMemoryConfiguration;
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.binding.builder.ChannelBuilder;
|
||||
import org.openhab.core.thing.binding.builder.ThingBuilder;
|
||||
import org.openhab.core.thing.type.ChannelTypeUID;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.openhab.core.types.State;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import Moka7.S7;
|
||||
import Moka7.S7Client;
|
||||
|
||||
/**
|
||||
* The {@link PLCMemoryHandler} is responsible for handling commands, which are
|
||||
* sent to one of the channels.
|
||||
*
|
||||
* @author Alexander Falkenstern - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class PLCMemoryHandler extends PLCCommonHandler {
|
||||
|
||||
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_MEMORY);
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(PLCMemoryHandler.class);
|
||||
private AtomicReference<PLCMemoryConfiguration> config = new AtomicReference<>();
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public PLCMemoryHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
if (!isThingOnline()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Channel channel = getThing().getChannel(channelUID.getId());
|
||||
String name = getBlockFromChannel(channel);
|
||||
if (!isValid(name) || (channel == null)) {
|
||||
logger.debug("Can not update channel {}, block {}.", channelUID, name);
|
||||
return;
|
||||
}
|
||||
|
||||
int address = getAddress(name);
|
||||
PLCLogoClient client = getLogoClient();
|
||||
if ((address != INVALID) && (client != null)) {
|
||||
String kind = getBlockKind();
|
||||
String type = channel.getAcceptedItemType();
|
||||
if (command instanceof RefreshType) {
|
||||
byte[] buffer = new byte[getBufferLength()];
|
||||
int result = client.readDBArea(1, 0, buffer.length, S7Client.S7WLByte, buffer);
|
||||
if (result == 0) {
|
||||
if (DIGITAL_OUTPUT_ITEM.equalsIgnoreCase(type) && MEMORY_BYTE.equalsIgnoreCase(kind)) {
|
||||
boolean value = S7.GetBitAt(buffer, address, getBit(name));
|
||||
updateState(channelUID, value ? OnOffType.ON : OnOffType.OFF);
|
||||
logger.debug("Channel {} accepting {} was set to {}.", channelUID, type, value);
|
||||
} else if (ANALOG_ITEM.equalsIgnoreCase(type) && MEMORY_BYTE.equalsIgnoreCase(kind)) {
|
||||
int value = buffer[address];
|
||||
updateState(channelUID, new DecimalType(value));
|
||||
logger.debug("Channel {} accepting {} was set to {}.", channelUID, type, value);
|
||||
} else if (ANALOG_ITEM.equalsIgnoreCase(type) && MEMORY_WORD.equalsIgnoreCase(kind)) {
|
||||
int value = S7.GetShortAt(buffer, address);
|
||||
updateState(channelUID, new DecimalType(value));
|
||||
logger.debug("Channel {} accepting {} was set to {}.", channelUID, type, value);
|
||||
} else if (ANALOG_ITEM.equalsIgnoreCase(type) && MEMORY_DWORD.equalsIgnoreCase(kind)) {
|
||||
int value = S7.GetDIntAt(buffer, address);
|
||||
updateState(channelUID, new DecimalType(value));
|
||||
logger.debug("Channel {} accepting {} was set to {}.", channelUID, type, value);
|
||||
} else {
|
||||
logger.debug("Channel {} will not accept {} items.", channelUID, type);
|
||||
}
|
||||
} else {
|
||||
logger.debug("Can not read data from LOGO!: {}.", S7Client.ErrorText(result));
|
||||
}
|
||||
} else if (command instanceof DecimalType) {
|
||||
int length = MEMORY_BYTE.equalsIgnoreCase(kind) ? 1 : 2;
|
||||
byte[] buffer = new byte[MEMORY_DWORD.equalsIgnoreCase(kind) ? 4 : length];
|
||||
if (ANALOG_ITEM.equalsIgnoreCase(type) && MEMORY_BYTE.equalsIgnoreCase(kind)) {
|
||||
buffer[0] = ((DecimalType) command).byteValue();
|
||||
} else if (ANALOG_ITEM.equalsIgnoreCase(type) && MEMORY_WORD.equalsIgnoreCase(kind)) {
|
||||
S7.SetShortAt(buffer, 0, ((DecimalType) command).intValue());
|
||||
} else if (ANALOG_ITEM.equalsIgnoreCase(type) && MEMORY_DWORD.equalsIgnoreCase(kind)) {
|
||||
S7.SetDIntAt(buffer, 0, ((DecimalType) command).intValue());
|
||||
} else {
|
||||
logger.debug("Channel {} will not accept {} items.", channelUID, type);
|
||||
}
|
||||
int result = client.writeDBArea(1, address, buffer.length, S7Client.S7WLByte, buffer);
|
||||
if (result != 0) {
|
||||
logger.debug("Can not write data to LOGO!: {}.", S7Client.ErrorText(result));
|
||||
}
|
||||
} else if (command instanceof OnOffType) {
|
||||
byte[] buffer = new byte[1];
|
||||
if (DIGITAL_OUTPUT_ITEM.equalsIgnoreCase(type) && MEMORY_BYTE.equalsIgnoreCase(kind)) {
|
||||
S7.SetBitAt(buffer, 0, 0, ((OnOffType) command) == OnOffType.ON);
|
||||
} else {
|
||||
logger.debug("Channel {} will not accept {} items.", channelUID, type);
|
||||
}
|
||||
int result = client.writeDBArea(1, 8 * address + getBit(name), buffer.length, S7Client.S7WLBit, buffer);
|
||||
if (result != 0) {
|
||||
logger.debug("Can not write data to LOGO!: {}.", S7Client.ErrorText(result));
|
||||
}
|
||||
} else {
|
||||
logger.debug("Channel {} received not supported command {}.", channelUID, command);
|
||||
}
|
||||
} else {
|
||||
logger.info("Invalid channel {} or client {} found.", channelUID, client);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setData(final byte[] data) {
|
||||
if (!isThingOnline()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.length != getBufferLength()) {
|
||||
logger.info("Received and configured data sizes does not match.");
|
||||
return;
|
||||
}
|
||||
|
||||
List<Channel> channels = thing.getChannels();
|
||||
if (channels.size() != getNumberOfChannels()) {
|
||||
logger.info("Received and configured channel sizes does not match.");
|
||||
return;
|
||||
}
|
||||
|
||||
for (Channel channel : channels) {
|
||||
ChannelUID channelUID = channel.getUID();
|
||||
String name = getBlockFromChannel(channel);
|
||||
|
||||
int address = getAddress(name);
|
||||
if (address != INVALID) {
|
||||
String kind = getBlockKind();
|
||||
String type = channel.getAcceptedItemType();
|
||||
Boolean force = config.get().isUpdateForced();
|
||||
|
||||
if (DIGITAL_OUTPUT_ITEM.equalsIgnoreCase(type) && kind.equalsIgnoreCase(MEMORY_BYTE)) {
|
||||
OnOffType state = (OnOffType) getOldValue(name);
|
||||
OnOffType value = S7.GetBitAt(data, address, getBit(name)) ? OnOffType.ON : OnOffType.OFF;
|
||||
if ((state == null) || (value != state) || force) {
|
||||
updateState(channelUID, value);
|
||||
logger.debug("Channel {} accepting {} was set to {}.", channelUID, type, value);
|
||||
}
|
||||
if (logger.isTraceEnabled()) {
|
||||
int buffer = (data[address] & 0xFF) + 0x100;
|
||||
logger.trace("Channel {} received [{}].", channelUID,
|
||||
Integer.toBinaryString(buffer).substring(1));
|
||||
}
|
||||
} else if (ANALOG_ITEM.equalsIgnoreCase(type) && MEMORY_BYTE.equalsIgnoreCase(kind)) {
|
||||
Integer threshold = config.get().getThreshold();
|
||||
DecimalType state = (DecimalType) getOldValue(name);
|
||||
int value = data[address];
|
||||
if ((state == null) || (Math.abs(value - state.intValue()) > threshold) || force) {
|
||||
updateState(channelUID, new DecimalType(value));
|
||||
logger.debug("Channel {} accepting {} was set to {}.", channelUID, type, value);
|
||||
}
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Channel {} received [{}].", channelUID, data[address]);
|
||||
}
|
||||
} else if (ANALOG_ITEM.equalsIgnoreCase(type) && MEMORY_WORD.equalsIgnoreCase(kind)) {
|
||||
Integer threshold = config.get().getThreshold();
|
||||
DecimalType state = (DecimalType) getOldValue(name);
|
||||
int value = S7.GetShortAt(data, address);
|
||||
if ((state == null) || (Math.abs(value - state.intValue()) > threshold) || force) {
|
||||
updateState(channelUID, new DecimalType(value));
|
||||
logger.debug("Channel {} accepting {} was set to {}.", channelUID, type, value);
|
||||
}
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Channel {} received [{}, {}].", channelUID, data[address], data[address + 1]);
|
||||
}
|
||||
} else if (ANALOG_ITEM.equalsIgnoreCase(type) && MEMORY_DWORD.equalsIgnoreCase(kind)) {
|
||||
Integer threshold = config.get().getThreshold();
|
||||
DecimalType state = (DecimalType) getOldValue(name);
|
||||
int value = S7.GetDIntAt(data, address);
|
||||
if ((state == null) || (Math.abs(value - state.intValue()) > threshold) || force) {
|
||||
updateState(channelUID, new DecimalType(value));
|
||||
logger.debug("Channel {} accepting {} was set to {}.", channelUID, type, value);
|
||||
}
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Channel {} received [{}, {}, {}, {}].", channelUID, data[address],
|
||||
data[address + 1], data[address + 2], data[address + 3]);
|
||||
}
|
||||
} else {
|
||||
logger.debug("Channel {} will not accept {} items.", channelUID, type);
|
||||
}
|
||||
} else {
|
||||
logger.info("Invalid channel {} found.", channelUID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateState(ChannelUID channelUID, State state) {
|
||||
super.updateState(channelUID, state);
|
||||
|
||||
Channel channel = thing.getChannel(channelUID.getId());
|
||||
setOldValue(getBlockFromChannel(channel), state);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateConfiguration(Configuration configuration) {
|
||||
super.updateConfiguration(configuration);
|
||||
config.set(getConfigAs(PLCMemoryConfiguration.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isValid(final String name) {
|
||||
if (3 <= name.length() && (name.length() <= 7)) {
|
||||
String kind = getBlockKind();
|
||||
if (Character.isDigit(name.charAt(2))) {
|
||||
boolean valid = MEMORY_BYTE.equalsIgnoreCase(kind) || MEMORY_WORD.equalsIgnoreCase(kind);
|
||||
return name.startsWith(kind) && (valid || MEMORY_DWORD.equalsIgnoreCase(kind));
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getBlockKind() {
|
||||
return config.get().getBlockKind();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getNumberOfChannels() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doInitialization() {
|
||||
Thing thing = getThing();
|
||||
logger.debug("Initialize LOGO! memory handler.");
|
||||
|
||||
config.set(getConfigAs(PLCMemoryConfiguration.class));
|
||||
|
||||
super.doInitialization();
|
||||
if (ThingStatus.OFFLINE != thing.getStatus()) {
|
||||
String kind = getBlockKind();
|
||||
String name = config.get().getBlockName();
|
||||
boolean isDigital = MEMORY_BYTE.equalsIgnoreCase(kind) && (getBit(name) != INVALID);
|
||||
String text = isDigital ? "Digital" : "Analog";
|
||||
|
||||
ThingBuilder tBuilder = editThing();
|
||||
|
||||
String label = thing.getLabel();
|
||||
if (label == null) {
|
||||
Bridge bridge = getBridge();
|
||||
label = (bridge == null) || (bridge.getLabel() == null) ? "Siemens Logo!" : bridge.getLabel();
|
||||
label += (": " + text.toLowerCase() + " in/output");
|
||||
}
|
||||
tBuilder.withLabel(label);
|
||||
|
||||
String type = config.get().getChannelType();
|
||||
ChannelUID uid = new ChannelUID(thing.getUID(), isDigital ? STATE_CHANNEL : VALUE_CHANNEL);
|
||||
ChannelBuilder cBuilder = ChannelBuilder.create(uid, type);
|
||||
cBuilder.withType(new ChannelTypeUID(BINDING_ID, type.toLowerCase()));
|
||||
cBuilder.withLabel(name);
|
||||
cBuilder.withDescription(text + " in/output block " + name);
|
||||
cBuilder.withProperties(Collections.singletonMap(BLOCK_PROPERTY, name));
|
||||
tBuilder.withChannel(cBuilder.build());
|
||||
setOldValue(name, null);
|
||||
|
||||
updateThing(tBuilder.build());
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate bit within address for block with given name.
|
||||
*
|
||||
* @param name Name of the LOGO! block
|
||||
* @return Calculated bit
|
||||
*/
|
||||
private int getBit(final String name) {
|
||||
int bit = INVALID;
|
||||
|
||||
logger.debug("Get bit of {} LOGO! for block {} .", getLogoFamily(), name);
|
||||
|
||||
if (isValid(name) && (getAddress(name) != INVALID)) {
|
||||
String[] parts = name.trim().split("\\.");
|
||||
if (parts.length > 1) {
|
||||
bit = Integer.parseInt(parts[1]);
|
||||
}
|
||||
} else {
|
||||
logger.info("Wrong configurated LOGO! block {} found.", name);
|
||||
}
|
||||
|
||||
return bit;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,339 @@
|
||||
/**
|
||||
* 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.plclogo.internal.handler;
|
||||
|
||||
import static org.openhab.binding.plclogo.internal.PLCLogoBindingConstants.*;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.plclogo.internal.PLCLogoClient;
|
||||
import org.openhab.binding.plclogo.internal.config.PLCPulseConfiguration;
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.OpenClosedType;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.binding.builder.ChannelBuilder;
|
||||
import org.openhab.core.thing.binding.builder.ThingBuilder;
|
||||
import org.openhab.core.thing.type.ChannelTypeUID;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.openhab.core.types.State;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import Moka7.S7;
|
||||
import Moka7.S7Client;
|
||||
|
||||
/**
|
||||
* The {@link PLCPulseHandler} is responsible for handling commands, which are
|
||||
* sent to one of the channels.
|
||||
*
|
||||
* @author Alexander Falkenstern - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class PLCPulseHandler extends PLCCommonHandler {
|
||||
|
||||
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_PULSE);
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(PLCPulseHandler.class);
|
||||
private AtomicReference<PLCPulseConfiguration> config = new AtomicReference<>();
|
||||
private AtomicReference<@Nullable Boolean> received = new AtomicReference<>();
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public PLCPulseHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
if (!isThingOnline()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Channel channel = getThing().getChannel(channelUID.getId());
|
||||
String name = getBlockFromChannel(channel);
|
||||
if (!isValid(name) || (channel == null)) {
|
||||
logger.debug("Can not update channel {}, block {}.", channelUID, name);
|
||||
return;
|
||||
}
|
||||
|
||||
int bit = getBit(name);
|
||||
int address = getAddress(name);
|
||||
PLCLogoClient client = getLogoClient();
|
||||
if ((address != INVALID) && (bit != INVALID) && (client != null)) {
|
||||
byte[] buffer = new byte[1];
|
||||
if (command instanceof RefreshType) {
|
||||
int result = client.readDBArea(1, address, buffer.length, S7Client.S7WLByte, buffer);
|
||||
if (result == 0) {
|
||||
updateChannel(channel, S7.GetBitAt(buffer, 0, bit));
|
||||
} else {
|
||||
logger.debug("Can not read data from LOGO!: {}.", S7Client.ErrorText(result));
|
||||
}
|
||||
} else if ((command instanceof OpenClosedType) || (command instanceof OnOffType)) {
|
||||
String type = channel.getAcceptedItemType();
|
||||
if (DIGITAL_INPUT_ITEM.equalsIgnoreCase(type)) {
|
||||
boolean flag = ((OpenClosedType) command == OpenClosedType.CLOSED);
|
||||
S7.SetBitAt(buffer, 0, 0, flag);
|
||||
received.set(flag);
|
||||
} else if (DIGITAL_OUTPUT_ITEM.equalsIgnoreCase(type)) {
|
||||
boolean flag = ((OnOffType) command == OnOffType.ON);
|
||||
S7.SetBitAt(buffer, 0, 0, flag);
|
||||
received.set(flag);
|
||||
} else {
|
||||
logger.debug("Channel {} will not accept {} items.", channelUID, type);
|
||||
}
|
||||
int result = client.writeDBArea(1, 8 * address + bit, buffer.length, S7Client.S7WLBit, buffer);
|
||||
if (result != 0) {
|
||||
logger.debug("Can not write data to LOGO!: {}.", S7Client.ErrorText(result));
|
||||
}
|
||||
} else {
|
||||
logger.debug("Channel {} received not supported command {}.", channelUID, command);
|
||||
}
|
||||
} else {
|
||||
logger.info("Invalid channel {} or client {} found.", channelUID, client);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setData(final byte[] data) {
|
||||
if (!isThingOnline()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.length != getBufferLength()) {
|
||||
logger.info("Received and configured data sizes does not match.");
|
||||
return;
|
||||
}
|
||||
|
||||
List<Channel> channels = thing.getChannels();
|
||||
if (channels.size() != getNumberOfChannels()) {
|
||||
logger.info("Received and configured channel sizes does not match.");
|
||||
return;
|
||||
}
|
||||
|
||||
PLCLogoClient client = getLogoClient();
|
||||
for (Channel channel : channels) {
|
||||
ChannelUID channelUID = channel.getUID();
|
||||
String name = getBlockFromChannel(channel);
|
||||
|
||||
int bit = getBit(name);
|
||||
int address = getAddress(name);
|
||||
if ((address != INVALID) && (bit != INVALID) && (client != null)) {
|
||||
DecimalType state = (DecimalType) getOldValue(channelUID.getId());
|
||||
if (STATE_CHANNEL.equalsIgnoreCase(channelUID.getId())) {
|
||||
boolean value = S7.GetBitAt(data, address - getBase(name), bit);
|
||||
if ((state == null) || ((value ? 1 : 0) != state.intValue())) {
|
||||
updateChannel(channel, value);
|
||||
}
|
||||
if (logger.isTraceEnabled()) {
|
||||
int buffer = (data[address - getBase(name)] & 0xFF) + 0x100;
|
||||
logger.trace("Channel {} received [{}].", channelUID,
|
||||
Integer.toBinaryString(buffer).substring(1));
|
||||
}
|
||||
} else if (OBSERVE_CHANNEL.equalsIgnoreCase(channelUID.getId())) {
|
||||
handleCommand(channelUID, RefreshType.REFRESH);
|
||||
DecimalType current = (DecimalType) getOldValue(channelUID.getId());
|
||||
if ((state != null) && (current.intValue() != state.intValue())) {
|
||||
Integer pulse = config.get().getPulseLength();
|
||||
scheduler.schedule(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Boolean value = received.getAndSet(null);
|
||||
if (value != null) {
|
||||
byte[] buffer = new byte[1];
|
||||
S7.SetBitAt(buffer, 0, 0, !value.booleanValue());
|
||||
String block = config.get().getBlockName();
|
||||
int bit = 8 * getAddress(block) + getBit(block);
|
||||
int result = client.writeDBArea(1, bit, buffer.length, S7Client.S7WLBit, buffer);
|
||||
if (result != 0) {
|
||||
logger.debug("Can not write data to LOGO!: {}.", S7Client.ErrorText(result));
|
||||
}
|
||||
} else {
|
||||
logger.debug("Invalid received value on channel {}.", channelUID);
|
||||
}
|
||||
}
|
||||
}, pulse.longValue(), TimeUnit.MILLISECONDS);
|
||||
}
|
||||
} else {
|
||||
logger.info("Invalid channel {} found.", channelUID);
|
||||
}
|
||||
} else {
|
||||
logger.info("Invalid channel {} or client {} found.", channelUID, client);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateState(ChannelUID channelUID, State state) {
|
||||
super.updateState(channelUID, state);
|
||||
DecimalType value = state.as(DecimalType.class);
|
||||
if (state instanceof OpenClosedType) {
|
||||
OpenClosedType type = (OpenClosedType) state;
|
||||
value = new DecimalType(type == OpenClosedType.CLOSED ? 1 : 0);
|
||||
}
|
||||
|
||||
setOldValue(channelUID.getId(), value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateConfiguration(Configuration configuration) {
|
||||
super.updateConfiguration(configuration);
|
||||
config.set(getConfigAs(PLCPulseConfiguration.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isValid(final String name) {
|
||||
if (2 <= name.length() && (name.length() <= 7)) {
|
||||
String kind = config.get().getObservedBlockKind();
|
||||
if (Character.isDigit(name.charAt(1))) {
|
||||
boolean valid = I_DIGITAL.equalsIgnoreCase(kind) || Q_DIGITAL.equalsIgnoreCase(kind);
|
||||
return name.startsWith(kind) && (valid || M_DIGITAL.equalsIgnoreCase(kind));
|
||||
} else if (Character.isDigit(name.charAt(2))) {
|
||||
String bKind = getBlockKind();
|
||||
boolean valid = NI_DIGITAL.equalsIgnoreCase(kind) || NQ_DIGITAL.equalsIgnoreCase(kind);
|
||||
valid = name.startsWith(kind) && (valid || MEMORY_BYTE.equalsIgnoreCase(kind));
|
||||
return (name.startsWith(bKind) && MEMORY_BYTE.equalsIgnoreCase(bKind)) || valid;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getBlockKind() {
|
||||
return config.get().getBlockKind();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getNumberOfChannels() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getAddress(final String name) {
|
||||
int address = super.getAddress(name);
|
||||
if (address != INVALID) {
|
||||
int base = getBase(name);
|
||||
if (base != 0) {
|
||||
address = base + (address - 1) / 8;
|
||||
}
|
||||
} else {
|
||||
logger.info("Wrong configurated LOGO! block {} found.", name);
|
||||
}
|
||||
return address;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doInitialization() {
|
||||
Thing thing = getThing();
|
||||
logger.debug("Initialize LOGO! pulse handler.");
|
||||
|
||||
config.set(getConfigAs(PLCPulseConfiguration.class));
|
||||
|
||||
super.doInitialization();
|
||||
if (ThingStatus.OFFLINE != thing.getStatus()) {
|
||||
ThingBuilder tBuilder = editThing();
|
||||
|
||||
String label = thing.getLabel();
|
||||
if (label == null) {
|
||||
Bridge bridge = getBridge();
|
||||
label = (bridge == null) || (bridge.getLabel() == null) ? "Siemens Logo!" : bridge.getLabel();
|
||||
label += (": digital pulse in/output");
|
||||
}
|
||||
tBuilder.withLabel(label);
|
||||
|
||||
String bName = config.get().getBlockName();
|
||||
String bType = config.get().getChannelType();
|
||||
ChannelUID uid = new ChannelUID(thing.getUID(), STATE_CHANNEL);
|
||||
ChannelBuilder cBuilder = ChannelBuilder.create(uid, bType);
|
||||
cBuilder.withType(new ChannelTypeUID(BINDING_ID, bType.toLowerCase()));
|
||||
cBuilder.withLabel(bName);
|
||||
cBuilder.withDescription("Control block " + bName);
|
||||
cBuilder.withProperties(Collections.singletonMap(BLOCK_PROPERTY, bName));
|
||||
tBuilder.withChannel(cBuilder.build());
|
||||
setOldValue(STATE_CHANNEL, null);
|
||||
|
||||
String oName = config.get().getObservedBlock();
|
||||
String oType = config.get().getObservedChannelType();
|
||||
cBuilder = ChannelBuilder.create(new ChannelUID(thing.getUID(), OBSERVE_CHANNEL), oType);
|
||||
cBuilder.withType(new ChannelTypeUID(BINDING_ID, oType.toLowerCase()));
|
||||
cBuilder.withLabel(oName);
|
||||
cBuilder.withDescription("Observed block " + oName);
|
||||
cBuilder.withProperties(Collections.singletonMap(BLOCK_PROPERTY, oName));
|
||||
tBuilder.withChannel(cBuilder.build());
|
||||
setOldValue(OBSERVE_CHANNEL, null);
|
||||
|
||||
updateThing(tBuilder.build());
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate bit within address for block with given name.
|
||||
*
|
||||
* @param name Name of the LOGO! block
|
||||
* @return Calculated bit
|
||||
*/
|
||||
private int getBit(final String name) {
|
||||
int bit = INVALID;
|
||||
|
||||
logger.debug("Get bit of {} LOGO! for block {} .", getLogoFamily(), name);
|
||||
|
||||
if (isValid(name) && (getAddress(name) != INVALID)) {
|
||||
String[] parts = name.trim().split("\\.");
|
||||
if (parts.length > 1) {
|
||||
bit = Integer.parseInt(parts[1]);
|
||||
} else if (parts.length == 1) {
|
||||
if (Character.isDigit(parts[0].charAt(1))) {
|
||||
bit = Integer.parseInt(parts[0].substring(1));
|
||||
} else if (Character.isDigit(parts[0].charAt(2))) {
|
||||
bit = Integer.parseInt(parts[0].substring(2));
|
||||
} else if (Character.isDigit(parts[0].charAt(3))) {
|
||||
bit = Integer.parseInt(parts[0].substring(3));
|
||||
}
|
||||
bit = (bit - 1) % 8;
|
||||
}
|
||||
} else {
|
||||
logger.info("Wrong configurated LOGO! block {} found.", name);
|
||||
}
|
||||
|
||||
return bit;
|
||||
}
|
||||
|
||||
private void updateChannel(final Channel channel, boolean value) {
|
||||
ChannelUID channelUID = channel.getUID();
|
||||
String type = channel.getAcceptedItemType();
|
||||
if (DIGITAL_INPUT_ITEM.equalsIgnoreCase(type)) {
|
||||
updateState(channelUID, value ? OpenClosedType.CLOSED : OpenClosedType.OPEN);
|
||||
logger.debug("Channel {} accepting {} was set to {}.", channelUID, type, value);
|
||||
} else if (DIGITAL_OUTPUT_ITEM.equalsIgnoreCase(type)) {
|
||||
updateState(channelUID, value ? OnOffType.ON : OnOffType.OFF);
|
||||
logger.debug("Channel {} accepting {} was set to {}.", channelUID, type, value);
|
||||
} else {
|
||||
logger.debug("Channel {} will not accept {} items.", channelUID, type);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<binding:binding id="plclogo" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
|
||||
|
||||
<name>PLCLogo Binding</name>
|
||||
<description>This binding provides native support for Siemens LOGO! PLC.</description>
|
||||
<author>Alexander Falkenstern</author>
|
||||
|
||||
</binding:binding>
|
||||
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<config-description:config-descriptions
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0 https://openhab.org/schemas/config-description-1.0.0.xsd">
|
||||
|
||||
<config-description uri="thing-type:plclogo:analog">
|
||||
<parameter name="kind" type="text" pattern="AI|AM|AQ|NAI|NAQ">
|
||||
<label>LOGO! Analog Block Kind</label>
|
||||
<description>LOGO! analog block kind</description>
|
||||
<required>true</required>
|
||||
</parameter>
|
||||
<parameter name="force" type="boolean">
|
||||
<label>Force Channels Update</label>
|
||||
<description>Propagate channels update to openHAB whether value changed or not</description>
|
||||
<default>false</default>
|
||||
<required>false</required>
|
||||
</parameter>
|
||||
<parameter name="threshold" type="integer" min="0">
|
||||
<label>Smallest Value Change to Sent</label>
|
||||
<description>Smallest value change will be sent to openHAB</description>
|
||||
<default>0</default>
|
||||
<required>false</required>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</config-description:config-descriptions>
|
||||
@@ -0,0 +1,42 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<config-description:config-descriptions
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0 https://openhab.org/schemas/config-description-1.0.0.xsd">
|
||||
|
||||
<config-description uri="thing-type:plclogo:bridge">
|
||||
<parameter name="address" type="text">
|
||||
<context>network-address</context>
|
||||
<label>Network Address</label>
|
||||
<description>Network address of the PLC.</description>
|
||||
<required>true</required>
|
||||
</parameter>
|
||||
<parameter name="family" type="text">
|
||||
<label>LOGO! Family</label>
|
||||
<description>LOGO! PLC hardware family version</description>
|
||||
<options>
|
||||
<option value="0BA7">0BA7</option>
|
||||
<option value="0BA8">0BA8</option>
|
||||
</options>
|
||||
<required>true</required>
|
||||
</parameter>
|
||||
<parameter name="localTSAP" type="text" pattern="(0x[0-9]{4})">
|
||||
<label>Local TSAP</label>
|
||||
<description>Local TSAP of the client as hex string</description>
|
||||
<required>true</required>
|
||||
<default>0x3000</default>
|
||||
</parameter>
|
||||
<parameter name="remoteTSAP" type="text" pattern="(0x[0-9]{4})">
|
||||
<label>Remote TSAP</label>
|
||||
<description>Remote TSAP of the client as hex string</description>
|
||||
<required>true</required>
|
||||
<default>0x2000</default>
|
||||
</parameter>
|
||||
<parameter name="refresh" type="integer" min="100" step="50">
|
||||
<label>Refresh Interval</label>
|
||||
<description>Milliseconds between reread data from PLC.</description>
|
||||
<required>true</required>
|
||||
<default>100</default>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</config-description:config-descriptions>
|
||||
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<config-description:config-descriptions
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0 https://openhab.org/schemas/config-description-1.0.0.xsd">
|
||||
|
||||
<config-description uri="thing-type:plclogo:datetime">
|
||||
<parameter name="block" type="text" pattern="VW(\d|[1-9]\d|[1-7]\d{2}|8[0-4]\d)">
|
||||
<label>LOGO! Memory Address</label>
|
||||
<description>LOGO! memory address</description>
|
||||
<required>true</required>
|
||||
</parameter>
|
||||
<parameter name="type" type="text">
|
||||
<label>Send Value As</label>
|
||||
<description>Interpret received channel value as date or time</description>
|
||||
<options>
|
||||
<option value="date">date</option>
|
||||
<option value="time">time</option>
|
||||
</options>
|
||||
<default>time</default>
|
||||
<required>true</required>
|
||||
</parameter>
|
||||
<parameter name="force" type="boolean">
|
||||
<label>Force Channels Update</label>
|
||||
<description>Propagate channels update to openHAB whether value changed or not</description>
|
||||
<default>false</default>
|
||||
<required>false</required>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</config-description:config-descriptions>
|
||||
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<config-description:config-descriptions
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0 https://openhab.org/schemas/config-description-1.0.0.xsd">
|
||||
|
||||
<config-description uri="thing-type:plclogo:digital">
|
||||
<parameter name="kind" type="text" pattern="I|M|Q|NI|NQ">
|
||||
<label>LOGO! Digital Block Kind</label>
|
||||
<description>LOGO! digital block kind</description>
|
||||
<required>true</required>
|
||||
</parameter>
|
||||
<parameter name="force" type="boolean">
|
||||
<label>Force Channels Update</label>
|
||||
<description>Propagate channels update to openHAB whether value changed or not</description>
|
||||
<default>false</default>
|
||||
<required>false</required>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</config-description:config-descriptions>
|
||||
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<config-description:config-descriptions
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0 https://openhab.org/schemas/config-description-1.0.0.xsd">
|
||||
|
||||
<config-description uri="thing-type:plclogo:memory">
|
||||
<parameter name="block" type="text"
|
||||
pattern="VB(\d|[1-9]\d|[1-7]\d{2}|8[0-4]\d|850)\.[0-7]|VB(\d|[1-9]\d|[1-7]\d{2}|8[0-4]\d|850)|VW(\d|[1-9]\d|[1-7]\d{2}|8[0-4]\d)|VD(\d|[1-9]\d|[1-7]\d{2}|8[0-3]\d|84[0-7])">
|
||||
<label>LOGO! Memory Address</label>
|
||||
<description>LOGO! memory address</description>
|
||||
<required>true</required>
|
||||
</parameter>
|
||||
<parameter name="force" type="boolean">
|
||||
<label>Force Channel Update</label>
|
||||
<description>Update of the channel be should propagated to openHAB</description>
|
||||
<default>false</default>
|
||||
<required>false</required>
|
||||
</parameter>
|
||||
<parameter name="threshold" type="integer" min="0">
|
||||
<label>Smallest Value Change to Sent</label>
|
||||
<description>Smallest value change will be sent to openHAB</description>
|
||||
<default>0</default>
|
||||
<required>false</required>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</config-description:config-descriptions>
|
||||
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<config-description:config-descriptions
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0 https://openhab.org/schemas/config-description-1.0.0.xsd">
|
||||
|
||||
<config-description uri="thing-type:plclogo:pulse">
|
||||
<parameter name="block" type="text" pattern="VB(\d|[1-9]\d|[1-7]\d{2}|8[0-4]\d|850)\.[0-7]">
|
||||
<label>LOGO! Memory Address</label>
|
||||
<description>LOGO! memory address</description>
|
||||
<required>true</required>
|
||||
</parameter>
|
||||
<parameter name="observe" type="text"
|
||||
pattern="I([1-9]|1\d|2[0-4])|NI([1-9]|[1-5]\d|6[0-4])|Q([1-9]|1\d|20)|NQ([1-9]|[1-5]\d|6[0-4])|M([1-9]|[1-5]\d|6[0-4])|VB(\d|[1-9]\d|[1-7]\d{2}|8[0-4]\d|850)\.[0-7]">
|
||||
<label>LOGO! Block/Memory Address</label>
|
||||
<description>LOGO! block or memory address to observe</description>
|
||||
<required>false</required>
|
||||
</parameter>
|
||||
<parameter name="pulse" type="integer">
|
||||
<label>Pulse Length</label>
|
||||
<description>Time to wait before state reset</description>
|
||||
<default>150</default>
|
||||
<required>false</required>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</config-description:config-descriptions>
|
||||
@@ -0,0 +1,25 @@
|
||||
# binding
|
||||
binding.plclogo.name = PLCLogo Binding
|
||||
binding.plclogo.description = Dieses Binding bietet Unterstüzung für Siemens LOGO! Steuerungen.
|
||||
|
||||
# thing types
|
||||
thing-type.plclogo.device.label = Steuerung
|
||||
thing-type.plclogo.device.description = Siemens LOGO! Steuerung
|
||||
thing-type.plclogo.analog.label = Analoge Ein-/Ausgänge
|
||||
thing-type.plclogo.analog.description = Siemens LOGO! analoge Ein-/Ausgänge
|
||||
thing-type.plclogo.digital.label = Digitale Ein-/Ausgänge
|
||||
thing-type.plclogo.digital.description = Siemens LOGO! digitale Ein-/Ausgänge
|
||||
thing-type.plclogo.memory.label = Speicheradresse
|
||||
thing-type.plclogo.memory.description = Siemens LOGO! analoger/digitaler Ein-/Ausgang
|
||||
thing-type.plclogo.datetime.label = Zeit-/Datumparameter
|
||||
thing-type.plclogo.datetime.description = Siemens LOGO! Zeit-/Datumparameter
|
||||
thing-type.plclogo.pulse.label = Pulse-Block
|
||||
thing-type.plclogo.pulse.description = Siemens LOGO! virtueller Pulse-Eingang
|
||||
|
||||
# channel types
|
||||
channel-type.plclogo.rtc.label = Echtzeituhr
|
||||
channel-type.plclogo.rtc.description = Wert des Siemens LOGO! RTC
|
||||
channel-type.plclogo.state.label = Digitaler Ein-/Ausgang
|
||||
channel-type.plclogo.state.description = Zustand des digitalen Siemens LOGO! Blocks
|
||||
channel-type.plclogo.value.label = Analoger Ein-/Ausgang
|
||||
channel-type.plclogo.value.description = Wert des analogen Siemens LOGO! Blocks
|
||||
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="plclogo"
|
||||
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">
|
||||
|
||||
<!--Siemens LOGO! Analog -->
|
||||
<thing-type id="analog">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="device"/>
|
||||
</supported-bridge-type-refs>
|
||||
<label>Analog Blocks</label>
|
||||
<description>Siemens LOGO! analog input/output blocks</description>
|
||||
<config-description-ref uri="thing-type:plclogo:analog"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,60 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="plclogo"
|
||||
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">
|
||||
|
||||
<!--Siemens LOGO! PLC -->
|
||||
<bridge-type id="device">
|
||||
<label>LOGO! PLC</label>
|
||||
<description>Siemens LOGO! PLC</description>
|
||||
<channels>
|
||||
<channel id="diagnostic" typeId="diagnostic"/>
|
||||
<channel id="rtc" typeId="rtc"/>
|
||||
<channel id="weekday" typeId="weekday"/>
|
||||
</channels>
|
||||
<config-description-ref uri="thing-type:plclogo:bridge"/>
|
||||
</bridge-type>
|
||||
|
||||
<!--Siemens LOGO! channels -->
|
||||
<channel-type id="diagnostic">
|
||||
<item-type>String</item-type>
|
||||
<label>Diagnostic</label>
|
||||
<description>The diagnostic reported by Siemens LOGO!</description>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="rtc">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Real Time Clock</label>
|
||||
<description>The value of Siemens LOGO! real time clock</description>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="weekday" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Day of Week</label>
|
||||
<description>The day of week reported by Siemens LOGO!</description>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<!--Siemens LOGO! digital channels -->
|
||||
<channel-type id="contact">
|
||||
<item-type>Contact</item-type>
|
||||
<label>Digital Input</label>
|
||||
</channel-type>
|
||||
<channel-type id="switch">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Digital Output</label>
|
||||
</channel-type>
|
||||
|
||||
<!--Siemens LOGO! analog channels -->
|
||||
<channel-type id="number">
|
||||
<item-type>Number</item-type>
|
||||
<label>Analog Number</label>
|
||||
</channel-type>
|
||||
<channel-type id="datetime">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Analog Date/Time</label>
|
||||
</channel-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="plclogo"
|
||||
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">
|
||||
|
||||
<!--Siemens LOGO! Digital -->
|
||||
<thing-type id="datetime">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="device"/>
|
||||
</supported-bridge-type-refs>
|
||||
<label>Date/Time Block</label>
|
||||
<description>Siemens LOGO! date/time block</description>
|
||||
<config-description-ref uri="thing-type:plclogo:datetime"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="plclogo"
|
||||
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">
|
||||
|
||||
<!--Siemens LOGO! Digital -->
|
||||
<thing-type id="digital">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="device"/>
|
||||
</supported-bridge-type-refs>
|
||||
<label>Digital Blocks</label>
|
||||
<description>Siemens LOGO! digital input/output blocks</description>
|
||||
<config-description-ref uri="thing-type:plclogo:digital"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="plclogo"
|
||||
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">
|
||||
|
||||
<!--Siemens LOGO! Digital -->
|
||||
<thing-type id="memory">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="device"/>
|
||||
</supported-bridge-type-refs>
|
||||
<label>Memory Address</label>
|
||||
<description>Siemens LOGO! memory address</description>
|
||||
<config-description-ref uri="thing-type:plclogo:memory"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="plclogo"
|
||||
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">
|
||||
|
||||
<!--Siemens LOGO! Digital -->
|
||||
<thing-type id="pulse">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="device"/>
|
||||
</supported-bridge-type-refs>
|
||||
<label>Pulse Block</label>
|
||||
<description>Siemens LOGO! pulse virtual block</description>
|
||||
<config-description-ref uri="thing-type:plclogo:pulse"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
||||
Reference in New Issue
Block a user