实习笔记(六)主机按钮状态上报完整流程

业务目标:产品面板按钮状态发生变化,依靠事件桥完成跨进程上报,最终组装 protobuf 报文发送给中心站。 比喻对照:事件桥 = 内部邮件系统;业务账本 = 内部笔记本;SDK 结构体 = 翻译草稿纸;Protobuf = 对外快递包裹(发送完就销毁) 数据流向:①业务账本 → ②通信层 sdk → ③proto 协议 ,开发严格遵循该顺序。 底层设计三原则:组件解耦、单写者、跨进程契约

0. 整体流程一览

复制代码
SystemManager(采集产生按钮状态)
  ↓ 组装CoreEvent邮件,dispatch_event交给事件桥(邮件系统)
ClientProxy(接收跨进程事件邮件)
  ↓ 步骤5:把状态写入①业务账本(内部笔记本,常驻内存保存真实状态)
  ↓ 步骤6:数据拷贝:①业务账本 → ②通信层sdk结构体(翻译草稿纸)
  ↓ 步骤7:数据拷贝:②通信层sdk结构体 → ③protobuf协议报文(快递包裹)
  ↓ 序列化为网络字节流,发送至中心站

关键注意:SystemManager 不能直接修改 ClientProxy 内部成员变量,进程隔离,只能发事件消息;proto 是对外通信契约,要放在流程最后实现。

第一部分:发送端 SystemManager,产生并向外投递事件邮件

步骤 1:定义全新事件 ID(头文件 .h)

通俗解释:定义邮件编号,用来标识这是一条 "面板按钮状态变更" 的消息,收发双方靠这个编号识别消息类型。 在原有事件枚举列表中,原有内部温度事件下方,添加新事件枚举:

复制代码
EV_MCP_UPDATE_INTERNAL_TEMP,        //原有:内部温度
EV_MCP_UPDATE_BOARD_BUTTON_STATUS,  //新增:面板按钮状态事件(邮件编号)

步骤 2:函数末尾触发发送事件(实现文件 .cpp)

通俗解释:硬件检测到按钮按下 / 松开,组装一封 CoreEvent 邮件,填上收件人、邮件编号、附件数据,调用 dispatch_event 把邮件交给事件桥(邮件系统)完成跨进程投递。 在按钮状态更新函数的函数末尾,增加事件组装与发送代码:

复制代码
// button_state:硬件读到的值,int,1按下,0松开
CoreEvent ev;
ev.type = EV_TYPE_LOCAL_CLIENT_PROXY;    //收件人:ClientProxy组件
ev.id  = EV_MCP_UPDATE_BOARD_BUTTON_STATUS; //邮件编号
ev.payload = std::move(button_state);   //邮件附件:携带按钮状态数值
dispatch_event(ev);                     //把邮件交给【事件桥】,底层负责跨进程分发

提示:此处只向外发送消息,不能直接操作其他组件内部数据;事件邮件处理完成之后就会销毁,不会保存状态

第二部分:三层数据容器定义(账本、通信层、协议层)

步骤 3:①业务账本结构体(头文件 .h)

通俗解释:准备好内部笔记本,增加一栏记录面板按钮状态;账本是类的成员变量,常驻内存,就算断网、邮件销毁,设备当前真实状态仍然保存在这里。 在业务状态结构体TechMaintainStatus内部,新增按钮状态成员变量:

复制代码
struct TechMaintainStatus {
    ...
    std::optional<bool> nursecall_status; //原有
    std::optional<bool> button_status;    //新增:①业务账本保存按钮状态(内部笔记本)
};

作用:std::optional<bool>区分三种状态:未收到上报、按下 true、松开 false。事件处理函数收到消息后,把值存入该账本。

步骤 4:②通信层 SDK 过渡结构体(头文件 .h)

通俗解释:准备翻译草稿纸;业务账本是固件内部格式,proto 是对外网络格式,用这一层做中转隔离。后续协议修改,只改草稿纸到 proto 的逻辑,内部业务账本代码不用改动。 在 SDK 通信中间结构体nx::sdk::TechMaintainStatus中增加字段:

复制代码
struct TechMaintainStatus {
    ...
    std::optional<bool> nursecall_status; //原有
    std::optional<MFDStatus> mfd_status;  //原有
    std::optional<bool> button_status;    //新增:②通信过渡层(翻译草稿纸)
};

作用:实现业务逻辑与网络报文解耦;业务层只管维护状态,通信层只管组装报文,互不耦合。

步骤 5:③protobuf 对外协议契约(.proto 协议文件)

