Add telemetry database insertion

This commit is contained in:
2026-01-04 12:11:30 +01:00
parent 9be0c7faf6
commit 4963db4205
10 changed files with 173 additions and 111 deletions

View File

@@ -48,7 +48,7 @@ class ClientApi:
This method makes a synchronous HTTP request by default. To make an This method makes a synchronous HTTP request by default. To make an
asynchronous HTTP request, please pass async_req=True asynchronous HTTP request, please pass async_req=True
>>> thread = api.create(client_dto, async_req=True) >>> thread = api.create_forwarder(client_dto, async_req=True)
>>> result = thread.get() >>> result = thread.get()
:param client_dto: (required) :param client_dto: (required)

View File

@@ -1,22 +1,23 @@
import json import json
import os import os
import threading
import requests
from dotenv import load_dotenv from dotenv import load_dotenv
from flask import Flask from flask import Flask
from src.controllers.AuthController import AuthController from src.controllers.AuthController import AuthController
from src.controllers.ClientController import ClientController from src.controllers.ClientController import ClientController
from src.controllers.message_controller import MessageController from src.controllers.message_controller import MessageController
from src.controllers.mqtt_forwarder import MQTTForwarder from src.controllers.mqtt_forwarder import create_forwarder
from src.models.AuthData import AuthData from src.models.AuthData import AuthData
from src.services.mongo_service import MongoService
from src.services.mqtt_service import MQTTService from src.services.mqtt_service import MQTTService
load_dotenv() load_dotenv()
app = Flask(__name__) app = Flask(__name__)
database_uri = os.getenv("MONGO_URI", "mongodb://localhost:27017")
database_name = os.getenv("DATABASE_NAME", "default")
local_broker_address = os.environ.get("LOCAL_BROKER_ADDRESS", "127.0.0.1") local_broker_address = os.environ.get("LOCAL_BROKER_ADDRESS", "127.0.0.1")
local_broker_port = int(os.environ.get("LOCAL_BROKER_PORT", 1883)) local_broker_port = int(os.environ.get("LOCAL_BROKER_PORT", 1883))
@@ -25,78 +26,22 @@ api_broker_port = int(os.environ.get("API_BROKER_PORT", 1883))
auth_data = AuthData() auth_data = AuthData()
database_service = MongoService(database_uri, database_name)
auth_controller = AuthController(app, auth_data, "https://192.168.15.120:8000") auth_controller = AuthController(app, auth_data, "https://192.168.15.120:8000")
client_controller = ClientController(app, auth_data, "https://192.168.15.120:8000") client_controller = ClientController(app, auth_data, "https://192.168.15.120:8000")
message_controller = MessageController(app, auth_data, "https://192.168.15.120:8000") message_controller = MessageController(app, auth_data, "https://192.168.15.120:8000", database_service)
def handle_login(data):
local_broker, api_broker, forwarder = create_forwarder(data, local_broker_address, local_broker_port, api_broker_address, api_broker_port)
def handle_message_received(topic: str, payload: str):
try:
print("=== MQTT MESSAGE RECEIVED ===", flush=True)
print("Raw payload:", payload, flush=True)
print("Payload type:", type(payload), flush=True)
data = json.loads(payload)
print("Parsed payload:", data, flush=True)
url = "https://192.168.15.125:1880/message/receive"
response = requests.post(
url,
json=data,
verify=False,
timeout=5
)
print("=== NODE-RED RESPONSE ===", flush=True)
print("Status code:", response.status_code, flush=True)
print("Headers:", response.headers, flush=True)
print("Raw response:", repr(response.text), flush=True)
if response.text.strip():
content_type = response.headers.get("Content-Type", "")
if content_type.startswith("application/json"):
print("Response JSON:", response.json(), flush=True)
else:
print("Response is not JSON", flush=True)
else:
print("Node-RED returned an empty response body", flush=True)
except json.JSONDecodeError as e:
print("Incoming payload is NOT valid JSON:", e, flush=True)
except requests.RequestException as e:
print("HTTP request to Node-RED failed:", e, flush=True)
except Exception as e:
print("Unexpected error:", e, flush=True)
def start_mqtt(data : AuthData):
client_id = data.get_client_id() client_id = data.get_client_id()
api_broker.subscribe(f"/chat/{client_id}/message", message_controller.handle_message_received)
forwarder.start(f"/customer/telemetry/#", f"/board-mate/{client_id}/telemetry", api_broker.publish)
local_broker = MQTTService(
local_broker_address,
local_broker_port,
client_id=client_id,
username="main",
password="hepl",
)
api_broker = MQTTService(
api_broker_address,
api_broker_port,
client_id=client_id,
username="customer",
password="hepl",
)
api_broker.subscribe(f"/chat/{client_id}/message", handle_message_received)
forwarder = MQTTForwarder(client_id, local_broker, api_broker)
forwarder.start(f"/customer/telemetry/#", f"/board-mate/{client_id}/telemetry")
if __name__ == '__main__': if __name__ == '__main__':
auth_controller.set_on_login(start_mqtt) auth_controller.set_on_login(handle_login)
app.run(host="0.0.0.0", port=5000, debug=True) app.run(host="0.0.0.0", port=5000, debug=True)

