使用PyQt5设计UI界面开发桌面应用程序的小伙伴,刚开始可能都会遇到这样让人不爽的事:用Qt Designer好不容易设计好的界面并写好逻辑代码,已经可以正常运行了,某天发现界面要做个小改动(这种事好象永远都避不了),重新修改好UI界面窗口,用PyUIC将界面窗口再次生成Python代码,这时你会悲伤的发现:以前千辛万苦写好的逻辑代码全部没有了,需要在新生成的界面代码内重新加入逻辑代码!这实在是一件让人奔溃的事。
只有将界面与逻辑代码分离,才能终结这种不辛的事再次发生!
本示例使用的发卡设备:https://item.taobao.com/item.htm?id=615391857885&spm=a1z10.5-c.w4002-21818769070.11.6cc85700Robi3x
一、使用Qt Designer新建窗体文件MifareCardRW.ui,将需要的控件拖入窗体并布局好,
二、Python项目内,右击已布局好的窗体文件MifareCardRW.ui,选击菜单External Tool\PyUIC,将窗体文件自动生成Python代码文件MifareCardRW.py。
三、新建逻辑代码文件call_MifareCardRW.py,加入代码from mainwindow import Ui_MainWindow引入窗体类,并且为需要的控件绑定槽函数:
四、运行逻辑代码call_MifareCardRW.py,你会发现以前设计好的窗体打开,且各控件事件运行正常,以后再改动窗体文件MifareCardRW.ui,只要不更改以前控件名称,重新生成MifareCardRW.py窗体代码,逻辑代码不用更改还正常运行,这样逻辑与界面代码就分开啦。
五、逻辑代码 call_MifareCardRW.py 源码
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import QApplication, QWidget, QDesktopWidget,QMessageBox
from MifareCardRW import Ui_Frame
import sys
import struct # struct的pack函数把任意数据类型变成字符串
import ctypes # 调用DLL动态库要有这个引用
# 控制字定义
BLOCK0_EN = eval('0x01') # 读写块0
BLOCK1_EN = eval('0x02') # 读写块1
BLOCK2_EN = eval('0x04') # 读写块2
NEEDSERIAL = eval('0x08') # 读写指定序列号的卡
EXTERNKEY = eval('0x10') # 需要每次指定密码
NEEDHALT = eval('0x20') # 写卡后是否休眠卡
readblockdata=bytes(16) #保存读取的块数据,用做写UID后面的厂家信息
class MainWindow(QtWidgets.QMainWindow, Ui_Frame):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.setupUi(self)
self.pushButton_beep.clicked.connect(self.pushButton_beep_Clicked)
self.pushButton_clearlist.clicked.connect(self.pushButton_clearlist_click)
self.pushButton_getdevicenum.clicked.connect(self.pushButton_getdevicenum_click)
self.pushButton_piccreadex.clicked.connect(self.pushButton_piccreadex_click)
self.pushButton_piccwriteex.clicked.connect(self.pushButton_piccwriteex_click)
self.pushButton_piccchangesinglekey.clicked.connect(self.pushButton_piccchangesinglekey_click)
self.pushButton_piccread.clicked.connect(self.pushButton_piccread_click)
self.pushButton_piccwrite.clicked.connect(self.pushButton_piccwrite_click)
self.pushButton_piccwriteserial.clicked.connect(self.pushButton_piccwriteserial_click)
self.textEdit_thisblock.textChanged.connect(self.textEdit_thisblock_change_handler)
self.textEdit_block0.textChanged.connect(self.textEdit_block0_change_handler)
self.textEdit_block1.textChanged.connect(self.textEdit_block1_change_handler)
self.textEdit_block2.textChanged.connect(self.textEdit_block2_change_handler)
self.textEdit_newkey.textChanged.connect(self.textEdit_newkey_change_handler)
def LoadLibrary(self):
import sys
if sys.platform == 'win32':
# windows系统加载当前目录下的DLL库
dllfile = sys.path[0] + '\OUR_MIFARE.dll'
Objdll = ctypes.windll.LoadLibrary(dllfile)
elif sys.platform == 'linux':
# Linux加载当前目录下的so库
dllfile = sys.path[0] + '/libOURMIFARE.so'
Objdll = ctypes.cdll.LoadLibrary(dllfile)
else:
# macOS加载当前目录下的.dylib库
dllfile = sys.path[0] + '/libOURMIFARE.dylib'
Objdll = ctypes.cdll.LoadLibrary(dllfile)
return Objdll
def textEdit_thisblock_change_handler(self):
max_length = 47
text = self.textEdit_thisblock.toPlainText()
current_len = len(text)
text_cursor = self.textEdit_thisblock.textCursor()
if current_len > max_length:
text_valid = text[0:47]
self.textEdit_thisblock.clear()
self.textEdit_thisblock.setText(text_valid)
self.textEdit_thisblock.setTextCursor(text_cursor)
def textEdit_block0_change_handler(self):
max_length = 47
text = self.textEdit_block0.toPlainText()
current_len = len(text)
text_cursor = self.textEdit_block0.textCursor()
if current_len > max_length:
text_valid = text[0:47]
self.textEdit_block0.clear()
self.textEdit_block0.setText(text_valid)
self.textEdit_block0.setTextCursor(text_cursor)
def textEdit_block1_change_handler(self):
max_length = 47
text = self.textEdit_block1.toPlainText()
current_len = len(text)
text_cursor = self.textEdit_block1.textCursor()
if current_len > max_length:
text_valid = text[0:47]
self.textEdit_block1.clear()
self.textEdit_block1.setText(text_valid)
self.textEdit_block1.setTextCursor(text_cursor)
def textEdit_block2_change_handler(self):
max_length = 47
text = self.textEdit_block2.toPlainText()
current_len = len(text)
text_cursor = self.textEdit_block2.textCursor()
if current_len > max_length:
text_valid = text[0:47]
self.textEdit_block2.clear()
self.textEdit_block2.setText(text_valid)
self.textEdit_block2.setTextCursor(text_cursor)
def textEdit_newkey_change_handler(self):
max_length = 47
text = self.textEdit_newkey.toPlainText()
current_len = len(text)
text_cursor = self.textEdit_newkey.textCursor()
if current_len > max_length:
text_valid = text[0:47]
self.textEdit_newkey.clear()
self.textEdit_newkey.setText(text_valid)
self.textEdit_newkey.setTextCursor(text_cursor)
def ListBottom(self):
self.listWidget.scrollToBottom()
seleid = self.listWidget.count() - 1
self.listWidget.item(seleid).setSelected(True)
# IC卡操作错误代码解释
def ListErrInf(self, Errcode):
if (Errcode == 8):
self.listWidget.addItem('错误代码:8,未寻到卡,请重新拿开卡后再放到感应区!')
elif (Errcode == 1):
self.listWidget.addItem('错误代码:1,0~2块都没读出来,可能刷卡太块。但卡序列号已被读出来!')
elif (Errcode == 2):
self.listWidget.addItem('错误代码:2,第0块已被读出,但1~2块读取失败。卡序列号已被读出来!')
elif (Errcode == 3):
self.listWidget.addItem('错误代码:3,第0、1块已被读出,但2块读取失败。卡序列号已被读出来!')
elif (Errcode == 9):
self.listWidget.addItem('错误代码:9,有多张卡在感应区,寻卡过程中防冲突失败,读序列吗错误!')
elif (Errcode == 10):
self.listWidget.addItem('错误代码:10,该卡可能已被休眠,无法选中卡片!')
elif (Errcode == 11):
self.listWidget.addItem('错误代码:11,密码装载失败!')
elif (Errcode == 12):
self.listWidget.addItem('错误代码:12,卡片密码认证失败!')
elif (Errcode == 13):
self.listWidget.addItem('错误代码:13,读指定块失败,原因是刷卡太快或本块所对应的区还没通过密码认证!')
elif (Errcode == 14):
self.listWidget.addItem('错误代码:14,写指定块失败,原因是刷卡太快或本块所对应的区还没通过密码认证!')
elif (Errcode == 21):
self.listWidget.addItem('错误代码:21,没有动态库!')
elif (Errcode == 22):
self.listWidget.addItem('错误代码:22,动态库或驱动程序异常!')
elif (Errcode == 23):
self.listWidget.addItem('错误代码:23,(表示未检测到免驱动读写器!)(有驱动读写器表示驱动程序错误或未检测到有驱读写器!)')
elif (Errcode == 24):
self.listWidget.addItem('错误代码:24,操作超时,一般是动态库没有反映!')
elif (Errcode == 25):
self.listWidget.addItem('错误代码:25,发送字数不够!')
elif (Errcode == 26):
self.listWidget.addItem('错误代码:26,发送的CRC错!')
elif (Errcode == 27):
self.listWidget.addItem('错误代码:27,接收的字数不够!')
elif (Errcode == 28):
self.listWidget.addItem('错误代码:28,接收的CRC错!')
else:
self.listWidget.addItem('未知错误,错误代码:' + str(Errcode))
self.ListBottom()
def pushButton_beep_Clicked(self):
Objdll = self.LoadLibrary()
status = Objdll.pcdbeep(50) % 256
if status == 0:
self.listWidget.addItem("读写器已执行响声指令!")
else:
self.listWidget.addItem("驱动设备嘀一声:USB通讯失败,请以sudo超级用户模式运行!")
self.ListBottom()
def pushButton_clearlist_click(self):
self.listWidget.clear()
def pushButton_getdevicenum_click(self):
devno = bytes(4) # 声明4个字节缓冲
Objdll = self.LoadLibrary()
status = Objdll.pcdgetdevicenumber(devno) % 256
if (status == 0):
Objdll.pcdbeep(38)
SerialNum = ''
for num in range(0, len(devno)):
SerialNum = SerialNum + '%02X' % (devno[num])
if (num < len(devno) - 1):
SerialNum = SerialNum + '-'
self.listWidget.addItem('设备 序列号:' + SerialNum)
self.ListBottom()
else:
self.ListErrInf(status)
def pushButton_piccreadex_click(self):
myctrlword = BLOCK0_EN + BLOCK1_EN + BLOCK2_EN + EXTERNKEY # 读写控制字
myareano = self.comboBox.currentIndex() # 指定读写区号
authmode = self.comboBox_2.currentIndex() # 大于0表示用A密码认证,推荐用A密码认证
mypicckey = bytes.fromhex(self.textEdit_oldkey.toPlainText()[0:12]) # 卡片认证密码,
mypiccserial = bytes(4) # 4字节卡序列号缓冲
mypiccdata = bytes(48) # 读卡数据缓冲,一个扇区共48个字节
Objdll = self.LoadLibrary()
status = Objdll.piccreadex(myctrlword, mypiccserial, myareano, authmode, mypicckey, mypiccdata) % 256
if (status == 0):
Objdll.pcdbeep(38)
dispstr = '16进制卡号:%02X%02X%02X%02X' % (mypiccserial[0], mypiccserial[1], mypiccserial[2], mypiccserial[3])
Cardno = mypiccserial[0]
Cardno = Cardno + (mypiccserial[1] * 256)
Cardno = Cardno + (mypiccserial[2] * 65536)
Cardno = Cardno + (mypiccserial[3] * 16777216)
CardnoStr = '%010d' % Cardno
self.listWidget.addItem(dispstr + ',转10进制卡号:' + CardnoStr + ',读卡扇区数据成功!')
piccdataStr = ''
for num in range(0, len(mypiccdata)):
piccdataStr = piccdataStr + '%02X ' % (mypiccdata[num])
self.textEdit_block0.setPlainText(piccdataStr[0:48]) #直接给文本框赋值,此方法无格式信息
self.textEdit_block1.setPlainText(piccdataStr[48:96]) # 直接给文本框赋值,此方法无格式信息
self.textEdit_block2.setPlainText(piccdataStr[96:143]) # 直接给文本框赋值,此方法无格式信息
self.ListBottom()
else:
self.ListErrInf(status)
def pushButton_piccwriteex_click(self):
myctrlword = BLOCK0_EN + BLOCK1_EN + BLOCK2_EN + EXTERNKEY # 读写控制字
myareano = self.comboBox.currentIndex() # 指定读写区号
authmode = self.comboBox_2.currentIndex() # 大于0表示用A密码认证,推荐用A密码认证
mypicckey = bytes.fromhex(self.textEdit_oldkey.toPlainText()[0:12]) # 卡片认证密码,
mypiccserial = bytes(4) # 4字节卡序列号缓冲
mypiccdata=bytes.fromhex(self.textEdit_block0.toPlainText()) # 写卡数据,总计48个字节
if(len(mypiccdata)==16):
mypiccdata=mypiccdata+bytes.fromhex(self.textEdit_block1.toPlainText()) # 写卡数据,总计48个字节
if (len(mypiccdata) == 32):
mypiccdata = mypiccdata + bytes.fromhex(self.textEdit_block2.toPlainText()) # 写卡数据,总计48个字节
if (len(mypiccdata)==48):
Objdll = self.LoadLibrary()
status = Objdll.piccwriteex(myctrlword, mypiccserial, myareano, authmode, mypicckey, mypiccdata) % 256
if (status == 0):
Objdll.pcdbeep(38)
dispstr = '16进制卡号:%02X%02X%02X%02X' % (mypiccserial[0], mypiccserial[1], mypiccserial[2], mypiccserial[3])
Cardno = mypiccserial[0]
Cardno = Cardno + (mypiccserial[1] * 256)
Cardno = Cardno + (mypiccserial[2] * 65536)
Cardno = Cardno + (mypiccserial[3] * 16777216)
CardnoStr = '%010d' % Cardno
self.listWidget.addItem(dispstr + ',转10进制卡号:' + CardnoStr + ',写卡扇区数据成功!')
self.ListBottom()
else:
self.ListErrInf(status)
else:
self.listWidget.addItem('第2块的写卡数据输入错误,请输入16个16进制的写卡数据!')
self.ListBottom()
else:
self.listWidget.addItem('第1块的写卡数据输入错误,请输入16个16进制的写卡数据!')
self.ListBottom()
else:
self.listWidget.addItem('第0块的写卡数据输入错误,请输入16个16进制的写卡数据!')
self.ListBottom()
def pushButton_piccchangesinglekey_click(self):
myctrlword = BLOCK0_EN + BLOCK1_EN + BLOCK2_EN + EXTERNKEY # 读写控制字
myareano = self.comboBox.currentIndex() # 指定读写区号
authmode = self.comboBox_2.currentIndex() # 大于0表示用A密码认证,推荐用A密码认证
mypiccoldkey = bytes.fromhex(self.textEdit_oldkey.toPlainText()[0:12]) # 卡片认证密码,
mypiccserial = bytes(4) # 4字节卡序列号缓冲
#mypiccnewkey 指定6字节新A密码+4字节控制码+6字节B密码+1字节功能码 ,注意:指定新密码时一定要记住,否则有可能找不回密码,导致该卡报废!!!
#功能码为:3 表示同时更改A、B 密码及权限访问字
#功能码为:2 表示密码权限访问字不更改,只改A、B密码
#功能码为:0 示只改A密码
newkey=self.textEdit_newkey.toPlainText()
if (self.comboBox_3.currentIndex() == 0):
newkey=newkey+" 00"
elif(self.comboBox_3.currentIndex() == 1):
newkey = newkey + " 02"
else:
newkey = newkey + " 03"
mypiccnewkey=bytes.fromhex(newkey) # 新密码
if (len(mypiccnewkey) == 17):
Objdll = self.LoadLibrary()
status = Objdll.piccchangesinglekeyex(myctrlword, mypiccserial, myareano, authmode, mypiccoldkey,mypiccnewkey) % 256
if (status == 0):
Objdll.pcdbeep(38)
dispstr = '16进制卡号:%02X%02X%02X%02X' % (mypiccserial[0], mypiccserial[1], mypiccserial[2], mypiccserial[3])
Cardno = mypiccserial[0]
Cardno = Cardno + (mypiccserial[1] * 256)
Cardno = Cardno + (mypiccserial[2] * 65536)
Cardno = Cardno + (mypiccserial[3] * 16777216)
CardnoStr = '%010d' % Cardno
self.listWidget.addItem(dispstr + ',转10进制卡号:' + CardnoStr + ',更改卡密码成功!')
self.ListBottom()
else:
self.ListErrInf(status)
else:
self.listWidget.addItem('新A、B密码及控制位不是16个字节,请输入正确的6字节A密码+4字节控制位+6字节B密码!')
self.ListBottom()
def pushButton_piccread_click(self):
global readblockdata
myblock = self.spinBox.value() # 指定读写块号
myareano=myblock//4 # 根据块号获取扇区号,为的是认证扇区密码
authmode = self.comboBox_2.currentIndex() # 大于0表示用A密码认证,推荐用A密码认证
mypicckey = bytes.fromhex(self.textEdit_oldkey.toPlainText()[0:12]) # 卡片认证密码,
mypiccserial = bytes(4) # 4字节卡序列号缓冲
myblockdata=bytes(16) #读卡数据缓冲,一个块共16个字节
Objdll = self.LoadLibrary()
status = Objdll.piccrequest(mypiccserial) % 256 #寻找感应区内的卡片
if(status==0):
status = Objdll.piccauthkey1(mypiccserial,myareano,authmode,mypicckey) % 256 #寻到卡后,认证要读块所在扇区的密码
if(status==0):
status = Objdll.piccread(myblock,myblockdata) % 256 #密码认证成功,读块数据
if(status==0):
Objdll.pcdbeep(38)
readblockdata=myblockdata #保存读取的块数据用于UID卡复制
dispstr = '16进制卡号:%02X%02X%02X%02X' % (
mypiccserial[0], mypiccserial[1], mypiccserial[2], mypiccserial[3])
Cardno = mypiccserial[0]
Cardno = Cardno + (mypiccserial[1] * 256)
Cardno = Cardno + (mypiccserial[2] * 65536)
Cardno = Cardno + (mypiccserial[3] * 16777216)
CardnoStr = '%010d' % Cardno
self.listWidget.addItem(dispstr + ',转10进制卡号:' + CardnoStr + ',读卡块数据成功!')
piccdataStr = ''
for num in range(0, len(myblockdata)):
piccdataStr = piccdataStr + '%02X ' % (myblockdata[num])
self.textEdit_thisblock.setPlainText(piccdataStr) #直接给文本框赋值,此方法无格式信息
self.ListBottom()
else:
self.ListErrInf(status)
else:
self.ListErrInf(status)
else:
self.ListErrInf(status)
def pushButton_piccwrite_click(self):
myblock = self.spinBox.value() # 指定读写块号
myareano=myblock//4 # 根据块号获取扇区号,为的是认证扇区密码
authmode = self.comboBox_2.currentIndex() # 大于0表示用A密码认证,推荐用A密码认证
mypicckey = bytes.fromhex(self.textEdit_oldkey.toPlainText()[0:12]) # 卡片认证密码,
mypiccserial = bytes(4) # 4字节卡序列号缓冲
myblockdata=bytes.fromhex(self.textEdit_thisblock.toPlainText()) #写卡数据缓冲,一个块共16个字节
if (len(myblockdata) == 16):
Objdll = self.LoadLibrary()
status = Objdll.piccrequest(mypiccserial) % 256 #寻找感应区内的卡片
if(status==0):
status = Objdll.piccauthkey1(mypiccserial,myareano,authmode,mypicckey) % 256 #寻到卡后,认证要读块所在扇区的密码
if(status==0):
status = Objdll.piccwrite(myblock,myblockdata) % 256 #密码认证成功,写块数据
if(status==0):
Objdll.pcdbeep(38)
dispstr = '16进制卡号:%02X%02X%02X%02X' % (
mypiccserial[0], mypiccserial[1], mypiccserial[2], mypiccserial[3])
Cardno = mypiccserial[0]
Cardno = Cardno + (mypiccserial[1] * 256)
Cardno = Cardno + (mypiccserial[2] * 65536)
Cardno = Cardno + (mypiccserial[3] * 16777216)
CardnoStr = '%010d' % Cardno
self.listWidget.addItem(dispstr + ',转10进制卡号:' + CardnoStr + ',写卡块数据成功!')
self.ListBottom()
else:
self.ListErrInf(status)
else:
self.ListErrInf(status)
else:
self.ListErrInf(status)
else:
self.listWidget.addItem('请输入16个字节16进制的写卡信息!')
self.ListBottom()
def pushButton_piccwriteserial_click(self):
global readblockdata
myctrlword = BLOCK0_EN # 读写控制字
myareano = self.comboBox.currentIndex() # 指定读写区号
authmode = self.comboBox_2.currentIndex() # 大于0表示用A密码认证,推荐用A密码认证
mypicckey = bytes.fromhex(self.textEdit_oldkey.toPlainText()[0:12]) # 卡片认证密码,
mypiccserial = bytes(4) # 4字节卡序列号缓冲
mypiccdata = bytes(16) # 16字节写入数据缓冲,UID卡号是前面4个字节,第五字节必须等于前四个字节的异或和
newuid=int(self.textEdit_uidno.toPlainText())
if(newuid>=0 and newuid<=4294967295):
mypiccdata = struct.pack('<I', newuid) # 将卡号转字节数组低位在前
mypiccdata=mypiccdata+readblockdata[4:16]
Objdll = self.LoadLibrary()
status=Objdll.piccwriteserial(myctrlword,mypiccserial,authmode,mypicckey,mypiccdata) % 256
if(status==0):
status = Objdll.piccrequest(mypiccserial) % 256 #寻找感应区内的卡片
if(status==0):
Objdll.pcdbeep(38)
dispstr = '16进制卡号:%02X%02X%02X%02X' % (
mypiccserial[0], mypiccserial[1], mypiccserial[2], mypiccserial[3])
Cardno = mypiccserial[0]
Cardno = Cardno + (mypiccserial[1] * 256)
Cardno = Cardno + (mypiccserial[2] * 65536)
Cardno = Cardno + (mypiccserial[3] * 16777216)
CardnoStr = '%010d' % Cardno
self.listWidget.addItem(dispstr + ',转10进制卡号:' + CardnoStr + ',写UID卡号成功!')
self.ListBottom()
else:
self.ListErrInf(status)
else:
self.ListErrInf(status)
else:
self.listWidget.addItem('UID的取值范围是:0-4294967295,请输入正确的UID号!')
self.ListBottom()
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
mainWindow = MainWindow()
screen = QDesktopWidget().screenGeometry()
size = mainWindow.geometry()
# 获得窗口相关坐标
newLeft = (screen.width() - size.width()) // 2
newTop = (screen.height() - size.height()) // 2
# 移动窗口使其居中
mainWindow.move(newLeft, newTop)
mainWindow.setWindowTitle("IC-02V2 Python Demo")
mainWindow.show()
mainWindow.textEdit_oldkey.setPlainText("FFFFFFFFFFFF")
mainWindow.textEdit_block0.setPlainText("00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00")
mainWindow.textEdit_block1.setPlainText("00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00")
mainWindow.textEdit_block2.setPlainText("00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00")
mainWindow.textEdit_newkey.setPlainText("FF FF FF FF FF FF FF 07 80 69 FF FF FF FF FF FF")
mainWindow.textEdit_thisblock.setPlainText("00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00")
mainWindow.textEdit_uidno.setPlainText("0123456789")
mainWindow.comboBox_2.setCurrentIndex(1)
sys.exit(app.exec_())