基本概念


CSM模块 (CSM Module)

CSM 是 Communicable State Machine 的缩写,简单说就是”会通讯的状态机”。在 LabVIEW 里,CSM模块就是一个 vi 文件,本质上是个状态机,只是加上了模块间通讯的能力。

模块命名规则

模块名称要唯一,重名会导致Critical Error。除此之外还有几个特殊规则:

  • 空字符串(“”):自动用UUID命名,独立运行不出现在模块列表里
  • .开头(如.MainApp):系统级模块,默认不会被列出,适合后台模块
  • 中间有.(如System.Logger):用来标记逻辑关系,并不是真的子模块
  • #结尾(如Worker#):协作者模式节点
  • $加数字(如Handler$1):责任链模式节点

更多高级模式说明请看这里

模块接口

要使用一个CSM模块,你只需要知道三个接口:

  • 消息接口:能调用哪些消息、需要什么参数、会返回什么
  • 广播接口:能订阅哪些广播、参数是什么
  • 属性接口:有哪些属性、类型是什么

状态(State) 和 宏状态(Macro)

状态对应Case结构里的一个分支,是CSM的基本逻辑单元。与其他状态机不同,CSM用字符串驱动状态跳转,并定义了特定的语法规则。

宏状态是多个状态的组合,相当于一组固定的状态序列。CSM模板默认有两个宏状态:Macro: Initialize(启动时)和Macro: Exit(退出时)。

消息队列(Message Queue)

消息队列是CSM运行的核心机制。每个CSM模块维护两个队列:

  • 高优先级队列:接收同步消息(-@)和中断广播(<interrupt>),模块会优先处理此队列的内容。
  • 低优先级队列:接收异步消息(->->|)和信号广播(<status>),在高优先级队列为空时才处理。

此外,模块内部还有一个状态队列(State Queue),用于存放本地待执行的状态序列(如宏状态展开后的多个步骤)。状态队列中的状态由模块自己生产和消费,不通过跨模块队列传递。

每次循环,Parse State Queue++.vi 会先从状态队列取状态执行;状态队列为空时,再从消息队列(高优先级优先)中取消息。

消息(Message)

消息是CSM模块间1对1通讯的方式。消息本质上是符合特定语法的字符串,包含状态名、参数、消息类型等信息。例如模块A发送”API: DoSth”给模块B,B就会进入对应状态。CSM提供了辅助VI来生成消息字符串,无需手写。

消息格式

基本格式如下:

[消息名] >> [参数] [符号] [目标模块] // [注释]
  • 消息名:要调用的状态名
  • >>:分隔符
  • 参数:传递的数据
  • 符号:-@(同步)、->(异步有返回)、->|(异步无返回)
  • 目标模块:接收消息的模块,为空表示自己处理
  • 注释://后面的内容,不会被解析

同步消息(Synchronous Message)

同步消息-@)发出后,发送方会暂停状态变化,等待目标模块处理完毕并返回结果,才继续执行后续状态。

  • 返回结果在发送方的 Response 状态处理
  • 默认超时时间为 -2(使用全局设置),可通过 CSM - Set TMO of Sync-Reply.vi 统一调整
  • 目标为空字符串时产生 NO Target Error,目标不存在时产生 Target Error,超时时产生 Target Timeout Error

异步消息(Asynchronous Message)

异步消息->)发出后,发送方立即进入 Async Message Posted 状态继续执行,不等待目标模块的返回。目标模块处理完毕后,会将结果回传给发送方,由发送方在 Async Response 状态处理。

  • 目标为空字符串时产生 NO Target Error,目标不存在时产生 Target Error
  • 不受超时限制

异步无返回消息(Asynchronous No-Reply Message)

异步无返回消息->|)与异步消息类似,发出后立即继续执行,但目标模块处理完成后不会回传结果,发送方也不会进入 Async Response 状态。”发完就忘”的模式,适合不需要确认结果的通知场景。

特殊操作消息

CSM还定义了一些系统操作,格式是<操作类型>

// 发送广播"TCP Connected",参数为"192.168.1.100"
TCP Connected >> 192.168.1.100 -> <broadcast>

// 订阅TCPModule的广播,触发时调用UI模块的UpdateLED
TCP Connected@TCPModule >> UpdateLED@UI -><register>

响应(Response)

响应是被调用模块的返回字符串。例如A调用B的”API: DoSth”,B处理完后返回”Received”表示收到。模板中响应默认为空字符串。

  • 同步消息在”Response”状态处理响应
  • 异步消息在”Async Response”状态处理响应

系统预置状态

CSM 的预置状态以 JKISM 为基础扩展。JKISM 提供了状态机生命周期管理的基本状态,CSM 在此基础上追加了模块间通讯相关的状态。每个 CSM 模块都应包含这些状态的处理逻辑:

预置状态 来源 触发时机
Macro: Initialize JKISM 模块启动时进入,完成初始化后才开始接收外部消息
Macro: Exit JKISM 收到退出请求时进入,退出后不再接收外部消息
Error Handler JKISM 模块发生错误时自动进入,CSM 还会同步广播 Error Occurred 状态以便外部捕获
Response CSM 扩展 收到同步消息的返回值时自动进入
Async Response CSM 扩展 收到异步消息的返回值时自动进入
Async Message Posted CSM 扩展 发出异步消息后立即进入,用于跟踪已发送的异步请求
Target Timeout Error CSM 扩展 同步消息超时未响应时进入
Target Error CSM 扩展 目标模块不存在时进入
Critical Error CSM 扩展 框架级严重错误(如模块名重复)时进入,模块将停止运行

