如何做 Perl 状态机(FSM)来解析比特流(字节序列)?

kat*_*sza 3 perl parsing byte command fsm

我目前正在使用 Perl 来解析来自 RS232 串行端口的传入命令序列。我尝试使用状态机,它的预期行为是:(1)从串口接收一系列字节;(2) 状态机使用字节作为输入,并跳转到适当的状态。

我想出了一个简化的演示Perl代码(贴在下面),但是遇到了一个问题: 当代码进入“while(1){}”时,就卡在这里,出不来。 因此,$din 字节序列分配被“while(1){}”阻止,并且对状态机不可见。因此,FSM 卡在“INIT”状态,根本不跳转。

我认为这应该是 Perl 编码中一个非常简单或入门级的练习,但是通过 Google 搜索并没有太大帮助。谁能帮我这个?先谢谢了~

...
my %next_state = (
        "INIT" => sub{
                $din eq "AA" and return "HEADER0" ;
                return "INIT"                     ;
                },
        "HEADER0" => sub{
                $din eq "99" and return "HEADER1" ;
                return "INIT"                     ;
                },
        ...
        );

# Set state machine's initial state.
my $cur_state = "INIT"   ;

# Integer for debugging purpose.
my $itgi = 0;

# Run the state machine.
while(1){
        $cur_state = $next_state{$cur_state}();
        print "$itgi, will jump to: $cur_state\n\n";
        $itgi++;
        }

# Send in input byte sequence, which simulates
# incoming bytes from RS-232 COM port:
$din = "AA"     ;
sleep(1)        ;
...
Run Code Online (Sandbox Code Playgroud)

========== 2020.10.09 22:10 更新 ==========

感谢@ikegami 经过一些努力和调试工作的帮助,现在我可以启动并运行我的小可爱 Perl 状态机,代码如下所示。

但是,它仍然存在一个问题,那就是:

输入字节序列(即@seq)必须是非 0x00 值;如果我将 0x00 放入命令序列中,那么 FSM 将在遇到 0x00 时退出。

为什么是这样?代码使用“ $cur_byte >= 0 ”,在我看来,它应该能够像处理非零值一样处理 0x00。

为什么 0x00 将状态机拉出运行状态?

use strict      ;
use warnings    ;

# input to the state machine
my $din ;

#---------------------------------------------------------------------
# FSM's state table.
#---------------------------------------------------------------------
# Expected input sequence is:
#   AA 99 00 01 ....
# In which:
#   (1) Fixed pattern "AA" and "99" are two bytes of header,
#   (2) Following bytes are uart ID, etc.
my %next_state = (
        "INIT" => sub{
                # If receives "AA" from input,
                # then jumpt to "HEADER0" state:
                $din eq "AA" and return "HEADER0" ; 
                # Otherwise just stay here:
                return "INIT"                     ; 
                },
        "HEADER0" => sub{
                # If receives "99" from input,
                # then proceed to "HEADER1" state:
                $din eq "99" and return "HEADER1" ; 
                # Otherwise, return to initial state:
                return "INIT"                     ; 
                },
        "HEADER1" => sub{
                # Capture first byte of uart ID:
                return "UARTID0";
                },
        "UARTID0" => sub{
                # Capture second byte of uart ID:
                return "UARTID1";
                },
        "UARTID1" => sub{
                # Capture second byte of uart ID:
                return "FINISHED";
                },
        "FINISHED" => sub{
                return "INIT";
                },
        );

#---------------------------------------------------------------------
# Set state machine's initial state.
#---------------------------------------------------------------------
my $cur_state = "INIT"   ;

#---------------------------------------------------------------------
# Send in command sequence
#---------------------------------------------------------------------
my @seq = (-1, 0xAA, -1, 0x99, -1, 0x06, -1, 0x07,
           -1, 0x08, -1, 0x09, -1, 0x0a, -1, 0x0b,
           -1, 0x0c, -1, 0x0d
          );

sub get_next_byte {
        while (@seq) { #(A)
                my $cur_byte = shift(@seq);
                return $cur_byte if $cur_byte >= 0;
                #
                sleep(-$cur_byte);
                }
        return (); #(B)
        }

#---------------------------------------------------------------------
# Run the state machine.
#---------------------------------------------------------------------
# Integer for debugging purpose.
my $itgi = 0;

while( $din = get_next_byte() ){ #(C)
        $din = sprintf("%02X",$din);
        $cur_state = $next_state{$cur_state}();
        print "-- Iteration $itgi, will jump to: $cur_state\n";
        $itgi++;
        }

print "-- Program finish.\n";
Run Code Online (Sandbox Code Playgroud)

ike*_*ami 5

您无需更改即可进入循环$din。你需要类似的东西

# Run the state machine.
while ( my ($din) = get_next_byte() ) {
   $din = sprintf("%02X", $din);
   $cur_state = $next_state{$cur_state}();
   print "$itgi, will jump to: $cur_state\n\n";
   $itgi++;
}
Run Code Online (Sandbox Code Playgroud)

出于测试目的,您可以使用

my @seq = (-1, 0xAA, -1, 0x99);

sub get_next_byte {
   while (@seq) {
      my $next = shift(@seq);
      return $next if $next >= 0;
      sleep(-$next);
   }

   return ();
}
Run Code Online (Sandbox Code Playgroud)