All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.digitaldan.jomnilinkII.Connection Maven / Gradle / Ivy

Go to download

jomnilink - A java library for connecting to HAI/Leviton Omni and Lumina home automation controllers using the HAI OmniLink II protocol.

The newest version!
/**
 * Copyright (c) 2009-2021 Dan Cunningham
 *
 * 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 com.digitaldan.jomnilinkII;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.digitaldan.jomnilinkII.MessageTypes.*;
import com.digitaldan.jomnilinkII.MessageTypes.statuses.*;
import com.digitaldan.jomnilinkII.MessageTypes.systemevents.SystemEvent;

public class Connection extends Thread {
	private static final Logger logger = LoggerFactory.getLogger(Connection.class);

	private static int PACKET_TYPE_CLIENT_REQUEST_NEW_SESSION = 1;
	private static int PACKET_TYPE_CONTROLLER_ACKNOWLEDGE_NEW_SESSION = 2;
	private static int PACKET_TYPE_CLIENT_REQUEST_SECURE_CONNECTION = 3;
	private static int PACKET_TYPE_CONTROLLER_ACKNOWLEDGE_SECURE_CONNECTION = 4;
	private static int PACKET_TYPE_CLIENT_SESSION_TERMINATED = 5;
	private static int PACKET_TYPE_CONTROLLER_SESSION_TERMINATED = 6;
	private static int PACKET_TYPE_CONTROLLER_CANNOT_START_NEW_SESSION = 7;
	private static int PACKET_TYPE_OMNI_LINK_MESSAGE = 32;

	public static int MAX_PACKET_SIZE = 255;
	// Omni will kick you after 5 minutes of not receiveing anything
	public static int OMNI_TO = 60 * 5 * 1000;
	// Keep alive time, omni timeout minus one minute
	public static int PING_TO = OMNI_TO - (1000 * 60);

	public boolean debug;
	private boolean connected;
	private boolean ping;
	private long lastTXMessageTime;
	private Socket socket;
	private InputStream is;
	private OutputStream os;
	private int tx;
	private int rx;
	private Aes aes;
	private Object writeLock = new Object();
	private Exception lastException;
	private final BlockingQueue notifications;
	private final List notificationListeners;
	private final List disconnectListeners;
	private final BlockingQueue responses;
	private ConnectionWatchdog watchdog;

	public Connection(String address, int port, String key) throws Exception, IOException, UnknownHostException {
		ping = true;
		notifications = new LinkedBlockingQueue<>();
		responses = new LinkedBlockingQueue<>();
		lastException = null;
		notificationListeners = new CopyOnWriteArrayList();
		disconnectListeners = new CopyOnWriteArrayList();

		byte[] _key = hexStringToByteArray(key.replaceAll("\\W", ""));

		socket = new Socket(address, port);
		is = socket.getInputStream();
		os = socket.getOutputStream();
		socket.setSoTimeout(OMNI_TO);
		tx = 1;
		rx = 1;

		sendBytes(new OmniPacket(PACKET_TYPE_CLIENT_REQUEST_NEW_SESSION, null));
		OmniPacket rec = readBytes();

		if (rec.type() != PACKET_TYPE_CONTROLLER_ACKNOWLEDGE_NEW_SESSION) {
			throw new Exception("Controller not accepting new connections");
		}

		byte[] data = rec.data();

		int version = (data[0] << 8) + (data[1] << 0);

		logger.debug("Controller version {}", version);

		byte[] sessionid = new byte[5];
		System.arraycopy(data, 2, sessionid, 0, 5);

		for (int i = 0; i < 5; i++) {
			_key[i + 11] ^= sessionid[i];
		}

		aes = new Aes(_key);
		sendBytesEncrypted(new OmniPacket(PACKET_TYPE_CLIENT_REQUEST_SECURE_CONNECTION, sessionid));

		rec = readBytesEncrypted();
		if (rec.type() != PACKET_TYPE_CONTROLLER_ACKNOWLEDGE_SECURE_CONNECTION) {
			throw new Exception("Could not establish secure connection");
		}

		data = rec.data();

		for (int i = 0; i < 5; i++) {
			logger.trace("Data {} mine {} controllers {}" + data[i], i, sessionid[i], data[i]);
			if (data[i] != sessionid[i]) {
				throw new IOException("Controller returned wrong sessioid");
			}
		}
		connected = true;
		lastTXMessageTime = System.currentTimeMillis();

		this.setName("OmniReaderThread");
		this.start();

		Thread notificationHandlerThread = new Thread(new NotificationHandler(notifications, notificationListeners));
		notificationHandlerThread.setName("NotificationHandlerThread");
		notificationHandlerThread.start();

		ConnectionWatchdog watchdog = new ConnectionWatchdog();
		watchdog.setName("ConnectionWatchdogThread");
		watchdog.start();
	}

	public void disconnect() {
		connected = false;
		if (socket != null) {
			try {
				socket.close();
			} catch (Exception e) {
			}
		}
		notifications.offer(NotificationHandler.POISON);
	}

	public boolean connected() {
		return connected;
	}

	public Exception lastError() {
		return lastException;
	}

	public boolean autoPingOmni() {
		return ping;
	}

	public void autoPingOmni(boolean ping) {
		this.ping = ping;
	}

	public void addNotificationListener(NotificationListener listener) {
		notificationListeners.add(listener);
	}

	public void removeNotificationListener(NotificationListener listener) {
		notificationListeners.remove(listener);
	}

	public void addDisconnectListener(DisconnectListener listener) {
		disconnectListeners.add(listener);
	}

	public void removeDisconnectListener(DisconnectListener listener) {
		disconnectListeners.remove(listener);
	}

	public Message sendAndReceive(Message message)
			throws IOException, OmniNotConnectedException, OmniUnknownMessageTypeException {
		synchronized (writeLock) {
			try {
				if (!connected) {
					throw new OmniNotConnectedException(lastError());
				}
				sendBytesEncrypted(new OmniPacket(PACKET_TYPE_OMNI_LINK_MESSAGE, MessageFactory.toBytes(message)));
				OmniPacket omniPacket = responses.poll(30, TimeUnit.SECONDS);
				if (omniPacket == null) {
					// Throw exception if poll times out.
					throw new IOException("Response not returned from Omni within 30 seconds");
				}
				// If an error occurs on our other thread it saves the exception
				if (!connected) {
					throw new OmniNotConnectedException(lastError());
				}

				// Used to ping after a certain amount of time
				lastTXMessageTime = System.currentTimeMillis();
				return MessageFactory.fromBytes(omniPacket.data());
			} catch (InterruptedException ex) {
				throw new IOException(ex);
			}
		}
	}

	@Override
	public void run() {
		while (connected) {
			try {
				OmniPacket omniPacket = readBytesEncryptedExtended();
				if ((omniPacket.seq() == 0 && omniPacket.type() == PACKET_TYPE_OMNI_LINK_MESSAGE)) {
					notifications.put(MessageFactory.fromBytes(omniPacket.data()));
					logger.debug("run: NOTIFICATION: Added message with type {}", omniPacket.type);
				} else if (omniPacket.type() == PACKET_TYPE_OMNI_LINK_MESSAGE) {
					responses.put(omniPacket);
				} else {
					throw new IOException("Non omnilink message");
				}
			} catch (OmniUnknownMessageTypeException e) {
				// ignore
				logger.debug("run: Uknown Messgage type {}  Continuing", e.getUnknowMessageType());
			} catch (Exception e) {
				disconnect();
				lastException = e;
				// tell listeners about exception
				notifyDisconnectHandlers(lastException);
			}
		}
		logger.debug("run: not connected, thread exiting");
	}

	/*
	 * The following procedure is used to encrypt Omni-Link II application data:
	 */
	private void sendBytesEncrypted(OmniPacket p) throws IOException {
		/*
		 * 1. Process data in 128-bit (16-byte) blocks. If available data does not fill
		 * a 16-byte block, the data is left-justified and padded on the right with
		 * zeros to fill the block.
		 */
		logger.trace("TX: {}", bytesToString(p.data()));
		int txlength = (p.data().length + 15) & ~0xF;
		byte[] paddedData = new byte[txlength];
		System.arraycopy(p.data(), 0, paddedData, 0, p.data().length);
		for (int i = p.data().length; i < txlength; i++) {
			paddedData[i] = 0x00;
		}

		/*
		 * 2. Modify the first two bytes of the 16-byte encryption block by performing a
		 * logical XOR operation with the two bytes of the “message sequence number” in
		 * the HAI header (i.e., XOR the first byte of the encryption block with the MSB
		 * of the message sequence number, and XOR the second byte of the encryption
		 * block with the LSB of the message sequence number).
		 */
		for (int i = 0; i < (txlength / 16); i++) {
			paddedData[0 + (16 * i)] ^= (tx >> 8) & 0xFF;
			paddedData[1 + (16 * i)] ^= (tx) & 0xFF;
		}

		/*
		 * 3. Encrypt the 16-byte block using the AES encryption algorithm and the
		 * 128-bit session key that was negotiated when the client and controller
		 * established the secure connection.
		 */
		byte[] encData;
		try {
			encData = aes.encrypt(paddedData);
		} catch (Exception e) {
			throw new IOException(e.getMessage());
		}

		/*
		 * 4. Process the next block of data until all data has been processed.
		 */
		sendBytes(new OmniPacket(p.type(), encData));
	}

	private void sendBytes(OmniPacket p) throws IOException {
		ByteArrayOutputStream bout = new ByteArrayOutputStream();
		DataOutputStream dout = new DataOutputStream(bout);
		dout.writeShort(tx);
		dout.writeByte(p.type());
		dout.writeByte(0);
		if (p.data() != null) {
			dout.write(p.data());
		}
		os.write(bout.toByteArray());
		os.flush();
		tx++;
		if (tx >= 65535) {
			tx = 1;
		}
	}

	private OmniPacket readBytesEncrypted() throws IOException, SocketTimeoutException {
		OmniPacket p = readBytes();
		logger.trace("Enc Dec " + bytesToString(p.data()));
		if (p.data().length == 0) {
			return p;
		}
		byte[] decData = aes.decrypt(p.data());
		/* XOR data */
		for (int i = 0; i < (decData.length / 16); i++) {
			decData[0 + (16 * i)] ^= (p.seq >> 8) & 0xFF;
			decData[1 + (16 * i)] ^= (p.seq) & 0xFF;
		}
		logger.trace("Data Dec {}", bytesToString(decData));
		return new OmniPacket(p.seq, p.type(), decData);
	}

	private OmniPacket readBytesEncryptedExtended() throws IOException, SocketTimeoutException {
		// Notifications have thrown a bit of a curve ball, its possible to have
		// two packets on the wire, but because the length of the packet
		// is encrypted we have to peek into the first 16 bytes, decrypt those
		// bytes and get the length, this makes the following code tricky and
		// unattractive,
		DataInputStream dis = new DataInputStream(is);
		logger.trace("Bytes available for reading: {}", is.available());
		int seq = dis.readUnsignedShort();
		int type = dis.readUnsignedByte();
		int reserved = dis.readUnsignedByte();

		byte[] encData = new byte[16];
		dis.readFully(encData);
		byte[] decData = aes.decrypt(encData);

		decData[0] ^= (seq >> 8) & 0xFF;
		decData[1] ^= (seq) & 0xFF;

		// not all messages are omnilink
		if (type != PACKET_TYPE_OMNI_LINK_MESSAGE) {
			logger.trace("NON OMNI LINK PACKET: {} RX Bytes: {}", type, bytesToString(decData));
			return new OmniPacket(seq, type, decData);
		}

		// continue with omnilink decoding
		int start = decData[0] & 0xFF;
		int length = decData[1] & 0xFF;

		if (start != Message.MESG_START) {
			logger.debug("invalid start char ({})", start);
		}
		// throw new IOException("invalid start char (" + start + ")");
		if (length < 0) {
			throw new IOException("invalid message length (" + length + ")");
		}

		logger.trace("Omni message Length {}", length);

		// length plus start and crc fields, round up to next 16, minus the bytes we
		// have already read
		int readLength = ((((length + 3) / 16) + 1) * 16) - 16;

		logger.trace("Additional bytes to read {}", readLength);

		if (readLength > 0) {
			// buffer for existing 16 bytes of data plus any on the wire
			byte[] decData2 = new byte[decData.length + readLength];
			encData = new byte[readLength];
			// copy the data we already have from decData to decData2
			System.arraycopy(decData, 0, decData2, 0, decData.length);
			// read the rest
			dis.readFully(encData);
			// add decrypted data to the buffer
			aes.decrypt(encData, 0, readLength, decData2, decData.length);
			/* XOR data */
			for (int i = 1; i < (decData2.length / 16); i++) {
				decData2[0 + (16 * i)] ^= (seq >> 8) & 0xFF;
				decData2[1 + (16 * i)] ^= (seq) & 0xFF;
			}
			decData = decData2;
		}

		logger.trace("RX: {} Data still available after read {}", bytesToString(decData), is.available());

		return new OmniPacket(seq, type, decData);
	}

	private OmniPacket readBytes() throws IOException, SocketTimeoutException {
		byte[] data = new byte[MAX_PACKET_SIZE];
		int cnt = is.read(data);

		DataInputStream dis = new DataInputStream(new ByteArrayInputStream(data));

		int seq = dis.readUnsignedShort();
		int type = dis.readUnsignedByte();
		int reserved = dis.readUnsignedByte();

		byte[] msgdata = new byte[cnt - 4];
		dis.readFully(msgdata);

		return new OmniPacket(seq, type, msgdata);
	}

	public void enableNotifications() throws IOException, OmniNotConnectedException, OmniInvalidResponseException,
			OmniUnknownMessageTypeException {
		Message msg = sendAndReceive(Notifications.enable());
		if (msg.getMessageType() != Message.MESG_TYPE_ACK) {
			throw new OmniInvalidResponseException(msg);
		}
	}

	public SystemInformation reqSystemInformation() throws IOException, OmniNotConnectedException,
			OmniInvalidResponseException, OmniUnknownMessageTypeException {
		Message msg = sendAndReceive(ReqSystemInformation.getInstance());
		if (msg.getMessageType() != Message.MESG_TYPE_SYS_INFO) {
			throw new OmniInvalidResponseException(msg);
		}
		return (SystemInformation) msg;
	}

	public SystemStatus reqSystemStatus() throws IOException, OmniNotConnectedException, OmniInvalidResponseException,
			OmniUnknownMessageTypeException {
		Message msg = sendAndReceive(ReqSystemStatus.getInstance());
		if (msg.getMessageType() != Message.MESG_TYPE_SYS_STATUS) {
			throw new OmniInvalidResponseException(msg);
		}
		return (SystemStatus) msg;
	}

	public SystemTroubles reqSystemTroubles() throws IOException, OmniNotConnectedException,
			OmniInvalidResponseException, OmniUnknownMessageTypeException {
		Message msg = sendAndReceive(ReqSystemTroubles.getInstance());
		if (msg.getMessageType() != Message.MESG_TYPE_SYS_TROUBLES) {
			throw new OmniInvalidResponseException(msg);
		}
		return (SystemTroubles) msg;
	}

	public SystemFeatures reqSystemFeatures() throws IOException, OmniNotConnectedException,
			OmniInvalidResponseException, OmniUnknownMessageTypeException {
		Message msg = sendAndReceive(ReqSystemFeatures.getInstance());
		if (msg.getMessageType() != Message.MESG_TYPE_SYS_FEATURES) {
			throw new OmniInvalidResponseException(msg);
		}
		return (SystemFeatures) msg;
	}

	public SystemFormats reqSystemFormats() throws IOException, OmniNotConnectedException, OmniInvalidResponseException,
			OmniUnknownMessageTypeException {
		Message msg = sendAndReceive(ReqSystemFormats.getInstance());
		if (msg.getMessageType() != Message.MESG_TYPE_SYS_FORMATS) {
			throw new OmniInvalidResponseException(msg);
		}
		return (SystemFormats) msg;
	}

	public ObjectTypeCapacities reqObjectTypeCapacities(int objectType) throws IOException, OmniNotConnectedException,
			OmniInvalidResponseException, OmniUnknownMessageTypeException {
		Message msg = sendAndReceive(ReqObjectTypeCapacities.builder().objectType(objectType).build());
		if (msg.getMessageType() != Message.MESG_TYPE_OBJ_CAPACITY) {
			throw new OmniInvalidResponseException(msg);
		}
		return (ObjectTypeCapacities) msg;
	}

	public Message reqObjectProperties(int objectType, int objectNum, int direction, int filter1, int filter2,
			int filter3) throws IOException, OmniNotConnectedException, OmniInvalidResponseException,
			OmniUnknownMessageTypeException {
		ReqObjectProperties reqObjectProperties = ReqObjectProperties.builder().objectType(objectType)
				.objectNumber(objectNum).direction(direction).filter1(filter1).filter2(filter2).filter3(filter3)
				.build();
		Message msg = sendAndReceive(reqObjectProperties);
		if (msg.getMessageType() != Message.MESG_TYPE_OBJ_PROP
				&& msg.getMessageType() != Message.MESG_TYPE_END_OF_DATA) {
			throw new OmniInvalidResponseException(msg);
		}
		return msg;
	}

	public ObjectStatus reqObjectStatus(int objectType, int startObject, int endObject) throws IOException,
			OmniNotConnectedException, OmniInvalidResponseException, OmniUnknownMessageTypeException {
		return reqObjectStatus(objectType, startObject, endObject, false);
	}

	public ExtendedObjectStatus reqExtendedObjectStatus(int objectType, int startObject, int endObject)
			throws IOException, OmniNotConnectedException, OmniInvalidResponseException,
			OmniUnknownMessageTypeException {
		return (ExtendedObjectStatus) reqObjectStatus(objectType, startObject, endObject, true);
	}

	public ObjectStatus reqObjectStatus(int objectType, int startObject, int endObject, boolean extended)
			throws IOException, OmniNotConnectedException, OmniInvalidResponseException,
			OmniUnknownMessageTypeException {
		Status[] s = null;
		switch (objectType) {
			case Message.OBJ_TYPE_ZONE : {
				if (extended) {
					s = new ExtendedZoneStatus[endObject - startObject + 1];
				} else {
					s = new ZoneStatus[endObject - startObject + 1];
				}
			}
				break;
			case Message.OBJ_TYPE_UNIT : {
				if (extended) {
					s = new ExtendedUnitStatus[endObject - startObject + 1];
				} else {
					s = new UnitStatus[endObject - startObject + 1];
				}
			}
				break;
			case Message.OBJ_TYPE_AREA : {
				if (extended) {
					s = new ExtendedAreaStatus[endObject - startObject + 1];
				} else {
					s = new AreaStatus[endObject - startObject + 1];
				}
			}
				break;
			case Message.OBJ_TYPE_THERMO : {
				if (extended) {
					s = new ExtendedThermostatStatus[endObject - startObject + 1];
				} else {
					s = new ThermostatStatus[endObject - startObject + 1];
				}
			}
				break;
			case Message.OBJ_TYPE_MESG : {
				if (extended) {
					s = new ExtendedMessageStatus[endObject - startObject + 1];
				} else {
					s = new MessageStatus[endObject - startObject + 1];
				}
			}
				break;
			case Message.OBJ_TYPE_AUX_SENSOR : {
				if (extended) {
					s = new ExtendedAuxSensorStatus[endObject - startObject + 1];
				} else {
					s = new AuxSensorStatus[endObject - startObject + 1];
				}
			}
				break;
			case Message.OBJ_TYPE_AUDIO_ZONE : {
				if (extended) {
					s = new ExtendedAudioZoneStatus[endObject - startObject + 1];
				} else {
					s = new AudioZoneStatus[endObject - startObject + 1];
				}
			}
				break;
			case Message.OBJ_TYPE_EXP : {
				if (extended) {
					s = new ExtendedExpansionStatus[endObject - startObject + 1];
				} else {
					s = new ExpansionStatus[endObject - startObject + 1];
				}
			}
				break;
			case Message.OBJ_TYPE_USER_SETTING : {
				if (extended) {
					s = new ExtendedUserSettingStatus[endObject - startObject + 1];
				} else {
					s = new UserSettingStatus[endObject - startObject + 1];
				}
			}
				break;
			case Message.OBJ_TYPE_CONTROL_READER : {
				if (extended) {
					s = new ExtendedAccessControlReaderStatus[endObject - startObject + 1];
				} else {
					s = new AccessControlReaderStatus[endObject - startObject + 1];
				}
			}
				break;
			case Message.OBJ_TYPE_CONTROL_LOCK : {
				if (extended) {
					s = new ExtendedAccessControlReaderLockStatus[endObject - startObject + 1];
				} else {
					s = new AccessControlReaderLockStatus[endObject - startObject + 1];
				}
			}
				break;
			default :
				break;
		}
		int current = startObject;
		int next = current;
		while (current <= endObject) {
			next += 25;
			if (next > endObject) {
				next = endObject;
			}

			Message msg = null;
			if (extended) {
				msg = sendAndReceive(ReqExtendedObjectStatus.builder().objectType(objectType).startObject(current)
						.endObject(next).build());
			} else {
				msg = sendAndReceive(
						ReqObjectStatus.builder().objectType(objectType).startObject(current).endObject(next).build());
			}

			if (msg.getMessageType() != Message.MESG_TYPE_OBJ_STATUS
					&& msg.getMessageType() != Message.MESG_TYPE_EXT_OBJ_STATUS) {
				throw new OmniInvalidResponseException(msg);
			}
			System.arraycopy(((ObjectStatus) msg).getStatuses(), 0, s, current - startObject, next - current + 1);
			current = next;
			if (current == endObject) {
				break;
			}
			logger.trace("Current: {}  end: {} ", current, endObject);
		}
		return ObjectStatus.builder().statusType(objectType).statuses(s).build();
	}

	public Message reqAudioSourceStatus(int source, int position) throws IOException, OmniNotConnectedException,
			OmniInvalidResponseException, OmniUnknownMessageTypeException {
		Message msg = sendAndReceive(ReqAudioSourceStatus.builder().source(source).position(position).build());
		if (msg.getMessageType() != Message.MESG_TYPE_AUDIO_SOURCE_STATUS
				&& msg.getMessageType() != Message.MESG_TYPE_END_OF_DATA) {
			throw new OmniInvalidResponseException(msg);
		}
		return msg;
	}

	public ZoneReadyStatus reqZoneReadyStatus() throws IOException, OmniNotConnectedException,
			OmniInvalidResponseException, OmniUnknownMessageTypeException {
		Message msg = sendAndReceive(ReqZoneReadyStatus.getInstance());
		if (msg.getMessageType() != Message.MESG_TYPE_ZONE_READY) {
			throw new OmniInvalidResponseException(msg);
		}
		return (ZoneReadyStatus) msg;
	}

	public ConnectedSecurityStatus reqConnectedSecurityStatus() throws IOException, OmniNotConnectedException,
			OmniInvalidResponseException, OmniUnknownMessageTypeException {
		Message msg = sendAndReceive(ReqConnectedSecurityStatus.getInstance());
		if (msg.getMessageType() != Message.MESG_TYPE_CONN_SEC_STATUS) {
			throw new OmniInvalidResponseException(msg);
		}
		return (ConnectedSecurityStatus) msg;
	}

	public Message readEventRecord(int number, int direction) throws IOException, OmniNotConnectedException,
			OmniInvalidResponseException, OmniUnknownMessageTypeException {
		Message msg = sendAndReceive(ReadEventRecord.builder().eventNumber(number).direction(direction).build());
		if (msg.getMessageType() != Message.MESG_TYPE_EVENT_LOG_DATA
				&& msg.getMessageType() != Message.MESG_TYPE_END_OF_DATA) {
			throw new OmniInvalidResponseException(msg);
		}
		return msg;
	}

	public Message readName(int objectType, int objectNumber) throws IOException, OmniNotConnectedException,
			OmniInvalidResponseException, OmniUnknownMessageTypeException {
		Message msg = sendAndReceive(ReadName.builder().objectType(objectType).objectNumber(objectNumber).build());
		if (msg.getMessageType() != Message.MESG_TYPE_NAME_DATA
				&& msg.getMessageType() != Message.MESG_TYPE_END_OF_DATA) {
			throw new OmniInvalidResponseException(msg);
		}
		return msg;
	}

	public void writeName(int objectType, int objectNumber, String name) throws IOException, OmniNotConnectedException,
			OmniInvalidResponseException, OmniUnknownMessageTypeException {
		Message msg = sendAndReceive(
				WriteName.builder().objectType(objectType).objectNumber(objectNumber).name(name).build());
		if (msg.getMessageType() != Message.MESG_TYPE_ACK) {
			throw new OmniInvalidResponseException(msg);
		}
	}

	public void connectedSecurityCommand(int command, int partition, int digit1, int digit2, int digit3, int digit4,
			int digit5, int digit6) throws IOException, OmniNotConnectedException, OmniInvalidResponseException,
			OmniUnknownMessageTypeException {
		ConnectedSecurityCommand connectedSecurityCommand = ConnectedSecurityCommand.builder().command(command)
				.partition(partition).digit1(digit1).digit2(digit2).digit3(digit3).digit4(digit4).digit5(digit5)
				.digit6(digit6).build();

		Message msg = sendAndReceive(connectedSecurityCommand);
		if (msg.getMessageType() != Message.MESG_TYPE_ACK) {
			throw new OmniInvalidResponseException(msg);
		}
	}

	public void controllerCommand(int command, int p1, int p2) throws IOException, OmniNotConnectedException,
			OmniInvalidResponseException, OmniUnknownMessageTypeException {
		Message msg = sendAndReceive(CommandMessage.builder().command(command).parameter1(p1).parameter2(p2).build());
		if (msg.getMessageType() != Message.MESG_TYPE_ACK) {
			throw new OmniInvalidResponseException(msg);
		}
	}

	public void setTimeCommand(int year, int month, int day, int dayOfWeek, int hour, int minute,
			boolean daylightSavings) throws IOException, OmniNotConnectedException, OmniInvalidResponseException,
			OmniUnknownMessageTypeException {
		Message msg = sendAndReceive(SetTimeCommand.builder().year(year).month(month).day(day).dayOfWeek(dayOfWeek)
				.hour(hour).minute(minute).daylightSavings(daylightSavings).build());
		if (msg.getMessageType() != Message.MESG_TYPE_ACK) {
			throw new OmniInvalidResponseException(msg);
		}
	}

	public void activateKeypadEmergency(int area, int emergencyType) throws IOException, OmniNotConnectedException,
			OmniInvalidResponseException, OmniUnknownMessageTypeException {
		Message msg = sendAndReceive(ActivateKeypadEmergency.builder().area(area).emergencyType(emergencyType).build());
		if (msg.getMessageType() != Message.MESG_TYPE_ACK) {
			throw new OmniInvalidResponseException(msg);
		}
	}

	public SecurityCodeValidation reqSecurityCodeValidation(int area, int digit1, int digit2, int digit3, int digit4)
			throws IOException, OmniNotConnectedException, OmniInvalidResponseException,
			OmniUnknownMessageTypeException {
		Message msg = sendAndReceive(ReqSecurityCodeValidation.builder().area(area).digit1(digit1).digit2(digit2)
				.digit3(digit3).digit4(digit4).build());
		if (msg.getMessageType() != Message.MESG_TYPE_SEC_CODE_VALID) {
			throw new OmniInvalidResponseException(msg);
		}
		return (SecurityCodeValidation) msg;
	}

	private static byte[] hexStringToByteArray(String s) {
		int len = s.length();
		byte[] data = new byte[len / 2];
		for (int i = 0; i < len; i += 2) {
			data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i + 1), 16));
		}
		return data;
	}

	private String bytesToString(byte[] bytes) {
		StringBuffer buff = new StringBuffer();
		for (int i = 0; i < bytes.length; i++) {
			buff.append("0x");
			buff.append(Integer.toString((bytes[i] & 0xff) + 0x100, 16).substring(1));
			buff.append(" ");
		}
		return buff.toString();
	}

	private class OmniPacket {
		private int seq;
		private int type;
		private byte[] data;

		public OmniPacket(int seq, int type, byte[] data) {
			this.seq = seq;
			this.type = type;
			this.data = data;
		}

		public OmniPacket(int type, byte[] data) {
			this.type = type;
			this.data = data;
		}

		public int seq() {
			return seq;
		}

		public int type() {
			return type;
		}

		public byte[] data() {
			return data;
		}
	}

	private void notifyDisconnectHandlers(Exception e) {
		for (DisconnectListener l : disconnectListeners) {
			l.notConnectedEvent(e);
		}
	}

	private class ConnectionWatchdog extends Thread {
		@Override
		public void run() {
			this.setName("ConnectionWatchdog");
			while (connected) {
				if (ping && System.currentTimeMillis() >= PING_TO + lastTXMessageTime) {
					logger.debug("Pinging Server");
					try {
						reqSystemStatus();
					} catch (Exception ignored) {
					} ;
				}
				try {
					sleep(1000);
				} catch (InterruptedException e) {
				}
			}
		}
	}

	private static class NotificationHandler implements Runnable {

		public static final Message POISON = new Message() {
			@Override
			public int getMessageType() {
				return 0;
			}
		};

		private final BlockingQueue notifications;
		private final List listeners;
		private boolean alive = true;

		private NotificationHandler(BlockingQueue notifications, List listeners) {
			this.notifications = notifications;
			this.listeners = listeners;
		}

		@Override
		public void run() {
			while (alive) {
				try {
					Message message = notifications.take();
					if (message == POISON) {
						alive = false;
					} else {
						for (NotificationListener listener : listeners) {
							if (message instanceof ObjectStatus) {
								listener.objectStatusNotification((ObjectStatus) message);
							} else if (message instanceof OtherEventNotifications) {
								for (int i : ((OtherEventNotifications) message).getNotifications()) {
									SystemEvent se = SystemEvent.fromEvent(i);
									if (se != null) {
										listener.systemEventNotification(se);
									}
								}
							} else {
								logger.debug("Unhandled notficiation message: {}", message);
							}
						}
					}
				} catch (Throwable t) {
					// Catch all exceptions to prevent notification thread from dying.
					logger.error("Notification Handler Caught Exception", t);
					t.printStackTrace();
				}
			}
		}
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy