MQTT Publish 데이터를 Flask에서 Subscribe 하여 받은 후, Vue로 socket 통신하는 과정을 기록하려 한다.
MQTT 통신은 IoT 기술에 많이 쓰이는데 현재 프로젝트에서도 센서 데이터를 무선으로 통신하기 위해 사용한다.
센서 데이터를 측정하여 gateway로 보내고, gateway에서 센서 데이터를 MQTT Publish로 발행한다.
이를 서버(Flask)에서 MQTT Subscribe로 구독하여 데이터를 받고 DB에도 저장. 그 후, Socket emit을 통해 Vue.js로 보낸 후 UI 상에 실시간 데이터를 보여줄 수 있도록 한다.
✅ 참고 사이트 : https://www.eclipse.org/paho/index.php?page=clients/python/docs/index.php#using-callback
이전 프로젝트에서는 publish에 paho.mqtt
를 써서 이번에도 사용하였다.
결론적으로 이 모듈은 socket 통신과 함께 사용할 수 없어 다른 모듈을 사용했다.
먼저 paho.mqtt
를 사용하여 삽질한 과정은 아래와 같다.
pip install paho-mqtt
# backend/main.py
from flask import Flask
from flask_socketio import SocketIO
app = Flask(__name__)
socketio = SocketIO(app, cors_allowed_origins="*", engineio_logger=False)
from backend.ws import *
socketio.run(app, debug=True, host="0.0.0.0", port=port)
# backend/ws.py
import paho.mqtt.subscribe as subscribe
from backend import app, socketio
from flask_socketio import emit
def on_message_print(client, userdata, message):
try:
print(message.payload)
emit('data', message.payload)
except Exception as e:
print(e)
@socketio.on('connect', namespace='/receiver')
def connect():
print("Connected")
subscribe.callback(on_message_print, "/example", hostname="localhost", port=1883)
@socketio.on('disconnect', namespace='/receiver')
def disconnect():
print('Disconnected')
처음에는 paho.mqtt.subscribe
모듈을 사용하여 subscribe를 callback 하는 형식으로 socket 메세지를 받으려 했다.
callback으로 받은 message.payload를 출력하니 값이 제대로 받아지기에 이를 emit으로 socket 통신을 해보니 vue에서 받는 데이터가 전혀 없었다.
그래서 다른 방법인 paho.mqtt.client
를 이용했다.
# backend/ws.py
import paho.mqtt.client as client
from backend import app, socketio
from flask_socketio import emit
def on_connect(client, userdata, rc, data):
print("connect")
def on_message(client, userdata, msg, data):
print(msg.payload)
emit('data', msg.payload
@socketio.on('connect', namespace='/receiver')
def connect():
print("Connected")
client = mqtt.Client()
client.username_pw_set(username, password)
client.on_connect = on_connect
client.on_message = on_message
client.subscribe('/example')
client.connect('localhost', 1883)
client.loop_start()
@socketio.on('disconnect', namespace='/receiver')
def disconnect():
print('Disconnected')
client.disconnect()
여전히 socket 통신이 되지 않았다.
RuntimeError: Working outside of request context.
이런 오류가 발생하여 그냥 emit이 아닌 SocketIO로 정의한 socketio.emit을 사용하였다. 에러는 뜨지 않았으나 여전히 통신이 되지 않았고, 이상하게 도중에 flask를 종료시키니 그 동안 쌓여있던 emit 메시지가 vue로 한 번에 날라가는 현상이 있었다.
loop_start 이후 mqtt 통신으로만 thread가 돌다가 종료되고 나서 쌓인 socket 통신이 뒤늦게 동작하는 것 같았다. thread를 적절히 사용하면 해결할 수 있을 것 같아 다양하게 수정해보았지만 Exception in thread Thread-1:
와 같은 오류로 Thread-1, 4, 5 까지 다양한 에러를 보았다.
이 paho.mqtt
모듈의 정확한 사용법과 해결방법을 발견하지 못했으나 개인적으로 생각해본건 socketio 를 선언할 때 Flask의 app을 연결하듯이 (socketio = SocketIO(app)
) mqtt도 이러한 형태로 구현해야 socket과 mqtt가 동시에 통신할 수 있을 것 같다.
결론은 paho.mqtt
를 사용했을 때는 socket 통신과 mqtt 통신이 동시에 되지 않는 것이다.
✅ 참고 사이트 : https://flask-mqtt.readthedocs.io/en/latest/usage.html#interact-with-socketio
pip install Flask-MQTT
# backend/main.py
from flask import Flask
from flask_socketio import SocketIO
from flask_mqtt import Mqtt
app = Flask(__name__)
app.config['MQTT_BROKER_URL'] = "localhost"
app.config['MQTT_BROKER_PORT'] = 1883
mqtt = Mqtt(app)
socketio = SocketIO(app, cors_allowed_origins="*", engineio_logger=False)
from backend.ws import *
socketio.run(app, debug=True, host="0.0.0.0", port=8080)
# backend/ws.py
from backend import app, socketio, mqtt
from flask_socketio import emit
@mqtt.on_connect()
def handle_connect(client, userdata, flags, rc):
print("mqtt connect")
@mqtt.on_message()
def handle_mqtt_message(client, userdata, message):
mqtt_data = json.loads(message.payload.decode())
socketio.emit('data', mqtt_data, namespace='/receiver')
@socketio.on('connect', namespace='/receiver')
def connect():
print("Connected")
mqtt.subscribe('/example')
@socketio.on('disconnect', namespace='/receiver')
def disconnect():
print('Disconnected')
mqtt.unsubscribe('/example')
이 모듈에서 방법을 찾았다.
mqtt와 socketio에 app을 연결시켜 통신하도록 하면 동시에 통신이 된다!!!!
이거 때문에 이틀 내내 골머리를 앓았는데 해결 돼서 정말 다행이다 😊💚🧡💛💙💜
혹시 나 처럼 flask에서 mqtt와 socket.io를 같이 쓸 일이 있다면 이걸 참고하자 ㅠㅠㅠㅠㅠㅠ