【python-can】CANの送受信をするclassを作った

皆さん、お疲れ様です。相も変わらずCAN通信について勉強してます。今日で7回目ですね。

今回はCAN通信の送受信する内容をclass化しました。

ある程度python-can使えるようになってきたら、「えっとぉ、BUSを繋いでぇ、メッセージ作ってぇ、送ってぇ、、、」っていちいち書くのがめんどくさくなってきました。そこでclass化しました。

IDと送信周期を指定したらそのIDの信号を送信し続けてくれる。受信もシミュレーション中は受信してくれるみたいな動きをするやつです。

これで複数IDのメッセージを簡単に量産できるようになるぞってね。そんな内容です。

スポンサーリンク

具体的にどういう動作するclass?

受信IDと送信IDと送信周期を指定してclass化します。

開始(start関数)すると受信モニター用のスレッドが開始されて受信IDの信号を受信します。これはイベント信号受信時に送信する。みたいな動作を実現させたくてつけた機能です。

開始すると送信用スレッドが開始されて、受信データをそのまま定期的送信し続ける。

受信データが更新されていない時は前回値を保持して、送信する。

みたいな感じです。では実際のコード。

実際のコード

import time
import threading
import can

class ThreadCAN:
    def __init__(self, id_rx, id_tx, period):
        self.ext_requested = False
        self.id_rx = id_rx
        self.id_tx = id_tx
        self.period = period
        #バス接続
        self.bus = can.interface.Bus(bustype='vector',
                                     channel=0,
                                     bitrate=500000,
                                     app_name='python-can')

        #msgの初期化
        self.msg = can.Message(arbitration_id = id_tx,
                               data = [0,0,0,0,0,0,0,0],
                               is_extended_id = False)
        self.recv_msg =  can.Message(arbitration_id = id_rx,
                                     data = [0,0,0,0,0,0,0,0],
                                     is_extended_id = False)


    def start(self):
        self.exit_requested = False
        self.threadsend = threading.Thread(target = self.thread_sendtask)#送信用スレッド
        self.threadrecv = threading.Thread(target = self.thread_recvtask)#受信用スレッド
        self.threadsend.start()
        self.threadrecv.start()
    
    def stop(self):
        self.exit_requested = True
        if self.threadsend.isAlive():
            self.threadsend.join()
        if self.threadrecv.isAlive():
            self.threadrecv.join()

    def thread_sendtask(self):
        while self.exit_requested == False:
            self.msg = can.Message(arbitration_id = self.id_tx,
                                   #data = [0,0,0,0,0,0,0,0],
                                   data = self.recv_msg.data,
                                   is_extended_id = False)
            self.bus.send(self.msg)
            time.sleep(self.period)

    def thread_recvtask(self):
        while self.exit_requested == False:
            self.recv_msg = self.bus.recv(timeout=3)

    def shutdown(self):
      self.stop()
      self.bus.shutdown()

if __name__ == '__main__':

    #RxID:465 TxID:123 period = 1sec
    UnitACAN = ThreadCAN(0x465, 0x123, 1)
    UnitACAN.start()

    print('Waiting for payload - maximum 5 sec')

    t1 = time.time()

    #試しに送信 ID465で送信してみる。
    bus = can.interface.Bus(bustype='vector',
                            channel=0,
                            bitrate=500000,
                            app_name='python-can')    

    msg = can.Message(arbitration_id = 0x465,
                 data= [0,2,3,4,5,6,7,8],
                 is_extended_id = False)

    count = 0
    while time.time() - t1 < 10:
        msg = can.Message(arbitration_id = 0x465,
                     data= [count,2,3,4,5,6,7,8],
                     is_extended_id = False)
        bus.send(msg)
        count = count + 1
        time.sleep(2)

    bus.shutdown()
    time.sleep(0.2)

    print("Exiting")
    UnitACAN.shutdown()

軽く解説

init関数で初期化してます。受信ID(rx_id)と送信ID(tx_id)と送信周期(period)[s]を指定します。

以下はまぁいつものpython-canの仮想BUSに繋いで、一応送受信のメッセージ変数を初期化しています。

        #バス接続
        self.bus = can.interface.Bus(bustype='vector',
                                     channel=0,
                                     bitrate=500000,
                                     app_name='python-can')

        #msgの初期化
        self.msg = can.Message(arbitration_id = id_tx,
                               data = [0,0,0,0,0,0,0,0],
                               is_extended_id = False)
        self.recv_msg =  can.Message(arbitration_id = id_rx,
                                     data = [0,0,0,0,0,0,0,0],
                                     is_extended_id = False)

