Perl状态机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)        ;
...

========== 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,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,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";

解决方法

您无需更改$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++;
}

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

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

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

   return ();
}
,

感谢@zdim和@ikegami的帮助,现在我终于完全完成了该程序。如果有人可能有相同的问题,我将在下面发布我的工作代码。

以下代码受zdim启发:

 try{
        System.out.printf("Hey,%s. How old are you?: ",name);
        age = input.nextInt();
    } catch (InputMismatchException e){
        System.out.printf("Agh! Sorry %s. We were expecting a number made up of digits between 0-9. Try again for us? \n How old are you?: ",name);
        input.nextLine();
        age = input.nextInt();
    }

下面的dode受到ikegami的启发,请注意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: # "AA" and "99" are two bytes of header,# "00" and "01" are two bytes of uart ID. 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{ # return "FINISHED"; # },"FINISHED" => sub{ return "INIT"; },); # Set state machine's initial state. my $cur_state = "INIT" ; # Integer for debugging purpose. my $itgi = 0; # Run the state machine. while($din = <>){ chomp $din ; $cur_state = $next_state{$cur_state}(); print "$itgi,will jump to: $cur_state\n\n"; $itgi++; } # Send in input bytes: $din = "AA" ; sleep(1) ; $din = "99" ; sleep(1) ; 和简单的($din)之间的区别,不带括号:使用括号,我们得出TRUE或FALSE结果;不带括号的话,我们得到的实际元素值为$din,如果该值为@seq,则0x00将变为while并退出

while(0)