~ PySerial 库与 NFC 技术尝试 ~
一、概述
- PySerial
- Python 中有一个进行串口通讯的包,叫做
PySerial
就是字面上的意思 “Python 串口” - 利用这个库可以非常方便地实现电脑与外部串口设备的通讯,不管你是连接设备还是开发智能硬件,都可以非常方便地使用 Python 写出一个上位机程序
- Python 中有一个进行串口通讯的包,叫做
- NFC
- IC 卡 ID 卡等卡片均适用 RFID 技术,现在喜欢称为 NFC,随着智能手机越来越多的开始搭载 NFC 功能,此项技术必然会普及开来,毕竟相比二维码,我是更加喜欢 NFC 技术的,所以了解一下 NFC 也是一件有意义的事情
- 其实我们的饭卡就是使用此技术的
- 安卓的智能手机直接有软件提供 NFC 卡片的读取
- iOS 11 中,苹果也为 iPhone 7 以上设备开放了 Core NFC API
- 除了平时使用 IC 卡作为饭卡之外,我们可以只是把它作为一个信息存储工具使用,这样,我们就可以把 IC 卡变成一张明信片
- 本程序运行于 Mac OS X 10.13,使用淘宝上二十来块钱的 NFC 模块,PN532 芯片
二、知识准备 *
- PySerial 的基本操作
- 导入库
import serial
- 创建一个串口连接的对象 s
s = serial.Serial
- 成员函数说明:
s.write(bytes) # void 写入一个 byte 串 s.inWaiting() # int 返回存储在寄存器中的字节数 s.read(int) # bytes 读取寄存器中的接下来 int 个字节 s.read(self.inWaiting()) # 这样就可以读取全部
- 导入库
- 数据的处理
- 因为发送、接受的都是二进制数据,我们需要在发送之前对数据进行处理,为此需要导入包
import binascii
- 发送数据:
s.write(bytes.fromhex('00 00 FF 03 FD D4 14 01 17 00'))
- 接受数据:
data = str(binascii.b2a_hex(self.read(self.inWaiting())))[2:-1]
- 此时
data
的数据为字符串"0000ff00ff00"
这样的格式
- 因为发送、接受的都是二进制数据,我们需要在发送之前对数据进行处理,为此需要导入包
- IC 卡的存储结构( M1卡 )
M1卡分为16个扇区,每个扇区由4块(块0、块1、块2、块3)组成,(我们也将16个扇区的64个块按绝对地址编号为0~63。
- PN532 芯片通讯协议
- 详见芯片手册
- 程序按照 PN532 模块的通讯指令与其通讯
三、概要设计
- 模块规划
- NFC 基本操作的模块
class NFC(serial.Serial): def wakeUP(self): # 唤醒模块 def findCard(self): # 寻卡 def auth(self): # 卡认证 def readBlock(self): # 读取某一扇区的某一块 def readDoc(self): # 读取全部扇区 def writeBlock(self): # 写某一扇区的某一块 def writeDoc(self): # 写全部扇区 def cmd(self): # 发送指令 def cmd_dcs(self): # 发送带 DCS 的指令 def addDCS(self): # 在指令尾添加 DCS def hexAddDCS(self):
- UI 模块
- UI 设计
- 实现一个简明的交互界面
四、用户手册
主程序界面如下:
灰色的按钮不可点击
输入正确的串口地址后,“连接” 按钮会被点亮
- 串口地址:
- Windows:COMx 如:
COM1
,COM2
- macOS: /dev/cn.x
- Linux: /dev/ttyx
- Windows:COMx 如:
- 串口地址:
连接成功之后,“读取” 和 “写入” 会被点亮,即可进行操作
读取卡之后,“保存到文件” 会被点亮,可将文本保存至文件
五、详细设计
UI 模块的设计
- 文件名称
UI.ui
- 设计工具
- QTDesigner
- 效果图
- 见用户手册
- 把 .ui 文件转换为 .py 代码:
- 在终端使用命令
python3 -m PyQt5.uic.pyuic UI.ui -o ui.py -x
- 文件名称
NFC 基本操作的模块
class NFC(serial.Serial):
def __init__(self, *args):
super(serial.Serial, self).__init__(*args)
def wakeUP(self):
c = bytes.fromhex('55 55 00 00 00 00 00 00 00 00 00 00 00 00 00 00 FF 03 FD D4 14 01 17 00')
self.write(c)
while self.inWaiting()==0:
pass
while self.inWaiting():
data= str(binascii.b2a_hex(self.read(self.inWaiting())))[2:-1]
if data == '0000ff00ff000000ff02fed5151600':
print('Waked!')
return True
else:
return False
def findCard(self):
c = bytes.fromhex('00 00 ff 04 fc d4 4a 02 00 e0 00')
self.write(c)
while self.inWaiting()==0:
pass
while self.inWaiting():
data= str(binascii.b2a_hex(self.read(self.inWaiting())))[2:-1]
print("FIND: " + data )
if data != '0000ff00ff00':
self.uid = data[-12:-10] + ' ' + data[-10:-8] + ' ' + data[-8:-6] + ' ' + data[-6:-4]
print('UID: ' + self.uid)
return True
time.sleep(0.1)
return False
def auth(self, card = '01', block = '07', pwd = 'FF FF FF FF FF FF', v = True):
c = bytes.fromhex(self.addDCS('00 00 FF 0F F1 D4 40 %s 60 %s %s %s'%(card, block, pwd, self.uid)))
self.write(c)
while self.inWaiting()==0:
pass
while self.inWaiting():
data= str(binascii.b2a_hex(self.read(self.inWaiting())))[2:-1]
if v:
print("AUTHED: " + data )
time.sleep(0.1)
return True
def readBlock(self, block, pwd = 'FF FF FF FF FF FF', card = '01'):
block = str('%.2x'%block)
while self.wakeUP() == False:
print('Waking....')
while(self.findCard() == False):
time.sleep(1)
self.auth(card = card, block = block, pwd = pwd)
c = bytes.fromhex(self.addDCS('00 00 FF 05 Fb D4 40 %s 30 %s'%(card, block)))
self.write(c)
while self.inWaiting()==0:
pass
while self.inWaiting():
data= str(binascii.b2a_hex(self.read(self.inWaiting())))[2:-1]
print(data)
print('DATA: ' + data[28:-4].upper())
time.sleep(0.1)
def readDoc(self, pwd = 'FF FF FF FF FF FF', card = '01'):
while self.wakeUP() == False:
print('Waking....')
while(self.findCard() == False):
time.sleep(1)
print('Reading....DATA:')
doc = ''
for i in range(64):
if (i+1)%4 == 0 or i == 0:
continue
block = str('%.2x'%i)
self.auth(card = card, block = block, pwd = pwd, v = False)
c = bytes.fromhex(self.addDCS('00 00 FF 05 Fb D4 40 %s 30 %s'%(card, block)))
self.write(c)
while self.inWaiting()==0:
pass
flag = False
while self.inWaiting():
data= str(binascii.b2a_hex(self.read(self.inWaiting())))[2:-1]
if('980604' in data):
flag = True
break
doc+=data[28:-4].upper()
if flag:
break
hs = ''
for i in range(len(doc)):
if i%2 == 0 and i != 0:
hs += ' '
hs += doc[i]
return bytes.fromhex(hs)
def writeBlock(self, block, doc, pwd = 'FF FF FF FF FF FF', card = '01'):
block = str('%.2x'%block)
while self.wakeUP() == False:
print('Waking....')
while(self.findCard() == False):
time.sleep(1)
self.auth(card = card, block = block, pwd = pwd)
c = bytes.fromhex(self.addDCS('00 00 FF 15 eb D4 40 %s A0 %s %s'%(card, block, doc)))
self.write(c)
while self.inWaiting()==0:
pass
while self.inWaiting():
data= str(binascii.b2a_hex(self.read(self.inWaiting())))[2:-1]
print(data)
time.sleep(0.1)
def writeDoc(self, string, pwd = 'FF FF FF FF FF FF', card = '01', encoding = 'utf8'):
doc = str(string).encode(encoding)
flag = 0
while len(doc) > 752:
flag = 1
e = int(len(string) - (len(doc)-752)/(len(doc)/len(string))) - 1
string = string[0:e]
doc = str(string).encode(encoding)
if flag == 1:
print("Document Cut")
print(string)
bu = bytes.fromhex('00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00')
if(len(doc)%16 != 0):
doc += bu[len(doc)%16:]
doclist = []
for i in range(int(len(doc)/16)+1):
doclist += [doc[i*16:(i+1)*16]]
doclist = doclist[:-1]
if (len(doclist) < 47):
doclist += [bytes.fromhex('98 06 04 00 00 00 00 00 00 00 00 00 00 00 00 00')]
while self.wakeUP() == False:
print('Waking....')
while(self.findCard() == False):
time.sleep(1)
print('Writing....Data....')
j = 0
for i in doclist:
if (j+1)%4 == 0 or j == 0:
j+=1
block = str('%.2x'%j)
self.auth(card = card, block = block, pwd = pwd, v = False)
docx = str(i)[2:-1].replace('\\x',' ')
c = self.hexAddDCS(bytes.fromhex('00 00 FF 15 eb d4 40 %s A0 %s'%(card, block)) + i)
# print(c)
self.write(c)
while self.inWaiting()==0:
pass
while self.inWaiting():
data= str(binascii.b2a_hex(self.read(self.inWaiting())))[2:-1]
j+=1
print('DONE!')
def cmd(self, hexcmd):
c = bytes.fromhex(hexcmd)
hexcmd = '00 00 ff ' + '%.2x '%len(c) + '%.2x '%(0x100 - len(c)) + hexcmd
finalcmd = self.addDCS(hexcmd)
print(finalcmd)
c = bytes.fromhex(finalcmd)
self.write(c)
while self.inWaiting()==0:
pass
while self.inWaiting():
data= str(binascii.b2a_hex(self.read(self.inWaiting())))[2:-1]
print('OUT:')
print(data)
print(data[28:-4])
time.sleep(0.1)
def cmd_dcs(self, hexcmd):
sum = 0
b = bytes.fromhex(hexcmd)
for i in b:
sum += i
DCS = str(hex(0xff - (0xff & sum)))[2:] + ' 00'
c = hexcmd + ' ' + DCS
print("CMD:" + c)
return self.cmd(c)
def addDCS(self, hexcmd):
sum = 0
b = bytes.fromhex(hexcmd)
for i in b:
sum += i
DCS = str(hex(0xff - (0xff & sum)))[2:] + ' 00'
c = hexcmd + ' ' + DCS
return c
def hexAddDCS(self, hexcmd):
sum = 0
b = hexcmd
for i in b:
sum += i
DCS = ('%.2x'%(0xff - (0xff & sum))) + ' 00'
c = hexcmd + bytes.fromhex(DCS)
return c
- 使用 UI 模块派生的子类,完成程序操作的功能
class MainUI(Ui_MainWindow):
def __init__(self, *args):
super(Ui_MainWindow, self).__init__(*args)
self.MainWindow = QtWidgets.QMainWindow()
self.setupUi(self.MainWindow)
self.connModify()
# logo init
scene = QtWidgets.QGraphicsScene()
image = QtGui.QImage()
image.load('logo.png')
scene.addPixmap(QtGui.QPixmap().fromImage(image).scaled(self.logoView.width(), self.logoView.height()))
self.logoView.setScene(scene)
# self.textEdit.setText('lalalalala')
self.readButton.setDisabled(True)
self.connButton.setDisabled(True)
self.writeButton.setDisabled(True)
self.saveButton.setDisabled(True)
self.connButton.setText('连 接')
def connModify(self):
self.devButton.clicked.connect(lambda: self.setPort())
self.connButton.clicked.connect(lambda: self.connDev())
self.readButton.clicked.connect(lambda: self.readDoc())
self.writeButton.clicked.connect(lambda: self.writeDoc())
self.saveButton.clicked.connect(lambda: self.saveDoc())
def setPort(self):
self.port = self.devEdit.text()
print(self.port)
self.connButton.setDisabled(False)
self.connButton.setText('连 接')
self.devButton.setDisabled(True)
self.devEdit.setDisabled(True)
self.devEdit.setText('已设定: ' + self.port)
def connDev(self):
if self.connButton.text() == '连 接':
try:
self.nfc = NFC(self.port,115200)
self.connButton.setText('断 开')
self.readButton.setDisabled(False)
self.writeButton.setDisabled(False)
except:
self.textEdit.setText('端口无法连接,请重新设定!')
self.connButton.setDisabled(True)
self.devButton.setDisabled(False)
self.devEdit.setDisabled(False)
self.devEdit.setText(self.port)
else:
self.nfc.close()
self.connButton.setText('连 接')
self.readButton.setDisabled(True)
self.writeButton.setDisabled(True)
def readDoc(self, encoding = 'utf8'):
self.textEdit.setText('正 在 读 取... 请 稍 候...')
try:
d = self.nfc.readDoc()
self.textEdit.setText(d.decode(encoding))
self.saveButton.setDisabled(False)
except:
self.textEdit.setText('读 取 错 误 请 重 试 !\n请 确 认 你 读 取 的 是 正 确 的 明 信 片 !')
def writeDoc(self, encoding = 'utf8'):
try:
self.nfc.writeDoc(self.textEdit.toPlainText(), encoding = encoding)
self.textEdit.setText('写 入 成 功 !')
except:
self.textEdit.setText('写 入 时 发 生 错 误,请 重 试 !')
def saveDoc(self):
f = open(self.txtEdit.text(),'w', encoding='utf8')
f.write('%s'%str(self.textEdit.toPlainText()))
f.close()
self.saveButton.setDisabled(True)
- 主程序调用演示
app = QtWidgets.QApplication(sys.argv)
ui = MainUI()
ui.MainWindow.show()
sys.exit(app.exec_())
六、附录
- PN532 芯片数据手册下载地址:
- https://wenku.baidu.com/view/f695b781680203d8ce2f247f.html
- PN532 模块手册下载地址:
- https://pan.baidu.com/s/1pJOgAuv