Python專案實踐:串列埠字串資料的讀取、分割與儲存到csv檔案

Nekomimi_Cocoa發表於2020-12-15

程式功能

	1、從串列埠讀取迴圈讀取如同$4567.0,12.3,12.3#的字串,字串開始符、分隔符、結束符是規定好的
	2、將字串分割為如同4567.0、12.3、12.3的浮點型資料
	3、將資料儲存到csv檔案中
	4、按esc結束執行

執行條件

Python 3.8.2

Python庫:
multiprocessing, csv, serial, os(python自帶)
keyboard 0.13.5
numpy 1.19.2

準備工作與說明

此程式原本開發的目的是將航模機載資料通過數傳(串列埠)傳到電腦上,供後期處理分析。

為了使串列埠除錯方便,可以通過使用arduino或者其他微控制器向串列埠按固定時間間隔輸入資料,來模模擬實數傳環境。如果有其他模擬方式也可,這不是重點。

為了便於終止程式、儲存文件(強制終止可能導致csv檔案損壞),採用雙執行緒的方式同時儲存資料和監聽鍵盤輸入。(才疏學淺,沒有找到python中斷的方法,並且目前瞭解到的鍵盤監聽不是很麻煩就是擴充性不強,因此選用雙執行緒通訊的方法解決退出的問題。)

現在期末烤漆太緊張,有空再回來完善。

程式碼解讀

import 部分

from multiprocessing import Process, Queue
import keyboard, csv, serial, os
import numpy as np

從串列埠讀取資料(函式)

def get_numbers_from_serial(target_com, begin='\r\n', end='$', cut=','):
    '''
    從串列埠獲得float型別資料列表
    如果兩個分隔符cut相鄰或分隔符中間為無效字串(無法轉換為float的字串),在中間位置補-32768
    若起始符begin不符則返回空列表
    '''
    
    data = target_com.read_until(bytes(end, encoding='utf-8'))  #一直讀取直到遇到截止符end
    data = data.decode('utf-8','ignore')
    #print('gotten:', data)

    begin_len=len(begin)
    if (data[0:begin_len]!=begin):    #檢測begin是否與字串開頭相符
        return []

    cut_len = len(cut)
    pos = begin_len
    num_list = []
    label = True
    while (data[pos:] != end):
        num_str = ''

        #確認目標位及其後方不為cut和end
        while (data[pos:] != end and data[pos:pos+cut_len] != cut):
            label = True    #至少採集到了一個有效字元
            num_str = num_str+data[pos]
            pos = pos+1
        
        #根據label和num_str向num_list中賦值
        if label:
            try:
                num_list.append(float(num_str))
            except Exception:
                num_list.append(-32768) #num_str為無效字串
        else:
            num_list.append(-32768) #label為負,兩個cut相鄰
        
        if data[pos:] != end:
            pos = pos+cut_len
        label = False
    
    return num_list

對csv檔案的處理(函式)

def deal_with_csv_file(file_path='save_csv.csv', csv_head=[], mode='new'):
    '''
    csv檔案預處理
    file_path:檔案儲存的路徑
    mode兩種模式:
    'new'刪除原同名檔案(如果有)並新建檔案
    'add'在原檔案後續寫
    csv_head是以列表形式給出的csv檔案每列標題,只有在'new'模式才有用
    '''

    def create_csv(csv_head=[], path='save_csv.csv'):
        '''
        新建csv檔案
        '''
        with open(path, 'w', newline='') as f:
            csv_write = csv.writer(f)
            if csv_head!=[]:
                csv_write.writerow(csv_head)

    if mode=='new': #新建模式
        if os.path.isfile(file_path):
            os.remove(file_path)
        create_csv(csv_head, file_path)
    elif mode=='add':   #續寫模式
        if os.path.isfile(file_path)==False:
            print('原始檔缺失,自動新建')
            create_csv(csv_head, file_path)

將串列埠資料讀取並儲存到csv(函式,主程式)

def serial_to_csv(q):
    '''
    第一程式,從串列埠儲存資料到csv檔案
    '''

    #初始設定
    file_path = 'save_csv.csv'  #儲存檔名稱
    csv_head = ['速度A', '速度B', '速度C'] #資料含義
    mode = 'new'    #新建檔案還是在原有檔案上續寫
    # mode = 'add'
    ser = serial.Serial('COM4', baudrate=115200)    #串列埠資訊(名稱,波特率)
    ser_msg = ['\r\n$', '#', ',']   #串列埠符號規定,若有換行則需在原有開始符前加\r\n
    show = True #是否實時展示資料

    #csv檔案預處理
    deal_with_csv_file(file_path, csv_head, mode)
    
    #開始讀數並儲存
    with open(file_path,'a+', newline='') as f:
        csv_write = csv.writer(f)
        n = 0
        while q.empty():
            num_list=get_numbers_from_serial(ser, begin=ser_msg[0], end=ser_msg[1], cut=ser_msg[2])
            csv_write.writerow(num_list)
            if show:
                print('\r', num_list, end='')
            n = n+1
        print('\n總共儲存了', n-1, '個資料')

鍵盤中斷(函式,第二程式)