start関数が送受信用のスレッドを開始する関数です。exit_requestedはスレッド内のwhile文のループから抜け出すための変数です。スレッドを終わらすときにTrueにします。

    def start(self):
        self.exit_requested = False
        self.threadsend = threading.Thread(target = self.thread_sendtask)#送信用スレッド
        self.threadrecv = threading.Thread(target = self.thread_recvtask)#受信用スレッド
        self.threadsend.start()
        self.threadrecv.start()

んで、classの動作を終わらすのがstop関数。exit_requestedをTrueにしてスレッドのループから抜け出します。if文以下は、ぶっちゃけようわかってない。

    def stop(self):
        self.exit_requested = True
        if self.threadsend.isAlive():
            self.threadsend.join()
        if self.threadrecv.isAlive():
            self.threadrecv.join()

↓ほぼここがメイン。送信用のthread_sendtask関数。まぁやっていることは単純で、受信したデータ(recv_msg.data)を送信して一定周期(period)寝というのを繰り返しています。

受信用のthread_recvtask関数。これも単純でbus.recvデータを保持するために変数に入れています。if文でIDとかを指定しといたら特定のIDだけを保存するみたいなことが出来ると思いますが、今回はめんどくさくなってやってませんね。timeoutは今回受信する信号の周期を2secとするので+1secして3secとしてます。

    def thread_sendtask(self):
        while self.exit_requested == False:
            self.msg = can.Message(arbitration_id = self.id_tx,
                                   #data = [0,0,0,0,0,0,0,0],
                                   data = self.recv_msg.data,
                                   is_extended_id = False)
            self.bus.send(self.msg)
            time.sleep(self.period)

    def thread_recvtask(self):
        while self.exit_requested == False:
            self.recv_msg = self.bus.recv(timeout=3)

↓最後はシャットダウン用の関数。whileから抜けるstop関数を実行してBUSをシャットダウンして終わりです。

    def shutdown(self):
      self.stop()
      self.bus.shutdown()

以下はシミュレーションの入力を記述しています。

UnitACANというclass名で作って、スタートしています。

while文は10sec間ID465を送り続けるって記述です。先頭データだけをインクリメントしています。実行結果をみて、意図通り動いているか?をわかりやすくするためです。

10sec間送信し続けたら、終わりって感じです。

if __name__ == '__main__':

    #RxID:465 TxID:123 period = 1sec
    UnitACAN = ThreadCAN(0x465, 0x123, 1)
    UnitACAN.start()

    print('Waiting for payload - maximum 5 sec')

    t1 = time.time()

    #試しに送信 ID465で送信してみる。
    bus = can.interface.Bus(bustype='vector',
                            channel=0,
                            bitrate=500000,
                            app_name='python-can')    

    msg = can.Message(arbitration_id = 0x465,
                 data= [0,2,3,4,5,6,7,8],
                 is_extended_id = False)

    count = 0
    while time.time() - t1 < 10:
        msg = can.Message(arbitration_id = 0x465,
                     data= [count,2,3,4,5,6,7,8],
                     is_extended_id = False)
        bus.send(msg)
        count = count + 1
        time.sleep(2)

    bus.shutdown()
    time.sleep(0.2)

    print("Exiting")
    UnitACAN.shutdown()

なので、想定動作としては

・ID465を2sec周期で送り続ける。先頭データだけインクリメントする。

・ID123は1sec周期で送り続ける。データは受信した値をそのまま送信する。

実行結果

では実行結果をBusmasterでモニターした結果を以下に示します。

ぱっと見では良く分からないので、まずはテスト用で送信したID465信号を見てみます。赤枠で囲みました。

周期が2secごとに送信していて、先頭データがインクリメントされています。意図通り問題なしです。

次はclass側の送信データID123です。青枠で囲みました↓。

周期が1secごとに送信していて、受信値を送信しています。意図通りで問題ありません。

はい。今日はそんなとこです。誰かの何かの参考になれば幸いです。

最後までお読みいただきありがとうございました!!!