Initial Commit

This commit is contained in:
Mark Milligan
2021-01-14 16:28:24 -06:00
parent 21c28201c5
commit 1334c110ff
318 changed files with 24160 additions and 0 deletions

View File

@@ -0,0 +1,237 @@
package com.lanternsoftware.zwave.controller;
import com.lanternsoftware.util.NullUtils;
import com.lanternsoftware.util.concurrency.ConcurrencyUtils;
import com.lanternsoftware.zwave.message.IMessageSubscriber;
import com.lanternsoftware.zwave.message.Message;
import com.lanternsoftware.zwave.message.MessageEngine;
import com.lanternsoftware.zwave.message.RequestMessage;
import com.lanternsoftware.zwave.message.ResponseMessage;
import com.lanternsoftware.zwave.message.impl.ByteMessage;
import com.lanternsoftware.zwave.message.impl.ControllerCapabilitiesRequest;
import com.lanternsoftware.zwave.message.impl.ControllerInitialDataRequest;
import com.lanternsoftware.zwave.message.impl.GetControllerIdRequest;
import com.lanternsoftware.zwave.message.impl.SendDataRequest;
import com.lanternsoftware.zwave.node.NodeManager;
import gnu.io.CommPortIdentifier;
import gnu.io.SerialPort;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
public class Controller {
private static final byte SOF = 0x01;
private static final byte ACK = 0x06;
private static final byte NAK = 0x15;
private static final byte CAN = 0x18;
private static final Logger logger = LoggerFactory.getLogger(Controller.class);
private SerialPort serialPort;
private OutputStream os;
private boolean running = false;
private AtomicInteger callbackId = new AtomicInteger(0);
private final Object ackMutex = new Object();
private final Object responseMutex = new Object();
private final Object callbackMutex = new Object();
private boolean responseReceived;
private final Map<Byte, Byte> callbacks = new HashMap<>();
private ExecutorService executor = Executors.newFixedThreadPool(2);
private NodeManager nodeManager;
public boolean start(String _port) {
try {
CommPortIdentifier portIdentifier = CommPortIdentifier.getPortIdentifier(_port);
serialPort = portIdentifier.open("zwaveport", 2000);
serialPort.setSerialPortParams(115200, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE);
serialPort.enableReceiveThreshold(1);
serialPort.enableReceiveTimeout(1000);
os = serialPort.getOutputStream();
running = true;
executor.submit(new MessageReceiver());
MessageEngine.subscribe(new SendDataRequestHandler());
nodeManager = new NodeManager(this);
send(new ControllerCapabilitiesRequest());
send(new ControllerInitialDataRequest());
send(new GetControllerIdRequest());
nodeManager.waitForStartup();
logger.debug("Finishing Controller Start");
return true;
} catch (Exception _e) {
if (serialPort != null) {
serialPort.close();
serialPort = null;
}
logger.error("Exception while starting controller", _e);
return false;
}
}
public void stop() {
running = false;
ConcurrencyUtils.sleep(2000);
IOUtils.closeQuietly(os);
if (serialPort != null) {
serialPort.close();
}
executor.shutdown();
}
public void send(Message _message) {
executor.submit(new MessageSender(_message));
}
private class MessageReceiver implements Runnable {
@Override
public void run() {
InputStream is = null;
try {
is = serialPort.getInputStream();
int nextByte = 0;
int offset = 0;
while (running) {
nextByte = is.read();
if (nextByte == -1)
continue;
switch (nextByte) {
case SOF:
int messageLength = is.read();
byte[] buffer = new byte[messageLength + 2];
buffer[0] = SOF;
buffer[1] = (byte) messageLength;
offset = 2;
while (offset < messageLength + 2) {
offset += is.read(buffer, offset, messageLength + 2 - offset);
}
processIncomingMessage(buffer);
break;
case ACK:
synchronized (ackMutex) {
logger.debug("Received ACK");
ackMutex.notify();
}
MessageEngine.publish(new ByteMessage((byte) nextByte));
break;
case NAK:
case CAN:
synchronized (ackMutex) {
logger.debug("Received: {}", NullUtils.toHex(new byte[]{(byte) nextByte}));
ackMutex.notify();
}
MessageEngine.publish(new ByteMessage((byte) nextByte));
break;
default:
sendRaw(new byte[]{NAK});
break;
}
}
} catch (IOException _e) {
logger.error("Exception while receiving inbound, stopping controller", _e);
stop();
} finally {
IOUtils.closeQuietly(is);
}
}
}
private class MessageSender implements Runnable {
private final Message message;
MessageSender(Message _message) {
message = _message;
}
@Override
public void run() {
try {
synchronized (Controller.this) {
byte callback = 0;
String log = "Sending message outbound: " + message.describe();
if (message.isCallbackExpected()) {
callback = (byte) (callbackId.getAndIncrement() % 126 + 1);
callbacks.put(callback, message.getNodeId());
log += " callback: " + callback;
}
logger.info(log);
byte[] data = message.toByteArray((byte) 0, callback);
logger.debug("Sending outbound: {}", NullUtils.toHexBytes(data));
responseReceived = false;
sendRaw(data);
synchronized (ackMutex) {
ackMutex.wait(1000);
}
logger.debug("Finished outbound of: {}", message.describe());
}
if (message instanceof RequestMessage) {
logger.debug("Waiting for response from: {}", message.describe());
synchronized (responseMutex) {
responseMutex.wait(1000);
logger.debug("Response received: {}", responseReceived);
responseReceived = false;
}
}
if (message.isCallbackExpected()) {
logger.debug("Waiting for callback from: {}", message.describe());
synchronized (callbackMutex) {
callbackMutex.wait(1000);
}
}
} catch (InterruptedException _e) {
logger.error("Interrupted while sending outbound", _e);
}
}
}
private void sendRaw(byte[] _data) {
try {
os.write(_data);
os.flush();
} catch (IOException _e) {
logger.error("IO exception while sending outbound", _e);
}
}
private void processIncomingMessage(byte[] _buffer) {
logger.debug("Received inbound: {}", NullUtils.toHexBytes(_buffer));
logger.debug("Sending ACK");
sendRaw(new byte[]{ACK});
Message message = MessageEngine.decode(_buffer);
if (message != null) {
logger.info("Received message inbound: {}", message.describe());
MessageEngine.publish(message);
if (message instanceof ResponseMessage) {
synchronized (responseMutex) {
responseReceived = true;
responseMutex.notify();
}
}
}
}
private class SendDataRequestHandler implements IMessageSubscriber<SendDataRequest> {
@Override
public Class<SendDataRequest> getHandledMessageClass() {
return SendDataRequest.class;
}
@Override
public void onMessage(SendDataRequest _message) {
Byte nodeId = callbacks.remove(_message.getCallbackId());
if (nodeId != null) {
logger.debug("Received callback for node: {} callback id: {}", nodeId, _message.getCallbackId());
synchronized (callbackMutex) {
callbackMutex.notify();
}
}
}
}
}

View File

