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,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.phc-${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-phc" description="PHC Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<feature>openhab-transport-serial</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.phc/${project.version}</bundle>
</feature>
</features>

View File

@@ -0,0 +1,54 @@
/**
* 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.phc.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID;
/**
* The {@link PHCBinding} class defines common constants, which are used across
* the whole binding.
*
* @author Jonas Hohaus - Initial contribution
*/
@NonNullByDefault
public class PHCBindingConstants {
public static final String BINDING_ID = "phc";
// List of all Thing Type UIDs
public static final ThingTypeUID THING_TYPE_BRIDGE = new ThingTypeUID(BINDING_ID, "bridge");
public static final ThingTypeUID THING_TYPE_AM = new ThingTypeUID(BINDING_ID, "AM");
public static final ThingTypeUID THING_TYPE_EM = new ThingTypeUID(BINDING_ID, "EM");
public static final ThingTypeUID THING_TYPE_JRM = new ThingTypeUID(BINDING_ID, "JRM");
public static final ThingTypeUID THING_TYPE_DIM = new ThingTypeUID(BINDING_ID, "DIM");
// List of all Channel Group IDs
public static final String CHANNELS_AM = "am";
public static final String CHANNELS_EM = "em";
public static final String CHANNELS_EM_LED = "emLed";
public static final String CHANNELS_JRM = "jrm";
public static final String CHANNELS_JRM_TIME = "jrmT";
public static final String CHANNELS_DIM = "dim";
// List of all configuration parameters
public static final String PORT = "port";
public static final String ADDRESS = "address";
public static final String UP_DOWN_TIME_1 = "upDownTime1";
public static final String UP_DOWN_TIME_2 = "upDownTime2";
public static final String UP_DOWN_TIME_3 = "upDownTime3";
public static final String UP_DOWN_TIME_4 = "upDownTime4";
public static final String DIM_TIME_1 = "dimTime1";
public static final String DIM_TIME_2 = "dimTime2";
}

View File

@@ -0,0 +1,102 @@
/**
* 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.phc.internal;
import static org.openhab.binding.phc.internal.PHCBindingConstants.*;
import java.util.Collections;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.phc.internal.handler.PHCBridgeHandler;
import org.openhab.binding.phc.internal.handler.PHCHandler;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.io.transport.serial.SerialPortManager;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
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;
import org.osgi.service.component.annotations.Reference;
/**
* The {@link PHCHandlerFactory} is responsible for creating things and thing
* handlers.
*
* @author Jonas Hohaus - Initial contribution
*/
@NonNullByDefault
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.phc")
public class PHCHandlerFactory extends BaseThingHandlerFactory {
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections
.unmodifiableSet(Stream.of(THING_TYPE_BRIDGE, THING_TYPE_AM, THING_TYPE_EM, THING_TYPE_JRM, THING_TYPE_DIM)
.collect(Collectors.toSet()));
private @NonNullByDefault({}) SerialPortManager serialPortManager;
@Reference
protected void setSerialPortManager(final SerialPortManager serialPortManager) {
this.serialPortManager = serialPortManager;
}
protected void unsetSerialPortManager(final SerialPortManager serialPortManager) {
this.serialPortManager = null;
}
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
}
@Override
public @Nullable Thing createThing(ThingTypeUID thingTypeUID, Configuration configuration,
@Nullable ThingUID thingUID, @Nullable ThingUID bridgeUID) {
Thing thing;
if (SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID)) {
if (thingTypeUID.equals(THING_TYPE_BRIDGE)) {
thing = super.createThing(thingTypeUID, configuration, thingUID, null);
} else {
ThingUID phcThingUID = new ThingUID(thingTypeUID, configuration.get(ADDRESS).toString());
thing = super.createThing(thingTypeUID, configuration, phcThingUID, bridgeUID);
}
} else {
throw new IllegalArgumentException(
"The thing type " + thingTypeUID + " is not supported by the phc binding.");
}
return thing;
}
@Override
protected @Nullable ThingHandler createHandler(Thing thing) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
ThingHandler handler = null;
if (thingTypeUID.equals(THING_TYPE_BRIDGE)) {
handler = new PHCBridgeHandler((Bridge) thing, serialPortManager);
} else if (SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID)) {
handler = new PHCHandler(thing);
}
return handler;
}
}

View File

@@ -0,0 +1,61 @@
/**
* 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.phc.internal;
import org.apache.commons.lang.StringUtils;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
/**
* The {@link PHCHelper} is responsible for finding the appropriate Thing(UID)
* to the Channel of the PHC module.
*
* @author Jonas Hohaus - Initial contribution
*/
@NonNullByDefault
public class PHCHelper {
/**
* Get the ThingUID by the given parameters.
*
* @param thingTypeUID
* @param moduleAddr reverse (to the reverse address - DIP switches)
* @return
*/
public static ThingUID getThingUIDreverse(ThingTypeUID thingTypeUID, byte moduleAddr) {
// convert to 5-bit binary string and reverse in second step
String thingID = StringUtils.leftPad(StringUtils.trim(Integer.toBinaryString(moduleAddr & 0xFF)), 5, '0');
thingID = new StringBuilder(thingID).reverse().toString();
ThingUID thingUID = new ThingUID(thingTypeUID, thingID);
return thingUID;
}
/**
* Convert the byte b into an binary String
*
* @param b
* @return
*/
public static Object bytesToBinaryString(byte[] bytes) {
StringBuilder bin = new StringBuilder();
for (byte b : bytes) {
bin.append(StringUtils.leftPad(StringUtils.trim(Integer.toBinaryString(b & 0xFF)), 8, '0'));
bin.append(' ');
}
return bin.toString();
}
}

