K4-05 模式状态机的完备性核对与可验证性设计
课程代码 K4-05 · 板块 K 控制、软件与标定 / 嵌入式软件与开发 时长 约 3.5~4.0 小时(5 讲) 前置 K3-01 整车热管理控制策略总览与模式管理;K4-02 基于模型开发(MBD):Simulink 建模与代码生成;B3-04 热泵型整车热管理架构(多通阀一体化方案);G8-06 冷却液侧集成模块(CIM):阀岛/泵/膨胀壶一体化架构 本讲义定位 讲师授课蓝本 / 学员自学讲义,是 K4-05 大纲的完整展开版
按任务导航
| 你手上的事 | 直接看 | 还要配 |
|---|---|---|
| 评审一台已建好的模式状态机,判「核过了没有」 | 第 1 讲:逐格填表算完备率、同状态出边两两不交叠 | 第 3 讲(到不到得了)· 第 5 讲(三份证据怎么接进出口判据) |
| 把架构侧的模式表与阀位真值表翻成软件层 guard | 第 2 讲:三个落点各给判据,另有一个窗口型落点 | 第 4 讲(寻零期那张合法阀位子集表)· 第 1 讲(阈值必带伴生项) |
| 给一个过渡态定超时时长与超时之后去哪 | 第 3 讲:夹逼结构与三选一去向,逐个过渡态显式选定 | 第 1 讲(过渡态必须各占一行,否则超时无处可挂) |
| 写上电初模式与异常复位恢复路径的规则表 | 第 4 讲:判定规则表的八列输入、信任存值的与条件 | 第 2 讲(窗口型不变式)· 第 3 讲(等回执的过渡态) |
| 签一份软件验证出口判据,判这句「通过」管到哪 | 第 5 讲:三类证据的分工、分母口径与结论三态 | 第 3 讲(分母要先剔不可达)· 第 1 讲(完备率是门禁) |
⛔ 本课不覆盖:模式集怎么收全、迟滞带与最小驻留怎么取 → K3-01 整车热管理控制策略总览与模式管理;模式表怎么反推 → B3-04 热泵型整车热管理架构(多通阀一体化方案);阀位组合真值表怎么穷举 → G8-06 冷却液侧集成模块(CIM):阀岛/泵/膨胀壶一体化架构;回路铁律的物理机理 → B3-01 冷却液回路拓扑:串/并联、多回路耦合与模式切换;切换的执行时序(水侧三段式与断流窗口 → K3-09 冷却液回路模式切换的执行时序:泵—阀联动、断流窗口与切换序列表,冷媒侧三段式与换向压力冲击 → K3-04 热泵与多通阀协调控制);上下电次序、就绪时间与休眠编排 → K3-11 热管理域的上下电时序、run-on 编排与休眠期能耗预算;非易失载体选型与掉电写一致性 → K4-06 控制器非易失存储设计:学习值、诊断数据与累计量的持久化;执行器寻零算法本体 → G4-01 电子膨胀阀(EXV)结构、控制与选型 K1-02 执行器驱动:电子阀/泵/风扇/压缩机 PWM C2-03 风门驱动电机与执行器(步进/有刷/PWM)选型 G8-05 集成模块的装配、检漏与售后可维护性;断电落点与失效安全档 → G12-03 执行器硬件接口与电气规格;降级等级、重试限次取值与故障码 → K5-02 热管理故障处理与降级策略;建模、代码生成与判定/条件/MC-DC → K4-02 基于模型开发(MBD):Simulink 建模与代码生成;在环阶梯、故障注入与需求覆盖率 → K4-03 MIL/SIL/HIL 验证流程;策略层二维表体检与切换质量验收页 → K3-13 模式表/场景表与仲裁规则的静态体检:可达性、死锁与切换质量;通用软件测试理论与模型检验的算法原理 → 本课自设禁区,不讲。以及任何一个具体的时长、门限与比例——迟滞带、最小驻留、去抖时间、过渡态超时、覆盖率出口目标、抖动率上限、重试限次与退避间隔、断电时长门槛、上电就绪时间与峰值电流预算——由本项目自己的整定与验证策略、以及所引体系文件的现行版本给出,⛔ 本课一个数都不给(唯一给出的那个数是完备率的判据值 100%,它由定义推出、不是经验值)。
引言:这一类病不会让任何一条用例失败
评审会上最常听见的一句结论是「状态机已经核过了」,而几乎没有人追问它指的是哪一件事核过了。它可能指有人把建模环境里那张图从头翻了一遍;可能指跑过一轮工具的自动检查、没报错;也可能指所有功能用例都通过了。这三种「核过了」查到的东西互不重叠,而它们写进会议纪要之后长得一模一样。⇒ 本课要做的第一件事,是把这句话拆成三份可以签字的东西。
本课的输入是三样已经存在的产物:模式集与仲裁优先级(归 K3-01 整车热管理控制策略总览与模式管理)、架构侧的模式表(归 B3-04 热泵型整车热管理架构(多通阀一体化方案))与阀位组合真值表(归 G8-06 冷却液侧集成模块(CIM):阀岛/泵/膨胀壶一体化架构)、以及一台已经在建模环境里画出来的模式状态机(K4-02 基于模型开发(MBD):Simulink 建模与代码生成 的产物)。输出固定为三份报告:
- 完备性核对报告——完备率、二义性清单与每一处已消解方式;
- 静态核对报告——不可达状态与死迁移清单及处置、等待图无环证明或破除方案、性质证明的三态记录;
- 覆盖准则声明——本项目取哪一档、分母口径与剔除清单、豁免论证。
三份报告分别建立三种判断力:一张表完不完备、一张图到不到得了、会不会锁死、一句「通过」管到哪儿为止。⇒ 本课全程只判三件事:这一格填了没有、填得矛不矛盾、到不到得了。⛔ 它不判「这一格填得对不对」——那是功能逻辑,归 K3-01 整车热管理控制策略总览与模式管理/K3-04 热泵与多通阀协调控制。这条分界要先立住:立不住,每一次评审都会滑进「这个模式该不该这么走」的争论,而那恰恰是本课查不了、也不该查的那一层。
钉子①:结构缺陷不产生失败,它产生沉默
状态机的病不长在功能逻辑里,长在结构上;而结构缺陷的共同现场表现是:不报错。
四条逐条对照:
- 迁移表里的一格空着——这个事件在这个状态下不匹配任何分支,被静默丢弃 ⇒ 现场是「按了没反应、也没有故障码」,既不进故障管理,也不留任何痕迹;
- 陷阱态(进得去出不来)——它每一拍都在正常地等一个完成事件,而那个事件已经不会来了;
- 优先序绑在绘图顺序上——重排一次图,行为就翻转,而功能代码一行都没改,任何 diff 都看不出来;
- 死锁——两个主体都在按自己写下的规则正常等待,各自的逻辑单独看全对。
四条的末端是同一个词:沉默。而沉默不会以「某条用例失败」的形式出现在任何一份测试报告里——报告上那一栏是空的,不是红的。
⇒ 三条推论,本课的整个做法都由它们决定:
- 本课全部方法都在跑之前做、在表上和图上做,⛔ 不是靠跑用例找出来的。动态测试为什么在原理上够不到这一层,第 5 讲给判据。
- 「结构」这个词在本课有确切所指——行头、列头、格、边、优先序、超时、初值,一共七样。⛔ 它不是「代码写得整不整齐」。
- 所以本课每一条判据都刻意写成可机检的形式:完备率是一个数、互斥性是一条布尔恒等式、无环是一条图论性质。写成这个样子,是为了能交给工具去证,⛔ 不是为了在评审会上逐条争论。
钉子②:每一个「满分」的分母都是你自己填的
把分母填小,比值就好看,而填小分母的那个动作,看起来正是「把工作做完」。本课有三处同形:
| 那个「满分」 | 它的分母是什么 | 它够不到的那一层 |
|---|---|---|
| 完备率 100% | 行数 × 列数——两侧都是你自己收的 | 没进行头的那一整行、没进列头的那一整列 |
| 迁移覆盖率 100% | 只含已经写进表里的那些迁移 | 从没被写下来的那条规则 |
| 模型检验工具报回来的一句「过了」 | 你写下的那几条性质 | 性质写漏的、写得太弱的、被 stub 掉的那部分 |
⇒ 三条推论:
- 一份「通过」必须同时说清三件事:它是哪一类证据、分母口径是什么、它够不到的那一层由谁补。需求覆盖答「该测的测了没」,结构与迁移覆盖答「有没有从没跑到的地方」,静态核对答「有没有从没写下来的地方」——三者互不替代,缺任一类都不算过出口。
- 分母口径必须带版本号,并随表一起冻结。两份报告的分母版本不同,结论就不可比,也不能互相引用。
- 凡是把已判定的不可达项留在分母里的做法,都会逼团队去编凑分用例 ⇒ 分母的清洗是第 3 讲的输出,也是第 5 讲能不能报数的前提。
反钉子:把空格填满,完备率就到 100%,这张表就完备了
本课最容易被读反的一条:「把迁移表的空格填满,完备率就到 100%,这张表就完备了。」
它的前半截完全正确:完备率确实是把空格填满才涨上去的,它也确实该做成提交门禁。本课程体系把「完备率 = 100%」立成一条硬判据,理由是它由定义推出:既然每一格空着都是一处未定义行为,那么「没有未定义行为」这句话的数学形式就是分子等于分母。⛔ 它不是经验值、不是行业统计值,也⛔ 不是任何标准里的条款。正因为前半截对、而且完备率是本课交出的第一个数,读者极容易把「把这个数做到 100%」当成第 1 讲的全部任务。
为什么它是反的:完备率=已写下确定去向的格数 ÷(行数 × 列数)——本课把它记作 C_table = N_filled /(N_state × N_event)——分子是你填的,分母也是你填的。填格只动分子,而决定这张表管不管用的是分母:行头(状态集)与列头(事件集)收全了没有。一张漏了「寻零中」这一行、漏了「执行器完成回执」这一列的表,把剩下的格全部填满,算出来是一个货真价实的 100%,而它对漏掉的那一整行、那一整列一个字都没定义。
读者会做错的那个具体动作:拿到一张现成的迁移表(多半是从上一代平台或架构侧的模式表转过来的),直接开始逐格补空;补完算出 100%,把它当绿灯交上去,并据此判定「完备性这一项已经过了,可以进下一道工序」。⛔ 他不会回头去问「行头列头是谁给的、收全了没有」——因为那个数已经满分了。⇒ 后果不是「差一点」,是他刚刚亲手把「还没查」变成了「已经查过」:后面每一道工序都会信任这个结论,可达性分析在缺行的图上做、迁移覆盖率在缺行的分母上算、模型检验对不存在的那一行无从报告。★ 这正是假的好消息比坏消息更不容易被质疑的地方——60% 会让人继续查,100% 让人停手。
同一个形状在本课还会以三种面貌再出现,各自在它该被点破的地方点破:一条 else 兜底被当成「这一行不会有未定义行为」(第 1 讲)· 迁移覆盖率 100% 被当成「状态机测过了」(第 5 讲)· 工具报「无不可达状态」被当成「状态机对了」,以及把「无定论/超时」读成「通过」(第 5 讲)。
⚠ 本课另有一个方向不同的做反动作,成因与上面这条无关,单独落在第 4 讲:热态复位后立刻把所有执行器强制归零,可能比不归零更危险。
交之前,先花五分钟做这一件事
拿你手上那张迁移表,先别补空格,按下面三步走一遍:
- 重数行头——五类逐类点名:热功能模式 × 车辆使用态、三个端点态、非用户模式、过渡态、降级与安全态。后两类是本课补的,它们在架构侧那张稳态模式表上本来就不占行,照抄模式表当行头必然整类漏掉。
- 重数列头——七类逐类点名,且粒度先冻结再数:同一物理量、同一方向、同一处置的门限合成一个事件,处置不同或来源不同即分列。逐类怎么点名、冻结规则怎么用,第 1 讲逐条给。
- 重算一次比值,与你上次报出去的那个数并排放着看。
这一步的读法:比值掉下去的那一部分,就是你上次报的那个 100% 里从来没有被定义过的东西——分子一格没少,只是分母终于算对了。⇒ 判出来之后的下一个动作是:先把行头列头冻结、写上版本号,再逐格填。⛔ 反过来做(先填格、后补行),每补一行都要把整张表的比值与可达集作废重算,而重算这件事一旦被跳过,你手上就同时有两个版本的分母。
什么时候这条结论不成立:如果行头列头已经冻结并带版本号,且冻结之后没有人改过表,那么补空格确实就是这一项的全部工作——完备率这条判据的有效期,正是从行列冻结那一刻起,到下一次改表为止。⇒ 所以「表的版本号」不是文档规矩,它是这个数成不成立的前提。
第 1 讲 迁移表:把「状态机做完了没有」换成一张能逐格核的表
现场对「这台状态机做完了没有」最常见的争执,不是「逻辑对不对」,而是「你说的做完了,指的是哪一件事做完了」。同一句「状态机已经核过了」,可能指有人把图从头到尾看了一遍,可能指跑过一轮模型检验,也可能指用例全绿。三件事互不覆盖,而它们在会议纪要上长得一模一样。
这一讲把这句话拆成可交付的东西:先把状态机的完备性从「图上看着没漏」换成一张行=状态、列=事件的二维表,让它变成能逐格核、能算成一个数、能当提交门禁的东西;再把这张表上会长出来的两类结构缺陷——格上的空白与边上的二义——各自配一条可机检的判据。后面四讲都挂在这张表上:第 2 讲往格里填 guard,第 3 讲在这张表生成的图上查可达性与死锁,第 4 讲补上电那几行,第 5 讲用它当覆盖率的分母。
本讲先钉住几个记号,全课统一、后面各讲照用:
| 记号 | 指什么 | 单位 |
|---|---|---|
| N_state | 迁移表的行数=状态集元素个数 | 个 |
| N_event | 迁移表的列数=事件集元素个数 | 个 |
| N_filled | 已写下确定去向的格数 | 个 |
| C_table | 迁移表完备率=N_filled /(N_state × N_event) | 无量纲,以百分数表述 |
| g_i | 同一状态第 i 条出边的守卫条件(布尔式) | 布尔(无量纲) |
| prio(i) | 同一状态第 i 条出边的优先序序号 | 无量纲(序数) |
| s / e | 状态序号(第 s 行)/事件序号(第 e 列) | 无量纲(序数) |
| ΔT_hys / t_dwell_min | 迟滞带/最小驻留时间(本讲只查它在不在,⛔ 不参与任何数值运算) | K / s |
⚠ i 与 j 在本课专门用来数同一状态的出边,只出现在 g_i ∧ g_j 与 prio(i) 里;数状态用 s、数事件用 e、数任何「个数」一律写成 N_〈被数的东西〉。⛔ 全课不写不带下标的 g——同一状态有多条出边是常态,不带下标就没法把互斥判据写出来。
1.1 本课的坐标系:三样输入,三份可签署的报告
本课的输入是三样,全部是别人已经交出来的东西:
- 模式集与仲裁优先级——K3-01 整车热管理控制策略总览与模式管理 交付;
- 架构侧的两张表——B3-04 热泵型整车热管理架构(多通阀一体化方案) 的模式表、G8-06 冷却液侧集成模块(CIM):阀岛/泵/膨胀壶一体化架构 的阀位组合真值表;
- 一台已经在建模环境里画出来的模式状态机——K4-02 基于模型开发(MBD):Simulink 建模与代码生成 的产物。
⇒ 本课不建模式集、不建模型,本课做的是给这三样做一次静态体检。
本课的输出是三份可签署的报告,它们各自回答一个不同的问题:
| 报告 | 它回答哪一个问题 | 里面必须有什么 | 在哪一讲交 |
|---|---|---|---|
| 完备性核对报告 | 这张表上,每一格都有定义了吗? | 完备率 C_table(带表的版本号)· 二义性清单与每条的已消解方式 | 第 1 讲(本讲) |
| 静态核对报告 | 写下来的这些,到得了、出得来吗? | 不可达状态与死迁移清单及逐条处置 · 等待图的无环证明或破除方案 · 性质证明的三态记录 | 第 3、5 讲 |
| 覆盖准则声明 | 我们据以宣布「测过了」的分母是什么? | 本项目取哪一档 · 分母口径与剔除清单 · 豁免项与论证 | 第 5 讲 |
为什么先立报告再讲方法。因为开头那句争执的根源就是没有靶子:三个人说的「核过了」各指一件事,谁也没说错,只是从来没人写下来要交什么。先把三份报告立起来,后面四讲每讲一条判据,都能明确落到某一份报告的某一行上;也才有资格接进 K4-03 MIL/SIL/HIL 验证流程 的用例矩阵与出口判据——那边要的是可签署的证据,⛔ 不是一句「已核对」。
本课的射程只有三问,一句话说完:这一格填了没有?填得矛不矛盾?到不到得了?
⛔ 本课不判「这一格填得对不对」——某个模式下该选哪条控制律、该走哪个阀位,那是功能逻辑,归 K3-01 整车热管理控制策略总览与模式管理/K3-04 热泵与多通阀协调控制。本课判的是结构:填了没有、填得矛不矛盾、到不到得了(第三问含「出不出得来」——第 3 讲把它拆成可达性与陷阱态两侧查)。这条边界要在动手之前就说清,否则完备性核对会变成一场功能评审,而结构缺陷仍然一条都没查。
易错点。① 把本课当成建模工具的操作教程去读——工具怎么点归工具供应商文档,本课交付的是判据;② 以为交出这三份报告就等于状态机「对了」——它们证明的是结构上没有未定义的地方,⛔ 不是「策略选对了」。
⚠ 本课全部的时间量、门限量与覆盖率目标值一律是平台相关量、本课不给数(每一处都会写明由谁给出、由什么夹出上下界);而三份报告的结构与它们各自回答的问题与平台无关,可以直接照用。
1.2 交付物先定形:格数=N_state × N_event,完备的判据是逐格有确定去向
完备的判据不是「箭头画全了」,而是「每个状态对每个事件都有确定去向」。
这两句话的差别不是措辞,是能力不对等:
- 图能表达「有哪些迁移」;
- 图表达不了「哪些组合没有迁移」——一张画得很干净的图,和一张漏了半列的图,长得一模一样。
⇒ 所以第一交付物必须是表,不是图。表的骨架:行=状态(第 s 行),列=事件(第 e 列),格数=N_state × N_event。每一格必须写下确定去向——迁到哪个状态,或者显式的「保持不动」。
空格是什么。空格在生成代码里就是「这个事件在这个状态下不匹配任何分支」,运行期表现是事件被静默丢弃。现场表现是那句最难归因的话:按了没反应,也没有故障码。它之所以最难归因,是因为它既不进故障管理、也不留任何痕迹——你手上唯一的信息是「用户说没反应」,而日志里什么都没有。
一个格里到底要写下什么。四样,缺任一样这一格都不算填好:
| 格里必须有 | 写成什么样 | 缺了会怎样 |
|---|---|---|
| ① 确定去向 | 迁到哪个状态,或显式「保持不动」 | 空格 ⇒ 事件被静默丢弃 |
| ② 守卫条件 g_i | 布尔表达式,⛔ 不是一句中文描述 | 判不了两条出边会不会同时成立 |
| ③ 优先序 prio(i) | 一个写在表里可读的序号 | 谁赢交给工具的内部次序(见 1.7) |
| ④ 若目标是过渡态:超时时长 t_to 与超时去向 | 两样都写在这一格里 | 超时逻辑落在故障管理那一侧,就不在这张表的分母里,也不会被静态核对看见(第 3 讲展开) |
「保持不动」有两种写法,行为不同,必须写明是哪一种。
- 消费并忽略——这个事件在本层被吃掉,上层与下一拍都收不到;
- 不消费——本层不动作,事件继续向上层或下一拍传递。
在层级/并行状态机里这两种的行为完全不同:把「消费并忽略」误写成「不消费」,事件会被外层的另一条迁移接走,现场表现是「子状态说不动、父状态却迁走了」;反过来则是外层永远收不到这个事件。而两种写法在表格上都可以只写「保持不动」四个字——不写清楚,落到代码里就是两种实现,且没有任何一道检查看得见这个差别。
⛔ 不许用「—」「N/A」「无」表示保持不动——这三种写法落盘之后与空格不可区分,等于把刚填的格又变回空的。
表的右侧还要另接三列,它们不算在 N_filled 里,但缺了这张表就核不完:else 兜底(每个状态的必填项,见 1.5)、二义性(本状态的出边两两是否恒不交叠,见 1.7)、表版本号(报完备率必须带它,见 1.6)。
易错点。① 把「评审时逐条看箭头」当成完备性核对——箭头是分子那一侧的东西,看再多遍也回答不了分母;② 以为工具的默认语义会兜底——不同层级、不同工具版本的默认语义并不一样,把行为交给默认值就是把行为交给一个没人写下来的约定。
⇒ 今天就能做的那个动作:把你手上那张迁移图里任选一个状态,横着数一遍事件列,看有几列你说不出它在这个状态下会发生什么。说不出的那几列就是空格,⛔ 不是「不会发生」。
1.3 行头(状态集)必须先收全再谈完备率,否则算出来是一个货真价实的假 100%
完备率的分母是 N_state × N_event。行头收不全,分母就小,比值就高。一张漏了一整行的表,把剩下的格全部填满,算出来是一个货真价实的 100%——而它对漏掉的那一整行一个字都没定义。
⇒ 所以顺序是死的:先收行头,再谈完备率。行头必须含下面五类,逐类点名、一类不许省:
| # | 这一行从哪儿来 | 包含什么 | 收全责任在谁 |
|---|---|---|---|
| ① | 热功能模式 × 车辆使用态构成的二维模式空间 | 由本项目的模式集给出 | K3-01 整车热管理控制策略总览与模式管理 |
| ② | 三个端点态 | Init / Shutdown / Sleep | K3-01 整车热管理控制策略总览与模式管理 |
| ③ | 非用户模式 | 产线下线检测(EOL)/售后服务/运输存放/诊断激励 | K3-01 整车热管理控制策略总览与模式管理 |
| ④ | ★ 过渡态(这一类是本课补的) | 阀在走行程/压缩机在升速/寻零在进行/跨域授权在等回执 | 本讲 |
| ⑤ | ★ 降级态与安全状态(这一类是本课补的) | 等级体系与各级进出条件归 K5-02 热管理故障处理与降级策略,去激励默认位归 I7-02 失效安全(Fail-safe)与冗余设计 | 本讲只要求它进同一张表 |
①②③ 的收全责任在 K3-01 整车热管理控制策略总览与模式管理,本讲只负责把它们摆进表的行头并逐格填。④⑤ 两类是本课归纳补上的行头分类,⛔ 不是本课程体系既有的分类——请在自己的报告里照此标注。
④ 为什么必须各自占一行。判据一句话:凡是有时长、且要等一个完成事件的动作,就是一个过渡态。过渡态不占行,第 3 讲的「超时时长与三选一去向」就无处可挂——一个不在表上的状态,不可能有哪一格写着它的超时去向,于是「过渡态没配超时」这类缺陷在表上永远不可见。⚠ 过渡态怎么命名、它在电源模式机里摆在哪一层,归 K3-11 热管理域的上下电时序、run-on 编排与休眠期能耗预算;本课要的只有一条结构要求:它必须是行头里的一行。
⑤ 为什么必须进同一张表、同一张图。因为「互相等待对方先降级」这类环恰恰只在降级路径上闭合——只体检正常路径会系统性漏检。I7-02 失效安全(Fail-safe)与冗余设计 已经把这条动作写死:把降级迁移画进同一张图再查一次环。两张表分开查,跨表的那个环两边都查不出来。
沿单一线索数行头,会整类漏掉什么。
- 只数「用户能感知的模式」——整类漏掉 ③④⑤;
- 直接照架构侧的模式表抄行头(最省事、也最常见的做法)——整类漏掉 ④。原因是架构侧那张表是稳态枚举,过渡态在它上面本来就不占行。
易错点。① 拿架构侧模式表当行头(上一条);② 把非用户模式当成「不是软件的事」而不进表;③ 先算完备率再补行头——补一行之后,C_table 与第 3 讲的可达状态集、可达迁移集全部作废重算,⇒ 报数必须带表的版本号,两份版本号不同的报告结论不可比。
⚠ 五类各有几行是平台相关量、本课不给数——它取决于本车的架构、执行器数与场景集。本讲给的是逐类点名的收全动作:照上面五行各数一遍,你就能算出自己那张表的行数。
1.4 列头(事件集)的粒度必须先冻结再算数
列头是分母的另一半,它比行头多一个麻烦:粒度。同一件事,写成一列还是五列,全凭习惯——而这个选择直接乘进格数。
先按来源分七类,逐类点名。前五类由本课程体系的既有口径给出,后两类是本课归纳补上的:
| # | 事件类 | 说明 |
|---|---|---|
| ① | 温度/压力门限越界 | — |
| ② | 用户或场景请求 | — |
| ③ | 故障置位与撤销 | 置位与撤销是两个方向,⛔ 不是一个 |
| ④ | 超时 | 它没有外部触发者,是状态机自己产生的 |
| ⑤ | 外部授权回执 | 只在含不可支配热源(发动机/增程器)的架构里出现;分类依据=K3-01 整车热管理控制策略总览与模式管理 的直辖/可请求/完全自治三段划分 |
| ⑥ | ★ 上下电与电源模式事件(本课补) | 点火状态跳变、唤醒源触发、休眠请求、刷写后首次启动 |
| ⑦ | ★ 执行器完成/失败回执(本课补) | 寻零完成、阀到位、压缩机达速、加热器允许通电 |
⑥ 漏掉的后果:第 4 讲整讲的入口全在这一类;漏掉它,上电路径在这张表上根本不存在,于是「上电走哪条路」这件事永远由代码里的一段初始化顺序隐式决定。
⑦ 漏掉的后果:它是过渡态唯一的正常出边。漏掉它,过渡态就只剩「超时」一条出边——等于把「正常完成」这条最常走的路径从来没有表达过。
沿「谁触发的」这条线索数事件,会整类漏掉 ④ 与 ⑦:④ 没有外部触发者;⑦ 在多数人的心智模型里是「输出」,不是事件源。
粒度的冻结规则(本课统一):同一物理量、同一方向、同一处置的门限合成一个事件;处置不同或来源不同即分列。
它要挡住的是两个方向相反的失真:
- 同一连续量的多个阈值拆成多个事件 ⇒ 表按乘法膨胀,逐格填变成不可能完成的工作量,于是团队开始留空格——表膨胀最终是以空格结算的;
- 不同来源的事件并成一个 ⇒ 丢掉「谁触发的」。同一个「需要制冷」,来自用户按键还是来自电池请求,处置完全不同,并成一列之后这个差别在表上消失。
⛔ 冻结之后再改列头,C_table 与可达集同样全部作废重算——和补行头是一样的代价。
易错点。① 边填格边改列头——每改一次,前面数出来的那个比值就作废;② 把「事件」与「信号」混为一谈——一个信号可以产生多个事件(越上界、回落到下界各一个),⛔ 不是一信号一列;③ 把 ⑦ 当成「查询执行器状态字」而不是事件——那会让状态机去轮询,而轮询与「等一个不会到来的事件」在卡住时的表现一模一样,都是安安静静地停在原地。
⇒ 今天就能做的那个动作:把你那张表的列头拿出来,逐列问一句「这一列的触发者是谁」。答得出来的落进 ①②③⑤,答「没有触发者」的是 ④,答「执行器」的是 ⑦;一列都归不进 ⑥⑦ 的表,多半就是漏了这两类。
1.5 else 兜底与逐格填写是两件事,⛔ 不得互相替代
else 兜底是每个状态的必填项。每个状态必须有一条「上述条件均不成立」的默认分支,且它的动作必须是安全侧的显式动作——进入某个已定义的保守状态、置一个标志、或者显式地保持并计数。
⛔ 不能是一条什么都没写的空分支。原因有两层:没有 else 的判定节点在生成代码里是一条落空的路径,条件都不成立时程序从判定节点直接掉出去,行为由工具的默认语义决定;而写成「什么都不做」看起来最安全,实际上它丢掉了「这次落空曾经发生过」这条信息——同样的落空发生一万次和发生一次,在系统里没有任何差别。⚠ else 的动作取什么是策略问题,归 K3-01 整车热管理控制策略总览与模式管理/K5-02 热管理故障处理与降级策略;本讲只要求它存在、且动作是写得出来的。
★ 而 else 不把该行的任何一格算作已填。这是本讲最容易被当成「优化」的一条:
- else 回答的是:这个状态的判定节点看到了这个事件、而列出的条件都不成立时怎么办;
- 逐格填写回答的是:这个事件在这个状态下有没有被看到。
两问的主语不同 ⇒ 一个状态可以既有 else、又有空格。那个根本没有进入该状态任何判定节点的事件,连 else 都碰不到——它在更外层就被丢弃了。
一个事件到达某个状态,只有三种归宿:
| 归宿 | 发生了什么 | 行为定义了吗 |
|---|---|---|
| 一 | 匹配某条出边 ⇒ 按 prio(i) 决出的那条迁移执行 | 已定义,可追溯 |
| 二 | 都不成立 ⇒ 落到 else,执行安全侧的显式动作 | 已定义,且这次落空被记录 |
| 三 | 根本没被看到 ⇒ 在更外层就被丢弃,连 else 都碰不到 | 未定义,不置码、不留痕 |
归宿三就是表上的空格。⇒ 用一条 else 替掉整行的逐格填写,在图上更干净、判定节点更少,而落盘之后整行的未定义行为原样留着。
易错点。① 把 C_table 的分子按「有 else 的行 = 整行已填」算——那会把完备率直接虚高一大截,而虚高的方向恰好是「不用再查了」;② 反过来,因为这一条就取消 else——两者都是必填项,⛔ 不是二选一。
⇒ 判据一句话:逐格看,⛔ 不是逐分支看。
1.6 完备率 C_table:判据是 100%,且这个数只判「有没有定义」
把前面几节合起来,就得到本课交付的第一个数:
C_table = N_filled /(N_state × N_event),完备判据 C_table = 100%。 其中 N_filled = 已写下确定去向的格数;显式写「保持不动」计入,空格不计入,一条 else 不能把整行算满。
为什么判据是 100% 而不是某个统计值。因为它由定义推出,⛔ 不是经验值、⛔ 不是行业统计值:既然每一格空着都是一处未定义行为,那么「没有未定义行为」这句话的数学形式就是分子等于分母。⚠ 它是本课程体系立的提交门禁,⛔ 不是任何标准的条款,也没有哪个编号的标准给过它——请在自己的报告里照此标注。
它的工程用途是把「这张表完不完备」变成一个可机检、可当提交门禁的数:评审据此直接打回,不必逐条争论。
⚠ 方向性:它只判「有没有定义」,⛔ 不判「定义得对不对」。填错一格照样 100%。正确性靠逐格条件表达式的评审与下游用例,⛔ 不靠这个比值。
★ 到这里必须把一句话掰开:「把迁移表的空格填满,完备率就到 100%,这张表就完备了。」
这句话的前半截完全正确——完备率确实是把空格填满才涨上去的,它也确实该做成提交门禁。正因为前半截对,后半截才特别容易跟着一起被接受。
反的是后半截。C_table = N_filled /(N_state × N_event),分子是你填的,分母也是你填的。填格只动分子,而决定这张表管不管用的是分母——行头与列头收全了没有。⇒ 一张漏了「寻零中」这一行、漏了「执行器完成回执」这一列的表,把剩下的格全部填满,算出来是一个货真价实的 100%,而它对漏掉的那一整行、那一整列一个字都没定义。
后果不是「差一点」。拿到一张现成的表就直接逐格补空、补完算出 100% 交上去的人,刚刚亲手把「还没查」变成了「已经查过」——后面所有工序都会信任这个结论:可达性分析在缺行的图上做、迁移覆盖率在缺行的分母上算、模型检验对不存在的行无从报告。★ 而且它比一个坏消息更难被拦住:60% 会让人继续查,100% 让人停手。
手算一遍——这一步不能省。这个数太容易被当成工具输出的一个指标,而亲手数一遍行头列头是唯一能让人意识到「分母是我自己填的」的动作。下面这个算例的所有数字都是教学假设,不对应任何平台:
设你从上一代平台转来一张迁移表,行头 6 行(Init/Shutdown/Sleep 三个端点态、座舱制冷、座舱制热、降级),列头 5 列(温度门限越界、用户请求、故障置位、故障撤销、超时)。逐格看下来有 3 个格是空的:
| 这一步 | N_state | N_event | 格数 | N_filled | C_table |
|---|---|---|---|---|---|
| 第一次算(照拿来的表) | 6 | 5 | 30 | 27 | 90.0% |
| 补一行「寻零中」之后再算(该行整行为空) | 7 | 5 | 35 | 27 | 77.1% |
第一次算完,你会做的动作是把那 3 个空格补上,得到 30 / 30 = 100%,交卷。这就是本课要拦住的那一刻——那 3 个格确实补对了,而「寻零中」这一整行第一次算的时候一个字都没定义,它却让比值看起来更好看。
⇒ 算例的全部意义在第二行:补一行,分子不变、分母涨、比值掉。(就算你先把 3 个空格填满再补这一行,也只有 30 / 35 ≈ 85.7%——同一个方向。)
(上表所有数字全部是教学假设,不对应任何平台。)⛔ 禁止照抄取用这个算例的规模,⛔ 也不得由它反推「热管理状态机的典型规模是多少」——规模是平台相关量,本课给的是怎么算出你自己那张表的规模:行数=上面五类逐类点名之和,列数=七类按冻结规则合并之后的条数。
⚠ 同一个形状在本课后面还会出现两次,⇒ 见到「一个满分」时先问一句「这个分母是谁给的」:迁移覆盖率的分母只含已经写进表里的迁移,从没被写下来的那条规则不会失败、它产生沉默;模型检验的分母是你写下的那几条性质,性质写漏、写得太弱、模型被替换掉的部分,它一律不报。两者的展开都在第 5 讲,这里只把形状先给你。
报这个数的两条纪律(两条都不满足就别报):① 行头与列头没冻结之前不报;② 报的时候带上表的版本号——版本号不同的两份报告,分母不是同一个东西,结论不可比。
易错点。① 方向性用反:拿 C_table = 100% 去论证「这张表填的内容是对的」;② 在行头列头未冻结前报这个数;③ 只算第一次不算「补一行之后」那一次——那一次才是算例的全部意义。
1.7 同一状态的两条出边:互斥、全序,还是绑在绘图顺序上
格上的病讲完了,接下来是边上的病。判据同样写成一条布尔恒等式:
∀ i ≠ j,g_i ∧ g_j ≡ 假;若不恒假,则必须存在显式且稳定的优先序,prio(i) ≠ prio(j)。
这是本课交给模型检验工具的第一条自定义性质(第 5 讲接手),也是它必须写成布尔恒等式而不是一句中文要求的原因——中文要求交不给求解器。⚠ 它同样是本课程体系立的判据,⛔ 不是某标准的条款。
为什么二义性和空格是同一类病。两条 guard 同时成立时,「谁赢」必须由某个东西决定。如果表里和模型里都没写,那么决定它的就是工具的内部次序——而那不是任何人设计过的东西。⇒ 二义性不是「偶尔会走错」,它是行为没有被定义,只是长在边上而不是格上。
同一个状态的两条出边,只可能是下面三态之一:
| 态 | 长什么样 | 行为定义了吗 | 可机检的强度 |
|---|---|---|---|
| A:恒不交叠 | g_i ∧ g_j ≡ 假 | 已定义 | 最强——可交给求解器逐对判定 |
| B:会交叠,但有显式全序 | prio(i) ≠ prio(j),写在表/模型里可读 | 已定义 | 中——只能验「序号互不相同」,验不了这个次序是不是设计者要的 |
| C:会交叠,靠绘制顺序 | 表里和模型里都没写,谁在前谁赢 | 未定义 | 无 |
★ 本课把「稳定」的定义钉死:稳定 = 换一个工具版本、重排一次迁移图,行为不变。
⛔ 不许把「稳定」理解成「每次跑结果一样」——绘制顺序也是确定的,每次跑结果都一样,而它一改图就翻转。态 C 的现场是:改一次图或换一次工具版本,优先序翻转,而功能代码一行都没改——任何 diff 都看不出来,任何回归用例只要没恰好覆盖那个冲突组合也发现不了。这正是本课那根钉子的典型实例:结构缺陷不产生失败,它产生沉默。
易错点。① 只检查「看起来会冲突」的那几对——两两全查是可机判的,没有理由抽查;② 用「实际不会同时发生」当理由跳过——那是对输入的假设,而假设必须写下来变成 guard 的一部分,否则它不受任何检查保护;③ 两条出边同优先级并列——那等于把结果交给到达次序,而到达次序不受设计控制;④ 只在有冲突的地方定优先序——今天不冲突的两条边,明天改一次 guard 就可能冲突 ⇒ 优先序应当在建表时就全序给定。
1.8 二义性消解只有三种合法写法,选了哪一种必须写下来
判出交叠之后,合法的消解写法只有三种:
| 写法 | 可读性 | 可机检性 | 引入新状态? | 主要后遗症 |
|---|---|---|---|---|
| ① 把 guard 改成互斥(给其中一条加互补条件) | 随条件变长而下降 | 最高(判据回到布尔恒等式恒假) | 否 | 条件越写越长、越难读;改一条可能在别的对上造出新交叠 |
| ② 显式定优先序 | 最好 | 只能验「序号互不相同」,验不了次序是否是设计者要的 | 否 | 次序的理由不写下来,下一个人不知道为什么是这个序 |
| ③ 合成一条出边,进入目标状态后在其内部再分流 | 图上最干净 | 中(复杂度搬进状态内部) | 是 | 新中间状态同样要进行头、逐格填、配超时 |
三种都合法,选哪一种是代价权衡,⛔ 不是对错题。但必须写明选了哪一种及理由——混着用还不留记录的后果很具体:下一个人看不出这里曾经有过冲突,改 guard 时把消解掉的冲突又打开,而那时它已经不在任何一份评审记录里了。
★ 只有 ③ 会引入新状态,⇒ 它的代价不止在这一处:新中间状态要占行头的一行、要逐格填、要配超时,并且会让 C_table 重算(分母涨)。这不是反对 ③,是提醒它的账要一次算完。
易错点。① 选了 ③ 却忘了给新中间状态补行——那等于用一处二义性换来一处空行;② 选了 ① 之后不重跑两两互斥检查——改一条 guard 可能在别的对上造出新的交叠。
⇒ 落成一行可以照抄的记录格式:〈状态〉的〈出边 i〉与〈出边 j〉曾交叠 | 选用写法〈①/②/③〉| 理由〈一句〉| 消解后已重跑两两互斥检查〈是/否〉。这一行进完备性核对报告的二义性清单。
1.9 本讲只做结构性检查,一个阈值也不定——唯一加的那条结构规则
本讲一个阈值也不定。迟滞带 ΔT_hys、最小驻留 t_dwell_min、去抖时间的取法与整定次序归 K3-01 整车热管理控制策略总览与模式管理 与 K1-01 热管理传感器信号处理与故障诊断,⛔ 本课不给数、⛔ 也不给取法。
本讲只加一条结构规则:
guard 里凡出现阈值,就必须有对应的迟滞或去抖伴生项;缺伴生项即打回。
为什么这条规则属于本课而不是整定课。因为它判的不是「取多少」,而是「有没有」——纯结构判据,能在表上机检。而缺伴生项的后果恰恰落在本课射程里:阈值附近的测量噪声会让同一条迁移在两个状态之间来回翻转,而每一次翻转在逻辑上都完全合法——它不是一处写错的逻辑,它是一条被写对了的迁移在噪声下的正常表现。它的账要到第 5 讲的切换质量验收上才结(切换次数 N_switch 在那一讲是验收量)。
⚠ 伴生项改变的是结构:把一条判定线换成一条带宽度的带、给状态加一段最小驻留——⛔ 不是把阈值挪一挪。带宽与位置由 K3-01 整车热管理控制策略总览与模式管理/K1-01 热管理传感器信号处理与故障诊断 给出,本课只查它在不在。
这条规则的下游用途:任意一对互为反向的迁移,若两条 guard 用的是同一个阈值而没有伴生项,它就是一个抖动候选——直接进第 5 讲切换质量的静态候选表。⇒ 这是一条在还没有任何日志的时候、光看表就能列出来的清单。
易错点。① 把这条规则读成「本课要求加多大的迟滞」——⛔ 不要求,只要求有;② 反过来,因为「本课不定阈值」就在 guard 里裸写阈值、连伴生项的位置都不留——那会把整定工作推到没有地方可以落的位置上。
1.10 本讲收口:完备性核对报告的交付规格与提交前自查
本讲交出的是三份报告里的第一份。它的规格是固定的,⛔ 与平台无关:
| 完备性核对报告必须含 | 判它合不合格 |
|---|---|
| 表的版本号 | 缺它,这份报告与下一份不可比 |
| 行头清单,逐类点名(五类) | 五类各写出本项目的条目,缺哪一类要写明为什么 |
| 列头清单 + 冻结规则的执行说明(七类) | 哪几个门限合成了一列、依据是什么 |
| C_table 与它的三个输入(N_state、N_event、N_filled) | 三个数都要能被第二个人独立数出来 |
| 二义性清单:每条写明状态、两条出边、选用的消解写法、理由、是否已重跑两两互斥检查 | 一条不留记录都不行 |
| 阈值伴生项检查结果:哪些 guard 里有阈值、各自的伴生项是什么、缺哪几处 | 判「有没有」,⛔ 不判「取多少」 |
提交前照着走一遍的自查(八条,全部只看表、不跑任何用例):
- 行头五类逐类点名过了吗?过渡态与降级态在不在行头里?
- 列头七类逐类点名过了吗?上下电事件与执行器完成/失败回执在不在列头里?
- 列头冻结了吗?冻结之后还改过吗?改过就重算。
- 每一格都有确定去向吗?「保持不动」是显式写的吗?写明了是「消费并忽略」还是「不消费」吗?
- 表里还有「—」「N/A」「无」这三种写法吗?有就等于空格。
- 每个状态都有 else 吗?它的动作是写得出来的安全侧动作吗?——并且,你没有拿它顶替任何一格吧?
- 每个状态的出边两两都判过 g_i ∧ g_j ≡ 假吗?不恒假的,prio(i) 是写下来的全序吗?
- guard 里出现阈值的地方,伴生的迟滞或去抖项都留了位置吗?
⇒ 八条全过,C_table 才有资格被报出来;报的时候连表的版本号一起报。第 2 讲开始往这些格子里填 guard——而填 guard 的前提,正是这张表的行头列头已经在这一讲里冻住了。
后面还有 4 讲正文 · 关键公式 · 案例拆解 · 常见误区 · 动手做