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