通俗解释:定义对外快递包裹格式,和外部中心站约定好数据包长什么样;BoolValue 包装类型可以区分 "无数据、true、false",普通 bool 做不到。 在 proto 的TechMaintainStatus消息体中添加字段:

复制代码
google.protobuf.BoolValue button_status = 11;

作用:设备和外部中心站之间的网络通信契约;编译后自动生成 has_button_status()判断字段是否有效。protobuf 对象是临时局部变量,报文发送完成就销毁,不能用来长期保存设备状态

第三部分:接收端 ClientProxy,接收事件、逐层搬运数据

步骤 6:事件处理 handler 声明与实现(.h 头文件 + .cpp 实现文件)

通俗解释:收到事件邮件,拆开邮件取出附件数据,把按钮状态写入内部笔记本(业务账本 tech_maintain_status_)。事件本身是一次性消息,用完销毁,真正状态保存在账本。

  1. 头文件添加函数声明

    ClientProxyEvResult handle_update_board_button_status_event(
    const CoreEvent& in, CoreEvent* out);

  2. cpp 实现文件编写处理函数

    ClientProxyEvResult ClientProxy::handle_update_board_button_status_event(
    const CoreEvent &in, CoreEvent *out)
    {
    bool is_fslev{false};
    //从事件payload取出邮件附件int类型按钮状态
    if (const auto &data = std::any_cast<int32_t>(&in.payload)) {
    //【对应动作:把数据写进内部笔记本‑业务账本】
    tech_maintain_status_.button_status = *data;
    }
    return {HANDLED, is_fslev};
    }

作用:事件实际处理入口,只负责更新内部业务状态,不直接做网络发送。int 数值自动转换:0 对应 false,1 对应 true。

步骤 7:注册事件处理函数路由(.cpp 实现文件)

通俗解释:告诉事件桥(邮件系统):收到编号为 EV_MCP_UPDATE_BOARD_BUTTON_STATUS 的邮件,请交给上面这个 handler 函数处理。 ⚠️重点坑点:忘记注册,代码编译完全通过,但运行时邮件直接静默丢弃,处理函数永远不会执行。

复制代码
register_client_proxy_ev_handler(
    EV_MCP_UPDATE_BOARD_BUTTON_STATUS,
    std::bind(&ClientProxy::handle_update_board_button_status_event, this,
        std::placeholders::_1, std::placeholders::_2));
事件完整内部调用链路
复制代码
dispatch_event()
  → 根据目标组件找到 ClientProxy::handle_event()
   → handle_clientproxy_event()
     → 在路由map中根据事件ID查找注册的处理函数
      → 执行我们编写的handler业务逻辑

步骤 8:数据搬运①业务账本 → ②通信层 SDK 结构体(.cpp 转换文件)

通俗解释:要发送报文时,把笔记本(业务账本)上面记录的数据,抄写一份到翻译草稿纸(SDK 结构体)。 参照现有其他状态拷贝代码,增加如下赋值逻辑:

复制代码
// src = 内部账本 tech_maintain_status_
// data = SDK草稿纸对象
if (src.button_status) {
    //【对应动作:把笔记本内容抄写至草稿纸】
    data->button_status = src.button_status; 
}

踩坑提醒:遗漏该步骤,账本数据正常,但通信结构体为空,最终报文不带该字段。

步骤 9:数据搬运②通信层 SDK → ③protobuf 报文(.cpp 类型转换文件)

通俗解释:拿着草稿纸上的数据,按照约定格式填充到临时快递包裹(protobuf 对象)。 增加代码把 SDK 数据填充到 protobuf 对象:

复制代码
// sdkdata:草稿纸 nx::sdk::TechMaintainStatus
// proto:待填充的protobuf对象(临时快递包裹)
if (sdkdata.button_status) {
    //【对应动作:把草稿纸上内容填进对外快递包裹】
    // mutable_xxx()是protoc自动生成接口,获取proto里面可修改的子对象
    proto->mutable_button_status()->set_value(sdkdata.button_status.value()); 
}

填充完成之后,调用框架原有序列化、发送逻辑即可:

复制代码
std::string bin = proto.SerializeAsString();
send_network_data(bin);

报文发送完毕,proto 局部对象销毁;业务账本tech_maintain_status_仍然保留设备最新状态。

交付验证与问题排查

  1. 对外接口说明 复用已有TechMaintainStatus通信通道; 字段语义:true按钮按下,false按钮松开;调用has_button_status()判断本条报文是否携带按钮状态数据。

  2. 调试日志埋点(仅开发调试使用,正式提交删除)

  • 日志 1:SystemManager 成功发出事件
  • 日志 2:handler 收到事件,业务账本写入数据
  • 日志 3:数据从账本拷贝到通信 SDK 结构体
  • 日志 4:数据填充进 protobuf 报文

