1. 问题的提出,及状态机简介
ZHUMAO整了个门禁用的读卡器,比以前那种更好,不需要发指令就能读,只要刷卡,读卡器就向串口上写数据。仍然是串口的,还是韦根协议。"刷卡就向上写"避免了轮询读卡器,效率更高,代码也容易了。不过,也造成一个问题。下发命令,然后轮询读的模式下,如果在串口线上只有一个读卡器,不需要对输入的数据特别检验和处理,接收到的数据一定是对的,按协议读入多少个字符,然后按偏移量取有效的部分就行了。"刷卡就向上写"的模式,需要保证对齐,必须从刷卡后产生的第一字符开始读,读到最后一个,要避免从中间读起。如果中间出现了噪音之类的干扰,由于不能下发指令要求对齐
(或者开始读),就再也找不到开始位置,数据全乱。
解决这个问题的方案是状态机。
状态机是个著名的数学模型,在数字电路、编译原理、面向对象系统分析与设计、形式语言与状态机中都有提及。状态机效率很不错,刘典同学曾经用状态机模型写程序参加过CSDN上的比赛,检验IP地址是否合法,获得过第二名。状态机描述问题清晰,邦哥和亮哥曾经用状态机重写安卓程序的界面部分,把原来"朴素"方法可能出现的BUG都去除了。
正确的思考方法是有效的工具,在解决问题中非常重要。人类通常不懈于在猛兽面前炫耀速度和力量,而是使用弩箭和陷阱。所以,工具对于成为人类多么重要。所以,不用状态机,而依靠单纯的智力是多么愚蠢。
2. 问题描述
该型号读卡器上传的数据看起来是这样的,以十六进制表示,"02 XX XX XX XX 0d 0a 03"。其中的4个 XX 表示卡号,是我们感兴趣的部分。其余的部分必须匹配,才能说明读卡器正常工作,卡号有效。
主程序准备写成这样:
1 if __name__ == ‘__main__‘:
2 s = state_machine()
3 t = serial.Serial(‘COM3‘)
4 while True:
5 str = s.go(t)
6 print str.upper()
其中第2行初始化一个 state_machine 类的实例。在第4行开始的循环中,每次迭代都调用 s.go(t),把串口传给状态机。在状态机的go中,读很多次串口,直到遇到一次完整有效的卡号,作为返回值,在第6行中打印出来。这个串口t如果改为在 state_machine 的构造函数中,会更好一些。不过我对python语法不熟,大部分时间都消耗在查语法手册上了,头昏眼花,当时就写成了这样。
当然,真实程序的目标不是打印卡号,而是用卡号作为检索条件,去数据库里查询和更新一些数据。
3. 状态机
开始写状态机,才发现 python 居然没有 switch-case。可见我对语法得多么不熟。
根据 "02 XX XX XX XX 0d 0a 03",状态转换如图所示。状态图中的关键是,我在哪个状态,接收到哪个消息后,会迁移到哪个状态,在迁移的过程中,会做哪些动作。
4. 状态机代码解释
代码如附录A所示。state_machine 是一个类 类型。
成员变量,count用于计数,在0x00状态 (及0x02状态) 一共接收了几个字符,这些字符应该添加到有效卡号 (成员变量ret)的末尾。成员变量state,是当前的状态,其初始状态是 0xff。
成员函数 str2hex 是从zhumao那里抄来的,用于把二进制转为十六进制文本形式。
成员函数 go (self, ser) 是核心部分。每次调用 go,它会从串口读入一个字符,并把这个字符作为发送给状态机的消息;状态机根据自己的 (1) 当前状态 state,然后再根据这个 (2) 消息 c,判定 (3)应该迁移到哪个状态, (4)应在迁移时做哪些动作。
在状态图中,(1) 当前状态标记为椭圆,箭尾所指的那个椭圆, (2)消息,标记为线上的文字,斜线"/"左边的部分, (3)应该迁移到哪个状态,箭头所指的那个椭圆, (4)在迁移时做的动作,标记为线上的文字,斜线右边的部分。
成员函数 go,一旦读到的字符可以拼成一个有效的卡号 (包括0x03也已读入),就给出卡号作为返回值,退出 go 函数,控制权转交回主函数。如果尚未形成有效的卡号,就继续在 go 里面转。
如果你想测试,还没有找到读卡器。那么把第12行改成从一个二进制文进中读入。
附录A 状态机代码
1 class state_machine:
2 count = 0
3 state = 0xff # 02 XX XX XX XX 0d 0a 03
4 ret = ""
5 def str2hex(self, c):
6 hvol = ord(c)
7 hhex = ‘%02x‘%hvol
8 return hhex
9
10 def go(self, ser):
11 while True:
12 c = ser.read(1)
13 c = self.str2hex(c)
14 # print self.state
15 # print c
16 # print self.ret
17 # print
18 if self.state == 0xff:
19 if c == ‘02‘:
20 self.state = 0x02
21 self.ret = ""
22 continue
23 if self.state == 0x02:
24 self.state = 0x00
25 self.count = 0
26 self.count=self.count+1
27 self.ret = self.ret + c
28 continue
29 if self.state == 0x00:
30 if self.count<4:
31 self.count=self.count+1
32 self.ret = self.ret + c
33 self.state = 0x00
34 continue
35 else:
36 if c == ‘0d‘:
37 self.state = 0x0d
38 continue
39 else:
40 self.state = 0xff
41 self.ret = ""
42 continue
43 if self.state == 0x0d:
44 if c == ‘0a‘:
45 self.state = 0x0a
46 continue
47 else:
48 self.state = 0xff
49 continue
50 if self.state == 0x0a:
51 if c == ‘03‘:
52 self.state = 0x03
53 self.state = 0xff
54 return self.ret
55 else:
56 self.state = 0xff
57 continue
58 else:
59 continue
附录 B 状态机图示的源代码 in graphviz
digraph state
{
graph [ nodesep=1.2]
start -> "0xff" ;
"0x00" [color=red];
"0xff" -> "0x02" [label="0x02"];
"0xff" -> "0xff" [label="不是0x02"];
"0x02" -> "0x00" [label="任意字符 / (count=1,该字符填入卡号末尾)", color=red, fontcolor=red];
"0x00"-> "0x00" [label="任意字符 & count<4 / (count+=1,该字符填入卡号末尾)", color=red, fontcolor=red];
"0x00" -> "0x0d" [label="0x0d & count>=4"];
"0x00" -> "0xff" [label="不是0x0d & count>=4"];
"0x0d" -> "0x0a" [label="0x0a"];
"0x0d" -> "0xff" [label="不是0x0a"];
"0x0a" -> "0x03" [label="0x03"];
"0x0a" -> "0xff" [label="不是0x03"];
"0x03" -> "0xff" [label="无条件"];
}
--------------------
博客会手工同步到以下地址:
[http://giftdotyoung.blogspot.com]
[http://blog.csdn.net/younggift]
=======================
读卡器的状态机, python实现,布布扣,bubuko.com