@@ -0,0 +1,119 @@
package com.lanternsoftware.zwave.message;
import java.util.HashMap;
import java.util.Map;
public enum CommandClass {
NO_OPERATION((byte)0x00, "NO_OPERATION"),
BASIC((byte)0x20, "BASIC"),
CONTROLLER_REPLICATION((byte)0x21, "CONTROLLER_REPLICATION"),
APPLICATION_STATUS((byte)0x22, "APPLICATION_STATUS"),
ZIP_SERVICES((byte)0x23, "ZIP_SERVICES"),
ZIP_SERVER((byte)0x24, "ZIP_SERVER"),
SWITCH_BINARY((byte)0x25, "SWITCH_BINARY", true, 0),
SWITCH_MULTILEVEL((byte)0x26, "SWITCH_MULTILEVEL", true, 0),
SWITCH_ALL((byte)0x27, "SWITCH_ALL"),
SWITCH_TOGGLE_BINARY((byte)0x28, "SWITCH_TOGGLE_BINARY"),
SWITCH_TOGGLE_MULTILEVEL((byte)0x29, "SWITCH_TOGGLE_MULTILEVEL"),
CHIMNEY_FAN((byte)0x2A, "CHIMNEY_FAN"),
SCENE_ACTIVATION((byte)0x2B, "SCENE_ACTIVATION"),
SCENE_ACTUATOR_CONF((byte)0x2C, "SCENE_ACTUATOR_CONF"),
SCENE_CONTROLLER_CONF((byte)0x2D, "SCENE_CONTROLLER_CONF"),
ZIP_CLIENT((byte)0x2E, "ZIP_CLIENT"),
ZIP_ADV_SERVICES((byte)0x2F, "ZIP_ADV_SERVICES"),
SENSOR_BINARY((byte)0x30, "SENSOR_BINARY"),
SENSOR_MULTILEVEL((byte)0x31, "SENSOR_MULTILEVEL"),
METER((byte)0x32, "METER", true, 60),
ZIP_ADV_SERVER((byte)0x33, "ZIP_ADV_SERVER"),
ZIP_ADV_CLIENT((byte)0x34, "ZIP_ADV_CLIENT"),
METER_PULSE((byte)0x35, "METER_PULSE"),
METER_TBL_CONFIG((byte)0x3C, "METER_TBL_CONFIG"),
METER_TBL_MONITOR((byte)0x3D, "METER_TBL_MONITOR"),
METER_TBL_PUSH((byte)0x3E, "METER_TBL_PUSH"),
THERMOSTAT_HEATING((byte)0x38, "THERMOSTAT_HEATING"),
THERMOSTAT_MODE((byte)0x40, "THERMOSTAT_MODE"),
THERMOSTAT_OPERATING_STATE((byte)0x42, "THERMOSTAT_OPERATING_STATE"),
THERMOSTAT_SETPOINT((byte)0x43, "THERMOSTAT_SETPOINT"),
THERMOSTAT_FAN_MODE((byte)0x44, "THERMOSTAT_FAN_MODE"),
THERMOSTAT_FAN_STATE((byte)0x45, "THERMOSTAT_FAN_STATE"),
CLIMATE_CONTROL_SCHEDULE((byte)0x46, "CLIMATE_CONTROL_SCHEDULE"),
THERMOSTAT_SETBACK((byte)0x47, "THERMOSTAT_SETBACK"),
DOOR_LOCK_LOGGING((byte)0x4C, "DOOR_LOCK_LOGGING"),
SCHEDULE_ENTRY_LOCK((byte)0x4E, "SCHEDULE_ENTRY_LOCK"),
BASIC_WINDOW_COVERING((byte)0x50, "BASIC_WINDOW_COVERING"),
MTP_WINDOW_COVERING((byte)0x51, "MTP_WINDOW_COVERING"),
MULTI_INSTANCE((byte)0x60, "MULTI_INSTANCE"),
DOOR_LOCK((byte)0x62, "DOOR_LOCK"),
USER_CODE((byte)0x63, "USER_CODE"),
CONFIGURATION((byte)0x70, "CONFIGURATION"),
ALARM((byte)0x71, "ALARM"),
MANUFACTURER_SPECIFIC((byte)0x72, "MANUFACTURER_SPECIFIC"),
POWERLEVEL((byte)0x73, "POWERLEVEL"),
PROTECTION((byte)0x75, "PROTECTION"),
LOCK((byte)0x76, "LOCK"),
NODE_NAMING((byte)0x77, "NODE_NAMING"),
FIRMWARE_UPDATE_MD((byte)0x7A, "FIRMWARE_UPDATE_MD"),
GROUPING_NAME((byte)0x7B, "GROUPING_NAME"),
REMOTE_ASSOCIATION_ACTIVATE((byte)0x7C, "REMOTE_ASSOCIATION_ACTIVATE"),
REMOTE_ASSOCIATION((byte)0x7D, "REMOTE_ASSOCIATION"),
BATTERY((byte)0x80, "BATTERY", true, 3600),
CLOCK((byte)0x81, "CLOCK"),
HAIL((byte)0x82, "HAIL"),
WAKE_UP((byte)0x84, "WAKE_UP"),
ASSOCIATION((byte)0x85, "ASSOCIATION"),
VERSION((byte)0x86, "VERSION"),
INDICATOR((byte)0x87, "INDICATOR"),
PROPRIETARY((byte)0x88, "PROPRIETARY"),
LANGUAGE((byte)0x89, "LANGUAGE"),
TIME((byte)0x8A, "TIME"),
TIME_PARAMETERS((byte)0x8B, "TIME_PARAMETERS"),
GEOGRAPHIC_LOCATION((byte)0x8C, "GEOGRAPHIC_LOCATION"),
COMPOSITE((byte)0x8D, "COMPOSITE"),
MULTI_INSTANCE_ASSOCIATION((byte)0x8E, "MULTI_INSTANCE_ASSOCIATION"),
MULTI_CMD((byte)0x8F, "MULTI_CMD"),
ENERGY_PRODUCTION((byte)0x90, "ENERGY_PRODUCTION"),
MANUFACTURER_PROPRIETARY((byte)0x91, "MANUFACTURER_PROPRIETARY"),
SCREEN_MD((byte)0x92, "SCREEN_MD"),
SCREEN_ATTRIBUTES((byte)0x93, "SCREEN_ATTRIBUTES"),
SIMPLE_AV_CONTROL((byte)0x94, "SIMPLE_AV_CONTROL"),
AV_CONTENT_DIRECTORY_MD((byte)0x95, "AV_CONTENT_DIRECTORY_MD"),
AV_RENDERER_STATUS((byte)0x96, "AV_RENDERER_STATUS"),
AV_CONTENT_SEARCH_MD((byte)0x97, "AV_CONTENT_SEARCH_MD"),
SECURITY((byte)0x98, "SECURITY"),
AV_TAGGING_MD((byte)0x99, "AV_TAGGING_MD"),
IP_CONFIGURATION((byte)0x9A, "IP_CONFIGURATION"),
ASSOCIATION_COMMAND_CONFIGURATION((byte)0x9B, "ASSOCIATION_COMMAND_CONFIGURATION"),
SENSOR_ALARM((byte)0x9C, "SENSOR_ALARM"),
SILENCE_ALARM((byte)0x9D, "SILENCE_ALARM"),
SENSOR_CONFIGURATION((byte)0x9E, "SENSOR_CONFIGURATION"),
MARK((byte)0xEF, "MARK"),
NON_INTEROPERABLE((byte)0xF0, "NON_INTEROPERABLE"),
ALL((byte)0xFF, null);
public final byte data;
public final String label;
public final boolean supportsPolling;
public final int secondsRefreshInterval;
private static final Map<Byte, CommandClass> classes = new HashMap<>();
static {
for (CommandClass cls : values()) {
classes.put(cls.data, cls);
}
}
CommandClass(byte _data, String _label) {
this(_data, _label, false, 0);
}
CommandClass(byte _data, String _label, boolean _supportsPolling, int _secondsRefreshInterval) {
data = _data;
label = _label;
supportsPolling = _supportsPolling;
secondsRefreshInterval = _secondsRefreshInterval;
}
public static CommandClass fromByte(byte _code) {
CommandClass cls = classes.get(_code);
return cls == null ? CommandClass.NO_OPERATION : cls;
}
}

View File

