라즈베리 파이, PyPortal Titano 및 Machinechat의 JEDI One으로 IoT MQTT 브로커 설치 및 테스트 하기

요약

이 프로젝트에서는 Machinechat의 JEDI One IoT 데이터 관리 소프트웨어를 사용하여 라즈베리 파이 4에 IoT MQTT 브로커를 설치합니다. JEDI One은 MQTT 브로커로 설정 가능한 데이터 수집기를 포함하고 있어서 외부 클라이언트 장치가 JEDI One의 토픽(topic)을 발행(publish) 및(또는) 구독(subscribe)할 수 있습니다. MQTT 브로커를 테스트하기 위해, Adafruit PyPortal Titano를 와이파이를 통해 라즈베리 파이의 MQTT 브로커를 구독 및 발행하는 클라이언트 장치로 설정합니다.

image

배경

MQTT(Message Queuing Telemetry Transport, 메시지 큐 원격 전송)는 장치 간에 메시지를 전송하는 경량형 발행-구독 방식 네트워크 프로토콜을 정의하는 개방형 OASIS 및 ISO 표준으로, 하나의 메시지 브로커와 다수의 클라이언트라는 두 종류의 네트워크 개체를 포함합니다. Machinechat의 JEDI One IoT 플랫폼에는 MQTT 메시지 브로커로 설정할 수 있는 데이터 수집기가 포함되어 있습니다. JEDI One에 토픽을 발행하거나 구독하도록 외부 클라이언트 장치를 설정할 수 있습니다. 토픽에는 MQTT뿐만 아니라 모든 출처에서 JEDI One으로 들어오는 모든 데이터가 포함됩니다. JEDI One에서 발행 메시지는 JSON 페이로드 형식이어야 하며 구독 메시지도 JSON 페이로드 형식으로 제공합니다.

하드웨어

소프트웨어

  • JEDI One
    JEDI One은 즉시 사용 가능한 IoT 데이터 관리 소프트웨어 솔루션으로, 센서, 장치 그리고 기계로부터 데이터를 수집하고, 직관적인 실시간 및 과거 데이터와 시스템 뷰 대시보드를 구축하고, 데이터 상태를 모니터링하고 자동으로 반응하는 규칙을 생성하고, 이메일 및 SMS로 경보 알림을 수신할 수 있습니다.
  • CircuitPython
    Adafruit의 MicroPython에서 분기된 CircuitPython은 저비용 마이크로컨트롤러에 대한 실험과 교육을 간단하게 할 수 있도록 고안되었습니다. 컴파일러, 링커 또는 IDE가 필요하지 않습니다.

구현

이 프로젝트의 경우, JEDI One 애플리케이션이 HTTP 데이터 수집기가 설정된 라즈베리 파이에 이미 설치되어 외부 센서 데이터를 수신하고 있습니다. 설명과 자세한 내용은 ESP32 및 TE Connectivity MS8607 센서를 사용하는 Machinechat을 참조하십시오. 다음으로 JEDI One MQTT 데이터 수집기를 설정합니다. 시스템을 테스트하기 위해, PyPortal Titano는 MQTT 브로커에 토픽을 구독 및 발행하도록 설정합니다. CircuitPython은 PyPortal에서 애플리케이션 코드를 구현하는 데 사용됩니다.

JEDI One MQTT 브로커 설정

1 - Machinechat JEDI One이 라즈베리 파이에 아직 설치되지 않은 경우 아래를 참조하십시오.

  • 라즈베리 파이 버전의 JEDI One을 준비합니다.
  • 라즈베리 파이에 설치합니다, Raspberry Pi - Installing JEDI One as a Service를 참조하십시오.
    주)게시글 작성 시점에, DigiKey에서는 JEDI One을 취급하고 있지 않습니다.

2 - MQTT 브로커 설정
JEDI One의 “Data Collectors” 탭에서 "Add Collector"를 선택하고 설정합니다. "Data Collector"의 이름을 지정하고 "Collector Type"에서 "MQTT Broker"를 선택합니다. MQTT 수집기 설정 스크린샷의 "Listen IP"는 JEDI One 라즈베리 파이의 IP 주소이며 "Listen Port"는 1883입니다. (참고: 아래 예시에서는 암호화되지 않은 설정을 보여주지만 Machinechat의 제품 설명 - How to Generate TLS Certificates and Keys에 설명된 대로 TLS 암호화를 설정할 수 있습니다)
image

PyPortal에 CircuitPython으로 MQTT 클라이언트 테스트 애플리케이션 설정

Pyportal 테스트 애플리케이션은 세 부분으로 되어 있습니다.

  1. JEDI One에서 수집 중인 기존 센서 데이터를 구독하는 MQTT 클라이언트

  2. JEDI One에 센서 데이터를 발행하는 MQTT 클라이언트
    image

  3. 구독한 센서 데이터를 PyPortal Titano 디스플레이에 출력하는 디스플레이 애플리케이션

1 - PyPortal Titano에 CircuitPython을 설치합니다. 참조: https://learn.adafruit.com/adafruit-pyportal-titano/circuitpython
(참고: 이 프로젝트에서는 CircuitPython 6.3.0을 사용했습니다)

2 - 애플리케이션에 필요한 라이브러리를 설치합니다.
다음 라이브러리가 CIRCUITPY(D:\lib)에 설치되어 있는지 확인합니다. 라이브러리는 CircuitPython 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 클라이언트 테스트 애플리케이션용 최신 소스 코드는 아래 깃허브 링크에 있습니다.

결론

Machinechat의 JEDI One 데이터 관리 소프트웨어와 라즈베리 파이를 조합하여 사용하기 쉬운 저비용 독립형 IoT MQTT 브로커 플랫폼을 구축하였습니다. 서드 파티 클라우드 서비스 또는 인터넷 연결 없이 MQTT 브로커 플랫폼의 토픽을 구독하거나 발행하도록 클라이언트 장치를 쉽게 설정할 수 있습니다.

참고문헌



영문 원본: Set up and test IoT MQTT Broker with Raspberry Pi, PyPortal Titano and machinechat’s JEDI One