lora 发送kb级文件 的 python 实现

目标

    服务器向只具备 lora 通讯功能的装置发送更新文件,以免因为bug造成装置的失联。

难点

    lora 是远距离、低功耗、低带宽的传输方式,典型空中传播速度只有 1.2 kbps 或 2.4 kbps(即300byte/s),空速越高传播距离越短。并且,lora没有tcp/ip的3次握手、丢包重传机制,是非可靠通讯。文件一般是以千字节为单位的,必须要切分成多个小包传输,这中间存在包大小、时延设置、串口缓存等技术问题需要解决否则就会丢包。初步测试时,用12k的zip文件分包传输时,每包正常是235字节,发送间隔5秒,有 10/50 的包不足235字节(丢数了),中间涉及到 服务器-4g转lora网关-lora模块-python串口(pserial)-python主程序 5个环节,都有可能出问题。

问题排查

  1. 服务器-4g转lora网关   这个环节走的是移动互联网,协议tcp/ip,根据网关硬件的工作指示灯,与服务器的下发同时闪烁,应该不是堵点;
  2. 网关-lora模块 这个环节空速2.4kbps、每包最大240字节,测试发现发送间隔敏感,1秒、2秒都不能保证240字节发送完成,3秒比较合适,用串口测试工具测试模块接收正常;
  3. lora模块-pserial库 这个环节经过了uart,调用的是pserial的 real_all(),timeout=5秒,发现收到的都是8字节一个的小数据包,改成read(240)后有20%包不完整。

     判断不是硬件问题,是pserial的调用方法不对,即不能一次读上百字节。

设计思路

  1. 服务端将文件按照小于240字节进行拆分,头部增加1个字节命令,尾部增加2个字节crc16;
  2. 服务端开始发送时首先发送一个字节的命令,模块据此判断接收开始;
  3. 终端python开辟bytearray用来缓存收到的字节,接收命令开始extend到bytearray中;
  4. 终端python在接收到空字节(pserial timeout时会返回空字节)判断是否超过服务器发送间隔(建议长一点,我设置的10秒),超过了认为接受结束;
  5. 终端将bytearray前n-2个字节(含1个字节命令)用来计算crc16,与最后两个字节对比,判断接受数据是否正确,正确则写入zip文件,然后解压执行更新。

规约结构

python核心通讯代码

    _read 方法放到thread回调里一直执行,读取serial的字节,添加到file_buffer中,直到delay>10秒。经测试,12k文件连续5次以上发送成功。

    def _read(self):
        """loop forever and read serial"""
        while self.alive:
			data = self.serial.read_all()
			if data:
				command = data[0]
				self.last_recv = datetime.now()
				if self.in_update == True:
					self.file_buffer.extend(data)
				elif command == 0x08:
				   self.file_buffer.extend(data) 
				   self.in_update = True
			else:
				delay = (datetime.now()-self.last_recv).seconds
				if delay > 10:
					if self.in_update == True:
						self._handle_update(bytes(self.file_buffer))
					self.in_update = False
					self.file_buffer.clear()

        self.alive=False

    def _handle_update(self,data):
        if data:
            command=data[0]
            if (command == 0x08) & (len(data) > 3) & (modbus.checkcrc16(data) == True):
                self.logger.info('call update...')
                try:            
                    self._file_update_cb(data[1:(len(data)-2)])
                except Exception as e:
                    self.logger.error("update error:"+str(e.args))
            else:
                self.logger.error('file_buffer bad, length='+str(len(data)))

猜你喜欢

转载自blog.csdn.net/xhydongda/article/details/111695108