mirror of
https://github.com/BotChain-Robots/firmware.git
synced 2026-07-08 09:37:21 +02:00
Fix RIP bugs, add in UART
This commit is contained in:
@@ -10,7 +10,7 @@
|
||||
#include "flatbuffers_generated/RobotModule_generated.h"
|
||||
|
||||
inline std::unordered_map<int, int> MODULE_TO_NUM_CHANNELS_MAP {
|
||||
{ModuleType_SPLITTER, 4},
|
||||
{ModuleType_SPLITTER, 3}, // should be 4
|
||||
{ModuleType_SERVO_1, 2},
|
||||
{ModuleType_DC_MOTOR, 1},
|
||||
{ModuleType_SERVO_2, 2},
|
||||
@@ -19,7 +19,7 @@ inline std::unordered_map<int, int> MODULE_TO_NUM_CHANNELS_MAP {
|
||||
{ModuleType_SPEAKER, 1},
|
||||
{ModuleType_IMU, 1},
|
||||
{ModuleType_DISTANCE_SENSOR, 1},
|
||||
{ModuleType_SPLITTER_2, 4},
|
||||
{ModuleType_SPLITTER_2, 3}, // should be 4
|
||||
{ModuleType_SPLITTER_3, 3},
|
||||
{ModuleType_SPLITTER_4, 3},
|
||||
{ModuleType_SPLITTER_5, 3},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
idf_component_register(SRCS "DataLinkManager.cpp" "DataLinkRIP.cpp" "DataLinkScheduler.cpp" "DataLinkFrames.cpp"
|
||||
PRIV_REQUIRES driver esp_event nvs_flash esp_netif rmt
|
||||
PRIV_REQUIRES driver esp_event nvs_flash esp_netif rmt softUART
|
||||
REQUIRES esp_timer ptrQueue
|
||||
INCLUDE_DIRS "include")
|
||||
|
||||
@@ -289,6 +289,15 @@ esp_err_t DataLinkManager::receive_rmt(uint8_t channel){
|
||||
return ESP_ERR_INVALID_RESPONSE;
|
||||
}
|
||||
|
||||
// Preamble sanity check — if byte 0 is not START_OF_FRAME this buffer
|
||||
// contains a merged frame, a partial capture, or line noise. Discard
|
||||
// immediately rather than letting it propagate to the CRC checker.
|
||||
if (data[0] != START_OF_FRAME){
|
||||
ESP_LOGW(DEBUG_LINK_TAG, "Discarding frame on ch %d: bad preamble 0x%02X (expected 0x%02X)",
|
||||
channel, data[0], START_OF_FRAME);
|
||||
return ESP_ERR_INVALID_RESPONSE;
|
||||
}
|
||||
|
||||
// ---- LUT fast-path for control frames ----
|
||||
// Raw wire layout for control frames:
|
||||
// [0] preamble
|
||||
@@ -301,7 +310,8 @@ esp_err_t DataLinkManager::receive_rmt(uint8_t channel){
|
||||
// [12..] actual payload
|
||||
// [-2..-1] CRC-16
|
||||
bool is_control = IS_CONTROL_FRAME(data[5]);
|
||||
if (is_control && recv_len >= (size_t)(CONTROL_FRAME_OVERHEAD + CONTROL_FRAME_HASH_SIZE)){
|
||||
bool is_rip = (GET_TYPE(data[5]) == static_cast<uint8_t>(FrameType::RIP_TABLE_CONTROL));
|
||||
if (is_control && !is_rip && recv_len >= (size_t)(CONTROL_FRAME_OVERHEAD + CONTROL_FRAME_HASH_SIZE)){
|
||||
uint32_t peeked_hash = ((uint32_t)data[8] ) |
|
||||
((uint32_t)data[9] << 8) |
|
||||
((uint32_t)data[10] << 16) |
|
||||
@@ -323,20 +333,36 @@ esp_err_t DataLinkManager::receive_rmt(uint8_t channel){
|
||||
|
||||
// RIP control frames are handled internally – replay them too
|
||||
if (static_cast<FrameType>(GET_TYPE(cached_header.type_flag)) == FrameType::RIP_TABLE_CONTROL){
|
||||
// Re-run the RIP update using the cached message
|
||||
for (size_t i = 0; i < cached_message.size() - 1; i += 2){
|
||||
uint8_t board_id = cached_message[i];
|
||||
uint8_t hops = cached_message[i + 1];
|
||||
RIPRow* entry = nullptr;
|
||||
// Re-run the RIP update using the cached message (3-byte stride: board_id, hops, sum_of_hops)
|
||||
for (size_t i = 0; i + 2 < cached_message.size(); i += RIP_WIRE_STRIDE){
|
||||
uint8_t board_id = cached_message[i];
|
||||
uint8_t hops = cached_message[i + 1];
|
||||
uint8_t sum_of_hops = cached_message[i + 2];
|
||||
|
||||
// Skip routes about ourselves
|
||||
if (board_id == this_board_id){
|
||||
continue;
|
||||
}
|
||||
|
||||
RIPRow* entry = nullptr;
|
||||
if (rip_find_entry(board_id, &entry, true) != ESP_OK || entry == nullptr){
|
||||
continue;
|
||||
}
|
||||
if (entry->valid == RIP_NEW_ROW){
|
||||
rip_add_entry(board_id, hops + 1, channel, &entry);
|
||||
rip_add_entry(board_id, hops + 1, sum_of_hops, channel, &entry);
|
||||
} else {
|
||||
rip_update_entry(hops + 1, channel, &entry);
|
||||
rip_update_entry(hops + 1, sum_of_hops, channel, &entry);
|
||||
}
|
||||
}
|
||||
|
||||
// If this was a discovery request, send our routing table back
|
||||
if (GET_FLAG(cached_header.type_flag) == FLAG_DISCOVERY){
|
||||
esp_err_t disc_res = send_rip_frame(false, cached_header.sender_id);
|
||||
if (disc_res != ESP_OK){
|
||||
ESP_LOGE(DEBUG_LINK_TAG, "Failed to send back rip table to board %d (LUT path)", cached_header.sender_id);
|
||||
}
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
@@ -407,9 +433,8 @@ esp_err_t DataLinkManager::receive_rmt(uint8_t channel){
|
||||
return res;
|
||||
}
|
||||
|
||||
// Control frame fully validated – store in LUT for future replays
|
||||
// Extract the hash that was embedded at data[8..11] (already validated in get_data_from_frame)
|
||||
{
|
||||
// Control frame fully validated – store in LUT for future replays (skip RIP frames: their payload is dynamic)
|
||||
if (!is_rip){
|
||||
uint32_t validated_hash = ((uint32_t)data[8] ) |
|
||||
((uint32_t)data[9] << 8) |
|
||||
((uint32_t)data[10] << 16) |
|
||||
@@ -424,10 +449,17 @@ esp_err_t DataLinkManager::receive_rmt(uint8_t channel){
|
||||
if (static_cast<FrameType>(GET_TYPE(header.type_flag)) == FrameType::RIP_TABLE_CONTROL){
|
||||
ESP_LOGI(DEBUG_LINK_TAG, "Got a RIP frame from channel %d", channel);
|
||||
|
||||
for (size_t i = 0; i < message_size-1; i+=2){
|
||||
uint8_t board_id = message->data()[i];
|
||||
uint8_t hops = message->data()[i+1];
|
||||
ESP_LOGI(DEBUG_LINK_TAG, "Received: board_id %d and number of hops %d on channel %d", board_id, hops, channel);
|
||||
for (size_t i = 0; i + 2 < message_size; i += RIP_WIRE_STRIDE){
|
||||
uint8_t board_id = message->data()[i];
|
||||
uint8_t hops = message->data()[i + 1];
|
||||
uint8_t sum_of_hops = message->data()[i + 2];
|
||||
|
||||
// Skip routes about ourselves - we already know we're 0 hops away from ourselves
|
||||
if (board_id == this_board_id){
|
||||
continue;
|
||||
}
|
||||
|
||||
ESP_LOGI(DEBUG_LINK_TAG, "Received: board_id %d hops %d sum_of_hops %d on channel %d", board_id, hops, sum_of_hops, channel);
|
||||
|
||||
RIPRow* entry = nullptr;
|
||||
|
||||
@@ -443,15 +475,14 @@ esp_err_t DataLinkManager::receive_rmt(uint8_t channel){
|
||||
|
||||
if (entry->valid == RIP_NEW_ROW){
|
||||
//adding a new entry
|
||||
rip_add_entry(board_id, hops + 1, channel, &entry);
|
||||
rip_add_entry(board_id, hops + 1, sum_of_hops, channel, &entry);
|
||||
} else {
|
||||
//updating an entry
|
||||
rip_update_entry(hops + 1, channel, &entry);
|
||||
rip_update_entry(hops + 1, sum_of_hops, channel, &entry);
|
||||
}
|
||||
|
||||
if (GET_FLAG(header.type_flag) == FLAG_DISCOVERY){
|
||||
//discovery -> send routing table
|
||||
// ESP_LOGI(DEBUG_LINK_TAG, "got discovery reply");
|
||||
RIPRow_public row_queue = {
|
||||
.info = entry->info,
|
||||
.channel = entry->channel
|
||||
@@ -461,7 +492,19 @@ esp_err_t DataLinkManager::receive_rmt(uint8_t channel){
|
||||
}
|
||||
|
||||
}
|
||||
if (message_size == RIP_DISCOVERY_MESSAGE_SIZE){
|
||||
|
||||
// Print the finalized RIP table after processing all received rows
|
||||
ESP_LOGI(DEBUG_LINK_TAG, "--- RIP Table (after update from channel %d) ---", channel);
|
||||
for (size_t i = 0; i < RIP_MAX_ROUTES; i++){
|
||||
RIPRow* row = nullptr;
|
||||
if (rip_get_row(&row, i) == ESP_OK && row != nullptr){
|
||||
ESP_LOGI(DEBUG_LINK_TAG, " [%d] board_id=%d hops=%d sum_of_hops=%d channel=%d ttl=%d",
|
||||
i, row->info.board_id, row->info.hops, row->info.sum_of_hops, row->channel, row->ttl);
|
||||
}
|
||||
}
|
||||
ESP_LOGI(DEBUG_LINK_TAG, "---------------------------------------------------");
|
||||
|
||||
if (GET_FLAG(header.type_flag) == FLAG_DISCOVERY){
|
||||
res = send_rip_frame(false, header.sender_id);
|
||||
if (res != ESP_OK){
|
||||
ESP_LOGE(DEBUG_LINK_TAG, "Failed to send back rip table to board %d", header.sender_id);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "BlockingQueue.h"
|
||||
#include "Frames.h"
|
||||
#include "RMTManager.h"
|
||||
#include "SoftUARTManager.h"
|
||||
#include "esp_log.h"
|
||||
#include "nvs_flash.h"
|
||||
#include <memory>
|
||||
@@ -13,9 +14,15 @@
|
||||
*
|
||||
* @param board_id Board ID of the current board. Will be written to the NVM under key "board" if not already written.
|
||||
*/
|
||||
DataLinkManager::DataLinkManager(uint8_t board_id, uint8_t num_channels = MAX_CHANNELS){
|
||||
DataLinkManager::DataLinkManager(uint8_t board_id, uint8_t num_channels, PhysicalLayerType phy_type){
|
||||
//init table for this board and set up link layer priority queue
|
||||
phys_comms = std::make_unique<RMTManager>(num_channels);
|
||||
if (phy_type == PhysicalLayerType::SOFT_UART){
|
||||
phys_comms = std::make_unique<SoftUARTManager>(num_channels);
|
||||
ESP_LOGI(DEBUG_LINK_TAG, "Using SoftUART physical layer");
|
||||
} else {
|
||||
phys_comms = std::make_unique<RMTManager>(num_channels);
|
||||
ESP_LOGI(DEBUG_LINK_TAG, "Using RMT physical layer");
|
||||
}
|
||||
if (phys_comms == nullptr){
|
||||
ESP_LOGE(DEBUG_LINK_TAG, "RMT object was not created. Link layer communications will not function.");
|
||||
return;
|
||||
|
||||
@@ -27,6 +27,7 @@ void DataLinkManager::init_rip(){
|
||||
rip_table[0].info = {
|
||||
.board_id = this_board_id,
|
||||
.hops = 0,
|
||||
.sum_of_hops = 0,
|
||||
};
|
||||
rip_table[0].channel = MAX_CHANNELS + 1;
|
||||
rip_table[0].ttl = RIP_TTL_START;
|
||||
@@ -37,7 +38,7 @@ void DataLinkManager::init_rip(){
|
||||
start_rip_tasks();
|
||||
}
|
||||
|
||||
esp_err_t DataLinkManager::rip_add_entry(uint8_t board_id, uint8_t hops, uint8_t channel, RIPRow** entry){
|
||||
esp_err_t DataLinkManager::rip_add_entry(uint8_t board_id, uint8_t hops, uint8_t sum_of_hops, uint8_t channel, RIPRow** entry){
|
||||
if (entry == nullptr){
|
||||
return ESP_FAIL;
|
||||
}
|
||||
@@ -49,16 +50,16 @@ esp_err_t DataLinkManager::rip_add_entry(uint8_t board_id, uint8_t hops, uint8_t
|
||||
(*entry)->channel = channel;
|
||||
(*entry)->info = {
|
||||
.board_id = board_id,
|
||||
.hops = hops
|
||||
.hops = hops,
|
||||
.sum_of_hops = sum_of_hops,
|
||||
};
|
||||
(*entry)->ttl = RIP_TTL_START;
|
||||
(*entry)->valid = 1;
|
||||
|
||||
|
||||
// ESP_LOGI(DEBUG_LINK_TAG, "board_id %d now has hops %d from channel %d", (*entry)->info.board_id, (*entry)->info.hops, channel);
|
||||
|
||||
xSemaphoreGive((*entry)->row_sem);
|
||||
|
||||
rip_update_self_sum_of_hops();
|
||||
|
||||
if (uxQueueMessagesWaiting(manual_broadcasts) == 0){
|
||||
bool dummy = true;
|
||||
xQueueSend(manual_broadcasts, &dummy, 0); //new row - send broadcast
|
||||
@@ -67,7 +68,32 @@ esp_err_t DataLinkManager::rip_add_entry(uint8_t board_id, uint8_t hops, uint8_t
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Recomputes and stores this board's own sum_of_hops (centrality score).
|
||||
* Called whenever the routing table changes. No semaphores are held on entry.
|
||||
*/
|
||||
void DataLinkManager::rip_update_self_sum_of_hops(){
|
||||
uint32_t total = 0;
|
||||
for (size_t i = 1; i < RIP_MAX_ROUTES; i++){
|
||||
if (xSemaphoreTake(rip_table[i].row_sem, pdMS_TO_TICKS(RIP_MAX_SEM_WAIT_MS)) != pdTRUE){
|
||||
continue;
|
||||
}
|
||||
if (rip_table[i].valid == RIP_VALID_ROW && rip_table[i].info.hops <= RIP_MAX_HOPS){
|
||||
total += rip_table[i].info.hops;
|
||||
}
|
||||
xSemaphoreGive(rip_table[i].row_sem);
|
||||
}
|
||||
|
||||
// Clamp to uint8_t max — with RIP_MAX_ROUTES=10 and RIP_MAX_HOPS=15 the max is 9*15=135, fits fine.
|
||||
if (xSemaphoreTake(rip_table[0].row_sem, pdMS_TO_TICKS(RIP_MAX_SEM_WAIT_MS)) == pdTRUE){
|
||||
rip_table[0].info.sum_of_hops = (total > 255) ? 255 : static_cast<uint8_t>(total);
|
||||
xSemaphoreGive(rip_table[0].row_sem);
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t DataLinkManager::rip_reset_entry_ttl(uint8_t board_id){
|
||||
// ...existing code...
|
||||
|
||||
RIPRow* entry = nullptr;
|
||||
|
||||
esp_err_t res;
|
||||
@@ -92,7 +118,7 @@ esp_err_t DataLinkManager::rip_reset_entry_ttl(uint8_t board_id){
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t DataLinkManager::rip_update_entry(uint8_t new_hop, uint8_t channel, RIPRow** entry){
|
||||
esp_err_t DataLinkManager::rip_update_entry(uint8_t new_hop, uint8_t new_sum_of_hops, uint8_t channel, RIPRow** entry){
|
||||
if (entry == nullptr){
|
||||
return ESP_FAIL; //board doesn't exist
|
||||
}
|
||||
@@ -102,22 +128,33 @@ esp_err_t DataLinkManager::rip_update_entry(uint8_t new_hop, uint8_t channel, RI
|
||||
}
|
||||
|
||||
uint8_t old_hops = (*entry)->info.hops;
|
||||
uint8_t old_sum_of_hops = (*entry)->info.sum_of_hops;
|
||||
bool hops_improved = (new_hop < (*entry)->info.hops) || ((*entry)->info.hops == RIP_MAX_HOPS + 1);
|
||||
|
||||
if ((*entry)->info.hops >= new_hop && (*entry)->info.hops != RIP_MAX_HOPS + 1){ //no count to infinity if path is invalid
|
||||
if (hops_improved){ //accept better paths and allow recovery from infinity
|
||||
(*entry)->info.hops = new_hop;
|
||||
(*entry)->info.sum_of_hops = new_sum_of_hops;
|
||||
(*entry)->channel = channel;
|
||||
// ESP_LOGI(DEBUG_LINK_TAG, "updated board_id %d now has hops %d from channel %d", (*entry)->info.board_id, (*entry)->info.hops, channel);
|
||||
} else {
|
||||
// Even if hops didn't improve, update the centrality score if it came from the same channel
|
||||
if ((*entry)->channel == channel){
|
||||
(*entry)->info.sum_of_hops = new_sum_of_hops;
|
||||
}
|
||||
}
|
||||
|
||||
(*entry)->ttl = RIP_TTL_START;
|
||||
(*entry)->valid = 1;
|
||||
|
||||
// ESP_LOGI(DEBUG_LINK_TAG, "refreshed board_id %d ttl", (*entry)->info.board_id);
|
||||
bool sum_of_hops_changed = (old_sum_of_hops != (*entry)->info.sum_of_hops);
|
||||
|
||||
xSemaphoreGive((*entry)->row_sem);
|
||||
|
||||
if (uxQueueMessagesWaiting(manual_broadcasts) == 0 && old_hops > new_hop && old_hops != RIP_MAX_HOPS + 1){
|
||||
//if hops were changed, send broadcast (if there isn't already one manual broadcast request pending)
|
||||
if (hops_improved || sum_of_hops_changed){
|
||||
rip_update_self_sum_of_hops();
|
||||
}
|
||||
|
||||
if (uxQueueMessagesWaiting(manual_broadcasts) == 0 && (old_hops > new_hop || old_hops == RIP_MAX_HOPS + 1 || sum_of_hops_changed)){
|
||||
//if hops were changed (or recovered from infinity), or sum_of_hops changed, send broadcast
|
||||
bool dummy = true;
|
||||
xQueueSend(manual_broadcasts, &dummy, 0);
|
||||
}
|
||||
@@ -139,7 +176,7 @@ esp_err_t DataLinkManager::rip_find_entry(uint8_t board_id, RIPRow** entry, bool
|
||||
if (xSemaphoreTake(rip_table[i].row_sem, pdMS_TO_TICKS(RIP_MAX_SEM_WAIT_MS)) != pdTRUE){
|
||||
return ESP_FAIL;
|
||||
}
|
||||
if (rip_table[i].valid == RIP_VALID_ROW && rip_table[i].info.board_id == board_id){
|
||||
if ((rip_table[i].valid == RIP_VALID_ROW || rip_table[i].valid == RIP_NEW_ROW) && rip_table[i].info.board_id == board_id){
|
||||
*entry = &rip_table[i];
|
||||
xSemaphoreGive(rip_table[i].row_sem);
|
||||
// ESP_LOGI(DEBUG_LINK_TAG, "Found %d in table at row %d", board_id, i);
|
||||
@@ -198,14 +235,11 @@ esp_err_t DataLinkManager::rip_get_row(RIPRow** entry, uint8_t row_num){
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
if (rip_table[row_num].info.hops == RIP_MAX_HOPS + 1){
|
||||
//invalid hop, decrement counter
|
||||
rip_table[row_num].ttl_flush--;
|
||||
if (rip_table[row_num].ttl_flush == 0){
|
||||
rip_table[row_num].valid = RIP_INVALID_ROW;
|
||||
xSemaphoreGive(rip_table[row_num].row_sem);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
if (rip_table[row_num].info.hops == RIP_MAX_HOPS + 1 && rip_table[row_num].ttl_flush == 0){
|
||||
//flush countdown has expired, invalidate the row
|
||||
rip_table[row_num].valid = RIP_INVALID_ROW;
|
||||
xSemaphoreGive(rip_table[row_num].row_sem);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
static RIPRow row_snapshots[RIP_MAX_ROUTES];
|
||||
@@ -228,8 +262,7 @@ esp_err_t DataLinkManager::rip_get_row(RIPRow** entry, uint8_t row_num){
|
||||
* @return esp_err_t
|
||||
*/
|
||||
esp_err_t DataLinkManager::send_rip_frame(bool broadcast, uint8_t dest_id){
|
||||
//use the control frame for the demo (as the number of rows increase, we will need to use the generic frame)
|
||||
//data will be [board_id (1), hops (1), board_id (2), hops (2), ...]
|
||||
// Wire format: [board_id (1), hops (1), sum_of_hops (1), ...] (RIP_WIRE_STRIDE bytes per entry)
|
||||
|
||||
uint16_t message_idx = 0;
|
||||
esp_err_t res;
|
||||
@@ -240,7 +273,7 @@ esp_err_t DataLinkManager::send_rip_frame(bool broadcast, uint8_t dest_id){
|
||||
if(broadcast){
|
||||
for (size_t channel = 0; channel < num_channels; channel++){
|
||||
auto rip_message = std::make_unique<std::vector<uint8_t>>();
|
||||
rip_message->resize(RIP_MAX_ROUTES * 2);
|
||||
rip_message->resize(RIP_MAX_ROUTES * RIP_WIRE_STRIDE);
|
||||
|
||||
for (size_t i = 0; i < RIP_MAX_ROUTES; i++){
|
||||
res = rip_get_row(&entry, i);
|
||||
@@ -253,15 +286,15 @@ esp_err_t DataLinkManager::send_rip_frame(bool broadcast, uint8_t dest_id){
|
||||
continue;
|
||||
}
|
||||
|
||||
// ESP_LOGI(DEBUG_LINK_TAG, "Found entry for board %d with hops %d", entry->info.board_id, entry->info.hops);
|
||||
|
||||
if (entry->channel == channel){
|
||||
//poisoned reverse
|
||||
rip_message->at(message_idx++) = entry->info.board_id;
|
||||
rip_message->at(message_idx++) = RIP_MAX_HOPS + 1;
|
||||
rip_message->at(message_idx++) = entry->info.sum_of_hops;
|
||||
} else {
|
||||
rip_message->at(message_idx++) = entry->info.board_id;
|
||||
rip_message->at(message_idx++) = entry->info.hops;
|
||||
rip_message->at(message_idx++) = entry->info.sum_of_hops;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -299,7 +332,7 @@ esp_err_t DataLinkManager::send_rip_frame(bool broadcast, uint8_t dest_id){
|
||||
}
|
||||
} else {
|
||||
auto rip_message = std::make_unique<std::vector<uint8_t>>();
|
||||
rip_message->resize(RIP_MAX_ROUTES * 2);
|
||||
rip_message->resize(RIP_MAX_ROUTES * RIP_WIRE_STRIDE);
|
||||
|
||||
for (size_t i = 0; i < RIP_MAX_ROUTES; i++){
|
||||
res = rip_get_row(&entry, i);
|
||||
@@ -313,6 +346,7 @@ esp_err_t DataLinkManager::send_rip_frame(bool broadcast, uint8_t dest_id){
|
||||
}
|
||||
rip_message->data()[message_idx++] = entry->info.board_id;
|
||||
rip_message->data()[message_idx++] = entry->info.hops;
|
||||
rip_message->data()[message_idx++] = entry->info.sum_of_hops;
|
||||
}
|
||||
ESP_LOGI(DEBUG_LINK_TAG, "replying to discovery request to board %d", dest_id);
|
||||
res = send(dest_id, std::move(rip_message), FrameType::RIP_TABLE_CONTROL, FLAG_DISCOVERY);
|
||||
@@ -382,7 +416,7 @@ esp_err_t DataLinkManager::get_routing_table(RIPRow_public* table, size_t* table
|
||||
return ESP_FAIL;
|
||||
}
|
||||
if (rip_table[i].valid == RIP_VALID_ROW){
|
||||
table[i].info = rip_table[i].info;
|
||||
table[i].info = rip_table[i].info; // copies board_id, hops, and sum_of_hops
|
||||
table[i].channel = rip_table[i].channel;
|
||||
curr_size++;
|
||||
}
|
||||
@@ -439,15 +473,20 @@ esp_err_t DataLinkManager::get_routing_table(RIPRow_public* table, size_t* table
|
||||
continue;
|
||||
}
|
||||
if (link_layer_obj->rip_table[i].ttl != 0){
|
||||
// link_layer_obj->rip_table[i].valid = RIP_INVALID_ROW;
|
||||
// ESP_LOGI(DEBUG_LINK_TAG, "Entry %d now has ttl %d", i, link_layer_obj->rip_table[i].ttl);
|
||||
// } else {
|
||||
link_layer_obj->rip_table[i].ttl--;
|
||||
if (link_layer_obj->rip_table[i].ttl == 0){
|
||||
link_layer_obj->rip_table[i].info.hops = RIP_MAX_HOPS + 1;
|
||||
link_layer_obj->rip_table[i].ttl_flush = RIP_FLUSH_COUNT;
|
||||
broadcast = true;
|
||||
}
|
||||
} else if (link_layer_obj->rip_table[i].info.hops == RIP_MAX_HOPS + 1){
|
||||
// TTL already zero and route is poisoned — count down the flush timer
|
||||
if (link_layer_obj->rip_table[i].ttl_flush > 0){
|
||||
link_layer_obj->rip_table[i].ttl_flush--;
|
||||
}
|
||||
if (link_layer_obj->rip_table[i].ttl_flush == 0){
|
||||
link_layer_obj->rip_table[i].valid = RIP_INVALID_ROW;
|
||||
}
|
||||
}
|
||||
|
||||
xSemaphoreGive(link_layer_obj->rip_table[i].row_sem);
|
||||
|
||||
@@ -275,15 +275,11 @@ esp_err_t DataLinkManager::scheduler_send(uint8_t channel){
|
||||
esp_err_t DataLinkManager::scheduler_send_rmt(uint8_t channel, SchedulerMetadata frame, uint8_t* send_data, size_t frame_size, bool wait_for_tx_done){
|
||||
esp_err_t res;
|
||||
uint8_t channel_to_route = MAX_CHANNELS;
|
||||
rmt_transmit_config_t config = {
|
||||
.loop_count = 0,
|
||||
.flags = {
|
||||
.eot_level = 0, // typically 0 or 1, depending on your output idle level
|
||||
}
|
||||
};
|
||||
|
||||
// config is physical-layer specific; each IPhysicalLayer implementation
|
||||
// uses its own internal configuration set at init time, so nullptr is fine.
|
||||
if (frame.header.receiver_id == BROADCAST_ADDR){
|
||||
// printf("Sending on channel %d\n", i);
|
||||
res = phys_comms->send(send_data, frame_size, &config, channel);
|
||||
res = phys_comms->send(send_data, frame_size, nullptr, channel);
|
||||
} else {
|
||||
res = route_frame(frame.header.receiver_id, &channel_to_route);
|
||||
|
||||
@@ -291,18 +287,12 @@ esp_err_t DataLinkManager::scheduler_send_rmt(uint8_t channel, SchedulerMetadata
|
||||
ESP_LOGE(DEBUG_LINK_TAG, "Failed to find entry for %d", frame.header.receiver_id);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
// ESP_LOGI(DEBUG_LINK_TAG, "Sending frame %d on channel %d to board %d frag_info 0x%X", frame.header.seq_num, channel, frame.header.receiver_id, frame.header.frag_info);
|
||||
res = phys_comms->send(send_data, frame_size, &config, channel_to_route);
|
||||
// if (wait_for_tx_done){
|
||||
// phys_comms->wait_until_send_complete(channel_to_route);
|
||||
// }
|
||||
res = phys_comms->send(send_data, frame_size, nullptr, channel_to_route);
|
||||
}
|
||||
|
||||
if (res != ESP_OK){
|
||||
ESP_LOGE(DEBUG_LINK_TAG, "Failed to send message");
|
||||
return ESP_FAIL;
|
||||
} else{
|
||||
// ESP_LOGI(DEBUG_LINK_TAG, "Sent frame %d frag_info 0x%X", frame.header.seq_num, frame.header.frag_info);
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
|
||||
@@ -10,12 +10,20 @@
|
||||
#include "freertos/semphr.h"
|
||||
#include "Frames.h"
|
||||
#include "Tables.h"
|
||||
#include "RMTManager.h"
|
||||
#include "IPhysicalLayer.h"
|
||||
#include "BlockingQueue.h"
|
||||
#include "BlockingPriorityQueue.h"
|
||||
#include <unordered_map>
|
||||
#include "Scheduler.h"
|
||||
|
||||
/**
|
||||
* @brief Selects which physical layer implementation to instantiate.
|
||||
*/
|
||||
enum class PhysicalLayerType : uint8_t {
|
||||
RMT = 0, ///< Use the RMT-based Manchester-encoded physical layer (default)
|
||||
SOFT_UART = 1, ///< Use the software-UART physical layer
|
||||
};
|
||||
|
||||
#define DEBUG_LINK_TAG "LinkLayer"
|
||||
|
||||
#define CRC_POLYNOMIAL 0x1021
|
||||
@@ -56,7 +64,7 @@ static const uint16_t crc16_table[256] = {
|
||||
*/
|
||||
class DataLinkManager{
|
||||
public:
|
||||
DataLinkManager(uint8_t board_id, uint8_t num_channels);
|
||||
DataLinkManager(uint8_t board_id, uint8_t num_channels, PhysicalLayerType phy_type = PhysicalLayerType::SOFT_UART);
|
||||
~DataLinkManager();
|
||||
esp_err_t send(uint8_t dest_board, std::unique_ptr<std::vector<uint8_t>>&& buffer, FrameType type, uint8_t flag);
|
||||
esp_err_t start_receive_frames(uint8_t curr_channel);
|
||||
@@ -69,7 +77,7 @@ class DataLinkManager{
|
||||
private:
|
||||
uint8_t this_board_id = 0;
|
||||
uint8_t num_channels = MAX_CHANNELS;
|
||||
std::unique_ptr<RMTManager> phys_comms;
|
||||
std::unique_ptr<IPhysicalLayer> phys_comms;
|
||||
|
||||
std::unordered_map<uint8_t, uint16_t> sequence_num_map;
|
||||
SemaphoreHandle_t sequence_num_map_mutex;
|
||||
@@ -93,10 +101,11 @@ class DataLinkManager{
|
||||
|
||||
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);
|
||||
esp_err_t rip_add_entry(uint8_t board_id, uint8_t hops, uint8_t channel, RIPRow** entry);
|
||||
esp_err_t rip_update_entry(uint8_t new_hop, uint8_t new_sum_of_hops, uint8_t channel, RIPRow** entry);
|
||||
esp_err_t rip_add_entry(uint8_t board_id, uint8_t hops, uint8_t sum_of_hops, uint8_t channel, RIPRow** entry);
|
||||
esp_err_t rip_reset_entry_ttl(uint8_t board_id);
|
||||
esp_err_t rip_get_row(RIPRow** entry, uint8_t row_num);
|
||||
void rip_update_self_sum_of_hops();
|
||||
|
||||
//this is stored locally with metadata `ttl`
|
||||
// std::unordered_map<uint8_t, RIPRow> rip_table; //using a hash map to store the routes to other boards - will be used as we scale up
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#define RIP_INVALID_ROW 0
|
||||
#define RIP_VALID_ROW 1
|
||||
#define RIP_NEW_ROW 2
|
||||
#define RIP_BROADCAST_INTERVAL 30000 //broadcast every 30 seconds (30000ms)
|
||||
#define RIP_BROADCAST_INTERVAL 15000 //broadcast every 30 seconds (15000ms)
|
||||
// #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
|
||||
@@ -15,13 +15,15 @@
|
||||
#define RIP_FLUSH_COUNT 8 //flush after 8*30 seconds = 240 seconds
|
||||
|
||||
#define RIP_DISCOVERY_MESSAGE_SIZE 1
|
||||
#define RIP_WIRE_STRIDE 3 // bytes per entry on the wire: board_id, hops, sum_of_hops
|
||||
/**
|
||||
* @brief Routing data to a board
|
||||
* This struct will be sent to other boards
|
||||
*/
|
||||
typedef struct _rip_hops{
|
||||
uint8_t board_id; //ID of the destination
|
||||
uint8_t hops; //hop count to `board_id`
|
||||
uint8_t board_id; //ID of the destination
|
||||
uint8_t hops; //hop count to `board_id`
|
||||
uint8_t sum_of_hops; //sum of hops from `board_id` to all other known boards (centrality score)
|
||||
} RIPHop;
|
||||
|
||||
typedef struct _rip_row{
|
||||
@@ -36,7 +38,7 @@ typedef struct _rip_row{
|
||||
|
||||
/**
|
||||
* @brief Public facing RIP table row
|
||||
*
|
||||
*
|
||||
*/
|
||||
typedef struct _rip_public_row{
|
||||
RIPHop info;
|
||||
|
||||
@@ -353,7 +353,8 @@ void RMTManager::reset_encoder_context(rmt_encoder_context_t* ctx){
|
||||
* @param config
|
||||
* @return int
|
||||
*/
|
||||
esp_err_t RMTManager::send(uint8_t* data, size_t size, rmt_transmit_config_t* config, uint8_t channel_num){
|
||||
esp_err_t RMTManager::send(uint8_t* data, size_t size, void* config_ptr, uint8_t channel_num){
|
||||
rmt_transmit_config_t* config = static_cast<rmt_transmit_config_t*>(config_ptr);
|
||||
if (channel_num >= num_channels){
|
||||
ESP_LOGE(DEBUG_TAG, "send() error: invalid channel number");
|
||||
return ESP_FAIL;
|
||||
|
||||
22
components/rmt/include/IPhysicalLayer.h
Normal file
22
components/rmt/include/IPhysicalLayer.h
Normal file
@@ -0,0 +1,22 @@
|
||||
#ifndef IPHYSICAL_LAYER_H
|
||||
#define IPHYSICAL_LAYER_H
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include "esp_err.h"
|
||||
|
||||
#ifndef MAX_CHANNELS
|
||||
#define MAX_CHANNELS 4
|
||||
#endif
|
||||
|
||||
class IPhysicalLayer {
|
||||
public:
|
||||
virtual ~IPhysicalLayer() = default;
|
||||
|
||||
virtual esp_err_t send(uint8_t* data, size_t size, void* config, uint8_t channel_num) = 0;
|
||||
virtual esp_err_t receive(uint8_t* recv_buf, size_t size, size_t* output_size, uint8_t channel_num) = 0;
|
||||
virtual esp_err_t start_receiving(uint8_t channel_num) = 0;
|
||||
virtual esp_err_t wait_until_send_complete(uint8_t channel_num) = 0;
|
||||
};
|
||||
|
||||
#endif // IPHYSICAL_LAYER_H
|
||||
@@ -9,8 +9,11 @@
|
||||
#include "driver/rmt_tx.h"
|
||||
#include "driver/rmt_rx.h"
|
||||
#include "soc/gpio_num.h"
|
||||
#include "IPhysicalLayer.h"
|
||||
|
||||
#ifndef MAX_CHANNELS
|
||||
#define MAX_CHANNELS 4
|
||||
#endif
|
||||
#define RMT_SYMBOL_BLOCK_SIZE 48
|
||||
|
||||
#define RECEIVE_BUFFER_SIZE 1024 //this is some value (we should probably set it to some packet size that we predetermine in some custom protocol:tm:)
|
||||
@@ -74,12 +77,12 @@ typedef struct _rmt_channel{
|
||||
* @author Justin Chow
|
||||
*
|
||||
*/
|
||||
class RMTManager{
|
||||
class RMTManager : public IPhysicalLayer {
|
||||
public:
|
||||
RMTManager(uint8_t num_channels);
|
||||
~RMTManager();
|
||||
esp_err_t send(uint8_t* data, size_t size, rmt_transmit_config_t* config, uint8_t channel_num); //temp function to send some string data
|
||||
esp_err_t receive(uint8_t* recv_buf, size_t size, size_t* output_size, uint8_t channel_num);
|
||||
~RMTManager() override;
|
||||
esp_err_t send(uint8_t* data, size_t size, void* config, uint8_t channel_num) override;
|
||||
esp_err_t receive(uint8_t* recv_buf, size_t size, size_t* output_size, uint8_t channel_num) override;
|
||||
|
||||
static size_t encoder_callback(const void* data, size_t data_size, size_t symbols_written,
|
||||
size_t symbols_free, rmt_symbol_word_t* symbols, bool* done, void* arg);
|
||||
@@ -87,9 +90,9 @@ class RMTManager{
|
||||
static bool rmt_rx_done_callback(rmt_channel_handle_t channel, const rmt_rx_done_event_data_t *edata, void *user_data);
|
||||
static bool rmt_tx_done_callback(rmt_channel_handle_t channel, const rmt_tx_done_event_data_t *edata, void *user_data);
|
||||
|
||||
esp_err_t start_receiving(uint8_t channel_num);
|
||||
esp_err_t start_receiving(uint8_t channel_num) override;
|
||||
|
||||
esp_err_t wait_until_send_complete(uint8_t channel_num);
|
||||
esp_err_t wait_until_send_complete(uint8_t channel_num) override;
|
||||
|
||||
private:
|
||||
uint8_t num_channels; //number of channels initalized
|
||||
@@ -107,21 +110,21 @@ class RMTManager{
|
||||
|
||||
// rmt_channel_handle_t tx_chan;
|
||||
|
||||
const gpio_num_t tx_gpio[MAX_CHANNELS] = {GPIO_NUM_4, GPIO_NUM_5, GPIO_NUM_11, GPIO_NUM_13}; //using pins 4,5,11,13 for channels 0,1,2,3 respectively for tx
|
||||
const gpio_num_t tx_gpio[MAX_CHANNELS] = {GPIO_NUM_4, GPIO_NUM_5, GPIO_NUM_11, GPIO_NUM_13};
|
||||
|
||||
QueueHandle_t memory_to_free;
|
||||
|
||||
//=====================RX=====================
|
||||
rmt_channel_handle_t rx_chan;
|
||||
|
||||
const gpio_num_t rx_gpio[MAX_CHANNELS] = {GPIO_NUM_3, GPIO_NUM_6, GPIO_NUM_12, GPIO_NUM_14}; //using pins 3,6,12,14 for channels 0,1,2,3 respectively for rx
|
||||
const gpio_num_t rx_gpio[MAX_CHANNELS] = {GPIO_NUM_3, GPIO_NUM_6, GPIO_NUM_12, GPIO_NUM_14};
|
||||
|
||||
//rx_receive_config
|
||||
rmt_receive_config_t receive_config = {
|
||||
.signal_range_min_ns = 200,
|
||||
.signal_range_max_ns = 200 * 1000,
|
||||
.flags = {
|
||||
.en_partial_rx = true
|
||||
.en_partial_rx = false // frames are max 121B = ~968 symbols, well within the 1024-symbol buffer
|
||||
}
|
||||
};
|
||||
|
||||
@@ -135,22 +138,21 @@ typedef struct _gpio_channel_pair {
|
||||
|
||||
static const GPIO_Channel_Pair gpio_channel_pairs[MAX_CHANNELS] = {
|
||||
{
|
||||
.tx_pin = GPIO_NUM_1,
|
||||
.tx_pin = GPIO_NUM_4,
|
||||
.rx_pin = GPIO_NUM_3
|
||||
},
|
||||
{
|
||||
.tx_pin = GPIO_NUM_5,
|
||||
.rx_pin = GPIO_NUM_6
|
||||
},
|
||||
{
|
||||
.tx_pin = GPIO_NUM_11,
|
||||
.rx_pin = GPIO_NUM_12
|
||||
},
|
||||
{
|
||||
.tx_pin = GPIO_NUM_2,
|
||||
.rx_pin = GPIO_NUM_13
|
||||
},
|
||||
{
|
||||
.tx_pin = GPIO_NUM_3,
|
||||
.rx_pin = GPIO_NUM_14
|
||||
},
|
||||
{
|
||||
.tx_pin = GPIO_NUM_4,
|
||||
.rx_pin = GPIO_NUM_15
|
||||
.rx_pin = GPIO_NUM_14
|
||||
}
|
||||
}; //todo: use these pairs directly instead of the two arrays in the class definition above
|
||||
// but ensure to update them first!!!
|
||||
|
||||
#endif //RMT_COMMUNICATIONS
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#define TAG "CommunicationRouter"
|
||||
#define MAX_RX_BUFFER_SIZE 1024
|
||||
#define WIRELESS_DEQUEUE_TIMEOUT_MS 3000
|
||||
#define LEADER_ELECTION_PERIOD_MS 3000
|
||||
|
||||
CommunicationRouter::~CommunicationRouter() {
|
||||
vTaskDelete(m_router_thread);
|
||||
@@ -35,6 +36,16 @@ CommunicationRouter::~CommunicationRouter() {
|
||||
}
|
||||
}
|
||||
|
||||
[[noreturn]] void CommunicationRouter::leader_election_thread(void *args) {
|
||||
const auto that = static_cast<CommunicationRouter *>(args);
|
||||
|
||||
while (true) {
|
||||
that->m_last_leader_updated = std::chrono::system_clock::now();
|
||||
that->update_leader();
|
||||
vTaskDelay(pdMS_TO_TICKS(LEADER_ELECTION_PERIOD_MS));
|
||||
}
|
||||
}
|
||||
|
||||
[[noreturn]] void CommunicationRouter::link_layer_thread(void *args) {
|
||||
const auto that = static_cast<CommunicationRouter *>(args);
|
||||
|
||||
@@ -61,21 +72,45 @@ void CommunicationRouter::update_leader() {
|
||||
size_t table_size = RIP_MAX_ROUTES;
|
||||
this->m_data_link_manager->get_routing_table(table, &table_size);
|
||||
|
||||
// Leader election (just get the highest id in rip)
|
||||
// Compute our own sum_of_hops from our local routing table (row 0 in the
|
||||
// internal table carries this, but it is also returned in the public table
|
||||
// as the self-entry). We reconstruct it here directly from the peer rows.
|
||||
uint32_t self_sum = 0;
|
||||
std::vector<int> connected_module_ids;
|
||||
uint8_t max = m_module_id;
|
||||
for (int i = 0; i < table_size; i++) {
|
||||
const auto id = table[i].info.board_id;
|
||||
connected_module_ids.emplace_back(id);
|
||||
if (id > max) { // todo: change this to be correct
|
||||
max = id;
|
||||
for (size_t i = 0; i < table_size; i++) {
|
||||
if (table[i].info.board_id == m_module_id) continue; // skip self-row if present
|
||||
connected_module_ids.emplace_back(table[i].info.board_id);
|
||||
self_sum += table[i].info.hops;
|
||||
}
|
||||
const uint8_t self_sum_clamped = (self_sum > 255) ? 255 : static_cast<uint8_t>(self_sum);
|
||||
|
||||
// Leader election: elect the module with the lowest sum_of_hops (most central).
|
||||
// Each module advertises its own sum_of_hops as the third byte of every RIP row,
|
||||
// so every peer in the routing table carries the score that module computed
|
||||
// locally from its own perspective — the true minimum total hop distance.
|
||||
//
|
||||
// Ties are broken by higher board_id for determinism.
|
||||
uint8_t best_id = m_module_id;
|
||||
uint8_t best_score = self_sum_clamped;
|
||||
|
||||
for (size_t i = 0; i < table_size; i++) {
|
||||
const auto& row = table[i];
|
||||
if (row.info.board_id == m_module_id) continue;
|
||||
|
||||
const uint8_t score = row.info.sum_of_hops;
|
||||
if (score == 0) continue; // a remote board advertising sum_of_hops=0 is invalid — never trust it
|
||||
if (score < best_score || (score == best_score && row.info.board_id > best_id)) {
|
||||
best_score = score;
|
||||
best_id = row.info.board_id;
|
||||
}
|
||||
}
|
||||
|
||||
const uint8_t elected = best_id;
|
||||
|
||||
// Leader has changed, we may need to change PC connection state
|
||||
if (this->m_leader != max) {
|
||||
ESP_LOGI(TAG, "Leader has changed from %d to %d", this->m_leader, max);
|
||||
if (max == m_module_id) {
|
||||
if (this->m_leader != elected) {
|
||||
ESP_LOGI(TAG, "Leader has changed from %d to %d (sum_of_hops=%d)", this->m_leader, elected, best_score);
|
||||
if (elected == m_module_id) {
|
||||
m_pc_connection->connect();
|
||||
m_lossless_server->startup();
|
||||
m_lossy_server->startup();
|
||||
@@ -86,7 +121,7 @@ void CommunicationRouter::update_leader() {
|
||||
}
|
||||
}
|
||||
|
||||
this->m_leader = max;
|
||||
this->m_leader = elected;
|
||||
|
||||
if (this->m_leader == m_module_id) {
|
||||
this->m_discovery_service->set_connected_boards(connected_module_ids);
|
||||
|
||||
@@ -50,12 +50,15 @@ public:
|
||||
xTaskCreate(router_thread, "router", 4096, this, 3, &this->m_router_thread);
|
||||
xTaskCreate(link_layer_thread, "router_rmt", 4096, this, 3,
|
||||
&this->m_link_layer_thread);
|
||||
xTaskCreate(leader_election_thread, "router_election", 4096, this, 3,
|
||||
&this->m_leader_election_thread);
|
||||
}
|
||||
|
||||
~CommunicationRouter();
|
||||
|
||||
[[noreturn]] static void router_thread(void *args);
|
||||
[[noreturn]] static void link_layer_thread(void *args);
|
||||
[[noreturn]] static void leader_election_thread(void *args);
|
||||
int send_msg(uint8_t *buffer, size_t length) const;
|
||||
void update_leader();
|
||||
void route(std::unique_ptr<std::vector<uint8_t>>&& buffer) const;
|
||||
@@ -71,6 +74,7 @@ public:
|
||||
private:
|
||||
TaskHandle_t m_router_thread = nullptr;
|
||||
TaskHandle_t m_link_layer_thread = nullptr;
|
||||
TaskHandle_t m_leader_election_thread = nullptr;
|
||||
ConfigManager &m_config_manager;
|
||||
std::unique_ptr<IConnectionManager> m_pc_connection;
|
||||
std::unique_ptr<IRPCServer> m_lossless_server;
|
||||
|
||||
4
components/softUART/CMakeLists.txt
Normal file
4
components/softUART/CMakeLists.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
idf_component_register(SRCS "SoftUARTManager.cpp"
|
||||
PRIV_REQUIRES driver esp_driver_uart esp_driver_gpio
|
||||
REQUIRES rmt
|
||||
INCLUDE_DIRS "include")
|
||||
219
components/softUART/SoftUARTManager.cpp
Normal file
219
components/softUART/SoftUARTManager.cpp
Normal file
@@ -0,0 +1,219 @@
|
||||
#include "SoftUARTManager.h"
|
||||
#include "esp_log.h"
|
||||
#include "driver/uart.h"
|
||||
#include "driver/gpio.h"
|
||||
#include <cstring>
|
||||
|
||||
#define TAG "SoftUART"
|
||||
|
||||
SoftUARTManager::SoftUARTManager(uint8_t num_channels) {
|
||||
if (num_channels == 0 || num_channels > SUART_NUM_CHANNELS) {
|
||||
ESP_LOGE(TAG, "Invalid num_channels %d (must be 1-%d)", num_channels, SUART_NUM_CHANNELS);
|
||||
this->num_channels = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
this->num_channels = num_channels;
|
||||
|
||||
memset(hw, 0, sizeof(hw));
|
||||
memset(ch_status, SUART_CHANNEL_NOT_READY, sizeof(ch_status));
|
||||
|
||||
for (uint8_t i = 0; i < num_channels; i++) {
|
||||
if (init_hw_channel(i) == ESP_OK) {
|
||||
ch_status[i] = SUART_CHANNEL_READY;
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to init HW UART channel %d", i);
|
||||
}
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "SoftUARTManager ready: %d HW UART channel(s)", num_channels);
|
||||
}
|
||||
|
||||
SoftUARTManager::~SoftUARTManager() {
|
||||
for (uint8_t i = 0; i < num_channels; i++) deinit_hw_channel(i);
|
||||
}
|
||||
|
||||
esp_err_t SoftUARTManager::init_hw_channel(uint8_t idx) {
|
||||
uart_port_t port = HW_PORT[idx];
|
||||
hw[idx].port = port;
|
||||
hw[idx].tx_gpio = HW_TX[idx];
|
||||
hw[idx].rx_gpio = HW_RX[idx];
|
||||
|
||||
const uart_config_t cfg = {
|
||||
.baud_rate = SUART_BAUD_RATE,
|
||||
.data_bits = UART_DATA_8_BITS,
|
||||
.parity = UART_PARITY_DISABLE,
|
||||
.stop_bits = UART_STOP_BITS_1,
|
||||
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
|
||||
.rx_flow_ctrl_thresh = 0,
|
||||
.source_clk = UART_SCLK_DEFAULT,
|
||||
};
|
||||
|
||||
esp_err_t res = uart_param_config(port, &cfg);
|
||||
if (res != ESP_OK) { ESP_LOGE(TAG, "HW[%d] uart_param_config: %s", idx, esp_err_to_name(res)); return res; }
|
||||
|
||||
res = uart_set_pin(port, (int)HW_TX[idx], (int)HW_RX[idx], UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
|
||||
if (res != ESP_OK) { ESP_LOGE(TAG, "HW[%d] uart_set_pin: %s", idx, esp_err_to_name(res)); return res; }
|
||||
|
||||
res = uart_driver_install(port, SUART_RX_BUF_SIZE * 2, SUART_RX_BUF_SIZE * 2, 0, nullptr, 0);
|
||||
if (res != ESP_OK) { ESP_LOGE(TAG, "HW[%d] uart_driver_install: %s", idx, esp_err_to_name(res)); return res; }
|
||||
|
||||
hw[idx].tx_done_sem = xSemaphoreCreateBinary();
|
||||
if (hw[idx].tx_done_sem == nullptr) return ESP_ERR_NO_MEM;
|
||||
|
||||
hw[idx].status = SUART_CHANNEL_READY;
|
||||
ESP_LOGI(TAG, "HW channel %d ready (UART%d TX=GPIO%d RX=GPIO%d)", idx, (int)port, (int)HW_TX[idx], (int)HW_RX[idx]);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void SoftUARTManager::deinit_hw_channel(uint8_t idx) {
|
||||
if (hw[idx].tx_done_sem) { vSemaphoreDelete(hw[idx].tx_done_sem); hw[idx].tx_done_sem = nullptr; }
|
||||
if (hw[idx].status != SUART_CHANNEL_NOT_READY) uart_driver_delete(hw[idx].port);
|
||||
hw[idx].status = SUART_CHANNEL_NOT_READY;
|
||||
}
|
||||
|
||||
esp_err_t SoftUARTManager::send(uint8_t* data, size_t size, void* /*config*/, uint8_t channel_num) {
|
||||
if (channel_num >= num_channels || data == nullptr || size == 0) return ESP_ERR_INVALID_ARG;
|
||||
if (ch_status[channel_num] == SUART_CHANNEL_NOT_READY) return ESP_ERR_INVALID_STATE;
|
||||
return send_hw(channel_num, data, size);
|
||||
}
|
||||
|
||||
esp_err_t SoftUARTManager::receive(uint8_t* recv_buf, size_t size, size_t* output_size, uint8_t channel_num) {
|
||||
if (channel_num >= num_channels || recv_buf == nullptr || output_size == nullptr) return ESP_FAIL;
|
||||
if (ch_status[channel_num] != SUART_CHANNEL_LISTENING) return ESP_FAIL;
|
||||
return receive_hw(channel_num, recv_buf, size, output_size);
|
||||
}
|
||||
|
||||
esp_err_t SoftUARTManager::start_receiving(uint8_t channel_num) {
|
||||
if (channel_num >= num_channels) return ESP_FAIL;
|
||||
if (ch_status[channel_num] == SUART_CHANNEL_NOT_READY) return ESP_ERR_INVALID_STATE;
|
||||
if (ch_status[channel_num] == SUART_CHANNEL_LISTENING) return ESP_OK;
|
||||
|
||||
uart_flush_input(hw[channel_num].port);
|
||||
ch_status[channel_num] = SUART_CHANNEL_LISTENING;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t SoftUARTManager::wait_until_send_complete(uint8_t channel_num) {
|
||||
if (channel_num >= num_channels) return ESP_FAIL;
|
||||
|
||||
SemaphoreHandle_t sem = hw[channel_num].tx_done_sem;
|
||||
if (sem == nullptr) return ESP_FAIL;
|
||||
|
||||
if (xSemaphoreTake(sem, SUART_TX_DONE_WAIT_TICKS) != pdTRUE) {
|
||||
ESP_LOGE(TAG, "wait_until_send_complete: timeout on channel %d", channel_num);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
uart_wait_tx_done(hw[channel_num].port, SUART_TX_DONE_WAIT_TICKS);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t SoftUARTManager::send_hw(uint8_t idx, uint8_t* data, size_t size) {
|
||||
uart_wait_tx_done(hw[idx].port, SUART_TX_DONE_WAIT_TICKS);
|
||||
int written = uart_write_bytes(hw[idx].port, reinterpret_cast<const char*>(data), size);
|
||||
if (written < 0 || static_cast<size_t>(written) != size) {
|
||||
ESP_LOGE(TAG, "HW[%d] uart_write_bytes returned %d", idx, written);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
xSemaphoreGive(hw[idx].tx_done_sem);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t SoftUARTManager::receive_hw(uint8_t idx, uint8_t* buf, size_t size, size_t* out) {
|
||||
uart_port_t port = hw[idx].port;
|
||||
|
||||
// wait for at least a minimum complete frame
|
||||
const size_t MIN_FRAME = 10;
|
||||
size_t available = 0;
|
||||
if (uart_get_buffered_data_len(port, &available) != ESP_OK || available < MIN_FRAME) {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
//scan for the preamble byte (0xAB)
|
||||
uint8_t preamble = 0;
|
||||
int discard_count = 0;
|
||||
while (true) {
|
||||
int n = uart_read_bytes(port, &preamble, 1, pdMS_TO_TICKS(2));
|
||||
if (n < 1) return ESP_FAIL;
|
||||
|
||||
if (preamble == 0xAB) break;
|
||||
|
||||
discard_count++;
|
||||
if (discard_count > (int)SUART_RX_BUF_SIZE) {
|
||||
ESP_LOGW(TAG, "HW[%d] no preamble after %d bytes — flushing", idx, discard_count);
|
||||
uart_flush_input(port);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
}
|
||||
if (discard_count > 0) {
|
||||
ESP_LOGD(TAG, "HW[%d] skipped %d noise byte(s) before preamble", idx, discard_count);
|
||||
}
|
||||
buf[0] = preamble;
|
||||
|
||||
// read the remaining 7 header bytes
|
||||
int peeked = uart_read_bytes(port, buf + 1, 7, pdMS_TO_TICKS(20));
|
||||
if (peeked < 7) {
|
||||
ESP_LOGE(TAG, "HW[%d] timeout reading header (got %d of 7)", idx, peeked);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
// determine frame type and total length
|
||||
bool is_control = (buf[5] & 0x80) != 0;
|
||||
uint16_t len_field = (uint16_t)buf[6] | ((uint16_t)buf[7] << 8);
|
||||
|
||||
size_t total_frame_len;
|
||||
if (is_control) {
|
||||
total_frame_len = 8 + len_field + 2;
|
||||
} else {
|
||||
uint8_t hdr2[4];
|
||||
int n2 = uart_read_bytes(port, hdr2, 4, pdMS_TO_TICKS(20));
|
||||
if (n2 < 4) {
|
||||
ESP_LOGE(TAG, "HW[%d] timeout reading generic header tail", idx);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
memcpy(buf + 8, hdr2, 4);
|
||||
uint16_t data_len = (uint16_t)hdr2[2] | ((uint16_t)hdr2[3] << 8);
|
||||
total_frame_len = 12 + data_len + 2;
|
||||
}
|
||||
|
||||
if (total_frame_len > size || total_frame_len < 10 || total_frame_len > 256) {
|
||||
ESP_LOGW(TAG, "HW[%d] unreasonable frame length %d — draining", idx, (int)total_frame_len);
|
||||
// Drain remaining bytes of this bad frame so the next preamble scan
|
||||
// doesn't find false 0xAB matches inside the body.
|
||||
size_t hdr_consumed = is_control ? 8 : 12;
|
||||
size_t to_drain = 0;
|
||||
if (total_frame_len > hdr_consumed && total_frame_len <= 256) {
|
||||
to_drain = total_frame_len - hdr_consumed;
|
||||
} else {
|
||||
size_t avail = 0;
|
||||
uart_get_buffered_data_len(port, &avail);
|
||||
to_drain = avail;
|
||||
}
|
||||
if (to_drain > 0) {
|
||||
uint8_t drain_buf[128];
|
||||
while (to_drain > 0) {
|
||||
size_t chunk = (to_drain > sizeof(drain_buf)) ? sizeof(drain_buf) : to_drain;
|
||||
int got = uart_read_bytes(port, drain_buf, chunk, pdMS_TO_TICKS(5));
|
||||
if (got <= 0) break;
|
||||
to_drain -= (size_t)got;
|
||||
}
|
||||
}
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
// read the rest of the frame
|
||||
size_t already_read = is_control ? 8 : 12;
|
||||
size_t remaining = total_frame_len - already_read;
|
||||
if (remaining > 0) {
|
||||
int nr = uart_read_bytes(port, buf + already_read, remaining, pdMS_TO_TICKS(100));
|
||||
if (nr < (int)remaining) {
|
||||
ESP_LOGE(TAG, "HW[%d] incomplete frame: got %d of %d remaining bytes",
|
||||
idx, nr, (int)remaining);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
}
|
||||
|
||||
*out = total_frame_len;
|
||||
return ESP_OK;
|
||||
}
|
||||
59
components/softUART/include/SoftUARTManager.h
Normal file
59
components/softUART/include/SoftUARTManager.h
Normal file
@@ -0,0 +1,59 @@
|
||||
#ifndef SOFT_UART_MANAGER_H
|
||||
#define SOFT_UART_MANAGER_H
|
||||
|
||||
#include "IPhysicalLayer.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "driver/uart.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "soc/gpio_num.h"
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
|
||||
#define SUART_NUM_CHANNELS 3
|
||||
|
||||
#define SUART_BAUD_RATE 115200
|
||||
#define SUART_RX_BUF_SIZE 1024
|
||||
#define SUART_TX_DONE_WAIT_TICKS pdMS_TO_TICKS(5000)
|
||||
|
||||
#define SUART_CHANNEL_NOT_READY 0x0
|
||||
#define SUART_CHANNEL_READY 0x1
|
||||
#define SUART_CHANNEL_LISTENING 0x2
|
||||
|
||||
typedef struct {
|
||||
uart_port_t port;
|
||||
gpio_num_t tx_gpio;
|
||||
gpio_num_t rx_gpio;
|
||||
uint8_t status;
|
||||
SemaphoreHandle_t tx_done_sem;
|
||||
} hw_channel_t;
|
||||
|
||||
class SoftUARTManager : public IPhysicalLayer {
|
||||
public:
|
||||
explicit SoftUARTManager(uint8_t num_channels);
|
||||
~SoftUARTManager() override;
|
||||
|
||||
esp_err_t send(uint8_t* data, size_t size, void* config, uint8_t channel_num) override;
|
||||
esp_err_t receive(uint8_t* recv_buf, size_t size, size_t* output_size, uint8_t channel_num) override;
|
||||
esp_err_t start_receiving(uint8_t channel_num) override;
|
||||
esp_err_t wait_until_send_complete(uint8_t channel_num) override;
|
||||
|
||||
private:
|
||||
uint8_t num_channels;
|
||||
|
||||
hw_channel_t hw[SUART_NUM_CHANNELS];
|
||||
|
||||
uint8_t ch_status[SUART_NUM_CHANNELS];
|
||||
|
||||
esp_err_t init_hw_channel(uint8_t idx);
|
||||
void deinit_hw_channel(uint8_t idx);
|
||||
|
||||
esp_err_t send_hw(uint8_t idx, uint8_t* data, size_t size);
|
||||
esp_err_t receive_hw(uint8_t idx, uint8_t* buf, size_t size, size_t* out);
|
||||
|
||||
static constexpr gpio_num_t HW_TX[SUART_NUM_CHANNELS] = { GPIO_NUM_4, GPIO_NUM_5, GPIO_NUM_11 };
|
||||
static constexpr gpio_num_t HW_RX[SUART_NUM_CHANNELS] = { GPIO_NUM_3, GPIO_NUM_6, GPIO_NUM_12 };
|
||||
static constexpr uart_port_t HW_PORT[SUART_NUM_CHANNELS] = { UART_NUM_0, UART_NUM_1, UART_NUM_2 };
|
||||
};
|
||||
|
||||
#endif // SOFT_UART_MANAGER_H
|
||||
Reference in New Issue
Block a user