2个版本
0.1.1 | 2024年3月9日 |
---|---|
0.1.0 | 2024年1月6日 |
#182 in 嵌入式开发
52KB
445 行
kaori-hsm
kaori_hsm 状态机库
kaori_hsm 是一个用于在 Rust 中开发分层状态机 (HSM) 的库。该库的低内存占用和执行速度是其主要关注点,因为它旨在在资源有限的系统上运行,如微控制器。由于该库是硬件无关的,因此它可以在任何有 rust 编译器的系统上运行。该库的一些关键优点包括
- 不使用动态内存分配
- 快速执行,低栈和程序内存使用
- 不使用 rust 标准库,也不使用任何其他外部包
什么是分层状态机?
状态机是软件实体,根据其当前状态以不同方式处理事件。不同的输入事件可能导致状态机执行不同的操作,并触发到其他状态的重置。
分层状态机是具有嵌套状态的状态机。这意味着如果一个事件在某个状态中无法处理,它的父状态最终可以处理它。因此,HSM 特别适用于设计具有复杂行为的状态机。
为了理解状态机(特别是 HSM)的工作原理,我特别推荐观看 Miro Samek 制作的视频系列,您可以在 这里找到
如何使用库?
要构建自己的状态机,您首先必须定义将保存其数据的结构,然后您需要在它上面实现库的以下特性:TopState
特性和您想要定义的状态的 State<Tag>
特性的任何变体。
以下顺序必须遵循以构建一个可操作的状态机
- 创建一个将保存您的状态机数据的结构的实例。
- 使用 [
InitStateMachine::from()
] 函数将此结构的实例封装到 InitStateMachine 实例中。 - 通过调用此实例上的[
InitStateMachine::init()
]方法来初始化状态机。这将初始化状态机并将其引导到其第一个状态。此方法将返回一个StateMachine
实例。此类型代表一个完全运行的状态机,并仅公开用于向其中注入事件变体的[StateMachine::dispatch()
]方法。
项目中的示例
该库包含许多示例,展示了其潜力并帮助您了解如何使用它。其中大部分可以在没有任何特定硬件的情况下运行。您将在库的类型和函数定义中找到嵌入的小示例,这些定义构成了此库。这些示例主要关注那些类型和函数的使用场景。然后还有更复杂的示例,您可以在kaori_hsm/examples
目录中找到。这些示例容易操作,是构建您自己的状态机的好基础。在kaori_hsm/tests
目录中的集成测试也可以作为示例使用,但非常严格,包含大量特定于测试的代码。最后,您将在此存储库中找到一个旨在测试该库在stm32f103c8T6微控制器上性能的项目。性能测试对于该库的新手可能不容易理解,但它可能是最实用的示例。
一个介绍性的分层状态机示例
以下示例展示了使用kaori_hsm
库编写的假设状态机。此HSM模拟根据按钮状态的改变闪烁LED。当状态机启动时,LED关闭。在按下按钮时,LED开始闪烁。当释放按钮时,LED停止闪烁。此示例关联了一段测试代码。测试使用一个队列,HSM每次采取特定行动时都会在队列上发布一个特定的字符串。在初始化HSM或将事件分发给它之后,测试代码会检查队列上的字符串序列是否符合预期。
use std::sync::mpsc::{channel, Receiver, Sender, TryRecvError};
use kaori_hsm::*;
enum BlinkingEvent{
ButtonPressed,
ButtonReleased,
TimerTick,
}
struct BasicStateMachine{
sender: Sender<String>,
}
impl BasicStateMachine{
pub fn new(sender: Sender<String>) -> BasicStateMachine {
BasicStateMachine { sender }
}
// Post a string to the test queue
fn post_string(&self, s : &str){
self.sender.send(String::from(s)).unwrap();
}
}
impl TopState for BasicStateMachine{
type Evt = BlinkingEvent;
fn init(&mut self) -> InitResult<Self> {
self.post_string("Starting HSM");
init_transition!(BlinkingDisabled)
}
}
#[state(super_state= Top)]
impl State<BlinkingDisabled> for BasicStateMachine{
fn handle(&mut self, evt: & BlinkingEvent) -> HandleResult<Self> {
match evt{
BlinkingEvent::ButtonPressed => {
self.post_string("Button pressed");
transition!(BlinkingEnabled)
}
_ => ignored!()
}
}
}
#[state(super_state= Top)]
impl State<BlinkingEnabled> for BasicStateMachine{
fn entry(&mut self) {
self.post_string("Arm timer");
}
fn exit(&mut self) {
self.post_string("Disarm timer");
}
fn init(&mut self) -> InitResult<Self>{
init_transition!(LedOn)
}
fn handle(&mut self, evt: & BlinkingEvent) -> HandleResult<Self> {
match evt{
BlinkingEvent::ButtonReleased => {
self.post_string("Button released");
transition!(BlinkingDisabled)
}
_ => ignored!()
}
}
}
#[state(super_state= BlinkingEnabled)]
impl State<LedOn> for BasicStateMachine{
fn entry(&mut self) {
self.post_string("Led turned on");
}
fn exit(&mut self) {
self.post_string("Led turned off");
}
fn handle(&mut self, evt: & BlinkingEvent) -> HandleResult<Self> {
match evt{
BlinkingEvent::TimerTick =>{
self.post_string("Timer tick");
transition!(LedOff)
}
_ => ignored!()
}
}
}
#[state(super_state= BlinkingEnabled)]
impl State<LedOff> for BasicStateMachine{
fn handle(&mut self, evt: & BlinkingEvent) -> HandleResult<Self> {
match evt{
BlinkingEvent::TimerTick =>{
self.post_string("Timer tick");
transition!(LedOn)
}
_ => ignored!()
}
}
}
let (sender, mut receiver) = channel();
let basic_state_machine = BasicStateMachine::new(sender);
let ism = InitStateMachine::from(basic_state_machine);
// Execute the topmost initial transition of the state machine, leading to BlinkingDisabled
// state
let mut sm = ism.init();
assert_eq_sm_output(&receiver, &["Starting HSM"]);
// Event ButtonReleased is ignored in this state
sm.dispatch(&BlinkingEvent::ButtonReleased);
assert_eq_sm_output(&receiver, &[]);
sm.dispatch(&BlinkingEvent::ButtonPressed);
assert_eq_sm_output(&receiver, &["Button pressed", "Arm timer","Led turned on"]);
sm.dispatch(&BlinkingEvent::TimerTick);
assert_eq_sm_output(&receiver, &["Timer tick", "Led turned off"]);
sm.dispatch(&BlinkingEvent::TimerTick);
assert_eq_sm_output(&receiver, &["Timer tick", "Led turned on"]);
sm.dispatch(&BlinkingEvent::ButtonReleased);
assert_eq_sm_output(&receiver, &["Button released","Led turned off", "Disarm timer"]);
Cargo命令索引
要运行每个Cargo命令,当前目录必须是kaori_hsm/kaori_hsm
。
以发布模式构建lib
cargo build --release
运行文档测试
cargo test --doc
运行特定的集成测试
cargo test --test [test_name]
从examples
目录运行特定的示例
cargo run --example [example_name]
许可证
根据您的选择,许可如下
- Apache许可证第2版 (LICENSE-APACHE 或 https://apache.ac.cn/licenses/LICENSE-2.0)
- MIT许可证 (LICENSE-MIT 或 http://opensource.org/licenses/MIT)
。
许可证:MIT OR Apache-2.0
依赖关系
~275–720KB
~17K SLoC