@@ -0,0 +1,92 @@
package com.lanternsoftware.zwave.message;
import com.lanternsoftware.util.CollectionUtils;
import java.util.Arrays;
import java.util.Map;
public enum ControllerMessageType {
None((byte)0x0,"None"),
SerialApiGetInitData((byte)0x02,"SerialApiGetInitData"), // Request initial information about devices in network
SerialApiApplicationNodeInfo((byte)0x03,"SerialApiApplicationNodeInfo"), // Set controller node information
ApplicationCommandHandler((byte)0x04,"ApplicationCommandHandler"), // Handle application command
GetControllerCapabilities((byte)0x05,"GetControllerCapabilities"), // Request controller capabilities (primary role, SUC/SIS availability)
SerialApiSetTimeouts((byte)0x06,"SerialApiSetTimeouts"), // Set Serial API timeouts
GetCapabilities((byte)0x07,"GetCapabilities"), // Request Serial API capabilities from the controller
SerialApiSoftReset((byte)0x08,"SerialApiSoftReset"), // Soft reset. Restarts Z-Wave chip
RfReceiveMode((byte)0x10,"RfReceiveMode"), // Power down the RF section of the stick
SetSleepMode((byte)0x11,"SetSleepMode"), // Set the CPU into sleep mode
SendNodeInfo((byte)0x12,"SendNodeInfo"), // Send Node Information Frame of the stick
SendData((byte)0x13,"SendData"), // Send data.
SendDataMulti((byte)0x14, "SendDataMulti"),
GetVersion((byte)0x15,"GetVersion"), // Request controller hardware version
SendDataAbort((byte)0x16,"SendDataAbort"), // Abort Send data.
RfPowerLevelSet((byte)0x17,"RfPowerLevelSet"), // Set RF Power level
SendDataMeta((byte)0x18, "SendDataMeta"),
GetRandom((byte)0x1c,"GetRandom"), // ???
MemoryGetId((byte)0x20,"MemoryGetId"), // ???
MemoryGetByte((byte)0x21,"MemoryGetByte"), // Get a byte of memory.
MemoryPutByte((byte)0x22, "MemoryPutByte"),
ReadMemory((byte)0x23,"ReadMemory"), // Read memory.
WriteMemory((byte)0x24, "WriteMemory"),
SetLearnNodeState((byte)0x40,"SetLearnNodeState"), // ???
IdentifyNode((byte)0x41,"IdentifyNode"), // Get protocol info (baud rate, listening, etc.) for a given node
SetDefault((byte)0x42,"SetDefault"), // Reset controller and node info to default (original) values
NewController((byte)0x43,"NewController"), // ???
ReplicationCommandComplete((byte)0x44,"ReplicationCommandComplete"), // Replication send data complete
ReplicationSendData((byte)0x45,"ReplicationSendData"), // Replication send data
AssignReturnRoute((byte)0x46,"AssignReturnRoute"), // Assign a return route from the specified node to the controller
DeleteReturnRoute((byte)0x47,"DeleteReturnRoute"), // Delete all return routes from the specified node
RequestNodeNeighborUpdate((byte)0x48,"RequestNodeNeighborUpdate"), // Ask the specified node to update its neighbors (then read them from the controller)
ApplicationUpdate((byte)0x49,"ApplicationUpdate"), // Get a list of supported (and controller) command classes
AddNodeToNetwork((byte)0x4a,"AddNodeToNetwork"), // Control the addnode (or addcontroller) process...start, stop, etc.
RemoveNodeFromNetwork((byte)0x4b,"RemoveNodeFromNetwork"), // Control the removenode (or removecontroller) process...start, stop, etc.
CreateNewPrimary((byte)0x4c,"CreateNewPrimary"), // Control the createnewprimary process...start, stop, etc.
ControllerChange((byte)0x4d,"ControllerChange"), // Control the transferprimary process...start, stop, etc.
SetLearnMode((byte)0x50,"SetLearnMode"), // Put a controller into learn mode for replication/ receipt of configuration info
AssignSucReturnRoute((byte)0x51,"AssignSucReturnRoute"), // Assign a return route to the SUC
EnableSuc((byte)0x52,"EnableSuc"), // Make a controller a Static Update Controller
RequestNetworkUpdate((byte)0x53,"RequestNetworkUpdate"), // Network update for a SUC(?)
SetSucNodeID((byte)0x54,"SetSucNodeID"), // Identify a Static Update Controller node id
DeleteSUCReturnRoute((byte)0x55,"DeleteSUCReturnRoute"), // Remove return routes to the SUC
GetSucNodeId((byte)0x56,"GetSucNodeId"), // Try to retrieve a Static Update Controller node id (zero if no SUC present)
SendSucId((byte)0x57, "SendSucId"),
RequestNodeNeighborUpdateOptions((byte)0x5a,"RequestNodeNeighborUpdateOptions"), // Allow options for request node neighbor update
RequestNodeInfo((byte)0x60,"RequestNodeInfo"), // Get info (supported command classes) for the specified node
RemoveFailedNodeID((byte)0x61,"RemoveFailedNodeID"), // Mark a specified node id as failed
IsFailedNodeID((byte)0x62,"IsFailedNodeID"), // Check to see if a specified node has failed
ReplaceFailedNode((byte)0x63,"ReplaceFailedNode"), // Remove a failed node from the controller's list (?)
GetRoutingInfo((byte)0x80,"GetRoutingInfo"), // Get a specified node's neighbor information from the controller
LockRoute((byte)0x90, "LockRoute"),
SerialApiSlaveNodeInfo((byte)0xA0,"SerialApiSlaveNodeInfo"), // Set application virtual slave node information
ApplicationSlaveCommandHandler((byte)0xA1,"ApplicationSlaveCommandHandler"), // Slave command handler
SendSlaveNodeInfo((byte)0xA2,"ApplicationSlaveCommandHandler"), // Send a slave node information frame
SendSlaveData((byte)0xA3,"SendSlaveData"), // Send data from slave
SetSlaveLearnMode((byte)0xA4,"SetSlaveLearnMode"), // Enter slave learn mode
GetVirtualNodes((byte)0xA5,"GetVirtualNodes"), // Return all virtual nodes
IsVirtualNode((byte)0xA6,"IsVirtualNode"), // Virtual node test
WatchDogEnable((byte)0xB6, "WatchDogEnable"),
WatchDogDisable((byte)0xB7, "WatchDogDisable"),
WatchDogKick((byte)0xB6, "WatchDogKick"),
RfPowerLevelGet((byte)0xBA,"RfPowerLevelSet"), // Get RF Power level
GetLibraryType((byte)0xBD, "GetLibraryType"), // Gets the type of ZWave library on the stick
SendTestFrame((byte)0xBE, "SendTestFrame"), // Send a test frame to a node
GetProtocolStatus((byte)0xBF, "GetProtocolStatus"),
SetPromiscuousMode((byte)0xD0,"SetPromiscuousMode"), // Set controller into promiscuous mode to listen to all frames
PromiscuousApplicationCommandHandler((byte)0xD1,"PromiscuousApplicationCommandHandler"),
ALL((byte)0xFF, null);
public final byte data;
public final String label;
ControllerMessageType(byte _data, String _label) {
data = _data;
label = _label;
}
private static Map<Byte, ControllerMessageType> types = CollectionUtils.transformToMap(Arrays.asList(values()), _type->_type.data);
public static ControllerMessageType fromByte(byte _data) {
ControllerMessageType type = types.get(_data);
return type == null ? ControllerMessageType.None : type;
}
}

View File

@@ -0,0 +1,6 @@
package com.lanternsoftware.zwave.message;
public interface IMessageSubscriber<T extends Message> {
Class<T> getHandledMessageClass();
void onMessage(T _message);
}

View File

