Python基于周立功盒子接收特定报文信号并实时绘制折线图(二)
一、背景
根据在上一篇文件Python基于周立功盒子接收特定报文信号并实时绘制折线图(一)的基础上需要做一些优化,原因是,因为取数据和画图都在同一个线程中进行,这有可能导致程序出现问题,所以想到了使用消费者和开发者模型来优化一下代码
二、实现难点
- 由于我们调用周立功盒子使用的是ZLG给的DLL库,生产的数据便是我们接受的CAN通道里面获取的数据,必然要掉用DLL库,但是生产者的线程中,无法直接将第三方库的句柄当做参数传进去使用;;
- 我们想要在消费者模型中使用matplotlib进行画图,以便于能将接受数据和画图分开进行,但是却发现matplotlib的刷新和展示无法在子线程中调用
三、给出解决方案
- 针对生产者模型中无法使用第三方库,那我就直接把主进程的主线程作为生产者模型的线程,换句话说,我不用单独实现创建一个线程供生产者使用,就在主线程中进行数据的接受并插入列表中,这样就解决了线程中不能直接使用第三方库的问题;
- 针对消费者模型中无法直接使用matplotlib的刷新和展示,我便不在消费者线程中展示刷新,因为刷新前是需要数据的,需要plot将数据添加到缓存区,以便展示使用,那我就在消费者模型中拿到数据先使用plot把数据先缓存到画布上,然后再到主进程中来刷新。
四、实现
结构跟上篇提到的Python基于周立功盒子接收特定报文信号并实时绘制折线图(一)是一样的,我们只是新改了业务代码zlg_test.py文件,以下是zlg_test.py文件的实现
import sys
from multiprocessing import JoinableQueue, Process
import time
from zlgcan import *
import threading
import matplotlib.pyplot as plt
import csv, queue
def input_thread():
input()
def config_and_start_zlgcan(zcanlib):
testcantype = 1 # 0:CAN; 1:canfd
handle = zcanlib.OpenDevice(ZCAN_USBCANFD_MINI, 0, 0)
if handle == INVALID_DEVICE_HANDLE:
print("Open CANFD Device failed!")
exit(0)
print("device handle:%d." % (handle))
info = zcanlib.GetDeviceInf(handle)
print("Device Information:\n%s" % (info))
# set auto send obj
AutoCAN_A = ZCAN_AUTO_TRANSMIT_OBJ()
AutoCAN_B = ZCAN_AUTO_TRANSMIT_OBJ()
AutoCAN_A.enable = 1 # enable
AutoCAN_A.index = 0
AutoCAN_A.interval = 200 # ms
AutoCAN_A.obj.frame.can_id = 0x100
AutoCAN_A.obj.transmit_type = 0
AutoCAN_A.obj.frame.eff = 0
AutoCAN_A.obj.frame.rtr = 0
AutoCAN_A.obj.frame.can_dlc = 8
for j in range(AutoCAN_A.obj.frame.can_dlc):
AutoCAN_A.obj.frame.data[j] = j
AutoCAN_B.enable = 1 # enable
AutoCAN_B.index = 1
AutoCAN_B.interval = 200 # ms
AutoCAN_B.obj.frame.can_id = 0x300
AutoCAN_B.obj.transmit_type = 0
AutoCAN_B.obj.frame.eff = 0
AutoCAN_B.obj.frame.rtr = 0
AutoCAN_B.obj.frame.can_dlc = 8
for j in range(AutoCAN_B.obj.frame.can_dlc):
AutoCAN_B.obj.frame.data[j] = j
AutoCAN_B_delay = ZCANFD_AUTO_TRANSMIT_OBJ_PARAM()
AutoCAN_B_delay.index = AutoCAN_B.index
AutoCAN_B_delay.type = 1
AutoCAN_B_delay.value = 100
# Start CAN
chn_handle = canfd_start(zcanlib, handle, 0, AutoCAN_A, AutoCAN_B, AutoCAN_B_delay)
print("channel handle:%d." % (chn_handle))
return handle, chn_handle
# USBCANFD-MINI Demo
def com_extract_signal(endian, bit_pos, bit_size, src_buf, factor, offset):
byte_no = bit_pos // 8
if (((byte_no * 8) + 8) - bit_pos) <= bit_size:
total_bits_copied = ((byte_no * 8) + 8) - bit_pos
a_data = src_buf[byte_no] >> (bit_pos - (byte_no * 8))
else:
a_data = src_buf[byte_no] >> (bit_pos - (byte_no * 8))
a_data = a_data & (~(0xFF << bit_size))
total_bits_copied = bit_size
while total_bits_copied < bit_size:
bits_left = bit_size - total_bits_copied
if endian != 1: # big_endian == 1
byte_no += 1
else:
byte_no -= 1
if bits_left >= 8:
a_data = (src_buf[byte_no] << total_bits_copied) | a_data
total_bits_copied = total_bits_copied + 8
else:
a_data = ((src_buf[byte_no] & (0xFF >> (8 - bits_left))) << total_bits_copied) | a_data
total_bits_copied = total_bits_copied + bits_left
return a_data * factor + offset
def creat_fig():
fig = plt.figure('CPU Load Monitor', figsize=(16, 9))
fig.suptitle(' J6VL3 ADU CPU Load Monitor ', fontsize='x-large')
ax = fig.add_subplot(6, 1, 1)
ax.plot([], [], 'bo-', markersize=1, linewidth=0.5, label='Core0 Cpuload')
ax.legend()
bx = fig.add_subplot(6, 1, 2)
bx.plot([], [], 'ro-', markersize=1, linewidth=0.5, label='Core1 Cpuload')
bx.legend()
cx = fig.add_subplot(6, 1, 3)
cx.plot([], [], 'go-', markersize=1, linewidth=0.5, label='Core2 Cpuload')
cx.legend()
dx = fig.add_subplot(6, 1, 4)
dx.plot([], [], 'co-', markersize=1, linewidth=0.5, label='Core3 Cpuload')
dx.legend()
ex = fig.add_subplot(6, 1, 5)
ex.plot([], [], 'mo-', markersize=1, linewidth=0.5, label='Core4 Cpuload')
ex.legend()
fx = fig.add_subplot(6, 1, 6)
fx.plot([], [], 'yo-', markersize=1, linewidth=0.5, label='Core5 Cpuload')
fx.legend()
fx.set_xlabel("Sampling: 1Hz")
return ax, bx, cx, dx, ex, fx
def get_mess_and_parse_sig(zcanlib, chn_handle):
rcv_canfd_num = zcanlib.GetReceiveNum(chn_handle, ZCAN_TYPE_CANFD)
if rcv_canfd_num:
# print("Receive CANFD message number:%d" % rcv_canfd_num)
rcv_canfd_msgs, rcv_canfd_num = zcanlib.ReceiveFD(chn_handle, rcv_canfd_num, 1000)
for i in range(rcv_canfd_num):
if rcv_canfd_msgs[i].frame.can_id == 0x101:
return rcv_canfd_msgs[i]
def get_sig_values(mess_info):
core0_cpuload = com_extract_signal(0, 88, 16, mess_info.frame.data, 0.1, 0)
core1_cpuload = com_extract_signal(0, 104, 16, mess_info.frame.data, 0.1, 0)
core2_cpuload = com_extract_signal(0, 120, 16, mess_info.frame.data, 0.1, 0)
core3_cpuload = com_extract_signal(0, 136, 16, mess_info.frame.data, 0.1, 0)
core4_cpuload = com_extract_signal(0, 152, 16, mess_info.frame.data, 0.1, 0)
core5_cpuload = com_extract_signal(0, 168, 16, mess_info.frame.data, 0.1, 0)
data_list = list([core0_cpuload, core1_cpuload, core2_cpuload, core3_cpuload, core4_cpuload, core5_cpuload])
return data_list
def put_data_in_y_list(y, data):
y[0].append(data[0])
y[1].append(data[1])
y[2].append(data[2])
y[3].append(data[3])
y[4].append(data[4])
y[5].append(data[5])
def draw_and_updata_figs(ax, bx, cx, dx, ex, fx, x, y, plt):
ax.plot(x, y[0], 'bo-', markersize=1, linewidth=0.5)
bx.plot(x, y[1], 'ro-', markersize=1, linewidth=0.5)
cx.plot(x, y[2], 'go-', markersize=1, linewidth=0.5)
dx.plot(x, y[3], 'co-', markersize=1, linewidth=0.5)
ex.plot(x, y[4], 'mo-', markersize=1, linewidth=0.5)
fx.plot(x, y[5], 'yo-', markersize=1, linewidth=0.5)
class Consumer(threading.Thread):
def __init__(self, q, ax, bx, cx, dx, ex, fx, y, plt):
# 从python3 开始,继承有了极大的改善
super().__init__()
# 我喜欢用单下划线去标识私有变量
self._queue = q
self.ax = ax
self.bx = bx
self.cx = cx
self.dx = dx
self.ex = ex
self.fx = fx
self.y = y
self.plt = plt
# 继承了Thread类后,需要重写run方法来自定义事件
def run(self):
x = []
count = 0
while True:
# msg即是我们说的消息,也是仓库中的货物
msg = self._queue.get()
# 我会在生产者中加入quit关键字,保证程序能够自动退出
if isinstance(msg, str) and msg == 'quit':
break
count += 1
x.append(count)
print(f"I'm a thread, and I received {msg}!!")
put_data_in_y_list(self.y, msg)
draw_and_updata_figs(self.ax, self.bx, self.cx, self.dx, self.ex, self.fx, x, self.y, self.plt)
print('Bye byes!')
if __name__ == "__main__":
time_max = 3600 # 秒
time_flag = 0
x = []
y = [[], [], [], [], [], []]
# 创建ZLCCAN对象
zcanlib = ZCAN()
# 创建zlccan句柄和通道句柄
handle, chn_handle = config_and_start_zlgcan(zcanlib)
q = queue.Queue() # 创建队列
rec_msg_thread = threading.Thread(target=input_thread)
rec_msg_thread.start()
# 创建六个坐标系的画布
ax, bx, cx, dx, ex, fx = creat_fig()
# 设置CSV文件格式并创建csv_writer
sys_time = '{}_{}_{}_{}_{}_{}'.format(time.localtime().tm_year, time.localtime().tm_mon,
time.localtime().tm_mday, time.localtime().tm_hour,
time.localtime().tm_min, time.localtime().tm_sec)
log_path = './LOG' + sys_time + '.csv'
fp = open(log_path, 'w')
csv_writer = csv.writer(fp)
csv_writer.writerow(['Time', 'Core0 Cpuload', 'Core1 Cpuload', 'Core2 Cpuload',
'Core3 Cpuload', 'Core4 Cpuload', 'Core5 Cpuload'])
# 初始化一个消费者实例
worker = Consumer(q, ax, bx, cx, dx, ex, fx, y, plt)
# 开启消费者线程
worker.start()
# 获取message并且解析信号值
while True:
rcv_canfd_num = zcanlib.GetReceiveNum(chn_handle, ZCAN_TYPE_CANFD)
if rcv_canfd_num:
# print("Receive CANFD message number:%d" % rcv_canfd_num)
rcv_canfd_msgs, rcv_canfd_num = zcanlib.ReceiveFD(chn_handle, rcv_canfd_num, 1000)
for i in range(rcv_canfd_num):
if rcv_canfd_msgs[i].frame.can_id == 0x101:
time_flag += 1
mess_info = rcv_canfd_msgs[i]
if time_flag >= time_max:
q.put('quit')
worker.join()
rec_msg_thread.join()
break
else:
data_list = get_sig_values(mess_info)
write_data = [mess_info.timestamp] + data_list
csv_writer.writerow(write_data)
q.put(data_list)
plt.pause(1) # 暂停1秒以更新图像
plt.ioff()
else:
if not rec_msg_thread.is_alive():
break
q.put('quit')
worker.join()
plt.savefig('./PNG/result_{}.png'.format(sys_time))
plt.close()
# Close CAN
ret = zcanlib.ResetCAN(chn_handle)
if ret == 1:
print("ResetCAN success! ")
# Close Device
ret = zcanlib.CloseDevice(handle)
if ret == 1:
print("CloseDevice success! ")
fp.close()
rec_msg_thread.join()