マイコンのUARTコマンド制御をJenkins、Raspberry Pi、Pythonで行う

## 1. はじめに
「[ハードウェアをE2Eテストできないなんて誰が言った？ - IoTのテストを自動化するメソッド](https://builderscon.io/tokyo/2018/session/58b03ed2-91a5-4f94-84fd-363e799efa38)」を拝見[^1]したところ以下のような工夫が紹介されていました。

1. テストスクリプトをクラウドへ集約する
- テストのコントロールにJenkinsを使用する
- テストランナーにRaspberry Piを使用する
- センサやサーボなどの制御はRaspberry Piに接続したArduinoで行う

また、筆者は以前「[ArduinoとオシロスコープをPythonで制御して測定する](https://qiita.com/pbjpkas/items/bd483d4773468e26414c)」でマイコンのUARTコマンド制御や測定器の操作をPySerialやPyVISAで試したのですがこれらはRaspberry Piで簡単に動かせることがわかりました。

そこで、これまで筆者はJenkinsの環境を組んだりRaspberry Piを使ったりしたことがなかったため以下のようなテストベンチを構築し、初めてらしくLチカ[^2]を試しました。

![jenkins-rpi-arduinoJigu.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221758/5effb477-b9f7-3dd8-83f4-4099e36b95d9.png)

Windows 10のPCをJenkinsのMasterに、Raspberry Pi 3B+をJenkinsのSlaveに使用し、パイプラインでMasterのワークスペースに配置したスクリプトをSlaveで実行します。Raspberry PiにArduino Leonardo治具を接続しPySerialで治具へコマンドを送信してArduino Leonardoに実装されているLED[^3]を点滅させます。

## 2. Arduino Leonardo治具
詳しくは「[Arduino Leonardoで多目的ツールの製作](https://qiita.com/pbjpkas/items/97dbf835b0aab6725e94)」をご参照ください。

- コマンド制御
  - Arduino LeonardoのDigital Pin #0、#1に接続しているUSBシリアル変換器(秋月電子[AE-TTL-232R](http://akizukidenshi.com/catalog/g/gK-09951))を経由して行う
  - 通信速度は9600[bps]
  - 起動後、コマンドではない任意の文字(例:"x")の受信を以ってコマンド制御に遷移
- 搭載コマンド→[4.3コマンドモード](https://qiita.com/pbjpkas/items/97dbf835b0aab6725e94#43-%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89%E3%83%A2%E3%83%BC%E3%83%89)
  - Digital Pin #13に接続されているLEDの点灯：port 4 high
  - Digital Pin #13に接続されているLEDの消灯：port 4 low
- ソースコード→[7.スケッチ](https://qiita.com/pbjpkas/items/97dbf835b0aab6725e94#7-%E3%82%B9%E3%82%B1%E3%83%83%E3%83%81)

## 3. Raspberry Pi
### 3.1 ハードウェア
最低限の入出力としてワイヤレスのタッチパッド付きキーボードとHDMI接続の5インチ(800x480)液晶モニタをRaspberry Pi 3B+に接続しました。ROMイメージを焼くMicro SDカードはAmazon Choiceの適当なやつです。電源も5V 3Aを給電可能なものを選んでいます。

- [RSコンポーネントRaspberry Pi 3B+](https://www.amazon.co.jp/dp/B07BFH96M3/)
- [Ewin タッチパッド&マウスホイール搭載 2.4GHz ワイヤレスミニキーボード(JIS配列)](https://www.amazon.co.jp/dp/B079KCS2X7/)
- [Kuman 5インチ HDMI ディスプレイ 800*480 Raspberry Pi用 TFTモニタ タッチスクリーン 保護ケースセット SC5AC ](https://www.amazon.co.jp/dp/B07M6VM1B6/)
- [Samsung microSDカード32GB EVOPlus MB-MC32GA/ECO](https://www.amazon.co.jp/dp/B06XSV23T1/)
- [Raspberry Pi用電源セット(5V 3.0A)－Pi3フル負荷検証済](https://www.amazon.co.jp/dp/B01N8ZIJL8)

### 3.2 Raspbianのインストールと設定
- [Raspbian](https://www.raspberrypi.org/downloads/raspbian/)からRaspbian Stretch with desktop and recommended software(2019-04-08)をダウンロードしました。
- Micro SDカードのフォーマットに[SDメモリカードフォーマッター](https://www.sdcard.org/jp/downloads/formatter/)を使用しました。
- ROM焼きに[Win32 Disk Imager](https://ja.osdn.net/projects/sfnet_win32diskimager/)を使用しました。
- sshでログインできるようRaspberry Piの設定→インターフェース→SSHを有効にしました。

以下はお好みでどうぞ。

- VNCを使えるようにRaspberry Piの設定→インターフェース→VNCを有効にしました。
- vimをインストールしました。sudo apt-get install vim

### 3.3 PySerialのインストール
Raspberry PiにPySerialとPyVISA(3.4参照)をインストールします。

#### 3.3.1 インストール
pip install pyserial

#### 3.3.2 動作確認
- Arduino LeonardoのDigital Pin #0、#1に接続しているUSB-TTL変換器(秋月電子AE-TTL-232R)とRaspberry Piを接続する
- 以下のport4_led_blink.pyをRaspberry Piに配置する
  - 改行コードはLF
- port4_led_blinkh.pyを実行しArduino LeonardoのDigltal Pin #13に接続しているLEDが点滅すればOK

```python:port4_led_blink.py(2019/6/23更新)
#!/usr/bin/python

from time import sleep
import serial

cmd = [
       ["sendcmd" , "x"],
       ["sendcmd" , "port 4 high"],
       ["sleep"   , "0.500"],
       ["sendcmd" , "port 4 low"],
       ["sleep"   , "0.500"],
       ["sendcmd" , "port 4 high"],
       ["sleep"   , "0.500"],
       ["sendcmd" , "port 4 low"]
      ]

def serial_open(port, baudrate):
    return serial.Serial(port, baudrate)

def serial_write(h, str):
    str = str + '\r\n'
    h.write(str)

def parse_cmd(operation, parameter):
    print(operation, parameter)
    
    if operation=="sendcmd" :
        serial_write(serial_h, parameter)
    elif operation=="sleep" :
        sleep( float(parameter) )

def main():
    for i in cmd:
        parse_cmd(i[0], i[1])

serial_h = serial_open("/dev/ttyUSB0", 9600)
main()
```

<details><summary>旧バージョン</summary><div>

```python:port4_led_blink.py

#!/usr/bin/python

from time import sleep
import serial

ser = serial.Serial("/dev/ttyUSB0", 9600)
data = "x"
data = data + '\r\n'
ser.write(data)

data = "port 4 high"
data = data + '\r\n'
ser.write(data)

sleep(0.500)

data = "port 4 low"
data = data + '\r\n'
ser.write(data)

sleep(0.500)

data = "port 4 high"
data = data + '\r\n'
ser.write(data)

sleep(0.500)

data = "port 4 low"
data = data + '\r\n'
ser.write(data)
```

</div></details>

### 3.4 PyVISAのインストール
今回は使用しませんが測定器を制御できるようRaspberry PiにVISAをインストールします[^4]。

#### 3.4.1 インストール
sudo pip install pyusb
sudo pip install pyvisa
sudo pip install pyvisa-py

#### 3.4.2動作確認
- VISA対応の測定器(例：RIGOL DS1104Z)とRaspberry Piを接続する
- ターミナルで以下の操作を行う

$ sudo python
import visa
rm=visa.ResourceManager('@py')
rm.list_resources()
※ここで測定器から応答が返って来ればOK

### 3.5 Jenkinsはインストールしない
スレーブ側であるRaspberry PiにJenkinsを手動でインストールする必要はありません。もしRaspberry PiでJenkinsが動作している場合は停めます。

sudo /etc/init.d/jenkins stop

## 4. マスター側Jenkins
### 4.1 Jenkinsのインストール
ふだん使っているWindows 10のPCにJenkinsをインストールします。

- [こちら](https://jenkins.io/download/)からWindows版Jenkinsをダウンロードし、インストールします。
- Javaは[Zulu](https://jp.azul.com/downloads/zulu/)からOpenJDK Ver.8u202のものをダウンロード、インストールしました。
- SSH Agentプラグインをインストールしました。

### 4.2 ワークスペースのフォルダの変更
ワークスペースのフォルダがC:\Program Files (x86)\Jenkins\workspaceのままだとファイルを編集するたびに管理者権限のダイアログが出るのでC:\jenkins\workspaceへ変更しました。

下記ファイルをいったんデスクトップへ移動し、
  C:\Program Files (x86)\Jenkins\config.xml

workspaceDirの設定を以下のように編集し、
編集前：\${JENKINS_HOME}/workspace/\${ITEM_FULL_NAME}
編集後：C:/jenkins/workspace/\${ITEM_FULL_NAME}

デスクトップから元のフォルダへ戻します。

その後、Jenkinsの管理→設定の再読み込みでJenkinsを再起動します。

### 4.3 スレーブの追加
Jenkinsダッシュボード→Jenkinsの管理→ノードの管理→新規ノード作成
ノード名：RPi00 (あとで増やせるよう00から連番)
Permanent AgentのラジオボタンをクリックしOKボタンを押下すると詳細な設定画面に遷移します。

|項目|値|補足|
|---|---|---|
|ノード名|RPi00|前のページで入力した値がプリセットされています。
|説明|空欄のまま|default値
|同時ビルド数|1|default値
|リモートFSルート|/home/pi/jenkins|Raspberry Piに左記のフォルダを作成します
|ラベル|autotest|とりあえず設定してみた
|用途|このスレーブをできるだけ利用する|
|起動方法|SSH経由でUnixマシンのスレーブエージェントを起動|
|ホスト|192.168.0.10|Raspberry PiのIPアドレスを設定します
|認証情報|pi/******|4.4参照
|Host Key Verification Strategy|Non verifying Verification Strategy|閉じたネットワーク内で動かすので
|可用性|Keep this agent online as much as possible

ノードプロパティはいずれもチェックなしで保存ボタンを押下します。

### 4.4 認証情報
認証情報欄の「追加」ボタンを押下すると"Jenkins Credentials Provider: Jenkins"というダイアログがポップアップしますのでRaspberry PiへSSHでログインするためのユーザー名とパスワードを設定します。

|項目|値|補足|
|---|---|---|
|Domain|グローバルドメイン|
|種類|ユーザー名とパスワード|
|スコープ|システム|
|ユーザー名|pi|
|パスワード|(ログインするためのパスワードを入力)
|ID|RPi00|この認証情報にIDを振ります
|説明|RPi login ID/PW|わかりやすい説明を記入します

## 5. Lチカ実行
3.3.2で使用したport4_led_blink.pyをJenkinsで実行します。

- マスターのワークスペース(C:\jenkins\workspace\pipelineSample)にport4_led_blink.pyを配置
- Jenkinsでパイプラインの新規ジョブを作成(アイテム名：pipelineSample)
- stash/unstashでノード間のファイルを転送[^5]
  - スレーブ側はunstash実行前にdeleteDir()でディレクトリを削除しています
- スレーブ側でport4_led_blink.pyに実行権限を付与し実行
  - unstashしただけだとファイルのパーミッションが644で実行権限がないため

```groovy:パイプラインの定義
node('master') {
    stash name: 'project'
}
node('RPi00') {
    deleteDir()
    unstash 'project'
    sh 'ls -l'
    sh 'sudo chmod 755 *.py'
    sh 'sudo ./port4_led_blink.py'
}
```

```:コンソール出力
ユーザー****が実行
Running in Durability level: MAX_SURVIVABILITY
[Pipeline] Start of Pipeline
[Pipeline] node
Running on Jenkins in C:\jenkins\workspace\pipelineSample
[Pipeline] {
[Pipeline] stash
Stashed 1 file(s)
[Pipeline] }
[Pipeline] // node
[Pipeline] node
Running on RPi00 in /home/pi/jenkins/workspace/pipelineSample
[Pipeline] {
[Pipeline] deleteDir
[Pipeline] unstash
[Pipeline] sh
+ ls -l
合計 4
-rw-r--r-- 1 pi pi 798  6月 23 00:49 port4_led_blink.py
[Pipeline] sh
+ sudo chmod 755 port4_led_blink.py
[Pipeline] sh
+ sudo ./port4_led_blink.py
('sendcmd', 'x')
('sendcmd', 'port 4 high')
('sleep', '0.500')
('sendcmd', 'port 4 low')
('sleep', '0.500')
('sendcmd', 'port 4 high')
('sleep', '0.500')
('sendcmd', 'port 4 low')
[Pipeline] }
[Pipeline] // node
[Pipeline] End of Pipeline
Finished: SUCCESS
```

ビルドを実行しArduino LeonardoのLEDが点滅すればOKです。
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">Jenkinsのパイプラインを使ってMaster(Windows)からSlave(Raspberry Pi 3B+)を経由してArduino Leonardo治具へコマンドを送ってLチカしてみた。 <a href="https://t.co/nTLZJBnipT">pic.twitter.com/nTLZJBnipT</a></p>&mdash; ka’s (@pbjpkas) <a href="https://twitter.com/pbjpkas/status/1140207730906419202?ref_src=twsrc%5Etfw">June 16, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> 

## 6. おわりに
WindowsのJenkinsのマスターからRaspberry Piのスレーブを経由してArduino Leonardoまで土管をつなぐことができました。次のステップとしてもうちょっとPythonらしいスクリプトを書いたり[^6]PyVISAや「[M5StackとPythonで受入テスト自動化の要素技術を試す](https://qiita.com/pbjpkas/items/97b6e596a4009a71f1ab)」で触ったOpenCV、PyOCRなども集約してみたいと思います。

[^1]: 「[builderscon tokyo 2018 に登壇してきました](https://akerun.hateblo.jp/entry/2018/09/17/215517)」も併せて参考にさせていただきました。
[^2]: LEDを点滅(チカチカ)させることをLチカと呼びます。初めて作るプログラムの定番ですね。
[^3]: Arduino Leonardoの電源端子そばの「L」とシルクの入っているDigital Pin #13に接続されているLEDです。
[^4]: 詳しくは「[How to Control Your Instruments From A Computer: It’s Easier Than You Think](https://hackaday.com/2016/11/16/how-to-control-your-instruments-from-a-computer-its-easier-than-you-think/)」をご覧ください。sudo重要。
[^5]: 「[Jenkins パイプラインの stash/unstash を使ってノード間でファイルを転送する](http://arasio.hatenablog.com/entry/2016/10/19/001944)」を参考にさせていただきました。
[^6]: port4_led_blink.pyを改修しました(2019/6/23)。