added migrated 2.x add-ons
Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
10
bundles/org.openhab.binding.phc/src/main/feature/feature.xml
Normal file
10
bundles/org.openhab.binding.phc/src/main/feature/feature.xml
Normal 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>
|
||||
@@ -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";
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user