概要
このプロジェクトでは、machinechatのIoTデータ管理ソフトウェアJEDI Oneを使って、Raspberry Pi 4にIoT MQTTブローカをセットアップします。JEDI Oneには、MQTTブローカとして設定可能なデータコレクタがあり、外部のクライアントデバイスがJEDI One上のトピックをパブリッシュしたり(あるいは)サブスクライブしたりすることができます。MQTTブローカをテストするために、Adafruit PyPortal Titanoを、WiFi経由でRaspberry Pi上のMQTTブローカにサブスクライブおよびパブリッシュするクライアントデバイスとしてセットアップしています。
背景
MQTT(Message Queuing Telemetry Transport)は、OASISおよびISOのオープン規格で、デバイス間でメッセージを伝送する軽量なパブリッシュ/サブスクライブネットワークプロトコルを定義しています。メッセージブローカと多数のクライアントという2種類のネットワークエンティティが含まれています。machinechatのIoTプラットフォームJEDI Oneには、MQTTメッセージブローカとして設定可能なデータコレクタが含まれています。外部クライアントデバイスは、JEDI One上のトピックにデータをパブリッシュしたり、サブスクライブするように設定できます(トピックには、MQTTだけでなく、あらゆるソースからJEDI Oneに入るすべてのデータを含みます)。 JEDI Oneは、JSONペイロード形式のパブリッシュメッセージを要求し、サブスクライブ メッセージをJSONペイロード形式で提供します。
ハードウェア
- RASPBERRY PI 4B/4GB
Raspberry PI 4 Model B、4GB SDRAM付き - Adafruit PyPortal Titano
PyPortal IoTキット - Arduino IDE、Circuit Pythonスタータキット
ソフトウェア
- JEDI One
JEDI Oneは、すぐに使えるIoTデータ管理ソフトウェアソリューションです。次のような機能があります。センサ、デバイス、マシンからのデータ収集、直感的なリアルタイムおよび履歴データ、およびシステムビューダッシュボードの構築、データ状況を自動的に監視・対応するルールの作成、電子メールやSMSによるアラート通知の受信などです。 - CircuitPython
CircuitPythonは、低価格のマイクロコントローラでの実験や教育を簡素化するために設計されたAdafruitのMicroPythonの派生版です。コンパイラ、リンカ、IDEは必要ありません。
実装
このプロジェクトでは、JEDI One アプリケーションが事前に Raspberry Pi にインストールされており、HTTPデータコレクタがセットアップされ、外部センサデータを受信しています(手順と詳細については、Machinechat with ESP32 and TE Connectivity MS8607 sensorをご参照ください)。そして、JEDI OneのMQTTデータコレクタをセットアップします。システムをテストするために、PyPortal Titanoは、MQTTブローカ上のトピックへのサブスクライブとパブリッシュの両方を行うように構成されています。PyPortal上のアプリケーションコードの実装にはCircuitPythonが使用されます。
JEDI OneのMQTTブローカをセットアップする
1 - machinechat JEDI OneがRaspberry Piにまだインストールされていない場合は、以下をご参照ください。
- Raspberry Pi版JEDI One DK-JEDIONE-RPを入手します。
- Raspberry Pi - Installing JEDI One as a Serviceを参照して、Raspberry Piにインストールしてください。
2 - MQTTブローカを設定する。
JEDI Oneの「Data Collector」タブで、「Add Collector」を選択し、設定します。「Data Collector」と名付け、「Collector Type」に「MQTT Broker」を選択します。MQTTコレクタ設定画面での「Listen IP」はJEDI One Raspberry PiのIPアドレスで、「Listen Port」は1883です(注:下記は暗号化されていない設定例ですが、TLS暗号化はmachinechatの製品ガイド - How to Generate TLS Certificates and Keys に示されているように設定することができます)。
PyPortal CircuitPythonのMQTTクライアントテストアプリケーションのセットアップ
Pyportalのテストアプリケーションは3つの部分で構成されています。
-
EDI Oneで収集されている既存のセンサデータをサブスクライブするMQTTクライアント
-
JEDI OneにセンサデータをパブリッシュするMQTTクライアント
-
ディスプレイPyPortal Titanoにサブスクライブしたセンサデータを表示するディスプレイアプリケーション
1 - PyPortal TitanoにCircuitPythonをセットアップします。リンクhttps://learn.adafruit.com/adafruit-pyportal-titano/circuitpython をご参照ださい(注:このプロジェクトではCircuitPython6.3.0を使用しました)。
2 - アプリケーションに必要なライブラリをインストールします。
CIRCUITPY (D:\lib)に以下のライブラリがインストールされていることを確認してください。ライブラリは、Librariesからダウンロードすることができます(注:このプロジェクトではadafruit-circuitpython-bundle-6.x-mpy-20210618.zipを使用しました)。
import neopixel
from adafruit_esp32spi import adafruit_esp32spi
from adafruit_esp32spi import adafruit_esp32spi_wifimanager
import adafruit_esp32spi.adafruit_esp32spi_socket as socket
import adafruit_minimqtt.adafruit_minimqtt as MQTT
from adafruit_pyportal import PyPortal
from adafruit_bitmap_font import bitmap_font
from adafruit_display_text import label
3 - コードウォークスルー(ファイル名:mqtt_jedi_display.py)
初期設定コード:
import time
import board
import busio
from digitalio import DigitalInOut
import neopixel
from adafruit_esp32spi import adafruit_esp32spi
from adafruit_esp32spi import adafruit_esp32spi_wifimanager
import adafruit_esp32spi.adafruit_esp32spi_socket as socket
import adafruit_minimqtt.adafruit_minimqtt as MQTT
import json
import sys
from adafruit_pyportal import PyPortal
import displayio
import terminalio
from adafruit_bitmap_font import bitmap_font
from adafruit_display_text import label
unique_id = "ppt10:52:1C:88:B6:F8" # set unique id based on pyportal MAC
latest_msg = "test" # set initial message
display = board.DISPLAY
main_group = displayio.Group(max_size=10)
MEDIUM_FONT = bitmap_font.load_font("fonts/Arial-16.bdf")
BIG_FONT = bitmap_font.load_font("fonts/Arial-Bold-24.bdf")
### WiFi ###
# Get wifi details and more from a secrets.py file
try:
from secrets import secrets
except ImportError:
print("WiFi secrets are kept in secrets.py, please add them there!")
raise
# If you are using a board with pre-defined ESP32 Pins:
esp32_cs = DigitalInOut(board.ESP_CS)
esp32_ready = DigitalInOut(board.ESP_BUSY)
esp32_reset = DigitalInOut(board.ESP_RESET)
# If you have an externally connected ESP32:
# esp32_cs = DigitalInOut(board.D9)
# esp32_ready = DigitalInOut(board.D10)
# esp32_reset = DigitalInOut(board.D5)
spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset)
"Use below for Most Boards"
status_light = neopixel.NeoPixel(
board.NEOPIXEL, 1, brightness=0.2
) # Uncomment for Most Boards
wifi = adafruit_esp32spi_wifimanager.ESPSPI_WiFiManager(esp, secrets, status_light)
MQTTのサブスクライブ/パブリッシュフィードの設定、およびディスプレイPyPortal Titanoへの表示
### Feeds ###
# Setup a feed named 'photocell' for publishing to a feed
photocell_feed = "datacache/photocell" # modify feed for JEDI One MQTT broker
# Setup a feed named 'pump' for subscribing to changes
pump_feed = "datacache/T960981B2D" # map feed to JEDI One MQTT broker data stream
### Code ###
# Define callback methods which are called when events occur
# pylint: disable=unused-argument, redefined-outer-name
def connected(client, userdata, flags, rc):
# This function will be called when the client is connected
# successfully to the broker.
print("Connected to JEDI One MQTT broker ! Listening for topic changes on %s" % pump_feed)
# Subscribe to all changes on the pump_feed.
client.subscribe(pump_feed)
def disconnected(client, userdata, rc):
# This method is called when the client is disconnected
print("Disconnected from JEDI One!")
def message(client, topic, message):
# This method is called when a topic the client is subscribed to
# has a new message.
global latest_msg
print("New message on topic {0}: {1}".format(topic, message))
latest_msg = message
parsed = json.loads(latest_msg)
data1 = "Humidity " + str(parsed["data1"])
print(data1)
print(parsed["data2"])
print(parsed["data3"])
print(parsed["timestamp"])
print(latest_msg)
# Connect to WiFi
print("Connecting to WiFi...")
wifi.connect()
print("Connected!")
# Initialize MQTT interface with the esp interface
MQTT.set_socket(socket, esp)
# Set up a MiniMQTT Client with JEDI One MQTT broker
# set client_id to unique id for connecting to mqtt broker
mqtt_client = MQTT.MQTT(
broker="192.168.1.7",
port = 1883,
client_id = unique_id,
)
# Setup the callback methods above
mqtt_client.on_connect = connected
mqtt_client.on_disconnect = disconnected
mqtt_client.on_message = message
# Connect the client to the MQTT broker.
print("Connecting to JEDI One MQTT broker...")
mqtt_client.connect()
# print header message on display
text_area1 = label.Label(BIG_FONT, text="Pump House Monitor", max_glyphs=40)
text_area1.x = 10
text_area1.y = 10
main_group.append(text_area1)
display.show(main_group)
data1 = "1234567" #set default display message
text_area = label.Label(MEDIUM_FONT, text=data1, max_glyphs=40)
text_area.x = 10
text_area.y = 100
main_group.append(text_area)
display.show(main_group)
text_area2 = label.Label(MEDIUM_FONT, text="abcde", max_glyphs=40)
text_area2.x = 10
text_area2.y = 200
main_group.append(text_area2)
display.show(main_group)
# photocell is a simulated sensor used by the pyportal to test publishing to JEDI One MQTT broker
photocell_val = 0 # set initial photocell value
# map photocell data dictionary object in prep to be compatible with JEDI One mqtt broker
photocell_dict = {}
photocell_dict["deviceType"] = "photocell"
photocell_dict["value"] = photocell_val
print(photocell_dict)
while True:
# Poll the message queue
mqtt_client.loop()
if latest_msg != "test": # check to see if new mqtt subscribe message
print("received mqtt subscribe message")
parsed = json.loads(latest_msg)
data1 = "Humidity " + str(parsed["data1"]) + " %"
data2 = "Temperature " + str(parsed["data2"]) + " F"
print(data1)
text_area.text = data1 # update humidity value for display
text_area2.text = data2 # update temperature value for display
latest_msg = "test" # reset latest message
photocell_dict["value"] = photocell_val # update to latest photocell value
photocell_json = json.dumps(photocell_dict) # convert photocell data to json format for JEDI One mqtt broker
print(photocell_json)
# Send a new message
print("Sending photocell json string: " + photocell_json)
mqtt_client.publish(photocell_feed, photocell_json)
print("Sent!")
photocell_val += 1
if photocell_val > 100: # reset counter for simulated sensor data
photocell_val = 0
print(latest_msg)
print(data1)
time.sleep(11)
MQTTクライアントテストアプリケーションの最新のソースコードは、以下のリンク先のgithubにあります。
まとめ
machinechatのデータ管理ソフトウェアJEDI OneとRaspberry Piを組み合わせることで、スタンドアロンで低コスト、かつ使いやすいIoT MQTTブローカプラットフォームが実現しました。サードパーティのクラウドサービスやインターネット接続を必要とせず、MQTTブローカプラットフォーム上のトピックにサブスクライブ/パブリッシュを行うクライアントデバイスを簡単に構成することが可能です。
参考資料
- Adafruit - PyPortal Titano
- machinechat製品ガイド - Built In MQTT Broker - Data Collector
- machinechat製品ガイド - MQTT Broker - Subscribing to a topic
- HIVEMQ - MQTT Essentials
- Machinechat JEDI One MQTT Broker Subscribe Example using Arduino / ESP8266
- Getting Started with machinechat’s JEDI One IoT Platform