/**
* 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.maxcul.internal.messages;
import org.openhab.binding.maxcul.internal.message.sequencers.MessageSequencer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Base Message Class acts as the parent to messages sent and received by the
* CUL device to communicate with the Max! devices.
*
* @author Paul Hampson (cyclingengineer)
* @since 1.6.0
*/
public class BaseMsg {
public byte len;
public byte msgCount;
public byte msgFlag;
public byte msgTypeRaw;
public MaxCulMsgType msgType;
public byte[] srcAddr = new byte[3];
public String srcAddrStr;
public byte[] dstAddr = new byte[3];
public String dstAddrStr;
public byte groupid;
public byte[] payload;
public String rawMsg;
private boolean fastSend = false;
private boolean flgReadyToSend = false;
private static final Logger logger = LoggerFactory.getLogger(BaseMsg.class);
/* define number of characters per byte */
private final static int PKT_CHARS_PER_BYTE = 2;
/* packet structure in terms of character representations of bytes */
private final static int PKT_POS_MSG_LEN = 1; /* Account for 'Z' at start */
private final static int PKT_POS_MSG_LEN_LEN = PKT_CHARS_PER_BYTE;
private final static int PKT_POS_MSG_START = PKT_POS_MSG_LEN + PKT_POS_MSG_LEN_LEN;
private final static int PKT_POS_MSG_COUNT = PKT_POS_MSG_LEN + PKT_POS_MSG_LEN_LEN;
private final static int PKT_POS_MSG_COUNT_LEN = PKT_CHARS_PER_BYTE;
private final static int PKT_POS_MSG_FLAG = PKT_POS_MSG_COUNT + PKT_POS_MSG_COUNT_LEN;
private final static int PKT_POS_MSG_FLAG_LEN = PKT_CHARS_PER_BYTE;
private final static int PKT_POS_MSG_TYPE = PKT_POS_MSG_FLAG + PKT_POS_MSG_FLAG_LEN;
private final static int PKT_POS_MSG_TYPE_LEN = PKT_CHARS_PER_BYTE;
private final static int PKT_POS_SRC_ADDR = PKT_POS_MSG_TYPE + PKT_POS_MSG_TYPE_LEN;
private final static int PKT_POS_SRC_ADDR_LEN = 3 * PKT_CHARS_PER_BYTE;
private final static int PKT_POS_DST_ADDR = PKT_POS_SRC_ADDR + PKT_POS_SRC_ADDR_LEN;
private final static int PKT_POS_DST_ADDR_LEN = 3 * PKT_CHARS_PER_BYTE;
private final static int PKT_POS_GROUP_ID = PKT_POS_DST_ADDR + PKT_POS_DST_ADDR_LEN;
private final static int PKT_POS_GROUP_ID_LEN = PKT_CHARS_PER_BYTE;
private final static int PKT_POS_PAYLOAD_START = PKT_POS_GROUP_ID + PKT_POS_GROUP_ID_LEN;
private MessageSequencer msgSequencer = null;
/**
* Constructor based on received message
*
* @param rawMsg
*/
public BaseMsg(String rawMsg) {
BaseMsg pkt = this;
this.rawMsg = rawMsg;
pkt.len = (byte) (Integer.parseInt(rawMsg.substring(1, 3), 16) & 0xFF); /*
* length
* of
* packet
*/
if (pkt.len != ((rawMsg.length() - 5) / 2)) /*
* -5 => 'Z' and len byte (2
* chars) and checksum at
* end?, div by two as it is
* a hex string
*/
{
logger.error("Unable to process packet " + rawMsg + ". Length is not correct.");
pkt.len = 0; /* indicate not a valid packet */
return;
}
pkt.msgCount = (byte) (Integer
.parseInt(rawMsg.substring(PKT_POS_MSG_COUNT, PKT_POS_MSG_COUNT + PKT_POS_MSG_COUNT_LEN), 16) & 0xFF);
pkt.msgFlag = (byte) (Integer
.parseInt(rawMsg.substring(PKT_POS_MSG_FLAG, PKT_POS_MSG_FLAG + PKT_POS_MSG_FLAG_LEN), 16) & 0xFF);
pkt.msgTypeRaw = (byte) (Integer
.parseInt(rawMsg.substring(PKT_POS_MSG_TYPE, PKT_POS_MSG_TYPE + PKT_POS_MSG_TYPE_LEN), 16) & 0xFF);
pkt.msgType = MaxCulMsgType.fromByte(pkt.msgTypeRaw);
pkt.srcAddrStr = rawMsg.substring(PKT_POS_SRC_ADDR, PKT_POS_SRC_ADDR + PKT_POS_SRC_ADDR_LEN);
for (int idx = PKT_POS_SRC_ADDR; idx < PKT_POS_SRC_ADDR + PKT_POS_SRC_ADDR_LEN; idx += PKT_CHARS_PER_BYTE) {
pkt.srcAddr[(idx - PKT_POS_SRC_ADDR) / PKT_CHARS_PER_BYTE] = (byte) (Integer
.parseInt(rawMsg.substring(idx, idx + PKT_CHARS_PER_BYTE), 16) & 0xFF);
}
pkt.dstAddrStr = rawMsg.substring(PKT_POS_DST_ADDR, PKT_POS_DST_ADDR + PKT_POS_DST_ADDR_LEN);
for (int idx = PKT_POS_DST_ADDR; idx < PKT_POS_DST_ADDR + PKT_POS_DST_ADDR_LEN; idx += PKT_CHARS_PER_BYTE) {
pkt.dstAddr[(idx - PKT_POS_DST_ADDR) / PKT_CHARS_PER_BYTE] = (byte) (Integer
.parseInt(rawMsg.substring(idx, idx + PKT_CHARS_PER_BYTE), 16) & 0xFF);
}
pkt.groupid = (byte) (Integer
.parseInt(rawMsg.substring(PKT_POS_GROUP_ID, PKT_POS_GROUP_ID + PKT_POS_GROUP_ID_LEN), 16) & 0xFF);
/*
* pkt.len accounts for message only (i.e. not first 3 chars) - so
* offset for characters that precede the message
*/
int payloadStrLen = ((pkt.len) * PKT_CHARS_PER_BYTE) + PKT_POS_MSG_START - PKT_POS_PAYLOAD_START;
int payloadByteLen = payloadStrLen / PKT_CHARS_PER_BYTE;
pkt.payload = new byte[payloadByteLen];
for (int payIdx = PKT_POS_PAYLOAD_START; payIdx < (PKT_POS_PAYLOAD_START
+ payloadStrLen); payIdx += PKT_CHARS_PER_BYTE) {
pkt.payload[(payIdx - PKT_POS_PAYLOAD_START) / PKT_CHARS_PER_BYTE] = (byte) (Integer
.parseInt(rawMsg.substring(payIdx, payIdx + PKT_CHARS_PER_BYTE), 16) & 0xFF);
}
}
/**
* This constructor creates an incomplete raw message ready for sending.
* Payload must be set before sending.
*
* @param msgCount
* Message Counter
* @param msgFlag
* Message flag
* @param msgType
* the message type
* @param groupId
* Group ID
* @param srcAddr
* Source address of controller
* @param dstAddr
* Dest addr of device
*/
public BaseMsg(byte msgCount, byte msgFlag, MaxCulMsgType msgType, byte groupId, String srcAddr, String dstAddr) {
buildHeader(msgCount, msgFlag, msgType, groupId, srcAddr, dstAddr);
}
/**
* This constructor creates a raw message ready for sending.
*
* @param msgCount
* Message Counter
* @param msgFlag
* Message flag
* @param msgType
* the message type
* @param groupId
* Group ID
* @param srcAddr
* Source address of controller
* @param dstAddr
* Dest addr of device
* @param payload
* payload of message
*/
public BaseMsg(byte msgCount, byte msgFlag, MaxCulMsgType msgType, byte groupId, String srcAddr, String dstAddr,
byte[] payload) {
this(msgCount, msgFlag, msgType, groupId, srcAddr, dstAddr);
appendPayload(payload);
}
private void buildHeader(byte msgCount, byte msgFlag, MaxCulMsgType msgType, byte groupId, String srcAddr,
String dstAddr) {
StringBuilder sb = new StringBuilder();
this.msgCount = msgCount;
sb.append(String.format("%02x", msgCount).toUpperCase());
this.msgFlag = msgFlag;
sb.append(String.format("%02x", msgFlag).toUpperCase());
this.msgType = msgType;
this.msgTypeRaw = this.msgType.toByte();
sb.append(String.format("%02x", this.msgTypeRaw).toUpperCase());
this.srcAddrStr = srcAddr;
this.srcAddr[0] = (byte) (Integer.parseInt(srcAddr.substring(0, 2), 16) & 0xFF);
this.srcAddr[1] = (byte) (Integer.parseInt(srcAddr.substring(2, 4), 16) & 0xFF);
this.srcAddr[2] = (byte) (Integer.parseInt(srcAddr.substring(4, 6), 16) & 0xFF);
sb.append(srcAddr.toUpperCase());
this.dstAddrStr = dstAddr;
this.dstAddr[0] = (byte) (Integer.parseInt(dstAddr.substring(0, 2), 16) & 0xFF);
this.dstAddr[1] = (byte) (Integer.parseInt(dstAddr.substring(2, 4), 16) & 0xFF);
this.dstAddr[2] = (byte) (Integer.parseInt(dstAddr.substring(4, 6), 16) & 0xFF);
sb.append(dstAddr.toUpperCase());
this.groupid = groupId;
sb.append(String.format("%02x", this.groupid).toUpperCase());
this.rawMsg = sb.toString();
}
protected void appendPayload(byte[] payload) {
if (this.flgReadyToSend) {
/* clear out old payload - we're updating */
this.flgReadyToSend = false;
this.buildHeader(msgCount, msgFlag, msgType, groupid, srcAddrStr, dstAddrStr);
}
StringBuilder sb = new StringBuilder(this.rawMsg);
this.flgReadyToSend = true;
this.payload = payload;
for (int byteIdx = 0; byteIdx < payload.length; byteIdx++) {
sb.append(String.format("%02X", payload[byteIdx]).toUpperCase());
}
/* prepend length & Z command */
byte len = (byte) ((sb.length() / PKT_CHARS_PER_BYTE) & 0xFF);
if (len * PKT_CHARS_PER_BYTE != sb.length()) {
this.flgReadyToSend = false;
logger.error("Unable to build raw message. Length is not correct");
}
if (isFastSend()) {
sb.insert(0, String.format("Zf%02X", len));
} else {
sb.insert(0, String.format("Zs%02X", len));
}
this.len = len;
this.rawMsg = sb.toString();
this.flgReadyToSend = true;
}
private static boolean pktLenOk(String rawMsg) {
int len = (byte) (Integer.parseInt(rawMsg.substring(PKT_POS_MSG_LEN, PKT_POS_MSG_LEN + PKT_POS_MSG_LEN_LEN), 16)
& 0xFF); /* length of packet */
if (len != ((rawMsg.length() - (PKT_POS_MSG_START + PKT_CHARS_PER_BYTE)) / PKT_CHARS_PER_BYTE)) /*
* account
* for
* preceding
* characters
* in
* the
* message
* and
* a
* byte
* at
* the
* end
* which
* i
* think
* is
* a
* checksum
*/
{
logger.error("Unable to process packet " + rawMsg + ". Length is not correct.");
return false;
}
return true;
}
/**
* Extract flag from a byte
*
* @param b
* Byte to look at
* @param pos
* Zero indexed position
* @return true if bit is 1, false if it is 0, false if pos>7
*/
protected boolean extractBitFromByte(byte b, int pos) {
if (pos > 7) {
return false;
}
return (((b & (0x1 << pos)) >> pos) == 1);
}
/**
* Return the type of message that has been received given the message
* string
*
* @param rawMsg
* Message string from CUL device
* @return MaxCulMsgType extracted from the message
*/
public static MaxCulMsgType getMsgType(String rawMsg) {
if (pktLenOk(rawMsg) == false) {
return MaxCulMsgType.UNKNOWN;
}
return MaxCulMsgType.fromByte((byte) (Integer
.parseInt(rawMsg.substring(PKT_POS_MSG_TYPE, PKT_POS_MSG_TYPE + PKT_POS_MSG_TYPE_LEN), 16) & 0xff));
}
public static boolean isForUs(String rawMsg, String addr) {
if (pktLenOk(rawMsg) == false) {
return false; // length is wrong ignore packet
}
return addr.equalsIgnoreCase(rawMsg.substring(PKT_POS_DST_ADDR, PKT_POS_DST_ADDR + PKT_POS_DST_ADDR_LEN));
}
public int requiredCredit() {
/*
* length in bits = amount of credit needed length in bits = num chars *
* 4 This is because each char represents 4 bits of a hex number. RawMsg
* length is decremented by one to account for the 'Z[s|f]'
*/
int credit = (this.rawMsg.length() - 2) * 4;
/* credit is in 10ms units, round up */
credit = (int) Math.ceil(credit / 10.0);
return credit;
}
/**
* Indicate if this packet is complete
*
* @return true if packet is ready to send
*/
public boolean readyToSend() {
return this.flgReadyToSend;
}
/**
* Print the payload out for debug
*/
protected void printDebugPayload() {
for (int i = 0; i < payload.length; i++) {
logger.debug("\t" + this.msgType + " byte[" + i + "] => 0x" + Integer.toHexString(0xff & payload[i]));
}
}
protected void printMessageHeader() {
logger.debug("Raw Message: " + this.rawMsg);
logger.debug("\tLength => " + Integer.toString(0xff & this.len));
logger.debug("\tMsg Count => 0x" + Integer.toHexString(0xff & this.msgCount));
logger.debug("\tMsg Flag => 0x" + Integer.toHexString(0xff & this.msgFlag));
logger.debug("\tMsg Type => " + this.msgType);
logger.debug("\tSrc Addr => " + this.srcAddrStr);
logger.debug("\tDst Addr => " + this.dstAddrStr);
logger.debug("\tGroup ID => " + Integer.toString(0xff & this.groupid));
}
/**
* Print output as decoded fields
*/
protected void printFormattedPayload() {
logger.debug("\tPrinting raw payload:");
printDebugPayload();
}
/**
* Print the full message out to debug
*/
public void printMessage() {
printMessageHeader();
printFormattedPayload();
}
/**
* Set this message to be part of a message sequence, also checks if it
* should be using fast send for this message or not
*
* @param msgSeq
* MessageSequence to associate with message
*/
public void setMessageSequencer(MessageSequencer msgSeq) {
msgSequencer = msgSeq;
if (msgSeq != null) {
this.setFastSend(msgSeq.useFastSend());
}
}
/**
* Get the Message Sequencer associated with this message
*
* @return MessageSequencer associated with message
*/
public MessageSequencer getMessageSequencer() {
return msgSequencer;
}
/**
* Check if this message is part of a sequence
*
* @return true if part of sequence
*/
public boolean isPartOfSequence() {
return (msgSequencer != null);
}
/**
* Set fast send flag manually
*
* @param useFastSend
* value to set fast send flag to
*/
public void setFastSend(boolean useFastSend) {
this.fastSend = useFastSend;
if (this.flgReadyToSend) {
logger.debug("Reconfiguring message to " + (fastSend ? "FAST" : "SLOW"));
if (fastSend) {
this.rawMsg = rawMsg.replaceFirst("Zs", "Zf"); // replace slow
// with fast
} else {
this.rawMsg = rawMsg.replaceFirst("Zf", "Zs"); // replace fast
// with slow
}
}
}
/**
* Get fast send status
*
* @return true if message is fastSend
*/
public boolean isFastSend() {
return fastSend;
}
}