MQTT通信をトリガーにしてRaspberry Pi 3 ModelBでカメラ撮影

## 目的
[M5StickCを使った玄関の鍵かけ忘れ防止システム(運用編)](https://qiita.com/marumen/items/5e435dc85e2b5e639d73) で作ったシステムに監視カメラ的な要素を追加するため、<b>M5StickCからのMQTT通信をトリガーにラズパイでカメラ撮影ができる仕組みを構築する</b>

## 環境
<b>全体像</b>
![image1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248487/c1e0fce4-3f15-db9f-9d65-7f2a92c9a9b0.png)

<b>詳細</b>

- Raspberry Pi 3 ModelB
 - カメラモジュール：Raspberry Pi Camera Module V2(https://www.amazon.co.jp/Raspberry-Pi-Camera-%E3%82%AB%E3%83%A1%E3%83%A9%E3%83%A2%E3%82%B8%E3%83%A5%E3%83%BC%E3%83%AB-Daylight/dp/B01ER2SKFS)
- M5StickC
- MQTT関連設定
 - Publisher：M5StickC
 - Broker：raspberry(mosquitto v1.5.7)
 - Subscriber：raspberry(paho-mqtt v1.6.1)

## 作業メモ

### M5StickC→ラズパイへMQTT通信("環境"の図の①)
<b>参考サイト</b>
https://yoshitaku-jp.hatenablog.com/entry/2019/06/30/210000
https://qiita.com/ekzemplaro/items/ab90dd630c3ad8e819ab
https://qiita.com/ekzemplaro/items/14ab3b599a6785187f6f

+ ラズパイ側に必要なライブラリを導入(mosquitto, paho-mqtt)

まずは mosquitto を導入

```
$ sudo apt install mosquitto
$ sudo apt install mosquitto-clients
```

とりあえず起動して動作確認
(今回はmosquittoはBrokerとしてのみの動作のため、これで動作確認としては十分)

```
$ sudo systemctl start mosquitto

$ sudo systemctl status mosquitto
● mosquitto.service - Mosquitto MQTT v3.1/v3.1.1 Broker
   Loaded: loaded (/lib/systemd/system/mosquitto.service; enabled; vendor preset: enabled)
   Active: active (running) since Sat 2021-09-18 17:57:29 JST; 3min 53s ago
     Docs: man:mosquitto.conf(5)
           man:mosquitto(8)
 Main PID: 1281 (mosquitto)
    Tasks: 1 (limit: 1939)
   CGroup: /system.slice/mosquitto.service
           mq1281 /usr/sbin/mosquitto -c /etc/mosquitto/mosquitto.conf

 9月 18 17:57:29 raspberrypi systemd[1]: Starting Mosquitto MQTT v3.1/v3.1.1 Broker...
 9月 18 17:57:29 raspberrypi systemd[1]: Started Mosquitto MQTT v3.1/v3.1.1 Broker.
```

次に paho-mqtt を導入
(Pythonライブラリなので、pipでインストール)

```
$ sudo pip install paho-mqtt
```

+ M5StickCからMQTT通信するコードを作成

<b>今回はM5StickC(MQTT Publisher)起動時に1回ラズパイ(MQTT Broker)へMQTT通信させる
(「camera_start」というメッセージを送信)</b>

```C
#include <M5StickC.h>
#include <Wire.h>
#include <WiFi.h>
#include <PubSubClient.h>

/*---------------------------------------------
   Global Data
-----------------------------------------------*/

/* -- 「*****」は環境に応じて設定必要 -- */
// For MQTT to raspberrypi
#define WLAN_SSID "*****"
#define WLAN_PASS "*****"

// MQTTの接続先のIP
const char *endpoint = "*****";
// MQTTのポート
const int port = 1883;
// デバイスID
char *deviceID = "*****";
// メッセージを知らせるトピック
char *pubTopic = "/pub/M5Stack";
// メッセージを待つトピック
char *subTopic = "/sub/M5Stack";
// 送信するメッセージ
char *pubMessage = "camera_start";
/* -- 環境に応じて設定必要(ここまで) -- */

WiFiClient client;
PubSubClient mqttClient(client);

void connectMQTT();

void setup() {
  M5.begin();
  Wire.begin(0,26);

  // WiFi設定
  WiFi.begin(WLAN_SSID, WLAN_PASS);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
  }

  // ラズパイへトリガー送信(MQTT)
  mqttClient.setServer(endpoint, port);
  connectMQTT();
}

void loop() {
}

void connectMQTT() {
  // Server側(ラズパイ)と接続できるまで繰り返し
  while (!mqttClient.connected()) {
    if (mqttClient.connect(deviceID)) {
      // メッセージ送信
      mqttClient.publish(pubTopic, pubMessage);

      int qos = 0;
      mqttClient.subscribe(subTopic, qos);
    } 
    else {
      Serial.print("Failed. Error state=");
      Serial.print(mqttClient.state());
      // リトライ前に1[s]wait
      delay(1000);
    }
  }
}
```

+ MQTT通信で来ているか動作結果

とりあえず簡単に動作確認するため、mosquittoのSubscribe機能を使って通信確認
→ 送信したメッセージ`camera_start`が受信できていることを確認
ラズパイ側のちゃんとした対応は次項で説明

```
$ mosquitto_sub -d -t /pub/M5Stack
Client mosqsub|1861-raspberryp sending CONNECT
Client mosqsub|1861-raspberryp received CONNACK (0)
Client mosqsub|1861-raspberryp sending SUBSCRIBE (Mid: 1, Topic: /pub/M5Stack, QoS: 0)
Client mosqsub|1861-raspberryp received SUBACK
Subscribed (mid: 1): 0
Client mosqsub|1861-raspberryp received PUBLISH (d0, q0, r0, m0, '/pub/M5Stack', ... (12 bytes))
camera_start
```

### MQTT通信を検知→カメラ撮影("環境"の図の②、③)
<b>参考サイト</b>
https://qiita.com/ekzemplaro/items/14ab3b599a6785187f6f
https://www.raspberrypi.com/documentation/accessories/camera.html

+ MQTT通信を検知するコードを作成(paho-mqttを利用)

`broker`や`topic`を環境に応じて設定が必要
カメラモジュールのオプションなどは [参考サイト](https://www.raspberrypi.com/documentation/accessories/camera.html) を参照

```Python:pahomqtt.py
#!/usr/bin/python3

import random
import subprocess
import datetime

from paho.mqtt import client as mqtt_client

# brokerにはラズパイのIPアドレスを指定
broker = '*****'
port = 1883
# M5StickCで指定したトピックと同じトピックを指定
topic = "/pub/M5Stack"
# client IDをランダム生成
client_id = f'python-mqtt-{random.randint(0, 100)}'

def connect_mqtt() -> mqtt_client:
    def on_connect(client, userdata, flags, rc):
        if rc == 0:
            print("Connected to MQTT Broker!")
        else:
            print("Failed to connect, return code %d\n", rc)
            sys.exit(rc)

    client = mqtt_client.Client(client_id)
    client.on_connect = on_connect
    client.connect(broker, port)
    return client


def subscribe(client: mqtt_client):
    def on_message(client, userdata, msg):
        # 撮影した画像のファイル名：[year][month][date][time].jpg
        t_delta = datetime.timedelta(hours=9)
        JST = datetime.timezone(t_delta, 'JST')
        now = datetime.datetime.now(JST)
        date = now.strftime('%Y%m%d%H%M%S')
        print(f"Received `{msg.payload.decode()}` from `{msg.topic}` topic")
        if "camera_start" in msg.payload.decode():
            try:
                # 解像度1024x768/quality=75で撮影
                cmd = "raspistill -o /home/pi/{0}.jpg -hf -vf -w 1024 -h 768 -q 75".format(date)
                res = subprocess.check_call(cmd, shell=True)
            except:
                print("rasp camera error")

    client.subscribe(topic)
    client.on_message = on_message


def run():
    client = connect_mqtt()
    subscribe(client)
    client.loop_forever()


if __name__ == '__main__':
    run()
```

動作確認

M5StickCからのMQTT通信を受信できると、`/home/pi`以下に撮影画像が保存される

```
$ ls ~/
20220220172347.jpg
....
```

+ 作成したコードを常時動作させるようにService化

[以前自分で書いた記事](https://qiita.com/marumen/items/78f65ad946a69603382e) を見つつ、常時動作ためのService登録をする

ここでは、先ほど作った`pahomqtt.py`を起動時に一度だけ実行する、Serviceを登録する
(`pahomqtt.py`はloopしてMQTT通信を待つ作りになっているので、実行は一度だけでいい)

```mqtt_run.service
[Unit]
Description=MQTT SUBSCRIBE
After=multi-user.target
DefaultDependencies=no

[Service]
Type=simple
ExecStart=/usr/bin/pahomqtt.py
Restart=no

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

必要なファイルを必要な場所に配置

```
$ chmod u+x pahomqtt.py
$ sudo cp pahomqtt.py /usr/bin/
$ sudo cp mqtt_run.service /etc/systemd/system/
```

再起動してServiceが正常に動いていることを確認

```
$ sudo systemctl status mqtt_run.service
● mqtt_run.service - MQTT SUBSCRIBE
   Loaded: loaded (/etc/systemd/system/mqtt_run.service; enabled; vendor preset:
   Active: active (running) since Sun 2022-02-20 18:26:29 JST; 2min 25s ago
 Main PID: 924 (pahomqtt.py)
    Tasks: 1 (limit: 1939)
   CGroup: /system.slice/mqtt_run.service
           mq924 /usr/bin/python3 /usr/bin/pahomqtt.py

 2月 20 18:26:29 raspberrypi systemd[1]: Started MQTT SUBSCRIBE.
```

+ MQTT通信を検知 → カメラ撮影の動作確認

<b>Service登録ができた状態でM5StickCを起動 → ラズパイでのカメラ撮影ができることを確認</b>

```
$ ls ~/
20220220172347.jpg   # こちらはお試し動作確認したときに撮った画像
20220220183642.jpg
....
```

## まとめ

目標を達成！！

>[M5StickCを使った玄関の鍵かけ忘れ防止システム(運用編)](https://qiita.com/marumen/items/5e435dc85e2b5e639d73) で作ったシステムに監視カメラ的な要素を追加するため、<b>M5StickCからのMQTT通信をトリガーにラズパイでカメラ撮影ができる仕組みを構築する</b>

今後は [M5StickCを使った玄関の鍵かけ忘れ防止システム(運用編)](https://qiita.com/marumen/items/5e435dc85e2b5e639d73) に今回の内容をマージしていきたい