Raspberry Piにはプログラムを自動起動する方法がいろいろありますが、「/etc/rc.localを使ったRaspberry Piのプログラムの自動起動」でrc.localを使った自動起動を説明しました。今回は、「Systemd」を使ってRaspberry Piのプログラムを自動起動します。Raspberry PiのOSは、「2018-04-18-raspbian-stretch」を使用します。

Systemdを使った自動起動(基本的な説明)

Systemdは、Raspbian Jessieから推奨されている自動起動の方法で、サービスとして、起動、シャットダウン、再起動できます。

個々のサービスなどの設定を行うファイル「 Unitファイル」の形式は次のようになります。 基本的には、「Description」にサービス名、「ExecStart」に実行したいプログラムを記載します。ユーザ作成のUnitファイルの置き場は、「/etc/systemd/system」になります。詳細については、「Systemd」を参照してください。

[Unit]
Description = サービス名

[Service]
ExecStart=実行したいプログラム名
Type=simple

[Install]
WantedBy=multi-user.target

サービスプロセスの起動完了の判定方法「Type」は、フォアグラウウンドで実行を継続するコマンドであれば、「Type=simple」とします。子プロセスをフォークしてバックグラウンドにまわして、最初のコマンド自体は終了するタイプであれば、「Type=forking」とします。

「WantedBy」には、ターゲット、つまりランレベルを設定します。形式を次に示します。

ランレベル target 説明
0 poweroff.target システム停止
1 rescue.target シングルユーザーモード
2-4 multi-user.target マルチユーザーモード
5 graphical.target GUIモード
6 reboot.target システム再起動
- emergency.target 緊急モード

サービスを再読み込みします。

$ sudo systemctl daemon-reload

サービスの動作状況を確認します。

$ sudo systemctl status サービス名

サービスを開始します。

$ sudo systemctl start サービス名

サービスを停止します。

$ sudo systemctl stop サービス名

サービスの自動起動を有効化します。

$ sudo systemctl enable サービス名

サービスの自動起動を無効化します。

$ sudo systemctl disable サービス名

ユーザーの環境変数を読み込む

systemdでユーザーの環境変数を設定したい場合、「EnvironmentFile」で環境ファイル名を指定し、環境ファイル内に環境件数とその値を「=」で定義します。なお環境ファイルはフォルダ「/etc/sysconfig/」に置きます。

ソケット通信を行うPythonスクリプトを自動起動(自動起動実施例)

/etc/rc.localを使ったRaspberry Piのプログラムの自動起動」で使用した自動起動用のPythonスクリプト「testserver.py」とクライアント側のPythonスクリプト「testclient.py」を使用します。ただし、次のIPアドレスの設定部分は変更しています。変更理由については、下記に示す「設定変更の理由」を参照してください。

  • 動起動用のPythonスクリプト「testserver.py」のIPアドレスの設定部分を「s.bind((‘127.0.0.1’, 50000))
    」としています。
  • クライアント側のPythonスクリプト「testclient.py」のIPアドレスの設定部分を「s.connect((‘127.0.0.1’, 50000))」としています。

Unitファイル「testserver.service」を次に示します。

[Unit]
Description = start up testserver.

[Service]
ExecStart=/home/pi/startuptest/testserver.py
Type=simple

[Install]
WantedBy=multi-user.target

「testserver.service」の「ExecStart」で、自動起動用のPythonスクリプト「testserver.py」を実行するために、次のチェックを行います。

  • 改行コードを「LF」とします。もし「CRLF」だと「/usr/bin/env: `python3\r’: そのようなファイルやディレクトリはありません」のエラーメッセージが表示されます。
  • pythonスクリプトを「 chmod +x 権限をつけたいファイル名」で実行権限を付与します。
  • シバン (shebang)を示す「#!/usr/bin/env python3」を追加します。
  • 「ImportError」が発生する場合、該当するモジュールがroot権限なしでモジュールをインストールしていないかを確認します。もしそうであれば、「sudo」を付けてインストールします。systemdはrootとして実行されます。 pip経由でインストールされたモジュールはシステムではなくユーザー用にインストールされるため、root権限なしでモジュールをインストールするとモジュールをrootにアクセスできなくなります。

次のコマンドで作成したUnitファイル「testserver.service」を読み込み、サービスを実行させてみて、自動起動用のPythonスクリプト「testserver.py」によるサーバが登録されているか(「TCP localhost:50000 」の登録)を確認し、確認後、サービスの自動起動を有効化しています。

 
$ sudo systemctl daemon-reload
$ sudo systemctl status testserver.service
* testserver.service - A sample unit file.
   Loaded: loaded (/etc/systemd/system/testserver.service; disabled; vendor preset: enabled)
   Active: inactive (dead)
$ sudo systemctl start testserver.service
$ sudo systemctl status testserver.service
* testserver.service - start up testserver.
   Loaded: loaded (/etc/systemd/system/testserver.service; disabled; vendor preset: enabled)
   Active: active (running) since Wed 2018-07-25 07:22:15 JST; 3s ago
 Main PID: 1331 (python3)
   CGroup: /system.slice/testserver.service
           `-1331 python3 /home/pi/startuptest/testserver.py

 7月 25 07:22:15 raspberrypi systemd[1]: Started start up testserver..
 7月 25 07:22:15 raspberrypi /testserver.py[1331]: syslog test:2018/07/25 07:22:15
