Add support for ACK response for Generic Frame Fragments

This commit is contained in:
superkor
2026-01-06 14:33:01 -05:00
parent cc8b23cd45
commit 14875aafa2
21 changed files with 2002 additions and 452 deletions

View File

@@ -18,6 +18,9 @@
#define CRC_POLYNOMIAL 0x1021
static const char* NVS_BOARD_ID_KEY = "id";
static const char* NVS_BOARD_NAMESPACE = "board";
//look up table for crc
static const uint16_t crc16_table[256] = {
0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, 0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF,
@@ -39,6 +42,7 @@ static const uint16_t crc16_table[256] = {
};
#define ASYNC_QUEUE_WAIT_TICKS 100
#define SEQUENCE_NUM_MAP_MUTEX_MAX_WAIT_MS 50
/**
* @brief Class to represent the Data Link Layer
@@ -56,11 +60,17 @@ class DataLinkManager{
esp_err_t get_routing_table(RIPRow_public* table, size_t* table_size);
esp_err_t async_receive_info(uint16_t* frame_size, FrameHeader* header, uint8_t channel);
esp_err_t async_receive(uint8_t* data, uint16_t data_len, FrameHeader* header, uint8_t channel);
esp_err_t ready();
esp_err_t send_ack(uint8_t sender_id, uint8_t* data, uint16_t data_len);
private:
uint8_t this_board_id = 0;
uint8_t num_channels = MAX_CHANNELS;
std::unique_ptr<RMTManager> phys_comms;
std::unordered_map<uint8_t, uint16_t> sequence_num_map;
SemaphoreHandle_t sequence_num_map_mutex;
esp_err_t get_inc_sequence_num(uint8_t board_id, uint16_t* seq_num);
esp_err_t get_sequence_num(uint8_t board_id, uint16_t* seq_num);
volatile bool stop_tasks = false; //used by the tasks to know when to stop (set true when DataLinkManager is destroyed)
TaskHandle_t rip_broadcast_task = NULL;
@@ -76,6 +86,7 @@ class DataLinkManager{
esp_err_t create_generic_frame(uint8_t* data, uint16_t data_len, GenericFrame generic_frame, uint16_t offset, uint8_t* send_data, size_t* send_data_len);
//==== RIP related functions ====
void init_rip();
esp_err_t rip_find_entry(uint8_t board_id, RIPRow** entry, bool reserve_row);
esp_err_t rip_update_entry(uint8_t new_hop, uint8_t channel, RIPRow** entry);
@@ -108,59 +119,38 @@ class DataLinkManager{
void init_scheduler();
esp_err_t push_frame_to_scheduler(SchedulerMetadata frame, uint8_t channel);
TaskHandle_t scheduler_task = NULL;
/**
* @brief Schedules which frame to send
*
* Scheduler:
* - All frames will be pushed to the back onto a queue
* - When a generic frame sends a chunk, it will be pushed back to the queue for the next chunk to be sent
*
* Scheduling may change (above scheduler will lead to starvation of control frames depending on the number of generic frames/fragments to send)
*/
[[noreturn]] static void frame_scheduler(void* args);
/**
* @brief Scheduler sending the actual frame at the top of the heap on a channel
*
* @return esp_err_t
*/
esp_err_t scheduler_send(uint8_t channel);
esp_err_t scheduler_send_rmt(uint8_t channel, SchedulerMetadata frame, uint8_t* send_data, size_t frame_size, bool wait_for_tx_done);
//Generic Frame Receive Fragments
/**
* @brief Store a fragment that has been received
*
* @param fragment
* @param channel
* @return esp_err_t
*/
esp_err_t store_fragment(GenericFrame* fragment, uint8_t channel);
/**
* @brief Stores generic frame fragments
*
* Mapping:
* Board ID -> Sequence number -> Array of Generic Frame Fragments, with size of the number of expected fragments
* Board ID (of the receiver) -> Sequence number -> Array of Generic Frame Fragments, with size of the number of expected fragments
*
* TODO:
* - When receiving a fragment, insert it into the map
* - When all fragments have been received in a sequence, remove the entire entry (sequence) from the map, and push final data for async receive
* - Sliding window + ACKs
*
*/
std::unordered_map<uint16_t, std::unordered_map<uint16_t, FragmentMetadata>> fragment_map;
std::unordered_map<uint16_t, std::unordered_map<uint16_t, FragmentMetadata>> fragment_map[MAX_CHANNELS];
esp_err_t complete_fragment(uint16_t board_id, uint16_t sequence_num, uint8_t channel);
SemaphoreHandle_t async_rx_queue_mutex[MAX_CHANNELS];
SemaphoreHandle_t rx_fragment_mutex[MAX_CHANNELS];
//Async receive
/**
* @brief Queue to store complete received frame data
*
* TODO:
* - Replace the public `receive()` with the `async_receive()`.
*
*/
std::queue<Rx_Metadata> async_receive_queue[MAX_CHANNELS];
@@ -185,6 +175,33 @@ class DataLinkManager{
TaskHandle_t receive_task = NULL;
/**
* @brief Generic Frame Sliding Window
*
* Mapping:
* Board Id (of the receiver) -> Sequence Number -> FrameAckRecord
*
*/
std::unordered_map<uint16_t, std::unordered_map<uint16_t, FrameAckRecord>> sliding_window[MAX_CHANNELS];
SemaphoreHandle_t sliding_window_mutex[MAX_CHANNELS];
esp_err_t inc_head_sliding_window(uint8_t channel, uint8_t board_id, uint16_t seq_num, FrameAckRecord* ack_record);
esp_err_t get_record_sliding_window(uint8_t channel, uint8_t board_id, uint16_t seq_num, FrameAckRecord* ack_record);
esp_err_t complete_record_sliding_window(uint8_t channel, uint8_t board_id, uint16_t seq_num);
/**
* @brief Thread for sending acks - Send ACKs on a separate thread to not hold up the receive thread (missing other frames)
*
* @param args
*/
[[noreturn]] static void send_ack_thread_main(void* args);
TaskHandle_t send_ack_task = NULL;
SemaphoreHandle_t send_ack_queue_mutex[MAX_CHANNELS];
std::queue<SendAckMetaData> send_ack_queue[MAX_CHANNELS];
};
#endif //DATA_LINK

View File

@@ -10,9 +10,9 @@
#define START_OF_FRAME 0xAB //0b1010_1011 - denotes the start of frame
#define MAX_CONTROL_DATA_LEN (1 << 8) // Max 256B
#define MAX_FRAME_SIZE 121 //Max 121B (due to rmt) - note this includes the overhead of the frame. the actual payload max depends on the frame type (eg. 121 - 9 B is the max control data length)
#define MAX_GENERIC_NUM_FRAG (1 << 16) // Max 2**16 Fragments can be made with a generic frame (total 2**16 *MAX_CONTROL_DATA_LEN B of data can be sent ~ 16 MiB)
#define MAX_GENERIC_NUM_FRAG (1 << 16) // Max 2**16 Fragments can be made with a generic frame (total 2**16 *MAX_GENERIC_DATA_LEN B of data can be sent ~ 6.7 MiB)
#define MAX_FRAME_QUEUE_SIZE 15 //Size of the queue for the frame scheduler (per channel)
@@ -30,17 +30,33 @@
#define CONTROL_FRAME_OVERHEAD 9
#define GENERIC_FRAME_OVERHEAD 14
#define MAX_GENERIC_DATA_LEN (MAX_FRAME_SIZE - GENERIC_FRAME_OVERHEAD)
#define MAX_CONTROL_DATA_LEN (MAX_FRAME_SIZE - CONTROL_FRAME_OVERHEAD)
//Generic Frame Fragment ACK
#define GENERIC_FRAG_ACK_DATA_SIZE 7
#define GENERIC_FRAG_ACK_PREAMBLE 0x69
#define CONTROL_FRAME_TYPE 0x80 //if the frame type MSB is set to 1, use the control frame
//Types (total 2^4 = 16 different types)
enum class FrameType : uint8_t {
//Control Frames
MOTOR_TYPE = 0x80, //0b1000_0000
SERVO_TYPE = 0xC0, //0b1100_0000
DISTANCE_SENSOR_TYPE = 0xE0, //0b1110_0000
DEBUG_CONTROL_TYPE = 0xC0, //0b1100_0000
DEBUG_GENERIC_TYPE = 0x00, //0b0000_0000
SYSTEM_TYPE = 0x30, //0x0011_0000 - used for statuses, discovery, and other maintainence requests
RIP_TABLE_CONTROL = 0x90, //0b1001_0000 - using the control frame to broadcast the RIP table
RIP_TABLE_GENERIC = 0x10 //0b0001_000 - using the generic frame to broadcast the RIP table
DISTANCE_SENSOR_TYPE = 0xA0, //0b1010_0000
SERVO_TYPE = 0xC0, //0b1100_0000
MISC_CONTROL_TYPE = 0xD0, //0b1101_0000
//Generic Frames
MISC_GENERIC_TYPE = 0x00, //0b0000_0000
MISC_UDP_GENERIC_TYPE = 0x10, // 0b0001_0000 - Same as MISC_GENERIC_TYPE except no ACK frames will be expected
SYSTEM_TYPE = 0x30, //0b0011_0000 - used for statuses, discovery, and other maintainence requests
ACK_TYPE = 0x60, //0b0110_0000 - ACK frames for Generic Fragments
RIP_TABLE_GENERIC = 0x70 //0b0111_0000 - using the generic frame to broadcast the RIP table (not used rn)
};
enum class FrameFlags : uint8_t {
ANY_FLAG = 0x0,
};
#pragma pack(push, 1) //these structs will be transmitted as is (ensure the structs are structured using 1B alignment - no padding)
@@ -51,7 +67,7 @@ typedef struct _control_frame{
uint16_t seq_num; //sequence number to differentiate frames being sent from sender to receiver
uint8_t type_flag; //(type << 4) | flag - both are 4 bits
uint16_t data_len; //Data Length (max 256B)
uint8_t data[MAX_CONTROL_DATA_LEN]; //Variable Length of Data
uint8_t data[MAX_FRAME_SIZE]; //Variable Length of Data
uint16_t crc_16; //CRC-16
} ControlFrame; //this will have a max size of 9 + 256B = 265B
@@ -64,7 +80,7 @@ typedef struct _data_link_frame{
uint16_t total_frag; //total number of fragments for this sequence
uint16_t frag_num; //current fragment number
uint16_t data_len; //Data Length (max 178B)
uint8_t data[MAX_CONTROL_DATA_LEN]; //Variable Length of Data
uint8_t data[MAX_FRAME_SIZE]; //Variable Length of Data
uint16_t crc_16; //CRC-16
} GenericFrame; //this will have a max size of 14 + 2^8 B = 270 B
#pragma pack(pop)

View File

@@ -3,23 +3,43 @@
#include "esp_timer.h"
#include <cstdint>
//TODO: need to make a private send() to accept a Frame type.
//the public send() is fine to keep but needs to be modified to simply create the Frame type based on the flags, data size, and args, and then push to the priority queue based on routing
//TODO: receive also needs to be updated, when receiving a frame not destined for that board; need to route that frame towards the actual destination (requires pushing that frame onto the priority queue)
//TODO: generic frames receive needs to be created (handle fragmenting, resend on corruption). ethe resend on corruption will be a lot of work as it requires ACK frames to be send and currently there is 0 support for ACK since we don't save any recently sent frames (can't resend)
#define SCHEDULER_MUTEX_WAIT 10 //max time duration to wait
#define SCHEDULER_PERIOD_MS 25
#define SCHEDULER_PERIOD_MS 140
#define RECEIVE_TASK_PERIOD_MS 2
#define GENERIC_FRAME_SLIDING_WINDOW_SIZE 5 //defines the maximum size of the sliding window before resending previously un-ack'd fragments
#define SLIDING_WINDOW_MUTEX_TIMEOUT_MS 5
#define GENERIC_FRAME_MOD_TIMEOUT 10 //be scheduled at most 9 + GENERIC_FRAME_MIN_TIMEOUT times before sending another fragment
#define GENERIC_FRAME_MIN_TIMEOUT 10
#define SEND_ACK_PERIOD_MS 50
#define SEND_ACK_MUTEX_WAIT 10
//Metadata representing the frame to be sent but is currently scheduled
typedef struct _frame_scheduler_metadata {
FrameHeader header; //header of the frame
uint16_t generic_frame_data_offset; //For data greater than MAX_CONTROL_DATA_LEN to keep track of fragment positions
uint16_t generic_frame_data_offset; //For data greater than MAX_GENERIC_DATA_LEN to keep track of fragment positions
int64_t enqueue_time_ns; //when the frame has been first enqueued into the priority queue
uint8_t* data; //dyanmically allocated memory - contains the actual data
uint16_t len; // length of the actual data
//sliding window
uint16_t last_ack; //fragment number represnting the last ack'd fragment (from rx) - head
uint16_t curr_fragment; //fragment number of the current fragment being sent
uint32_t timeout;
} SchedulerMetadata;
typedef struct _frame_ack_record {
uint16_t last_ack; //last ack'd fragment recevied from the rx
uint16_t total_frags; //total number of fragments associated with the sequence number
uint16_t seq_num; //sequence number this ack corresponds to
} FrameAckRecord;
typedef struct _send_ack_metadata{
uint8_t data[GENERIC_FRAG_ACK_DATA_SIZE];
uint8_t sender_id;
} SendAckMetaData;
typedef struct _frame_compare {
/**
* @brief Uses aging based priority scheduling (linearly increasing priority with time)

View File

@@ -11,7 +11,7 @@
// #define RIP_BROADCAST_INTERVAL 3000 //temp broadcast every 3 seconds (3000ms)
#define RIP_TTL_START 180 //seconds
#define RIP_MS_TO_SEC 1000 //1000 ms to 1 sec
#define RIP_MAX_SEM_WAIT 30
#define RIP_MAX_SEM_WAIT_MS 30
#define RIP_FLUSH_COUNT 8 //flush after 8*30 seconds = 240 seconds
#define RIP_DISCOVERY_MESSAGE_SIZE 1