这里写自定义目录标题
使用Pyqt进行客户端升级工具开发
嵌入式主板有4个arm芯片,有4个裁剪过的arm linux系统,而且要做100个板子的批量升级,所以需要进行合理的线程管理,不拘泥与UI和逻辑分离的开发,要考虑的因素比较多
需要了解的工具
因为涉及到主机和嵌入式板子的通信,所以选择一个合理的通信方式是非常有必要的。
- Telnet
之所以还会用这种不安全的通信方式,是因为系统的升级方式有很多种。第一种是通过串口(uart)通信,在uboot模式下进行系统镜像的烧写,第二种是通过一个AP机(外设主机)进行通信升级,第三种是通过网口进行系统的升级。Telnet适用于网口通信,占据23端口。如果板载系统处于uboot模式,并想要通过网口进行系统升级,只能通过telnet实现,因为uboot模式可以将telnet的client端集中进去,而ssh是不行的
import telnetlib(只能进行命令行的传输) - Serial
串口通信是在板子没有系统时进行升级的唯一外部方式。如果是命令行可以直接用serial.write(),如果是包的传输,要使用xmodem,ymodem,zmodem这样的协议进行串口包的传输,但是传输速率不快
import serial(命令行传输)
xmodem的方式可以写成一个类,git上有,自行参考(包的传输)
import serial
ser=serial.Serial("/dev/ttyUSB0",9600,timeout=0.5) #使用USB连接串行口
ser=serial.Serial("/dev/ttyAMA0",9600,timeout=0.5) #使用树莓派的GPIO口连接串行口
ser=serial.Serial(1,9600,timeout=0.5)#winsows系统使用com1口连接串行口
ser=serial.Serial(“com1”,9600,timeout=0.5)#winsows系统使用com1口连接串行口
ser=serial.Serial("/dev/ttyS1",9600,timeout=0.5)#Linux系统使用com1口连接串行口
ser.open() #打开端口
s = ser.read(10)#从端口读10个字节
ser.write(“hello”)#向端口些数据
ser.close()#关闭端口
data = ser.read(20) #是读20个字符
data = ser.readline() #是读一行,以/n结束,要是没有/n就一直读,阻塞。
#以下为获取输入缓存区的数据大小,然后再读取数据(这是读取数据的常规操作,必须的一个步骤)
n = self.serial.in_waiting
if n:
buf = self._serial_read(n)
- SSH SFTP
网口的安全通信方式,如果系统处于激活状态进行升级,这是最好的通信方式。
python的包 Paramiko封装了ssh的功能,paramiko包含两个核心组件:SSHClient和SFTPClient。
- SSHClient的作用类似于Linux的ssh命令,是对SSH会话的封装,该类封装了传输(Transport),通道(Channel)及SFTPClient建立的方法(open_sftp),通常用于执行远程命令。(命令行传输)
- SFTPClient的作用类似与Linux的sftp命令,是对SFTP客户端的封装,用以实现远程文件操作,如文件上传、下载、修改文件权限等操作。(包的传输)
import paramiko
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect(hostname='192.168.1.105', port=22, username='root', password='123456')
stdin, stdout, stderr = client.exec_command('df -h ') # stdout 为正确输出,stderr为错误输出,同时是有1个变量有值
print(stdout.read().decode('utf-8'))
client.close()
- TFTP
端口号固定为69,TFTP是一个传输文件的简单协议,它基于UDP协议而实现。适合在局域网内进行小文件的传输,如果按照规则配置tftp,会比较麻烦,所以可以直接使用开源的第三包,调用API接口就可以了,第三包是tftpy
import tftpy - Struct
MCU通过网口进行升级的时候,这个包是非常重要的。首先通过socket的方式进行主机和mcu板载网口的通信,而socket.write()和mcu通信需要的是字节流(bytes),而python都是字符流,所以需要进行转换。至于这两者的区别,可以自行baidu。
- 例子1:
数据格式为:c的结构体
unsigned short id;
char[4] tag;
unsigned int version;
unsigned int count;
解包 id, tag, version, count = struct.unpack("!H4s2I", s)
封包 ss = struct.pack("!H4s2I", id, tag, version, count)。 - 例子2:
2字节(包长度)+4字节(包id)+包内容
解包
size, = struct.unpack(’>H’,raw[0:2])
cmd, = struct.unpack(’>H’, raw[2:4])
string, = struct.unpack(’>{0}s’.format(size - 4), raw[4:size])
封包
fmt = “>HH{0}s”.format(len(result)) #结构化数据{}的地方用len代替进去,里面有个0是用于补齐
args = (len(result), cmd,result)
data = struct.pack(fmt, *args)
pack怎么使用有很多资料可以参考
- Logging
logging.basicConfig(
level=logging.DEBUG, format='[%(levelname)s] %(asctime)s %(filename)s:%(lineno)d %(message)s')
logging.info("输出至log文件")
- Threading
- 使用线程可以把占据长时间的程序中的任务放到后台去处理。
- 用户界面可以更加吸引人,比如用户点击了一个按钮去触发某些事件的处理,可以弹出一个进度条来显示处理的进度。
- 程序的运行速度可能加快。
- 在一些等待的任务实现上如用户输入、文件读写和网络收发数据等,线程就比较有用了。在这种情况下我们可以释放一些珍贵的资源如内存占用等等。
def start(self):
logging.debug('tftp server start on %s' % self.tftp_root)
self.tftpd_thread = threading.Thread(target=self._tftpd_worker) #_tftpd_worker函数单独进行
self.tftpd_thread.setDaemon(True) #设置为true,则主线程消失,该线程也跟着消失
self.tftpd_thread.start()#开始工作
return True
def isAlive(self):
return self.tftpd_thread.isAlive()
常用的python包如上,可以先好好学一下各部分的功能,有些git有集成的包,可以直接用