@@ -0,0 +1,126 @@
package com.lanternsoftware.zwave.message;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
public abstract class Message {
protected byte nodeId;
protected final ControllerMessageType controllerMessageType;
protected final MessageType messageType;
protected final CommandClass commandClass;
protected final byte command;
public Message(ControllerMessageType _controllerMessageType, MessageType _messageType, CommandClass _commandClass, byte _command) {
this((byte) 0, _controllerMessageType, _messageType, _commandClass, _command);
}
public Message(byte _nodeId, ControllerMessageType _controllerMessageType, MessageType _messageType, CommandClass _commandClass, byte _command) {
nodeId = _nodeId;
controllerMessageType = _controllerMessageType;
messageType = _messageType;
commandClass = _commandClass;
command = _command;
}
public byte getNodeId() {
return nodeId;
}
public void setNodeId(byte _nodeId) {
nodeId = _nodeId;
}
public byte[] toPayload() {
try {
byte[] payload = getPayload();
ByteArrayOutputStream os = new ByteArrayOutputStream();
if (nodeId > 0)
os.write(nodeId);
if (commandClass != CommandClass.NO_OPERATION) {
os.write(payload.length + 2);
os.write(commandClass.data);
os.write(command);
}
if (payload.length > 0)
os.write(payload);
os.close();
return os.toByteArray();
} catch (IOException _e) {
_e.printStackTrace();
return new byte[0];
}
}
public void fromPayload(byte[] _payload) {
}
public byte[] getPayload() {
return new byte[0];
}
public byte[] toByteArray(byte _transmitOptions, byte _callbackId) {
try {
ByteArrayOutputStream os = new ByteArrayOutputStream();
byte[] payload = toPayload();
os.write((byte) 0x01);
int messageLength = payload.length + (isCallbackExpected() ? 5 : 3);
os.write((byte) messageLength);
os.write(messageType.data);
os.write(controllerMessageType.data);
if (payload.length > 0)
os.write(payload);
if (isCallbackExpected()) {
os.write(_transmitOptions);
os.write(_callbackId);
}
os.write((byte) 1);
byte[] msg = os.toByteArray();
msg[msg.length - 1] = calculateChecksum(msg);
return msg;
} catch (IOException _e) {
_e.printStackTrace();
return null;
}
}
public String getKey() {
return toKey(controllerMessageType.data, messageType.data, commandClass.data, command);
}
public static String toKey(byte _controllerMessageType, byte _messageType, byte _commandClass, byte _command) {
return String.format("%02X%02X%02X%02X", _controllerMessageType, _messageType, _commandClass, _command);
}
public static byte calculateChecksum(byte[] buffer) {
byte checkSum = (byte) 0xFF;
for (int i = 1; i < buffer.length - 1; i++) {
checkSum = (byte) (checkSum ^ buffer[i]);
}
return checkSum;
}
protected byte[] asByteArray(int _byte) {
byte[] ret = new byte[1];
ret[0] = (byte) _byte;
return ret;
}
protected byte[] asByteArray(byte... _bytes) {
return _bytes;
}
public String name() {
return getClass().getSimpleName();
}
public boolean isCallbackExpected() {
return (controllerMessageType == ControllerMessageType.SendData) && (messageType == MessageType.REQUEST);
}
public String describe() {
return name();
}
}

View File

@@ -0,0 +1,63 @@
package com.lanternsoftware.zwave.message;
import com.lanternsoftware.util.CollectionUtils;
import com.lanternsoftware.util.NullUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;
public abstract class MessageEngine {
private static final Logger logger = LoggerFactory.getLogger(MessageEngine.class);
private static final Map<String, Message> messages = new HashMap<>();
private static final Map<Class<?>, List<IMessageSubscriber<?>>> subscribers = new HashMap<>();
static {
for (Message m : ServiceLoader.load(Message.class)) {
messages.put(m.getKey(), m);
}
for (IMessageSubscriber s : ServiceLoader.load(IMessageSubscriber.class)) {
subscribe(s);
}
}
public static Message decode(byte[] _data) {
byte messageCheckSum = Message.calculateChecksum(_data);
byte messageCheckSumReceived = _data[_data.length - 1];
if (messageCheckSum != messageCheckSumReceived) {
logger.debug("Invalid checksum for message: {}", NullUtils.toHex(_data));
return null;
}
MessageType messageType = _data[2] == 0x00 ? MessageType.REQUEST : MessageType.RESPONSE;
ControllerMessageType controllerMessageType = ControllerMessageType.fromByte((byte)(_data[3] & 0xFF));
int offset = ((messageType == MessageType.REQUEST) && NullUtils.isOneOf(controllerMessageType, ControllerMessageType.SendData, ControllerMessageType.ApplicationCommandHandler))?7:5;
CommandClass commandClass = _data.length > offset + 1 ? CommandClass.fromByte((byte)(_data[offset] & 0xFF)):CommandClass.NO_OPERATION;
byte command = ((commandClass == CommandClass.NO_OPERATION) || (_data.length <= offset+2))?0:(byte)(_data[offset+1] & 0xFF);
Message message = messages.get(Message.toKey(controllerMessageType.data, messageType.data, commandClass.data, command));
if (message == null) {
logger.debug("Could not find message class for message: {} {} {} {}", controllerMessageType.label, messageType.name(), commandClass.label, command);
return null;
}
try {
Message ret = message.getClass().newInstance();
ret.fromPayload(_data);
return ret;
} catch (Exception _e) {
_e.printStackTrace();
return null;
}
}
public static void publish(Message _m) {
for (IMessageSubscriber s : CollectionUtils.makeNotNull(subscribers.get(_m.getClass()))) {
s.onMessage(_m);
}
}
public static void subscribe(IMessageSubscriber<?> _subscriber) {
CollectionUtils.addToMultiMap(_subscriber.getHandledMessageClass(), _subscriber, subscribers);
}
}

View File

@@ -0,0 +1,12 @@
package com.lanternsoftware.zwave.message;
public enum MessageType {
REQUEST((byte)0),
RESPONSE((byte)1);
public final byte data;
MessageType(byte _data) {
data = _data;
}
}

View File

@@ -0,0 +1,39 @@
package com.lanternsoftware.zwave.message;
import java.math.BigDecimal;
import java.math.RoundingMode;
public abstract class MessageUtil {
private static final int SIZE_MASK = 0x07;
private static final int SCALE_MASK = 0x18;
private static final int SCALE_SHIFT = 0x03;
private static final int PRECISION_MASK = 0xe0;
private static final int PRECISION_SHIFT = 0x05;
public static double getTemperatureCelsius(byte[] _payload, int _offset) {
int size = _payload[_offset] & SIZE_MASK;
int scale = (_payload[_offset] & SCALE_MASK) >> SCALE_SHIFT;
int precision = (_payload[_offset] & PRECISION_MASK) >> PRECISION_SHIFT;
if ((size+_offset) >= _payload.length)
return 0.0;
int value = 0;
for (int i = 0; i < size; ++i) {
value <<= 8;
value |= _payload[_offset + i + 1] & 0xFF;
}
BigDecimal result;
if ((_payload[_offset + 1] & 0x80) == 0x80) {
if (size == 1)
value |= 0xffffff00;
else if (size == 2)
value |= 0xffff0000;
}
result = BigDecimal.valueOf(value);
BigDecimal divisor = BigDecimal.valueOf(Math.pow(10, precision));
double temp = result.divide(divisor, RoundingMode.HALF_EVEN).doubleValue();
return (scale == 1) ? (temp-32)/1.8 : temp;
}
}

View File

@@ -0,0 +1,11 @@
package com.lanternsoftware.zwave.message;
public abstract class NoCommandRequestMessage extends RequestMessage {
public NoCommandRequestMessage(ControllerMessageType _controllerMessageType) {
this((byte)0, _controllerMessageType);
}
public NoCommandRequestMessage(byte _nodeId, ControllerMessageType _controllerMessageType) {
super(_nodeId, _controllerMessageType, CommandClass.NO_OPERATION, (byte) 0);
}
}

View File

@@ -0,0 +1,7 @@
package com.lanternsoftware.zwave.message;
public abstract class NoCommandResponseMessage extends ResponseMessage {
public NoCommandResponseMessage(ControllerMessageType _controllerMessageType) {
super(_controllerMessageType, CommandClass.NO_OPERATION, (byte) 0);
}
}

View File

@@ -0,0 +1,11 @@
package com.lanternsoftware.zwave.message;
public abstract class RequestMessage extends Message {
public RequestMessage(ControllerMessageType _controllerMessageType, CommandClass _commandClass, byte _command) {
this((byte)0, _controllerMessageType, _commandClass, _command);
}
public RequestMessage(byte _nodeId, ControllerMessageType _controllerMessageType, CommandClass _commandClass, byte _command) {
super(_nodeId, _controllerMessageType, MessageType.REQUEST, _commandClass, _command);
}
}

View File

