29 嵌入式操作系统 | 网关维护状态表 + 每 2 秒上报(含设备在线判定)

发布时间:2026/10/2 7:12:30
29 嵌入式操作系统 | 网关维护状态表 + 每 2 秒上报(含设备在线判定) 嵌入式操作系统 | 网关维护状态表 每 2 秒上报含设备在线判定本课程开源地址Giteehttps://gitee.com/fujianxinxi/qianrushixitongyingyongkaifa.git课件、示例代码与验收脚本都在该仓库可直接git clone或下载 ZIP 使用。模块七 · 2 学时 · 环境Ubuntugcc libubox socat 虚拟串口一句话概述网关不再收到就转发而是把设备状态记进一张表然后每 2 秒自己组一帧0x81帧尾多一个在线字节UDP 上报给固定目标设备超过 3 秒没上报就标成离线。下课时你能看到灯 亮 | 风扇 2 档 | 25.0 ℃ | 在线→ 关掉设备 →离线→ 设备回来 →在线。本节目标会定义设备状态表结构体灯/风扇/温度/最后收到时间/在线标志并在收到帧时更新它会用uloop_timeout做周期上报回调里再排一次会自己组帧build_frame CRC16会做在线判定用单调时间比较并说清为什么要把上报目标固定下来对比课时 27 的坑能复现全过程上线 → 掉线 → 恢复一、讲解内容1.1 知识讲解本节在课时 28 的基础上加什么。上一节的网关只是把设备帧广播出去这一节它开始记住设备的当前状态并主动、定期地对外汇报设备面板 ──0x80 状态帧每秒──▶ 网关 ├── ① 更新设备状态表灯/风扇/温度/最后收到时间 ├── ② 广播给已连接的 TCP 客户端课时 28 已有 └── ③ 每 2 秒自己组一帧 0x81 ──UDP──▶ 监听端/界面/上位机 表里的值 online 标志为什么要状态表三个现实原因学生要能说出来原因说明查询要快上位机问现在什么状态网关直接读表回答不用现去问设备设备可能正忙/掉线掉线要能判断表里记了最后收到的时间一比较就知道设备是不是失联了掉线后最后已知值还能显示上报要解耦上报节奏2 秒和设备的节奏1 秒不必绑定设备换了、上报目标变了都不用动设备帧格式加了一版v1.1网关对外发的是0x81比设备的0x80多一个在线字节方向TYPELENPAYLOAD设备 → 网关0x804灯(1) 风扇(1) 温度×10(2 大端)网关 → 上位机0x815灯(1) 风扇(1) 温度×10(2 大端)在线(1)在线判定怎么做关键是用单调时间CLOCK_MONOTONIC不是墙上时钟date那种会被手动/校时改动staticlongnow_ms(void)/* 单调时间的毫秒数 */{structtimespects;clock_gettime(CLOCK_MONOTONIC,ts);return(long)ts.tv_sec*1000(long)(ts.tv_nsec/1000000);}/* 收到设备状态帧时 */state.last_seen_msnow_ms();/* 记下最后一次见到 */state.online1;/* 每 2 秒上报时先判一次 */if(state.onlinenow_ms()-state.last_seen_msOFFLINE_MS){state.online0;/* 3 秒没消息 → 掉线 */}阈值怎么定要比设备的上报周期宽 2~3 倍设备 1 秒一帧 → 3 秒判掉线本课。太紧会抖动设备只是慢了半拍就报掉线太松则掉线发现慢。上报目标固定是本节特意改的设计对比课时 27课时 27UDP 收命令本节上报发给谁“最近给我发过包的那个人”peer固定的127.0.0.1:9100用./gateway_full /tmp/ttyV0 9000 127.0.0.1 9100指定风险任何程序发个包就把地址顶掉真正的上位机收不到不受谁说话影响目标可以写死、也可以课时 31从 uci 读适合快速验证真实部署1.2 操作步骤一条命令一步看现象第 0 步环境检查gcc、libubox、socat、pyserial完整清单见课时 24 附录 Fgcc--version|head-1ls/usr/local/include/libubox/uloop.hwhichsocat python3-cimport serial; print(pyserial OK)第 1 步编译本节的网关是从课时 28 复制过来改的cd~/lab/课时29-网关状态表与上报ls# 应看到 gateway_full.c、state_listener.pygcc-Wall-Wextra-ogateway_full gateway_full.c-lubox现象实测[OK] gateway_full 编译无警告。第 2 步四个终端准备好A 串口 / B 设备面板 / C 网关 / D 监听端# 终端 A虚拟串口socat-d-dpty,raw,echo0,link/tmp/ttyV0 pty,raw,echo0,link/tmp/ttyV1# 终端 B设备面板课时 26 的程序cd~/lab/课时26-设备面板接串口python3 device_panel_serial.py# 终端 C网关参数串口 / TCP端口 / 上报目标IP / 上报端口cd~/lab/课时29-网关状态表与上报 ./gateway_full /tmp/ttyV09000127.0.0.19100# 终端 D监听上报只看不发命令python3-ustate_listener.py9100网关启动的现象实测网关已启动串口 /tmp/ttyV0 9600 ⇄ TCP 0.0.0.0:9000CtrlC 退出 上位机发命令先发 2 字节长度大端再发那一帧 设备状态① 广播给已连接的 TCP 客户端② 每 2000 毫秒 UDP 上报到 127.0.0.1:9100终端 D 的现象网关每 2 秒上报一次注意最后那个在线监听 UDP 9100等网关每 2 秒上报一次CtrlC 退出 收到 55 AA 81 05 00 00 00 F1 01 0E 0D → 灯 灭 | 风扇 0 档 | 24.1 ℃ | 在线 收到 55 AA 81 05 00 00 00 F3 01 0F 6D → 灯 灭 | 风扇 0 档 | 24.3 ℃ | 在线帧里的81 05 TYPE 0x81、LEN 5最后那个字节01就是在线。第 3 步用 TCP 命令控制设备看上报跟着变课时 28 的客户端就能用cd~/lab/课时29-网关状态表与上报 python3-u../课时28-网关TCP命令通道/tcp_client.py led on python3-u../课时28-网关TCP命令通道/tcp_client.py fan2现象实测终端 D收到 55 AA 81 05 01 02 00 F2 01 32 85 → 灯 亮 | 风扇 2 档 | 24.2 ℃ | 在线 收到 55 AA 81 05 01 02 00 F4 01 31 25 → 灯 亮 | 风扇 2 档 | 24.4 ℃ | 在线命令走 TCP时长 28状态走 UDP 上报本节——控制与数据分开这也是真实工控系统的常见做法。第 4 步★掉线实验——把设备拔掉pkill-fdevice_panel_seria[l]# 杀掉设备面板相当于把设备断电/拔线等 5~6 秒终端 D 的现象实测先保持最后已知值再把在线位变成 00收到 55 AA 81 05 01 02 00 F3 01 33 15 → 灯 亮 | 风扇 2 档 | 24.3 ℃ | 在线 收到 55 AA 81 05 01 02 00 F1 01 32 75 → 灯 亮 | 风扇 2 档 | 24.1 ℃ | 在线 收到 55 AA 81 05 01 02 00 F1 00 F3 B5 → 灯 亮 | 风扇 2 档 | 24.1 ℃ | 离线 ← online 变 0网关那边同时打印实测★ 设备掉线了超过 3000 毫秒没收到状态帧 网关上报状态(0x81)11 字节55 AA 81 05 01 02 00 F1 00 F3 B5 → 灯 亮 | 风扇 2 档 | 24.1 ℃ | 离线第 7 次上报第 5 步★设备回来了cd~/lab/课时26-设备面板接串口 python3 device_panel_serial.py现象实测几秒内自动恢复在线★ 设备上线了灯 灭风扇 0 档24.9 ℃ ← 网关日志 收到 55 AA 81 05 00 00 00 F7 01 0D AD → 灯 灭 | 风扇 0 档 | 24.7 ℃ | 在线第 6 步停网关看统计CtrlC停网关终端 C现象实测网关退出。统计串口收 18 帧、TCP 广播 4 次TCP 收 2 帧、转给串口 2 帧状态上报 11 次三个数字对照着看18 帧是从设备收到的、11 次是每 2 秒上报的次数、2 帧是客户端下发的命令。第 7 步收工。依次 CtrlC监听端 → 网关 → 设备面板 → socat。1.3 常见错误与排查现象原因解决编译报undefined reference to clock_gettime老环境需要链接 rt加-lrtUbuntu 20.04 一般不需要监听端一直没输出① 网关没起来 ② 上报端口不对 ③ 被防火墙挡看网关日志有没有网关上报状态(0x81)确认监听端端口与网关第 4 个参数一致监听端报Address already in use9100 被别的程序占着ss -lunp | grep 9100找出来杀掉或两边都换端口网关第 4 参数 监听端口状态一直是离线设备面板没跑 / 串口不对确认设备面板用/tmp/ttyV1、网关用/tmp/ttyV0看设备面板日志有没有发送 0x80掉线实验里杀不掉设备kill的是xvfb-run里面 python 还活着用pkill -f device_panel_seria[l]按进程名杀用pgrep -af device_panel确认在线/离线来回跳判定阈值太小比设备上报周期还短阈值至少是设备上报周期的 2~3 倍本课设备 1 秒一帧 → 3 秒判掉线温度不变化设备面板停了离线时表里是最后已知值这是设计如此离线后保留最后值界面上要显示离线最后值上报帧 CRC 不对组帧时 CRC 覆盖范围写错范围是TYPE LEN PAYLOAD不含 55 AA也不含 CRC 本身二、学生练习本节命令速查表命令作用示例gcc -Wall -Wextra -o gateway_full gateway_full.c -lubox编译本节网关—./gateway_full /tmp/ttyV0 9000 127.0.0.1 9100启动串口/TCP端口/上报IP/上报端口4 个参数都能省用默认值python3 -u state_listener.py 9100监听上报看 在线/离线CtrlC 退出python3 -u ../课时28-网关TCP命令通道/tcp_client.py led on用 TCP 命令控制设备复用课时 28 的工具pkill -f device_panel_seria[l]模拟设备掉线看上报变 离线ss -lunp | grep 9100看谁占着上报端口排查端口冲突编号任务提示预期结果L29.1编译 四个终端起来第 1、2 步监听端每 2 秒一行 …L29.2用 TCP 命令改状态第 3 步上报里的灯/风扇跟着变L29.3★掉线实验第 4 步3 秒后 online 变 0网关打印设备掉线了L29.4设备恢复第 5 步网关打印设备上线了上报恢复在线L29.5停网关看统计第 6 步三个数字能对上收帧/上报/命令L29.6把OFFLINE_MS改成 1000 再试改#define OFFLINE_MS 1000掉线判得很快但正常时也可能误判体会阈值取舍L29.7把上报周期改成 500 毫秒改#define REPORT_MS 500监听端每 0.5 秒一行网络和 CPU 占用也会上升L29.8让上报目标改成宿主机 IP第 4 个参数填宿主机 IP192.168.31.x宿主机上用 Python/工具收到上报体会上报目标可配置三、作业必做交 3 张截图监听端在线的一行 / 掉线后离线的一行 网关设备掉线了 / 恢复后在线并回答① 网关为什么要维护设备状态表不维护行不行② 在线判定的阈值应该怎么定定太小/太大分别会怎样③ 为什么要用CLOCK_MONOTONIC单调时间而不是date那种时间④ 本节把上报目标改成了固定地址比课时 27 的谁说话听谁的好在哪选做① 在0x81帧里再加一个最后更新时间比如距离上次收到设备帧多少秒1 字节监听端也翻译它② 给网关加一条设备连续离线超过 10 秒就打印一条告警的日志提示在send_state_report里加个计数器提交截图 文字回答命名课时29-作业截图.png四、本节小结与下节预告本节网关长脑子了一张设备状态表灯/风扇/温度/最后收到时间/在线、每 2 秒自己组帧0x81上报含在线字节、超过 3 秒就判掉线并且把上报目标固定下来不再受谁说话影响。你还亲手做了在线 → 掉线 → 恢复的全过程实验。下节课课时 30做上位机界面用 Flask 起一个网页页面上显示灯/风扇/温度/在线收网关的 UDP 上报并放控制按钮走 TCP 发命令——做完之后用鼠标就能控制那个串口设备而且在 Windows 宿主机浏览器里也能操作。附录怎么运行 完整代码两个文件在代码/课时29-网关状态表与上报/gateway_full.c网关从课时 28 复制后加功能和state_listener.py上报监听端。完整代码以仓库文件为准下面摘本节新增的代码。附录 A环境准备 怎么运行# ⓿ 环境准备缺哪个装哪个完整清单见课时 24 附录 Fsudoaptupdatesudoaptinstall-ybuild-essential cmake pkg-config socatsudoaptinstall-ypython3-pyqt5 python3-serial xvfb# 终端 A虚拟串口 socat-d-dpty,raw,echo0,link/tmp/ttyV0 pty,raw,echo0,link/tmp/ttyV1# 终端 B设备面板 cd~/lab/课时26-设备面板接串口python3 device_panel_serial.py# 终端 C本节网关每 2 秒上报cd~/lab/课时29-网关状态表与上报 gcc-Wall-Wextra-ogateway_full gateway_full.c-lubox./gateway_full /tmp/ttyV09000127.0.0.19100# 终端 D监听上报 python3-ustate_listener.py9100# 掉线 / 恢复实验 pkill-fdevice_panel_seria[l]# 掉线上报变离线cd~/lab/课时26-设备面板接串口python3 device_panel_serial.py# 恢复在线附录 B本节新增的代码从课时 28 复制后加的部分B-1 状态表 单调时间文件开头/* 课时 29 新增的常量 */#defineT_STATUS0x80/* 设备 → 网关状态上报灯 / 风扇 / 温度 */#defineT_STATE_REPORT0x81/* 网关 → 上位机网关状态多一个在线字节 */#defineREPORT_MS2000/* 每 2 秒上报一次 */#defineOFFLINE_MS3000/* 超过 3 秒没收到设备状态帧 → 判定离线 */#defineSTATE_PORT_DEF9100/* 上报目标端口课时 31 会改成从 uci 读 */#defineSTATE_HOST_DEF127.0.0.1/* 课时 29 新增设备状态表 上报定时器 */structdevice_state{intled;/* 灯0 灭 / 1 亮 */intfan;/* 风扇0~3 档 */inttemp_x10;/* 温度 ×10250 25.0 ℃ */longlast_seen_ms;/* 最近一次收到设备状态帧的时间毫秒 */intonline;/* 1 在线 / 0 离线 */};staticstructdevice_statestate{0,0,250,0,0};/* 初值灯灭、风扇停、25.0℃、离线 */staticstructuloop_timeoutt_report;/* 周期上报定时器 */staticintudp_fd-1;/* 只用来发上报sendto */staticstructsockaddr_instate_peer;/* 上报目标固定 */staticintreport_cnt;/* 上报了多少次 *//* 单调时间的毫秒数用 clock_gettime不受系统时间被改的影响 */staticlongnow_ms(void){structtimespects;clock_gettime(CLOCK_MONOTONIC,ts);return(long)ts.tv_sec*1000(long)(ts.tv_nsec/1000000);}B-2 收到设备状态帧 → 记进状态表顺便判上线/* 收到设备的 0x80 帧把它的值记到状态表里这才是网关该干的事 */staticvoidupdate_state_from(constuint8_t*frame,inttotal){uint8_ttypeframe[2];intplenframe[3];constuint8_t*payloadframe4;(void)total;if(type!T_STATUS||plen!4){printf(这一帧不是设备状态帧不进状态表TYPE0x%02X LEN%d\n,type,plen);return;}state.ledpayload[0];state.fanpayload[1];state.temp_x10(payload[2]8)|payload[3];state.last_seen_msnow_ms();if(!state.online){state.online1;printf(★ 设备上线了灯 %s风扇 %d 档%.1f ℃\n,state.led?亮:灭,state.fan,state.temp_x10/10.0);fflush(stdout);}}B-3 周期上报先判在线 → 组 0x81 → sendto → 再排下一次/* 每 2 秒判在线 → 组一帧 0x81 → 发给固定上报目标 → 再排下一次 */staticvoidsend_state_report(structuloop_timeout*t){uint8_tframe[32];uint8_tpayload[5];intlen;/* ① 在线判定太久没收到设备状态帧就算离线值保留界面还能显示最后已知值 */if(state.onlinestate.last_seen_ms!0now_ms()-state.last_seen_msOFFLINE_MS){printf(★ 设备掉线了超过 %d 毫秒没收到状态帧\n,OFFLINE_MS);fflush(stdout);state.online0;}/* ② 组 0x81灯 风扇 温度高 温度低 在线 */payload[0](uint8_t)state.led;payload[1](uint8_t)state.fan;payload[2](uint8_t)((state.temp_x108)0xFF);payload[3](uint8_t)(state.temp_x100xFF);payload[4](uint8_t)state.online;lenbuild_frame(frame,T_STATE_REPORT,payload,5);/* ③ 发出去 */if(sendto(udp_fd,frame,(size_t)len,0,(structsockaddr*)state_peer,sizeof(state_peer))!len){perror(sendto(state));}else{report_cnt;dump(网关上报状态(0x81),frame,len);printf( → 灯 %s | 风扇 %d 档 | %.1f ℃ | %s第 %d 次上报\n,state.led?亮:灭,state.fan,state.temp_x10/10.0,state.online?在线:离线,report_cnt);fflush(stdout);}uloop_timeout_set(t,REPORT_MS);/* 周期任务的写法回调里再排一次 */}B-4 main() 里注册定时器和上报目标constchar*dev(argc2)?argv[1]:DEV_DEFAULT;intport(argc3)?atoi(argv[2]):TCP_PORT;constchar*state_host(argc4)?argv[3]:STATE_HOST_DEF;/* 课时 29 新增 */intstate_port(argc5)?atoi(argv[4]):STATE_PORT_DEF;.../* 课时 29 新增UDP socket只用来上报 上报定时器 */udp_fdsocket(AF_INET,SOCK_DGRAM,0);if(udp_fd0){perror(socket(udp));close(ser_fd);close(listen_fd);uloop_done();return1;}memset(state_peer,0,sizeof(state_peer));state_peer.sin_familyAF_INET;state_peer.sin_porthtons((uint16_t)state_port);inet_aton(state_host,state_peer.sin_addr);t_report.cbsend_state_report;uloop_timeout_set(t_report,REPORT_MS);/* 2 秒后开始第一次上报 */另外serial_handle_frames()里在广播那一行前面加了一句update_state_from(ser_rxbuf, total);——设备帧既广播给 TCP 客户端也记进状态表两条路互不影响。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询