用樹莓派、PyPortal Titano 和 machinechat JEDI One 設定和測試 MQTT 伺服器

描述

此項目使用 machinechat 的 JEDI One 物聯網資料管理軟體在樹莓派4上設定了一個物聯網 MQTT 伺服器(broker)。JEDI One 包含一個資料收集器,可以將其配置為 MQTT 伺服器,外部客戶機設備可以向其發布和/或訂閱JEDI One 上的主題。為了測試 MQTT 伺服器,將 Adafruit PyPortal Titano 設定為客戶端設備,該設備透過 WiFi 訂閱和發布樹莓派上的 MQTT 伺服器。

image

image

背景

MQTT(Message Queuing Telemetry Transport),或稱「訊息佇列遙測傳輸」,是一個開放的 OASIS 和 ISO 標準,它定義了在設備之間傳輸訊息的輕量級發布-訂閱網路協定。它包括兩種類型的網路實體,分別是一個訊息代理程式和一些客戶端。machinechat 的 JEDI One 物聯網平台包含一個資料收集器,可以配置為 MQTT 訊息伺服器。外部客戶機設備可以配置為在 JEDI One 上發布數據或訂閱主題(主題包括從任何來源進入 JEDI One 的所有數據,而不僅僅是 MQTT)。JEDI One 要求發布訊息採用 JSON 有效負載格式,並以 JSON 有效負載格式提供訂閱訊息。

硬體

軟體

  • JEDI One
    是一款即用型物聯網資料管理軟體解決方案。功能包括:收集來自感測器、設備和機器的數據;構建直觀的實時和歷史數據以及系統視圖儀表板;創建規則,自動監控和響應數據情況;透過電子郵件和短信接收警報通知。
  • CircuitPython
    是 Adafruit 的 MicroPython 分支,旨在簡化低成本微控制器的實驗和教育。不需要編譯器、連結器或 IDE。

實現

對於這項目,JEDI One 應用程式先前已安裝在樹莓派上,並設定了 HTTP 資料收集器並接收外部感測器資料(有關說明和詳細資料,請參考「Machinechat with ESP32 and TE Connectivity MS8607 sensor」)。然後設定 JEDI One MQTT 資料收集器。為了測試系統,將 PyPortal Titano 配置為訂閱和發布 MQTT 伺服器上的主題。CircuitPython 用於在 PyPortal 上實作應用程式程式碼。

設定 JEDI One MQTT 伺服器

  1. 如果 machinechat JEDI One 尚未安裝在樹莓派上,請參閱以下內容:
  2. 設定 MQTT 伺服器
    在 JEDI One「資料收集器」標籤中,選擇「新增收集器」並進行設定。命名「資料收集器」,並為「收集器類型」選擇「MQTT Broker」。MQTT 採集器設定截圖中的「監聽 IP」是 JEDI One 樹莓派的 IP 位址,「監聽埠」是 1883。(註:以下是未加密配置的範例,但可以按照 machinechat 產品指南 - How to Generate TLS Certificates and Keys

image

設定 PyPortal CircuitPython MQTT 客戶端測試應用程式

Pyportal 測試應用程式有三個部分:

  1. MQTT客戶端,訂閱正在 JEDI One 上收集的現有感測器數據

image

  1. 將感測器資料發佈到 JEDI One 的 MQTT 客戶端

image

  1. 顯示應用程序,將訂閱的傳感器數據列印到 PyPortal Titano 顯示器

image

1 - 在 PyPortal Titano 上設定 CircuitPython。請參閱連結 Set up CircuitPython Quick Start!
(注意:本項目使用 CircuitPython 6.3.0)

2 - 安裝應用程式所需的函式庫:

檢查下列程式庫是否安裝在 CIRCUITPY (D:\lib) 中。所需的庫可從 CircuitPython 庫下載。(註:本項目使用 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 資料管理軟體和樹莓派的結合形成了一個獨立的、低成本的、易於使用的物聯網 MQTT 伺服器平台。可以輕鬆地將客戶機設備配置為訂閱和/或發佈到 MQTT 伺服器平台上的主題,而不需要第三方雲端服務或網路連線。

參考資料