com.rapplogic.xbee.api.XBeePacket Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of xbee-api Show documentation
Show all versions of xbee-api Show documentation
A java library for communicating with XBee radios
The newest version!
/**
* Copyright (c) 2008 Andrew Rapp. All rights reserved.
*
* This file is part of XBee-API.
*
* XBee-API is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* XBee-API is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with XBee-API. If not, see .
*/
package com.rapplogic.xbee.api;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
import org.apache.log4j.Logger;
import com.rapplogic.xbee.util.ByteUtils;
/**
* Packages a frame data array into an XBee packet.
*
* @author andrew
*
*/
public class XBeePacket {
public enum SpecialByte {
START_BYTE (0x7e), // ~
ESCAPE (0x7d), // }
XON (0x11),
XOFF (0x13);
private static final Map lookup = new HashMap();
static {
for(SpecialByte s : EnumSet.allOf(SpecialByte.class)) {
lookup.put(s.getValue(), s);
}
}
public static SpecialByte get(int value) {
return lookup.get(value);
}
private final int value;
SpecialByte(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
private final static Logger log = Logger.getLogger(XBeePacket.class);
private int[] packet;
/**
* Performs the necessary activities to construct an XBee packet from the frame data.
* This includes: computing the checksum, escaping the necessary bytes, adding the start byte and length bytes.
*
* The format of a packet is as follows:
*
* start byte - msb length byte - lsb length byte - frame data - checksum byte
*
* @param frameData
*/
public XBeePacket(int[] frameData) {
// checksum is always computed on pre-escaped packet
Checksum checksum = new Checksum();
for (int aFrameData : frameData) {
checksum.addByte(aFrameData);
}
checksum.compute();
// packet size is frame data + start byte + 2 length bytes + checksum byte
packet = new int[frameData.length + 4];
packet[0] = SpecialByte.START_BYTE.getValue();
// Packet length does not include escape bytes or start, length and checksum bytes
XBeePacketLength length = new XBeePacketLength(frameData.length);
// msb length (will be zero until maybe someday when > 255 bytes packets are supported)
packet[1] = length.getMsb();
// lsb length
packet[2] = length.getLsb();
for (int i = 0; i < frameData.length; i++) {
if (frameData[i] > 255) {
throw new RuntimeException("Packet values must not be greater than one byte (255): " + frameData[i]);
}
packet[3 + i] = frameData[i];
}
// set last byte as checksum
// note: if checksum is not correct, XBee won't send out packet or return error. ask me how I know.
packet[packet.length - 1] = checksum.getChecksum();
// for (int i = 0; i < packet.length; i++) {
// log.debug("XBeeApi pre-escape packet byte " + i + " is " + ByteUtils.toBase16(packet[i]));
// }
int preEscapeLength = packet.length;
// TODO save escaping for the serial out method. this is an unnecessary operation
packet = escapePacket(packet);
if (log.isDebugEnabled()) {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("Packet: ");
for (int i = 0; i < packet.length; i++) {
stringBuilder.append(ByteUtils.toBase16(packet[i]));
if (i < packet.length - 1) {
stringBuilder.append(" ");
}
}
log.debug(stringBuilder);
log.debug("pre-escape packet size is " + preEscapeLength + ", post-escape packet size is " + packet.length);
}
}
/**
* Escape all bytes in packet after start byte, and including checksum
*
* @param packet
* @return
*/
private static int[] escapePacket(int[] packet) {
int escapeBytes = 0;
// escape packet. start at one so we don't escape the start byte
for (int i = 1; i < packet.length; i++) {
if (isSpecialByte(packet[i])) {
log.debug("escapeFrameData: packet byte requires escaping byte " + ByteUtils.toBase16(packet[i]));
escapeBytes++;
}
}
if (escapeBytes == 0) {
return packet;
} else {
log.debug("packet requires escaping");
int[] escapePacket = new int[packet.length + escapeBytes];
int pos = 1;
escapePacket[0] = SpecialByte.START_BYTE.getValue();
for (int i = 1; i < packet.length; i++) {
if (isSpecialByte(packet[i])) {
escapePacket[pos] = SpecialByte.ESCAPE.getValue();
escapePacket[++pos] = 0x20 ^ packet[i];
log.debug("escapeFrameData: xor'd byte is 0x" + Integer.toHexString(escapePacket[pos]));
} else {
escapePacket[pos] = packet[i];
}
pos++;
}
return escapePacket;
}
}
/**
* @deprecated use getByteArray
* @return
*/
public int[] getPacket() {
return this.getByteArray();
}
public int[] getByteArray() {
return packet;
}
public static boolean isSpecialByte(int b) {
return b == SpecialByte.START_BYTE.getValue() || b == SpecialByte.ESCAPE.getValue() || b == SpecialByte.XON.getValue() ||
b == SpecialByte.XOFF.getValue();
}
public String toString() {
return ByteUtils.toBase16(this.packet);
}
/**
* Must be called with unescapted packet
*
* @param packet
* @return
*/
public static int getPacketLength(int[] packet) {
return new XBeePacketLength(packet[1], packet[2]).getLength();
}
/**
* Returns true if the packet is valid, Verifies both escaped and un-escaped packets
*
* @param packet
* @return
*/
public static boolean verify(int[] packet) {
boolean valid = true;
try {
if (packet[0] != SpecialByte.START_BYTE.getValue()) {
return false;
}
if (packetEndsWithEscapeByte(packet)) {
// packet can never end on a escape byte since the following byte is xor to unescape and will result in a array out of bounds exception
// this is an incomplete packet
return false;
}
// first need to un-escape packet since escape bytes complicate things
int[] unEscaped = unEscapePacket(packet);
// in theory 3 bytes are the minimum for 0 byte packet with no escape bytes. any less it can't be valid
if (unEscaped.length < 3) {
return false;
}
int len = getPacketLength(unEscaped);
// total packet length = stated length + 1 start byte + 1 checksum byte + 2 length bytes
// stated packet length does include escaping bytes, so actual packet size can be much larger, almost double
int expectedPacketLength = len + 4;
// if we are less bytes than expected packet length it can't be valid
if (unEscaped.length != expectedPacketLength) {
return false;
}
int[] frameData = new int[len];
Checksum checksum = new Checksum();
for (int i = 3; i < unEscaped.length - 1; i++) {
frameData[i - 3] = unEscaped[i];
checksum.addByte(frameData[i - 3]);
}
// add checksum byte to verify -- the last byte
checksum.addByte(unEscaped[unEscaped.length - 1]);
if (!checksum.verify()) {
valid = false;
}
} catch (Exception e) {
throw new RuntimeException("Packet verification failed with error: ", e);
}
return valid;
}
public static boolean packetEndsWithEscapeByte(int[] packet) {
return (packet[packet.length - 1] == SpecialByte.ESCAPE.getValue());
}
/**
*
* @param packet
* @return
* @throws IncompletePacketException
*/
public static int[] unEscapePacket(int[] packet) {
int escapeBytes = 0;
if (packetEndsWithEscapeByte(packet)) {
//packet can never end on a escape byte since the following byte is xor to unescape and will result in a array out of bounds exception
throw new RuntimeException("Invalid packet -- packet cannot end with an escape byte " + ByteUtils.toBase16(packet));
}
// first check if escape byte exists, if not we don't allocate a new array
for (int b : packet) {
if (b == SpecialByte.ESCAPE.getValue()) {
escapeBytes++;
}
}
if (escapeBytes == 0) {
return packet;
}
int[] unEscapedPacket = new int[packet.length - escapeBytes];
int pos = 0;
for (int i = 0; i < packet.length; i++) {
if (packet[i] == SpecialByte.ESCAPE.getValue()) {
// discard escape byte and un-escape following byte
if (i >= packet.length - 1) {
return packet;
}
unEscapedPacket[pos] = 0x20 ^ packet[++i];
} else {
unEscapedPacket[pos] = packet[i];
}
pos++;
}
return unEscapedPacket;
}
}