Raspberry PiでCAN通信

# はじめに
Raspberry Piに慣れるために、以前、NUCLEO L476RGとCANシールドで作成したボードとRaspberry PiでCAN通信を行うことにしました。

# 使用機器
|メーカ|型格|
|:-:|:-:|
|Raspberry Pi|Raspberry Pi 4 Model B|
|inno-maker|USB2CAN|

# 作りたいもの
+ Raspberry Pi上で動くGUIアプリケーション
+ CAN通信ができる
これを実現するためにPythonを使うことを使うことにしました。GUIを実現する方法はいくつかあるようでしたがGoogle Trendsで上位であったTkinterを使うことにしました。

# Pythonの準備
Raspberry Piには標準でPython3がインストールされているのでアップデートするだけ使用できました。
エラーが発生したのでpip（Pythonのパッケージを管理するツール）のアップデートを行いました。
```
sudo pip3 install --upgrade pip
```

# python-canの準備
PythonでCAN通信を行うためにpython-canをインストールしました。
```
pip install python-can
```
inno-markerのユーザーズマニュアルにツールをインストールすることとあったので下記をインストールしました。
```
sudo apt-get install can-utils
```
python-canをインストールすればこちらは不要なのかもしれません。

# Tkinterの準備
Tkinterのインストールは必要ありませんでした。

# GUIアプリケーション
NUCLEOから送られてくるCANデータを1000ミリ秒ごとにポーリングしています。
通信速度は250kbpsとなります。

``` tk.py
import tkinter as tk
import tkinter.filedialog
from tkinter.scrolledtext import ScrolledText
from UsbCan import UsbCan

def canopen():
    openbtn['state'] = 'disable'
    closebtn['state'] = 'active'
    fopenbtn['state'] = 'active'
    textbox.delete(0, 'end')
    can.open()

    global id
    id = root.after(1000, polling)
    
    return

def canclose():
    root.after_cancel(id)

    openbtn['state'] = 'active'
    closebtn['state'] = 'disable'
    fopenbtn['state'] = 'disable'
    dwlbtn['state'] = 'disable'
    textbox.delete(0, 'end')
    can.close()

    return

def fileOpen():
    fname = tk.filedialog.askopenfilename(filetypes=[('binary', '*.bin')], initialdir='./')

    if fname != None:
        textbox.insert('end', fname)
        dwlbtn['state'] = 'active'

    return

def polling():
    msg = can.receive()

    if msg != None:
        text.insert('end', msg)
        text.insert('end', '\n')

    global id
    id = root.after(100, polling)

    return

can = UsbCan()

root = tk.Tk()
root.title('USB 2 CAN')

# set window size
root.geometry('680x480')

# frame
frame = tk.Frame(root, relief='flat')
frame.pack(fill='x', side='top')

# create open button
openbtn = tk.Button(frame, text='open', state='active', command=canopen)
openbtn.pack(fill='x', side='left')

# create close button
closebtn = tk.Button(frame, text='close', state='disable', command=canclose)
closebtn.pack(fill='x', side='left')

# frame
fileFrame = tk.Frame(root, relief='flat')
fileFrame.pack(fill='x', side='top')

# create file open button
fopenbtn = tk.Button(fileFrame, text='file', state='disable', command=fileOpen)
fopenbtn.pack(fill='x', side='left')

# create text
textbox = tk.Entry(fileFrame, width=80)
textbox.pack(fill='both', side='left')

# create download button
dwlbtn = tk.Button(root, text='Download', state='disable')
dwlbtn.pack(side='top')

# create scrolled text
text = ScrolledText(root)
text.pack(fill='both', side='top')

# loop
root.mainloop()
```
```UsbCan.py
import subprocess
import os
import can

class UsbCan:

    """constructor"""
    def __init__(self):
        self.__state = False
        return

    """destructor"""
    def __del__(self):
        if self.__state == True:
            os.system('sudo ifconfig can0 down')

        return

    """open can port"""
    def open(self):
        self.__state = True
        os.system('sudo ip link set can0 type can bitrate 250000')
        os.system('sudo ifconfig can0 up')

        self.__can0 = can.interface.Bus(channel='can0', bustype='socketcan')

        return

    """close can port"""
    def close(self):
        self.__state = False
        os.system('sudo ifconfig can0 down')
        return

    """send data"""
    def send(self, id, data, ext=False):
        if self.__state == True:
            msg = can.Message(id, data, ext)
            self.__can0.send(msg)

        return

    """receive data"""
    def receive(self):
        if self.__state == True:
            msg = self.__can0.recv(1.0)
        else:
            msg = None

        return msg
```

# 編集後記
+ os.system と subprocess.call について
Pythonで他のプログラム（コマンド）を実行するための標準ライブラリであるos.systemですが、これからはsubprocess.callに置き換わるということで使ってみましたが動作しませんでした。
+ socketについて
inno-markerのサンプルでは
``` sample.py
# Bind the socket to 'can0'.
can0 = can.interface.Bus(cahnnel='can0', bustype='socketcan_ctypes')
```
とありましたが、socketcan_ctypesがないとエラーになりました。
socketcanにすることで動作しました。
socketcan_nativeを使用すればいいとの情報もありどのように決めるのか分かりませんでした。

python-canの下記のドキュメントからsocketcanにするのだろうと考えています。
>python-can versions before 2.2 had two different　implementations named socketcan_ctypes and　socketcan_native. These were removed in version 4.0.0 after a deprecation period.
>
>DeepLの翻訳
>2.2 より前のバージョンの python-can には、socketcan_ctypes と socketcan_native という 2 種類の実装がありました。これらは非推奨期間を経て、バージョン 4.0.0 で削除されました。

# 参考サイト
[Python3](https://okashimoze.jp/blog/raspberry-pi/385.html)
[Python-CAN](https://python-can.readthedocs.io/en/master/installation.html#installing-python-can-in-development-mode)
[Tkinter](https://python.keicode.com/advanced/tkinter-install.php)
[SocketCAN](https://python-can.readthedocs.io/en/master/interfaces/socketcan.html)
[USB2CAN WiKi](http://wiki.inno-maker.com/display/HOMEPAGE/usb2can)
[subprocess](https://www.sejuku.net/blog/65485)