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, inJul 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