001/** 002 * Copyright (c) 2014 Digi International Inc., 003 * All rights not expressly granted are reserved. 004 * 005 * This Source Code Form is subject to the terms of the Mozilla Public 006 * License, v. 2.0. If a copy of the MPL was not distributed with this file, 007 * You can obtain one at http://mozilla.org/MPL/2.0/. 008 * 009 * Digi International Inc. 11001 Bren Road East, Minnetonka, MN 55343 010 * ======================================================================= 011 */ 012package com.digi.xbee.api.packet; 013 014import java.io.ByteArrayInputStream; 015import java.io.ByteArrayOutputStream; 016import java.util.LinkedHashMap; 017 018import com.digi.xbee.api.exceptions.InvalidPacketException; 019import com.digi.xbee.api.models.SpecialByte; 020import com.digi.xbee.api.models.OperatingMode; 021import com.digi.xbee.api.utils.ByteUtils; 022import com.digi.xbee.api.utils.HexUtils; 023 024/** 025 * This abstract class represents the basic structure of an XBee packet. 026 * 027 * <p>Derived classes should implement their own payload generation depending 028 * on their type.</p> 029 * 030 * <p>Generic actions like checksum compute or packet length calculation is 031 * performed here.</p> 032 */ 033public abstract class XBeePacket { 034 035 // Variables. 036 private XBeeChecksum checksum; 037 038 /** 039 * Class constructor. Instantiates a new {@code XBeePacket} object. 040 */ 041 protected XBeePacket() { 042 checksum = new XBeeChecksum(); 043 } 044 045 /** 046 * Generates the XBee packet byte array. 047 * 048 * <p>Use only while working in API mode 1. If API mode is 2, use 049 * {@link #generateByteArrayEscaped()}.</p> 050 * 051 * @return The XBee packet byte array. 052 * 053 * @see #generateByteArrayEscaped() 054 */ 055 public byte[] generateByteArray() { 056 checksum.reset(); 057 byte[] packetData = getPacketData(); 058 ByteArrayOutputStream os = new ByteArrayOutputStream(); 059 os.write(SpecialByte.HEADER_BYTE.getValue()); 060 if (packetData != null) { 061 byte[] length = ByteUtils.shortToByteArray((short)packetData.length); 062 int msb = length[0]; 063 int lsb = length[1]; 064 os.write(msb); 065 os.write(lsb); 066 for (int i = 0; i < packetData.length; i++) { 067 checksum.add(packetData[i]); 068 os.write(packetData[i]); 069 } 070 } else { 071 os.write(0); 072 os.write(0); 073 } 074 os.write((byte)checksum.generate() & 0xFF); 075 return os.toByteArray(); 076 } 077 078 /** 079 * Generates the XBee packet byte array escaping the special bytes. 080 * 081 * <p>Use only while working in API mode 2. If API mode is 1 use 082 * {@link #generateByteArray()}.</p> 083 * 084 * @return The XBee packet byte array with escaped characters. 085 * 086 * @see #generateByteArray() 087 */ 088 public byte[] generateByteArrayEscaped() { 089 byte[] unescapedArray = generateByteArray(); 090 ByteArrayOutputStream os = new ByteArrayOutputStream(); 091 // Write header byte and do not escape it. 092 os.write(SpecialByte.HEADER_BYTE.getValue()); 093 for (int i = 1; i < unescapedArray.length; i++) { 094 // Start at 1 to avoid escaping header byte. 095 if (SpecialByte.isSpecialByte(unescapedArray[i])) { 096 os.write(SpecialByte.ESCAPE_BYTE.getValue()); 097 SpecialByte specialByte = SpecialByte.get(unescapedArray[i]); 098 os.write(specialByte.escapeByte()); 099 } else 100 os.write(unescapedArray[i]); 101 } 102 return os.toByteArray(); 103 } 104 105 /** 106 * Returns the packet data. 107 * 108 * @return The packet data. 109 */ 110 public abstract byte[] getPacketData(); 111 112 /** 113 * Returns the packet length. 114 * 115 * @return The packet length. 116 */ 117 public int getPacketLength() { 118 byte[] packetData = getPacketData(); 119 if (packetData == null) 120 return 0; 121 return packetData.length; 122 } 123 124 /** 125 * Returns the packet checksum. 126 * 127 * <p>To calculate: Not including frame delimiters and length, add all 128 * bytes keeping only the lowest 8 bits of the result and subtract the 129 * result from {@code 0xFF}.</p> 130 * 131 * @return The packet checksum. 132 */ 133 public int getChecksum() { 134 checksum.reset(); 135 byte[] packetData = getPacketData(); 136 if (packetData != null) 137 checksum.add(packetData); 138 return (byte)checksum.generate() & 0xFF; 139 } 140 141 /** 142 * Returns a map with the XBee packet parameters and their values. 143 * 144 * @return A sorted map containing the XBee packet parameters with their 145 * values. 146 */ 147 public LinkedHashMap<String, String> getParameters() { 148 LinkedHashMap<String, String> parameters = new LinkedHashMap<String, String>(); 149 parameters.put("Start delimiter", HexUtils.integerToHexString(SpecialByte.HEADER_BYTE.getValue(), 1)); 150 parameters.put("Length", HexUtils.prettyHexString(HexUtils.integerToHexString(getPacketLength(), 2)) + " (" + getPacketLength() + ")"); 151 parameters.putAll(getPacketParameters()); 152 parameters.put("Checksum", toString().substring(toString().length() - 2)); 153 return parameters; 154 } 155 156 /** 157 * Returns a map with the XBee packet parameters and their values. 158 * 159 * @return A sorted map containing the XBee packet parameters with their 160 * values. 161 */ 162 protected abstract LinkedHashMap<String, String> getPacketParameters(); 163 164 /* 165 * (non-Javadoc) 166 * @see java.lang.Object#toString() 167 */ 168 @Override 169 public String toString() { 170 return HexUtils.byteArrayToHexString(generateByteArray()); 171 } 172 173 /** 174 * Returns a pretty string representing the packet. 175 * 176 * @return Pretty String representing the packet. 177 */ 178 public String toPrettyString() { 179 String value = "Packet: " + toString() + "\n"; 180 LinkedHashMap<String, String> parameters = getParameters(); 181 for (String parameter:parameters.keySet()) 182 value = value + parameter + ": " + parameters.get(parameter) + "\n"; 183 return value; 184 } 185 186 /** 187 * Parses the given hexadecimal string and returns a Generic XBee packet. 188 * 189 * <p>The string can contain white spaces.</p> 190 * 191 * @param packet The hexadecimal string to parse. 192 * @param mode The operating mode to parse the packet (API 1 or API 2). 193 * 194 * @return The generated Generic XBee Packet. 195 * 196 * @throws IllegalArgumentException if {@code mode != OperatingMode.API } and 197 * if {@code mode != OperatingMode.API_ESCAPE}. 198 * @throws InvalidPacketException if the given string does not represent a 199 * valid frame: invalid checksum, length, 200 * start delimiter, etc. 201 * @throws NullPointerException if {@code packet == null}. 202 * 203 * @see com.digi.xbee.api.models.OperatingMode#API 204 * @see com.digi.xbee.api.models.OperatingMode#API_ESCAPE 205 */ 206 public static XBeePacket parsePacket(String packet, OperatingMode mode) throws InvalidPacketException { 207 if (packet == null) 208 throw new NullPointerException("Packet cannot be null."); 209 210 return parsePacket(HexUtils.hexStringToByteArray(packet.trim().replace(" ", "")), mode); 211 } 212 213 /** 214 * Parses the given byte array and returns a Generic XBee packet. 215 * 216 * @param packet The byte array to parse. 217 * @param mode The operating mode to parse the packet (API 1 or API 2). 218 * 219 * @return The generated Generic XBee Packet. 220 * 221 * @throws IllegalArgumentException if {@code mode != OperatingMode.API } and 222 * if {@code mode != OperatingMode.API_ESCAPE} 223 * or if {@code packet.length == 0}. 224 * @throws InvalidPacketException if the given byte array does not represent 225 * a valid frame: invalid checksum, length, 226 * start delimiter, etc. 227 * @throws NullPointerException if {@code packet == null}. 228 * 229 * @see com.digi.xbee.api.models.OperatingMode#API 230 * @see com.digi.xbee.api.models.OperatingMode#API_ESCAPE 231 */ 232 public static XBeePacket parsePacket(byte[] packet, OperatingMode mode) throws InvalidPacketException { 233 if (packet == null) 234 throw new NullPointerException("Packet byte array cannot be null."); 235 236 if (mode != OperatingMode.API && mode != OperatingMode.API_ESCAPE) 237 throw new IllegalArgumentException("Operating mode must be API or API Escaped."); 238 239 if (packet.length == 0) 240 throw new IllegalArgumentException("Packet length should be greater than 0."); 241 242 if (packet.length > 1 && ((packet[0] & 0xFF) != SpecialByte.HEADER_BYTE.getValue())) 243 throw new InvalidPacketException("Invalid start delimiter."); 244 245 XBeePacketParser parser = new XBeePacketParser(); 246 XBeePacket xbeePacket = parser.parsePacket(new ByteArrayInputStream(packet, 1, packet.length - 1), mode); 247 return xbeePacket; 248 } 249}