All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.digi.xbee.api.packet.XBeePacketParser Maven / Gradle / Ivy

There is a newer version: 1.3.2
Show newest version
/**
 * Copyright 2017, Digi International Inc.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, you can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */
package com.digi.xbee.api.packet;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Date;

import com.digi.xbee.api.exceptions.InvalidPacketException;
import com.digi.xbee.api.models.SpecialByte;
import com.digi.xbee.api.models.OperatingMode;
import com.digi.xbee.api.packet.cellular.RXSMSPacket;
import com.digi.xbee.api.packet.cellular.TXSMSPacket;
import com.digi.xbee.api.packet.common.ATCommandPacket;
import com.digi.xbee.api.packet.common.ATCommandQueuePacket;
import com.digi.xbee.api.packet.common.ATCommandResponsePacket;
import com.digi.xbee.api.packet.common.ExplicitAddressingPacket;
import com.digi.xbee.api.packet.common.ExplicitRxIndicatorPacket;
import com.digi.xbee.api.packet.common.IODataSampleRxIndicatorPacket;
import com.digi.xbee.api.packet.common.ModemStatusPacket;
import com.digi.xbee.api.packet.common.ReceivePacket;
import com.digi.xbee.api.packet.common.RemoteATCommandPacket;
import com.digi.xbee.api.packet.common.RemoteATCommandResponsePacket;
import com.digi.xbee.api.packet.common.TransmitPacket;
import com.digi.xbee.api.packet.common.TransmitStatusPacket;
import com.digi.xbee.api.packet.devicecloud.DeviceRequestPacket;
import com.digi.xbee.api.packet.devicecloud.DeviceResponsePacket;
import com.digi.xbee.api.packet.devicecloud.DeviceResponseStatusPacket;
import com.digi.xbee.api.packet.devicecloud.FrameErrorPacket;
import com.digi.xbee.api.packet.devicecloud.SendDataRequestPacket;
import com.digi.xbee.api.packet.devicecloud.SendDataResponsePacket;
import com.digi.xbee.api.packet.ip.RXIPv4Packet;
import com.digi.xbee.api.packet.ip.TXIPv4Packet;
import com.digi.xbee.api.packet.raw.RX16IOPacket;
import com.digi.xbee.api.packet.raw.RX16Packet;
import com.digi.xbee.api.packet.raw.RX64IOPacket;
import com.digi.xbee.api.packet.raw.RX64Packet;
import com.digi.xbee.api.packet.raw.TX16Packet;
import com.digi.xbee.api.packet.raw.TX64Packet;
import com.digi.xbee.api.packet.raw.TXStatusPacket;
import com.digi.xbee.api.packet.wifi.IODataSampleRxIndicatorWifiPacket;
import com.digi.xbee.api.packet.wifi.RemoteATCommandResponseWifiPacket;
import com.digi.xbee.api.packet.wifi.RemoteATCommandWifiPacket;
import com.digi.xbee.api.utils.HexUtils;

