1 硬件设备
- TTL串口摄像头(VC0706)
- USB转TTL烧录器
2 serial安装
第一次安装的是serial的包导包的时候发现下载错了,正确应该是pyserial。安装后直接import就可以了。
3 实现串口通信
3.1 发现端口
Windows下为COM(N, N=1、2...), Ubuntu下为‘/dev/ttyS0
'。
Windows初学者,可以给您一下两种方式确定端口号。
方法一:输入在终端(cmd)中输入
python -m serial.tools.list_ports
输出结果:
COM5 1 ports found
方法二:搜索电脑上的设备管理器,打开以后然后插入烧录器,自动就会弹出。如果没有弹出就可能是驱动没有安装,安装好以后不好使,重启一下电脑,到了工作的时候大家都知道程序员会跟你说,你重启一下,清一下缓存,这两句话。也有可能是驱动安装的不对。
方法三:直接找一个有端口扫描的上位机,点击扫描就可以了。大部分上位机都是你一插进去就会检测到你的端口。
注意:当串口被占用的时候也有可能导致失败,例如你在编译器有两个进程运行下面的测试代码,第二个进程就会因为端口占用而失效。也有的上位机是因为同时打开了两个上位机的缘故(实验课的时候同学遇到过情况),可以用任务管理器kill掉。
测试:
import serial #Windows ser = serial.Serial(port='COM5', baudrate=115200, timeout=0.5) print(ser.name)
控制台打印结果:
COM5 Process finished with exit code 0
建立ser对象的代码:
class PicSerial: __ser = None # ser的单例 __isinit = False @staticmethod def get_available_port(): """ 检测可以使用的端口号 :return->str: 端口号的名称 """ port = list(list_ports.comports()) if len(port) > 0: port_name = port[0].device print(port_name) return port_name # logging.info("Available port:", ports) else: print("There is no available port.") # logging.error("There is no available port.") def __new__(cls, *args, **kwargs): if PicSerial.__ser is None: cls.__ser = object.__new__(cls) return cls.__ser def __init__(self): if not PicSerial.__isinit: self.sername = self.get_available_port() self.ser = serial.Serial(port=self.sername, baudrate=BAUDRATE) PicSerial.__isinit = False print("PicSerial init.")
3.2 发送命令
3.2.1 协议格式
3.2.2 serial传送的方式
serial传送的方式有:
串行端口对象。只传单个字节。字符串。字节数组+字节数组长度。
所以直接选用数组传数据,这里会遇到一个问题就是python的list会自动把十六进制数转换为整形。
所以要进行转换你可以直接写成b“/x56/x00/x17/x00”。假如你不需要传十进制也可以转成list,直接map(chr,x)或map(ord,x)也是可以的。读的时候也要注意只要你放进list里面就会自动转成整形。
【我觉得这样写很降智,但是又不得不这样写】
#在PicSerial中 def isreply(self, cmd: bytes, option: str) -> bool: """ 检测是否有回复 :return:回复的内容 :param cmd: :param option: :return: True则有回复 """ if isinstance(cmd, bytes) and isinstance(option, str) and len(cmd) > 0 and len(option) > 0: self.ser.write(cmd) reply = self.ser.read(4) reply = list(map(chr, list(reply))) print("49h,The function'{}' is running. reply:{}".format(sys._getframe().f_code.co_name, reply)) if len(reply) >= 4 and reply[0] == 'v' and reply[1] == SERIAL_NUM and reply[2] == option and reply[3] == STATUS: return True return False
测试:
#在test文件中 class TestSerial(unittest.TestCase): def test_isreply(self): self.assertTrue(ser.isreply(GET_VERSION_CMD, VERSION)) self.assertFalse(ser.isreply('\x56\x00\x11\x00', VERSION)) self.assertFalse(ser.isreply(GET_VERSION_CMD, b'\x11')) self.assertFalse(ser.isreply(123456, b'\x11')) self.assertFalse(ser.isreply('', VERSION)) self.assertFalse(ser.isreply(b'', VERSION)) self.assertFalse(ser.isreply(GET_VERSION_CMD, '')) self.assertFalse(ser.isreply(GET_VERSION_CMD, None)) self.assertFalse(ser.isreply(b'', '')) self.assertFalse(ser.isreply(b'\x56\x00\xAA\x00', VERSION)) self.assertFalse(ser.isreply(GET_VERSION_CMD, '\xAA')) #之后就省略不写了 if __name__ == '__main__': unittest.main()
结果:
3.3 获取版本号(hello world)
按照协议一步一步操作
主 机 发:56 00 11 00 摄像头回:76 00 11 00 0B 56 43 30 37 30 33 20 31 2E 30 30 (VC0703 1.00)
#在PicSerial中 def getversion(self) -> str: """ 获取版本号 :return: """ cmd = GET_VERSION_CMD option = VERSION if self.isreply(cmd, option): left = self.ser.readall() print("75h,The function'{}' has responded.left{}".format(sys._getframe().f_code.co_name, left)) return self.ser.read(12).decode()[1:]
测试:
#在test文件中 def test_getversion(self): self.assertEqual(ser.getversion(), 'VC0703 1.00')
结果:通过测试
3.4 复位
主 机 发: 56 00 26 00
摄像头回: 76 00 26 00 00
#在PicSerial中 def reset(self): """ 复位 :return: """ cmd = REST_CMD option = RESET if self.isreply(cmd, option): if self.ser.read(1) == b'': left = self.ser.readall() print("75h,The function'{}' has responded.left{}".format(sys._getframe().f_code.co_name, left)) return True return False
*测试和运行结果不一样。
花了一点时间找到原因了,单元测我都是点击前面绿色的小箭头,以为只是运行当前的测试函数的内容,但是我发现它把其他的函数都运行了。所以要把之前的测试函数注释掉得到的结果就一样了。
测试通过。
3.5 照相
- 停止当前帧刷新
- 获娶图片长度
- 获取图片
- 恢复帧更新
3.5.1 停止当前帧刷新
这一步执行一次就够了。因为读命令的时候会出现麻烦。但是这一步是有意义的,就是当你发现图片很大,的时候正常大小就两个byte可以表示完了(排除你的图片面积十分大或十分清晰),又或者是突然读空了。假如数值非常的大,可以使用该函数,再不行就要选择复位。
def stoprefresh(self): """ 停止刷新当前帧 :return: """ cmd = STOP_REFRESH_CMD option = TAKE_PHOTO self.ser.write(cmd) if self.isreply(cmd, option) and self.ser.read(1) == b"\x00": left = self.ser.readall() print("87h,The function'{}' has responded.left{}".format(sys._getframe().f_code.co_name, left)) return True return False
通过测试
def test_stoprefresh(self): self.assertTrue(ser.stoprefresh())
3.5.2 获娶图片长度
def getlength_bytes(self) -> bytes: """ 获取图片的长度 :return: """ cmd = GET_LENGTH_CMD option_pic = '4' self.ser.write(cmd) if self.isreply(cmd, option_pic): if self.ser.read(1) == b'\x04': res = self.ser.read(4) left = self.ser.readall() print("103h,The function'{}' has responded.left{}".format(sys._getframe().f_code.co_name, left)) return res return b'\x00\x00\x00\x00'
测试通过
def test_getlength(self): self.assertEqual(ser.getlength(), b'\x00\x00\x12\x34')
3.5.3 恢复帧更新
def recover_refresh(self): """ 恢复帧刷新 :return: """ cmd = RECOVER_REFRESH_CMD option = TAKE_PHOTO self.ser.write(cmd) if self.isreply(cmd, option): # 读出剩余的字节 left = self.ser.readall() print("142h,The function'{}' has responded.left{}".format(sys._getframe().f_code.co_name, left)) return True return False
测试并通过:
def test_recover_refresh(self): self.assertTrue(ser.recover_refresh())
3.5.4 拍照
在这里卡了很长时间,不知道为什么长度是不确定的,每一次读的长度都没读完,看代码。
下面代码只是演示
#下面代码只是演示不在最终版本中 def savephoto(self, cmd, option, len): """ 保存图片 :param cmd: :param option: :param len: 照片的长度 :return: """ with open('write_pic/serialpic/photo.jpg', 'wb') as f: if self.isreply(cmd, option): print(self.ser.read(1)) countofread_complete_byte = 0 # 用于计算当前已经写入的长度 while countofread_complete_byte != len + 10: # read()是有上限的,不可以把全部都读取 lines = self.ser.read(len + 10 - countofread_complete_byte) countofread_complete_byte += lines.__len__() f.write(lines) print("142h,countofread_complete_byte:", countofread_complete_byte, "lines", lines.__len__()) left = self.ser.readall() print("146h,少读内容:", left, "共", left.__len__(), "个字节") res = self.ser.readall() print(res)
现象:
现象是运行一直不停都是手动stop console,或者没有stop console会一直打印lines为空,就此可以猜测read()不是阻塞的。是图片字节总数不断增多。每次遍历完后满足self.ser.read(len + 10 - countofread_complete_byte)后再readall()还是有剩余的内容。
我发现此时readall一共读出了4049个字节,图片数据4030个字节+首尾两部分共10个字节,那多出来的9个字节是什么火眼金睛的Unyielding ● L发现了正确的开始位置为上图红色方块处,碰巧多出来的是九个字节,所以多出来的就不是这一张图片的内容,所以可以猜想程序没有停止的原因是上一次图片还没读完我就手动停止,所以留下了数据,上一次没有读完的内容,这一次读到了。
字节串和字符串都可以切片。直接切出来保存。
def getphoto(self): """ 拍照并且保存图片 :return: """ # self.reset() # 1、停止帧刷新 # self.stoprefresh() # 获取图片长度 # 返回字节长度用于整合命令,表示图片的总字节数 length = self.getlength_bytes() # 返回整形,表示图片的总字节数 len = self.bytesToInt(length) print("158h,len:", len) # 拍照 cmd = GET_PHOTO_START_CMD + length + GET_PHOTO_END_CMD print("159hcmd", cmd) self.ser.write(cmd) readall = self.ser.readall() readall_len = readall.__len__() differ = readall_len - len - 10 if differ != 0: res = readall[differ + 5:-5] print("172h:", res) self.savephoto(res) else: res = readall[5:-5] print("175h:", res) self.savephoto(res) # 关闭串口 self.ser.close() # 恢复刷新 # self.recover_refresh()
成功输出结果:
为了方便debug,停帧回复帧都是手动发送的,剩下的问题就是把注释打开试一试能不能成功组合成一个函数,发现有的命令会读空,所以可以推断:一定又是前一个命令里面又留下来什么还没有被读取的字节造成读到的内容篡位了。
每一次执行完命令后看一看还有没有遗留字节,把剩余字节都取出来,然后differ的判断都不需要了。【读者看到的代码都是最新版本的,此处我添加了left和print到对应函数中】,处理结果打印到控制台:
def getphoto(self): """ 拍照并且保存图片 :return: """ # 1、停止帧刷新 self.stoprefresh() # 获取图片长度 # 返回字节长度用于整合命令,表示图片的总字节数 length = self.getlength_bytes() # 返回整形,表示图片的总字节数 len = self.bytesToInt(length) print("161h,The function'{}' is running. len:{}".format(sys._getframe().f_code.co_name, len)) # 拍照 cmd = GET_PHOTO_START_CMD + length + GET_PHOTO_END_CMD print("165h,The function'{}' is running. cmd:{}".format(sys._getframe().f_code.co_name, cmd)) self.ser.write(cmd) readall = self.ser.readall() readall_len = readall.__len__() differ = readall_len - len - 10 if differ != 0: res = readall[differ + 5:-5] print("161h,The function'{}' is running. res:{}".format(sys._getframe().f_code.co_name, res)) self.savephoto(res) else: res = readall[5:-5] print("161h,The function'{}' is running. res:{}".format(sys._getframe().f_code.co_name, res)) self.savephoto(res) # 恢复刷新 self.recover_refresh()
输出图片结果(拍的是导线没有聚焦)
# 这个测试应该怎么写? 有图片就输出并且可以打开就可以了惹?有人能教教我? def test_getphoto(self): pass
4 反思犯了很多
‘我觉得'的错误,我觉得这个值是什么,多打断点看清楚,那一段演示代码里面因为协议写了是五个字节,isreply我已经读了4个字节再读一个一定是0x00,后来打印那一行返回的值是0x04这才为猜想读到上一张图作下铺垫。
当你写的很复杂超过20行的逻辑代码就知道一定是错了。--UnyieldingL
编码方面的内容耗费了很长时间,就在反思的时候发现了decode("hex")。惹?
list='aabbccddee' hexer=list.decode("hex") print hexer
打印日志要详细。 每一个变量涉及的变量长度函数名,此时哪个函数运行。
还有一个问题,pycharm还是没有自己停止,打印某个线程的堆栈。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
免责声明:本站资源来自互联网收集,仅供用于学习和交流,请遵循相关法律法规,本站一切资源不代表本站立场,如有侵权、后门、不妥请联系本站删除!
《魔兽世界》大逃杀!60人新游玩模式《强袭风暴》3月21日上线
暴雪近日发布了《魔兽世界》10.2.6 更新内容,新游玩模式《强袭风暴》即将于3月21 日在亚服上线,届时玩家将前往阿拉希高地展开一场 60 人大逃杀对战。
艾泽拉斯的冒险者已经征服了艾泽拉斯的大地及遥远的彼岸。他们在对抗世界上最致命的敌人时展现出过人的手腕,并且成功阻止终结宇宙等级的威胁。当他们在为即将于《魔兽世界》资料片《地心之战》中来袭的萨拉塔斯势力做战斗准备时,他们还需要在熟悉的阿拉希高地面对一个全新的敌人──那就是彼此。在《巨龙崛起》10.2.6 更新的《强袭风暴》中,玩家将会进入一个全新的海盗主题大逃杀式限时活动,其中包含极高的风险和史诗级的奖励。
《强袭风暴》不是普通的战场,作为一个独立于主游戏之外的活动,玩家可以用大逃杀的风格来体验《魔兽世界》,不分职业、不分装备(除了你在赛局中捡到的),光是技巧和战略的强弱之分就能决定出谁才是能坚持到最后的赢家。本次活动将会开放单人和双人模式,玩家在加入海盗主题的预赛大厅区域前,可以从强袭风暴角色画面新增好友。游玩游戏将可以累计名望轨迹,《巨龙崛起》和《魔兽世界:巫妖王之怒 经典版》的玩家都可以获得奖励。
更新日志
- 【雨果唱片】中国管弦乐《鹿回头》WAV
- APM亚流新世代《一起冒险》[FLAC/分轨][106.77MB]
- 崔健《飞狗》律冻文化[WAV+CUE][1.1G]
- 罗志祥《舞状元 (Explicit)》[320K/MP3][66.77MB]
- 尤雅.1997-幽雅精粹2CD【南方】【WAV+CUE】
- 张惠妹.2007-STAR(引进版)【EMI百代】【WAV+CUE】
- 群星.2008-LOVE情歌集VOL.8【正东】【WAV+CUE】
- 罗志祥《舞状元 (Explicit)》[FLAC/分轨][360.76MB]
- Tank《我不伟大,至少我能改变我。》[320K/MP3][160.41MB]
- Tank《我不伟大,至少我能改变我。》[FLAC/分轨][236.89MB]
- CD圣经推荐-夏韶声《谙2》SACD-ISO
- 钟镇涛-《百分百钟镇涛》首批限量版SACD-ISO
- 群星《继续微笑致敬许冠杰》[低速原抓WAV+CUE]
- 潘秀琼.2003-国语难忘金曲珍藏集【皇星全音】【WAV+CUE】
- 林东松.1997-2039玫瑰事件【宝丽金】【WAV+CUE】