$ sudo lsof -i
       ・・・
python3   1331  root    4u  IPv4  14967      0t0  TCP localhost:50000 (LISTEN)
$ sudo systemctl enable testserver.service
Created symlink /etc/systemd/system/multi-user.target.wants/testserver.service -> /etc/systemd/system/testserver.service.
$ sudo systemctl status testserver.service
* testserver.service - start up testserver.
   Loaded: loaded (/etc/systemd/system/testserver.service; enabled; vendor preset: enabled)
   Active: active (running) since Wed 2018-07-25 07:22:15 JST; 2min 51s ago
 Main PID: 1331 (python3)
   CGroup: /system.slice/testserver.service
           `-1331 python3 /home/pi/startuptest/testserver.py

 7月 25 07:22:15 raspberrypi systemd[1]: Started start up testserver..
 7月 25 07:22:15 raspberrypi /testserver.py[1331]: syslog test:2018/07/25 07:22:15

「/var/log/syslog」には次の内容がログされています。

       ・・・
Jul 25 07:03:00 raspberrypi systemd[1]: Started start up testserver..
       ・・・
Jul 25 07:03:01 raspberrypi /testserver.py: syslog test:2018/07/25 07:03:01
       ・・・
       ・・・

次のコマンドでTCPポート「50000」のサーバが立ち上がっていることを確認します。

$ sudo lsof -i
       ・・・
python3    324  root    4u  IPv4  10780      0t0  TCP localhost:50000 (LISTEN)
       ・・・
       ・・・

設定変更の理由

s.bind((‘192.168.10.6’, 50000))のようにローカルIPアドレスでbindしたところ、次のようなメッセージが「/var/log/syslog」に出力され、「testserver.py」が起動できませんでいた。IPアドレス「192.168.10.6」はDHCPによるIPアドレス割り当てのため、取得するタイミングが遅いのかもしれません。

Jul 24 14:29:59 raspberrypi /testserver.py: syslog test:2018/07/24 14:29:59
Jul 24 14:29:59 raspberrypi testserver.py[320]: Traceback (most recent call last):
Jul 24 14:29:59 raspberrypi testserver.py[320]:   File "/home/pi/startuptest/testserver.py", line 18, in 
Jul 24 14:29:59 raspberrypi testserver.py[320]:     s.bind(('192.168.10.6', 50000))
Jul 24 14:29:59 raspberrypi testserver.py[320]: OSError: [Errno 99] Cannot assign requested address
Jul 24 14:29:59 raspberrypi systemd[1]: testserver.service: Main process exited, code=exited, status=1/FAILURE
Jul 24 14:29:59 raspberrypi systemd[1]: testserver.service: Unit entered failed state.
Jul 24 14:29:59 raspberrypi systemd[1]: testserver.service: Failed with result 'exit-code'.

_tkinter.TclError: no display name and no $DISPLAY environment variable

Tkinterを使ったPythonスクリプトをSystemdで起動したときに上記のエラーが発生した場合、次のUnitファイルを使用します。

[Unit]
Description = start up test.
After=graphical.target
Wants=graphical.target

[Service]
User=pi
Group=pi
Environment="DISPLAY=:0.0"
Environment="XAUTHORITY=/home/pi/.Xauthority"
#ExecStartPre=/usr/bin/printenv
ExecStart=/usr/bin/python3 /home/pi/starttest/testmain.py

[Install]
WantedBy=graphical.target