/** * 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.satel.internal.protocol; import java.util.Arrays; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Represents a message sent to (a command) or received from (a response) * {@link SatelModule}. It consists of one byte that specifies command type or * response status and certain number of payload bytes. Number of payload byte * depends on command type. The class allows to serialize a command to bytes and * deserialize a response from given bytes. It also computes and validates * message checksum. * * @author Krzysztof Goworek * @since 1.7.0 */ public class SatelMessage { private static final Logger logger = LoggerFactory.getLogger(SatelMessage.class); private byte command; private byte[] payload; protected static final byte[] EMPTY_PAYLOAD = new byte[0]; /** * Creates new instance with specified command code and payload. * * @param command * command code * @param payload * command payload */ public SatelMessage(byte command, byte[] payload) { this.command = command; this.payload = payload; } /** * Creates new instance with specified command code and empty payload. * * @param command * command code */ public SatelMessage(byte command) { this(command, EMPTY_PAYLOAD); } /** * Deserializes new message instance from specified byte buffer. * * @param buffer * bytes to deserialize a message from * @return deserialized message instance */ public static SatelMessage fromBytes(byte[] buffer) { // we need at least command and checksum if (buffer.length < 3) { logger.error("Invalid message length: {}", buffer.length); return null; } // check crc int receivedCrc = 0xffff & ((buffer[buffer.length - 2] << 8) | (buffer[buffer.length - 1] & 0xff)); int expectedCrc = calculateChecksum(buffer, buffer.length - 2); if (receivedCrc != expectedCrc) { logger.error("Invalid message checksum: received = {}, expected = {}", receivedCrc, expectedCrc); return null; } SatelMessage message = new SatelMessage(buffer[0], new byte[buffer.length - 3]); if (message.payload.length > 0) { System.arraycopy(buffer, 1, message.payload, 0, buffer.length - 3); } return message; } /** * Returns command byte. * * @return the command */ public byte getCommand() { return this.command; } /** * Returns the payload bytes. * * @return payload as byte array */ public byte[] getPayload() { return this.payload; } /** * Returns the message serialized as array of bytes with checksum calculated * at last two bytes. * * @return the message as array of bytes */ public byte[] getBytes() { byte buffer[] = new byte[this.payload.length + 3]; buffer[0] = this.command; if (this.payload.length > 0) { System.arraycopy(this.payload, 0, buffer, 1, this.payload.length); } int checksum = calculateChecksum(buffer, buffer.length - 2); buffer[buffer.length - 2] = (byte) ((checksum >> 8) & 0xff); buffer[buffer.length - 1] = (byte) (checksum & 0xff); return buffer; } private String getPayloadAsHex() { StringBuilder result = new StringBuilder(); for (int i = 0; i < this.payload.length; ++i) { if (i > 0) { result.append(" "); } result.append(String.format("%02X", this.payload[i])); } return result.toString(); } /** * Calculates a checksum for the specified buffer. * * @param buffer * the buffer to calculate. * @return the checksum value. */ private static int calculateChecksum(byte[] buffer, int length) { int checkSum = 0x147a; for (int i = 0; i < length; i++) { checkSum = ((checkSum << 1) | ((checkSum >> 15) & 1)); checkSum ^= 0xffff; checkSum += ((checkSum >> 8) & 0xff) + (buffer[i] & 0xff); } checkSum &= 0xffff; logger.trace("Calculated checksum = {}", String.format("%04X", checkSum)); return checkSum; } /** * {@inheritDoc} */ @Override public String toString() { return String.format("Message: command = %02X, payload = %s", this.command, getPayloadAsHex()); }; /** * {@inheritDoc} */ @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (!obj.getClass().equals(this.getClass())) { return false; } SatelMessage other = (SatelMessage) obj; if (other.command != this.command) { return false; } if (!Arrays.equals(other.payload, this.payload)) { return false; } return true; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + command; result = prime * result + Arrays.hashCode(payload); return result; } }