2 个版本

0.1.1 2024年3月9日
0.1.0 2024年1月6日

#7#state-charts

每月 44 次下载
用于 kaori-hsm

MIT/Apache

17KB
94

kaori-hsm

kaori_hsm 状态机库

kaori_hsm 是一个用于在 Rust 中开发分层状态机 (HSM) 的库。该库的低内存占用和执行速度是其主要关注点,因为它旨在在资源有限的系统上运行,例如微控制器。由于它是硬件无关的,因此该库可以在任何存在 Rust 编译器的系统上运行。该库的一些关键优势包括

  • 不使用动态内存分配
  • 快速执行,低栈和程序内存使用
  • 不使用 Rust 标准库,也不使用任何其他外部 crate

什么是分层状态机?

状态机是软件实体,根据它们所处的状态以不同的方式处理事件。不同的输入事件可能导致状态机执行不同的操作,并触发到其他状态的转换。

分层状态机是可以有嵌套状态的状态机。这意味着如果一个事件在某个状态下无法处理,其父状态最终可以处理它。因此,HSMs 特别适用于设计具有复杂行为的状态机。

为了理解状态机和尤其是 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或将事件分发给它之后,测试代码检查队列上的字符串序列是否与预期相符。

intro_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命令索引

当前目录必须是kaori_hsm/kaori_hsm才能运行每个Cargo命令。

以发布模式构建lib

cargo build --release

运行文档测试

cargo test --doc

运行特定的集成测试

cargo test --test [test_name]

运行examples目录中的特定示例

cargo run --example [example_name]

许可证

许可方式如下:

由您选择。

许可证:MIT OR Apache-2.0

依赖项

~285–740KB
~18K SLoC