はじめに

モノづくりの現場において、さまざまな面でデジタル化が進んできました。精密機器や洋服のデザインは紙から CAD(コンピュータ支援設計: Computer-Aided Design)へ、建築業であれば CAD から BIM(建物情報モデル: Building Information Modeling)、実験やテストにおいても、現物ベースのテストからシミュレータの活用など、新規データの作成から、過去データの活用まで、さまざまな形でデジタルデータが活用されています。

ただし、ソフトウェアの側から見ると、必ずしも入力される一次情報が全てデジタル由来というわけではなく、カメラやマイク、さらには人間の五感では捉えきれない情報がセンサーを通じて入力されているのも、一方では事実です。

ソフトウェアテストという観点からは、そういった非デジタルに由来する入力のテストをどう扱うか、単体レベルではスタブやドライバ、モックでソフトウェア的に代替しつつも、システムレベルでは実機テスト、実環境テストを適用したい場合があります。従来、「ハードウェア」のカテゴリで扱われていた製品であっても、ソフトウェアのバージョンアップによって、新たな機能が加わる(テスラの車がまさにそうですね)、つまり、ハードウェアであっても powered by software な時代に、CI/CD はどういった貢献ができるのか。CircleCI ランナー や Arm ベースの Machine Executor など、組み込みソフトウェア開発への CI/CD の適用について、ご紹介していきます。

取り上げる内容

具体的な話に移りましょう。今回のターゲットデバイスは、Raspberry Pi(ラズベリーパイ、ラズパイ)です。Raspberry Pi にSeeed 社の Grove システムが提供するさまざまなセンサーやアクチュエータ、出力デバイスを(はんだ付けすることなく)つなげて、ハードウェアとソフトウェアの開発、およびテストをしていきます。

全4回で、次のような内容をご紹介します。

  1. Raspberry Pi 上での CircleCI ランナーの設定
  2. Raspberry Pi 上での GrovePI+ 実行環境の設定
  3. CircleCI ランナーを使ったセンサー使用ソフトウェアのテスト(本ブログ)
  4. Arm ベースの Machine Executor を使ったソフトウェアのビルド

なお、本ブログの記述時(2021 年 4 月現在)、CircleCI ランナーに関わる 1, 3 をご自身で実際に試すには、CircleCI の Scale プランをご契約いただく必要があります。一方、Arm ベースの Machine Executor に関わる 4 をご自身でお試しいただくには、こちらのアクセス申請フォームよりお申し込みをいただく必要があります(Free プランを含む全てのプランで利用可能です)。

プロジェクト リポジトリの作成

前回は デバイス(Raspberry Pi)上で直接、ソースコード grove_dht_pro.py を入力していきましたが、今回は、デバイス上でではなく、PC や Mac などの環境で作業を進めていきます。

まずは、CircleCI を使って自動化を行うプロジェクト リポジトリを作成します。今回、ご紹介する例では、筆者は CCI-RPI-TempHum という名前のリポジトリを GitHub 上に作成しました。

次にリポジトリを PC 上に clone し、ルートディレクトリに grove_dht_pro.py というファイルを作成します。grove_dht_pro.py ファイルの内容は、前回ご紹介したものを(Ctrl+C 入力待ちで無限ループにならないよう)一部改変しています。ただし、前回問題なく動作しているのであれば、CircleCI ランナー経由であっても同様に動作することが期待できます。

import grovepi
import math
# Grove Temperature & Humidity Sensor Pro を デジタルポート D3 に接続します。
# このサンプルでは、白い色のセンサーを使用しています。
# SIG,NC,VCC,GND
sensor = 3  # センサーの接続先はデジタルポート 3

# temp_humidity_sensor_type
blue = 0    # 青色のセンサーを使用
white = 1   # 白色のセンサーを使用

i = 0

while i < 10:
    try:
        # 第1引数は接続ポート、第2引数はセンサー種別
        [temp,humidity] = grovepi.dht(sensor, white)
        if math.isnan(temp) == False and math.isnan(humidity) == False:
            print("temp = %.02fC humidity = %.02f%%"%(temp, humidity))
    except IOError:
        print ("Error")
    finally:
        i += 1
        time.sleep(1)

ランナーを使って実機で動作させるためのコンフィグの作成

では、コンフィグを作成していきましょう。リポジトリに .circleci ディレクトリを作成し、その中にファイル config.yml を記述していきます。

ここでは2つのジョブを定義します。最初に定義するジョブは CircleCI ランナーを使って、Raspberry Pi 上で grobe_dht_pro.py を実行し、実行結果をワークスペースに保持しておくためのジョブです。次に定義するジョブは、ワークスペースに保持されている実行結果を表示するためのジョブです(実際には表示された結果が正しいかどうかの検証が必要となりますが、今回はデバイスの実行結果を CircleCI のワークスペース経由で簡単に共有できるところまでをゴールとします)。

ジョブ 1: CircleCI ランナーを使って実機で実行