@@ -0,0 +1,11 @@
package com.lanternsoftware.zwave.message;
public abstract class ResponseMessage extends Message {
public ResponseMessage(ControllerMessageType _controllerMessageType, CommandClass _commandClass, byte _command) {
this((byte) 0, _controllerMessageType, _commandClass, _command);
}
public ResponseMessage(byte _nodeId, ControllerMessageType _controllerMessageType, CommandClass _commandClass, byte _command) {
super(_nodeId, _controllerMessageType, MessageType.RESPONSE, _commandClass, _command);
}
}

View File

@@ -0,0 +1,11 @@
package com.lanternsoftware.zwave.message;
public abstract class SendDataRequestMessage extends RequestMessage {
public SendDataRequestMessage(CommandClass _commandClass, byte _command) {
this((byte)0, _commandClass, _command);
}
public SendDataRequestMessage(byte _nodeId, CommandClass _commandClass, byte _command) {
super(_nodeId, ControllerMessageType.SendData, _commandClass, _command);
}
}

View File

@@ -0,0 +1,42 @@
package com.lanternsoftware.zwave.message.impl;
import com.lanternsoftware.zwave.message.CommandClass;
import com.lanternsoftware.zwave.message.ControllerMessageType;
import com.lanternsoftware.zwave.message.RequestMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
public class ApplicationUpdateRequest extends RequestMessage {
private static final Logger logger = LoggerFactory.getLogger(ApplicationUpdateRequest.class);
private List<CommandClass> commandClasses;
public ApplicationUpdateRequest() {
super(ControllerMessageType.ApplicationUpdate, CommandClass.NO_OPERATION, (byte) 0);
}
@Override
public void fromPayload(byte[] _payload) {
nodeId = _payload[5];
if (_payload[4] == (byte) 0x84)
logger.debug("Received node information for node: {}", nodeId);
int length = _payload[6];
commandClasses = new ArrayList<>();
for (int i = 7; i < length + 7; i++) {
if (_payload[i] != (byte) 0xef) {
CommandClass commandClass = CommandClass.fromByte(_payload[i]);
if (commandClass != CommandClass.NO_OPERATION) {
logger.debug("Received command class: {} for node: {}", commandClass.name(), nodeId);
commandClasses.add(commandClass);
}
}
}
}
public List<CommandClass> getCommandClasses() {
return commandClasses;
}
}

View File

@@ -0,0 +1,39 @@
package com.lanternsoftware.zwave.message.impl;
import com.lanternsoftware.zwave.message.CommandClass;
import com.lanternsoftware.zwave.message.SendDataRequestMessage;
public class BinarySwitchSetRequest extends SendDataRequestMessage {
private boolean on;
public BinarySwitchSetRequest() {
this(true);
}
public BinarySwitchSetRequest(boolean _on) {
this((byte)0, _on);
}
public BinarySwitchSetRequest(byte _nodeId, boolean _on) {
super(_nodeId, CommandClass.SWITCH_BINARY, (byte)0x01);
on = _on;
}
public boolean isOn() {
return on;
}
public void setOn(boolean _on) {
on = _on;
}
@Override
public byte[] getPayload() {
return asByteArray(on?0xFF:0);
}
@Override
public String describe() {
return name() + " node: " + nodeId + " on: " + on;
}
}

View File

@@ -0,0 +1,30 @@
package com.lanternsoftware.zwave.message.impl;
import com.lanternsoftware.zwave.message.ControllerMessageType;
import com.lanternsoftware.zwave.message.NoCommandRequestMessage;
public class ByteMessage extends NoCommandRequestMessage {
private byte b;
public ByteMessage() {
super(ControllerMessageType.None);
}
public ByteMessage(byte _b) {
super(ControllerMessageType.None);
b = _b;
}
public byte getByte() {
return b;
}
public void setByte(byte _b) {
b = _b;
}
@Override
public byte[] toByteArray(byte _transmitOptions, byte _callbackId) {
return asByteArray(b);
}
}

View File

@@ -0,0 +1,10 @@
package com.lanternsoftware.zwave.message.impl;
import com.lanternsoftware.zwave.message.ControllerMessageType;
import com.lanternsoftware.zwave.message.NoCommandRequestMessage;
public class ControllerCapabilitiesRequest extends NoCommandRequestMessage {
public ControllerCapabilitiesRequest() {
super(ControllerMessageType.GetCapabilities);
}
}

View File

@@ -0,0 +1,75 @@
package com.lanternsoftware.zwave.message.impl;
import com.lanternsoftware.zwave.message.CommandClass;
import com.lanternsoftware.zwave.message.ControllerMessageType;
import com.lanternsoftware.zwave.message.NoCommandResponseMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashSet;
import java.util.Set;
public class ControllerCapabilitiesResponse extends NoCommandResponseMessage {
private static final Logger logger = LoggerFactory.getLogger(ControllerCapabilitiesResponse.class);
private String serialAPIVersion;
private int manufacturerId;
private int deviceType;
private int deviceId;
private Set<CommandClass> supportedCommandClasses;
public ControllerCapabilitiesResponse() {
super(ControllerMessageType.GetCapabilities);
}
@Override
public void fromPayload(byte[] _payload) {
serialAPIVersion = String.format("%d.%d", _payload[4], _payload[5]);
manufacturerId = getShort(_payload, 6);
deviceType = getShort(_payload, 8);
deviceId = getShort(_payload, 10);
supportedCommandClasses = new HashSet<>();
for (int by = 12; by < _payload.length-1; by++) {
for (int bi = 0; bi < 8; bi++) {
if ((_payload[by] & (0x01 << bi)) != 0) {
byte commandClassByte = (byte) (((by - 12) << 3) + bi + 1);
CommandClass commandClass = CommandClass.fromByte(commandClassByte);
if (commandClass != CommandClass.NO_OPERATION) {
logger.debug("Supports command class: {}", commandClass.label);
supportedCommandClasses.add(commandClass);
} else {
logger.debug("Supports unknown command class: {}", commandClassByte);
}
}
}
}
}
private int getShort(byte[] _data, int _offset) {
return (toShort(_data[_offset]) << 8) | toShort(_data[_offset + 1]);
}
private int toShort(byte _bt) {
return _bt & 0xFF;
}
public String getSerialAPIVersion() {
return serialAPIVersion;
}
public int getManufacturerId() {
return manufacturerId;
}
public int getDeviceType() {
return deviceType;
}
public int getDeviceId() {
return deviceId;
}
public Set<CommandClass> getSupportedCommandClasses() {
return supportedCommandClasses;
}
}

View File

@@ -0,0 +1,10 @@
package com.lanternsoftware.zwave.message.impl;
import com.lanternsoftware.zwave.message.ControllerMessageType;
import com.lanternsoftware.zwave.message.NoCommandRequestMessage;
public class ControllerInitialDataRequest extends NoCommandRequestMessage {
public ControllerInitialDataRequest() {
super(ControllerMessageType.SerialApiGetInitData);
}
}

View File

@@ -0,0 +1,50 @@
package com.lanternsoftware.zwave.message.impl;
import com.lanternsoftware.zwave.message.ControllerMessageType;
import com.lanternsoftware.zwave.message.NoCommandResponseMessage;
import java.util.ArrayList;
import java.util.List;
public class ControllerInitialDataResponse extends NoCommandResponseMessage {
private boolean master;
private boolean primary;
private List<Byte> nodeIds;
public ControllerInitialDataResponse() {
super(ControllerMessageType.SerialApiGetInitData);
}
@Override
public void fromPayload(byte[] _payload) {
int length = _payload[6];
if (length == 29) {
byte nodeId = 1;
nodeIds = new ArrayList<>();
for (int i = 7; i < 7 + length; i++) {
byte curByte = _payload[i];
for (int j = 0; j < 8; j++) {
int bit = 1 << j;
if ((curByte & bit) == bit) {
nodeIds.add(nodeId);
}
nodeId++;
}
}
master = (_payload[5] & 0x1) == 0;
primary = (_payload[5] & 0x4) == 0;
}
}
public boolean isMaster() {
return master;
}
public boolean isPrimary() {
return primary;
}
public List<Byte> getNodeIds() {
return nodeIds;
}
}

View File