View File

@@ -10,7 +10,8 @@ services:
- mongo - mongo
- mosquitto - mosquitto
environment: environment:
- MONGO_URI=mongodb://mongo:27017/mydb - MONGO_URI=mongodb://user:psk358xpg@customer-database:27017
- DATABASE_NAME=customer-db
- LOCAL_BROKER_USERNAME=main - LOCAL_BROKER_USERNAME=main
- LOCAL_BROKER_PASSWORD=hepl - LOCAL_BROKER_PASSWORD=hepl
- LOCAL_BROKER_ADDRESS=customer-broker - LOCAL_BROKER_ADDRESS=customer-broker

View File

@@ -1,8 +1,12 @@
db = db.getSiblingDB("customer-db"); db = db.getSiblingDB("customer-db");
db.createCollection("systems"); db.createCollection("messages");
db.createCollection("clients");
db.createCollection("rooms");
db.createCollection("telemetry");
db.createCollection("games"); db.createCollection("games");
db.createUser({ db.createUser({
user: "user", user: "user",
pwd: "psk358xpg", pwd: "psk358xpg",

View File

@@ -1,15 +1,16 @@
aenum
flask flask
requests
paho-mqtt paho-mqtt
python_dateutil python_dateutil
setuptools python-dotenv
urllib3
pydantic pydantic
typing-extensions pymongo
requests
python-dotenv python-dotenv
pyyaml pyyaml
aenum setuptools
python-dotenv typing-extensions
urllib3
file:./api-resources/auth-components file:./api-resources/auth-components
file:./api-resources/client-components file:./api-resources/client-components

View File

@@ -1,8 +1,10 @@
import requests
from board_mate.message import Configuration, ApiClient, MessageDto, MessageApi, MessagePostRequestDto from board_mate.message import Configuration, ApiClient, MessageDto, MessageApi, MessagePostRequestDto
from flask import jsonify, request from flask import jsonify, request
from pydantic import StrictStr from pydantic import StrictStr
from src.models.AuthData import AuthData from src.models.AuthData import AuthData
from src.services.mongo_service import MongoService
from src.services.mqtt_service import MQTTService from src.services.mqtt_service import MQTTService
import json import json
@@ -11,12 +13,14 @@ class MessageController:
_client_id : MQTTService = None _client_id : MQTTService = None
_auth_data : AuthData = None _auth_data : AuthData = None
_database_service : MongoService = None
def __init__(self, app, auth_data : AuthData, host : str): def __init__(self, app, auth_data : AuthData, host : str, database_service : MongoService):
self._register_routes(app) self._register_routes(app)
self.config = Configuration(host=host) self.config = Configuration(host=host)
self.config.verify_ssl=False self.config.verify_ssl=False
self._auth_data = auth_data self._auth_data = auth_data
self._database_service = database_service
def _register_routes(self, app): def _register_routes(self, app):
app.add_url_rule("/message/send", view_func=self.send, methods=['POST']) app.add_url_rule("/message/send", view_func=self.send, methods=['POST'])
@@ -40,8 +44,33 @@ class MessageController:
) )
message_api.post_message(new_message) message_api.post_message(new_message)
return jsonify({"success" : True, "message": None}), 200 return jsonify({"success" : True, "message": None}), 200
except Exception as e: except Exception as e:
print(e) print(e)
return jsonify({"success" : False, "message" : f"An error occurred : {self._auth_data.get_token()} {self._auth_data.get_client_id()} \n {e}"}), 500 return jsonify({"success" : False, "message" : f"An error occurred : {self._auth_data.get_token()} {self._auth_data.get_client_id()} \n {e}"}), 500
def handle_message_received(self, topic: str, payload: str) -> None :
try:
data = json.loads(payload)
print("Parsed payload:", data, flush=True)
url = "https://192.168.15.125:1880/message/receive"
response = requests.post(
url,
json=data,
verify=False,
timeout=5
)
self._database_service.insert("messages", data)
except json.JSONDecodeError as e:
print("Incoming payload is NOT valid JSON:", e, flush=True)
except requests.RequestException as e:
print("HTTP request to Node-RED failed:", e, flush=True)
except Exception as e:
print("Unexpected error:", e, flush=True)

View File

@@ -1,21 +1,60 @@
import json
from typing import Callable
from pymongo import MongoClient
from src.models.AuthData import AuthData
from src.services.mongo_service import MongoService
from src.services.mqtt_service import MQTTService from src.services.mqtt_service import MQTTService
class MQTTForwarder: class MQTTForwarder:
client_id : str _client_id : str
local_broker : MQTTService _local_broker : MQTTService
central_broker : MQTTService _central_broker : MQTTService
_db_service : MongoService
def __init__(self, client_id : str, local_mqtt: MQTTService, central_mqtt: MQTTService): def __init__(self, client_id : str, local_mqtt: MQTTService, central_mqtt: MQTTService, db_service : MongoService):
self.client_id = client_id self._client_id = client_id
self.local_broker = local_mqtt self._local_broker = local_mqtt
self.central_broker = central_mqtt self._central_broker = central_mqtt
self._db_service = db_service
def start(self, src_topic: str, dst_topic: str): def start(self, src_topic: str, dst_topic: str, handler : Callable[[str, str], None]):
try: try:
def forward_handler(topic: str, msg: str): def forward_handler(topic: str, msg: str):
self.central_broker.publish(dst_topic, msg) #self.central_broker.publish(dst_topic, msg)
data = json.loads(msg)
self._db_service.insert("telemetry", data)
handler(dst_topic, msg)
self.local_broker.subscribe(src_topic, forward_handler) self._local_broker.subscribe(src_topic, forward_handler)
except Exception as e: except Exception as e:
print(f"An error occurred while forwarding from {src_topic} to {dst_topic}: {e}") print(f"An error occurred while forwarding from {src_topic} to {dst_topic}: {e}")
def create_forwarder(auth_data : AuthData,
local_broker_address : str, local_broker_port : int,
api_broker_address : str, api_broker_port : int) -> tuple[MQTTService, MQTTService, MQTTForwarder]:
client_id = auth_data.get_client_id()
local_broker = MQTTService(
local_broker_address,
local_broker_port,
client_id=client_id,
username="main",
password="hepl",
)
api_broker = MQTTService(
api_broker_address,
api_broker_port,
client_id=client_id,
username="customer",
password="hepl",
)
forwarder = MQTTForwarder(client_id, local_broker, api_broker)
return local_broker, api_broker, forwarder

View File

@@ -0,0 +1,44 @@
import json
from pymongo import MongoClient
from pymongo.synchronous.database import Database
class MongoService:
_client : MongoClient
_db : Database
def __init__(self, uri : str, database : str):
self._client = MongoClient(uri)
self._db = self._client[database]
def insert(self, collection : str, data : object):
collection = self._db[collection]
payload = self._to_document(data)
result = collection.insert_one(payload)
return result.inserted_id
def find(self, collection: str, field: str, value):
col = self._db[collection]
return list(col.find({field: value}))
def _to_document(self, obj):
if obj is None or isinstance(obj, (str, int, float, bool)):
return obj
if isinstance(obj, list):
return [self._to_document(i) for i in obj]
if isinstance(obj, dict):
return {k: self._to_document(v) for k, v in obj.items()}
if hasattr(obj, "__dict__"):
return {
k: self._to_document(v)
for k, v in vars(obj).items()
if not k.startswith("_")
}
return str(obj)

View File

@@ -41,7 +41,8 @@ class MQTTService:
self._connected = False self._connected = False
def connect(self): def connect(self):
if not self._connected: if self._connected: return
print(f"Connecting to {self.address}...") print(f"Connecting to {self.address}...")
self.client.connect(self.address, self.port) self.client.connect(self.address, self.port)
self.client.loop_start() self.client.loop_start()
@@ -52,8 +53,6 @@ class MQTTService:
if not self._connected: if not self._connected:
raise ConnectionError(f"Cannot connect to MQTT broker at {self.address}:{self.port}") raise ConnectionError(f"Cannot connect to MQTT broker at {self.address}:{self.port}")
print(f"Successfully connected to {self.address}") print(f"Successfully connected to {self.address}")
else :
print(f"Already connected to {self.address}...")
def disconnect(self): def disconnect(self):
if self._connected: if self._connected:

View File

@@ -7,29 +7,29 @@ from serial import Serial
class SerialReader: class SerialReader:
serial : Serial = None serial : Serial = None
__thread : Thread | None = None _thread : Thread | None = None
__listeners : list[Callable] = None _listeners : list[Callable] = None
def __init__(self, port, baudrate): def __init__(self, port, baudrate):
self.serial = Serial(port, baudrate) self.serial = Serial(port, baudrate)
self._run_event = threading.Event() self._run_event = threading.Event()
self.__listeners = [] self._listeners = []
def start(self) -> None: def start(self) -> None:
self._run_event.set() self._run_event.set()
if self.__thread is None or not self.__thread.is_alive(): if self._thread is None or not self._thread.is_alive():
self.__thread = Thread(target=self._read, daemon=True) self._thread = Thread(target=self._read, daemon=True)
self.__thread.start() self._thread.start()
def stop(self) -> None: def stop(self) -> None:
if self._run_event.is_set(): if self._run_event.is_set():
self._run_event.clear() self._run_event.clear()
def subscribe(self, listener : Callable[[str], None]) -> None: def subscribe(self, listener : Callable[[str], None]) -> None:
self.__listeners.append(listener) self._listeners.append(listener)
def _notify(self, data : str): def _notify(self, data : str):
for listener in self.__listeners: for listener in self._listeners:
listener(data) listener(data)
def _read(self): def _read(self):