文章目录
- 前言
- 一、系统设计要点
- 二、核心数据结构
- 2.1 设备唯一标识(DeviceUID)
- 2.2 节点信息(Node)
- 2.3 节点管理器(NodeManager)
- 三、核心算法实现
- 3.1 初始化与清理
- 3.1.1 初始化节点管理器
- 3.1.2 清理节点管理器
- 3.2 动态ID分配策略
- 3.2.1 查找最小可用ID
- 3.2.2 ID使用检查
- 3.3 心跳处理机制
- 3.4 超时检测机制
- 四、节点查找与管理
- 4.1 通过UID查找节点
- 4.2 通过ID查找节点
- 4.3 获取活跃节点列表
- 五、回调机制实现
- 5.1 回调函数注册
- 5.2 示例回调函数
- 六、应用示例
- 七、总结
前言
在嵌入式设备网络中,节点的动态加入与退出是常态,尤其是在无人机、传感器网络、智能家居等系统中,节点通常无法提前预设 ID,这就要求系统具备动态 ID 分配与管理能力。本篇博客将围绕“动态 ID 管理”这一核心,介绍一个基于设备 UID 的动态 ID 分配系统,支持重复上线检测、最小可用 ID 分配、ID 冲突检测、ID 释放与复用等功能,代码完全由 C 语言实现,结构清晰,易于移植与扩展。
一、系统设计要点
该系统以节点唯一标识符 DeviceUID 为基础,实现了以下关键特性:
-
✅ 动态 ID 分配:无需提前为设备分配 ID,系统自动为新设备分配最小可用 ID。
-
✅ UID 唯一识别机制:通过对 UID 的比较实现节点重复检测与状态更新。
-
✅ ID 冲突检测:避免多个设备使用相同 ID 导致状态混乱。
-
✅ ID 释放与复用:支持节点主动释放 ID,或超时后自动回收,以复用资源。
-
✅ 回调机制:支持注册上线、下线回调函数,便于系统业务集成。
-
✅ 心跳检测:通过心跳机制维护节点活跃状态
-
✅ 超时处理:自动检测并清理离线节点
二、核心数据结构
2.1 设备唯一标识(DeviceUID)
typedef struct {
uint8_t bytes[6]; // 6字节的唯一设备标识
} DeviceUID;
这个结构体用于存储设备的唯一标识符,通常可以是MAC地址或其他硬件唯一ID。
2.2 节点信息(Node)
typedef struct Node {
uint8_t id; // 分配的节点ID
DeviceUID uid; // 设备唯一标识
uint64_t lastSeenMs; // 最后活跃时间戳(毫秒)
struct Node* next; // 下一个节点指针
} Node;
每个节点包含分配的ID、设备唯一标识、最后活跃时间和指向下一个节点的指针。
2.3 节点管理器(NodeManager)
typedef struct {
Node* head; // 链表头指针
uint8_t activeCount; // 活跃节点计数
NodeOnlineCallback onOnline; // 节点上线回调函数
NodeOfflineCallback onOffline; // 节点下线回调函数
} NodeManager;
节点管理器维护所有活跃节点的链表,并提供回调函数接口。
三、核心算法实现
3.1 初始化与清理
3.1.1 初始化节点管理器
void NodeManager_Init(NodeManager* manager) {
manager->head = NULL;
manager->activeCount = 0;
manager->onOnline = NULL;
manager->onOffline = NULL;
}
3.1.2 清理节点管理器
void NodeManager_Cleanup(NodeManager* manager) {
Node* current = manager->head;
while (current) {
Node* next = current->next;
// 回调通知节点离线
if (manager->onOffline) {
manager->onOffline(current->id, ¤t->uid);
}
free(current);
current = next;
}
manager->head = NULL;
manager->activeCount = 0;
}
3.2 动态ID分配策略
3.2.1 查找最小可用ID
static uint8_t FindMinAvailableID(NodeManager* manager) {
for (uint8_t id = MIN_VALID_ID; id <= MAX_VALID_ID; id++) {
if (!IsIDUsed(manager, id)) return id;
}
return INVALID_ID;
}
该算法从MIN_VALID_ID开始遍历,返回第一个未被使用的ID。
3.2.2 ID使用检查
static bool IsIDUsed(NodeManager* manager, uint8_t id) {
Node* current = manager->head;
while (current) {
if (current->id == id) return true;
current = current->next;
}
return false;
}
3.3 心跳处理机制
uint8_t ProcessHeartbeat(NodeManager* manager, uint8_t nodeId, const DeviceUID* uid) {
// 1. 检查是否已有相同UID的节点
Node* existing = FindNodeByUID(manager, uid);
if (existing) {
existing->lastSeenMs = GetSysTimeMs(); // 更新活跃时间
return existing->id;
}
// 2. 检查请求的ID是否已被占用
if (nodeId != INVALID_ID && FindNodeByID(manager, nodeId)) {
nodeId = INVALID_ID; // 如果已被占用,则重置为无效ID
}
// 3. 分配新ID
if (nodeId == INVALID_ID) {
nodeId = FindMinAvailableID(manager);
if (nodeId == INVALID_ID) return INVALID_ID; // 无可用ID
}
// 4. 添加新节点
return AddNode(manager, nodeId, uid) ? nodeId : INVALID_ID;
}
-
心跳处理流程:
-
如果是已知节点,更新其活跃时间
-
如果是新节点,检查请求ID是否可用
-
分配最小可用ID
-
添加新节点到管理器
-
3.4 超时检测机制
void CheckTimeoutNodes(NodeManager* manager) {
uint64_t now = GetSysTimeMs();
Node** pnode = &manager->head;
while (*pnode) {
Node* current = *pnode;
if ((now - current->lastSeenMs) > HEARTBEAT_TIMEOUT) {
*pnode = current->next; // 从链表中移除
// 回调通知节点离线
if (manager->onOffline) {
manager->onOffline(current->id, ¤t->uid);
}
free(current); // 释放节点内存
manager->activeCount--;
} else {
pnode = &(*pnode)->next;
}
}
}
该函数遍历所有节点,检查最后活跃时间是否超时,超时则移除节点并触发下线回调。
四、节点查找与管理
4.1 通过UID查找节点
static Node* FindNodeByUID(NodeManager* manager, const DeviceUID* uid) {
Node* current = manager->head;
while (current) {
if (CompareDeviceUID(¤t->uid, uid)) return current;
current = current->next;
}
return NULL;
}
4.2 通过ID查找节点
static Node* FindNodeByID(NodeManager* manager, uint8_t id) {
Node* current = manager->head;
while (current) {
if (current->id == id) return current;
current = current->next;
}
return NULL;
}
4.3 获取活跃节点列表
uint8_t GetActiveNodeIDs(NodeManager* manager, uint8_t* outputBuffer, uint8_t bufferSize) {
uint8_t count = 0;
Node* current = manager->head;
while (current && count < bufferSize) {
outputBuffer[count++] = current->id;
current = current->next;
}
qsort(outputBuffer, count, sizeof(uint8_t), CompareNodeIDs);
return count;
}
五、回调机制实现
5.1 回调函数注册
void NodeManager_RegisterCallbacks(NodeManager* manager,
NodeOnlineCallback onOnline,
NodeOfflineCallback onOffline) {
manager->onOnline = onOnline;
manager->onOffline = onOffline;
}
5.2 示例回调函数
void OnNodeOnline(uint8_t id, const DeviceUID* uid) {
printf("[Callback] Node %d is ONLINE!\n", id);
}
void OnNodeOffline(uint8_t id, const DeviceUID* uid) {
printf("[Callback] Node %d is OFFLINE!\n", id);
}
六、应用示例
void TestNodeManager() {
NodeManager manager;
NodeManager_Init(&manager);
// 注册回调
NodeManager_RegisterCallbacks(&manager, OnNodeOnline, OnNodeOffline);
// 模拟设备UID
DeviceUID uid1 = {{0x00, 0x11, 0x22, 0x33, 0x44, 0x55}};
DeviceUID uid2 = {{0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB}};
// 节点上线
ProcessHeartbeat(&manager, 1, &uid1); // 指定ID=1
ProcessHeartbeat(&manager, INVALID_ID, &uid2); // 自动分配ID
// 打印活跃节点
PrintActiveNodes(&manager);
// 模拟超时
printf("\nSimulating timeout...\n");
GetSysTimeMs(); // 模拟时间流逝
CheckTimeoutNodes(&manager);
PrintActiveNodes(&manager);
// 主动释放节点
printf("\nManually releasing node...\n");
ReleaseNodeID(&manager, 2);
PrintActiveNodes(&manager);
// 清理
NodeManager_Cleanup(&manager);
}
七、总结
本文详细介绍了一个高效的动态ID管理系统的设计与实现,该系统具有以下优点:
-
灵活性:支持动态ID分配和释放
-
可靠性:通过心跳机制确保节点状态准确
-
可扩展性:易于添加新功能如安全验证等
-
低开销:内存占用小,适合嵌入式环境
-
事件驱动:通过回调机制实现松耦合
这种动态ID管理方案非常适合物联网设备、传感器网络等需要管理大量动态节点的嵌入式应用场景。