文艳格
长安大学工程机械学院 公开处刑抄我文章不加引用
因为店家只给了我两页纸的vc0706通信协议,许多细节我还是不清楚,写得也没有特别优雅,大家就凑合看吧。
目录
1 硬件设备
2 serial安装
3 实现串口通信
3.1 发现端口
3.2 发送命令
3.2.1 协议格式
3.2.2 serial传送的方式
3.3 获取版本号(hello world)
3.4 复位
3.5 照相
3.5.1 停止当前帧刷新
3.5.2 获娶图片长度
3.5.3 恢复帧更新
3.5.4 拍照
4 反思
第一次安装的是serial的包导包的时候发现下载错了,正确应该是pyserial。安装后直接import就可以了。
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对象的代码,不管你是window还是linux都可以不会报错:
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.")
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()
结果:
按照协议一步一步操作
#在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')
结果:通过测试
#在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
*测试和运行结果不一样。
花了一点时间找到原因了,单元测我都是点击前面绿色的小箭头,以为只是运行当前的测试函数的内容,但是我发现它把其他的函数都运行了。所以要把之前的测试函数注释掉得到的结果就一样了。
测试通过。
这一步每一次拍照前必须执行一次。因为读照片命令的时候会出现麻烦。这一步是有意义的,就是当你发现图片很大,的时候正常大小就两个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())
此时读完后还是要小心会有后序的内容没有读完也会影响后序的读buffer。
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')
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())
在这里卡了很长时间,不知道为什么长度是不确定的,每一次读的长度都没读完,看代码。
下面代码只是演示
#下面代码只是演示不在最终版本中
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)
现象:
我发现此时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
更新2月13日
通过无数种方式打印各种信息,纳闷了没有错啊,一个进程,单线程,都打印了saosao进程结束了啊!!没有没关闭的进程或者是线程,with不是自带close吗?我不放心也加了close(),我调用完getphoto以后连串口都关闭了,到底是什么地方需要输入?明明没有一个地方需要输入的地方。最后我做了一个大胆的猜想,我新建了一个demo.py试一下运行一句helloworld发现helloworld也是停不了,我猜想是pycharm自带输入导致程序不停止,果然我解决了这个问题,我的九尾狐奶奶太恶毒了!
list='aabbccddee'
hexer=list.decode("hex")
print hexer
技术指导:UnyieldingL
程序媛鼓励师:风向晚。