/**
 * This class reads and parses XBee packets from the input stream returning
 * a generic {@code XBeePacket} which can be casted later to the corresponding 
 * high level specific API packet.
 * 
 * 

All the API and API2 logic is already included so all packet reads are * independent of the XBee operating mode.

* *

Two API modes are supported and both can be enabled using the {@code AP} * (API Enable) command: * *

    *
  • API1 - API Without Escapes *

    The data frame structure is defined as follows:

    * *
     * {@code 
     *   Start Delimiter          Length                   Frame Data                   Checksum
     *       (Byte 1)            (Bytes 2-3)               (Bytes 4-n)                (Byte n + 1)
     * +----------------+  +-------------------+  +--------------------------- +  +----------------+
     * |      0x7E      |  |   MSB   |   LSB   |  |   API-specific Structure   |  |     1 Byte     |
     * +----------------+  +-------------------+  +----------------------------+  +----------------+
     *                MSB = Most Significant Byte, LSB = Least Significant Byte
     * }
     * 
    *
  • * *
  • API2 - API With Escapes *

    The data frame structure is defined as follows:

    * *
     * {@code 
     *   Start Delimiter          Length                   Frame Data                   Checksum
     *       (Byte 1)            (Bytes 2-3)               (Bytes 4-n)                (Byte n + 1)
     * +----------------+  +-------------------+  +--------------------------- +  +----------------+
     * |      0x7E      |  |   MSB   |   LSB   |  |   API-specific Structure   |  |     1 Byte     |
     * +----------------+  +-------------------+  +----------------------------+  +----------------+
     *                     \___________________________________  _________________________________/
     *                                                         \/
     *                                             Characters Escaped If Needed
     *                                             
     *                MSB = Most Significant Byte, LSB = Least Significant Byte
     * }
     * 
    * *

    When sending or receiving an API2 frame, specific data values must be * escaped (flagged) so they do not interfere with the data frame sequencing. * To escape an interfering data byte, the byte {@code 0x7D} is inserted before * the byte to be escaped XOR'd with {@code 0x20}.

    * *

    The data bytes that need to be escaped:

    *
      *
    • {@code 0x7E} - Frame Delimiter ({@link SpecialByte#HEADER_BYTE})
    • *
    • {@code 0x7D} - Escape ({@link SpecialByte#ESCAPE_BYTE})
    • *
    • {@code 0x11} - XON ({@link SpecialByte#XON_BYTE})
    • *
    • {@code 0x13} - XOFF ({@link SpecialByte#XOFF_BYTE})
    • *
    * *
  • *
* *

The length field has a two-byte value that specifies the number of * bytes that will be contained in the frame data field. It does not include the * checksum field.

* *

The frame data forms an API-specific structure as follows:

* *
 * {@code 
 *   Start Delimiter          Length                   Frame Data                   Checksum
 *       (Byte 1)            (Bytes 2-3)               (Bytes 4-n)                (Byte n + 1)
 * +----------------+  +-------------------+  +--------------------------- +  +----------------+
 * |      0x7E      |  |   MSB   |   LSB   |  |   API-specific Structure   |  |     1 Byte     |
 * +----------------+  +-------------------+  +----------------------------+  +----------------+
 *                                            /                                                 \
 *                                           /  API Identifier        Identifier specific data   \
 *                                           +------------------+  +------------------------------+
 *                                           |       cmdID      |  |           cmdData            |
 *                                           +------------------+  +------------------------------+
 * }
 * 
* *

The {@code cmdID} frame (API-identifier) indicates which API messages * will be contained in the {@code cmdData} frame (Identifier-specific data). *

* *

To test data integrity, a checksum is calculated and verified on * non-escaped data.

* * @see APIFrameType * @see XBeePacket * @see com.digi.xbee.api.models.OperatingMode */ public class XBeePacketParser { /** * Parses the bytes from the given input stream depending on the provided * operating mode and returns the API packet. * *

The operating mode must be {@link OperatingMode#API} or * {@link OperatingMode#API_ESCAPE}.

* * @param inputStream Input stream to read bytes from. * @param mode XBee device operating mode. * * @return Parsed packet from the input stream. * * @throws IllegalArgumentException if {@code mode != OperatingMode.API } and * if {@code mode != OperatingMode.API_ESCAPE}. * @throws InvalidPacketException if there is not enough data in the stream or * if there is an error verifying the checksum or * if the payload is invalid for the specified frame type. * @throws NullPointerException if {@code inputStream == null} or * if {@code mode == null}. * * @see XBeePacket * @see com.digi.xbee.api.models.OperatingMode#API * @see com.digi.xbee.api.models.OperatingMode#API_ESCAPE */ public XBeePacket parsePacket(InputStream inputStream, OperatingMode mode) throws InvalidPacketException { if (inputStream == null) throw new NullPointerException("Input stream cannot be null."); if (mode == null) throw new NullPointerException("Operating mode cannot be null."); if (mode != OperatingMode.API && mode != OperatingMode.API_ESCAPE) throw new IllegalArgumentException("Operating mode must be API or API Escaped."); try { // Read packet size. int hSize = readByte(inputStream, mode); int lSize = readByte(inputStream, mode); int length = hSize << 8 | lSize; // Read the payload. byte[] payload = readBytes(inputStream, mode, length); // Calculate the expected checksum. XBeeChecksum checksum = new XBeeChecksum(); checksum.add(payload); byte expectedChecksum = (byte)(checksum.generate() & 0xFF); // Read checksum from the input stream. byte readChecksum = (byte)(readByte(inputStream, mode) & 0xFF); // Verify the checksum of the read bytes. if (readChecksum != expectedChecksum) throw new InvalidPacketException("Invalid checksum (expected 0x" + HexUtils.byteToHexString(expectedChecksum) + ")."); return parsePayload(payload); } catch (IOException e) { throw new InvalidPacketException("Error parsing packet: " + e.getMessage(), e); } } /** * Parses the bytes from the given array depending on the provided operating * mode and returns the API packet. * *

The operating mode must be {@link OperatingMode#API} or * {@link OperatingMode#API_ESCAPE}.

* * @param packetByteArray Byte array with the complete frame, starting from * the header and ending in the checksum. * @param mode XBee device operating mode. * * @return Parsed packet from the given byte array. * * @throws InvalidPacketException if there is not enough data in the array or * if there is an error verifying the checksum or * if the payload is invalid for the specified frame type. * @throws IllegalArgumentException if {@code mode != OperatingMode.API } and * if {@code mode != OperatingMode.API_ESCAPE}. * @throws NullPointerException if {@code packetByteArray == null} or * if {@code mode == null}. * * @see XBeePacket * @see com.digi.xbee.api.models.OperatingMode#API * @see com.digi.xbee.api.models.OperatingMode#API_ESCAPE */ public XBeePacket parsePacket(byte[] packetByteArray, OperatingMode mode) throws InvalidPacketException { if (packetByteArray == null) throw new NullPointerException("Packet byte array cannot be null."); if (mode == null) throw new NullPointerException("Operating mode cannot be null."); if (mode != OperatingMode.API && mode != OperatingMode.API_ESCAPE) throw new IllegalArgumentException("Operating mode must be API or API Escaped."); // Check the byte array has at least 4 bytes. if (packetByteArray.length < 4) throw new InvalidPacketException("Error parsing packet: Incomplete packet."); // Check the header of the frame. if ((packetByteArray[0] & 0xFF) != SpecialByte.HEADER_BYTE.getValue()) throw new InvalidPacketException("Invalid start delimiter (expected 0x" + HexUtils.byteToHexString((byte)SpecialByte.HEADER_BYTE.getValue()) + ")."); return parsePacket(new ByteArrayInputStream(packetByteArray, 1, packetByteArray.length - 1), mode); } /** * Parses the given API payload to get the right API packet, depending * on its API type ({@code payload[0]}). * * @param payload The payload of the API frame. * * @return The corresponding API packet or {@code UnknownXBeePacket} if * the frame API type is unknown. * * @throws InvalidPacketException if the payload is invalid for the * specified frame type. * * @see APIFrameType * @see XBeePacket */ private XBeePacket parsePayload(byte[] payload) throws InvalidPacketException { // Get the API frame type. APIFrameType apiType = APIFrameType.get(payload[0] & 0xFF); if (apiType == null) // Create unknown packet. return UnknownXBeePacket.createPacket(payload); // Parse API payload depending on API ID. XBeePacket packet = null; switch (apiType) { case TX_64: packet = TX64Packet.createPacket(payload); break; case TX_16: packet = TX16Packet.createPacket(payload); break; case REMOTE_AT_COMMAND_REQUEST_WIFI: packet = RemoteATCommandWifiPacket.createPacket(payload); break; case AT_COMMAND: packet = ATCommandPacket.createPacket(payload); break; case AT_COMMAND_QUEUE: packet = ATCommandQueuePacket.createPacket(payload); break; case TRANSMIT_REQUEST: packet = TransmitPacket.createPacket(payload); break; case EXPLICIT_ADDRESSING_COMMAND_FRAME: packet = ExplicitAddressingPacket.createPacket(payload); break; case REMOTE_AT_COMMAND_REQUEST: packet = RemoteATCommandPacket.createPacket(payload); break; case TX_SMS: packet = TXSMSPacket.createPacket(payload); break; case TX_IPV4: packet = TXIPv4Packet.createPacket(payload); break; case SEND_DATA_REQUEST: packet = SendDataRequestPacket.createPacket(payload); break; case DEVICE_RESPONSE: packet = DeviceResponsePacket.createPacket(payload); break; case RX_64: packet = RX64Packet.createPacket(payload); break; case RX_16: packet = RX16Packet.createPacket(payload); break; case RX_IO_64: packet = RX64IOPacket.createPacket(payload); break; case RX_IO_16: packet = RX16IOPacket.createPacket(payload); break; case REMOTE_AT_COMMAND_RESPONSE_WIFI: packet = RemoteATCommandResponseWifiPacket.createPacket(payload); break; case AT_COMMAND_RESPONSE: packet = ATCommandResponsePacket.createPacket(payload); break; case TX_STATUS: packet = TXStatusPacket.createPacket(payload); break; case MODEM_STATUS: packet = ModemStatusPacket.createPacket(payload); break; case TRANSMIT_STATUS: packet = TransmitStatusPacket.createPacket(payload); break; case IO_DATA_SAMPLE_RX_INDICATOR_WIFI: packet = IODataSampleRxIndicatorWifiPacket.createPacket(payload); break; case RECEIVE_PACKET: packet = ReceivePacket.createPacket(payload); break; case EXPLICIT_RX_INDICATOR: packet = ExplicitRxIndicatorPacket.createPacket(payload); break; case IO_DATA_SAMPLE_RX_INDICATOR: packet = IODataSampleRxIndicatorPacket.createPacket(payload); break; case REMOTE_AT_COMMAND_RESPONSE: packet = RemoteATCommandResponsePacket.createPacket(payload); break; case RX_SMS: packet = RXSMSPacket.createPacket(payload); break; case RX_IPV4: packet = RXIPv4Packet.createPacket(payload); break; case SEND_DATA_RESPONSE: packet = SendDataResponsePacket.createPacket(payload); break; case DEVICE_REQUEST: packet = DeviceRequestPacket.createPacket(payload); break; case DEVICE_RESPONSE_STATUS: packet = DeviceResponseStatusPacket.createPacket(payload); break; case FRAME_ERROR: packet = FrameErrorPacket.createPacket(payload); break; case GENERIC: packet = GenericXBeePacket.createPacket(payload); break; case UNKNOWN: default: packet = UnknownXBeePacket.createPacket(payload); } return packet; } /** * Reads one byte from the input stream. * *

This operation checks several things like the working mode in order * to consider escaped bytes.

* * @param inputStream Input stream to read bytes from. * @param mode XBee device working mode. * * @return The read byte. * * @throws InvalidPacketException if there is not enough data in the stream or * if there is an error verifying the checksum. * @throws IOException if the first byte cannot be read for any reason other than end of file, or * if the input stream has been closed, or * if some other I/O error occurs. */ private int readByte(InputStream inputStream, OperatingMode mode) throws InvalidPacketException, IOException { int timeout = 300; int b = readByteFrom(inputStream, timeout); if (b == -1) throw new InvalidPacketException("Error parsing packet: Incomplete packet."); /* Process the byte for API1. */ if (mode == OperatingMode.API) return b; /* Process the byte for API2. */ // Check if the byte is special. if (!SpecialByte.isSpecialByte(b)) return b; // Check if the byte is ESCAPE. if (b == SpecialByte.ESCAPE_BYTE.getValue()) { // Read next byte and escape it. b = readByteFrom(inputStream, timeout); if (b == -1) throw new InvalidPacketException("Error parsing packet: Incomplete packet."); b ^= 0x20; } else // If the byte is not a escape there is a special byte not escaped. throw new InvalidPacketException("Special byte not escaped: 0x" + HexUtils.byteToHexString((byte)(b & 0xFF)) + "."); return b; } /** * Reads the given amount of bytes from the input stream. * *

This operation checks several things like the working mode in order * to consider escaped bytes.

* * @param inputStream Input stream to read bytes from. * @param mode XBee device working mode. * @param numBytes Number of bytes to read. * * @return The read byte array. * * @throws IOException if the first byte cannot be read for any reason other than end of file, or * if the input stream has been closed, or * if some other I/O error occurs. * @throws InvalidPacketException if there is not enough data in the stream or * if there is an error verifying the checksum. */ private byte[] readBytes(InputStream inputStream, OperatingMode mode, int numBytes) throws IOException, InvalidPacketException { byte[] data = new byte[numBytes]; for (int i = 0; i < numBytes; i++) data[i] = (byte)readByte(inputStream, mode); return data; } /** * Reads a byte from the given input stream. * * @param inputStream The input stream to read the byte. * @param timeout Timeout to wait for a byte in the input stream * in milliseconds. * * @return The read byte or {@code -1} if the timeout expires or the end * of the stream is reached. * * @throws IOException if an I/O errors occurs while reading the byte. */ private int readByteFrom(InputStream inputStream, int timeout) throws IOException { long deadline = new Date().getTime() + timeout; int b = inputStream.read(); // Let's try again if the byte is -1. while (b == -1 && new Date().getTime() < deadline) { b = inputStream.read(); try { Thread.sleep(10); } catch (InterruptedException e) {} } return b; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy