当前位置:首页 > 工业控制 > 工业控制
[导读]工业网关断点续传不是“断网了把数据写文件、联网了读出来发”这么粗糙。核心就一条:采集线程永远不阻塞,上行失败只进本地缓存,收到业务ACK才清记录。网络断几小时、网关掉电重启,数据都该按采集时间顺序补回去。


工业网关断点续传不是“断网了把数据写文件、联网了读出来发”这么粗糙。核心就一条:采集线程永远不阻塞,上行失败只进本地缓存,收到业务ACK才清记录。网络断几小时、网关掉电重启,数据都该按采集时间顺序补回去。

一、架构:采集与补传彻底解耦

采集线程 ─▶ RAM环形缓冲 ─▶ 批量落盘(Flash/FRAM/eMMC)

                               │

                         未确认记录区

                               │

                   补传线程(网络OK才动)

                               │

                         MQTT/QoS1或HTTP

                               │

                         服务端ACK回写

采集侧只push,不等socket;补传侧独立线程扫缓存。 关键纪律:采集线程绝不因网络卡住,否则一台设备掉线拖垮整网关。

二、缓存记录格式

每条采集值带采集时间戳+seq,服务端按时间戳入库去重:

typedef struct __attribute__((packed)){

   uint32_t id;        // 自增主键,补传顺序

   uint64_t ts_ms;     // 采集时刻,不是发送时刻

   uint16_t point_id;

   int32_t  value;

   uint8_t  qual;      // 0正常 1估算 2超时

   uint8_t  sent;      // 0未传 1已确认

   uint16_t crc;

} rec_t;

裸机用定长二进制环区,Linux网关用SQLite表(id,ts_ms,point_id,value,qual,sent),索引建(sent,ts_ms)。

三、采集落盘:批量刷,别每条写

高频采集每点都写Flash,擦写寿命几天就废。正确做法RAM攒批:

#define BATCH_N 64

static rec_t wb[BATCH_N];

static int wi;


void sample_push(uint16_t pid,int32_t v){

   rec_t*r=&wb[wi];

   r->id = next_id++;

   r->ts_ms = rtc_ms();

   r->point_id=pid; r->value=v; r->qual=0;

   r->sent=0; r->crc=crc16(r,sizeof(*r)-2);

   if(++wi>=BATCH_N){

       flash_append(wb,sizeof(wb));   // 批量落盘

       wi=0;

   }

}


/* 定时2s或满批刷一次,防掉电丢内存 */

void sample_flush(void){

   if(wi) flash_append(wb,wi*sizeof(rec_t));

   wi=0;

}

断网期间也照常落盘;网络恢复线程只读sent=0记录。

四、断网检测:别只信网卡link

以太网口up≠云端通。应用层心跳+发送超时双判:

void net_monitor(void){

   if(mqtt_ping_timeout(3)){          // 连续3次无PINGRESP

       g_net_online=0;

   }

}

void on_mqtt_connect(void){

   g_net_online=1;

   resume_upload();                   // 触发补传

}

掉线状态里采集照常写缓存,不发网络包。

五、补传状态机

按id升序取窗口,收到ACK才标sent,失败就停:

#define WIN 32

void upload_task(void){

   if(!g_net_online) return;

   rec_t buf[WIN];

   int n = cache_load_unsent(buf, WIN);   // SELECT ... WHERE sent=0 ORDER BY id

   for(int i=0;i<n;i++){

       if(post_one(&buf[i])!=0){           // HTTP 2xx / MQTT PUBACK

           break;                          // 当前条失败,后面保留

       }

       cache_mark_sent(buf[i].id);         // UPDATE sent=1

   }

}

几个硬规矩:

发成功再删/标sent,别先删再发→发送中掉电就丢

MQTT用QoS1,PUBACK丢失会重发→服务端按point_id+ts_ms去重

批次合并DELETE,别逐条删,SQLite写放大炸

补传限速,刚恢复别几万条一起灌爆broker

六、掉电保护

RAM环缓掉电清空,所以:

关键记录进FRAM/NOR,配置/断点指针也放这

eMMC/SD走SQLite WAL,synchronous=NORMAL

电源加掉电检测+PVD,中断里只做sample_flush()最小写入

写记录加commit字节或CRC,重启扫描丢弃半截记录

超级电容留50~200ms写窗口

七、缓存满策略

长断网缓存爆了必须决断:

工业默认丢最老保最新(FIFO覆盖)

关键工序可改“停采保历史”,但实时监控会空窗

水位超80%上报告警,别等满才处理

if(cache_usage()>0.9f){

   cache_drop_oldest(16);   // 批量裁旧,不单条删

   warn_upload_backlog();

}

八、落坑清单

采集线程里阻塞发MQTT→整链卡死

每条样本写Flash→寿命耗尽

用接收时间入库→补传数据时间错乱

先删缓存再等ACK→掉电丢数据

QoS0当可靠传输→断网必丢

不标sent字段→重启重复全传

恢复瞬间全量重发→broker被打挂

内存队列当持久层→断电全没

只信网卡link→公网断了还在发

缓存满不处理→OOM网关重启

九、落地顺序

定义rec_t:ts_ms+point_id+value+sent+crc

采集线程只写RAM环缓,满批/定时刷Flash

网络状态用心跳判,不只看link

补传线程按id升序取窗口

post成功才标sent,失败停当前批

MQTT QoS1+服务端时间戳去重

掉电中断只做最小flush,FRAM存断点

缓存水位超阈值裁旧+告警

断点续传的本质是“采集和上行解耦成两块”:采集侧 guaranteed store,上行侧 best-effort forward,中间靠本地缓存扛住断网和掉电。 真正稳的网关不是不断网,而是断网时数据进盘、恢复时按序补、重复靠时间戳去重、掉电重启能从断点继续。别在采集中断里碰socket,也别把内存队列当黑匣子——这两点守住,工业现场4G抖几小时,MES里数据也只是晚到,不会缺。



本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除( 邮箱:macysun@21ic.com )。
关闭