四条日志全部打印,代表整条数据流完整跑通。

对象生命周期对照表

对象 对应代码变量名 生命周期
事件邮件 CoreEvent CoreEvent ev 投递处理完直接销毁,不存状态
业务账本(内部笔记本) tech_maintain_status_ 类成员变量,常驻内存,设备运行一直存在
SDK 草稿纸 nx::sdk::TechMaintainStatus 每次上报临时生成拷贝
Protobuf 快递包裹 proto 局部对象 发送完毕函数结束就销毁

整体工作知识点总结

1. 属于什么工作

属于嵌入式 Linux 医疗设备固件开发,做设备内部业务状态跨进程上报、上层业务到网络协议的数据适配开发;是设备业务与外部上位机通信链路的新增业务字段开发。

2. 使用到的技术原理 & C++ 知识点

  1. 跨进程事件驱动架构(事件桥 CoreEvent):不同进程之间不能直接访问对方内部变量,通过消息完成通信,实现组件解耦。事件只是 "状态发生变化" 的通知信号,事件本身不长期保存业务数据。
  2. 分层设计、关注点分离思想
    • 业务账本层:只关心设备当前真实业务状态;
    • SDK 中间过渡层:隔离内部业务逻辑与外部网络协议;
    • protobuf 协议层:只负责网络传输二进制格式。 每一层职责单一,修改其中一层尽量不影响其他层。
  3. C++17 std::optional:可以表达 "值不存在" 语义,区分「状态从未更新」和「布尔值 false」两种完全不同场景。
  4. std::any / std::any_cast :事件载荷通用容器,可以存放任意类型数据,接收端做安全类型提取。
  5. std::bind + std::placeholders:将类的成员函数包装成回调,绑定 this 指针,注册到事件路由表。
  6. Protobuf 协议 :IDL 接口描述语言,编译自动生成 C++ 访问接口;mutable_xxx()获取可修改子对象,has_xxx()判断字段是否存在;协议字段编号属于网络契约,旧编号不能随意修改。
  7. 逐层数据拷贝流转:每层使用独立结构体对象,逐层赋值搬运,避免业务逻辑和网络逻辑耦合。

3. 开发踩坑经验

  1. 编译通过 ≠ 运行正常:事件 handler 漏注册不会编译报错,只会运行静默丢消息;
  2. 分层拷贝任何一层遗漏赋值,都会造成下游报文字段丢失;
  3. proto 不要最先写,要按照数据流顺序,先业务层、中间层,最后再定义协议,防止逻辑顺序颠倒;
  4. BoolValue和普通 bool 不一样:普通 bool 无法区分 "没有上报" 和 "主动 false",对外协议必须使用包装类型;
  5. ❌禁止:收到事件 payload 直接组装 proto 发送。事件消息是瞬时一次性的,网络丢包、定时重传时没有地方获取设备当前状态;
  6. ✅正确:事件只做通知,真实业务状态必须保存在常驻内存的业务账本。

4. 一句话概括整个业务

设备采集主机面板按钮状态,通过事件桥跨进程传递变更通知;接收组件将状态保存至本地业务账本,上报时分层拷贝中转,最终转换成 protobuf 网络报文,上报给外部中心站。

相关推荐
洋不写bug1 小时前
二叉树(二) 常见基础操作解析|结点数、树高、查找结点、判断完全二叉树
java·开发语言·数据结构·完全二叉树·二叉树结点数·树高·查找结点
平头哥技术团队2 小时前
Day 16 | 用 ul、ol、li 一个标签,把散资料收成有序的技能清单页
开发语言·前端·html·html5
是个西兰花2 小时前
网络基础1
linux·网络·c++·智能路由器
统计学小王子2 小时前
数学建模国赛倒计时 2 天 ——《统计模型精讲(广义线性模型 R 语言实例 + 分析)》
开发语言·数学建模·r语言
此冬歌咏2 小时前
自动化服务器运维监控系统(python+shell)
linux·运维·服务器·开发语言·python·自动化
老赵的博客2 小时前
c++QT之动态库加载常见报错
c++·qt
wabs6662 小时前
关于二叉树【429.N叉树的层序遍历的思考】
数据结构·c++·算法·leetcode·二叉树
Cx330❀2 小时前
【Linux网络】深入TCP协议:从滑动窗口到拥塞控制与性能优化全景解析
linux·开发语言·网络·tcp/ip·ai·性能优化·ai编程
hetao17338372 小时前
2026-09-08 hetao1733837 的刷题记录
c++·算法