@@ -0,0 +1,15 @@
package com.lanternsoftware.zwave.message.impl;
import com.lanternsoftware.zwave.message.CommandClass;
import com.lanternsoftware.zwave.message.ControllerMessageType;
import com.lanternsoftware.zwave.message.RequestMessage;
public class DeviceManufacturerActionRequest extends RequestMessage {
public DeviceManufacturerActionRequest() {
this((byte) 0);
}
public DeviceManufacturerActionRequest(byte _nodeId) {
super(_nodeId, ControllerMessageType.SendData, CommandClass.MANUFACTURER_SPECIFIC, (byte) 0x04);
}
}

View File

@@ -0,0 +1,10 @@
package com.lanternsoftware.zwave.message.impl;
import com.lanternsoftware.zwave.message.ControllerMessageType;
import com.lanternsoftware.zwave.message.NoCommandRequestMessage;
public class GetControllerIdRequest extends NoCommandRequestMessage {
public GetControllerIdRequest() {
super(ControllerMessageType.MemoryGetId);
}
}

View File

@@ -0,0 +1,39 @@
package com.lanternsoftware.zwave.message.impl;
import com.lanternsoftware.zwave.message.ControllerMessageType;
import com.lanternsoftware.zwave.message.NoCommandResponseMessage;
public class GetControllerIdResponse extends NoCommandResponseMessage {
private long homeId;
private int controllerId;
public GetControllerIdResponse() {
super(ControllerMessageType.MemoryGetId);
}
@Override
public void fromPayload(byte[] _payload) {
homeId = (getByte(_payload, 4) << 24) | (getByte(_payload, 5) << 16) | (getByte(_payload, 6) << 8) | getByte(_payload, 7);
controllerId = _payload[8];
}
public long getHomeId() {
return homeId;
}
public void setHomeId(int _homeId) {
homeId = _homeId;
}
public int getControllerId() {
return controllerId;
}
public void setControllerId(int _controllerId) {
controllerId = _controllerId;
}
private long getByte(byte[] _data, int _offset) {
return _data[_offset] & 0xFF;
}
}

View File

@@ -0,0 +1,20 @@
package com.lanternsoftware.zwave.message.impl;
import com.lanternsoftware.zwave.message.CommandClass;
import com.lanternsoftware.zwave.message.ControllerMessageType;
import com.lanternsoftware.zwave.message.RequestMessage;
public class MultilevelSensorGetRequest extends RequestMessage {
public MultilevelSensorGetRequest() {
this((byte)0);
}
public MultilevelSensorGetRequest(byte _nodeId) {
super(_nodeId, ControllerMessageType.SendData, CommandClass.SENSOR_MULTILEVEL, (byte)0x04);
}
@Override
public String describe() {
return name() + " node: " + nodeId;
}
}

View File

@@ -0,0 +1,34 @@
package com.lanternsoftware.zwave.message.impl;
import com.lanternsoftware.zwave.message.CommandClass;
import com.lanternsoftware.zwave.message.ControllerMessageType;
import com.lanternsoftware.zwave.message.MessageUtil;
import com.lanternsoftware.zwave.message.RequestMessage;
public class MultilevelSensorReportRequest extends RequestMessage {
private double temperature;
public MultilevelSensorReportRequest() {
this((byte) 0);
}
public MultilevelSensorReportRequest(byte _nodeId) {
super(_nodeId, ControllerMessageType.ApplicationCommandHandler, CommandClass.SENSOR_MULTILEVEL, (byte) 0x05);
}
@Override
public void fromPayload(byte[] _payload) {
nodeId = _payload[5];
if (_payload[9] == (byte) 1)
temperature = MessageUtil.getTemperatureCelsius(_payload, 10);
}
public double getTemperatureCelsius() {
return temperature;
}
@Override
public String describe() {
return name() + " node: " + nodeId + " level: " + temperature;
}
}

View File

@@ -0,0 +1,28 @@
package com.lanternsoftware.zwave.message.impl;
import com.lanternsoftware.zwave.message.CommandClass;
import com.lanternsoftware.zwave.message.ControllerMessageType;
import com.lanternsoftware.zwave.message.RequestMessage;
public class MultilevelSwitchReportRequest extends RequestMessage {
private int level;
public MultilevelSwitchReportRequest() {
super(ControllerMessageType.ApplicationCommandHandler, CommandClass.SWITCH_MULTILEVEL, (byte) 0x03);
}
@Override
public void fromPayload(byte[] _payload) {
nodeId = _payload[5];
level = _payload[9];
}
public int getLevel() {
return level;
}
@Override
public String describe() {
return name() + " node: " + nodeId + " level: " + level;
}
}

View File

@@ -0,0 +1,31 @@
package com.lanternsoftware.zwave.message.impl;
import com.lanternsoftware.zwave.message.CommandClass;
import com.lanternsoftware.zwave.message.SendDataRequestMessage;
public class MultilevelSwitchSetRequest extends SendDataRequestMessage {
private int level;
public MultilevelSwitchSetRequest() {
this(99);
}
public MultilevelSwitchSetRequest(int _level) {
this((byte)0, _level);
}
public MultilevelSwitchSetRequest(byte _nodeId, int _level) {
super(_nodeId, CommandClass.SWITCH_MULTILEVEL, (byte) 0x01);
level = _level;
}
@Override
public byte[] getPayload() {
return asByteArray(level);
}
@Override
public String describe() {
return name() + " node: " + nodeId + " level: " + level;
}
}

View File

@@ -0,0 +1,14 @@
package com.lanternsoftware.zwave.message.impl;
import com.lanternsoftware.zwave.message.ControllerMessageType;
import com.lanternsoftware.zwave.message.NoCommandRequestMessage;
public class NodeInfoRequest extends NoCommandRequestMessage {
public NodeInfoRequest() {
super(ControllerMessageType.RequestNodeInfo);
}
public NodeInfoRequest(byte _nodeId) {
super(_nodeId, ControllerMessageType.RequestNodeInfo);
}
}

View File

@@ -0,0 +1,10 @@
package com.lanternsoftware.zwave.message.impl;
import com.lanternsoftware.zwave.message.ControllerMessageType;
import com.lanternsoftware.zwave.message.NoCommandResponseMessage;
public class NodeInfoResponse extends NoCommandResponseMessage {
public NodeInfoResponse() {
super(ControllerMessageType.RequestNodeInfo);
}
}

View File

@@ -0,0 +1,60 @@
package com.lanternsoftware.zwave.message.impl;
import com.lanternsoftware.util.CollectionUtils;
import com.lanternsoftware.zwave.message.CommandClass;
import com.lanternsoftware.zwave.message.ControllerMessageType;
import com.lanternsoftware.zwave.message.RequestMessage;
public class SendDataRequest extends RequestMessage {
public enum TransmissionState {
COMPLETE_OK((byte) 0x00, "Transmission complete and ACK received"),
COMPLETE_NO_ACK((byte) 0x01, "Transmission complete, no ACK received"),
COMPLETE_FAIL((byte) 0x02, "Transmission failed"),
COMPLETE_NOT_IDLE((byte) 0x03, "Transmission failed, network busy"),
COMPLETE_NOROUTE((byte) 0x04, "Tranmission complete, no return route");
public byte key;
public String label;
TransmissionState(byte _key, String _label) {
key = _key;
label = _label;
}
public static TransmissionState fromKey(byte _key) {
for (TransmissionState state : values()) {
if (state.key == _key)
return state;
}
return COMPLETE_FAIL;
}
}
private TransmissionState state;
private byte callbackId;
public SendDataRequest() {
super(ControllerMessageType.SendData, CommandClass.NO_OPERATION, (byte) 0);
}
@Override
public void fromPayload(byte[] _payload) {
if (CollectionUtils.length(_payload) > 5) {
callbackId = _payload[4];
state = TransmissionState.fromKey(_payload[5]);
}
}
public TransmissionState getState() {
return state;
}
public byte getCallbackId() {
return callbackId;
}
@Override
public String describe() {
return name() + " callbackId: " + callbackId + " state: " + state.name();
}
}

View File

