ゆるい空き室モニタをつくる2(RPi MQTT Broker編)

人感センサの情報をサーバで受取り、他のメディアに配信したり、別の場所で表示したりしたい。
前回は、センサの基本的な動作確認。
いよいよ、人感センサの検知データをWifi経由で、サーバに送信する。
ということで、protocolは、MQTTを使う。実装は、mosquitto。
なんでMQTT
ネットが、掲示版からSNSに変遷していったのと似てるかも。
publisher がインフルエンサーで、subscriber がフォロワー。
ただし、 生配信のみ
。フォロワーは24時間365日、がんばって張り付く必要がある。
技術的に説明すると、こんな感じ。
掲示版的なサービス
シンプルにありそうなのは、サーバ側で適当なAPIをつくって、センサであるクライアントが、そこに検知データPOSTする、HTTP Server構成だろう。
検知データは、サーバ側でCSVなり、sqliteなり、DBに保存すればよい。
SNS的なサービス
が、センサの数は増えてきたら?数千の単位だったら?
モニタ数もそうだが、用途によって見たい(収集した)情報が異なる画面が増えたら?
トラフィックが増えて、HTTPサーバ、およびDBがボトルネックになってしまう。
サーバを高性能なものにすれば、それなりに動くかもしれない。
が、もっといい方法があるのでは?
MQTT のモデル
ということで、開発されたのがMQTTのモデル。
赤い矢印が集まるbrokerが、いわゆるサーバにあたる。
が、このサーバは、自身でデータをもたない。publisherから来たデータは数分もすれば消える。
- publisher
とにかく検知したデータを、brockerに押しつける(publish)。 - subscriber
欲しいデータ範囲(topic)をbroker指定して、リアルタイムで受け取れるようにデータ待ちする。
このpublisher と subscriber をいい感じにサポートするのがbrokerの仕事。
MQTT のsetup
ここで最近手にいれたRPi4の出番。
メモリが2Gもあるので、MQTTサーバなんて余裕で動く!
install
$ sudo apt-get install mosquitto
次回からrebootで自動的に起動するように設定
$ sudo systemctl enable mosquitto
起動チェック
$ systemctl status mosquitto
● mosquitto.service - Mosquitto MQTT Broker
Loaded: loaded (/lib/systemd/system/mosquitto.service; enabled; vendor preset: enabled)
Active: active (running) since Tue 2022-03-08 09:03:27 JST; 4 days ago
Docs: man:mosquitto.conf(5)
man:mosquitto(8)
Main PID: 8406 (mosquitto)
Tasks: 1 (limit: 3717)
CPU: 3min 11.856s
CGroup: /system.slice/mosquitto.service
└─8406 /usr/sbin/mosquitto -c /etc/mosquitto/mosquitto.conf
Warning: journal has been rotated since unit was started, output may be incomplete.
broker のサービスIP設定
brokerは、defaultだとlocalhostのみアクセスできる。
他のマシン(private subnet)からアクセスできるように、listener の設定をする。
- brokerのprivateIP
10.0.0.1
- brokerのport
1883
- 認証なしのアクセスOK
/etc/mosquitto/conf.d/listener.conf
listener 1883 10.0.0.1 allow_anonymous true
再起動
$ systemctl restart mosquitto
これでbroker はOK。
mosquitto client のinstall
テストのために、command line 版の mosquitto client をinstall
$ sudo apt-get install mosquitto-clients
これで、publisher と sunscriber のやり取りを確認できる。
subsriber では、 topic sensor/#
で待ち受けるように実行する。
$ mosquitto_sub -v -h {brokerのIP} -t 'sensor/#'
#
は正規表現の *
にあたり、 sensor
、 sensor/room01
など先頭がsensor
のtopic全てを待ち受ける指定。
-v
option をつけることで、publisherからのデータ受信時に、 topic
情報も表示される。
この topic sensor
向けに、publisher として情報 {"hello": 1}
送信する。
brokerのIPを 10.0.0.1
とすると、
$ mosquitto_pub -h 10.0.0.1 -t sensor/room1 -m '{"hello": 1}'
subscriber の表示
$ mosquitto_sub -v -h 10.0.0.1 -t sensor/#
sensor/room1 {"hello": 1}
M5StickCをpublisherにする
上記のpublisherをM5StickC で実装する。
#include <M5StickC.h> | |
#include <WiFi.h> | |
#include <HTTPClient.h> | |
#include "PubSubClient.h" | |
// | |
// wifi | |
// | |
const char *ssid = "AP SSID"; | |
const char *password = "AP PASWORD"; | |
WiFiClient wClient; | |
// | |
// MQTT | |
// | |
const char *mqttHost = "xxxx.xxx.xxx.xxx"; // MQTTのIPかホスト名 | |
const int mqttPort = 1883; // MQTTのポート | |
const char *topic = "sensor/room1"; // 送信先のトピック名 | |
char *payload; // 送信するデータ | |
PubSubClient mqttClient(wClient); | |
int Last_sec = -1; | |
bool Is_on = false; | |
#define WAIT_TIME 100 | |
#define PIR_HAT_PIN 36 | |
// | |
// setup RTC by NTP | |
// | |
void setup_rtc() | |
{ | |
struct tm timeinfo; | |
uint8_t hh, mm, ss; | |
RTC_TimeTypeDef TimeStruct; | |
// connect NTP server | |
configTime(9 * 3600, 0, "ntp.nict.jp", "ntp.jst.mfeed.ad.jp"); | |
if (!getLocalTime(&timeinfo)) | |
{ | |
M5.Lcd.println("Failed to obtain time"); | |
return; | |
} | |
// | |
// set localtime to RTC | |
// | |
hh = timeinfo.tm_hour; | |
mm = timeinfo.tm_min; | |
ss = timeinfo.tm_sec; | |
TimeStruct.Hours = hh; | |
TimeStruct.Minutes = mm; | |
TimeStruct.Seconds = ss; | |
M5.Rtc.SetTime(&TimeStruct); | |
} | |
// | |
// connect wifi | |
// | |
int setup_wifi() | |
{ | |
M5.Lcd.setTextSize(1); | |
M5.Lcd.setCursor(0, 0); | |
M5.Lcd.fillScreen(BLACK); | |
M5.Lcd.setTextColor(DARKGREY); | |
WiFi.disconnect(true); | |
WiFi.begin(ssid, password); // connect to wifi | |
int ntry = 100; | |
while (WiFi.status() != WL_CONNECTED) | |
{ | |
delay(500); | |
M5.Lcd.print("."); | |
ntry -= 1; | |
if (ntry == 0) | |
{ | |
// Serial.println("max try failed"); | |
M5.Lcd.setTextSize(2); | |
M5.Lcd.setTextColor(RED); | |
M5.Lcd.setCursor(0, 0); | |
M5.Lcd.println("fail wifi connect"); | |
return -1; | |
} | |
} | |
M5.Lcd.println(""); | |
M5.Lcd.println("WiFi connected."); | |
M5.Lcd.print("IP address: "); | |
M5.Lcd.println(WiFi.localIP()); | |
M5.Lcd.println(); | |
return 0; | |
} | |
/** | |
* Connect MQTT | |
* | |
*/ | |
void connectMqtt() | |
{ | |
mqttClient.setServer(mqttHost, mqttPort); | |
while (!mqttClient.connected()) | |
{ | |
Serial.println("Connecting to MQTT..."); | |
if (mqttClient.connect("room1")) | |
{ | |
Serial.println("connected"); | |
} | |
delay(500); | |
} | |
M5.Lcd.setTextSize(2); | |
M5.Lcd.setCursor(16, 9 * 6); | |
M5.Lcd.setTextColor(DARKGREEN); | |
M5.Lcd.printf(topic); | |
} | |
void setup() | |
{ | |
M5.begin(); | |
M5.Axp.ScreenBreath(10); | |
M5.Lcd.setRotation(3); // 左を上にす | |
M5.Lcd.setTextSize(1); | |
M5.Lcd.fillScreen(BLACK); | |
M5.Lcd.setTextColor(DARKGREY); | |
delay(500); | |
pinMode(GPIO_NUM_10, OUTPUT); | |
pinMode(PIR_HAT_PIN, INPUT_PULLUP); | |
// | |
// Wifi | |
// | |
setup_wifi(); | |
// | |
// setup RTC time | |
// | |
setup_rtc(); | |
// | |
// MQTT | |
// | |
connectMqtt(); | |
} | |
void loop() | |
{ | |
RTC_TimeTypeDef RTC_TimeStruct; | |
M5.update(); | |
// PIR HAT(AS312) | |
int pir = digitalRead(PIR_HAT_PIN); | |
if (pir) | |
{ | |
digitalWrite(GPIO_NUM_10, 0); | |
payload = (char *)"{\"status\":true}"; | |
Is_on = true; | |
} | |
else | |
{ | |
digitalWrite(GPIO_NUM_10, 1); | |
payload = (char *)"{\"status\":false}"; | |
} | |
M5.Rtc.GetTime(&RTC_TimeStruct); | |
int diff = Last_sec - RTC_TimeStruct.Seconds; | |
if (abs(diff) > 3) | |
{ | |
M5.Lcd.setTextSize(1); | |
M5.Lcd.setCursor(32, 9 * 8); | |
M5.Lcd.setTextColor(DARKGREEN); | |
M5.Lcd.fillRect(0, 9 * 8, 256, 12, BLACK); | |
M5.Lcd.printf("Time: %02d : %02d : %02d\n", | |
RTC_TimeStruct.Hours, RTC_TimeStruct.Minutes, RTC_TimeStruct.Seconds); | |
Last_sec = RTC_TimeStruct.Seconds; | |
if (WiFi.status() == WL_CONNECTED) | |
{ | |
if (mqttClient.publish(topic, payload)) | |
{ | |
Serial.print("publish ok"); | |
Serial.println(payload); | |
} | |
else | |
{ | |
Serial.println("publish ng"); | |
} | |
Is_on = false; | |
} | |
} | |
mqttClient.loop(); | |
delay(WAIT_TIME); | |
} |
publisher の結果
command line で、M5StickCから、sensor 情報が来てるのを確認
$ mosquitto_sub -v -h 10.0.0.1 -t sensor/#
sensor/room1 {"status":true}
sensor/room1 {"status":false}
sensor/room1 {"status":false}
このままcommand line だと面白くないので、次回はNodeREDで可視化する。