View File

@@ -0,0 +1,74 @@
/**
* 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.phc.internal.handler;
import java.util.Arrays;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
/**
* Buffer for received messages
*
* @author Jonas Hohaus - Initial contribution
*/
class InternalBuffer {
private static final int MAX_SIZE = 512;
private final BlockingQueue<byte[]> byteQueue = new LinkedBlockingQueue<>();
private byte[] buffer;
private int bufferIndex = 0;
private int size;
public void offer(byte[] buffer) {
// If the buffer becomes too large, already processed commands accumulate and
// the reaction becomes slow.
if (size < MAX_SIZE) {
byte[] localBuffer = Arrays.copyOf(buffer, Math.min(MAX_SIZE - size, buffer.length));
byteQueue.offer(localBuffer);
synchronized (this) {
size += localBuffer.length;
}
}
}
public boolean hasNext() {
return (size > 0);
}
public byte get() throws InterruptedException {
byte[] buf = getBuffer();
if (buf != null) {
byte result = buf[bufferIndex++];
synchronized (this) {
size--;
}
return result;
}
throw new IllegalStateException("get without hasNext");
}
public int size() {
return size;
}
private byte[] getBuffer() throws InterruptedException {
if (buffer == null || bufferIndex == buffer.length) {
buffer = byteQueue.take();
bufferIndex = 0;
}
return buffer;
}
}

View File

