integrated send mqtt message; optimised tui; logging added

This commit is contained in:
Dirk Alders 2025-07-20 14:48:28 +02:00
parent 011ee98477
commit b3db98346f
5 changed files with 69 additions and 18 deletions

3
.gitmodules vendored
View File

@ -1,3 +1,6 @@
[submodule "mqtt"] [submodule "mqtt"]
path = mqtt path = mqtt
url = https://git.mount-mockery.de/pylib/mqtt.git url = https://git.mount-mockery.de/pylib/mqtt.git
[submodule "report"]
path = report
url = https://git.mount-mockery.de/pylib/report.git

3
config.py Normal file
View File

@ -0,0 +1,3 @@
APP_NAME = 'mqtt_sniffer'
DEBUG = True

View File

@ -1,19 +1,24 @@
import argparse import argparse
import config
import getpass import getpass
import json
import logging import logging
import mqtt import mqtt
import re import re
import report
import time import time
from textual.app import App, ComposeResult from textual.app import App, ComposeResult
from textual.containers import Vertical from textual.containers import Vertical
from textual.widgets import Footer, Header, Input, RichLog from textual.widgets import Footer, Header, Input, RichLog, Button
try:
from config import APP_NAME as ROOT_LOGGER_NAME
except ImportError:
ROOT_LOGGER_NAME = 'root'
logger = logging.getLogger(ROOT_LOGGER_NAME)
# TODO: Integrate sending of message (topic + payload) class MqttHandler(object):
class MqttReceiver(object):
def __init__(self, app): def __init__(self, app):
self.app = app self.app = app
args = app.args args = app.args
@ -22,9 +27,18 @@ class MqttReceiver(object):
self.mqtt_client = mqtt.mqtt_client('mqtt_sniffer', args.hostname, args.port, username=args.username, password=password) self.mqtt_client = mqtt.mqtt_client('mqtt_sniffer', args.hostname, args.port, username=args.username, password=password)
self.mqtt_client.add_callback("#", self.__rx__) self.mqtt_client.add_callback("#", self.__rx__)
def __get_logger__(self, prefix, topic):
return logging.getLogger(ROOT_LOGGER_NAME).getChild(prefix + '.' + topic.replace('/', '.'))
def __rx__(self, client, userdata, message): def __rx__(self, client, userdata, message):
logger = self.__get_logger__('rx', message.topic)
logger.debug("Received message: topic=%s, payload=%s type=%s", message.topic, repr(message.payload), repr(type(message.payload)))
self.app.call_from_thread(self.app.add_log, message) self.app.call_from_thread(self.app.add_log, message)
def send(self, topic, payload):
logger = self.__get_logger__('tx', topic)
logger.debug("Sending message: topic=%s, payload=%s type=%s", topic, repr(payload), repr(type(payload)))
self.mqtt_client.send(topic, payload)
class MqttSniffer(App): class MqttSniffer(App):
"""a textual application for viewing mqtt messages.""" """a textual application for viewing mqtt messages."""
@ -38,8 +52,11 @@ class MqttSniffer(App):
self.args = args self.args = args
self.password = password self.password = password
# #
self.mqtt = None
self.all_logs = [] self.all_logs = []
self.topic_filter = "" self.topic_filter = ""
self.send_topic = ""
self.send_payload = ""
self.log_display = RichLog(highlight=True, markup=True) self.log_display = RichLog(highlight=True, markup=True)
def compose(self) -> ComposeResult: def compose(self) -> ComposeResult:
@ -49,11 +66,14 @@ class MqttSniffer(App):
yield self.log_display yield self.log_display
with Vertical(id="bottom-bar"): with Vertical(id="bottom-bar"):
yield Input(placeholder="topic filter...", id="topic_filter") yield Input(placeholder="topic filter...", id="topic_filter")
yield Input(placeholder="topic...", id="send_topic")
yield Input(placeholder="payload...", id="send_payload")
yield Button("Send", variant="success", id="send_button")
yield Footer() yield Footer()
def on_mount(self) -> None: def on_mount(self) -> None:
"""start the mqtt receiver.""" """start the mqtt receiver."""
MqttReceiver(self) self.mqtt = MqttHandler(self)
def add_log(self, record) -> None: def add_log(self, record) -> None:
"""Add new mqt messages and update the tui.""" """Add new mqt messages and update the tui."""
@ -74,15 +94,10 @@ class MqttSniffer(App):
pass # No valid regular expression pass # No valid regular expression
if topic_match: if topic_match:
try:
payload = json.loads(record.payload)
except:
payload = record.payload.decode('utf-8')
message = ( message = (
f"[[dim]{asctime}[/dim]] " f"[[dim]{asctime}[/dim]] "
f"[bold]{record.topic}[/bold] - " f"[bold]{record.topic}[/bold] - "
f"{repr(payload)}" f"{repr(record.payload)}"
) )
self.log_display.write(message) self.log_display.write(message)
@ -94,14 +109,29 @@ class MqttSniffer(App):
self._apply_filters_to_log(record) self._apply_filters_to_log(record)
def on_input_changed(self, message: Input.Changed) -> None: def on_input_changed(self, message: Input.Changed) -> None:
"""Update the filter and filtered messages after the user did changes.""" """Update the tui inputs and execute task, if requireed."""
if message.input.id == "topic_filter": if message.input.id == "topic_filter":
self.topic_filter = message.value self.topic_filter = message.value
self._update_display()
elif message.input.id == "send_topic":
self.send_topic = message.value
elif message.input.id == "send_payload":
self.send_payload = message.value
self._update_display() def on_button_pressed(self, event: Button.Pressed) -> None:
"""Event handler called when a button is pressed."""
if event.button.id == "send_button":
if self.mqtt is not None:
self.mqtt.send(self.send_topic, self.send_payload)
if __name__ == "__main__": if __name__ == "__main__":
#
# Logging
#
if config.DEBUG:
report.appLoggingConfigure(None, None, ((config.APP_NAME, logging.DEBUG), ), target_level=logging.WARNING, fmt=report.SHORT_FMT, host='localhost', port=19996)
# #
# Parse Arguments # Parse Arguments
# #

1
report Submodule

@ -0,0 +1 @@
Subproject commit 74e6f20d09cf76b3fbbdfa04c192b01708e50d5d

View File

@ -2,16 +2,30 @@
layout: vertical; layout: vertical;
overflow-y: scroll; overflow-y: scroll;
scrollbar-gutter: stable; scrollbar-gutter: stable;
height: 89%; height: 1fr;
} }
#bottom-bar { #bottom-bar {
layout: grid; layout: grid;
grid-size: 1; grid-size: 4;
height: 11%; grid-columns: 3fr 2fr 2fr 1fr;
height: 4;
border-top: solid $primary; border-top: solid $primary;
} }
#module_filter { #topic_filter {
width: 100%; width: 100%;
} }
#send_button {
width: 100%;
}
#send_topic {
width: 100%;
}
#send_payload {
width: 100%;
}