@@ -0,0 +1,33 @@
package com.lanternsoftware.zwave.message.impl;
import com.lanternsoftware.util.CollectionUtils;
import com.lanternsoftware.zwave.message.CommandClass;
import com.lanternsoftware.zwave.message.ControllerMessageType;
import com.lanternsoftware.zwave.message.ResponseMessage;
public class SendDataResponse extends ResponseMessage {
private byte response;
public SendDataResponse() {
this((byte) 0);
}
public SendDataResponse(byte _response) {
super(ControllerMessageType.SendData, CommandClass.NO_OPERATION, (byte) 0);
response = _response;
}
@Override
public void fromPayload(byte[] _payload) {
if (CollectionUtils.length(_payload) > 0)
response = _payload[0];
}
public boolean isSuccess() {
return response != 0;
}
public String describe() {
return name() + ": " + (isSuccess() ? "SUCCESS" : "FAILURE");
}
}

View File

@@ -0,0 +1,19 @@
package com.lanternsoftware.zwave.message.impl;
import com.lanternsoftware.zwave.message.CommandClass;
import com.lanternsoftware.zwave.message.SendDataRequestMessage;
public class ThermostatModeGetRequest extends SendDataRequestMessage {
public ThermostatModeGetRequest() {
this((byte) 0);
}
public ThermostatModeGetRequest(byte _nodeId) {
super(_nodeId, CommandClass.THERMOSTAT_MODE, (byte) 0x02);
}
@Override
public String describe() {
return name() + " node: " + nodeId;
}
}

View File

@@ -0,0 +1,28 @@
package com.lanternsoftware.zwave.message.impl;
import com.lanternsoftware.zwave.message.CommandClass;
import com.lanternsoftware.zwave.message.SendDataRequestMessage;
import com.lanternsoftware.zwave.message.thermostat.ThermostatMode;
public class ThermostatModeSetRequest extends SendDataRequestMessage {
private ThermostatMode mode;
public ThermostatModeSetRequest() {
this((byte) 0, ThermostatMode.OFF);
}
public ThermostatModeSetRequest(byte _nodeId, ThermostatMode _mode) {
super(_nodeId, CommandClass.THERMOSTAT_MODE, (byte) 0x01);
mode = _mode;
}
@Override
public byte[] getPayload() {
return asByteArray(mode.data);
}
@Override
public String describe() {
return name() + " node: " + nodeId;
}
}

View File

@@ -0,0 +1,28 @@
package com.lanternsoftware.zwave.message.impl;
import com.lanternsoftware.zwave.message.CommandClass;
import com.lanternsoftware.zwave.message.SendDataRequestMessage;
import com.lanternsoftware.zwave.message.thermostat.ThermostatSetPointIndex;
public class ThermostatSetPointGetRequest extends SendDataRequestMessage {
private ThermostatSetPointIndex index;
public ThermostatSetPointGetRequest() {
this((byte) 0, ThermostatSetPointIndex.HEATING);
}
public ThermostatSetPointGetRequest(byte _nodeId, ThermostatSetPointIndex _index) {
super(_nodeId, CommandClass.THERMOSTAT_SETPOINT, (byte) 0x02);
index = _index;
}
@Override
public byte[] getPayload() {
return asByteArray(index.index);
}
@Override
public String describe() {
return name() + " node: " + nodeId;
}
}

View File

@@ -0,0 +1,41 @@
package com.lanternsoftware.zwave.message.impl;
import com.lanternsoftware.zwave.message.CommandClass;
import com.lanternsoftware.zwave.message.ControllerMessageType;
import com.lanternsoftware.zwave.message.MessageUtil;
import com.lanternsoftware.zwave.message.RequestMessage;
import com.lanternsoftware.zwave.message.thermostat.ThermostatSetPointIndex;
public class ThermostatSetPointReportRequest extends RequestMessage {
private ThermostatSetPointIndex index;
private double temperature;
public ThermostatSetPointReportRequest() {
this((byte) 0);
}
public ThermostatSetPointReportRequest(byte _nodeId) {
super(_nodeId, ControllerMessageType.ApplicationCommandHandler, CommandClass.THERMOSTAT_SETPOINT, (byte) 0x03);
}
@Override
public void fromPayload(byte[] _payload) {
nodeId = _payload[5];
index = ThermostatSetPointIndex.fromIndex(_payload[9]);
if (index != null)
temperature = MessageUtil.getTemperatureCelsius(_payload, 10);
}
public ThermostatSetPointIndex getIndex() {
return index;
}
public double getTemperatureCelsius() {
return temperature;
}
@Override
public String describe() {
return name() + " node: " + nodeId + " level: " + temperature;
}
}

View File

@@ -0,0 +1,34 @@
package com.lanternsoftware.zwave.message.impl;
import com.lanternsoftware.zwave.message.CommandClass;
import com.lanternsoftware.zwave.message.SendDataRequestMessage;
import com.lanternsoftware.zwave.message.thermostat.ThermostatSetPointIndex;
public class ThermostatSetPointSetRequest extends SendDataRequestMessage {
private ThermostatSetPointIndex index;
private int level;
public ThermostatSetPointSetRequest() {
this(ThermostatSetPointIndex.HEATING, 72);
}
public ThermostatSetPointSetRequest(ThermostatSetPointIndex _index, int _level) {
this((byte)0, _index, _level);
}
public ThermostatSetPointSetRequest(byte _nodeId, ThermostatSetPointIndex _index, int _level) {
super(_nodeId, CommandClass.THERMOSTAT_SETPOINT, (byte) 0x01);
index = _index;
level = _level;
}
@Override
public byte[] getPayload() {
return asByteArray(index.index, (byte)9, (byte)level);
}
@Override
public String describe() {
return name() + " node: " + nodeId + " level: " + level;
}
}

View File

@@ -0,0 +1,19 @@
package com.lanternsoftware.zwave.message.impl;
import com.lanternsoftware.zwave.message.CommandClass;
import com.lanternsoftware.zwave.message.SendDataRequestMessage;
public class ThermostatSetPointSupportedGetRequest extends SendDataRequestMessage {
public ThermostatSetPointSupportedGetRequest() {
this((byte) 0);
}
public ThermostatSetPointSupportedGetRequest(byte _nodeId) {
super(_nodeId, CommandClass.THERMOSTAT_SETPOINT, (byte) 0x04);
}
@Override
public String describe() {
return name() + " node: " + nodeId;
}
}

View File

@@ -0,0 +1,46 @@
package com.lanternsoftware.zwave.message.impl;
import com.lanternsoftware.util.CollectionUtils;
import com.lanternsoftware.zwave.message.CommandClass;
import com.lanternsoftware.zwave.message.ControllerMessageType;
import com.lanternsoftware.zwave.message.RequestMessage;
import java.util.Set;
import java.util.TreeSet;
public class ThermostatSetPointSupportedReportRequest extends RequestMessage {
private Set<Byte> supportedSetPointIndices;
public ThermostatSetPointSupportedReportRequest() {
this((byte) 0);
}
public ThermostatSetPointSupportedReportRequest(byte _nodeId) {
super(_nodeId, ControllerMessageType.ApplicationCommandHandler, CommandClass.THERMOSTAT_SETPOINT, (byte) 0x05);
}
@Override
public void fromPayload(byte[] _payload) {
supportedSetPointIndices = new TreeSet<>();
for (int i = 9; i < _payload.length - 1; ++i) {
for (int bit = 0; bit < 8; ++bit) {
if ((_payload[i] & (1 << bit)) != 0) {
supportedSetPointIndices.add((byte)(((i - 9) << 3) + bit));
}
}
}
}
public Set<Byte> getSupportedSetPointIndices() {
return supportedSetPointIndices;
}
public boolean isSupportedIndex(byte _index) {
return CollectionUtils.contains(supportedSetPointIndices, _index);
}
@Override
public String describe() {
return name() + " node: " + nodeId;
}
}

View File

@@ -0,0 +1,23 @@
package com.lanternsoftware.zwave.message.thermostat;
public enum ThermostatMode {
OFF((byte)0),
HEAT((byte)1),
COOL((byte)2),
AUTO((byte)3),
AUXILIARY((byte)4);
public final byte data;
ThermostatMode(byte _data) {
data = _data;
}
public static ThermostatMode fromByte(byte _bt) {
for (ThermostatMode mode : values()) {
if (mode.data == _bt)
return mode;
}
return null;
}
}

