第三章 DMI软件架构与设计模式

各位好,我是老张。在轨道交通信号这行摸爬滚打了十几年,今天咱们聊聊DMI的软件架构。说实话,这部分内容看着抽象,但你要是真理解了,后面做开发会顺很多。

DMI不是个简单的显示设备。它要处理司机操作、接收ATP指令、管理界面状态,还得保证实时响应。我见过不少项目,前期架构没想清楚,后期改得想哭。所以这一章,咱们把地基打牢。

3.1 DMI软件分层架构

先说说分层。我个人习惯把DMI软件分成三层:应用层、中间层、驱动层。你想想看,这样分的好处是什么?

  • 应用层:负责业务逻辑。比如速度表怎么画、报警怎么显示、按钮怎么响应。这一层只关心“做什么”,不关心“怎么做”。
  • 中间层:负责数据转换和通信。把ATP发来的二进制报文,转成应用层能用的结构体。我管它叫“翻译官”。
  • 驱动层:直接跟硬件打交道。屏幕刷新、触摸检测、蜂鸣器控制。这一层最底层,也最容易出问题。

关键原则:上层不能直接调用下层。每一层只通过接口跟相邻层通信。这样做的好处是,换屏幕驱动不用改应用代码。

我在项目中遇到过一件事。有个同事把触摸检测的逻辑直接写在了应用层里。后来换了触摸芯片,整个应用层都得重写。嗯,这就是不分层的代价。

3.2 MVC模式在DMI中的应用

说到设计模式,MVC(Model-View-Controller)在DMI里特别实用。说白了,就是把数据、显示、控制分开。

组件 职责 DMI中的例子
Model(模型) 管理数据状态 当前速度值、目标距离、模式状态
View(视图) 负责界面显示 速度表盘、数字显示、图标
Controller(控制器) 处理用户输入 按钮点击、触摸滑动、旋钮操作

为什么会这样设计?我举个例子。司机按下“确认”按钮,Controller收到事件,通知Model更新状态(比如“确认报警”),Model变了,View自动刷新。你看,每个角色各司其职。

// 伪代码示例:MVC在DMI中的交互
class SpeedModel {
    int currentSpeed;
    void updateSpeed(int newSpeed) {
        currentSpeed = newSpeed;
        notifyView();  // 通知视图更新
    }
}

class SpeedView {
    void displaySpeed(int speed) {
        // 绘制速度表盘
        drawDial(speed);
    }
}

class SpeedController {
    void onSpeedChange(int rawData) {
        int speed = convertRawToSpeed(rawData);
        model.updateSpeed(speed);
    }
}

我的建议:刚开始用MVC时,别把Controller写得太重。我见过有人把业务逻辑全塞进Controller,结果Controller比Model还复杂。记住,Controller只是“传话筒”。

3.3 状态机设计

DMI的状态管理,是很多新手头疼的地方。为什么?因为DMI的状态太多了。待机、启动、正常运行、故障、紧急制动...每个状态下的界面和响应都不一样。

我建议用有限状态机(FSM)来管理。说白了,就是定义好“状态”、“事件”、“转移”这三个要素。

  • 状态:DMI当前处于什么模式。比如SB(待机)、FS(全监督)、OS(部分监督)等。
  • 事件:触发状态变化的条件。比如收到ATP的“紧急制动”指令、司机按下“缓解”按钮。
  • 转移:从一个状态到另一个状态的规则。比如从FS状态收到“紧急制动”事件,转移到EB(紧急制动)状态。

注意:状态机最怕“未定义转移”。我曾经在一个项目里,测试时发现DMI在某个特殊场景下卡死了。查了两天才发现,有个状态转移没定义,程序跑飞了。所以,一定要覆盖所有可能的转移路径。

// 状态机伪代码示例
enum DMIState {
    SB,   // 待机
    FS,   // 全监督
    OS,   // 部分监督
    EB,   // 紧急制动
    TR    // 故障
};

DMIState currentState = SB;

void handleEvent(Event event) {
    switch (currentState) {
        case SB:
            if (event == STARTUP) {
                currentState = FS;
                showMainScreen();
            }
            break;
        case FS:
            if (event == EMERGENCY_BRAKE) {
                currentState = EB;
                showEmergencyWarning();
                activateBrake();
            }
            break;
        // ... 其他状态处理
    }
}

嗯,这里要注意。状态机实现时,别用太多if-else嵌套。我习惯用状态转移表,清晰又好维护。你想想看,如果状态多了,if-else能把你绕晕。

避坑指南:我曾经犯过一个错,把状态机的“进入动作”和“持续动作”混在一起。比如进入EB状态时,我只触发了一次报警,但报警应该持续响。后来我改成:进入状态时执行一次动作,在状态内循环执行持续动作。这才对。

最后总结一下。DMI的软件架构,分层是基础,MVC是骨架,状态机是灵魂。这三样东西搞明白了,DMI开发就成功了一半。下一章咱们聊聊具体的界面布局设计,到时候见。