added migrated 2.x add-ons

Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
Kai Kreuzer
2020-09-21 01:58:32 +02:00
parent bbf1a7fd29
commit 6df6783b60
11662 changed files with 1302875 additions and 11 deletions

View 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>

View 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>

View 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

View 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.

File diff suppressed because it is too large Load Diff

View 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>

View File

@@ -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>

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>