From 68778b8a434050c08dbd8d142c83b7a3bac2fa44 Mon Sep 17 00:00:00 2001 From: Christopher Piggott Date: Tue, 10 Mar 2015 06:40:25 -0400 Subject: [PATCH] Added initial project --- .gitignore | 4 + pom.xml | 28 ++ .../autofrog/xbee/api/XbeeMessageParser.java | 257 ++++++++++++++++++ .../api/exceptions/XbeeChecksumException.java | 25 ++ .../exceptions/XbeeEmptyMessageException.java | 25 ++ .../xbee/api/exceptions/XbeeException.java | 26 ++ .../api/listeners/XbeeMessageListener.java | 13 + .../XbeeParsingExceptionListener.java | 11 + .../api/messages/XbeeExplicitRxMessage.java | 107 ++++++++ .../xbee/api/messages/XbeeMessageBase.java | 19 ++ .../xbee/api/messages/XbeeUnknownMessage.java | 8 + .../xbee/api/protocol/XbeeApiConstants.java | 43 +++ .../xbee/api/protocol/XbeeMessageType.java | 35 +++ .../xbee/api/XbeeMessageParserTest.java | 98 +++++++ 14 files changed, 699 insertions(+) create mode 100644 pom.xml create mode 100644 src/main/java/com/autofrog/xbee/api/XbeeMessageParser.java create mode 100644 src/main/java/com/autofrog/xbee/api/exceptions/XbeeChecksumException.java create mode 100644 src/main/java/com/autofrog/xbee/api/exceptions/XbeeEmptyMessageException.java create mode 100644 src/main/java/com/autofrog/xbee/api/exceptions/XbeeException.java create mode 100644 src/main/java/com/autofrog/xbee/api/listeners/XbeeMessageListener.java create mode 100644 src/main/java/com/autofrog/xbee/api/listeners/XbeeParsingExceptionListener.java create mode 100644 src/main/java/com/autofrog/xbee/api/messages/XbeeExplicitRxMessage.java create mode 100644 src/main/java/com/autofrog/xbee/api/messages/XbeeMessageBase.java create mode 100644 src/main/java/com/autofrog/xbee/api/messages/XbeeUnknownMessage.java create mode 100644 src/main/java/com/autofrog/xbee/api/protocol/XbeeApiConstants.java create mode 100644 src/main/java/com/autofrog/xbee/api/protocol/XbeeMessageType.java create mode 100644 src/test/java/com/autofrog/xbee/api/XbeeMessageParserTest.java diff --git a/.gitignore b/.gitignore index c1d2f93..9f9e4bd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,9 @@ build + +.idea +*.iml + .idea/workspace.xml .idea/tasks.xml .idea/dictionaries diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..cadb28d --- /dev/null +++ b/pom.xml @@ -0,0 +1,28 @@ + + + 4.0.0 + + com.autofrog.xbee + xbee-api-java + 1.0-SNAPSHOT + + + + junit + junit + 4.12 + test + + + + org.easymock + easymock + 3.3.1 + test + + + + + \ No newline at end of file diff --git a/src/main/java/com/autofrog/xbee/api/XbeeMessageParser.java b/src/main/java/com/autofrog/xbee/api/XbeeMessageParser.java new file mode 100644 index 0000000..c274754 --- /dev/null +++ b/src/main/java/com/autofrog/xbee/api/XbeeMessageParser.java @@ -0,0 +1,257 @@ +package com.autofrog.xbee.api; + +import com.autofrog.xbee.api.exceptions.XbeeChecksumException; +import com.autofrog.xbee.api.exceptions.XbeeEmptyMessageException; +import com.autofrog.xbee.api.exceptions.XbeeException; +import com.autofrog.xbee.api.listeners.XbeeMessageListener; +import com.autofrog.xbee.api.listeners.XbeeParsingExceptionListener; +import com.autofrog.xbee.api.messages.XbeeExplicitRxMessage; +import com.autofrog.xbee.api.messages.XbeeMessageBase; +import com.autofrog.xbee.api.protocol.XbeeApiConstants; +import com.autofrog.xbee.api.protocol.XbeeMessageType; + +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * Main entry point for the Xbee API parser. + */ +public class XbeeMessageParser { + + /** + * States the parser may be in on a byte-by-byte basis + */ + public enum RxState { + WAITING_FOR_FLAG, + WAITING_FOR_LEN1, + WAITING_FOR_LEN2, + WAIT_FOR_FRAME_TYPE, + COUNTING, + WAITING_FOR_CHECKSUM + } + + protected RxState rxState = RxState.WAITING_FOR_FLAG; + private byte frameType; + private int count; + private int len; + private byte checksum; + private byte[] buf = null; + private boolean escaped = false; + private boolean transmissionPaused = false; + private final Map> frameTypeSpecificListeners; + private final List allFrameTypesListeners; + private final List errorListeners; + + protected XbeeMessageParser() { + frameTypeSpecificListeners = new ConcurrentHashMap>(); + allFrameTypesListeners = new CopyOnWriteArrayList(); + errorListeners = new CopyOnWriteArrayList(); + } + + /** + * Add a listener for ALL xbee messages + * + * @param newListener + */ + public void addListener(XbeeMessageListener newListener) { + allFrameTypesListeners.add(newListener); + } + + /** + * Add a listener for a specific xbee message type + * + * @param frameType + * @param newListener + */ + public void addListener(XbeeMessageType frameType, XbeeMessageListener newListener) { + + if (frameTypeSpecificListeners.containsKey(frameType) == false) { + frameTypeSpecificListeners.put(frameType, new CopyOnWriteArrayList()); + } + + frameTypeSpecificListeners.get(frameType).add(newListener); + } + + /** + * Remove a listener from everything it's subscribed to + * + * @param listenerToRemove + */ + public void removeListener(XbeeMessageListener listenerToRemove) { + allFrameTypesListeners.remove(listenerToRemove); + + for (List list : frameTypeSpecificListeners.values()) { + list.remove(listenerToRemove); + } + } + + + public void addXbeeExceptionListener(XbeeParsingExceptionListener listener) { + errorListeners.add(listener); + } + + public void removeXbeeExceptionListener(XbeeParsingExceptionListener listener) { + errorListeners.remove(listener); + } + + protected void notifyListenersOfException(XbeeException e) { + for (XbeeParsingExceptionListener listener : errorListeners) { + listener.xbeeParsingError(this, e); + } + } + + protected void notifyListeners(XbeeMessageBase msg) { + for (XbeeMessageListener l : allFrameTypesListeners) { + l.onXbeeMessage(this, msg); + } + + List specificListeners = frameTypeSpecificListeners.get(msg.getRawFrameType()); + if (specificListeners != null) { + for (XbeeMessageListener specificListener : specificListeners) { + specificListener.onXbeeMessage(this, msg); + } + } + } + + public void byteIn(byte b) { + XbeeMessageBase msg = processByte(b); + if (msg != null) { + notifyListeners(msg); + } + } + + public void bytesIn(byte[] bytes) { + for (byte b : bytes) { + byteIn(b); + } + } + + public void bytesIn(byte[] bytes, int start, int len) { + int end = start + len; + for (int i = start; i < end; i++) { + byteIn(bytes[i]); + } + } + + /** + * The main state machine + * @param b + * @return + */ + private XbeeMessageBase processByte(byte b) { + switch (b) { + case XbeeApiConstants.API_FLAG: + if (rxState != RxState.WAITING_FOR_FLAG) { + /* We did not expect two flags in a row */ + notifyListenersOfException(new XbeeEmptyMessageException()); + } + + rxState = RxState.WAITING_FOR_LEN1; + break; + case XbeeApiConstants.API_ESCAPE: + escaped = true; + break; + case XbeeApiConstants.API_XON: + transmissionPaused = false; + break; + case XbeeApiConstants.API_XOFF: + transmissionPaused = true; + break; + + default: + if (escaped) { + b = (byte) (b ^ 0x20); + escaped = false; + } + + switch (rxState) { + case WAITING_FOR_FLAG: + break; + + case WAITING_FOR_LEN1: + len = b << 8; + rxState = RxState.WAITING_FOR_LEN2; + + break; + + case WAITING_FOR_LEN2: + len = len | b; + buf = new byte[len - 1]; + count = 0; + rxState = RxState.WAIT_FOR_FRAME_TYPE; + break; + + case WAIT_FOR_FRAME_TYPE: + frameType = b; + checksum = frameType; + rxState = RxState.COUNTING; + break; + + case COUNTING: + buf[count++] = b; + checksum = (byte) ((checksum + b) & 0xFF); + /* This is len-1 to skip the frame type */ + if (count >= (len-1)) { + rxState = RxState.WAITING_FOR_CHECKSUM; + } + break; + + case WAITING_FOR_CHECKSUM: + byte expected = (byte) (0xFF - checksum); + if (expected == b) { + + try { + return buildSpecificMessage(frameType, buf); + } catch ( Exception e) { + /* Notify of some kind of parsing error */ + notifyListenersOfException(new XbeeException()); + + return null; + } finally { + rxState = RxState.WAITING_FOR_FLAG; + } + + } else { + /* Notify of a checksum error */ + notifyListenersOfException(new XbeeChecksumException()); + rxState = RxState.WAITING_FOR_FLAG; + } + + } + } + return null; + } + + /** + * Get the RX state on a byte by byte basis. Only really useful for debugging. + * + * @return + */ + public RxState getRxState() { + return rxState; + } + + private XbeeMessageBase buildSpecificMessage(byte frameType, byte[] buf) throws IOException { + XbeeMessageType type = XbeeMessageType.lookup(frameType); + + switch (type) { + case EXPLICIT_RX: + return XbeeExplicitRxMessage.create(buf); + case NODE_DISCOVERY: + break; + case RX_IO_SAMPLE: + break; + default: + + } + return null; + } + + public String dumpParserState() { + return String.format("state=%s len=%d count=%d escaped=%s", + rxState, len, count, escaped); + } +} diff --git a/src/main/java/com/autofrog/xbee/api/exceptions/XbeeChecksumException.java b/src/main/java/com/autofrog/xbee/api/exceptions/XbeeChecksumException.java new file mode 100644 index 0000000..89d3c4b --- /dev/null +++ b/src/main/java/com/autofrog/xbee/api/exceptions/XbeeChecksumException.java @@ -0,0 +1,25 @@ +package com.autofrog.xbee.api.exceptions; + +/** + * Created by chrisp on 009 3/9/2015. + */ +public class XbeeChecksumException extends XbeeException { + public XbeeChecksumException() { + } + + public XbeeChecksumException(String message) { + super(message); + } + + public XbeeChecksumException(String message, Throwable cause) { + super(message, cause); + } + + public XbeeChecksumException(Throwable cause) { + super(cause); + } + + public XbeeChecksumException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/src/main/java/com/autofrog/xbee/api/exceptions/XbeeEmptyMessageException.java b/src/main/java/com/autofrog/xbee/api/exceptions/XbeeEmptyMessageException.java new file mode 100644 index 0000000..7207be1 --- /dev/null +++ b/src/main/java/com/autofrog/xbee/api/exceptions/XbeeEmptyMessageException.java @@ -0,0 +1,25 @@ +package com.autofrog.xbee.api.exceptions; + +/** + * Created by chrisp on 009 3/9/2015. + */ +public class XbeeEmptyMessageException extends XbeeException { + public XbeeEmptyMessageException() { + } + + public XbeeEmptyMessageException(String message) { + super(message); + } + + public XbeeEmptyMessageException(String message, Throwable cause) { + super(message, cause); + } + + public XbeeEmptyMessageException(Throwable cause) { + super(cause); + } + + public XbeeEmptyMessageException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/src/main/java/com/autofrog/xbee/api/exceptions/XbeeException.java b/src/main/java/com/autofrog/xbee/api/exceptions/XbeeException.java new file mode 100644 index 0000000..81f0969 --- /dev/null +++ b/src/main/java/com/autofrog/xbee/api/exceptions/XbeeException.java @@ -0,0 +1,26 @@ +package com.autofrog.xbee.api.exceptions; + +/** + * Created by chrisp on 009 3/9/2015. + */ +public class XbeeException extends Exception { + + public XbeeException() { + } + + public XbeeException(String message) { + super(message); + } + + public XbeeException(String message, Throwable cause) { + super(message, cause); + } + + public XbeeException(Throwable cause) { + super(cause); + } + + public XbeeException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/src/main/java/com/autofrog/xbee/api/listeners/XbeeMessageListener.java b/src/main/java/com/autofrog/xbee/api/listeners/XbeeMessageListener.java new file mode 100644 index 0000000..cfb9951 --- /dev/null +++ b/src/main/java/com/autofrog/xbee/api/listeners/XbeeMessageListener.java @@ -0,0 +1,13 @@ +package com.autofrog.xbee.api.listeners; + +import com.autofrog.xbee.api.messages.XbeeMessageBase; + +/** + * + * @author chrisp + */ +public interface XbeeMessageListener { + + public void onXbeeMessage(Object sender, XbeeMessageBase msg); + +} diff --git a/src/main/java/com/autofrog/xbee/api/listeners/XbeeParsingExceptionListener.java b/src/main/java/com/autofrog/xbee/api/listeners/XbeeParsingExceptionListener.java new file mode 100644 index 0000000..47f83a4 --- /dev/null +++ b/src/main/java/com/autofrog/xbee/api/listeners/XbeeParsingExceptionListener.java @@ -0,0 +1,11 @@ +package com.autofrog.xbee.api.listeners; + +import com.autofrog.xbee.api.exceptions.XbeeEmptyMessageException; +import com.autofrog.xbee.api.exceptions.XbeeException; + +/** + * Created by chrisp on 009 3/9/2015. + */ +public interface XbeeParsingExceptionListener { + void xbeeParsingError(Object sender, XbeeException exception); +} diff --git a/src/main/java/com/autofrog/xbee/api/messages/XbeeExplicitRxMessage.java b/src/main/java/com/autofrog/xbee/api/messages/XbeeExplicitRxMessage.java new file mode 100644 index 0000000..4f6b3e6 --- /dev/null +++ b/src/main/java/com/autofrog/xbee/api/messages/XbeeExplicitRxMessage.java @@ -0,0 +1,107 @@ +package com.autofrog.xbee.api.messages; + +import com.autofrog.xbee.api.protocol.XbeeMessageType; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * Created by chrisp on 009 3/9/2015. + */ +public class XbeeExplicitRxMessage extends XbeeMessageBase { + + private long sourceAddress; + private short sourceNetworkAddress; + private byte sourceEndpoint; + private byte destEndpoint; + private short profileId; + private short clusterId; + private byte rxOpts; + + + private XbeeExplicitRxMessage(byte[] data) { + super.rawFrameType = XbeeMessageType.EXPLICIT_RX.valueOf(); + super.rawData = data; + } + + /** + * Helper method to parse messages. This builds a new byte buffer every time, so + * it's more efficient to use the alternative methodt hat takes a byte buffer in + * as the parameter. + * + * @param bytes + * @return an explicit message, or null if there isn't one + * @throws Exception if something goes wrong (e.g. IO Exception) + */ + public static XbeeExplicitRxMessage create(byte[] bytes) throws IOException { + return create(ByteBuffer.wrap(bytes)); + } + + /** + * Create this message type from a byte buffer. + * + * @param bb + * @return an XbeeExplicitRxMessage, or null if this byte buffer doesn't match the criteria + */ + public static XbeeExplicitRxMessage create(ByteBuffer bb) { + + /* Preserve the current byte ordering on the buffer */ + ByteOrder oldOrder = bb.order(); + + bb.order(ByteOrder.BIG_ENDIAN); + XbeeExplicitRxMessage o = new XbeeExplicitRxMessage(bb.array()); + + o.sourceAddress = bb.getLong(); + o.sourceNetworkAddress = bb.getShort(); + + o.sourceEndpoint = bb.get(); + o.destEndpoint = bb.get(); + + o.clusterId = bb.getShort(); + o.profileId = bb.getShort(); + o.rxOpts = bb.get(); + + o.rawData = new byte[bb.remaining()]; + bb.get(o.rawData); + return o; + + + } + + public short getClusterId() { + return clusterId; + } + + public byte getDestEndpoint() { + return destEndpoint; + } + + public short getProfileId() { + return profileId; + } + + public byte getRxOpts() { + return rxOpts; + } + + public long getSourceAddress() { + return sourceAddress; + } + + public byte getSourceEndpoint() { + return sourceEndpoint; + } + + public short getSourceNetworkAddress() { + return sourceNetworkAddress; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("XbeeExplicitRxMessage { ").append(String.format("sourceAddress=0x%016X", sourceAddress)).append(String.format(", sourceNeworkAddress=0x%04X", sourceNetworkAddress)).append(String.format(", sourceEndpoint=0x%02X", sourceEndpoint)).append(String.format(", destEndpoint=0x%02X", destEndpoint)).append(String.format(", profileId=0x%04X", profileId)).append(String.format(", clusterId=0x%04X", clusterId)).append(String.format(", rxOpts=0x%02X", rxOpts)).append(" }"); + return sb.toString(); + + } +} + diff --git a/src/main/java/com/autofrog/xbee/api/messages/XbeeMessageBase.java b/src/main/java/com/autofrog/xbee/api/messages/XbeeMessageBase.java new file mode 100644 index 0000000..bac5905 --- /dev/null +++ b/src/main/java/com/autofrog/xbee/api/messages/XbeeMessageBase.java @@ -0,0 +1,19 @@ +package com.autofrog.xbee.api.messages; + +/** + * Created by chrisp on 009 3/9/2015. + */ +public abstract class XbeeMessageBase { + protected byte[] rawData; + protected byte rawFrameType; + + + public byte getRawFrameType() { + return rawFrameType; + } + + public byte[] getRawData() { + return rawData; + } + +} diff --git a/src/main/java/com/autofrog/xbee/api/messages/XbeeUnknownMessage.java b/src/main/java/com/autofrog/xbee/api/messages/XbeeUnknownMessage.java new file mode 100644 index 0000000..62e2ad1 --- /dev/null +++ b/src/main/java/com/autofrog/xbee/api/messages/XbeeUnknownMessage.java @@ -0,0 +1,8 @@ +package com.autofrog.xbee.api.messages; + +/** + * A message whose type we don't understand + */ +public class XbeeUnknownMessage extends XbeeMessageBase { + +} diff --git a/src/main/java/com/autofrog/xbee/api/protocol/XbeeApiConstants.java b/src/main/java/com/autofrog/xbee/api/protocol/XbeeApiConstants.java new file mode 100644 index 0000000..4f825d5 --- /dev/null +++ b/src/main/java/com/autofrog/xbee/api/protocol/XbeeApiConstants.java @@ -0,0 +1,43 @@ +package com.autofrog.xbee.api.protocol; + +/** + * Constants used by the Xbee API protocol + */ +public class XbeeApiConstants { + + /** + * Flag constant. Separates and defines messages. + */ + public static final byte API_FLAG = 0x7E; + + /** + * Escape constant. Escapes special characters. The escaped API (mode 2) is + * used by this library because it gives you the best possible chance of detecting + * and recovering from errors in the stream, for example if there are overflows. + */ + public static final byte API_ESCAPE = 0x7D; + + /** + * XON mode, when he Xbee is telling us it's OK to resume sending after an XOFF. + * Used only when transmitting. + */ + public static final byte API_XON = 0x11; + + /** + * XOFF constant, where the Xbee is telling us it's not ready to accept more bytes. + * Used only when tramsmitting. + */ + public static final byte API_XOFF = 0x13; + + + /** + * Broadcast address - for sending to all nodes. + */ + public final static long ADDR_BROADCAST = 0xFFFF; + + + /** + * Digi's default profile id, used for many of their messages + */ + public static final short DIGI_PROFILE_ID = (short) (0xC105); +} diff --git a/src/main/java/com/autofrog/xbee/api/protocol/XbeeMessageType.java b/src/main/java/com/autofrog/xbee/api/protocol/XbeeMessageType.java new file mode 100644 index 0000000..e41b8f0 --- /dev/null +++ b/src/main/java/com/autofrog/xbee/api/protocol/XbeeMessageType.java @@ -0,0 +1,35 @@ +package com.autofrog.xbee.api.protocol; + +import java.util.EnumSet; +import java.util.HashMap; +import java.util.Map; + +/** + * Created by chrisp on 009 3/9/2015. + */ +public enum XbeeMessageType { + TRANSMIT((byte) 0x10), + EXPLICIT_RX((byte) 0x91), + RX_IO_SAMPLE((byte) 0x92), + NODE_DISCOVERY((byte) 0x95); + + private final byte value; + private final static Map reverse = new HashMap(); + + static { + for(XbeeMessageType s : EnumSet.allOf(XbeeMessageType.class)) + reverse.put(s.valueOf(), s); + } + + XbeeMessageType(byte value) { + this.value = value; + } + + public byte valueOf() { + return value; + } + + public static XbeeMessageType lookup(byte value) { + return reverse.get(value); + } +} diff --git a/src/test/java/com/autofrog/xbee/api/XbeeMessageParserTest.java b/src/test/java/com/autofrog/xbee/api/XbeeMessageParserTest.java new file mode 100644 index 0000000..43b3195 --- /dev/null +++ b/src/test/java/com/autofrog/xbee/api/XbeeMessageParserTest.java @@ -0,0 +1,98 @@ +package com.autofrog.xbee.api; + +import com.autofrog.xbee.api.exceptions.XbeeEmptyMessageException; +import com.autofrog.xbee.api.listeners.XbeeMessageListener; +import com.autofrog.xbee.api.listeners.XbeeParsingExceptionListener; + +import static org.easymock.EasyMock.*; +import static org.junit.Assert.*; + +import com.autofrog.xbee.api.messages.XbeeExplicitRxMessage; +import org.easymock.*; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(EasyMockRunner.class) +public class XbeeMessageParserTest extends EasyMockSupport { + private final byte[] msg1 = { + (byte) 0x7e, + (byte) 0x00, + (byte) 0x02, + (byte) 0x23, + (byte) 0x11, + (byte) 0xCB + }; + + /* This example is from the xbee user's manual but with escapes added for AP=2 */ + private final byte[] msg2 = { + (byte) 0x7E, + (byte) 0x00, + (byte) 0x18, + (byte) 0x91, + (byte) 0x00, + (byte) 0x7d, 0x33, + (byte) 0xa2, + (byte) 0x00, + (byte) 0x40, + (byte) 0x52, + (byte) 0x2b, + (byte) 0xaa, + (byte) 0x7d, 0x5D, + (byte) 0x84, + (byte) 0xe0, + (byte) 0xe0, + (byte) 0x22, + (byte) 0x7d, 0x31, + (byte) 0xc1, + (byte) 0x05, + (byte) 0x02, + (byte) 0x52, + (byte) 0x78, + (byte) 0x44, + (byte) 0x61, + (byte) 0x74, + (byte) 0x61, + (byte) 0x52}; + + private final byte[] emptyMessage = new byte[]{ + 0x7E, 0x7E + }; + + @TestSubject + private XbeeMessageParser parser = new XbeeMessageParser(); + @Mock + private XbeeParsingExceptionListener mockExceptionListener; + @Mock + private XbeeMessageListener mockMessageListener; + +// @Test +// public void testEmptyMessageNotification() throws Exception { +// parser.addXbeeExceptionListener(mockExceptionListener); +// parser.addListener(mockMessageListener); +// mockExceptionListener.xbeeParsingError(eq(parser), anyObject(XbeeEmptyMessageException.class)); +// replayAll(); +// parser.bytesIn(emptyMessage); +// verifyAll(); +// } + + @Test + public void testSampleExplicitRx() throws Exception { + parser.addXbeeExceptionListener(mockExceptionListener); + parser.addListener(mockMessageListener); + Capture resultCapture = Capture.newInstance(CaptureType.FIRST); + mockMessageListener.onXbeeMessage(eq(parser), capture(resultCapture)); + replayAll(); + parser.bytesIn(msg2); + verifyAll(); + XbeeExplicitRxMessage result = resultCapture.getValue(); + + assertEquals((long) 0x2211, result.getClusterId()); + assertEquals((byte) 0xE0, (byte) result.getSourceEndpoint()); + assertEquals((byte) 0xE0, (byte) result.getDestEndpoint()); + assertEquals((short)0xC105, (short)result.getProfileId()); + assertEquals((byte) 0x02, (byte) result.getRxOpts()); + assertEquals(0x0013a20040522baaL, result.getSourceAddress()); + } + +} \ No newline at end of file