def key_board_listen(q):
    '''
    第二程式,鍵盤監聽充當中斷函式
    '''

    keyboard.wait('esc')
    q.put(1)

程式主體

if __name__ == "__main__":
    '''
    從串列埠儲存資料到csv檔案,具體引數設定在serial_to_csv中。
    按下esc停止儲存,程式退出
    '''

    q = Queue()
    p1 = Process(target=serial_to_csv, args=(q,))
    p2 = Process(target=key_board_listen, args=(q,))
    p1.start()
    p2.start()
    p1.join()
    p2.join()

全部程式碼

from multiprocessing import Process, Queue
import keyboard, csv, serial, os
import numpy as np


def get_numbers_from_serial(target_com, begin='\r\n', end='$', cut=','):
    '''
    從串列埠獲得float型別資料列表
    如果兩個分隔符cut相鄰或分隔符中間為無效字串(無法轉換為float的字串),在中間位置補-32768
    若起始符begin不符則返回空列表
    '''
    
    data = target_com.read_until(bytes(end, encoding='utf-8'))  #一直讀取直到遇到截止符end
    data = data.decode('utf-8','ignore')
    #print('gotten:', data)

    begin_len=len(begin)
    if (data[0:begin_len]!=begin):    #檢測begin是否與字串開頭相符
        return []

    cut_len = len(cut)
    pos = begin_len
    num_list = []
    label = True
    while (data[pos:] != end):
        num_str = ''

        #確認目標位及其後方不為cut和end
        while (data[pos:] != end and data[pos:pos+cut_len] != cut):
            label = True    #至少採集到了一個有效字元
            num_str = num_str+data[pos]
            pos = pos+1
        
        #根據label和num_str向num_list中賦值
        if label:
            try:
                num_list.append(float(num_str))
            except Exception:
                num_list.append(-32768) #num_str為無效字串
        else:
            num_list.append(-32768) #label為負,兩個cut相鄰
        
        if data[pos:] != end:
            pos = pos+cut_len
        label = False
    
    return num_list

def deal_with_csv_file(file_path='save_csv.csv', csv_head=[], mode='new'):
    '''
    csv檔案預處理
    file_path:檔案儲存的路徑
    mode兩種模式:
    'new'刪除原同名檔案(如果有)並新建檔案
    'add'在原檔案後續寫
    csv_head是以列表形式給出的csv檔案每列標題,只有在'new'模式才有用
    '''

    def create_csv(csv_head=[], path='save_csv.csv'):
        '''
        新建csv檔案
        '''
        with open(path, 'w', newline='') as f:
            csv_write = csv.writer(f)
            if csv_head!=[]:
                csv_write.writerow(csv_head)

    if mode=='new': #新建模式
        if os.path.isfile(file_path):
            os.remove(file_path)
        create_csv(csv_head, file_path)
    elif mode=='add':   #續寫模式
        if os.path.isfile(file_path)==False:
            print('原始檔缺失,自動新建')
            create_csv(csv_head, file_path)

def serial_to_csv(q):
    '''
    第一程式,從串列埠儲存資料到csv檔案
    '''

    #初始設定
    file_path = 'save_csv.csv'  #儲存檔名稱
    csv_head = ['速度A', '速度B', '速度C'] #資料含義
    mode = 'new'    #新建檔案還是在原有檔案上續寫
    # mode = 'add'
    ser = serial.Serial('COM4', baudrate=115200)    #串列埠資訊(名稱,波特率)
    ser_msg = ['\r\n$', '#', ',']   #串列埠符號規定,若有換行則需在原有開始符前加\r\n
    show = True #是否實時展示資料

    #csv檔案預處理
    deal_with_csv_file(file_path, csv_head, mode)
    
    #開始讀數並儲存
    with open(file_path,'a+', newline='') as f:
        csv_write = csv.writer(f)
        n = 0
        while q.empty():
            num_list=get_numbers_from_serial(ser, begin=ser_msg[0], end=ser_msg[1], cut=ser_msg[2])
            csv_write.writerow(num_list)
            if show:
                print('\r', num_list, end='')
            n = n+1
        print('\n總共儲存了', n-1, '個資料')

def key_board_listen(q):
    '''
    第二程式,鍵盤監聽充當中斷函式
    '''

    keyboard.wait('esc')
    q.put(1)


if __name__ == "__main__":
    '''
    從串列埠儲存資料到csv檔案,具體引數設定在serial_to_csv中。
    按下esc停止儲存,程式退出
    '''

    q = Queue()
    p1 = Process(target=serial_to_csv, args=(q,))
    p2 = Process(target=key_board_listen, args=(q,))
    p1.start()
    p2.start()
    p1.join()
    p2.join()

幫助、參考資料(文章)

程式間通訊(multiprocessing庫):

程式間的通訊:multiprocessing庫下的Pipe類與Queue類
python多程式的理解 multiprocessing Process join run
【python】詳解multiprocessing多程式-process模組(一)

鍵盤監聽:

Python 鍵盤/滑鼠監聽及控制

python串列埠的使用:

Python 之 Serial串列埠通訊 (可能不全)

csv檔案操作:

python 讀寫csv檔案(建立,追加,覆蓋)

os庫的使用(系統檔案操作):

python讀寫、建立檔案、資料夾等等

相關文章