/** ****************************************************************************** * @addtogroup OpenPilotSystem OpenPilot System * @{ * @addtogroup OpenPilotLibraries OpenPilot System Libraries * @{ * * @file packet_handler.c * @author The OpenPilot Team, http://www.openpilot.org Copyright (C) 2012. * @brief A packet handler for handeling radio packet transmission. * @see The GNU Public License (GPL) Version 3 * *****************************************************************************/ /* * This program 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. * * This program 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 this program; if not, write to the Free Software Foundation, Inc., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "openpilot.h" #include "packet_handler.h" #include "aes.h" #include "ecc.h" extern char *debug_msg; // Private types and constants typedef struct { PacketHandlerConfig cfg; PHPacket *tx_packets; uint8_t tx_win_start; uint8_t tx_win_end; PHPacket *rx_packets; uint8_t rx_win_start; uint8_t rx_win_end; uint16_t tx_seq_id; PHOutputStream stream; xSemaphoreHandle lock; PHOutputStream output_stream; PHDataHandler data_handler; PHStatusHandler status_handler; PHPPMHandler ppm_handler; } PHPacketData, *PHPacketDataHandle; // Private functions static uint8_t PHLSendAck(PHPacketDataHandle data, PHPacketHandle p); static uint8_t PHLSendNAck(PHPacketDataHandle data, PHPacketHandle p); static uint8_t PHLTransmitPacket(PHPacketDataHandle data, PHPacketHandle p); /** * Initialize the Packet Handler library * \param[in] txWinSize The transmission window size (number of tx packet buffers). * \param[in] streme A callback function for transmitting the packet. * \param[in] id The source ID of transmitter. * \return PHInstHandle The Pachet Handler instance data. * \return 0 Failure */ PHInstHandle PHInitialize(PacketHandlerConfig *cfg) { // Allocate the primary structure PHPacketDataHandle data = pvPortMalloc(sizeof(PHPacketData)); if (!data) return 0; data->cfg = *cfg; data->tx_seq_id = 0; // Allocate the packet windows data->tx_packets = pvPortMalloc(sizeof(PHPacket) * data->cfg.winSize); data->rx_packets = pvPortMalloc(sizeof(PHPacket) * data->cfg.winSize); // Initialize the windows data->tx_win_start = data->tx_win_end = 0; data->rx_win_start = data->rx_win_end = 0; for (uint8_t i = 0; i < data->cfg.winSize; ++i) { data->tx_packets[i].header.type = PACKET_TYPE_NONE; data->rx_packets[i].header.type = PACKET_TYPE_NONE; } // Create the lock data->lock = xSemaphoreCreateRecursiveMutex(); // Initialize the ECC library. initialize_ecc(); // Return the structure. return (PHInstHandle)data; } /** * Register an output stream handler * \param[in] h The packet handler instance data pointer. * \param[in] f The output stream handler function */ void PHRegisterOutputStream(PHInstHandle h, PHOutputStream f) { PHPacketDataHandle data = (PHPacketDataHandle)h; data->output_stream = f; } /** * Register a data handler * \param[in] h The packet handler instance data pointer. * \param[in] f The data handler function */ void PHRegisterDataHandler(PHInstHandle h, PHDataHandler f) { PHPacketDataHandle data = (PHPacketDataHandle)h; data->data_handler = f; } /** * Register a PPM packet handler * \param[in] h The packet handler instance data pointer. * \param[in] f The PPM handler function */ void PHRegisterStatusHandler(PHInstHandle h, PHStatusHandler f) { PHPacketDataHandle data = (PHPacketDataHandle)h; data->status_handler = f; } /** * Register a PPM packet handler * \param[in] h The packet handler instance data pointer. * \param[in] f The PPM handler function */ void PHRegisterPPMHandler(PHInstHandle h, PHPPMHandler f) { PHPacketDataHandle data = (PHPacketDataHandle)h; data->ppm_handler = f; } /** * Get a packet out of the transmit buffer. * \param[in] h The packet handler instance data pointer. * \param[in] dest_id The destination ID of this connection * \return PHPacketHandle A pointer to the packet buffer. * \return 0 No packets buffers avaiable in the transmit window. */ uint32_t PHConnect(PHInstHandle h, uint32_t dest_id) { return 1; } /** * Get a packet out of the transmit buffer. * \param[in] h The packet handler instance data pointer. * \return PHPacketHandle A pointer to the packet buffer. * \return 0 No packets buffers avaiable in the transmit window. */ PHPacketHandle PHGetTXPacket(PHInstHandle h) { PHPacketDataHandle data = (PHPacketDataHandle)h; // Lock xSemaphoreTakeRecursive(data->lock, portMAX_DELAY); // Find a free packet. PHPacketHandle p = NULL; for (uint8_t i = 0; i < data->cfg.winSize; ++i) if (data->tx_packets[i].header.type == PACKET_TYPE_NONE) { p = data->tx_packets + i; break; } // Release lock xSemaphoreGiveRecursive(data->lock); // Return a pointer to the packet at the end of the TX window. return p; } /** * Release a packet from the transmit packet buffer window. * \param[in] h The packet handler instance data pointer. * \param[in] p A pointer to the packet buffer. * \return Nothing */ void PHReleaseTXPacket(PHInstHandle h, PHPacketHandle p) { PHPacketDataHandle data = (PHPacketDataHandle)h; // Lock xSemaphoreTakeRecursive(data->lock, portMAX_DELAY); // Change the packet type so we know this packet is unused. p->header.type = PACKET_TYPE_NONE; // If this packet is at the start of the window, increment the start index. while ((data->tx_win_start != data->tx_win_end) && (data->tx_packets[data->tx_win_start].header.type == PACKET_TYPE_NONE)) data->tx_win_start = (data->tx_win_start + 1) % data->cfg.winSize; // Release lock xSemaphoreGiveRecursive(data->lock); } /** * Get a packet out of the receive buffer. * \param[in] h The packet handler instance data pointer. * \return PHPacketHandle A pointer to the packet buffer. * \return 0 No packets buffers avaiable in the transmit window. */ PHPacketHandle PHGetRXPacket(PHInstHandle h) { PHPacketDataHandle data = (PHPacketDataHandle)h; // Lock xSemaphoreTakeRecursive(data->lock, portMAX_DELAY); // Find a free packet. PHPacketHandle p = NULL; for (uint8_t i = 0; i < data->cfg.winSize; ++i) if (data->rx_packets[i].header.type == PACKET_TYPE_NONE) { p = data->rx_packets + i; break; } // Release lock xSemaphoreGiveRecursive(data->lock); // Return a pointer to the packet at the end of the TX window. return p; } /** * Release a packet from the receive packet buffer window. * \param[in] h The packet handler instance data pointer. * \param[in] p A pointer to the packet buffer. * \return Nothing */ void PHReleaseRXPacket(PHInstHandle h, PHPacketHandle p) { PHPacketDataHandle data = (PHPacketDataHandle)h; // Lock xSemaphoreTakeRecursive(data->lock, portMAX_DELAY); // Change the packet type so we know this packet is unused. p->header.type = PACKET_TYPE_NONE; // If this packet is at the start of the window, increment the start index. while ((data->rx_win_start != data->rx_win_end) && (data->rx_packets[data->rx_win_start].header.type == PACKET_TYPE_NONE)) data->rx_win_start = (data->rx_win_start + 1) % data->cfg.winSize; // Release lock xSemaphoreGiveRecursive(data->lock); } /** * Transmit a packet from the transmit packet buffer window. * \param[in] h The packet handler instance data pointer. * \param[in] p A pointer to the packet buffer. * \return 1 Success * \return 0 Failure */ uint8_t PHTransmitPacket(PHInstHandle h, PHPacketHandle p) { PHPacketDataHandle data = (PHPacketDataHandle)h; // Try to transmit the packet. if (!PHLTransmitPacket(data, p)) return 0; // If this packet doesn't require an ACK, remove it from the TX window. switch (p->header.type) { case PACKET_TYPE_READY: case PACKET_TYPE_NOTREADY: case PACKET_TYPE_DATA: case PACKET_TYPE_PPM: PHReleaseTXPacket(h, p); break; } return 1; } /** * Verify that a buffer contains a valid packet. * \param[in] h The packet handler instance data pointer. * \param[in] p A pointer to the packet buffer. * \param[in] received_len The length of data received. * \return < 0 Failure * \return > 0 Number of bytes consumed. */ int32_t PHVerifyPacket(PHInstHandle h, PHPacketHandle p, uint16_t received_len) { // Verify the packet length. // Note: The last two bytes should be the RSSI and AFC. uint16_t len = PHPacketSizeECC(p); if (received_len < (len + 2)) { DEBUG_PRINTF(1, "Packet length error %d %d\n\r", received_len, len + 2); return -1; } // Attempt to correct any errors in the packet. decode_data((unsigned char*)p, len); // Check that there were no unfixed errors. bool rx_error = check_syndrome() != 0; if(rx_error) { DEBUG_PRINTF(1, "Error in packet\n\r"); return -2; } return len + 2; } /** * Process a packet that has been received. * \param[in] h The packet handler instance data pointer. * \param[in] p A pointer to the packet buffer. * \param[in] received_len The length of data received. * \return 0 Failure * \return 1 Success */ uint8_t PHReceivePacket(PHInstHandle h, PHPacketHandle p, bool rx_error) { PHPacketDataHandle data = (PHPacketDataHandle)h; uint16_t len = PHPacketSizeECC(p); // Extract the RSSI and AFC. int8_t rssi = *(((int8_t*)p) + len); int8_t afc = *(((int8_t*)p) + len + 1); switch (p->header.type) { case PACKET_TYPE_STATUS: if (!rx_error) // Pass on the channels to the status handler. if(data->status_handler) data->status_handler((PHStatusPacketHandle)p, rssi, afc); break; case PACKET_TYPE_ACKED_DATA: // Send the ACK / NACK if (rx_error) { DEBUG_PRINTF(1, "Sending NACK\n\r"); PHLSendNAck(data, p); } else { PHLSendAck(data, p); // Pass on the data. if(data->data_handler) data->data_handler(p->data, p->header.data_size, rssi, afc); } break; case PACKET_TYPE_ACK: { // Find the packet ID in the TX buffer, and free it. unsigned int i = 0; for (unsigned int i = 0; i < data->cfg.winSize; ++i) if (data->tx_packets[i].header.tx_seq == p->header.rx_seq) PHReleaseTXPacket(h, data->tx_packets + i); #ifdef DEBUG_LEVEL if (i == data->cfg.winSize) DEBUG_PRINTF(1, "Error finding acked packet to release\n\r"); #endif } break; case PACKET_TYPE_NACK: { // Resend the packet. unsigned int i = 0; for ( ; i < data->cfg.winSize; ++i) if (data->tx_packets[i].header.tx_seq == p->header.rx_seq) PHLTransmitPacket(data, data->tx_packets + i); #ifdef DEBUG_LEVEL if (i == data->cfg.winSize) DEBUG_PRINTF(1, "Error finding acked packet to NACK\n\r"); DEBUG_PRINTF(1, "Resending after NACK\n\r"); #endif } break; case PACKET_TYPE_PPM: if (!rx_error) // Pass on the channels to the PPM handler. if(data->ppm_handler) data->ppm_handler(((PHPpmPacketHandle)p)->channels); break; case PACKET_TYPE_DATA: if (!rx_error) // Pass on the data to the data handler. if(data->data_handler) data->data_handler(p->data, p->header.data_size, rssi, afc); break; default: break; } // Release the packet. PHReleaseRXPacket(h, p); return 1; } /** * Transmit a packet from the transmit packet buffer window. * \param[in] data The packet handler instance data pointer. * \param[in] p A pointer to the packet buffer. * \return 1 Success * \return 0 Failure */ static uint8_t PHLTransmitPacket(PHPacketDataHandle data, PHPacketHandle p) { if(!data->output_stream) return 0; // Set the sequence ID to the current ID. p->header.tx_seq = data->tx_seq_id++; // Add the error correcting code. encode_data((unsigned char*)p, PHPacketSize(p), (unsigned char*)p); // Transmit the packet using the output stream. if(data->output_stream(p) == -1) return 0; return 1; } /** * Send an ACK packet. * \param[in] data The packet handler instance data pointer. * \param[in] p A pointer to the packet buffer of the packet to be ACKed. * \return 1 Success * \return 0 Failure */ static uint8_t PHLSendAck(PHPacketDataHandle data, PHPacketHandle p) { // Create the ACK message PHPacketHeader ack; ack.destination_id = p->header.source_id; ack.type = PACKET_TYPE_ACK; ack.rx_seq = p->header.tx_seq; ack.data_size = 0; // Send the packet. return PHLTransmitPacket(data, (PHPacketHandle)&ack); } /** * Send an NAck packet. * \param[in] data The packet handler instance data pointer. * \param[in] p A pointer to the packet buffer of the packet to be ACKed. * \return 1 Success * \return 0 Failure */ static uint8_t PHLSendNAck(PHPacketDataHandle data, PHPacketHandle p) { // Create the NAck message PHPacketHeader ack; ack.destination_id = p->header.source_id; ack.type = PACKET_TYPE_NACK; ack.rx_seq = p->header.tx_seq; ack.data_size = 0; // Set the packet. return PHLTransmitPacket(data, (PHPacketHandle)&ack); }