在同步/异步消息的 Response / Async Response 状态中,可以从 Additional Information 获取被调用方的错误信息,也可以通过 Source CSM 输出得知返回值来自哪个模块。

广播(Broadcast)

广播是CSM的1对多通讯方式,类似Pub/Sub模式。必须先订阅才能收到广播,目前只支持同一个LabVIEW应用内的模块。

信号广播(Status Broadcast)

信号广播(<status><broadcast>)是普通优先级的广播,通过低优先级队列传递,行为类似异步消息。模块需要显式调用才能发出:

ModuleStatus >> Arguments -> <status>

当模块中还有未处理的异步消息或其他信号广播时,信号广播会依次排队处理。

中断广播(Interrupt Broadcast)

中断广播(<interrupt>)是高优先级的广播,通过高优先级队列传递,行为类似同步消息,会被优先处理。模块同样需要显式调用:

UrgentEvent >> Arguments -> <interrupt>

当模块中存在其他低优先级的消息或信号广播时,中断广播会被优先处理。

状态广播(State Broadcast)

状态广播是 CSM 特有的隐式广播机制。CSM 模块的任意一个状态,都可以被外部模块订阅。当该状态执行完毕时,框架会自动将该状态的 Response 作为参数触发广播,无需在代码中显式发送。

状态广播出于效率考虑,只有存在订阅关系时才会发送。

注意:信号广播/中断广播名称不应与状态名重复,否则可能出现重复触发的情况。

广播格式和订阅

// 信号广播(默认)
ModuleStatus >> Arguments -> <status>

// 中断广播
UrgentEvent >> Arguments -> <interrupt>

// 订阅/取消订阅
Status@SourceModule >> API@TargetModule -><register>
Status@SourceModule >> API@TargetModule -><unregister>

// 订阅时改变优先级
Status@SourceModule >> API@TargetModule -><register as interrupt>
interrupt@SourceModule >> API@TargetModule -><register as status>

订阅可以用*匹配任意源模块。在本模块订阅时可以省略目标模块名。

订阅的生命周期

  • 指定了目标模块名:全局规则,模块退出后不会自动删除
  • 未指定目标模块名:内部规则,模块退出时自动删除

参数(Arguments) 和 属性(Attribute)

参数:消息、响应、广播都能携带的数据,都是字符串形式。

属性:模块的配置数据,提供直接读写的方法:

  • 外部可以通过模块名+属性名直接读写,不需要发消息
  • Worker模式和Chain模式的多个节点共享同一个属性空间
  • 属性有明确的数据类型(LabVIEW数据类型)
  • 常用于存配置参数、状态数据等

详细用法看参考文档

参数数据类型

由于参数只能是字符串,传递复杂数据时需要先编码、收到后再解码。CSM内置了三种编码方案:

  • HEXSTR:将任意LabVIEW数据序列化为十六进制字符串,格式为 <HEXSTR>十六进制字符串。适合传递簇、数组等复杂类型,但数据量大时字符串较长。
  • ERRSTR:专为LabVIEW错误簇设计,将错误码和描述格式化为可读字符串,格式为 <ERRSTR>[Error: 错误码] 描述
  • SAFESTR:将包含CSM关键字(如 ->;\r 等)的字符串中的特殊字符转义为 %Hex 格式,格式为 <SAFESTR>已转义字符串

通过 CSM - Argument Type.vi 可提取编码类型标记,再调用对应的解码VI还原数据。也可以通过插件(Addon)扩展自定义数据类型。

事件结构支持

含UI的CSM模块通常用事件结构(Event Structure)响应用户操作。事件结构会阻塞等待事件,但外部消息到来时需要立即跳出处理——CSM通过以下机制解决:

  • <New State Notifier Event>:CSM为每个含事件结构的模块提供一个用户事件句柄。当模块收到外部消息时,框架自动触发该事件,使事件结构立即中断等待,进入消息处理流程。
  • Timeout Selector.vi:辅助VI,当状态队列(Remaining States)非空时自动将超时设为 0,保证挂起的状态能被立刻处理,不会被事件结构的超时等待阻塞。
  • Events: Register / Events: Unregister:在 Macro: InitializeMacro: Exit 中注册/注销用户事件,管理事件句柄的生命周期。

对于用户操作逻辑非常复杂的场景(如鼠标拖动组合操作、高频事件过滤),可以使用 DQMH 风格的 CSM 模板,将事件结构循环独立放在一个专用的循环中,通过 CSM - Flood of Events Handler Side Loop.vi 模板实现。

概念小结

CSM的核心概念:

  • 模块、状态、消息、广播是基础构成
  • 消息队列驱动状态机运行,分高优先级(同步/中断)和低优先级(异步/信号)两级
  • 同步/异步两种通讯方式,同步支持全局超时控制
  • 预置状态(Response、Async Response、Error Handler 等)处理通讯生命周期
  • 订阅机制实现1对多通知
  • 参数和属性承载数据,参数通过 HEXSTR/ERRSTR/SAFESTR 编码支持复杂类型
  • 事件结构支持让含UI的模块能在等待用户操作的同时响应外部消息

掌握这些概念后即可开始使用CSM,具体用法请参阅其他章节。