消息队列使用梳理

BCU 工程消息队列使用梳理

工程中仅使用 1 条 POSIX 消息队列 /calibration_queue,用于将 HMI 标定指令和 EMS 控制指令跨进程传递给 ASW(Simulink 模型进程)。


一、全局架构

┌──────────┐  RS485    ┌──────────┐  mq_send()   ┌──────────────┐  mq_receive()  ┌──────────┐
│   HMI    │──────────►│  rtu.c   │─────────────►│ /calibration │──────────────►│ ASW.cpp  │
│ (触摸屏) │           │ (生产者) │              │   _queue     │               │ (消费者) │
└──────────┘           └──────────┘              └──────────────┘               └──────────┘
                              │                                                        │
                              │  mq_send()                                             ▼
                        ┌─────┴────────┐                               Simulink 输入结构体
                        │iec104_adapter│                              (SOXInputBus / SSM_inputSig)
                        │  (生产者2)   │
                        └──────────────┘
                             ▲
                      IEC 60870-5-104
                             │
                          EMS 调度

两个生产者,一个消费者,一个消息队列。


二、队列定义

名称与属性(global.h:38-69

#define CALIBRATION_MQ_NAME  "/calibration_queue"
#define MQ_SIZE              20                   // 队列最大消息数

typedef struct {
    unsigned int point_id;       // 标定点位 ID
    int64_t      value;          // 标定值
    uint16_t     flag;           // 标定标志
    float        factor;         // 系数
    char         description[128]; // 描述
    char         uint[32];       // 单位
} calibration_notify_t;

全局句柄(global.c:63

mqd_t calibration_mq_fd = -1;   // 初始无效,mq_init() 后赋值

三、生命周期管理(rtu.c)

3.1 创建 — mq_init()rtu.c:3540-3558

void mq_init(void) {
    struct mq_attr attr;
    attr.mq_flags   = O_NONBLOCK;                    // 非阻塞发送
    attr.mq_maxmsg  = MQ_SIZE;                       // 最多 20 条
    attr.mq_msgsize = sizeof(calibration_notify_t);  // 每条固定大小
    attr.mq_curmsgs = 0;

    calibration_mq_fd = mq_open(CALIBRATION_MQ_NAME,
                                O_CREAT | O_RDWR | O_NONBLOCK, 0666, &attr);
}

由 rtu.c 在 main() 中调用。因为 rtu 进程启动最早,负责创建队列。

3.2 销毁 — 进程退出时(rtu.c:3942-3945

if (calibration_mq_fd != -1) {
    mq_close(calibration_mq_fd);       // 关闭描述符
    mq_unlink(CALIBRATION_MQ_NAME);    // 从内核删除队列
}

四、生产者 1 — rtu.c(HMI 标定触发)

位于 handle_modbus_write() 中(rtu.c:2796-2815)。当 HMI 通过串口2(Modbus RTU)写入标定点位时触发。

触发条件:HMI 修改了点位值且 point_id 在标定范围内(如 20660~20674,10590 等)。

if (calibration_mq_fd != -1) {
    calibration_notify_t notify = {0};
    notify.point_id = pt->point_id;       // 标定点位 ID
    notify.value    = pt->current_value;  // HMI 设置的新值
    notify.flag     = 1;
    notify.factor   = pt->factor;
    strncpy(notify.description, pt->description, ...);
    strncpy(notify.uint, pt->uint, ...);

    mq_send(calibration_mq_fd, (const char*)&notify, sizeof(notify), 0);
}

发送模式:非阻塞(O_NONBLOCK),队列满时立即返回失败。


五、生产者 2 — iec104_adapter.c(EMS 调度指令)

位于 IEC 60870-5-104 从站回调中(iec104_adapter.c:421-433)。当 EMS 主站通过 104 协议下发充放电指令时触发。

extern mqd_t calibration_mq_fd;   // 引用 rtu.c 中的全局句柄

// 收到 EMS 的充放电指令(IOA=10590)
if (calibration_mq_fd != (mqd_t)-1) {
    calibration_notify_t notify = {0};
    notify.point_id = 10590;
    notify.value    = value;         // EMS 指令值
    notify.flag     = 1;
    notify.factor   = 1.0f;
    strcpy(notify.description, "EMS充放电指令");

    mq_send(calibration_mq_fd, (const char*)&notify, sizeof(notify), 0);
}

两个生产者共用同一个 calibration_mq_fd 句柄和同一个队列,通过 point_id 区分来源。


六、消费者 — ASW.cpp(Simulink 模型进程)

6.1 打开队列(ASW.cpp:200

// 只读 + 阻塞模式(等待消息时挂起线程,不占 CPU)
g_calib_mq_fd = mq_open(CALIBRATION_MQ_NAME, O_RDONLY);

注意:ASW 是只读打开O_RDONLY),不创建队列(无 O_CREAT)。
这意味着 rtu 必须先启动创建队列,ASW 后启动才能成功打开。

6.2 阻塞接收 + 分拣(ASW.cpp:198-306

static void* calibration_receiver_thread(void* arg) {
    g_calib_mq_fd = mq_open(CALIBRATION_MQ_NAME, O_RDONLY);

    calibration_notify_t msg;
    while (g_calib_running.load()) {
        ssize_t n = mq_receive(g_calib_mq_fd, (char*)&msg, sizeof(msg), nullptr);
        // 阻塞等待,有消息才返回

        switch (msg.point_id) {
            // ── SOX 标定 ──
            case 20668: SOXInputBus_str.SOCCalValue = msg.value;
                        SOXInputBus_str.SOCCalFlg = 1;    break;
            case 20669: SOXInputBus_str.SOHCalValue = msg.value;
                        SOXInputBus_str.SOHCalFlg = 1;    break;
            case 20671: /* 累计充电安时标定 */              break;
            case 20672: /* 累计放电安时标定 */              break;
            case 20673: /* 累计充电能量标定 */              break;
            case 20674: /* 累计放电能量标定 */              break;

            // ── SSM 标定(继电器强制控制)──
            case 20660: /* 强制负极吸合 */                  break;
            case 20661: /* 强制正极吸合 */                  break;
            case 20662: /* 强制预充吸合 */                  break;
            case 20663: /* 强制断路器吸合 */                break;
            case 20664: /* 系统模式控制 */                  break;
            case 20665: /* 一键合闸分闸 */                  break;

            // ── EMS 控制指令 ──
            case 10590: SSM_inputSig_str.EMSCmd = msg.value;
                        SSM_inputSig_str.EMSCmdFlg = 1;  break;
            default: /* 未处理的 point_id */               break;
        }
    }
    mq_close(g_calib_mq_fd);
}

接收模式阻塞(默认),无消息时线程挂起,不消耗 CPU。


七、两种打开模式对比

rtu.c(生产者)ASW.cpp(消费者)
打开方式O_CREAT | O_RDWR | O_NONBLOCKO_RDONLY
发送/接收mq_send 非阻塞mq_receive 阻塞
队列满/空满时立即返回 -1空时挂起线程,有消息才唤醒
角色创建者 + 写入者只读消费者

八、完整生命周期

rtu 进程启动
 │
 ├─ mq_init()
 │    └─ mq_open(O_CREAT)    ← 创建 /calibration_queue
 │
 ├─ 运行时:
 │    │
 │    ├─ HMI 写入标定点位 → mq_send()
 │    │
 │    └─ [iec104_adapter] EMS 指令 → mq_send()
 │
 └─ 退出时:
      └─ mq_close() + mq_unlink()  ← 删除队列

────────────────────────────────────────────

ASW 进程启动(rtu 之后)
 │
 ├─ pthread_create(calibration_receiver_thread)
 │    └─ mq_open(O_RDONLY)   ← 打开已有队列
 │    └─ while: mq_receive() (阻塞) → 分拣赋值
 │
 └─ 退出时:
      └─ mq_close()

九、涉及的 API 汇总

API调用位置功能
mq_openrtu.c (创建) / ASW.cpp (打开)创建或打开消息队列
mq_sendrtu.c + iec104_adapter.c发送标定通知
mq_receiveASW.cpp阻塞接收标定通知
mq_closertu.c + ASW.cpp关闭队列描述符
mq_unlinkrtu.c从内核删除队列
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值