まずは最初のジョブ test_on_arm-runner の定義をご紹介します。

jobs:
  test_on_arm-runner:
    machine: true
    resource_class: mayoct/arm-runner
    steps:
      - checkout
      - run:
          command: |
            mkdir -p tmp
            python grove_dht_pro.py > tmp/sensor.txt
      - persist_to_workspace:
          root: .
          paths:
            - "tmp"

リソースクラスには、1回目の CircleCI ランナーの設定時に作成したランナー用リソースクラス(ここでは mayoct/arm-runner)を設定しておきます。

そのあとの run では tmp ディレクトリを作成しておいた上で、python コマンドを使って grove_dht_pro.py を実行します。出力内容は、tmp ディレクトリ内に sensor.txt という名前のファイルでリダイレクトしておきます。

このままでは sensor.txt の内容を確認するには、Raspberry Pi にログインしないといけないので、CircleCI のワークスペースに保持しておき、CircleCI にログイン可能な環境から見られるようにしておきます。

ジョブ 2: CircleCI ワークスペースで実行結果を確認

次に、実行結果を CircleCI 上(クラウド上)で表示するためのジョブの定義をご紹介します。

review_on_cloud:
  docker:
    - image: cimg/base:stable
  steps:
    - checkout
    - attach_workspace:
        at: .
    - run:
        command: |
          cat tmp/sensor.txt

ワークスペースの内容をカレントディレクトリに展開し、tmp/sensor.txtの内容を表示します。

この2つのジョブを順に実行するためのワークフローの定義は、次のようになります。

workflows:
  version: 2
  runner-workflow:
    jobs:
      - test_on_arm-runner
      - review_on_cloud:
          requires:
            - test_on_arm-runner

いざ実行

さて、grove_dht_pro.py ファイルと .circleci/config.yml ファイルをリポジトリにコミット、プッシュすると意図したようにデバイス上で動作し、実行結果をクラウド側で参照できるでしょうか?

リポジトリのイラスト

残念ながら、前回 Raspberry Pi 上で直接実行した時と同様に、

wiringPiSetup: Unable to open /dev/mem or /dev/gpiomem: Permission denied.
  Aborting your program because if it can not access the GPIO
  hardware then it most certianly won't work
  Try running with sudo?

ハードウェア(/dev/mem または /dev/gpiomem)へのアクセス権限がないためにエラーになってしまいます(ちなみに前回説明しませんでしたが、GPIO とは General Purpose Input/Output、つまり「汎用目的入出力」の略です)。ただし、今回はメッセージに書かれているように、sudoで管理者権限で実行するようにはしません。

第1回でも説明したように、CircleCI ランナーは、

  • ローンチ エージェント (root ユーザーとして実行)
  • タスク エージェント (circleci ユーザーとして実行)

の実行ユーザーの異なる2つのエージェントが役割を分担することで、コンフィグ中で指定された指示(今回で言えば、python grove_dht_pro.py > tmp/sensor.txtの実行)をタスク エージェントに circleci ユーザーの権限で実行させることにより、限定しているのです。

ここで、/dev/gpiomemの所有者とグループを確認してみます。

$ ls -l /dev/gpiomem
crw-rw---- 1 root dialout 509, 0 Apr 21 23:26 /dev/gpiomem

所有者が root、グループが dialout であることが分かります(dialout とはシリアルポートの管理グループ)。それでは、circleci ユーザーが属しているグループを確認してみましょう。

$ groups circleci
circleci : circleci

circleci ユーザーは circleci グループに属しているだけなので、dialout グループへの所属を追加します。

$ sudo adduser circleci dialout

CircleCI の画面から Rerun Workflow from Failed を実行します。

rerun workflow sudo することなくハードウェア(GPIO)アクセスに成功しました。

実行結果確認

実行結果、つまりセンサーで検知した温度、湿度のログは、Raspberry Pi に SSH ログインしなくても、CircleCI の画面から確認することが可能です。

review_on_cloud ジョブの実行結果のうち、cat tmp/sensor.txt を見れば、実際に検知した温湿度が表示されます。

cat tmイラスト

今回はファイル名固定でしたが、日時をファイル名にする、複数台の Raspberry Pi + センサーを使い、CircleCI でパラレルに実行し、結果を比較するなどの動作確認方法が考えられます。

さいごに

今回は Python プログラムということもあり、コンパイルすることなく、リポジトリに格納されたコードをそのまま実行しました。一方、C や C++で書かれたコードであれば、実行前に、コンパイルやリンクといったプロセスが必要となります。

Raspberry Pi 上でコンパイルやリンクを実行することも(当然)可能ですが、より CPU やメモリリソースがリッチで、高速なディスクが使える環境でコンパイルしてみてはいかがでしょう? 次回は Arm Executor を使い、クラウド上で Raspberry Pi 向けの実行ファイル(バイナリ)を生成し、CircleCI ランナーを使って実機で動作確認を行う方法を取り上げることにします。