/**
* Copyright (c) 2010-2016 by the respective copyright holders.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.openhab.binding.dscalarm1.internal.protocol;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.apache.commons.lang.StringUtils;
import org.openhab.binding.dscalarm1.internal.DSCAlarmEventListener;
import org.openhab.binding.dscalarm1.internal.connector.DSCAlarmConnector;
import org.openhab.binding.dscalarm1.internal.connector.DSCAlarmConnectorType;
import org.openhab.binding.dscalarm1.internal.connector.DSCAlarmInterfaceType;
import org.openhab.binding.dscalarm1.internal.connector.SerialConnector;
import org.openhab.binding.dscalarm1.internal.connector.TCPConnector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A class that utilizes the API/TPI for the DSC IT-100 Serial Interface or the EyezOn Envisalink 3/2DS (TPI)
*
* @author Russell Stephens
* @since 1.6.0
*/
public class API {
private static final Logger logger = LoggerFactory.getLogger(API.class);
/** DSC Alarm connector type - Serial or TCP. **/
private DSCAlarmConnectorType connectorType = null;
/** DSC Alarm Interface Type - IT-100 or Envisalink. **/
private DSCAlarmInterfaceType interfaceType = null;
/** DSC Alarm Connector **/
private DSCAlarmConnector dscAlarmConnector = null;
/** DSC IT-100 Serial Interface serial port name. **/
private String serialPort = "";
/** DSC IT-100 Serial Interface default baud rate. **/
public static final int DEFAULT_BAUD_RATE = 9600;
/** EyezOn Envisalink 3/2DS IP address. **/
private String ipAddress = "192.168.0.100";
/** EyezOn Envisalink 3/2DS default TCP port. **/
private int tcpPort = 4025;
/** EyezOn Envisalink 3/2DS TCP Connection timeout in milliseconds **/
private static final int TCP_CONNECTION_TIMEOUT = 5000;
/** EyezOn Envisalink 3/2DS default password **/
private static final String DEFAULT_PASSWORD = "user";
/** DSC Alarm default user code **/
private static final String DEFAULT_USER_CODE = "1234";
/** Baud Rate for serial connection - set to default **/
private int baudRate = DEFAULT_BAUD_RATE;
/** User password for network login - set to default **/
private String password = DEFAULT_PASSWORD;
/** DSC Alarm user code for some commands - set to default **/
private String dscAlarmUserCode = DEFAULT_USER_CODE;
/** DSC Alarm connection variable **/
private boolean connected = false;
/** DSC Alarm valid baud rates **/
private int[] baudRates = { 9600, 19200, 38400, 57600, 115200 };
/**
* Constructor for Serial Connection
*
* @param sPort
* @param baud
*/
public API(String sPort, int baud, String userCode, DSCAlarmInterfaceType interfaceType) {
if (StringUtils.isNotBlank(sPort)) {
serialPort = sPort;
}
if (isValidBaudRate(baud)) {
baudRate = baud;
}
if (StringUtils.isNotBlank(userCode)) {
this.dscAlarmUserCode = userCode;
}
// The IT-100 requires 6 digit codes. Shorter codes are right padded with 0.
this.dscAlarmUserCode = StringUtils.rightPad(dscAlarmUserCode, 6, '0');
connectorType = DSCAlarmConnectorType.SERIAL;
if (interfaceType != null) {
this.interfaceType = interfaceType;
} else {
this.interfaceType = DSCAlarmInterfaceType.IT100;
}
}
/**
* Constructor for TCP connection
*
* @param ip
* @param password
* @param userCode
*/
public API(String ip, int port, String password, String userCode, DSCAlarmInterfaceType interfaceType) {
if (StringUtils.isNotBlank(ip)) {
ipAddress = ip;
}
tcpPort = port;
if (StringUtils.isNotBlank(password)) {
this.password = password;
}
if (StringUtils.isNotBlank(userCode)) {
this.dscAlarmUserCode = userCode;
}
connectorType = DSCAlarmConnectorType.TCP;
if (interfaceType != null) {
this.interfaceType = interfaceType;
} else {
this.interfaceType = DSCAlarmInterfaceType.ENVISALINK;
}
}
/**
* Add event listener
*
* @param listener
**/
public void addEventListener(DSCAlarmEventListener listener) {
dscAlarmConnector.addEventListener(listener);
}
/**
* Remove event listener
*
* @param listener
**/
public synchronized void removeEventListener(DSCAlarmEventListener listener) {
dscAlarmConnector.removeEventListener(listener);
}
/**
* Returns Connector Type
*
* @return connectorType
**/
public DSCAlarmConnectorType getConnectorType() {
return connectorType;
}
/**
* Method to check if baud rate is valid
*
* @return boolean
*/
private boolean isValidBaudRate(int baudRate) {
boolean isValid = false;
for (int i = 0; i < baudRates.length; i++) {
if (baudRate == baudRates[i]) {
isValid = true;
}
}
return isValid;
}
/**
* Return DSC Alarm User Code.
**/
public String getUserCode() {
return dscAlarmUserCode;
}
/**
* Sets the DSC Alarm Interface Type.
*
* @param interfaceType
*/
public void setDSCAlarmInterfaceType(DSCAlarmInterfaceType interfaceType) {
this.interfaceType = interfaceType;
}
/**
* Connect to the DSC Alarm System through the EyezOn Envisalink 3/2DS or the DSC IT-100.
**/
public boolean open() {
switch (connectorType) {
case SERIAL:
if (dscAlarmConnector == null) {
dscAlarmConnector = new SerialConnector(serialPort, baudRate);
}
break;
case TCP:
if (StringUtils.isNotBlank(ipAddress)) {
if (dscAlarmConnector == null) {
dscAlarmConnector = new TCPConnector(ipAddress, tcpPort, TCP_CONNECTION_TIMEOUT);
}
} else {
logger.error("open(): Unable to Make API TCP Connection!");
connected = false;
return connected;
}
break;
default:
break;
}
dscAlarmConnector.open();
connected = dscAlarmConnector.isConnected();
if (connected) {
if (interfaceType == DSCAlarmInterfaceType.ENVISALINK) {
sendCommand(APICode.NetworkLogin);
}
connected = dscAlarmConnector.isConnected();
}
if (!connected) {
logger.error("open(): Unable to Make API Connection!");
}
logger.debug("open(): Connected = {}, Connection Type: {}, Interface Type: {}", connected ? true : false,
connectorType, interfaceType);
return connected;
}
/**
* Close the connection to the DSC Alarm system.
*/
public boolean close() {
logger.debug("close(): Disconnecting from API Connection!");
dscAlarmConnector.close();
connected = dscAlarmConnector.isConnected();
return connected;
}
/**
* Read a API message received from the DSC Alarm system.
*/
public String read() {
return dscAlarmConnector.read();
}
/**
* Return Connected Status.
*/
public boolean isConnected() {
return dscAlarmConnector.isConnected();
}
/**
* Send an API command to the DSC Alarm system.
*
* @param apiCode
* @param apiData
* @return
*/
public boolean sendCommand(APICode apiCode, String... apiData) {
boolean successful = false;
boolean validCommand = false;
String command = apiCode.getCode();
String data = "";
switch (apiCode) {
case Poll: /* 000 */
case StatusReport: /* 001 */
validCommand = true;
break;
case LabelsRequest: /* 002 */
if (!interfaceType.equals(DSCAlarmInterfaceType.IT100)) {
break;
}
validCommand = true;
break;
case NetworkLogin: /* 005 */
if (!interfaceType.equals(DSCAlarmInterfaceType.ENVISALINK)) {
break;
}
if (password == null || password.length() < 1 || password.length() > 6) {
logger.error("sendCommand(): Password is invalid, must be between 1 and 6 chars", password);
break;
}
data = password;
validCommand = true;
break;
case DumpZoneTimers: /* 008 */
if (!interfaceType.equals(DSCAlarmInterfaceType.ENVISALINK)) {
break;
}
validCommand = true;
break;
case SetTimeDate: /* 010 */
Date date = new Date();
SimpleDateFormat dateTime = new SimpleDateFormat("HHmmMMddYY");
data = dateTime.format(date);
validCommand = true;
break;
case CommandOutputControl: /* 020 */
if (apiData[0] == null || !apiData[0].matches("[1-8]")) {
logger.error(
"sendCommand(): Partition number must be a single character string from 1 to 8, it was: {}",
apiData[0]);
break;
}
if (apiData[1] == null || !apiData[1].matches("[1-4]")) {
logger.error(
"sendCommand(): Output number must be a single character string from 1 to 4, it was: {}",
apiData[1]);
break;
}
data = apiData[0];
validCommand = true;
break;
case KeepAlive: /* 074 */
if (!interfaceType.equals(DSCAlarmInterfaceType.ENVISALINK)) {
break;
}
case PartitionArmControlAway: /* 030 */
case PartitionArmControlStay: /* 031 */
case PartitionArmControlZeroEntryDelay: /* 032 */
if (apiData[0] == null || !apiData[0].matches("[1-8]")) {
logger.error(
"sendCommand(): Partition number must be a single character string from 1 to 8, it was: {}",
apiData[0]);
break;
}
data = apiData[0];
validCommand = true;
break;
case PartitionArmControlWithUserCode: /* 033 */
case PartitionDisarmControl: /* 040 */
if (apiData[0] == null || !apiData[0].matches("[1-8]")) {
logger.error(
"sendCommand(): Partition number must be a single character string from 1 to 8, it was: {}",
apiData[0]);
break;
}
if (dscAlarmUserCode == null || dscAlarmUserCode.length() < 4 || dscAlarmUserCode.length() > 6) {
logger.error("sendCommand(): User Code is invalid, must be between 4 and 6 chars: {}",
dscAlarmUserCode);
break;
}
if (interfaceType.equals(DSCAlarmInterfaceType.IT100)) {
data = apiData[0] + String.format("%-6s", dscAlarmUserCode).replace(' ', '0');
} else {
data = apiData[0] + dscAlarmUserCode;
}
validCommand = true;
break;
case VirtualKeypadControl: /* 058 */
if (!interfaceType.equals(DSCAlarmInterfaceType.IT100)) {
break;
}
case TimeStampControl: /* 055 */
case TimeDateBroadcastControl: /* 056 */
case TemperatureBroadcastControl: /* 057 */
if (apiData[0] == null || !apiData[0].matches("[0-1]")) {
logger.error("sendCommand(): Value must be a single character string of 0 or 1: {}", apiData[0]);
break;
}
data = apiData[0];
validCommand = true;
break;
case TriggerPanicAlarm: /* 060 */
if (apiData[0] == null || !apiData[0].matches("[1-3]")) {
logger.error("sendCommand(): FAPcode must be a single character string from 1 to 3, it was: {}",
apiData[0]);
break;
}
data = apiData[0];
validCommand = true;
break;
case KeyStroke: /* 070 */
if (interfaceType.equals(DSCAlarmInterfaceType.ENVISALINK)) {
if (apiData[0] == null || apiData[0].length() != 1 || !apiData[0].matches("[0-9]|A|\\*|#")) {
logger.error(
"sendCommand(): \'keystroke\' must be a single character string from 0 to 9, *, #, or A, it was: {}",
apiData[0]);
break;
}
} else if (interfaceType.equals(DSCAlarmInterfaceType.IT100)) {
if (apiData[0] == null || apiData[0].length() != 1
|| !apiData[0].matches("[0-9]|\\*|#|F|A|P|[a-e]|<|>|=|\\^|L")) {
logger.error(
"sendCommand(): \'keystroke\' must be a single character string from 0 to 9, *, #, F, A, P, a to e, <, >, =, or ^, it was: {}",
apiData[0]);
break;
} else if (apiData[0].equals("L")) { /* Long Key Press */
try {
Thread.sleep(1500);
data = "^";
validCommand = true;
break;
} catch (InterruptedException e) {
logger.error("sendCommand(): \'keystroke\': Error with Long Key Press!");
break;
}
}
} else {
break;
}
data = apiData[0];
validCommand = true;
break;
case KeySequence: /* 071 */
if (!interfaceType.equals(DSCAlarmInterfaceType.ENVISALINK)) {
break;
}
if (apiData[0] == null || apiData[0].length() > 6 || !apiData[0].matches("(\\d|#|\\*)+")) {
logger.error(
"sendCommand(): \'keysequence\' must be a string of up to 6 characters consiting of 0 to 9, *, or #, it was: {}",
apiData[0]);
break;
}
data = apiData[0];
validCommand = true;
break;
case CodeSend: /* 200 */
if (dscAlarmUserCode == null || dscAlarmUserCode.length() < 4 || dscAlarmUserCode.length() > 6) {
logger.error("sendCommand(): Access Code is invalid, must be between 4 and 6 chars: {}",
apiData[0]);
break;
}
data = dscAlarmUserCode;
validCommand = true;
break;
default:
validCommand = false;
break;
}
if (validCommand) {
APICommand apiCommand = new APICommand();
apiCommand.setAPICommand(command, data);
dscAlarmConnector.write(apiCommand.toString());
successful = true;
logger.debug("sendCommand(): '{}' Command Sent - {}", apiCode, apiCommand);
} else {
logger.error("sendCommand(): Command Not Sent - Invalid!");
}
return successful;
}
}