@@ -0,0 +1,744 @@
/**
* 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.phc.internal.handler;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TooManyListenersException;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import org.apache.commons.lang.StringUtils;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.phc.internal.PHCBindingConstants;
import org.openhab.binding.phc.internal.PHCHelper;
import org.openhab.core.io.transport.serial.PortInUseException;
import org.openhab.core.io.transport.serial.SerialPort;
import org.openhab.core.io.transport.serial.SerialPortEvent;
import org.openhab.core.io.transport.serial.SerialPortEventListener;
import org.openhab.core.io.transport.serial.SerialPortIdentifier;
import org.openhab.core.io.transport.serial.SerialPortManager;
import org.openhab.core.io.transport.serial.UnsupportedCommOperationException;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.library.types.StopMoveType;
import org.openhab.core.thing.Bridge;
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.ThingUID;
import org.openhab.core.thing.binding.BaseBridgeHandler;
import org.openhab.core.types.Command;
import org.openhab.core.util.HexUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link PHCBridgeHandler} is responsible for handling the serial Communication to and from the PHC Modules.
*
* @author Jonas Hohaus - Initial contribution
*/
@NonNullByDefault
public class PHCBridgeHandler extends BaseBridgeHandler implements SerialPortEventListener {
private final Logger logger = LoggerFactory.getLogger(PHCBridgeHandler.class);
private static final int BAUD = 19200;
private static final int SEND_RETRY_COUNT = 20; // max count to send the same message
private static final int SEND_RETRY_TIME_MILLIS = 60; // time to wait for an acknowledge before send the message
// again in milliseconds
private @Nullable InputStream serialIn;
private @Nullable OutputStream serialOut;
private @Nullable SerialPort commPort;
private final SerialPortManager serialPortManager;
private final Map<Byte, Boolean> toggleMap = new HashMap<>();
private final InternalBuffer buffer = new InternalBuffer();
private final BlockingQueue<QueueObject> receiveQueue = new LinkedBlockingQueue<>();
private final BlockingQueue<QueueObject> sendQueue = new LinkedBlockingQueue<>();
private final ScheduledThreadPoolExecutor threadPoolExecutor = new ScheduledThreadPoolExecutor(3);
private final byte emLedOutputState[] = new byte[32];
private final byte amOutputState[] = new byte[32];
private final byte dmOutputState[] = new byte[32];
private final List<Byte> modules = new ArrayList<>();
public PHCBridgeHandler(Bridge phcBridge, SerialPortManager serialPortManager) {
super(phcBridge);
this.serialPortManager = serialPortManager;
}
@Override
public void initialize() {
String port = ((String) getConfig().get(PHCBindingConstants.PORT));
// find the given port
SerialPortIdentifier portId = serialPortManager.getIdentifier(port);
if (portId == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Serial port '" + port + "' could not be found.");
return;
}
try {
// initialize serial port
SerialPort serialPort = portId.open(this.getClass().getName(), 2000); // owner, timeout
serialIn = serialPort.getInputStream();
// set port parameters
serialPort.setSerialPortParams(BAUD, SerialPort.DATABITS_8, SerialPort.STOPBITS_2, SerialPort.PARITY_NONE);
serialPort.setFlowControlMode(SerialPort.FLOWCONTROL_NONE);
serialPort.addEventListener(this);
// activate the DATA_AVAILABLE notifier
serialPort.notifyOnDataAvailable(true);
// get the output stream
serialOut = serialPort.getOutputStream();
commPort = serialPort;
sendPorBroadcast();
byte[] b = { 0x01 };
for (int j = 0; j <= 0x1F; j++) {
serialWrite(buildMessage((byte) j, 0, b, false));
}
updateStatus(ThingStatus.ONLINE);
// receive messages
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
processReceivedBytes();
}
});
// process received messages
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
processReceiveQueue();
}
});
// sendig commands to the modules
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
processSendQueue();
}
});
} catch (PortInUseException | TooManyListenersException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Could not open serial port " + port + ": " + e.getMessage());
} catch (UnsupportedCommOperationException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Could not configure serial port " + port + ": " + e.getMessage());
} catch (IOException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Failed to get input or output stream for serialPort: " + e.getMessage());
logger.debug("Failed to get inputstream for serialPort", e);
}
}
/**
* Reads the data on serial port and puts it into the internal buffer.
*/
@Override
public void serialEvent(SerialPortEvent event) {
if (event.getEventType() == SerialPortEvent.DATA_AVAILABLE && serialIn != null) {
try {
byte[] bytes = new byte[serialIn.available()];
serialIn.read(bytes);
buffer.offer(bytes);
if (logger.isTraceEnabled()) {
logger.trace("buffer offered {}", HexUtils.bytesToHex(bytes, " "));
}
} catch (IOException e) {
logger.warn("Error on reading input stream to internal buffer", e);
}
}
}
/**
* process internal incoming buffer (recognize on read messages)
*/
private void processReceivedBytes() {
int faultCounter = 0;
try {
byte module = buffer.get();
while (true) {
// Recognition of messages from byte buffer.
// not a known module address
if (!modules.contains(module)) {
module = buffer.get();
continue;
}
if (logger.isDebugEnabled()) {
logger.debug("get module: {}", new String(HexUtils.byteToHex(module)));
}
byte sizeToggle = buffer.get();
// read length of command and check if makes sense
if ((sizeToggle < 1 || sizeToggle > 3) && ((sizeToggle & 0xFF) < 0x81 || (sizeToggle & 0xFF) > 0x83)) {
if (logger.isDebugEnabled()) {
logger.debug("get invalid sizeToggle: {}", new String(HexUtils.byteToHex(sizeToggle)));
}
module = sizeToggle;
continue;
}
// read toggle, size and command
int size = (sizeToggle & 0x7F);
boolean toggle = (sizeToggle & 0x80) == 0x80;
logger.debug("get toggle: {}", toggle);
byte[] command = new byte[size];
for (int i = 0; i < size; i++) {
command[i] = buffer.get();
}
// log command
if (logger.isTraceEnabled()) {
logger.trace("command read: {}", PHCHelper.bytesToBinaryString(command));
}
// read crc
byte crcByte1 = buffer.get();
byte crcByte2 = buffer.get();
short crc = (short) (crcByte1 & 0xFF);
crc |= (crcByte2 << 8);
// calculate checkCrc
short checkCrc = calcCrc(module, sizeToggle, command);
// check crc
if (crc != checkCrc) {
logger.debug("CRC not correct (crc from message, calculated crc): {}, {}", crc, checkCrc);
faultCounter = handleCrcFault(faultCounter);
module = buffer.get();
continue;
}
if (logger.isTraceEnabled()) {
logger.trace("get crc: {}", HexUtils.bytesToHex(new byte[] { crcByte1, crcByte2 }, " "));
}
faultCounter = 0;
processReceivedMsg(module, toggle, command);
module = buffer.get();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
private int handleCrcFault(int faultCounter) throws InterruptedException {
if (faultCounter > 0) {
// Normally in this case we read the message repeatedly offset to the real -> skip one to 6 bytes
for (int i = 0; i < faultCounter; i++) {
if (buffer.hasNext()) {
buffer.get();
}
}
}
int resCounter = faultCounter + 1;
if (resCounter > 6) {
resCounter = 0;
}
return resCounter;
}
private void processReceivedMsg(byte module, boolean toggle, byte[] command) {
// Acknowledgement received (command first byte 0)
if (command[0] == 0) {
String moduleType;
byte channel = 0; // only needed for dim
if ((module & 0xE0) == 0x40) {
moduleType = PHCBindingConstants.CHANNELS_AM;
} else if ((module & 0xE0) == 0xA0) {
moduleType = PHCBindingConstants.CHANNELS_DIM;
channel = (byte) ((command[0] >>> 5) & 0x0F);
} else {
moduleType = PHCBindingConstants.CHANNELS_EM_LED;
}
setModuleOutputState(moduleType, (byte) (module & 0x1F), command[1], channel);
toggleMap.put(module, !toggle);
// initialization (first byte FF)
} else if (command[0] == (byte) 0xFF) {
if ((module & 0xE0) == 0x00) { // EM
sendEmConfig(module);
} else if ((module & 0xE0) == 0x40 || (module & 0xE0) == 0xA0) { // AM, JRM and DIM
sendAmConfig(module);
}
logger.debug("initialization: {}", module);
// ignored - ping (first byte 01)
} else if (command[0] == 0x01) {
logger.debug("first byte 0x01 -> ignored");
// EM command / update
} else {
if (((module & 0xE0) == 0x00)) {
sendEmAcknowledge(module, toggle);
logger.debug("send acknowledge (modul, toggle) {} {}", module, toggle);
byte channel = (byte) ((command[0] >>> 4) & 0x0F);
OnOffType onOff = OnOffType.OFF;
if ((command[0] & 0x0F) == 2) {
onOff = OnOffType.ON;
}
QueueObject qo = new QueueObject(PHCBindingConstants.CHANNELS_EM, module, channel, onOff);
// put recognized message into queue
if (!receiveQueue.contains(qo)) {
receiveQueue.offer(qo);
}
// ignore if message not from EM module
} else if (logger.isDebugEnabled()) {
logger.debug("Incoming message (module, toggle, command) not from EM module: {} {} {}",
new String(HexUtils.byteToHex(module)), toggle, PHCHelper.bytesToBinaryString(command));
}
}
}
/**
* process receive queue
*/
private void processReceiveQueue() {
while (true) {
try {
QueueObject qo = receiveQueue.take();
logger.debug("Consume Receive QueueObject: {}", qo);
handleIncomingCommand(qo.getModuleAddress(), qo.getChannel(), (OnOffType) qo.getCommand());
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
/**
* process send queue
*/
private void processSendQueue() {
while (true) {
try {
QueueObject qo = sendQueue.take();
sendQueueObject(qo);
} catch (InterruptedException e1) {
Thread.currentThread().interrupt();
}
}
}
private void sendQueueObject(QueueObject qo) {
int sendCount = 0;
// Send the command to the module until a response is received. Max. SEND_RETRY_COUNT repeats.
do {
switch (qo.getModuleType()) {
case PHCBindingConstants.CHANNELS_AM:
sendAm(qo.getModuleAddress(), qo.getChannel(), qo.getCommand());
break;
case PHCBindingConstants.CHANNELS_EM_LED:
sendEm(qo.getModuleAddress(), qo.getChannel(), qo.getCommand());
break;
case PHCBindingConstants.CHANNELS_JRM:
sendJrm(qo.getModuleAddress(), qo.getChannel(), qo.getCommand(), qo.getTime());
break;
case PHCBindingConstants.CHANNELS_DIM:
sendDim(qo.getModuleAddress(), qo.getChannel(), qo.getCommand(), qo.getTime());
break;
}
sendCount++;
try {
Thread.sleep(SEND_RETRY_TIME_MILLIS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
} while (!isChannelOutputState(qo.getModuleType(), qo.getModuleAddress(), qo.getChannel(), qo.getCommand())
&& sendCount < SEND_RETRY_COUNT);
if (PHCBindingConstants.CHANNELS_JRM.equals(qo.getModuleType())) {
// there aren't state per channel for JRM modules
amOutputState[qo.getModuleAddress() & 0x1F] = -1;
} else if (PHCBindingConstants.CHANNELS_DIM.equals(qo.getModuleType())) {
// state ist the same for every dim level except zero/off -> inizialize state
// with 0x0F after sending an command.
dmOutputState[qo.getModuleAddress() & 0x1F] |= (0x0F << (qo.getChannel() * 4));
}
if (sendCount >= SEND_RETRY_COUNT) {
// change the toggle: if no acknowledge received it may be wrong.
byte module = qo.getModuleAddress();
if (PHCBindingConstants.CHANNELS_AM.equals(qo.getModuleType())
|| PHCBindingConstants.CHANNELS_JRM.equals(qo.getModuleType())) {
module |= 0x40;
} else if (PHCBindingConstants.CHANNELS_DIM.equals(qo.getModuleType())) {
module |= 0xA0;
}
toggleMap.put(module, !getToggle(module));
if (logger.isDebugEnabled()) {
logger.debug("No acknowledge from the module {} received.", qo.getModuleAddress());
}
}
}
private void setModuleOutputState(String moduleType, byte moduleAddress, byte state, byte channel) {
if (PHCBindingConstants.CHANNELS_EM_LED.equals(moduleType)) {
emLedOutputState[moduleAddress] = state;
} else if (PHCBindingConstants.CHANNELS_AM.equals(moduleType)) {
amOutputState[moduleAddress & 0x1F] = state;
} else if (PHCBindingConstants.CHANNELS_DIM.equals(moduleType)) {
dmOutputState[moduleAddress & 0x1F] = (byte) (state << channel * 4);
}
}
private boolean isChannelOutputState(String moduleType, byte moduleAddress, byte channel, Command cmd) {
int state = OnOffType.OFF.equals(cmd) ? 0 : 1;
if (PHCBindingConstants.CHANNELS_EM_LED.equals(moduleType)) {
return ((emLedOutputState[moduleAddress & 0x1F] >>> channel) & 0x01) == state;
} else if (PHCBindingConstants.CHANNELS_AM.equals(moduleType)) {
return ((amOutputState[moduleAddress & 0x1F] >>> channel) & 0x01) == state;
} else if (PHCBindingConstants.CHANNELS_JRM.equals(moduleType)) {
return (amOutputState[moduleAddress & 0x1F] != -1);
} else if (PHCBindingConstants.CHANNELS_DIM.equals(moduleType)) {
return ((dmOutputState[moduleAddress & 0x1F] >>> channel * 4) & 0x0F) != 0x0F;
} else {
return false;
}
}
private boolean getToggle(byte moduleAddress) {
if (!toggleMap.containsKey(moduleAddress)) {
toggleMap.put(moduleAddress, false);
}
return toggleMap.get(moduleAddress);
}
/**
* Put the given command into the queue to send.
*
* @param moduleType
* @param moduleAddress
* @param channel
* @param command
* @param upDownTime
*/
public void send(@Nullable String moduleType, int moduleAddress, String channel, Command command,
short upDownTime) {
if (PHCBindingConstants.CHANNELS_JRM.equals(moduleType)
|| PHCBindingConstants.CHANNELS_DIM.equals(moduleType)) {
sendQueue.offer(new QueueObject(moduleType, moduleAddress, channel, command, upDownTime));
} else {
sendQueue.offer(new QueueObject(moduleType, moduleAddress, channel, command));
}
}
private void sendAm(byte moduleAddress, byte channel, Command command) {
byte module = (byte) (moduleAddress | 0x40);
byte[] cmd = { (byte) (channel << 5) };
if (OnOffType.ON.equals(command)) {
cmd[0] |= 2;
} else {
cmd[0] |= 3;
}
serialWrite(buildMessage(module, channel, cmd, getToggle(module)));
}
private void sendEm(byte moduleAddress, byte channel, Command command) {
byte[] cmd = { (byte) (channel << 4) };
if (OnOffType.ON.equals(command)) {
cmd[0] |= 2;
} else {
cmd[0] |= 3;
}
serialWrite(buildMessage(moduleAddress, channel, cmd, getToggle(moduleAddress)));
}
private void sendJrm(byte moduleAddress, byte channel, Command command, short upDownTime) {
// The up and the down message needs two additional bytes for the time.
int size = (command == StopMoveType.STOP) ? 2 : 4;
byte[] cmd = new byte[size];
if (channel == 0) {
channel = 4;
}
byte module = (byte) (moduleAddress | 0x40);
cmd[0] = (byte) (channel << 5);
cmd[1] = 0x3F;
switch (command.toString()) {
case "UP":
cmd[0] |= 5;
cmd[2] = (byte) (upDownTime & 0xFF);// Time 1/10 sec. LSB
cmd[3] = (byte) ((upDownTime >> 8) & 0xFF); // 1/10 sec. MSB
break;
case "DOWN":
cmd[0] |= 6;
cmd[2] = (byte) (upDownTime & 0xFF);// Time 1/10 sec. LSB
cmd[3] = (byte) ((upDownTime >> 8) & 0xFF); // 1/10 sec. MSB
break;
case "STOP":
cmd[0] |= 2;
break;
}
serialWrite(buildMessage(module, channel, cmd, getToggle(module)));
}
private void sendDim(byte moduleAddress, byte channel, Command command, short dimTime) {
byte module = (byte) (moduleAddress | 0xA0);
byte[] cmd = new byte[(command instanceof PercentType && !(((PercentType) command).byteValue() == 0)) ? 3 : 1];
cmd[0] = (byte) (channel << 5);
if (command instanceof OnOffType) {
if (OnOffType.ON.equals(command)) {
cmd[0] |= 3;
} else if (OnOffType.OFF.equals(command)) {
cmd[0] |= 4;
}
} else {
if (((PercentType) command).byteValue() == 0) {
cmd[0] |= 4;
} else {
cmd[0] |= 22;
cmd[1] = (byte) (((PercentType) command).byteValue() * 2.55);
cmd[2] = (byte) dimTime;
}
}
serialWrite(buildMessage(module, channel, cmd, getToggle(module)));
}
private void sendPorBroadcast() {
byte[] msg = buildMessage((byte) 0xFF, 0, new byte[] { 0 }, false);
for (int i = 0; i < 20; i++) {
serialWrite(msg);
}
}
private void sendAmConfig(byte moduleAddress) {
byte[] cmd = new byte[3];
cmd[0] = (byte) 0xFE;
cmd[1] = 0;
cmd[2] = (byte) 0xFF;
serialWrite(buildMessage(moduleAddress, 0, cmd, false));
}
private void sendEmConfig(byte moduleAddress) {
byte[] cmd = new byte[52];
int pos = 0;
cmd[pos++] = (byte) 0xFE;
cmd[pos++] = (byte) 0x00; // POR
cmd[pos++] = 0x00;
cmd[pos++] = 0x00;
for (int i = 0; i < 16; i++) { // 16 inputs
cmd[pos++] = (byte) ((i << 4) | 0x02);
cmd[pos++] = (byte) ((i << 4) | 0x03);
cmd[pos++] = (byte) ((i << 4) | 0x05);
}
serialWrite(buildMessage(moduleAddress, 0, cmd, false));
}
private void sendEmAcknowledge(byte module, boolean toggle) {
byte[] msg = buildMessage(module, 0, new byte[] { 0 }, toggle);
for (int i = 0; i < 3; i++) { // send three times stops the module faster from sending messages if the first
// response is not recognized.
serialWrite(msg);
}
}
/**
* Build a serial message from the given parameters.
*
* @param modulAddr
* @param channel
* @param cmd
* @param toggle
* @return
*/
private byte[] buildMessage(byte modulAddr, int channel, byte[] cmd, boolean toggle) {
int len = cmd.length;
byte[] buffer = new byte[len + 4];
buffer[0] = modulAddr;
buffer[1] = (byte) (toggle ? (len | 0x80) : len); // 0x80: 1000 0000
System.arraycopy(cmd, 0, buffer, 2, len);
short crc = calcCrc(modulAddr, buffer[1], cmd);
buffer[2 + len] = (byte) (crc & 0xFF);
buffer[3 + len] = (byte) ((crc >> 8) & 0xFF);
return buffer;
}
/**
* Calculate the 16 bit crc of the message.
*
* @param module
* @param sizeToggle
* @param cmd
* @return
*/
private short calcCrc(byte module, byte sizeToggle, byte[] cmd) {
short crc = (short) 0xFFFF;
crc = crc16Update(crc, module);
crc = crc16Update(crc, sizeToggle);
for (byte b : cmd) {
crc = crc16Update(crc, b);
}
crc ^= 0xFFFF;
return crc;
}
/**
* Update the 16 bit crc of the message.
*
* @param crc
* @param data
* @return
*/
private short crc16Update(short crc, byte messagePart) {
byte data = (byte) (messagePart ^ (crc & 0xFF));
data ^= data << 4;
short data16 = data;
return (short) (((data16 << 8) | (((crc >> 8) & 0xFF) & 0xFF)) ^ ((data >> 4) & 0xF)
^ ((data16 << 3) & 0b11111111111));
}
/**
* Send the incoming command to the appropriate handler and channel.
*
* @param moduleAddress
* @param channel
* @param cmd
* @param rcvCrc
*/
private void handleIncomingCommand(byte moduleAddress, int channel, OnOffType onOff) {
ThingUID uid = PHCHelper.getThingUIDreverse(PHCBindingConstants.THING_TYPE_EM, moduleAddress);
Thing thing = getThing().getThing(uid);
String channelId = "em#" + StringUtils.leftPad(Integer.toString(channel), 2, '0');
if (thing != null && thing.getHandler() != null) {
logger.debug("Input: {}, {}, {}", thing.getUID(), channelId, onOff);
PHCHandler handler = (PHCHandler) thing.getHandler();
if (handler != null) {
handler.handleIncoming(channelId, onOff);
} else {
logger.debug("No Handler for Thing {} available.", thing.getUID());
}
} else {
logger.debug("No Thing with UID {} available.", uid.getAsString());
}
}
private void serialWrite(byte[] msg) {
if (serialOut != null) {
try {
// write to serial port
serialOut.write(msg);
serialOut.flush();
} catch (IOException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Error writing '" + msg + "' to serial port : " + e.getMessage());
}
if (logger.isTraceEnabled()) {
logger.trace("send: {}", PHCHelper.bytesToBinaryString(msg));
}
}
}
/**
* Adds the given address to the module list.
*
* @param module
*/
public void addModule(byte module) {
modules.add(module);
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
// unnecessary
}
@Override
public void dispose() {
threadPoolExecutor.shutdownNow();
if (commPort != null) {
commPort.close();
commPort = null;
}
}
}

View File

@@ -0,0 +1,183 @@
/**
* 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.phc.internal.handler;
import static org.openhab.binding.phc.internal.PHCBindingConstants.*;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
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.PercentType;
import org.openhab.core.library.types.StopMoveType;
import org.openhab.core.library.types.UpDownType;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.types.Command;
import org.openhab.core.types.State;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link PHCHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Jonas Hohaus - Initial contribution
*
*/
public class PHCHandler extends BaseThingHandler {
private final Logger logger = LoggerFactory.getLogger(PHCHandler.class);
private String moduleAddress; // like DIP switches
private byte module;
private final short[] times = new short[4];
private final Map<String, State> channelState = new HashMap<>();
private PHCBridgeHandler bridgeHandler;
public PHCHandler(Thing thing) {
super(thing);
}
@Override
public void initialize() {
moduleAddress = (String) getConfig().get(ADDRESS);
if (getPHCBridgeHandler() == null) {
return;
}
module = Byte.parseByte(new StringBuilder(moduleAddress).reverse().toString(), 2);
if (getThing().getThingTypeUID().equals(THING_TYPE_AM) || getThing().getThingTypeUID().equals(THING_TYPE_JRM)) {
module |= 0x40;
} else if (getThing().getThingTypeUID().equals(THING_TYPE_DIM)) {
module |= 0xA0;
}
getPHCBridgeHandler().addModule(module);
if (getThing().getThingTypeUID().equals(THING_TYPE_JRM)) {
times[0] = (short) (((BigDecimal) getConfig().get(UP_DOWN_TIME_1)).shortValue() * 10);
times[1] = (short) (((BigDecimal) getConfig().get(UP_DOWN_TIME_2)).shortValue() * 10);
times[2] = (short) (((BigDecimal) getConfig().get(UP_DOWN_TIME_3)).shortValue() * 10);
times[3] = (short) (((BigDecimal) getConfig().get(UP_DOWN_TIME_4)).shortValue() * 10);
} else if (getThing().getThingTypeUID().equals(THING_TYPE_DIM)) {
times[0] = (((BigDecimal) getConfig().get(DIM_TIME_1)).shortValue());
times[1] = (((BigDecimal) getConfig().get(DIM_TIME_2)).shortValue());
}
Bridge bridge = getBridge();
if (bridge != null && bridge.getStatus() == ThingStatus.ONLINE) {
updateStatus(ThingStatus.ONLINE);
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
}
}
public void handleIncoming(String channelId, OnOffType state) {
if (logger.isDebugEnabled()) {
logger.debug("EM command: {}, last: {}, in: {}", channelId, channelState.get(channelId), state);
}
if (!channelState.containsKey(channelId) || !channelState.get(channelId).equals(state)) {
postCommand(channelId, state);
channelState.put(channelId, state);
}
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
final String groupId = channelUID.getGroupId();
if (getThing().getStatus().equals(ThingStatus.ONLINE)) {
if ((CHANNELS_JRM.equals(groupId) && (command instanceof UpDownType || command instanceof StopMoveType))
|| (CHANNELS_DIM.equals(groupId)
&& (command instanceof OnOffType || command instanceof PercentType))) {
getPHCBridgeHandler().send(groupId, module & 0x1F, channelUID.getIdWithoutGroup(), command,
times[Integer.parseInt(channelUID.getIdWithoutGroup())]);
} else if ((CHANNELS_AM.equals(groupId) || CHANNELS_EM_LED.equals(groupId))
&& command instanceof OnOffType) {
getPHCBridgeHandler().send(groupId, module & 0x1F, channelUID.getIdWithoutGroup(), command, (short) 0);
}
logger.debug("send command: {}, {}", channelUID, command);
} else {
logger.info("The Thing {} is offline.", getThing().getUID());
}
}
@Override
public void handleUpdate(ChannelUID channelUID, State newState) {
if (CHANNELS_JRM_TIME.equals(channelUID.getGroupId())) {
times[Integer
.parseInt(channelUID.getIdWithoutGroup())] = (short) (((DecimalType) newState).floatValue() * 10);
}
}
@Override
public void handleConfigurationUpdate(Map<String, Object> configurationParameters) {
if (isInitialized()) { // prevents change of address
validateConfigurationParameters(configurationParameters);
Configuration configuration = editConfiguration();
for (Entry<String, Object> configurationParmeter : configurationParameters.entrySet()) {
if (!configurationParmeter.getKey().equals(ADDRESS)) {
configuration.put(configurationParmeter.getKey(), configurationParmeter.getValue());
} else {
configuration.put(configurationParmeter.getKey(), moduleAddress);
}
}
// persist new configuration and reinitialize handler
dispose();
updateConfiguration(configuration);
initialize();
} else {
super.handleConfigurationUpdate(configurationParameters);
}
}
private PHCBridgeHandler getPHCBridgeHandler() {
if (bridgeHandler == null) {
Bridge bridge = getBridge();
if (bridge == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING,
"The Thing requires to select a Bridge");
return null;
}
ThingHandler handler = bridge.getHandler();
if (handler instanceof PHCBridgeHandler) {
bridgeHandler = (PHCBridgeHandler) handler;
} else {
logger.debug("No available bridge handler for {}.", bridge.getUID());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_MISSING_ERROR,
"No available bridge handler.");
return null;
}
}
return bridgeHandler;
}
}

View File

@@ -0,0 +1,81 @@
/**
* 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.phc.internal.handler;
import org.openhab.core.types.Command;
/**
* Object to save a whole message.
*
* @author Jonas Hohaus - Initial contribution
*/
class QueueObject {
private final String moduleType;
private final byte moduleAddress;
private final byte channel;
private final Command command;
private short time;
public QueueObject(String moduleType, byte moduleAddress, byte channel, Command command) {
this.moduleType = moduleType;
this.moduleAddress = moduleAddress;
this.channel = channel;
this.command = command;
}
public QueueObject(String moduleType, int moduleAddress, String channel, Command command) {
this.moduleType = moduleType;
this.moduleAddress = (byte) moduleAddress;
this.channel = Byte.parseByte(channel);
this.command = command;
}
public QueueObject(String moduleType, int moduleAddress, String channel, Command command, short time) {
this(moduleType, moduleAddress, channel, command);
this.time = time;
}
public String getModuleType() {
return moduleType;
}
public byte getModuleAddress() {
return moduleAddress;
}
public byte getChannel() {
return channel;
}
public Command getCommand() {
return command;
}
public short getTime() {
return time;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("moduleType: ");
sb.append(moduleType);
sb.append(", moduleAddress: ");
sb.append(moduleAddress);
sb.append(", channel: ");
sb.append(channel);
return sb.toString();
}
}

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8" ?>
<binding:binding id="phc" 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>PHC Binding</name>
<description>This is a binding for PHC modules (EM, AM, JRM and DIM). It communicates with the PHC Modulbus (RS485).</description>
<author>Jonas Hohaus</author>
</binding:binding>

View File

@@ -0,0 +1,109 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="phc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<!-- Channel Group Types -->
<channel-group-type id="amChannels">
<label>AM Channels</label>
<description>Outgoing switch channels (relay).</description>
<channels>
<channel id="00" typeId="am-channel"/>
<channel id="01" typeId="am-channel"/>
<channel id="02" typeId="am-channel"/>
<channel id="03" typeId="am-channel"/>
<channel id="04" typeId="am-channel"/>
<channel id="05" typeId="am-channel"/>
<channel id="06" typeId="am-channel"/>
<channel id="07" typeId="am-channel"/>
</channels>
</channel-group-type>
<channel-group-type id="emChannels">
<label>EM Channels</label>
<description>Incoming channels.</description>
<channels>
<channel id="00" typeId="em-channel"/>
<channel id="01" typeId="em-channel"/>
<channel id="02" typeId="em-channel"/>
<channel id="03" typeId="em-channel"/>
<channel id="04" typeId="em-channel"/>
<channel id="05" typeId="em-channel"/>
<channel id="06" typeId="em-channel"/>
<channel id="07" typeId="em-channel"/>
<channel id="08" typeId="em-channel"/>
<channel id="09" typeId="em-channel"/>
<channel id="10" typeId="em-channel"/>
<channel id="11" typeId="em-channel"/>
<channel id="12" typeId="em-channel"/>
<channel id="13" typeId="em-channel"/>
<channel id="14" typeId="em-channel"/>
<channel id="15" typeId="em-channel"/>
</channels>
</channel-group-type>
<channel-group-type id="jrmChannels">
<label>JRM Channels</label>
<description>Outgoing shutter channels (relay).</description>
<channels>
<channel id="00" typeId="jrm-channel"/>
<channel id="01" typeId="jrm-channel"/>
<channel id="02" typeId="jrm-channel"/>
<channel id="03" typeId="jrm-channel"/>
</channels>
</channel-group-type>
<channel-group-type id="jrmTimeChannels" advanced="true">
<label>JRM time Channels</label>
<description>Time for shutter channels in seconds with an accuracy of 1/10 seconds.</description>
<channels>
<channel id="00" typeId="jrmTime-channel"/>
<channel id="01" typeId="jrmTime-channel"/>
<channel id="02" typeId="jrmTime-channel"/>
<channel id="03" typeId="jrmTime-channel"/>
</channels>
</channel-group-type>
<channel-group-type id="dimChannels">
<label>DIM Channels</label>
<description>Outgoing dimmer channels.</description>
<channels>
<channel id="00" typeId="dim-channel"/>
<channel id="01" typeId="dim-channel"/>
</channels>
</channel-group-type>
<!-- Channel Types -->
<channel-type id="am-channel">
<item-type>Switch</item-type>
<label>PHC AM Channel</label>
<description>Channel to an AM or EM(LED) module.</description>
</channel-type>
<channel-type id="em-channel">
<item-type>Switch</item-type>
<label>PHC EM Channel</label>
<description>Channel from an EM module.</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="jrm-channel">
<item-type>Rollershutter</item-type>
<label>PHC JRM Channel</label>
<description>Channel to an JRM module.</description>
</channel-type>
<channel-type id="jrmTime-channel" advanced="true">
<item-type>Number</item-type>
<label>JRM-time Channel</label>
<description>The Time in seconds for an JRM channel.</description>
<state min="1" max="65535"/>
</channel-type>
<channel-type id="dim-channel">
<item-type>Dimmer</item-type>
<label>DIM Channel</label>
<description>Channel for a DIM module.</description>
</channel-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,148 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="phc" 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">
<!-- Bridge -->
<bridge-type id="bridge">
<label>PHC Bridge</label>
<description>The serial bridge to the PHC modules. Max 32 modules per model group(thing type) per Bridge, equates one
STM.</description>
<config-description>
<parameter name="port" type="text">
<label>Serial Port</label>
<description>Serial Port the PHC modules are connected to</description>
<required>true</required>
<context>serial-port</context>
<limitToOptions>false</limitToOptions>
</parameter>
</config-description>
</bridge-type>
<!-- Thing Types -->
<thing-type id="AM">
<supported-bridge-type-refs>
<bridge-type-ref id="bridge"/>
</supported-bridge-type-refs>
<label>PHC AM</label>
<description>Thing for an output/relay module (AM).</description>
<channel-groups>
<channel-group id="am" typeId="amChannels"/>
</channel-groups>
<config-description>
<parameter name="address" type="text" pattern="[0-1]{5}" min="5" max="5">
<label>Address</label>
<description>Address of the module as binary, like the DIP switches.</description>
<required>true</required>
</parameter>
</config-description>
</thing-type>
<thing-type id="EM">
<supported-bridge-type-refs>
<bridge-type-ref id="bridge"/>
</supported-bridge-type-refs>
<label>PHC EM</label>
<description>Thing for an input/switch module (EM).</description>
<channel-groups>
<channel-group id="em" typeId="emChannels"/>
<channel-group id="emLed" typeId="amChannels"/>
</channel-groups>
<config-description>
<parameter name="address" type="text" pattern="[0-1]{5}" min="5" max="5">
<label>Address</label>
<description>Address of the module as binary, like the DIP switches.</description>
<required>true</required>
</parameter>
</config-description>
</thing-type>
<thing-type id="JRM">
<supported-bridge-type-refs>
<bridge-type-ref id="bridge"/>
</supported-bridge-type-refs>
<label>PHC JRM</label>
<description>Thing for an shutter module (JRM).</description>
<channel-groups>
<channel-group id="jrm" typeId="jrmChannels"/>
<channel-group id="jrmT" typeId="jrmTimeChannels"/>
</channel-groups>
<config-description>
<parameter name="address" type="text" pattern="[0-1]{5}" min="5" max="5">
<label>Address</label>
<description>Address of the module as binary, like the DIP switches.</description>
<required>true</required>
</parameter>
<parameter name="upDownTime1" type="integer" min="1" max="65535">
<advanced>true</advanced>
<label>Time Shutter 1</label>
<description>The time (in seconds) which the first shutter needs to move up/down.</description>
<default>30</default>
</parameter>
<parameter name="upDownTime2" type="integer" min="1" max="65535">
<advanced>true</advanced>
<label>Time Shutter 2</label>
<description>The time (in seconds) which the second shutter needs to move up/down.</description>
<default>30</default>
</parameter>
<parameter name="upDownTime3" type="integer" min="1" max="65535">
<advanced>true</advanced>
<label>Time Shutter 3</label>
<description>The time (in seconds) which the third shutter needs to move up/down.</description>
<default>30</default>
</parameter>
<parameter name="upDownTime4" type="integer" min="1" max="65535">
<advanced>true</advanced>
<label>Time Shutter 4</label>
<description>The time (in seconds) which the fourth shutter needs to move up/down.</description>
<default>30</default>
</parameter>
</config-description>
</thing-type>
<thing-type id="DIM">
<supported-bridge-type-refs>
<bridge-type-ref id="bridge"/>
</supported-bridge-type-refs>
<label>PHC DIM</label>
<description>Thing for a dimmer module (DM).</description>
<channel-groups>
<channel-group id="dim" typeId="dimChannels"/>
</channel-groups>
<config-description>
<parameter name="address" type="text" pattern="[0-1]{5}" min="5" max="5">
<label>Address</label>
<description>Address of the module as binary, like the DIP switches.</description>
<required>true</required>
</parameter>
<parameter name="dimTime1" type="integer" min="1" max="255">
<advanced>true</advanced>
<label>Time Dimmer 1</label>
<description>The time (in seconds) in which the first dimmer should dim 100%.</description>
<default>2</default>
</parameter>
<parameter name="dimTime2" type="integer" min="1" max="255">
<advanced>true</advanced>
<label>Time Dimmer 2</label>
<description>The time (in seconds) in which the second dimmer should dim 100%.</description>
<default>2</default>
</parameter>
</config-description>
</thing-type>
</thing:thing-descriptions>