首页 > 代码库 > 读卡器的状态机, python实现

读卡器的状态机, python实现

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]
=======================