added migrated 2.x add-ons
Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<features name="org.openhab.binding.cm11a-${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-cm11a" description="cm11a 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.cm11a/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
||||
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* 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.cm11a.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
/**
|
||||
* The {@link CM11ABinding} class defines common constants, which are
|
||||
* used across the whole binding.
|
||||
*
|
||||
* @author Bob Raker - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class CM11ABindingConstants {
|
||||
|
||||
public static final String BINDING_ID = "cm11a";
|
||||
|
||||
/**
|
||||
* Bridge Type UIDs
|
||||
*/
|
||||
public static final ThingTypeUID THING_TYPE_CM11A = new ThingTypeUID(BINDING_ID, "cm11a");
|
||||
|
||||
/**
|
||||
* List of all Thing Type UIDs
|
||||
*/
|
||||
public static final ThingTypeUID THING_TYPE_DIMMER = new ThingTypeUID(BINDING_ID, "dimmer");
|
||||
public static final ThingTypeUID THING_TYPE_SWITCH = new ThingTypeUID(BINDING_ID, "switch");
|
||||
|
||||
/**
|
||||
* List of all Channel ids
|
||||
*/
|
||||
public static final String CHANNEL_LIGHTLEVEL = "lightlevel";
|
||||
public static final String CHANNEL_SWITCH = "switchstatus";
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* 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.cm11a.internal;
|
||||
|
||||
import static org.openhab.binding.cm11a.internal.CM11ABindingConstants.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.openhab.binding.cm11a.internal.handler.Cm11aApplianceHandler;
|
||||
import org.openhab.binding.cm11a.internal.handler.Cm11aBridgeHandler;
|
||||
import org.openhab.binding.cm11a.internal.handler.Cm11aLampHandler;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerFactory;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link Cm11aHandlerFactory} is responsible for creating things and thing
|
||||
* handlers.
|
||||
*
|
||||
* @author Bob Raker - Initial contribution
|
||||
*/
|
||||
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.cm11a")
|
||||
public class Cm11aHandlerFactory extends BaseThingHandlerFactory {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(Cm11aHandlerFactory.class);
|
||||
|
||||
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections
|
||||
.unmodifiableSet(new HashSet<>(Arrays.asList(THING_TYPE_SWITCH, THING_TYPE_DIMMER)));
|
||||
|
||||
@Override
|
||||
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
||||
return THING_TYPE_CM11A.equals(thingTypeUID) || SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ThingHandler createHandler(Thing thing) {
|
||||
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||
logger.trace("**** Cm11AHandlerFactory.createHandler for {}", thingTypeUID.getAsString());
|
||||
|
||||
if (thingTypeUID.equals(THING_TYPE_CM11A)) {
|
||||
return new Cm11aBridgeHandler((Bridge) thing);
|
||||
} else if (thingTypeUID.equals(THING_TYPE_SWITCH)) {
|
||||
return new Cm11aApplianceHandler(thing);
|
||||
} else if (thingTypeUID.equals(THING_TYPE_DIMMER)) {
|
||||
return new Cm11aLampHandler(thing);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* 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.cm11a.internal;
|
||||
|
||||
/**
|
||||
* Exception for invalid X10 House / Unit code
|
||||
*
|
||||
* @author Bob Raker - Initial contribution
|
||||
*/
|
||||
public class InvalidAddressException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = -1049253542819790311L;
|
||||
|
||||
public InvalidAddressException(String string) {
|
||||
super(string);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,808 @@
|
||||
/**
|
||||
* 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.cm11a.internal;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Calendar;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TooManyListenersException;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
|
||||
import org.openhab.binding.cm11a.internal.handler.Cm11aAbstractHandler;
|
||||
import org.openhab.binding.cm11a.internal.handler.Cm11aBridgeHandler;
|
||||
import org.openhab.binding.cm11a.internal.handler.ReceivedDataListener;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import gnu.io.CommPortIdentifier;
|
||||
import gnu.io.NoSuchPortException;
|
||||
import gnu.io.PortInUseException;
|
||||
import gnu.io.SerialPort;
|
||||
import gnu.io.SerialPortEvent;
|
||||
import gnu.io.SerialPortEventListener;
|
||||
import gnu.io.UnsupportedCommOperationException;
|
||||
|
||||
/**
|
||||
* Driver for the CM11 X10 interface.
|
||||
*
|
||||
*
|
||||
* @author Anthony Green - Original code
|
||||
* @author Bob Raker - updates to setClock code, adapted code for use in openHAB2
|
||||
* @see <a href="http://www.heyu.org/docs/protocol.txt">CM11 Protocol specification</a>
|
||||
* @see <a href="http://www.rxtx.org">RXTX Serial API for Java</a>
|
||||
*/
|
||||
public class X10Interface extends Thread implements SerialPortEventListener {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(X10Interface.class);
|
||||
|
||||
// X10 Function codes
|
||||
public static final int FUNC_ALL_UNITS_OFF = 0x0;
|
||||
public static final int FUNC_ALL_LIGHTS_ON = 0x1;
|
||||
public static final int FUNC_ON = 0x2;
|
||||
public static final int FUNC_OFF = 0x3;
|
||||
public static final int FUNC_DIM = 0x4;
|
||||
public static final int FUNC_BRIGHT = 0x5;
|
||||
public static final int FUNC_ALL_LIGHTS_OFF = 0x6;
|
||||
public static final int FUNC_EXTENDED = 0x7;
|
||||
public static final int FUNC_HAIL_REQ = 0x8;
|
||||
public static final int FUNC_HAIL_ACK = 0x9;
|
||||
public static final int FUNC_PRESET_DIM_1 = 0xA;
|
||||
public static final int FUNC_PRESET_DIM_2 = 0xB;
|
||||
public static final int FUNC_EXT_DATA_TRANSFER = 0xC;
|
||||
public static final int FUNC_STATUS_ON = 0xD;
|
||||
public static final int FUNC_STATUS_OFF = 0xE;
|
||||
public static final int FUNC_STATUS_REQ = 0xF;
|
||||
|
||||
// Definitions for the header:code bits
|
||||
/**
|
||||
* Bit mask for the bit that is always set in a header:code
|
||||
*/
|
||||
static final int HEAD = 0x04;
|
||||
/**
|
||||
* Bit mask for Function/Address bit of header:code.
|
||||
*/
|
||||
static final int HEAD_FUNC = 0x02;
|
||||
/**
|
||||
* Bit mask for standard/extended transmission bit of header:code.
|
||||
*/
|
||||
static final int HEAD_EXTENDED = 0x01;
|
||||
|
||||
/**
|
||||
* Byte sent from PC to Interface to acknowledge the receipt of a correct checksum.
|
||||
* If the checksum was incorrect, the PC should retransmit.
|
||||
*/
|
||||
static final int CHECKSUM_ACK = 0x00;
|
||||
/**
|
||||
* Byte send from Interface to PC to indicate it has sent the desired data over the
|
||||
* X10/power lines.
|
||||
*/
|
||||
static final int IF_READY = 0x55;
|
||||
|
||||
/**
|
||||
* Byte sent from Interface to PC to request that its clock is set.
|
||||
* Interface will send this to the PC repeatedly after a power-failure and will not respond to commands
|
||||
* until its clock has been set.
|
||||
*/
|
||||
static final int CLOCK_SET_REQ = 0xA5;
|
||||
/**
|
||||
* Byte sent from PC to interface to start a transmission that sets the interface clock.
|
||||
*/
|
||||
static final int CLOCK_SET_HEAD = 0x9B;
|
||||
|
||||
/**
|
||||
* Byte sent from interface to PC to indicate that it has X10 data pending transmission to the PC.
|
||||
*/
|
||||
static final int DATA_READY_REQ = 0x5a;
|
||||
static final int DATA_READY_HEAD = 0xc3;
|
||||
|
||||
/**
|
||||
* This command is purely intended for the CP10.
|
||||
* The power-strip contains an input filter and electrical surge protection
|
||||
* that is monitored by the microcontroller. If this protection should
|
||||
* become compromised (i.e. resulting from a lightening strike) the
|
||||
* interface will attempt to wake the computer with a 'filter-fail poll'.
|
||||
*/
|
||||
static final int INPUT_FILTER_FAIL_REQ = 0xf3;
|
||||
static final int INPUT_FILTER_FAIL_HEAD = 0xf3;
|
||||
|
||||
/**
|
||||
* Byte sent from PC to interface to enable interface feature that brings serial port RI high when
|
||||
* data arrives to send to PC
|
||||
*/
|
||||
static final int RI_ENABLE = 0xeb;
|
||||
static final int RI_DISABLE = 0x55;
|
||||
|
||||
/**
|
||||
* THe house code to be monitored. Not sure what this means, but it is part of the clock set instruction.
|
||||
* For the moment hardcoded here to be House 'E'.
|
||||
*/
|
||||
static final int MONITORED_HOUSE_CODE = 0x10;
|
||||
|
||||
static final Map<Character, Integer> HOUSE_CODES;
|
||||
static final Map<Integer, Integer> DEVICE_CODES;
|
||||
|
||||
static {
|
||||
HashMap<Character, Integer> houseCodes = new HashMap<>(16);
|
||||
houseCodes.put('A', 0x60);
|
||||
houseCodes.put('B', 0xE0);
|
||||
houseCodes.put('C', 0x20);
|
||||
houseCodes.put('D', 0xA0);
|
||||
houseCodes.put('E', 0x10);
|
||||
houseCodes.put('F', 0x90);
|
||||
houseCodes.put('G', 0x50);
|
||||
houseCodes.put('H', 0xD0);
|
||||
houseCodes.put('I', 0x70);
|
||||
houseCodes.put('J', 0xF0);
|
||||
houseCodes.put('K', 0x30);
|
||||
houseCodes.put('L', 0xB0);
|
||||
houseCodes.put('M', 0x00);
|
||||
houseCodes.put('N', 0x80);
|
||||
houseCodes.put('O', 0x40);
|
||||
houseCodes.put('P', 0xC0);
|
||||
|
||||
HOUSE_CODES = Collections.unmodifiableMap(houseCodes);
|
||||
|
||||
HashMap<Integer, Integer> deviceCodes = new HashMap<>(16);
|
||||
deviceCodes.put(1, 0x06);
|
||||
deviceCodes.put(2, 0x0E);
|
||||
deviceCodes.put(3, 0x02);
|
||||
deviceCodes.put(4, 0x0A);
|
||||
deviceCodes.put(5, 0x01);
|
||||
deviceCodes.put(6, 0x09);
|
||||
deviceCodes.put(7, 0x05);
|
||||
deviceCodes.put(8, 0x0D);
|
||||
deviceCodes.put(9, 0x07);
|
||||
deviceCodes.put(10, 0x0F);
|
||||
deviceCodes.put(11, 0x03);
|
||||
deviceCodes.put(12, 0x0B);
|
||||
deviceCodes.put(13, 0x00);
|
||||
deviceCodes.put(14, 0x08);
|
||||
deviceCodes.put(15, 0x04);
|
||||
deviceCodes.put(16, 0x0C);
|
||||
|
||||
DEVICE_CODES = Collections.unmodifiableMap(deviceCodes);
|
||||
}
|
||||
|
||||
// Constants that control the interaction with the hardware
|
||||
static final int IO_PORT_OPEN_TIMEOUT = 5000;
|
||||
|
||||
/**
|
||||
* Number of CM11a dim increments for dimable devices
|
||||
*/
|
||||
static final int CM11A_DIM_INCREMENTS = 22;
|
||||
|
||||
/**
|
||||
* How long to wait between attempts to reconnect to the interface. (ms)
|
||||
*/
|
||||
static final int IO_RECONNECT_INTERVAL = 5000;
|
||||
/**
|
||||
* Maximum number of times to retry sending a bit of data when checksum errors occur.
|
||||
*/
|
||||
static final int IO_MAX_SEND_RETRY_COUNT = 5;
|
||||
|
||||
static final int SERIAL_TIMEOUT_MSEC = 4000;
|
||||
|
||||
// Hardware IO attributes
|
||||
protected CommPortIdentifier portId;
|
||||
protected SerialPort serialPort;
|
||||
protected boolean connected = false;
|
||||
protected DataOutputStream serialOutput;
|
||||
protected OutputStream serialOutputStr;
|
||||
protected DataInputStream serialInput;
|
||||
protected InputStream serialInputStr;
|
||||
|
||||
// Listeners that are to be notified when new data is received from the cm11a
|
||||
private List<ReceivedDataListener> receiveListeners = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Flag to indicate that background thread should be killed. Used to deactivate plugin.
|
||||
*/
|
||||
protected volatile boolean killThread = false;
|
||||
|
||||
// Scheduling attributes
|
||||
/**
|
||||
* Queue of as-yet un-actioned requests.
|
||||
*/
|
||||
protected BlockingQueue<Cm11aAbstractHandler> deviceUpdateQueue = new ArrayBlockingQueue<>(256);
|
||||
|
||||
// Need to keep last addresses found for data that comes in over the serial interface because if the incoming
|
||||
// command is a dim or bright the address isn't included. In addition some controllers will send the address
|
||||
// in one message and the function in a second one
|
||||
private List<String> lastAddresses;
|
||||
|
||||
/**
|
||||
* Need to have access to BridgeHandler so it's status can be updated.
|
||||
*/
|
||||
private Cm11aBridgeHandler bridgeHandler;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param serialPort serial port device. e.g. /dev/ttyS0
|
||||
* @throws NoSuchPortException
|
||||
*
|
||||
*/
|
||||
public X10Interface(String serialPort, Cm11aBridgeHandler bridgeHandler) throws NoSuchPortException {
|
||||
super();
|
||||
logger.trace("**** Constructing X10Interface for serial port: {} *******", serialPort);
|
||||
portId = CommPortIdentifier.getPortIdentifier(serialPort);
|
||||
this.bridgeHandler = bridgeHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes a serial connection to the hardware, if one is not already established.
|
||||
*/
|
||||
protected boolean connect() {
|
||||
if (!connected) {
|
||||
if (serialPort != null) {
|
||||
logger.trace("Closing stale serialPort object before reconnecting");
|
||||
serialPort.close();
|
||||
}
|
||||
logger.debug("Connecting to X10 hardware on serial port: {}", portId.getName());
|
||||
try {
|
||||
serialPort = portId.open("Openhab CM11A Binding", IO_PORT_OPEN_TIMEOUT);
|
||||
serialPort.setSerialPortParams(4800, SerialPort.DATABITS_8, SerialPort.STOPBITS_1,
|
||||
SerialPort.PARITY_NONE);
|
||||
serialPort.setFlowControlMode(SerialPort.FLOWCONTROL_NONE);
|
||||
serialPort.disableReceiveTimeout();
|
||||
serialPort.enableReceiveThreshold(1);
|
||||
|
||||
serialOutputStr = serialPort.getOutputStream();
|
||||
serialOutput = new DataOutputStream(serialOutputStr);
|
||||
serialInputStr = serialPort.getInputStream();
|
||||
serialInput = new DataInputStream(serialInputStr);
|
||||
|
||||
serialPort.addEventListener(this);
|
||||
connected = true;
|
||||
|
||||
serialPort.notifyOnDataAvailable(true);
|
||||
serialPort.notifyOnRingIndicator(true);
|
||||
|
||||
// Bob Raker - add serial timeout to prevent possible hang conditions
|
||||
serialPort.enableReceiveTimeout(SERIAL_TIMEOUT_MSEC);
|
||||
if (!serialPort.isReceiveTimeoutEnabled()) {
|
||||
logger.info("Serial receive timeout not supported by this driver.");
|
||||
}
|
||||
|
||||
bridgeHandler.changeBridgeStatusToUp();
|
||||
} catch (PortInUseException e) {
|
||||
String message = String.format("Serial port %s is in use by another application (%s)", portId.getName(),
|
||||
e.currentOwner);
|
||||
logger.warn("{}", message);
|
||||
bridgeHandler.changeBridgeStatusToDown(message);
|
||||
} catch (UnsupportedCommOperationException e) {
|
||||
logger.warn("Serial port {} doesn't support the required baud/parity/stopbits or Timeout",
|
||||
portId.getName());
|
||||
} catch (IOException e) {
|
||||
logger.warn("IO Problem with serial port {} . {}", portId.getName(), e.getMessage());
|
||||
} catch (TooManyListenersException e) {
|
||||
logger.warn(
|
||||
"TooManyListeners error when trying to connect to serial port. Interface is unlikely to work, raise a bug report.",
|
||||
e);
|
||||
bridgeHandler.changeBridgeStatusToDown("ToManyListenersException");
|
||||
}
|
||||
} else {
|
||||
logger.trace("Already connected to hardware, skipping reconnection.");
|
||||
}
|
||||
|
||||
return connected;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transmits a standard (non-extended) X10 function.
|
||||
*
|
||||
* @param address
|
||||
* @param function
|
||||
* @param dims 0-22, number of dims/brights to send.
|
||||
* @return true if update was successful
|
||||
* @throws InvalidAddressException
|
||||
* @throws IOException
|
||||
*/
|
||||
public boolean sendFunction(String address, int function, int dims) throws InvalidAddressException, IOException {
|
||||
boolean success = false;
|
||||
|
||||
if (!validateAddress(address)) {
|
||||
throw new InvalidAddressException("Address " + address + " is not a valid X10 address");
|
||||
}
|
||||
|
||||
int houseCode = HOUSE_CODES.get(address.charAt(0));
|
||||
int deviceCode = DEVICE_CODES.get(Integer.parseInt(address.substring(1)));
|
||||
|
||||
int[] data = new int[2];
|
||||
|
||||
if (connect()) {
|
||||
synchronized (serialPort) {
|
||||
logger.trace("Sending a standard X10 function to device: {}", address);
|
||||
// First send address
|
||||
data[0] = HEAD;
|
||||
data[1] = houseCode | deviceCode;
|
||||
sendData(data);
|
||||
|
||||
// Now send function call
|
||||
data[0] = HEAD | HEAD_FUNC | (dims << 3);
|
||||
data[1] = houseCode | function;
|
||||
sendData(data);
|
||||
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that the given string is a valid X10 address. Returns true if this is the case.
|
||||
*
|
||||
* @param address
|
||||
* @return
|
||||
*/
|
||||
public static boolean validateAddress(String address) {
|
||||
return (!(address.length() < 2 || address.length() > 3
|
||||
|| !HOUSE_CODES.containsKey(new Character(address.charAt(0)))
|
||||
|| !DEVICE_CODES.containsKey(Integer.parseInt(address.substring(1)))));
|
||||
}
|
||||
|
||||
/**
|
||||
* Queues a standard (non-extended) X10 function for transmission.
|
||||
*
|
||||
* @param address
|
||||
* @param function
|
||||
* @return true for success
|
||||
* @throws InvalidAddressException
|
||||
* @throws IOException
|
||||
*/
|
||||
public boolean sendFunction(String address, int function) throws InvalidAddressException, IOException {
|
||||
return sendFunction(address, function, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add specified device into the queue for hardware updates.
|
||||
*
|
||||
* <p>
|
||||
* If device is already queued, it will be removed from queue and moved to the end.
|
||||
* </p>
|
||||
*
|
||||
* @param device
|
||||
*/
|
||||
public void scheduleHWUpdate(Cm11aAbstractHandler device) {
|
||||
deviceUpdateQueue.remove(device);
|
||||
if (!deviceUpdateQueue.offer(device)) {
|
||||
logger.warn(
|
||||
"X10 function call queue full. Too many outstanding commands. This command will be discarded");
|
||||
}
|
||||
logger.debug("Added item to cm11a queue for: {}", device.getThing().getLabel());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends data to the hardware and handles the checksuming and retry process.
|
||||
*
|
||||
* <p>
|
||||
* When applicable, method blocks until the data has actually been sent over the powerlines using X10
|
||||
* </p>
|
||||
*
|
||||
* @param data Data to be sent.
|
||||
* @throws IOException
|
||||
*/
|
||||
protected void sendData(int[] data) throws IOException {
|
||||
int calcChecksum = 0;
|
||||
int checksumResponse = -1;
|
||||
|
||||
// Calculate expected checksum:
|
||||
for (int i = 0; i < data.length; i++) {
|
||||
// Note that ints are signed in Java, but the checksum uses unsigned ints.
|
||||
// Hence some jiggery pokery to get an unsigned int from the data int.
|
||||
calcChecksum = (calcChecksum + (0x000000FF & (data[i]))) & 0x000000FF;
|
||||
logger.trace("Checksum calc: int {} = {}", i, Integer.toHexString(data[i]));
|
||||
}
|
||||
|
||||
if (connect()) {
|
||||
synchronized (serialPort) {
|
||||
long startTime = System.currentTimeMillis(); // do some timing analysis
|
||||
|
||||
// Stop background data listener as we want to have a dialogue with the interface here.
|
||||
serialPort.notifyOnDataAvailable(false);
|
||||
|
||||
// Need to catch possible EOF exception if there is a timeout
|
||||
try {
|
||||
int retryCount = 0;
|
||||
while (checksumResponse != calcChecksum) {
|
||||
retryCount++;
|
||||
for (int i = 0; i < data.length; i++) {
|
||||
serialOutput.write(data[i]);
|
||||
serialOutput.flush();
|
||||
}
|
||||
long sendTime = System.currentTimeMillis();
|
||||
logger.trace("Sent the following data out the serial port in {} msec, {}",
|
||||
(sendTime - startTime), Arrays.toString(data));
|
||||
checksumResponse = serialInput.readUnsignedByte();
|
||||
logger.trace("Attempted to send data, try number: {} Checksum expected: {} received: {}",
|
||||
retryCount, Integer.toHexString(calcChecksum), Integer.toHexString(checksumResponse));
|
||||
long ckSumTime = System.currentTimeMillis();
|
||||
logger.trace("Received serial port check sum in {} msec", (ckSumTime - sendTime));
|
||||
|
||||
if (checksumResponse != calcChecksum) {
|
||||
// On initial device power up, nothing works until we set the clock. Check to see if the
|
||||
// unexpected data was actually a request from interface to PC.
|
||||
processRequestFromIFace(checksumResponse);
|
||||
|
||||
if (retryCount > IO_MAX_SEND_RETRY_COUNT) {
|
||||
logger.warn("Failed to send data to X10 hardware due to too many checksum failures");
|
||||
serialPort.notifyOnDataAvailable(true);
|
||||
throw new IOException("Max retries exceeded");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logger.trace(
|
||||
"Data transmission to interface was successful, sending ACK. X10 transmission over powerline will now commence.");
|
||||
long ackTime = System.currentTimeMillis();
|
||||
serialOutput.write(CHECKSUM_ACK);
|
||||
serialOutput.flush();
|
||||
|
||||
int response = serialInput.readUnsignedByte();
|
||||
if (response == IF_READY) {
|
||||
long cmpltdTime = System.currentTimeMillis();
|
||||
logger.trace("Serial port X10 ACK completed in {} msec, TOTAL X10 TRANSMISSION TIME in {} ms",
|
||||
(cmpltdTime - ackTime), (cmpltdTime - startTime));
|
||||
} else {
|
||||
logger.warn("Expected IF_READY ({}) response from hardware but received: {} instead",
|
||||
Integer.toHexString(IF_READY & 0x00000FF), Integer.toHexString(response & 0x00000FF));
|
||||
}
|
||||
} catch (EOFException ex) {
|
||||
logger.warn(
|
||||
"Received EOF exception while sending X10 command after {} ms. Make sure the cm11a is connected to the serial port",
|
||||
(System.currentTimeMillis() - startTime));
|
||||
}
|
||||
serialPort.notifyOnDataAvailable(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
logger.trace("Starting X10Interface background thread...");
|
||||
|
||||
// call connect so any X10 events on the powerline will be picked up by the serialEvent listener
|
||||
connect();
|
||||
|
||||
while (!killThread) {
|
||||
try {
|
||||
Cm11aAbstractHandler nextModule;
|
||||
logger.trace("Getting next module to be updated");
|
||||
nextModule = deviceUpdateQueue.take();
|
||||
logger.trace("Got a device. Going to run it.");
|
||||
|
||||
// Keep retrying to update this device until it is successful.
|
||||
updateHardware(nextModule);
|
||||
} catch (InterruptedException e1) {
|
||||
Thread.currentThread().interrupt(); // See https://www.ibm.com/developerworks/library/j-jtp05236/ for
|
||||
// discussion of resetting interrupt flag
|
||||
logger.warn("Unexpected interrupt on X10 scheduling thread. Ignoring and continuing anyway...");
|
||||
}
|
||||
}
|
||||
logger.trace("Stopping background thread...");
|
||||
this.notifyAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform Hardware update. Keep trying until it is successful
|
||||
*
|
||||
* @param nextModule - Next module that needs to be updated
|
||||
* @throws InterruptedException
|
||||
*/
|
||||
private void updateHardware(Cm11aAbstractHandler nextModule) throws InterruptedException {
|
||||
boolean success = false;
|
||||
while (!success) {
|
||||
try {
|
||||
if (connect()) {
|
||||
nextModule.updateHardware(this);
|
||||
success = true;
|
||||
} else {
|
||||
Thread.sleep(IO_RECONNECT_INTERVAL);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
connected = false;
|
||||
String message = "IO Exception when updating module hardware. Will retry shortly";
|
||||
logger.warn(message, e);
|
||||
bridgeHandler.changeBridgeStatusToDown(message);
|
||||
Thread.sleep(IO_RECONNECT_INTERVAL);
|
||||
} catch (InvalidAddressException e) {
|
||||
logger.warn("Attempted to send an X10 Function call with invalid address. Ignoring this.");
|
||||
success = true; // Pretend this was successful as retrying will be pointless.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serialEvent(SerialPortEvent event) {
|
||||
try {
|
||||
if (event.getEventType() == SerialPortEvent.DATA_AVAILABLE || event.getEventType() == SerialPortEvent.RI) {
|
||||
synchronized (serialPort) {
|
||||
logger.trace("Serial port data available or RI indicator event received");
|
||||
while (serialPort.isRI() && serialInput.available() <= 0) {
|
||||
logger.trace("Ring indicator is High but there is no data. Waiting for data...");
|
||||
try {
|
||||
Thread.sleep(20);
|
||||
} catch (InterruptedException e) {
|
||||
logger.debug("Interrupted while sleeping", e);
|
||||
}
|
||||
}
|
||||
|
||||
while (serialInput.available() > 0) {
|
||||
logger.trace("{} bytes of data available to be read", serialInput.available());
|
||||
int readint = serialInput.read();
|
||||
|
||||
processRequestFromIFace(readint);
|
||||
|
||||
// Wait a while before rechecking to give interface time to switch off Ring Indicator.
|
||||
while (serialPort.isRI() && serialInput.available() <= 0) {
|
||||
logger.trace("Ring indicator is High but there is no data. Waiting for data...");
|
||||
try {
|
||||
Thread.sleep(300);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
// Reset the flag and continue
|
||||
}
|
||||
}
|
||||
}
|
||||
logger.trace(
|
||||
"Reading data from interface complete. Ring Indicator has cleared and no data is left to read.");
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.warn("IO Exception in serial port handler callback: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a request made from the interface to the PC. Only handles requests initiated by the interface,
|
||||
* not those that form part of a conversation triggered by the PC.
|
||||
*
|
||||
* @param readint
|
||||
* @throws IOException
|
||||
*/
|
||||
protected void processRequestFromIFace(int readint) throws IOException {
|
||||
switch (readint) {
|
||||
case CLOCK_SET_REQ:
|
||||
setClock();
|
||||
break;
|
||||
case DATA_READY_REQ:
|
||||
receiveCommandData();
|
||||
break;
|
||||
case INPUT_FILTER_FAIL_REQ:
|
||||
serialOutput.write(DATA_READY_HEAD);
|
||||
logger.warn(
|
||||
"X10 Interface has indicated that the filter and/or surge protection in the device has failed.");
|
||||
break;
|
||||
default:
|
||||
logger.warn("Unexpected data received from X10 interface: {}", Integer.toHexString(readint));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the internal clock on the X10 interface
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
private void setClock() throws IOException {
|
||||
logger.debug("Setting clock in X10 interface");
|
||||
Calendar cal = Calendar.getInstance();
|
||||
|
||||
int[] clockData = new int[7];
|
||||
clockData[0] = CLOCK_SET_HEAD;
|
||||
clockData[1] = cal.get(Calendar.SECOND);
|
||||
clockData[2] = cal.get(Calendar.MINUTE) + (cal.get(Calendar.HOUR) % 2) * 60;
|
||||
clockData[3] = cal.get(Calendar.HOUR_OF_DAY) / 2;
|
||||
// Note: Calendar.DAY_OF_YEAR starts at 1 but the C language tm_yday starts at 0. Need 0 based DAY_OF_YEAR for
|
||||
// cm11a
|
||||
clockData[4] = (cal.get(Calendar.DAY_OF_YEAR) - 1) % 256;
|
||||
// Note: Calendar.DAY_OF_WEEK is 1 based and need 0 based (i.e. Sunday = 1
|
||||
clockData[5] = (((cal.get(Calendar.DAY_OF_YEAR - 1)) / 256) << 7)
|
||||
| (0x01 << (cal.get(Calendar.DAY_OF_WEEK) - 1));
|
||||
|
||||
// There are other flags in this final byte to do with clearing timer, battery timer and monitored status.
|
||||
// I've no idea what they are, so have left them unset.
|
||||
clockData[6] = MONITORED_HOUSE_CODE;
|
||||
|
||||
sendData(clockData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process data that the X10 interface is waiting to send to the PC
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
private void receiveCommandData() throws IOException {
|
||||
logger.debug("Receiving X10 data from interface");
|
||||
|
||||
// Send acknowledgement to interface
|
||||
serialOutput.write(DATA_READY_HEAD);
|
||||
|
||||
// Read the buffer size
|
||||
// There might be several DATA_READY_REQ bytes which need to be ignored
|
||||
int length = 0;
|
||||
do {
|
||||
length = serialInput.read();
|
||||
} while (length == DATA_READY_REQ || length > 8); // if the length is >8 this isn't a valid length so ignore
|
||||
|
||||
// Next read the Function / Address Mask byte
|
||||
int mask = serialInput.read();
|
||||
length--; // This read counts as part of the length
|
||||
logger.debug("Receiving [{}] bytes, Addr/Func mask: {}", length, Integer.toBinaryString(mask));
|
||||
|
||||
// It is possible for the cm11a buffer to be empty in which case no processing can take place
|
||||
if (length > 0) {
|
||||
int[] data = new int[length];
|
||||
for (int i = 0; i < length; i++) {
|
||||
int recvByte = serialInputStr.read();
|
||||
data[i] = recvByte;
|
||||
logger.debug(" Received X10 data [{}]: {}", i, Integer.toHexString(recvByte));
|
||||
}
|
||||
|
||||
processCommandData(mask, data);
|
||||
} else {
|
||||
logger.warn("cm11a buffer was overrun. Any pending commands will be ignorred until the buffer clears.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process raw data received from the interface and convert into human readable data
|
||||
*
|
||||
* @param mask Function / Address Mask. Each bit corresponds to the contents of a data. A 0 bit
|
||||
* corresponds to an address. A 1 bit corresponds to a function.
|
||||
* @param data
|
||||
*/
|
||||
private void processCommandData(int mask, int[] data) {
|
||||
int localMask = mask;
|
||||
X10ReceivedData.X10COMMAND command = X10ReceivedData.X10COMMAND.UNDEF; // Just set it to something
|
||||
List<String> addresses = new ArrayList<>();
|
||||
int dims = 0;
|
||||
List<X10ReceivedData> rcvData = new ArrayList<>();
|
||||
|
||||
for (int i = 0; i < data.length; i++) {
|
||||
int d = data[i];
|
||||
int dataType = localMask & 0x01;
|
||||
if (dataType == 0) {
|
||||
// The data byte is an address
|
||||
int houseIndex = (d >> 4) & 0x0f;
|
||||
int unitIndex = d & 0x0f;
|
||||
addresses.add(Character.toString(X10ReceivedData.HOUSE_CODE[houseIndex])
|
||||
+ Integer.toString(X10ReceivedData.UNIT_CODE[unitIndex]));
|
||||
} else {
|
||||
// The data byte is a function
|
||||
command = X10ReceivedData.COMMAND_MAP.get(d & 0x0f);
|
||||
if (command == null) {
|
||||
command = X10ReceivedData.X10COMMAND.UNDEF;
|
||||
} else if (command == X10ReceivedData.X10COMMAND.BRIGHT || command == X10ReceivedData.X10COMMAND.DIM) {
|
||||
// Have to read one more byte which is the dim level
|
||||
if (i < data.length) {
|
||||
dims = data[++i];
|
||||
// This dims is a number between 0 and 210 and is the CHANGE in brightness level
|
||||
// The interface transmits 1 to 22 dims to go from full bright to full dim
|
||||
// therefore this need to be converted to a number between 1 and 22. Always want to dim or
|
||||
// brighten one increment. The conversion is therefore:
|
||||
dims = (dims * CM11A_DIM_INCREMENTS) / 210;
|
||||
dims = dims > 0 ? dims : 1;
|
||||
}
|
||||
// Also in this case no addresses would have been sent so use the saved addresses
|
||||
// If there were no previous commands that specified an address then lastAddress will be null. In
|
||||
// this case we can't do anything with this dim request.
|
||||
if (lastAddresses == null) {
|
||||
logger.info(
|
||||
"cm11a received a dim command but there is no prior commands that included an address.");
|
||||
continue;
|
||||
}
|
||||
addresses = lastAddresses;
|
||||
} else if (command == X10ReceivedData.X10COMMAND.ALL_LIGHTS_OFF
|
||||
|| command == X10ReceivedData.X10COMMAND.ALL_LIGHTS_ON
|
||||
|| command == X10ReceivedData.X10COMMAND.ALL_UNITS_OFF) {
|
||||
logger.warn("cm11a received the command: {}. This command is ignored by this binding.", command);
|
||||
continue;
|
||||
} else {
|
||||
// A valid command must have been received.
|
||||
// As indicated above, some controllers send the address in one transmission and the function in a
|
||||
// second transaction.
|
||||
// Check if we have gotten an address in the transmission and if not use lastAddresses if they
|
||||
// are not null
|
||||
if (addresses.isEmpty()) {
|
||||
// No addresses were sent in this transmission, see if we can use lastAddresses
|
||||
if (lastAddresses == null) {
|
||||
logger.info("cm11a received a command but the transmission didn't include an address.");
|
||||
continue;
|
||||
} else {
|
||||
addresses = lastAddresses;
|
||||
logger.info(
|
||||
"cm11a received a command without any addresses. Addresses from a prior reception are available and will be used.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Every time we get a function it is the end of the transmission and we can bundle the data into an
|
||||
// X10ReceivedData object.
|
||||
X10ReceivedData rd = new X10ReceivedData(addresses.toArray(new String[0]), command, dims);
|
||||
rcvData.add(rd);
|
||||
logger.debug("cm11a: Added received data to queue: {}", rd.toString());
|
||||
// reset the data objects for the next thing in the buffer, if any
|
||||
command = X10ReceivedData.X10COMMAND.UNDEF;
|
||||
// Collections.copy(lastAddresses, addresses);
|
||||
lastAddresses = addresses;
|
||||
addresses = new ArrayList<>();
|
||||
dims = 0;
|
||||
}
|
||||
|
||||
localMask = localMask >> 1;
|
||||
}
|
||||
|
||||
// Done processing buffer from cm11a. Notify interested parties about the data
|
||||
for (X10ReceivedData rd : rcvData) {
|
||||
logger.debug("cm11a: Converted received data to human form: {}", rd.toString());
|
||||
notifyReceiveListeners(rd);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by classes that want to be notified when data has been received from the cm11a
|
||||
*
|
||||
* @param listener The class to be called
|
||||
*/
|
||||
public void addReceivedDataListener(ReceivedDataListener listener) {
|
||||
receiveListeners.add(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to notify classes that data has been received
|
||||
*
|
||||
* @param rcv
|
||||
*/
|
||||
private void notifyReceiveListeners(X10ReceivedData rcv) {
|
||||
for (ReceivedDataListener rl : receiveListeners) {
|
||||
rl.receivedX10Data(rcv);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnect from hardware
|
||||
*/
|
||||
public void disconnect() {
|
||||
// Kill the thread that performs the hardware updates
|
||||
killThread = true;
|
||||
|
||||
if (serialInput != null) {
|
||||
try {
|
||||
serialInput.close();
|
||||
} catch (IOException e) {
|
||||
// nothing to do if there is an issue closing the stream.
|
||||
}
|
||||
}
|
||||
if (serialOutput != null) {
|
||||
try {
|
||||
serialOutput.close();
|
||||
} catch (IOException e) {
|
||||
// nothing to do if there is an issue closing the stream.
|
||||
}
|
||||
}
|
||||
|
||||
if (serialPort != null) {
|
||||
serialPort.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
/**
|
||||
* 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.cm11a.internal;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Container for data received from the powerline by the cm11a interface. When data is received one of more of these
|
||||
* objects is created and then passed to interested objects.
|
||||
*
|
||||
* @author Bob Raker - Initial contribution
|
||||
*/
|
||||
|
||||
public class X10ReceivedData {
|
||||
|
||||
/**
|
||||
* All of the possible X10 commands
|
||||
*
|
||||
*/
|
||||
public enum X10COMMAND {
|
||||
ALL_UNITS_OFF,
|
||||
ALL_LIGHTS_ON,
|
||||
ON,
|
||||
OFF,
|
||||
DIM,
|
||||
BRIGHT,
|
||||
ALL_LIGHTS_OFF,
|
||||
EXTENDED_CODE,
|
||||
HAIL_REQ,
|
||||
HAIL_ACK,
|
||||
PRESET_DIM_1,
|
||||
PRESET_DIM_2,
|
||||
EXTD_DATA_XFER,
|
||||
STATUS_ON,
|
||||
STATUS_OFF,
|
||||
STATUS_REQ,
|
||||
UNDEF // If no match, which shouldn't happen
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to decode the function bits received from the cm11a into an X10 function code
|
||||
*
|
||||
*/
|
||||
protected static final Map<Integer, X10COMMAND> COMMAND_MAP;
|
||||
static {
|
||||
Map<Integer, X10COMMAND> tempMap = new HashMap<>();
|
||||
tempMap.put(0, X10COMMAND.ALL_UNITS_OFF);
|
||||
tempMap.put(1, X10COMMAND.ALL_LIGHTS_ON);
|
||||
tempMap.put(2, X10COMMAND.ON);
|
||||
tempMap.put(3, X10COMMAND.OFF);
|
||||
tempMap.put(4, X10COMMAND.DIM);
|
||||
tempMap.put(5, X10COMMAND.BRIGHT);
|
||||
tempMap.put(6, X10COMMAND.ALL_LIGHTS_OFF);
|
||||
tempMap.put(7, X10COMMAND.EXTENDED_CODE);
|
||||
tempMap.put(8, X10COMMAND.HAIL_REQ);
|
||||
tempMap.put(9, X10COMMAND.HAIL_ACK);
|
||||
tempMap.put(10, X10COMMAND.PRESET_DIM_1);
|
||||
tempMap.put(11, X10COMMAND.PRESET_DIM_2);
|
||||
tempMap.put(12, X10COMMAND.EXTD_DATA_XFER);
|
||||
tempMap.put(13, X10COMMAND.STATUS_ON);
|
||||
tempMap.put(14, X10COMMAND.STATUS_OFF);
|
||||
tempMap.put(15, X10COMMAND.STATUS_REQ);
|
||||
COMMAND_MAP = Collections.unmodifiableMap(tempMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup table to convert House code received from the cm11a into an X10 house code
|
||||
*/
|
||||
public static final char HOUSE_CODE[] = new char[] { 'M', 'E', 'C', 'K', 'O', 'G', 'A', 'I', 'N', 'F', 'D', 'L',
|
||||
'P', 'H', 'B', 'J' };
|
||||
|
||||
/**
|
||||
* Lookup table to convert Unit code received from the cm11a into an X10 unit code
|
||||
*/
|
||||
public static final byte UNIT_CODE[] = new byte[] { 13, 5, 3, 11, 15, 7, 1, 9, 14, 6, 4, 12, 16, 8, 2, 10 };
|
||||
|
||||
private String[] addr;
|
||||
private X10COMMAND cmd;
|
||||
private int dims;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public X10ReceivedData(String[] addr, X10COMMAND cmd, int dims) {
|
||||
this.addr = addr;
|
||||
this.cmd = cmd;
|
||||
this.dims = dims;
|
||||
}
|
||||
|
||||
public String[] getAddr() {
|
||||
return addr;
|
||||
}
|
||||
|
||||
public X10COMMAND getCmd() {
|
||||
return cmd;
|
||||
}
|
||||
|
||||
public int getDims() {
|
||||
return dims;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "X10ReceivedData [addr=" + Arrays.toString(addr) + ", cmd=" + cmd + ", dims=" + dims + "]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + Arrays.hashCode(addr);
|
||||
result = prime * result + ((cmd == null) ? 0 : cmd.hashCode());
|
||||
result = prime * result + dims;
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
X10ReceivedData other = (X10ReceivedData) obj;
|
||||
if (!Arrays.equals(addr, other.addr)) {
|
||||
return false;
|
||||
}
|
||||
if (cmd != other.cmd) {
|
||||
return false;
|
||||
}
|
||||
if (dims != other.dims) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* 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.cm11a.internal.config;
|
||||
|
||||
/**
|
||||
* Configuration constants for Cm11A
|
||||
*
|
||||
* @author Bob Raker - Initial contribution
|
||||
*
|
||||
*/
|
||||
public class Cm11aConfig {
|
||||
|
||||
/**
|
||||
* Serial port of the CM11A device.
|
||||
*/
|
||||
public String serialPort = null;
|
||||
|
||||
/**
|
||||
* refresh rate - I don't think we will need this
|
||||
*/
|
||||
public int refresh = 60;
|
||||
}
|
||||
@@ -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.cm11a.internal.handler;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.openhab.binding.cm11a.internal.InvalidAddressException;
|
||||
import org.openhab.binding.cm11a.internal.X10Interface;
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
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.ThingStatusInfo;
|
||||
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||
import org.openhab.core.types.State;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* This is an abstract base class for the "Thing" handlers (i.e. Cm11aApplianceHandler and Cm11aLampHandler).
|
||||
* It is not used by the Bridge handler (Cm11aHandler)
|
||||
*
|
||||
* @author Bob Raker - Initial contribution
|
||||
*
|
||||
*/
|
||||
public abstract class Cm11aAbstractHandler extends BaseThingHandler {
|
||||
|
||||
/**
|
||||
* The House and Unit codes set on the module, i.e. A1, J14
|
||||
*/
|
||||
protected String houseUnitCode;
|
||||
|
||||
/**
|
||||
* The X10 function
|
||||
*/
|
||||
protected int x10Function;
|
||||
|
||||
/**
|
||||
* The channel ID
|
||||
*/
|
||||
protected ChannelUID channelUID;
|
||||
|
||||
/**
|
||||
* The current State of the device
|
||||
*/
|
||||
protected State currentState;
|
||||
|
||||
/**
|
||||
* Number of CM11a dim increments for dimable devices
|
||||
*/
|
||||
static final int X10_DIM_INCREMENTS = 22;
|
||||
static final String HOUSE_UNIT_CODE = "houseUnitCode";
|
||||
static final String NO_BRIDGE_ERROR = "No bridge found using house unit code ";
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(Cm11aAbstractHandler.class);
|
||||
|
||||
/**
|
||||
* The construction
|
||||
*
|
||||
* @param thing The "Thing" to be handled
|
||||
*/
|
||||
public Cm11aAbstractHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
Configuration config = thing.getConfiguration();
|
||||
|
||||
houseUnitCode = (String) config.get(HOUSE_UNIT_CODE);
|
||||
Bridge bridge = getBridge();
|
||||
if (bridge == null) {
|
||||
logger.warn("{}", NO_BRIDGE_ERROR + houseUnitCode);
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, NO_BRIDGE_ERROR + houseUnitCode);
|
||||
return;
|
||||
}
|
||||
|
||||
if (ThingStatus.ONLINE.equals(bridge.getStatus())) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
houseUnitCode = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
|
||||
logger.debug("CM11A status changed to {}.", bridgeStatusInfo.getStatus());
|
||||
|
||||
if (bridgeStatusInfo.getStatus() != ThingStatus.ONLINE) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
|
||||
logger.debug("CM11A is not online. Bridge status: {}", bridgeStatusInfo.getStatus());
|
||||
return;
|
||||
}
|
||||
|
||||
if (bridgeStatusInfo.getStatus() == ThingStatus.ONLINE) {
|
||||
if (houseUnitCode.length() > 0) {
|
||||
// The config must be present and was set during initialization
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Will be called by the X10Interface when it is ready for this X10 device to use the X10 bus.
|
||||
* Child classes should override this method with the specific process necessary to update the
|
||||
* hardware with the latest data.
|
||||
*
|
||||
* <p>
|
||||
* Warning: This will be called in a different thread. It must be thread safe.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Retries in the event of interface problems will be handled by the X10Interface. If a comms
|
||||
* problem occurs and the method throws an exception, this device will be rescheduled again later.
|
||||
* </p>
|
||||
*/
|
||||
public abstract void updateHardware(X10Interface x10Interface) throws IOException, InvalidAddressException;
|
||||
|
||||
public State getCurrentState() {
|
||||
return currentState;
|
||||
}
|
||||
|
||||
public void setCurrentState(State currentState) {
|
||||
this.currentState = currentState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Subtract the specified number of X10 "dims" from the current state
|
||||
*
|
||||
* @param dims The number of dims to remove
|
||||
* @return The updated current state
|
||||
*/
|
||||
public State addDimsToCurrentState(int dims) {
|
||||
if (!(currentState instanceof PercentType)) {
|
||||
if (currentState instanceof OnOffType && currentState == OnOffType.ON) {
|
||||
currentState = PercentType.HUNDRED;
|
||||
} else {
|
||||
currentState = PercentType.ZERO;
|
||||
}
|
||||
}
|
||||
// The current state is stored in a PercentType object and therefore needs to be converted to an incremental
|
||||
// percent
|
||||
int curPercent = ((PercentType) currentState).intValue();
|
||||
// dims is a number between 0 and 22 which represents the full range of cm11a
|
||||
int dimsPercent = (dims * 100) / X10_DIM_INCREMENTS;
|
||||
int newPercent = curPercent - dimsPercent;
|
||||
newPercent = Math.max(newPercent, 0);
|
||||
newPercent = Math.min(newPercent, 100);
|
||||
currentState = new PercentType(newPercent);
|
||||
return currentState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the specified number of X10 "dims" from the current state
|
||||
*
|
||||
* @param dims The number of dims to remove
|
||||
* @return The updated current state
|
||||
*/
|
||||
public State addBrightsToCurrentState(int dims) {
|
||||
return addDimsToCurrentState(-dims);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
* 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.cm11a.internal.handler;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.openhab.binding.cm11a.internal.InvalidAddressException;
|
||||
import org.openhab.binding.cm11a.internal.X10Interface;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
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.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Handler for Appliance (also called Switch) modules. These modules only support ON and OFF states
|
||||
*
|
||||
* @author Bob Raker - Initial contribution
|
||||
*
|
||||
*/
|
||||
public class Cm11aApplianceHandler extends Cm11aAbstractHandler {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(Cm11aApplianceHandler.class);
|
||||
|
||||
private State desiredState = UnDefType.UNDEF;
|
||||
|
||||
public Cm11aApplianceHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
logger.debug("**** Cm11aApplianceHandler handleCommand command = {}, channelUID = {}", command,
|
||||
channelUID.getAsString());
|
||||
|
||||
x10Function = 0;
|
||||
Bridge bridge = getBridge();
|
||||
if (bridge == null) {
|
||||
logger.debug("Unable to handle command. Bridge is null.");
|
||||
return;
|
||||
}
|
||||
this.channelUID = channelUID;
|
||||
|
||||
Cm11aBridgeHandler cm11aHandler = (Cm11aBridgeHandler) bridge.getHandler();
|
||||
if (cm11aHandler != null && cm11aHandler.getThing().getStatus().equals(ThingStatus.ONLINE)) {
|
||||
if (command == OnOffType.ON) {
|
||||
x10Function = X10Interface.FUNC_ON;
|
||||
desiredState = OnOffType.ON;
|
||||
} else if (command == OnOffType.OFF) {
|
||||
x10Function = X10Interface.FUNC_OFF;
|
||||
desiredState = OnOffType.OFF;
|
||||
} else if (command instanceof RefreshType) {
|
||||
x10Function = X10Interface.FUNC_OFF;
|
||||
desiredState = OnOffType.OFF;
|
||||
logger.info("Received REFRESH command for switch {}", houseUnitCode);
|
||||
}
|
||||
|
||||
if (x10Function > 0) {
|
||||
X10Interface x10Interface = cm11aHandler.getX10Interface();
|
||||
x10Interface.scheduleHWUpdate(this);
|
||||
} else {
|
||||
logger.debug("Received invalid command for switch {} command: {}", houseUnitCode, command);
|
||||
}
|
||||
} else {
|
||||
logger.debug("Attempted to change switch to {} for {} because the cm11a is not online", command,
|
||||
houseUnitCode);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateHardware(X10Interface x10Interface) throws IOException, InvalidAddressException {
|
||||
if (!desiredState.equals(currentState)) {
|
||||
if (x10Interface.sendFunction(houseUnitCode, x10Function)) {
|
||||
// Hardware update was successful so update openHAB
|
||||
updateState(channelUID, desiredState);
|
||||
setCurrentState(desiredState);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,259 @@
|
||||
/**
|
||||
* 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.cm11a.internal.handler;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.openhab.binding.cm11a.internal.X10Interface;
|
||||
import org.openhab.binding.cm11a.internal.X10ReceivedData;
|
||||
import org.openhab.binding.cm11a.internal.X10ReceivedData.X10COMMAND;
|
||||
import org.openhab.binding.cm11a.internal.config.Cm11aConfig;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.binding.BaseBridgeHandler;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.State;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import gnu.io.NoSuchPortException;
|
||||
|
||||
/**
|
||||
* The {@link Cm11aBridgeHandler} is the Bridge (see
|
||||
* https://openhab.org/documentation/development/bindings/bridge-handler.html
|
||||
* for a description of OpenHAB bridges. This gets called first and is responsible for setting up the handler. Mostly it
|
||||
* loads the 10Interface class which does all of the heavy lifting.
|
||||
*
|
||||
* @author Bob Raker - Initial contribution
|
||||
*/
|
||||
public class Cm11aBridgeHandler extends BaseBridgeHandler implements ReceivedDataListener {
|
||||
|
||||
private Cm11aConfig cm11aConfig;
|
||||
|
||||
private X10Interface x10Interface;
|
||||
|
||||
private Bridge bridge;
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(Cm11aBridgeHandler.class);
|
||||
|
||||
public Cm11aBridgeHandler(Bridge bridge) {
|
||||
super(bridge);
|
||||
this.bridge = bridge;
|
||||
}
|
||||
|
||||
public Cm11aConfig getCm11AConfig() {
|
||||
return this.cm11aConfig;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
// Commands are handled by the "Things" which include Cm11aSwitchHandler and Cm11aLampHandler
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
// Get serial port number from config
|
||||
cm11aConfig = getThing().getConfiguration().as(Cm11aConfig.class);
|
||||
logger.trace("********* cm11a initialize started *********");
|
||||
|
||||
// Verify the configuration is valid
|
||||
if (!validateConfig(this.cm11aConfig)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize the X10 interface
|
||||
try {
|
||||
x10Interface = new X10Interface(cm11aConfig.serialPort, this);
|
||||
x10Interface.setDaemon(true);
|
||||
x10Interface.start();
|
||||
x10Interface.addReceivedDataListener(this);
|
||||
logger.info("Initialized CM11A X10 interface on: {}", cm11aConfig.serialPort);
|
||||
} catch (NoSuchPortException e) {
|
||||
x10Interface = null;
|
||||
logger.error("No such port exists on this machine: {}", cm11aConfig.serialPort);
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"No such port exists on this machine: " + cm11aConfig.serialPort);
|
||||
return;
|
||||
}
|
||||
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
// Close the serial port
|
||||
if (x10Interface != null) {
|
||||
x10Interface.disconnect();
|
||||
x10Interface = null;
|
||||
}
|
||||
logger.debug("Cm11aBridgeHandler is being removed.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that the configuration is valid
|
||||
*
|
||||
* @param cm11aConfig2
|
||||
* @return
|
||||
*/
|
||||
private boolean validateConfig(Cm11aConfig config) {
|
||||
if (config == null) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "cm11a configuration missing");
|
||||
return false;
|
||||
}
|
||||
|
||||
String port = config.serialPort;
|
||||
if (port == null || port.isEmpty()) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "cm11a serialPort not specified");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see org.openhab.binding.cm11a.handler.ReceivedDataListener#receivedX10Data(org.openhab.binding.cm11a.internal.
|
||||
* X10ReceivedData)
|
||||
*/
|
||||
@Override
|
||||
public void receivedX10Data(X10ReceivedData rd) {
|
||||
logger.debug("Cm11aReceivedDataManager received the following data: {}", rd);
|
||||
|
||||
List<Thing> things = bridge.getThings();
|
||||
|
||||
// This block goes through the Things attached to this bridge and find the HouseUnitCode that matches what came
|
||||
// from the serial port. Then it looks at the channels in that thing and looks for a channel that ends with
|
||||
// "switchstatus" or "lightlevel"
|
||||
// which is the one that should be updated.
|
||||
synchronized (rd) {
|
||||
for (Thing thing : things) {
|
||||
String houseUnitCode = (String) thing.getConfiguration().get("houseUnitCode");
|
||||
for (String messageHouseUnitCode : rd.getAddr()) {
|
||||
if (messageHouseUnitCode.equals(houseUnitCode)) {
|
||||
// The channel we want should end in "switchstatus" or "lightlevel". In reality there is
|
||||
// probably only one channel since these things only define one channel
|
||||
ChannelUID desiredChannelUid = findX10Channel(thing.getChannels());
|
||||
if (desiredChannelUid == null) {
|
||||
return;
|
||||
}
|
||||
X10COMMAND cmd = rd.getCmd();
|
||||
int dims = rd.getDims();
|
||||
updateX10State(thing, desiredChannelUid, cmd, dims);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the X10 channel.
|
||||
*
|
||||
* @param channels
|
||||
* @return
|
||||
*/
|
||||
private ChannelUID findX10Channel(List<Channel> channels) {
|
||||
ChannelUID desiredChannelUid = null;
|
||||
for (Channel channel : channels) {
|
||||
if (channel.getUID().toString().endsWith("switchstatus")
|
||||
|| channel.getUID().toString().endsWith("lightlevel")) {
|
||||
desiredChannelUid = channel.getUID();
|
||||
break;
|
||||
}
|
||||
}
|
||||
return desiredChannelUid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the X10 state
|
||||
*
|
||||
* @param thing
|
||||
* @param channelUid
|
||||
* @param cmd
|
||||
* @param dims
|
||||
*/
|
||||
private void updateX10State(Thing thing, ChannelUID channelUid, X10COMMAND cmd, int dims) {
|
||||
if (thing == null) {
|
||||
logger.debug("Unable to update X10 state: thing is null");
|
||||
return;
|
||||
}
|
||||
|
||||
Cm11aAbstractHandler handler = (Cm11aAbstractHandler) thing.getHandler();
|
||||
if (handler == null) {
|
||||
logger.debug("Unable to update X10 state: handler is null");
|
||||
return;
|
||||
}
|
||||
|
||||
// Perform appropriate update based on X10Command received
|
||||
// Handle ON/OFF commands
|
||||
if (cmd == X10ReceivedData.X10COMMAND.ON) {
|
||||
handleUpdate(channelUid, OnOffType.ON);
|
||||
handler.setCurrentState(OnOffType.ON);
|
||||
} else if (cmd == X10ReceivedData.X10COMMAND.OFF) {
|
||||
handleUpdate(channelUid, OnOffType.OFF);
|
||||
handler.setCurrentState(OnOffType.OFF);
|
||||
// Handle DIM/Bright commands
|
||||
} else if (cmd == X10ReceivedData.X10COMMAND.DIM) {
|
||||
State newState = handler.addDimsToCurrentState(dims);
|
||||
handleUpdate(channelUid, newState);
|
||||
handler.setCurrentState(newState);
|
||||
logger.debug("Current state set to: {}", handler.getCurrentState().toFullString());
|
||||
} else if (cmd == X10ReceivedData.X10COMMAND.BRIGHT) {
|
||||
State newState = handler.addBrightsToCurrentState(dims);
|
||||
handleUpdate(channelUid, newState);
|
||||
handler.setCurrentState(newState);
|
||||
logger.debug("Current state set to: {}", handler.getCurrentState().toFullString());
|
||||
} else {
|
||||
logger.warn("Received unknown command from cm11a: {}", cmd);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The default implementation of this method doesn't do anything. We need it to update the ui as done by the
|
||||
* updateState function.
|
||||
* And, update state can not be called directly because it is protected
|
||||
*/
|
||||
@Override
|
||||
public void handleUpdate(ChannelUID channelUID, State newState) {
|
||||
updateState(channelUID, newState);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the X10Interface
|
||||
*
|
||||
* @return the X10Interface
|
||||
*/
|
||||
public X10Interface getX10Interface() {
|
||||
return x10Interface;
|
||||
}
|
||||
|
||||
public void changeBridgeStatusToUp() {
|
||||
if (getThing().getStatus().equals(ThingStatus.OFFLINE)) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
logger.debug("Changed the Bridge status to online because the serial interface is working again.");
|
||||
}
|
||||
}
|
||||
|
||||
public void changeBridgeStatusToDown(String message) {
|
||||
if (getThing().getStatus().equals(ThingStatus.ONLINE)) {
|
||||
// Bridge was online but the serial interface is now down
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, message);
|
||||
logger.debug("Changed the Bridge status to offline because {}.", message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
/**
|
||||
* 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.cm11a.internal.handler;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.openhab.binding.cm11a.internal.InvalidAddressException;
|
||||
import org.openhab.binding.cm11a.internal.X10Interface;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
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.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Handler for Lamp modules. These modules support ON, OFF and brightness level states
|
||||
*
|
||||
* @author Bob Raker - Initial contribution
|
||||
*
|
||||
*/
|
||||
public class Cm11aLampHandler extends Cm11aAbstractHandler {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(Cm11aLampHandler.class);
|
||||
|
||||
private State desiredState = UnDefType.UNDEF;
|
||||
|
||||
/**
|
||||
* Constructor for the Thing
|
||||
*
|
||||
* @param thing
|
||||
*/
|
||||
public Cm11aLampHandler(Thing thing) {
|
||||
super(thing);
|
||||
currentState = OnOffType.ON; // Assume it is on. During refresh it will be turned off and the currentState will
|
||||
// be updated appropriately
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
logger.debug("**** Cm11aLampHandler handleCommand command = {}, channelUID = {}", command.toString(),
|
||||
channelUID.getAsString());
|
||||
|
||||
x10Function = 0;
|
||||
Bridge bridge = getBridge();
|
||||
if (bridge == null) {
|
||||
logger.debug("Unable to handle command. Bridge is null.");
|
||||
return;
|
||||
}
|
||||
this.channelUID = channelUID;
|
||||
|
||||
Cm11aBridgeHandler cm11aHandler = (Cm11aBridgeHandler) bridge.getHandler();
|
||||
if (cm11aHandler != null && cm11aHandler.getThing().getStatus().equals(ThingStatus.ONLINE)) {
|
||||
if (OnOffType.ON.equals(command)) {
|
||||
desiredState = OnOffType.ON;
|
||||
} else if (OnOffType.OFF.equals(command)) {
|
||||
desiredState = OnOffType.OFF;
|
||||
} else if (command instanceof PercentType) {
|
||||
desiredState = (PercentType) command;
|
||||
} else if (command instanceof RefreshType) {
|
||||
// Refresh is triggered by framework during startup.
|
||||
// Force the lamp off by indicating it is currently on and we want it off
|
||||
desiredState = PercentType.ZERO; // Start with it off
|
||||
logger.info("Received REFRESH command for switch {}", houseUnitCode);
|
||||
} else {
|
||||
logger.error("Ignoring unknown command received for device: {}", houseUnitCode);
|
||||
}
|
||||
|
||||
if (!(desiredState instanceof UnDefType)) {
|
||||
X10Interface x10Interface = cm11aHandler.getX10Interface();
|
||||
x10Interface.scheduleHWUpdate(this);
|
||||
}
|
||||
} else {
|
||||
logger.error("Attempted to change switch {} cm11a is not online", houseUnitCode);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see org.openhab.binding.cm11a.handler.Cm11aAbstractHandler#updateHardware(org.openhab.binding.cm11a.internal.
|
||||
* X10Interface)
|
||||
*/
|
||||
@Override
|
||||
public void updateHardware(X10Interface x10Interface) throws IOException, InvalidAddressException {
|
||||
if (!desiredState.equals(currentState)) {
|
||||
boolean x10Status = false;
|
||||
if (desiredState.equals(OnOffType.ON)) {
|
||||
x10Status = x10Interface.sendFunction(houseUnitCode, X10Interface.FUNC_ON);
|
||||
} else if (desiredState.equals(OnOffType.OFF)) {
|
||||
x10Status = x10Interface.sendFunction(houseUnitCode, X10Interface.FUNC_OFF);
|
||||
} else if (desiredState instanceof PercentType) {
|
||||
// desiredState must be a PercentType if we got here.
|
||||
// Calc how many bright increments we need to send (0 to 22)
|
||||
int desiredPercentFullBright = ((PercentType) desiredState).intValue();
|
||||
int dims = (desiredPercentFullBright * X10_DIM_INCREMENTS) / 100;
|
||||
if (currentState.equals(OnOffType.ON)) {
|
||||
// The current level isn't known because it would have gone to
|
||||
// the same level as when last turned on. Need to go to full dim and then up to desired
|
||||
// level.
|
||||
x10Interface.sendFunction(houseUnitCode, X10Interface.FUNC_DIM, X10_DIM_INCREMENTS);
|
||||
x10Status = x10Interface.sendFunction(houseUnitCode, X10Interface.FUNC_BRIGHT, dims);
|
||||
} else if (currentState.equals(OnOffType.OFF)) {
|
||||
// desiredState must be a PercentType if we got here. And, the light should be off
|
||||
// We should just be able to send the appropriate number if dims
|
||||
x10Status = x10Interface.sendFunction(houseUnitCode, X10Interface.FUNC_BRIGHT, dims);
|
||||
} else if (currentState instanceof PercentType) {
|
||||
// This is the expected case
|
||||
// Now currentState and desiredState are both PercentType's
|
||||
// Need to calc how much to dim or brighten
|
||||
int currentPercentFullBright = ((PercentType) currentState).intValue();
|
||||
int percentToBrighten = desiredPercentFullBright - currentPercentFullBright;
|
||||
int brightens = (percentToBrighten * X10_DIM_INCREMENTS) / 100;
|
||||
if (brightens > 0) {
|
||||
x10Status = x10Interface.sendFunction(houseUnitCode, X10Interface.FUNC_BRIGHT, brightens);
|
||||
} else if (brightens < 0) {
|
||||
x10Status = x10Interface.sendFunction(houseUnitCode, X10Interface.FUNC_DIM, -brightens);
|
||||
}
|
||||
} else {
|
||||
// Current state is not as expected
|
||||
logger.warn("Starting state of dimmer was not as expected: {}", currentState.getClass().getName());
|
||||
}
|
||||
}
|
||||
|
||||
// Now the hardware should have been updated. If successful update the status
|
||||
if (x10Status) {
|
||||
// Hardware update was successful so update OpenHAB
|
||||
updateState(channelUID, desiredState);
|
||||
setCurrentState(desiredState);
|
||||
} else {
|
||||
// Hardware update failed, log
|
||||
logger.error("cm11a failed to update device: {}", houseUnitCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* 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.cm11a.internal.handler;
|
||||
|
||||
import org.openhab.binding.cm11a.internal.X10ReceivedData;
|
||||
|
||||
/**
|
||||
* Interface to support listening for data received by the cm11a. This needs to be sent to OpenHAB so it has the current
|
||||
* status of the modules
|
||||
*
|
||||
* @author Bob Raker - Initial contribution
|
||||
*
|
||||
*/
|
||||
public interface ReceivedDataListener {
|
||||
|
||||
/**
|
||||
* This is the method called when data is received from the cm11a
|
||||
*
|
||||
* @param rd
|
||||
*/
|
||||
void receivedX10Data(X10ReceivedData rd);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<binding:binding id="cm11a" 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>CM11A Binding</name>
|
||||
<description>This is the binding for the X10 Cm11a interface</description>
|
||||
<author>Bob Raker</author>
|
||||
|
||||
</binding:binding>
|
||||
@@ -0,0 +1,84 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="cm11a"
|
||||
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 which is the cm11a device -->
|
||||
<bridge-type id="cm11a">
|
||||
<label>CM11a Controller for X10 Devices</label>
|
||||
<description>CM11 is a device that allows control of X10 devices for a computer. </description>
|
||||
|
||||
<config-description>
|
||||
<parameter name="serialPort" type="text" required="true">
|
||||
<label>Serial Port</label>
|
||||
<context>serial-port</context>
|
||||
<limitToOptions>false</limitToOptions>
|
||||
<description>Serial port used to communicate with the CM11a</description>
|
||||
</parameter>
|
||||
<parameter name="refresh" type="integer" min="1">
|
||||
<label>Refresh Interval</label>
|
||||
<description>Specifies the refresh interval in seconds.</description>
|
||||
<default>60</default>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</bridge-type>
|
||||
|
||||
<!-- For dimable X10 devices attached to the cm11a -->
|
||||
<thing-type id="dimmer">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="cm11a"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>X10 Dimmer</label>
|
||||
<description>Controls dimmable loads</description>
|
||||
|
||||
<channels>
|
||||
<channel id="lightlevel" typeId="lightDimmer"/>
|
||||
</channels>
|
||||
|
||||
<config-description>
|
||||
<parameter name="houseUnitCode" type="text" required="true">
|
||||
<label>X10 House and Unit Code</label>
|
||||
<description>Address of dimmer in the X10 system</description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</thing-type>
|
||||
|
||||
<!-- For non-dimable X10 devices attached to the cm11a -->
|
||||
<thing-type id="switch">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="cm11a"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>X10 Switch</label>
|
||||
<description>On/off switch</description>
|
||||
|
||||
<channels>
|
||||
<channel id="switchstatus" typeId="switchState"/>
|
||||
</channels>
|
||||
|
||||
<config-description>
|
||||
<parameter name="houseUnitCode" type="text" required="true">
|
||||
<label>X10 House and Unit Code</label>
|
||||
<description>Address of switch in the X10 system</description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</thing-type>
|
||||
|
||||
<channel-type id="lightDimmer">
|
||||
<item-type>Dimmer</item-type>
|
||||
<label>Light Level</label>
|
||||
<description>Increase/decrease the light level</description>
|
||||
<category>DimmableLight</category>
|
||||
<state min="0" max="100" pattern="%.0f %%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="switchState">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Switch State</label>
|
||||
<description>On/off status of the switch</description>
|
||||
<category>Switch</category>
|
||||
</channel-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
Reference in New Issue
Block a user