Raspberry Piでプログラムを自動起動(1)_systemd

# Raspberry Piで自動起動する方法
 [参考の記事](https://qiita.com/karaage0703/items/ed18f318a1775b28eab4)によると大きく5つがあるようです。
 本記事はsystemedを使った自動起動を試行し
 print関数の内容表示やstop時に例外処理を実行する方法も併せてメモしておきます。

* /etc/rc.local
* autostart　　　　　←別の記事
* crontab @reboot
* /etc/init.d
* systemd　　　　　　←今回の記事

# 紹介内容
* RaspberryPiを起動した際に、プログラムをsystemedで自動起動させる。
* print関数の内容を表示
* stopコマンド時に例外処理実行

# 1.自動起動の手段
systemdが主流、推奨のようなのでsystemdで実装。
[ココのサイト](https://monomonotech.jp/kurage/raspberrypi/systemd_autostart.html)の手順で実行。
 
```py:systemed
[Unit]
Description=Play sound effects at startup
After=network.target

[Service]
ExecStart=/usr/bin/python3 start_app.py
WorkingDirectory=/home/pi/myapp

[Install]
WantedBy=multi-user.target
```

# 2.応用編　
　上記のファイルを追記、改変して
1. print関数の内容をstatusで表示。 →`.service`に記載
2. stop時に例外処理を実行。　→ `.py`に記載
　(リソースの開放などができず、再度起動した際にエラーになることを防ぐため)
3. 実行条件の指定
　　マウントが完了した後に実行など
  
 をやってみます。

 追加したsystemファイルは下記のようになります。
```py:systemed
[Unit]
Description = A sample unit file.
After=local-fs.target #ローカルファイルシステムへのマウントが完了した後に実行
ConditionPathExists=/opt/RasPiSample/bin

[Service]
ExecStart=/opt/RasPiSample/bin/autoexec.sh
Environment=PYTHONUNBUFFERED=1 #print関数の内容をログに残す
Restart=no
Type=simple
StandardOutput=journal+console #コンソールにも出力を表示させる

[Install]
WantedBy=multi-user.target
```
解説をしていきます。

# 1.print関数の内容をログに残す
　[Service]セクションに以下の行を追加すると print 関数の内容をログに残すことができます。
```py:systemed
[Service]
Environment=PYTHONUNBUFFERED=1
```
 ※ [systemdで実行中のPythonのprint関数の出力をログに出力させてみた](https://zenn.dev/kmiura55/articles/systemctl-python)
 
# 2.stopコマンドを検知し、例外処理を実行

# 
 リファクタリングはしてません。

<details><summary>Ctl+C　で.pyプログラムを終了できるコード例</summary>
<div>

```py:start_app.py
import subprocess
import time
import signal

class TerminatedExecption(Exception):
    pass

def raise_exception(*_):
    raise TerminatedExecption()

def end_process():
    try:
        print('--- start end process ---')
        time.sleep(2)
    except Exception as e:
        raise
    print('--- success end process ---')

if __name__ == '__main__':
    # set signal handler to detect to be stopped by systemd
    signal.signal(signal.SIGTERM, raise_exception)

    print('--- start main process ---')
    subprocess.run(['aplay', 'start.wav'])
    try:
        print('--- start main loop ---')
        count=0
        while True:
            count+=1
            print('count= ',count)
            time.sleep(1)

    # (1) if ctrl-C is pushed, stop program nomally
    except KeyboardInterrupt:
        print("KeyboardI]nterrupt: stopped by keyboard input (ctrl-C)")
        time.sleep(1)
        pass

    # (2) if stopped by systemd, stop program nomally
    except TerminatedExecption:
        print("TerminatedExecption: stopped by systemd")
        time.sleep(1)
        pass
    end_process()
```

</div>
</details>

main関数の最初で、SIGTERMを受信した際の動作を、raise_exception関数を呼び、TerminatedExecptionをraiseするように変更。
 下記サイトがわかりやすくまとめられていました。
 　※[【systemd】Pythonでstopを検知する＆例外時に自動で再起動する](https://dev.classmethod.jp/articles/detect-systemd-stop-and-restart-automatically-on-error-with-python)

# 3.実行条件の指定
### 3.1　ローカルファイルシステムへのマウントが完了した後に実行
通常, 組込Linuxの場合は, サードパーティーのプログラムを導入して自動起動させるはずですので, /opt/パッケージ名のディレクトリは, 存在することを期待して良いと考えられますが, 念のため, ローカルファイルシステムへのマウントが完了した後に実行されることを保証するため, [unit]内に下記を追記します。

```py:systemed
[unit]
After=local-fs.target
```
ローカルファイルシステムへのマウントが完了した後に実行されることを保証する
After=には, 以下のような実行条件を記載することが出来ます．

* `local-fs.target` 装置に直接繋がったストレージのマウントが完了した後で実行する
* `network-online.target` ネットワークを利用可能になった後で実行する
* `remote-fs.target` ネットワークファイルシステム（NFSなど）を利用可能になった後で実行する
* `nss-lookup.target` DNSなどのホスト/ネットワークサービスを利用可能になった後で実行する
上記の場合, local-fs.targetの完了後, つまり, 装置に直接繋がったストレージのマウントが完了した後でプログラムを実行する指示になります．

### 3.2 ファイルシステムのマウントに失敗した場合に正しくエラーを返す
ファイルシステムのマウントに何らかの理由で失敗した場合に正しくエラーを返せるようにする

```py:systemed
[unit]
ConditionPathExists=/opt/RasPiSample/bin
```

### 3.3 systemdにコンソールにも出力を表示させるように指示する方法
下記を記載することでコンソール出力とSystemD内のログの双方にprint文の内容が出力されます．
```py:systemed
[Service]
StandardOutput=syslog+console
```
 ※ [systemdを用いたプログラムの自動起動](https://qiita.com/tkato/items/6a227e7c2c2bde19521c)

# 4.serviceに実行権限を与える
必要に応じてPython のコードに対して実行権限を与えます。
```py:
sudo chmod 755 /opt/***.py
```

その後、systemd で Python のコードを実行してみます。
```py:
sudo systemctl daemon-reload
sudo systemctl start ***.service
```
systemd 実行後に
```py:
jornalctl -u ***.service
```
を実行してログの中にHello Worldと表示されれば成功です。

# 5.サービスの確認
```py:サービス有効化
sudo systemctl enable start_app.service
```
```py:サービス無効化
sudo systemctl disable start_app.service
```
```py:サービスの状態やログ確認
sudo systemctl status start_app.service
```
起動している場合
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483299/4531ba0c-ebaa-571e-8c25-85ee0a338171.png)

停止している
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483299/7fd2e50e-9fb4-ad9f-2673-f8c993fbbb34.png)


# 参考情報
### 既にある`.service`ファイルを編集する場合
```py:vimで編集
cd /etc/systemd/system
sudo vim start_app.service
```

## 定刻でのプログラム自動起動
1.timerが始動していることを確認
```py:ステータスの確認
sudo systemctl status myscript.timer
```
2.タイマーの一覧と設定状況
```py:タイマーの一覧と設定状況
sudo systemctl list-timers
```
上で起動した****.timerの次回実行時刻、前回実行からの経過時間などが確認できます。

3. 実行ログの確認
```py：systemdのログ全般
journalctl -e
```
systemdのログ全般は journalctl コマンドを使用します。

4.無効化
```py:
sudo systemctl disable ****.timer
```

※ [Raspberry Pi で systemd を使ってプログラムを自動実行する](https://qiita.com/molchiro/items/ee32a11b81fa1dc2fd8d)
※ [Systemdのtimer設定チートシート（コマンドまとめ）](https://gamingpc.one/dev/systemd-timer-cheat/)


以上

# 参考サイト
* [クラゲのブログ　systemdを使ってスクリプト自動起動](https://monomonotech.jp/kurage/raspberrypi/systemd_autostart.html)
* [systemdを用いたプログラムの自動起動](https://qiita.com/tkato/items/6a227e7c2c2bde19521c)
* [Raspberry Pi で systemd を使ってプログラムを自動実行する](https://qiita.com/molchiro/items/ee32a11b81fa1dc2fd8d)
    * 定刻でのプログラム自動起動
* [pythonスクリプトをdaemonにする[systemd編]](https://qiita.com/katsuNakajima/items/7ece6c74f992f652d732)
* [Systemdのtimer設定チートシート（コマンドまとめ）](https://gamingpc.one/dev/systemd-timer-cheat/)
* [systemdで実行中のPythonのprint関数の出力をログに出力させてみた](https://zenn.dev/kmiura55/articles/systemctl-python)
* [【systemd】Pythonでstopを検知する＆例外時に自動で再起動する](https://dev.classmethod.jp/articles/detect-systemd-stop-and-restart-automatically-on-error-with-python)
* [【systemd】プログラム自動起動の設定時にハマったこと（ユーザ指定・Pythonパス指定・ウィンドウ表示）](https://dev.classmethod.jp/articles/systemd-setting-to-execute-program-automatically-on-start/)
* [systemdとsystemctlコマンド：サービスの自動起動、停止、再起動](https://office54.net/iot/linux/linux-systemd-systemctl)
<img src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483299/845425f6-db35-bdbc-80ef-1228187d146a.png width=320>

* [Raspberry Piでプログラムを自動起動する5種類の方法を比較・解説](https://qiita.com/karaage0703/items/ed18f318a1775b28eab4)
* [【systemd】Jetsonで起動時に自動でファンを回す](https://dev.classmethod.jp/articles/jetson-run-fan-automatically-with-systemd/)
* [Jetson NanoでRealSense D435iとT265を使ったプログラムを自動起動させる方法](https://cvl-robot.hateblo.jp/entry/2019/08/26/113311)
* [systemd のテンプレートユニットで同じプロセスを複数立ち上げる](https://ngyuki.hatenablog.com/entry/2019/12/29/113502)