View File

@@ -0,0 +1,48 @@
package com.lanternsoftware.zwave.message.thermostat;
public enum ThermostatSetPointIndex {
HEATING((byte)1),
COOLING((byte)2),
FURNACE((byte)7),
DRY_AIR((byte)8),
MOIST_AIR((byte)9),
AUTO_CHANGEOVER((byte)10),
HEATING_ECON((byte)11),
COOLING_ECON((byte)12),
AWAY_HEATING((byte)13),
COOLING_HEATING((byte)14),
HEATING_MINIMUM((byte)101),
COOLING_MINIMUM((byte)102),
FURNACE_MINIMUM((byte)107),
DRY_AIR_MINIMUM((byte)108),
MOIST_AIR_MINIMUM((byte)109),
AUTO_CHANGEOVER_MINIMUM((byte)110),
HEATING_ECON_MINIMUM((byte)111),
COOLING_ECON_MINIMUM((byte)112),
AWAY_HEATING_MINIMUM((byte)113),
COOLING_HEATING_MINIMUM((byte)114),
HEATING_MAXIMUM((byte)201),
COOLING_MAXIMUM((byte)202),
FURNACE_MAXIMUM((byte)207),
DRY_AIR_MAXIMUM((byte)208),
MOIST_AIR_MAXIMUM((byte)209),
AUTO_CHANGEOVER_MAXIMUM((byte)210),
HEATING_ECON_MAXIMUM((byte)211),
COOLING_ECON_MAXIMUM((byte)212),
AWAY_HEATING_MAXIMUM((byte)213),
COOLING_HEATING_MAXIMUM((byte)214);
public final byte index;
ThermostatSetPointIndex(byte _index) {
index = _index;
}
public static ThermostatSetPointIndex fromIndex(byte _index) {
for (ThermostatSetPointIndex idx : values()) {
if (idx.index == _index)
return idx;
}
return null;
}
}

View File

@@ -0,0 +1,34 @@
package com.lanternsoftware.zwave.node;
import com.lanternsoftware.zwave.message.CommandClass;
import java.util.Set;
public class Node {
private byte id;
private Set<CommandClass> commandClasses;
public Node() {
}
public Node(byte _id, Set<CommandClass> _commandClasses) {
id = _id;
commandClasses = _commandClasses;
}
public byte getId() {
return id;
}
public void setId(byte _id) {
id = _id;
}
public Set<CommandClass> getCommandClasses() {
return commandClasses;
}
public void setCommandClasses(Set<CommandClass> _commandClasses) {
commandClasses = _commandClasses;
}
}

View File

@@ -0,0 +1,108 @@
package com.lanternsoftware.zwave.node;
import com.lanternsoftware.util.CollectionUtils;
import com.lanternsoftware.zwave.controller.Controller;
import com.lanternsoftware.zwave.message.IMessageSubscriber;
import com.lanternsoftware.zwave.message.MessageEngine;
import com.lanternsoftware.zwave.message.impl.ApplicationUpdateRequest;
import com.lanternsoftware.zwave.message.impl.ControllerInitialDataResponse;
import com.lanternsoftware.zwave.message.impl.GetControllerIdResponse;
import com.lanternsoftware.zwave.message.impl.NodeInfoRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
public class NodeManager {
private static final Logger logger = LoggerFactory.getLogger(NodeManager.class);
private Controller controller;
private ControllerInitialDataResponse initialDataResponse;
private GetControllerIdResponse controllerIdResponse;
private final Set<Byte> missingNodes = new HashSet<>();
private Map<Byte, Node> nodes = new HashMap<>();
public NodeManager(final Controller _controller) {
controller = _controller;
MessageEngine.subscribe(new IMessageSubscriber<ControllerInitialDataResponse>() {
@Override
public Class<ControllerInitialDataResponse> getHandledMessageClass() {
return ControllerInitialDataResponse.class;
}
@Override
public void onMessage(ControllerInitialDataResponse _response) {
synchronized (NodeManager.this) {
initialDataResponse = _response;
init();
}
}
});
MessageEngine.subscribe(new IMessageSubscriber<GetControllerIdResponse>() {
@Override
public Class<GetControllerIdResponse> getHandledMessageClass() {
return GetControllerIdResponse.class;
}
@Override
public void onMessage(GetControllerIdResponse _response) {
synchronized (NodeManager.this) {
controllerIdResponse = _response;
init();
}
}
});
MessageEngine.subscribe(new IMessageSubscriber<ApplicationUpdateRequest>() {
@Override
public Class<ApplicationUpdateRequest> getHandledMessageClass() {
return ApplicationUpdateRequest.class;
}
@Override
public void onMessage(ApplicationUpdateRequest _request) {
synchronized (NodeManager.this) {
if (missingNodes.remove(_request.getNodeId()))
nodes.put(_request.getNodeId(), new Node(_request.getNodeId(), CollectionUtils.asHashSet(_request.getCommandClasses())));
logger.debug("Received command classes for node: {}", _request.getNodeId());
requestNodeInfo();
}
}
});
}
private void init() {
if (!isStarted())
return;
missingNodes.clear();
logger.info("Node Ids:{}", CollectionUtils.transformToCommaSeparated(initialDataResponse.getNodeIds(), String::valueOf));
// missingNodes.addAll(CollectionUtils.filter(initialDataResponse.getNodeIds(), _b->_b != controllerIdResponse.getControllerId()));
requestNodeInfo();
}
private void requestNodeInfo() {
if (!missingNodes.isEmpty())
controller.send(new NodeInfoRequest(CollectionUtils.getFirst(missingNodes)));
else
notify();
}
private boolean isStarted() {
return ((initialDataResponse != null) && (controllerIdResponse != null));
}
public void waitForStartup() {
synchronized (this) {
if (!isStarted()) {
try {
wait();
} catch (InterruptedException _e) {
_e.printStackTrace();
}
}
}
}
}

View File

@@ -0,0 +1,24 @@
com.lanternsoftware.zwave.message.impl.ApplicationUpdateRequest
com.lanternsoftware.zwave.message.impl.BinarySwitchSetRequest
com.lanternsoftware.zwave.message.impl.ByteMessage
com.lanternsoftware.zwave.message.impl.ControllerCapabilitiesRequest
com.lanternsoftware.zwave.message.impl.ControllerCapabilitiesResponse
com.lanternsoftware.zwave.message.impl.ControllerInitialDataRequest
com.lanternsoftware.zwave.message.impl.ControllerInitialDataResponse
com.lanternsoftware.zwave.message.impl.DeviceManufacturerActionRequest
com.lanternsoftware.zwave.message.impl.GetControllerIdRequest
com.lanternsoftware.zwave.message.impl.GetControllerIdResponse
com.lanternsoftware.zwave.message.impl.MultilevelSensorGetRequest
com.lanternsoftware.zwave.message.impl.MultilevelSensorReportRequest
com.lanternsoftware.zwave.message.impl.MultilevelSwitchReportRequest
com.lanternsoftware.zwave.message.impl.MultilevelSwitchSetRequest
com.lanternsoftware.zwave.message.impl.NodeInfoRequest
com.lanternsoftware.zwave.message.impl.NodeInfoResponse
com.lanternsoftware.zwave.message.impl.SendDataRequest
com.lanternsoftware.zwave.message.impl.SendDataResponse
com.lanternsoftware.zwave.message.impl.ThermostatModeGetRequest
com.lanternsoftware.zwave.message.impl.ThermostatSetPointGetRequest
com.lanternsoftware.zwave.message.impl.ThermostatSetPointReportRequest
com.lanternsoftware.zwave.message.impl.ThermostatSetPointSetRequest
com.lanternsoftware.zwave.message.impl.ThermostatSetPointSupportedGetRequest
com.lanternsoftware.zwave.message.impl.ThermostatSetPointSupportedReportRequest