From ba7c1e972ea74790001cbd38650244a72a278eee Mon Sep 17 00:00:00 2001
From: Maciej Janicki
Date: Tue, 28 May 2024 19:35:34 +0200
Subject: [PATCH 1/7] Feature: mqttprotogenerator initial commit
---
src/mqttprotogenerator.py | 139 ++++++++++++++++++++++++++++++++++++++
1 file changed, 139 insertions(+)
create mode 100644 src/mqttprotogenerator.py
diff --git a/src/mqttprotogenerator.py b/src/mqttprotogenerator.py
new file mode 100644
index 0000000..e1ed3d4
--- /dev/null
+++ b/src/mqttprotogenerator.py
@@ -0,0 +1,139 @@
+from pkgutil import iter_modules
+import protofiles
+import importlib
+from google.protobuf.message import Message
+import random
+import string
+import pandas as pd
+import sys
+
+
+class MqttProtoGenerator():
+ logger = None
+
+ def __init__(self, message_name: string, message_file_path=''):
+ self.message_constructors = MqttProtoGenerator.get_message_constructors()
+ self.message_constructor = self.message_constructors[message_name]
+ self.constructed_messages = None
+ if message_file_path != '':
+ self.constructed_messages = self.read_message_csv(
+ message_name, message_file_path)
+ self.curr_message = 0
+
+ def get_message_constructors():
+ message_constructors = {}
+ for submodule in iter_modules(protofiles.__path__):
+ submodule_name = submodule.name
+ submodule_fullname = f"protofiles.{submodule_name}"
+ imported_module = importlib.import_module(submodule_fullname)
+ for v in vars(imported_module).values():
+ if not (isinstance(v, type) and issubclass(v, Message)):
+ continue
+ message_constructors[v.__name__] = v
+ return message_constructors
+
+ def get_random_message(self):
+ message = self.message_constructor()
+ for field_descriptor in self.message_constructor.DESCRIPTOR.fields:
+
+ message_type = field_descriptor.message_type
+ if field_descriptor.message_type is not None:
+ gen = MqttProtoGenerator(message_type.name)
+ m = gen.get_random_message()
+ attr = getattr(message, field_descriptor.name)
+ attr.CopyFrom(m)
+ continue
+ field_type = field_descriptor.type
+ # boolean
+ if field_type == 8:
+ setattr(message, field_descriptor.name, random.getrandbits(1))
+ # string
+ elif field_type == 9:
+ setattr(message, field_descriptor.name, ''.join(
+ random.choice(string.ascii_lowercase) for i in range(10)))
+ # float or double
+ elif field_type == 2 or field_type == 1:
+ setattr(message, field_descriptor.name, random.random())
+ # bytes
+ elif field_type == 12:
+ setattr(message, field_descriptor.name, random.randbytes(10))
+ # int
+ else:
+ setattr(message, field_descriptor.name,
+ random.randint(0, 1000))
+ return message
+
+ def get_next_message(self):
+ ret = None
+ if self.constructed_messages is not None:
+ ret = self.constructed_messages[self.curr_message]
+ self.curr_message = (self.curr_message +
+ 1) % len(self.constructed_messages)
+ else:
+ ret = self.get_random_message()
+ return ret
+
+ def read_message_csv(self, message_name: str, message_file: str) -> list:
+ df = None
+ try:
+ df = pd.read_csv(message_file)
+ except FileNotFoundError:
+ MqttProtoGenerator.logger.error(
+ f"ERROR: File {message_file} not found. Defaulting to sending random messages")
+ return
+
+ messages = []
+ for i in range(df.shape[0]):
+ fields = dict()
+ for field in df.columns:
+ fields[field] = df[field][i]
+ messages.append(self.construct_message(message_name, fields))
+ return messages
+
+ def construct_message(self, message_name: str, fields: dict) -> Message:
+ message_constructor = self.message_constructors[message_name]
+ new_message = message_constructor()
+ for field_descriptor in message_constructor.DESCRIPTOR.fields:
+
+ message_type = field_descriptor.message_type
+ if field_descriptor.message_type is not None:
+ passed_on_fields = dict()
+ for field in fields.keys():
+ if message_type.name not in field:
+ continue
+ sub_fields = field.split('.')
+ new_field_name = sub_fields[sub_fields.index(
+ message_type.name)+1:]
+ passed_on_fields['.'.join(
+ new_field_name)] = fields[field]
+ m = self.construct_message(message_type.name, passed_on_fields)
+ attr = getattr(new_message, field_descriptor.name)
+ attr.CopyFrom(m)
+ continue
+ field_type = field_descriptor.type
+ if field_descriptor.name not in fields.keys():
+ MqttProtoGenerator.logger.error(
+ f"ERROR: data for field \'{field_descriptor.name}\' not found when constructing message \'{message_name}\'.")
+ continue
+ # bolean
+ if field_type == 8:
+ setattr(new_message, field_descriptor.name,
+ bool(fields[field_descriptor.name]))
+ # string
+ elif field_type == 9:
+ setattr(new_message, field_descriptor.name,
+ fields[field_descriptor.name])
+ # float or double
+ elif field_type == 2 or field_type == 1:
+ setattr(new_message, field_descriptor.name,
+ float(fields[field_descriptor.name]))
+ # bytes
+ elif field_type == 12:
+ setattr(new_message, field_descriptor.name,
+ fields[field_descriptor.name])
+
+ # int
+ else:
+ setattr(new_message, field_descriptor.name,
+ int(fields[field_descriptor.name]))
+ return new_message
From 0ca285802097917c6b097ca95bd6b5fe3980f0f2 Mon Sep 17 00:00:00 2001
From: Maciej Janicki
Date: Tue, 28 May 2024 19:36:15 +0200
Subject: [PATCH 2/7] Update: random bugfixes with f-strings
---
src/gui/ui.py | 34 ++++++++++++++++++++++------------
src/mqttsim.py | 21 ++++++++++++++-------
2 files changed, 36 insertions(+), 19 deletions(-)
diff --git a/src/gui/ui.py b/src/gui/ui.py
index 7ee8577..31bf579 100644
--- a/src/gui/ui.py
+++ b/src/gui/ui.py
@@ -26,7 +26,8 @@ def __init__(self, icon: str, tooltip: str):
super(MqttSimTopicToolButton, self).__init__()
self.setCursor(Qt.CursorShape.PointingHandCursor)
self.setIcon(QIcon(icon))
- self.setToolTip(QCoreApplication.translate("MainWindow", tooltip, None))
+ self.setToolTip(QCoreApplication.translate(
+ "MainWindow", tooltip, None))
class MqttSimAddTopicWindow(Ui_AddTopicDialog, QDialog):
@@ -34,12 +35,14 @@ def __init__(self):
super(MqttSimAddTopicWindow, self).__init__()
self.setupUi(self)
self.setWindowIcon(QIcon(":/icons/mqtt.svg"))
- self.load_from_file_btn.clicked.connect(self.__on_load_from_file_btn_clicked)
+ self.load_from_file_btn.clicked.connect(
+ self.__on_load_from_file_btn_clicked)
self.__load_patterns()
self.predefined_pattern_combo_box.currentIndexChanged.connect(
self.__on_pattern_selected
)
- self.save_as_pattern_btn.clicked.connect(self.__on_save_as_pattern_btn_clicked)
+ self.save_as_pattern_btn.clicked.connect(
+ self.__on_save_as_pattern_btn_clicked)
def __on_load_from_file_btn_clicked(self) -> None:
filename, _ = QFileDialog.getOpenFileName(
@@ -89,7 +92,8 @@ def __load_patterns(self):
path.basename(pattern_filename).replace("_", " ")
)[0]
pattern_value = file.read()
- self.predefined_pattern_combo_box.addItem(pattern_name, pattern_value)
+ self.predefined_pattern_combo_box.addItem(
+ pattern_name, pattern_value)
def __on_save_as_pattern_btn_clicked(self):
pattern_name, accepted = QInputDialog.getText(
@@ -164,7 +168,8 @@ def __init__(self, topic_name: str):
# edit btn
self.edit_btn = MqttSimTopicToolButton(
- ":/icons/edit.svg", QCoreApplication.translate("MainWindow", "Edit", None)
+ ":/icons/edit.svg", QCoreApplication.translate(
+ "MainWindow", "Edit", None)
)
self.edit_btn.setCursor(Qt.CursorShape.PointingHandCursor)
hlayout.addWidget(self.edit_btn)
@@ -180,6 +185,7 @@ def set_topic_name(self, new_topic_name: str) -> None:
self.topic = new_topic_name
self.topic_lbl.setText(new_topic_name)
+
class MqttSimMainWindow(Ui_MainWindow, QMainWindow):
def __init__(self, sim: MqttSim):
super(MqttSimMainWindow, self).__init__()
@@ -207,12 +213,14 @@ def on_broker_connect_btn_clicked() -> None:
QCoreApplication.translate("MainWindow", "Connect", None)
)
self.broker_connect_btn.setToolTip(
- QCoreApplication.translate("MainWindow", "Connect to broker", None)
+ QCoreApplication.translate(
+ "MainWindow", "Connect to broker", None)
)
else:
if self.__sim.connect_to_broker():
self.broker_connect_btn.setText(
- QCoreApplication.translate("MainWindow", "Disconnect", None)
+ QCoreApplication.translate(
+ "MainWindow", "Disconnect", None)
)
self.broker_connect_btn.setToolTip(
QCoreApplication.translate(
@@ -227,7 +235,7 @@ def on_clear_logs_btn_clicked() -> None:
def on_add_topic_btn_clicked() -> None:
def validate_input(topic_config) -> bool:
return len(topic_config.get("topic")) > 0
-
+
add_topic_window = MqttSimAddTopicWindow()
while True:
if add_topic_window.exec() == QDialog.Accepted:
@@ -247,7 +255,8 @@ def validate_input(topic_config) -> bool:
break # Break out of the loop if dialog is cancelled
def on_broker_info_changed() -> None:
- self.__sim.set_broker(self.broker_hostname.text(), self.broker_port.value())
+ self.__sim.set_broker(
+ self.broker_hostname.text(), self.broker_port.value())
def on_topic_search_text_changed() -> None:
for widget in self.topics_list_widget.findChildren(MqttSimTopicWidget):
@@ -261,7 +270,8 @@ def on_topic_search_text_changed() -> None:
self.add_topic_btn.clicked.connect(on_add_topic_btn_clicked)
self.broker_hostname.textChanged.connect(on_broker_info_changed)
self.broker_port.valueChanged.connect(on_broker_info_changed)
- self.topic_search_line_edit.textChanged.connect(on_topic_search_text_changed)
+ self.topic_search_line_edit.textChanged.connect(
+ on_topic_search_text_changed)
def __add_topic_to_item_list(self, topic_uuid: str) -> None:
topic_name = self.__config.get_topic_data(topic_uuid).get("topic")
@@ -271,7 +281,7 @@ def on_remove_btn_clicked() -> None:
result = QMessageBox.question(
self,
"Remove topic?",
- f"Are you sure you want to remove topic {self.__config.get_topic_data(topic_uuid).get("topic")} [uuid={topic_uuid}]?",
+ f'Are you sure you want to remove topic {self.__config.get_topic_data(topic_uuid).get("topic")} [uuid={topic_uuid}]?',
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No)
if result == QMessageBox.StandardButton.Yes:
self.__sim.remove_topic(topic_uuid)
@@ -291,7 +301,7 @@ def on_edit_btn_clicked() -> None:
topic_widget.set_topic_name(edited_data.get("topic"))
self.__sim.edit(topic_uuid, edited_data)
self.__logger.info(
- f"Edited topic {data.get("topic")} [uuid={topic_uuid}] ({data} -> {edited_data})."
+ f'Edited topic {data.get("topic")} [uuid={topic_uuid}] ({data} -> {edited_data}).'
)
topic_widget.remove_btn.clicked.connect(on_remove_btn_clicked)
diff --git a/src/mqttsim.py b/src/mqttsim.py
index 5235cbf..8dfdbda 100644
--- a/src/mqttsim.py
+++ b/src/mqttsim.py
@@ -6,6 +6,7 @@
from mqttsimdatagenerator import MqttSimDataGenerator
from uuid import uuid4
+
class MqttSimConfig:
def __init__(self, path: str):
self.__config = ConfigHandler(path)
@@ -28,7 +29,7 @@ def put_broker(self, host: str, port: int, username: str | None = None, password
def get_broker(self) -> tuple[str, int, str, str]:
broker_info = self.__config.get("broker")
if broker_info is None:
- self.__config.put("broker", { "host": "localhost", "port": 1883 })
+ self.__config.put("broker", {"host": "localhost", "port": 1883})
return self.get_broker()
return (
broker_info.get("host", "localhost"),
@@ -69,7 +70,8 @@ def time_diff_in_seconds(time1, time2) -> int:
return diff_dt.total_seconds()
topics_data = self.__config.get_topics()
- last_sent = {topic_uuid: datetime.now() for topic_uuid in topics_data.keys()}
+ last_sent = {topic_uuid: datetime.now()
+ for topic_uuid in topics_data.keys()}
while not self.__should_stop_publishing_thread:
if not self.is_connected_to_broker():
@@ -95,7 +97,8 @@ def on_connect(client, userdata, flags, rc) -> None:
if rc == CONNACK_ACCEPTED:
self.__logger.info("Connected to broker.")
else:
- self.__logger.error(f"Error when connecting to broker (rc={rc}).")
+ self.__logger.error(
+ f"Error when connecting to broker (rc={rc}).")
def on_disconnect(client, userdata, rc) -> None:
if rc == 0:
@@ -154,14 +157,16 @@ def remove_topic(self, topic_uuid: str) -> None:
topic_data = self.__config.get_topic_data(topic_uuid)
self.__client.unsubscribe(topic_data.get("topic"))
self.__config.remove_topic(topic_uuid)
- self.__logger.info(f"Removed topic: {topic_data.get("topic")}) [uuid={topic_uuid}].")
+ self.__logger.info(
+ f'Removed topic: {topic_data.get("topic")} [uuid={topic_uuid}].')
del self.__topic_data_generators[topic_uuid]
# Adds topic to config (and saves it into config file).
# If publishing thread was already started, it will take the topic into account.
def add_topic(self, topic_config: dict) -> str:
uuid = self.__config.put_topic(topic_config)
- self.__logger.info(f"Added topic: {topic_config.get("topic")} [uuid={uuid}].")
+ self.__logger.info(
+ f'Added topic: {topic_config.get("topic")} [uuid={uuid}].')
self.__topic_data_generators[uuid] = MqttSimDataGenerator(
topic_config.get("data_format")
)
@@ -181,9 +186,11 @@ def edit(self, topic_uuid, new_data) -> None:
def send_single_message(self, topic_uuid) -> None:
if not self.is_connected_to_broker():
- self.__logger.error("Trying to send message when not connected to broker.")
+ self.__logger.error(
+ "Trying to send message when not connected to broker.")
return
topic_data = self.__config.get_topic_data(topic_uuid)
- self.__logger.info(f"Publishing data on {topic_data.get("topic")} [uuid={topic_uuid}]...")
+ self.__logger.info(
+ f'Publishing data on {topic_data.get("topic")} [uuid={topic_uuid}]...')
message = self.__topic_data_generators.get(topic_uuid).next_message()
self.__client.publish(topic_data.get("topic"), message)
From 157aa2cc5fd741ec9fe1d63eb24ef77866aa6158 Mon Sep 17 00:00:00 2001
From: Maciej Janicki
Date: Tue, 28 May 2024 20:08:08 +0200
Subject: [PATCH 3/7] Feature: integration of proto topic to mqttsim
---
src/mqttsim.py | 62 ++++++++++++++++++++++++++++++--------
src/protofiles/__init__.py | 8 +++++
2 files changed, 57 insertions(+), 13 deletions(-)
create mode 100644 src/protofiles/__init__.py
diff --git a/src/mqttsim.py b/src/mqttsim.py
index 8dfdbda..4e8c2a1 100644
--- a/src/mqttsim.py
+++ b/src/mqttsim.py
@@ -5,6 +5,7 @@
from time import sleep
from mqttsimdatagenerator import MqttSimDataGenerator
from uuid import uuid4
+from mqttprotogenerator import MqttProtoGenerator
class MqttSimConfig:
@@ -56,6 +57,14 @@ def __init__(self, config: MqttSimConfig, logger: any):
self.__topic_data_generators = {
topic_uuid: MqttSimDataGenerator(topic_config.get("data_format"))
for topic_uuid, topic_config in self.__config.get_topics().items()
+ if "data_format" in topic_config
+ }
+ MqttProtoGenerator.logger = logger
+ self.__proto_topic_data_generators = {
+ topic_uuid: MqttProtoGenerator(
+ topic_config.get("message"), topic_config.get("file"))
+ for topic_uuid, topic_config in self.__config.get_topics().items()
+ if "message" in topic_config
}
self.__setup_client()
self.__setup_publishing_thread()
@@ -91,7 +100,16 @@ def time_diff_in_seconds(time1, time2) -> int:
def __setup_client(self) -> None:
def on_message(client, userdata, message) -> None:
- self.__logger.info(f"Received message from broker: '{message}'.")
+ if message.topic in self.__topic_data_generators:
+ self.__logger.info(
+ f"Received message from broker {message.topic}: '{message.payload}'.")
+ elif message.topic in self.__proto_topic_data_generators:
+ message_constructor = self.__proto_topic_data_generators[
+ message.topic].message_constructor
+ proto_message = message_constructor()
+ proto_message.ParseFromString(message.payload)
+ self.__logger.info(
+ f"Received message from broker on topic {message.topic}: '{proto_message}'.")
def on_connect(client, userdata, flags, rc) -> None:
if rc == CONNACK_ACCEPTED:
@@ -137,7 +155,7 @@ def connect_to_broker(self) -> bool:
return False
except Exception:
self.__logger.error(
- f"Unknown error occured when trying to connect to broker."
+ "Unknown error occured when trying to connect to broker."
)
return False
return True
@@ -157,9 +175,14 @@ def remove_topic(self, topic_uuid: str) -> None:
topic_data = self.__config.get_topic_data(topic_uuid)
self.__client.unsubscribe(topic_data.get("topic"))
self.__config.remove_topic(topic_uuid)
- self.__logger.info(
- f'Removed topic: {topic_data.get("topic")} [uuid={topic_uuid}].')
- del self.__topic_data_generators[topic_uuid]
+ if topic_uuid in self.__topic_data_generators:
+ self.__logger.info(
+ f'Removed topic: {topic_data.get("topic")} [uuid={topic_uuid}].')
+ del self.__topic_data_generators[topic_uuid]
+ else:
+ self.__logger.info(
+ f'Removed topic: {topic_data.get("topic")} [uuid={topic_uuid}].')
+ del self.__proto_topic_data_generators[topic_uuid]
# Adds topic to config (and saves it into config file).
# If publishing thread was already started, it will take the topic into account.
@@ -180,17 +203,30 @@ def get_config(self) -> MqttSimConfig:
def edit(self, topic_uuid, new_data) -> None:
self.__config.put_topic(new_data, uuid=topic_uuid)
- self.__topic_data_generators[topic_uuid].reinitalize(
- new_data.get("data_format")
- )
+ if topic_uuid in self.__topic_data_generators:
+ self.__topic_data_generators[topic_uuid].reinitalize(
+ new_data.get("data_format")
+ )
+ else:
+ self.__proto_topic_data_generators[topic_uuid] = MqttProtoGenerator(
+ new_data.get("message"), new_data.get("file"))
def send_single_message(self, topic_uuid) -> None:
if not self.is_connected_to_broker():
self.__logger.error(
"Trying to send message when not connected to broker.")
return
- topic_data = self.__config.get_topic_data(topic_uuid)
- self.__logger.info(
- f'Publishing data on {topic_data.get("topic")} [uuid={topic_uuid}]...')
- message = self.__topic_data_generators.get(topic_uuid).next_message()
- self.__client.publish(topic_data.get("topic"), message)
+ if topic_uuid in self.__topic_data_generators:
+ topic_data = self.__config.get_topic_data(topic_uuid)
+ self.__logger.info(
+ f'Publishing data on {topic_data.get("topic")} [uuid={topic_uuid}]...')
+ message = self.__topic_data_generators.get(
+ topic_uuid).next_message()
+ self.__client.publish(topic_data.get("topic"), message)
+ else:
+ self.__logger.info(
+ f'Publishing data on {topic_data.get("topic")} [uuid={topic_uuid}]...')
+ gen = self.__proto_topic_data_generators[topic_uuid]
+ proto_message = gen.get_next_message()
+ self.__client.publish(
+ topic_data.get("topic"), proto_message.SerializeToString())
diff --git a/src/protofiles/__init__.py b/src/protofiles/__init__.py
new file mode 100644
index 0000000..7f2f0fe
--- /dev/null
+++ b/src/protofiles/__init__.py
@@ -0,0 +1,8 @@
+from os.path import dirname, basename, isfile, join
+import os
+import sys
+import glob
+modules = glob.glob(join(dirname(__file__), "*.py"))
+__all__ = [basename(f)[:-3] for f in modules if isfile(f)
+ and not f.endswith('__init__.py')]
+sys.path.insert(0, os.path.abspath(os.path.dirname(__file__)))
From 948b40102bdb6e3ce38d6d74f1e0197b41d62e46 Mon Sep 17 00:00:00 2001
From: Maciej Janicki
Date: Tue, 28 May 2024 21:07:56 +0200
Subject: [PATCH 4/7] Update: Final integrations pre-merge
---
.gitignore | 4 +-
readme.md | 29 ++++
src/gui/addprototopicdialog.ui | 169 +++++++++++++++++++++++
src/gui/choosetopicdialog.ui | 45 ++++++
src/gui/generated/addprototopicdialog.py | 140 +++++++++++++++++++
src/gui/generated/choosetopicdialog.py | 53 +++++++
src/gui/ui.py | 121 ++++++++++++++--
src/mqttprotogenerator.py | 37 ++++-
src/mqttsim.py | 26 ++--
src/requirements.txt | 7 +
10 files changed, 601 insertions(+), 30 deletions(-)
create mode 100644 src/gui/addprototopicdialog.ui
create mode 100644 src/gui/choosetopicdialog.ui
create mode 100644 src/gui/generated/addprototopicdialog.py
create mode 100644 src/gui/generated/choosetopicdialog.py
diff --git a/.gitignore b/.gitignore
index a8e81fc..cda907d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,4 +7,6 @@ env/
venv/
.idea/
logs.txt
-.DS_Store
\ No newline at end of file
+.DS_Store
+*pb2.py
+.venv
diff --git a/readme.md b/readme.md
index 7825ce1..6980944 100644
--- a/readme.md
+++ b/readme.md
@@ -125,3 +125,32 @@ Main window
Add topic window
+
+# Protobuf
+To use protobuf place compiled message files in src/protofiles.
+
+## Custom message content
+
+To send a message with custom contents use *.csv file. Program will look for specified file in root directory.
+
+### Example:
+
+Given these protobuf messages:
+```protobuf
+message Message1 {
+ int32 my_int = 1;
+ Message2 nested_message = 2;
+}
+message Message2 {
+ int32 my_int = 1;
+}
+```
+
+example csv file for Message1 would look like this:
+
+```csv
+my_int,Message2.my_int
+123,456
+456,1312
+123123,45123
+```
diff --git a/src/gui/addprototopicdialog.ui b/src/gui/addprototopicdialog.ui
new file mode 100644
index 0000000..a94adc2
--- /dev/null
+++ b/src/gui/addprototopicdialog.ui
@@ -0,0 +1,169 @@
+
+
+ AddProtoTopicDialog
+
+
+
+ 0
+ 0
+ 457
+ 337
+
+
+
+
+ 12
+
+
+
+ Add topic
+
+
+ -
+
+
-
+
+
+
+ 14
+
+
+
+ Add topic
+
+
+
+ -
+
+
-
+
+
+ Name
+
+
+
+ -
+
+
+ Name of topic
+
+
+
+ -
+
+
+ Message
+
+
+
+ -
+
+
+ -
+
+
+ Interval (seconds)
+
+
+
+ -
+
+
+ Interval of incoming data
+
+
+ 0.100000000000000
+
+
+ 60.000000000000000
+
+
+ 0.500000000000000
+
+
+ 1.500000000000000
+
+
+
+ -
+
+
+ Manual
+
+
+
+ -
+
+
+ If checked, the data will be automatically send every <interval> seconds
+
+
+
+
+
+ false
+
+
+
+ -
+
+
+ -
+
+
+ File (optional)
+
+
+
+
+
+
+
+ -
+
+
+ Qt::Orientation::Horizontal
+
+
+ QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok
+
+
+
+
+
+
+
+
+ buttonBox
+ accepted()
+ AddProtoTopicDialog
+ accept()
+
+
+ 248
+ 254
+
+
+ 157
+ 274
+
+
+
+
+ buttonBox
+ rejected()
+ AddProtoTopicDialog
+ reject()
+
+
+ 316
+ 260
+
+
+ 286
+ 274
+
+
+
+
+
diff --git a/src/gui/choosetopicdialog.ui b/src/gui/choosetopicdialog.ui
new file mode 100644
index 0000000..3e49633
--- /dev/null
+++ b/src/gui/choosetopicdialog.ui
@@ -0,0 +1,45 @@
+
+
+ ChooseTopicDialog
+
+
+
+ 0
+ 0
+ 319
+ 153
+
+
+
+ Choose message type
+
+
+
+
+ 9
+ 19
+ 291
+ 121
+
+
+
+ -
+
+
+ Protobuf
+
+
+
+ -
+
+
+ JSON
+
+
+
+
+
+
+
+
+
diff --git a/src/gui/generated/addprototopicdialog.py b/src/gui/generated/addprototopicdialog.py
new file mode 100644
index 0000000..ccc58cd
--- /dev/null
+++ b/src/gui/generated/addprototopicdialog.py
@@ -0,0 +1,140 @@
+# -*- coding: utf-8 -*-
+
+################################################################################
+## Form generated from reading UI file 'addprototopicdialog.ui'
+##
+## Created by: Qt User Interface Compiler version 6.7.0
+##
+## WARNING! All changes made in this file will be lost when recompiling UI file!
+################################################################################
+
+from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
+ QMetaObject, QObject, QPoint, QRect,
+ QSize, QTime, QUrl, Qt)
+from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
+ QFont, QFontDatabase, QGradient, QIcon,
+ QImage, QKeySequence, QLinearGradient, QPainter,
+ QPalette, QPixmap, QRadialGradient, QTransform)
+from PySide6.QtWidgets import (QAbstractButton, QApplication, QCheckBox, QDialog,
+ QDialogButtonBox, QDoubleSpinBox, QFormLayout, QLabel,
+ QLineEdit, QListWidget, QListWidgetItem, QSizePolicy,
+ QVBoxLayout, QWidget)
+
+class Ui_AddProtoTopicDialog(object):
+ def setupUi(self, AddProtoTopicDialog):
+ if not AddProtoTopicDialog.objectName():
+ AddProtoTopicDialog.setObjectName(u"AddProtoTopicDialog")
+ AddProtoTopicDialog.resize(457, 337)
+ font = QFont()
+ font.setPointSize(12)
+ AddProtoTopicDialog.setFont(font)
+ self.verticalLayout_2 = QVBoxLayout(AddProtoTopicDialog)
+ self.verticalLayout_2.setObjectName(u"verticalLayout_2")
+ self.verticalLayout = QVBoxLayout()
+ self.verticalLayout.setObjectName(u"verticalLayout")
+ self.label = QLabel(AddProtoTopicDialog)
+ self.label.setObjectName(u"label")
+ font1 = QFont()
+ font1.setPointSize(14)
+ self.label.setFont(font1)
+
+ self.verticalLayout.addWidget(self.label, 0, Qt.AlignmentFlag.AlignTop)
+
+ self.formLayout = QFormLayout()
+ self.formLayout.setObjectName(u"formLayout")
+ self.name_lbl = QLabel(AddProtoTopicDialog)
+ self.name_lbl.setObjectName(u"name_lbl")
+
+ self.formLayout.setWidget(0, QFormLayout.LabelRole, self.name_lbl)
+
+ self.name_line_edit = QLineEdit(AddProtoTopicDialog)
+ self.name_line_edit.setObjectName(u"name_line_edit")
+
+ self.formLayout.setWidget(0, QFormLayout.FieldRole, self.name_line_edit)
+
+ self.format_lbl = QLabel(AddProtoTopicDialog)
+ self.format_lbl.setObjectName(u"format_lbl")
+
+ self.formLayout.setWidget(1, QFormLayout.LabelRole, self.format_lbl)
+
+ self.message_list = QListWidget(AddProtoTopicDialog)
+ self.message_list.setObjectName(u"message_list")
+
+ self.formLayout.setWidget(1, QFormLayout.FieldRole, self.message_list)
+
+ self.interval_lbl = QLabel(AddProtoTopicDialog)
+ self.interval_lbl.setObjectName(u"interval_lbl")
+
+ self.formLayout.setWidget(3, QFormLayout.LabelRole, self.interval_lbl)
+
+ self.interval_spin_box = QDoubleSpinBox(AddProtoTopicDialog)
+ self.interval_spin_box.setObjectName(u"interval_spin_box")
+ self.interval_spin_box.setMinimum(0.100000000000000)
+ self.interval_spin_box.setMaximum(60.000000000000000)
+ self.interval_spin_box.setSingleStep(0.500000000000000)
+ self.interval_spin_box.setValue(1.500000000000000)
+
+ self.formLayout.setWidget(3, QFormLayout.FieldRole, self.interval_spin_box)
+
+ self.manual_lbl = QLabel(AddProtoTopicDialog)
+ self.manual_lbl.setObjectName(u"manual_lbl")
+
+ self.formLayout.setWidget(4, QFormLayout.LabelRole, self.manual_lbl)
+
+ self.manual_check_box = QCheckBox(AddProtoTopicDialog)
+ self.manual_check_box.setObjectName(u"manual_check_box")
+ self.manual_check_box.setChecked(False)
+
+ self.formLayout.setWidget(4, QFormLayout.FieldRole, self.manual_check_box)
+
+ self.file_line_edit = QLineEdit(AddProtoTopicDialog)
+ self.file_line_edit.setObjectName(u"file_line_edit")
+
+ self.formLayout.setWidget(2, QFormLayout.FieldRole, self.file_line_edit)
+
+ self.path_label = QLabel(AddProtoTopicDialog)
+ self.path_label.setObjectName(u"path_label")
+
+ self.formLayout.setWidget(2, QFormLayout.LabelRole, self.path_label)
+
+
+ self.verticalLayout.addLayout(self.formLayout)
+
+
+ self.verticalLayout_2.addLayout(self.verticalLayout)
+
+ self.buttonBox = QDialogButtonBox(AddProtoTopicDialog)
+ self.buttonBox.setObjectName(u"buttonBox")
+ self.buttonBox.setOrientation(Qt.Orientation.Horizontal)
+ self.buttonBox.setStandardButtons(QDialogButtonBox.StandardButton.Cancel|QDialogButtonBox.StandardButton.Ok)
+
+ self.verticalLayout_2.addWidget(self.buttonBox)
+
+
+ self.retranslateUi(AddProtoTopicDialog)
+ self.buttonBox.accepted.connect(AddProtoTopicDialog.accept)
+ self.buttonBox.rejected.connect(AddProtoTopicDialog.reject)
+
+ QMetaObject.connectSlotsByName(AddProtoTopicDialog)
+ # setupUi
+
+ def retranslateUi(self, AddProtoTopicDialog):
+ AddProtoTopicDialog.setWindowTitle(QCoreApplication.translate("AddProtoTopicDialog", u"Add topic", None))
+ self.label.setText(QCoreApplication.translate("AddProtoTopicDialog", u"Add topic", None))
+ self.name_lbl.setText(QCoreApplication.translate("AddProtoTopicDialog", u"Name", None))
+#if QT_CONFIG(tooltip)
+ self.name_line_edit.setToolTip(QCoreApplication.translate("AddProtoTopicDialog", u"Name of topic", None))
+#endif // QT_CONFIG(tooltip)
+ self.format_lbl.setText(QCoreApplication.translate("AddProtoTopicDialog", u"Message", None))
+ self.interval_lbl.setText(QCoreApplication.translate("AddProtoTopicDialog", u"Interval (seconds)", None))
+#if QT_CONFIG(tooltip)
+ self.interval_spin_box.setToolTip(QCoreApplication.translate("AddProtoTopicDialog", u"Interval of incoming data", None))
+#endif // QT_CONFIG(tooltip)
+ self.manual_lbl.setText(QCoreApplication.translate("AddProtoTopicDialog", u"Manual", None))
+#if QT_CONFIG(tooltip)
+ self.manual_check_box.setToolTip(QCoreApplication.translate("AddProtoTopicDialog", u"If checked, the data will be automatically send every seconds", None))
+#endif // QT_CONFIG(tooltip)
+ self.manual_check_box.setText("")
+ self.path_label.setText(QCoreApplication.translate("AddProtoTopicDialog", u"File (optional)", None))
+ # retranslateUi
+
diff --git a/src/gui/generated/choosetopicdialog.py b/src/gui/generated/choosetopicdialog.py
new file mode 100644
index 0000000..038e7a7
--- /dev/null
+++ b/src/gui/generated/choosetopicdialog.py
@@ -0,0 +1,53 @@
+# -*- coding: utf-8 -*-
+
+################################################################################
+## Form generated from reading UI file 'choosetopicdialog.ui'
+##
+## Created by: Qt User Interface Compiler version 6.5.2
+##
+## WARNING! All changes made in this file will be lost when recompiling UI file!
+################################################################################
+
+from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
+ QMetaObject, QObject, QPoint, QRect,
+ QSize, QTime, QUrl, Qt)
+from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
+ QFont, QFontDatabase, QGradient, QIcon,
+ QImage, QKeySequence, QLinearGradient, QPainter,
+ QPalette, QPixmap, QRadialGradient, QTransform)
+from PySide6.QtWidgets import (QApplication, QDialog, QHBoxLayout, QPushButton,
+ QSizePolicy, QWidget)
+
+class Ui_ChooseTopicDialog(object):
+ def setupUi(self, ChooseTopicDialog):
+ if not ChooseTopicDialog.objectName():
+ ChooseTopicDialog.setObjectName(u"ChooseTopicDialog")
+ ChooseTopicDialog.resize(319, 153)
+ self.horizontalLayoutWidget = QWidget(ChooseTopicDialog)
+ self.horizontalLayoutWidget.setObjectName(u"horizontalLayoutWidget")
+ self.horizontalLayoutWidget.setGeometry(QRect(9, 19, 291, 121))
+ self.horizontalLayout = QHBoxLayout(self.horizontalLayoutWidget)
+ self.horizontalLayout.setObjectName(u"horizontalLayout")
+ self.horizontalLayout.setContentsMargins(0, 0, 0, 0)
+ self.choose_proto_button = QPushButton(self.horizontalLayoutWidget)
+ self.choose_proto_button.setObjectName(u"choose_proto_button")
+
+ self.horizontalLayout.addWidget(self.choose_proto_button)
+
+ self.choose_json_button = QPushButton(self.horizontalLayoutWidget)
+ self.choose_json_button.setObjectName(u"choose_json_button")
+
+ self.horizontalLayout.addWidget(self.choose_json_button)
+
+
+ self.retranslateUi(ChooseTopicDialog)
+
+ QMetaObject.connectSlotsByName(ChooseTopicDialog)
+ # setupUi
+
+ def retranslateUi(self, ChooseTopicDialog):
+ ChooseTopicDialog.setWindowTitle(QCoreApplication.translate("ChooseTopicDialog", u"Choose message type", None))
+ self.choose_proto_button.setText(QCoreApplication.translate("ChooseTopicDialog", u"Protobuf", None))
+ self.choose_json_button.setText(QCoreApplication.translate("ChooseTopicDialog", u"JSON", None))
+ # retranslateUi
+
diff --git a/src/gui/ui.py b/src/gui/ui.py
index 31bf579..639f8a1 100644
--- a/src/gui/ui.py
+++ b/src/gui/ui.py
@@ -19,6 +19,9 @@
from logger import QListWidgetLogHandler
from os import listdir, path, getcwd
import icons.generated.icons
+from gui.generated.addprototopicdialog import Ui_AddProtoTopicDialog
+from gui.generated.choosetopicdialog import Ui_ChooseTopicDialog
+from mqttprotogenerator import MqttProtoGenerator
class MqttSimTopicToolButton(QToolButton):
@@ -186,6 +189,41 @@ def set_topic_name(self, new_topic_name: str) -> None:
self.topic_lbl.setText(new_topic_name)
+class MqttSimAddProtoTopicWindow(Ui_AddProtoTopicDialog, QDialog):
+ def __init__(self):
+ super(MqttSimAddProtoTopicWindow, self).__init__()
+ self.setupUi(self)
+ self.setWindowIcon(QIcon(":/icons/mqtt.svg"))
+
+
+class MqttSimEditProtoTopicWindow(MqttSimAddProtoTopicWindow, QDialog):
+ def __init__(self, topic_name: str, topic_data: dict):
+ super(MqttSimEditProtoTopicWindow, self).__init__()
+ self.__set_topic_values(topic_name, topic_data)
+ self.setWindowIcon(QIcon(":/icons/mqtt.svg"))
+
+ def __set_topic_values(self, topic_name, topic_data) -> None:
+ messages = MqttProtoGenerator.get_message_constructors()
+ current_item = None
+ for message_name in messages.keys():
+ new_item = QListWidgetItem(message_name)
+ self.message_list.addItem(new_item)
+ if message_name == topic_data["message"]:
+ current_item = new_item
+ self.message_list.setCurrentItem(current_item)
+ self.name_line_edit.setText(topic_name)
+ self.file_line_edit.setText(topic_data["file"])
+ self.interval_spin_box.setValue(topic_data.get("interval"))
+ self.manual_check_box.setChecked(topic_data.get("manual"))
+
+
+class MqttSimChooseTopicWindow(Ui_ChooseTopicDialog, QDialog):
+ def __init__(self):
+ super(MqttSimChooseTopicWindow, self).__init__()
+ self.setupUi(self)
+ self.setWindowIcon(QIcon(":/icons/mqtt.svg"))
+
+
class MqttSimMainWindow(Ui_MainWindow, QMainWindow):
def __init__(self, sim: MqttSim):
super(MqttSimMainWindow, self).__init__()
@@ -233,6 +271,18 @@ def on_clear_logs_btn_clicked() -> None:
self.__logger.info("Cleared logs.")
def on_add_topic_btn_clicked() -> None:
+ choose_topic_window = MqttSimChooseTopicWindow()
+ choose_topic_window.choose_json_button.clicked.connect(
+ choose_topic_window.close)
+ choose_topic_window.choose_json_button.clicked.connect(
+ on_add_json_btn_clicked)
+ choose_topic_window.choose_proto_button.clicked.connect(
+ choose_topic_window.close)
+ choose_topic_window.choose_proto_button.clicked.connect(
+ on_add_proto_btn_clicked)
+ choose_topic_window.exec()
+
+ def on_add_json_btn_clicked() -> None:
def validate_input(topic_config) -> bool:
return len(topic_config.get("topic")) > 0
@@ -254,6 +304,32 @@ def validate_input(topic_config) -> bool:
else:
break # Break out of the loop if dialog is cancelled
+ def on_add_proto_btn_clicked() -> None:
+ def validate_input(topic_name, topic_config) -> bool:
+ return (len(topic_name) > 0 and topic_name not in self.__config.get_topics().keys())
+
+ add_topic_window = MqttSimAddProtoTopicWindow()
+ messages = MqttProtoGenerator.get_message_constructors()
+ for message_name in messages.keys():
+ add_topic_window.message_list.addItem(message_name)
+ if add_topic_window.exec():
+ if add_topic_window.message_list.currentItem() is None:
+ QMessageBox().critical(self, "Error!", "Invalid topic input.")
+ return
+ topic_name = add_topic_window.name_line_edit.text()
+ topic_config = {
+ "topic": add_topic_window.name_line_edit.text(),
+ "message": add_topic_window.message_list.currentItem().text(),
+ "interval": add_topic_window.interval_spin_box.value(),
+ "manual": add_topic_window.manual_check_box.isChecked(),
+ "file": add_topic_window.file_line_edit.text()
+ }
+ if validate_input(topic_name, topic_config):
+ uuid = self.__sim.add_proto_topic(topic_config)
+ self.__add_topic_to_item_list(uuid)
+ else:
+ QMessageBox().critical(self, "Error!", "Invalid topic input.")
+
def on_broker_info_changed() -> None:
self.__sim.set_broker(
self.broker_hostname.text(), self.broker_port.value())
@@ -289,20 +365,37 @@ def on_remove_btn_clicked() -> None:
def on_edit_btn_clicked() -> None:
data = self.__config.get_topic_data(topic_uuid)
- edit_topic_window = MqttSimEditTopicWindow(data)
- if edit_topic_window.exec():
- edited_data = {
- "topic": edit_topic_window.name_line_edit.text(),
- "data_format": edit_topic_window.format_text_edit.toPlainText(),
- "interval": edit_topic_window.interval_spin_box.value(),
- "manual": edit_topic_window.manual_check_box.isChecked(),
- }
- if edited_data != data:
- topic_widget.set_topic_name(edited_data.get("topic"))
- self.__sim.edit(topic_uuid, edited_data)
- self.__logger.info(
- f'Edited topic {data.get("topic")} [uuid={topic_uuid}] ({data} -> {edited_data}).'
- )
+ if "data_format" in data.keys():
+ edit_topic_window = MqttSimEditTopicWindow(data)
+ if edit_topic_window.exec():
+ edited_data = {
+ "topic": edit_topic_window.name_line_edit.text(),
+ "data_format": edit_topic_window.format_text_edit.toPlainText(),
+ "interval": edit_topic_window.interval_spin_box.value(),
+ "manual": edit_topic_window.manual_check_box.isChecked(),
+ }
+ if edited_data != data:
+ topic_widget.set_topic_name(edited_data.get("topic"))
+ self.__sim.edit(topic_uuid, edited_data)
+ self.__logger.info(
+ f'Edited topic {data.get("topic")} [uuid={topic_uuid}] ({data} -> {edited_data}).'
+ )
+ else:
+ edit_topic_window = MqttSimEditProtoTopicWindow(
+ topic_name, data)
+ if edit_topic_window.exec():
+ edited_topic_data = {
+ "topic": edit_topic_window.name_line_edit.text(),
+ "message": edit_topic_window.message_list.currentItem().text(),
+ "interval": edit_topic_window.interval_spin_box.value(),
+ "manual": edit_topic_window.manual_check_box.isChecked(),
+ "file": edit_topic_window.file_line_edit.text()
+ }
+ if edited_topic_data != data:
+ self.__sim.edit(topic_uuid, edited_topic_data)
+ self.__logger.info(
+ f'Edited topic {data.get("topic")} [uuid={topic_uuid}] ({data} -> {edited_topic_data}).'
+ )
topic_widget.remove_btn.clicked.connect(on_remove_btn_clicked)
topic_widget.edit_btn.clicked.connect(on_edit_btn_clicked)
diff --git a/src/mqttprotogenerator.py b/src/mqttprotogenerator.py
index e1ed3d4..7c6feed 100644
--- a/src/mqttprotogenerator.py
+++ b/src/mqttprotogenerator.py
@@ -6,6 +6,7 @@
import string
import pandas as pd
import sys
+import os
class MqttProtoGenerator():
@@ -15,7 +16,17 @@ def __init__(self, message_name: string, message_file_path=''):
self.message_constructors = MqttProtoGenerator.get_message_constructors()
self.message_constructor = self.message_constructors[message_name]
self.constructed_messages = None
+ self.message_name = message_name
if message_file_path != '':
+ try:
+ self.file_time_stamp = os.stat(message_file_path).st_mtime
+ except FileNotFoundError:
+ MqttProtoGenerator.logger.error(
+ f"ERROR: File {message_file_path} not found. Defaulting to sending random messages")
+ self.message_file_path = ''
+ return
+
+ self.message_file_path = message_file_path
self.constructed_messages = self.read_message_csv(
message_name, message_file_path)
self.curr_message = 0
@@ -66,6 +77,15 @@ def get_random_message(self):
def get_next_message(self):
ret = None
if self.constructed_messages is not None:
+ time_stamp = os.stat(self.message_file_path).st_mtime
+ if time_stamp != self.file_time_stamp:
+ MqttProtoGenerator.logger.info(
+ f"INFO: file {self.message_file_path} changed. Reconstructing messages")
+ self.constructed_messages = self.read_message_csv(
+ self.message_name, message_file=self.message_file_path)
+ self.file_time_stamp = time_stamp
+ self.curr_message = 0
+
ret = self.constructed_messages[self.curr_message]
self.curr_message = (self.curr_message +
1) % len(self.constructed_messages)
@@ -102,8 +122,17 @@ def construct_message(self, message_name: str, fields: dict) -> Message:
if message_type.name not in field:
continue
sub_fields = field.split('.')
- new_field_name = sub_fields[sub_fields.index(
- message_type.name)+1:]
+ try:
+ new_field_name = sub_fields[sub_fields.index(
+ message_type.name)+1:]
+ except ValueError:
+ if ' ' in field:
+ MqttProtoGenerator.logger.error(
+ f"ERROR: Field {field} contains spaces!!! You should not do that!")
+ else:
+ MqttProtoGenerator.logger.error(
+ f"ERROR: Field with key \'{field}\' not found in message \'{message_name}\'.")
+ return new_message
passed_on_fields['.'.join(
new_field_name)] = fields[field]
m = self.construct_message(message_type.name, passed_on_fields)
@@ -112,8 +141,8 @@ def construct_message(self, message_name: str, fields: dict) -> Message:
continue
field_type = field_descriptor.type
if field_descriptor.name not in fields.keys():
- MqttProtoGenerator.logger.error(
- f"ERROR: data for field \'{field_descriptor.name}\' not found when constructing message \'{message_name}\'.")
+ MqttProtoGenerator.logger.info(
+ f"INFO: data for field \'{field_descriptor.name}\' not found when constructing message \'{message_name}\'.")
continue
# bolean
if field_type == 8:
diff --git a/src/mqttsim.py b/src/mqttsim.py
index 4e8c2a1..b448912 100644
--- a/src/mqttsim.py
+++ b/src/mqttsim.py
@@ -100,18 +100,13 @@ def time_diff_in_seconds(time1, time2) -> int:
def __setup_client(self) -> None:
def on_message(client, userdata, message) -> None:
- if message.topic in self.__topic_data_generators:
- self.__logger.info(
- f"Received message from broker {message.topic}: '{message.payload}'.")
- elif message.topic in self.__proto_topic_data_generators:
- message_constructor = self.__proto_topic_data_generators[
- message.topic].message_constructor
- proto_message = message_constructor()
- proto_message.ParseFromString(message.payload)
- self.__logger.info(
- f"Received message from broker on topic {message.topic}: '{proto_message}'.")
+ self.__logger.info(
+ f"Received message from broker {message.topic}.")
def on_connect(client, userdata, flags, rc) -> None:
+ topics = self.__config.get_topics()
+ for topic_uuid in topics:
+ self.__client.subscribe(topics[topic_uuid]["topic"])
if rc == CONNACK_ACCEPTED:
self.__logger.info("Connected to broker.")
else:
@@ -216,8 +211,8 @@ def send_single_message(self, topic_uuid) -> None:
self.__logger.error(
"Trying to send message when not connected to broker.")
return
+ topic_data = self.__config.get_topic_data(topic_uuid)
if topic_uuid in self.__topic_data_generators:
- topic_data = self.__config.get_topic_data(topic_uuid)
self.__logger.info(
f'Publishing data on {topic_data.get("topic")} [uuid={topic_uuid}]...')
message = self.__topic_data_generators.get(
@@ -230,3 +225,12 @@ def send_single_message(self, topic_uuid) -> None:
proto_message = gen.get_next_message()
self.__client.publish(
topic_data.get("topic"), proto_message.SerializeToString())
+
+ def add_proto_topic(self, topic_config: dict) -> None:
+ uuid = self.__config.put_topic(topic_config)
+ self.__logger.info(
+ f'Added topic: {topic_config.get("topic")} [uuid={uuid}].')
+ self.__proto_topic_data_generators[uuid] = MqttProtoGenerator(
+ topic_config.get("message"), topic_config.get("file"))
+ self.__client.subscribe(topic_config.get("topic"))
+ return uuid
diff --git a/src/requirements.txt b/src/requirements.txt
index 2108776..bc00439 100644
--- a/src/requirements.txt
+++ b/src/requirements.txt
@@ -1,5 +1,12 @@
+numpy==1.26.4
paho-mqtt==2.1.0
+pandas==2.2.2
+protobuf==5.27.0
PySide6==6.7.0
PySide6_Addons==6.7.0
PySide6_Essentials==6.7.0
+python-dateutil==2.9.0.post0
+pytz==2024.1
shiboken6==6.7.0
+six==1.16.0
+tzdata==2024.1
From 8891791dc4df7d7238ca597609faee24663043de Mon Sep 17 00:00:00 2001
From: Maciej Janicki
Date: Thu, 30 May 2024 20:40:04 +0200
Subject: [PATCH 5/7] Fix: formatting.
---
readme.md | 29 +++++++++++++++--------------
src/gui/ui.py | 24 ++++++++----------------
src/mqttprotogenerator.py | 23 +++++++++++------------
src/mqttsim.py | 30 ++++++++++--------------------
src/protofiles/__init__.py | 11 +++++------
5 files changed, 49 insertions(+), 68 deletions(-)
diff --git a/readme.md b/readme.md
index 6980944..07055fb 100644
--- a/readme.md
+++ b/readme.md
@@ -112,20 +112,6 @@ You can always create ```config.json``` file yourself if you think writing it wi
```
> result - app publishes random int value on 'topic1' every second and random uint value on 'test/topic2'
-## Screenshots
-Keep in mind that the look of the app is dependent on user's system - QT uses native components.
-
-
-
-Main window
-
-
-
-
-
-Add topic window
-
-
# Protobuf
To use protobuf place compiled message files in src/protofiles.
@@ -154,3 +140,18 @@ my_int,Message2.my_int
456,1312
123123,45123
```
+
+## Screenshots
+Keep in mind that the look of the app is dependent on user's system - QT uses native components.
+
+
+
+Main window
+
+
+
+
+
+Add topic window
+
+
diff --git a/src/gui/ui.py b/src/gui/ui.py
index 639f8a1..faf7427 100644
--- a/src/gui/ui.py
+++ b/src/gui/ui.py
@@ -29,8 +29,7 @@ def __init__(self, icon: str, tooltip: str):
super(MqttSimTopicToolButton, self).__init__()
self.setCursor(Qt.CursorShape.PointingHandCursor)
self.setIcon(QIcon(icon))
- self.setToolTip(QCoreApplication.translate(
- "MainWindow", tooltip, None))
+ self.setToolTip(QCoreApplication.translate("MainWindow", tooltip, None))
class MqttSimAddTopicWindow(Ui_AddTopicDialog, QDialog):
@@ -38,8 +37,7 @@ def __init__(self):
super(MqttSimAddTopicWindow, self).__init__()
self.setupUi(self)
self.setWindowIcon(QIcon(":/icons/mqtt.svg"))
- self.load_from_file_btn.clicked.connect(
- self.__on_load_from_file_btn_clicked)
+ self.load_from_file_btn.clicked.connect(self.__on_load_from_file_btn_clicked)
self.__load_patterns()
self.predefined_pattern_combo_box.currentIndexChanged.connect(
self.__on_pattern_selected
@@ -95,8 +93,7 @@ def __load_patterns(self):
path.basename(pattern_filename).replace("_", " ")
)[0]
pattern_value = file.read()
- self.predefined_pattern_combo_box.addItem(
- pattern_name, pattern_value)
+ self.predefined_pattern_combo_box.addItem(pattern_name, pattern_value)
def __on_save_as_pattern_btn_clicked(self):
pattern_name, accepted = QInputDialog.getText(
@@ -171,8 +168,7 @@ def __init__(self, topic_name: str):
# edit btn
self.edit_btn = MqttSimTopicToolButton(
- ":/icons/edit.svg", QCoreApplication.translate(
- "MainWindow", "Edit", None)
+ ":/icons/edit.svg", QCoreApplication.translate("MainWindow", "Edit", None)
)
self.edit_btn.setCursor(Qt.CursorShape.PointingHandCursor)
hlayout.addWidget(self.edit_btn)
@@ -251,8 +247,7 @@ def on_broker_connect_btn_clicked() -> None:
QCoreApplication.translate("MainWindow", "Connect", None)
)
self.broker_connect_btn.setToolTip(
- QCoreApplication.translate(
- "MainWindow", "Connect to broker", None)
+ QCoreApplication.translate("MainWindow", "Connect to broker", None)
)
else:
if self.__sim.connect_to_broker():
@@ -262,8 +257,7 @@ def on_broker_connect_btn_clicked() -> None:
)
self.broker_connect_btn.setToolTip(
QCoreApplication.translate(
- "MainWindow", "Disconnect from broker", None
- )
+ "MainWindow", "Disconnect from broker", None)
)
def on_clear_logs_btn_clicked() -> None:
@@ -331,8 +325,7 @@ def validate_input(topic_name, topic_config) -> bool:
QMessageBox().critical(self, "Error!", "Invalid topic input.")
def on_broker_info_changed() -> None:
- self.__sim.set_broker(
- self.broker_hostname.text(), self.broker_port.value())
+ self.__sim.set_broker(self.broker_hostname.text(), self.broker_port.value())
def on_topic_search_text_changed() -> None:
for widget in self.topics_list_widget.findChildren(MqttSimTopicWidget):
@@ -346,8 +339,7 @@ def on_topic_search_text_changed() -> None:
self.add_topic_btn.clicked.connect(on_add_topic_btn_clicked)
self.broker_hostname.textChanged.connect(on_broker_info_changed)
self.broker_port.valueChanged.connect(on_broker_info_changed)
- self.topic_search_line_edit.textChanged.connect(
- on_topic_search_text_changed)
+ self.topic_search_line_edit.textChanged.connect(on_topic_search_text_changed)
def __add_topic_to_item_list(self, topic_uuid: str) -> None:
topic_name = self.__config.get_topic_data(topic_uuid).get("topic")
diff --git a/src/mqttprotogenerator.py b/src/mqttprotogenerator.py
index 7c6feed..7e99c14 100644
--- a/src/mqttprotogenerator.py
+++ b/src/mqttprotogenerator.py
@@ -2,24 +2,23 @@
import protofiles
import importlib
from google.protobuf.message import Message
-import random
-import string
+from random import getrandbits, randint, random, choice, randbytes
+from string import ascii_lowercase
import pandas as pd
-import sys
-import os
+from os import stat
class MqttProtoGenerator():
logger = None
- def __init__(self, message_name: string, message_file_path=''):
+ def __init__(self, message_name: str, message_file_path=''):
self.message_constructors = MqttProtoGenerator.get_message_constructors()
self.message_constructor = self.message_constructors[message_name]
self.constructed_messages = None
self.message_name = message_name
if message_file_path != '':
try:
- self.file_time_stamp = os.stat(message_file_path).st_mtime
+ self.file_time_stamp = stat(message_file_path).st_mtime
except FileNotFoundError:
MqttProtoGenerator.logger.error(
f"ERROR: File {message_file_path} not found. Defaulting to sending random messages")
@@ -57,27 +56,27 @@ def get_random_message(self):
field_type = field_descriptor.type
# boolean
if field_type == 8:
- setattr(message, field_descriptor.name, random.getrandbits(1))
+ setattr(message, field_descriptor.name, getrandbits(1))
# string
elif field_type == 9:
setattr(message, field_descriptor.name, ''.join(
- random.choice(string.ascii_lowercase) for i in range(10)))
+ choice(ascii_lowercase) for i in range(10)))
# float or double
elif field_type == 2 or field_type == 1:
- setattr(message, field_descriptor.name, random.random())
+ setattr(message, field_descriptor.name, random())
# bytes
elif field_type == 12:
- setattr(message, field_descriptor.name, random.randbytes(10))
+ setattr(message, field_descriptor.name, randbytes(10))
# int
else:
setattr(message, field_descriptor.name,
- random.randint(0, 1000))
+ randint(0, 1000))
return message
def get_next_message(self):
ret = None
if self.constructed_messages is not None:
- time_stamp = os.stat(self.message_file_path).st_mtime
+ time_stamp = stat(self.message_file_path).st_mtime
if time_stamp != self.file_time_stamp:
MqttProtoGenerator.logger.info(
f"INFO: file {self.message_file_path} changed. Reconstructing messages")
diff --git a/src/mqttsim.py b/src/mqttsim.py
index b448912..3c99348 100644
--- a/src/mqttsim.py
+++ b/src/mqttsim.py
@@ -79,8 +79,7 @@ def time_diff_in_seconds(time1, time2) -> int:
return diff_dt.total_seconds()
topics_data = self.__config.get_topics()
- last_sent = {topic_uuid: datetime.now()
- for topic_uuid in topics_data.keys()}
+ last_sent = {topic_uuid: datetime.now() for topic_uuid in topics_data.keys()}
while not self.__should_stop_publishing_thread:
if not self.is_connected_to_broker():
@@ -100,8 +99,7 @@ def time_diff_in_seconds(time1, time2) -> int:
def __setup_client(self) -> None:
def on_message(client, userdata, message) -> None:
- self.__logger.info(
- f"Received message from broker {message.topic}.")
+ self.__logger.info(f"Received message from broker {message.topic}.")
def on_connect(client, userdata, flags, rc) -> None:
topics = self.__config.get_topics()
@@ -110,8 +108,7 @@ def on_connect(client, userdata, flags, rc) -> None:
if rc == CONNACK_ACCEPTED:
self.__logger.info("Connected to broker.")
else:
- self.__logger.error(
- f"Error when connecting to broker (rc={rc}).")
+ self.__logger.error(f"Error when connecting to broker (rc={rc}).")
def on_disconnect(client, userdata, rc) -> None:
if rc == 0:
@@ -171,20 +168,17 @@ def remove_topic(self, topic_uuid: str) -> None:
self.__client.unsubscribe(topic_data.get("topic"))
self.__config.remove_topic(topic_uuid)
if topic_uuid in self.__topic_data_generators:
- self.__logger.info(
- f'Removed topic: {topic_data.get("topic")} [uuid={topic_uuid}].')
+ self.__logger.info(f'Removed topic: {topic_data.get("topic")} [uuid={topic_uuid}].')
del self.__topic_data_generators[topic_uuid]
else:
- self.__logger.info(
- f'Removed topic: {topic_data.get("topic")} [uuid={topic_uuid}].')
+ self.__logger.info(f'Removed topic: {topic_data.get("topic")} [uuid={topic_uuid}].')
del self.__proto_topic_data_generators[topic_uuid]
# Adds topic to config (and saves it into config file).
# If publishing thread was already started, it will take the topic into account.
def add_topic(self, topic_config: dict) -> str:
uuid = self.__config.put_topic(topic_config)
- self.__logger.info(
- f'Added topic: {topic_config.get("topic")} [uuid={uuid}].')
+ self.__logger.info(f'Added topic: {topic_config.get("topic")} [uuid={uuid}].')
self.__topic_data_generators[uuid] = MqttSimDataGenerator(
topic_config.get("data_format")
)
@@ -208,19 +202,16 @@ def edit(self, topic_uuid, new_data) -> None:
def send_single_message(self, topic_uuid) -> None:
if not self.is_connected_to_broker():
- self.__logger.error(
- "Trying to send message when not connected to broker.")
+ self.__logger.error("Trying to send message when not connected to broker.")
return
topic_data = self.__config.get_topic_data(topic_uuid)
if topic_uuid in self.__topic_data_generators:
- self.__logger.info(
- f'Publishing data on {topic_data.get("topic")} [uuid={topic_uuid}]...')
+ self.__logger.info(f'Publishing data on {topic_data.get("topic")} [uuid={topic_uuid}]...')
message = self.__topic_data_generators.get(
topic_uuid).next_message()
self.__client.publish(topic_data.get("topic"), message)
else:
- self.__logger.info(
- f'Publishing data on {topic_data.get("topic")} [uuid={topic_uuid}]...')
+ self.__logger.info(f'Publishing data on {topic_data.get("topic")} [uuid={topic_uuid}]...')
gen = self.__proto_topic_data_generators[topic_uuid]
proto_message = gen.get_next_message()
self.__client.publish(
@@ -228,8 +219,7 @@ def send_single_message(self, topic_uuid) -> None:
def add_proto_topic(self, topic_config: dict) -> None:
uuid = self.__config.put_topic(topic_config)
- self.__logger.info(
- f'Added topic: {topic_config.get("topic")} [uuid={uuid}].')
+ self.__logger.info(f'Added topic: {topic_config.get("topic")} [uuid={uuid}].')
self.__proto_topic_data_generators[uuid] = MqttProtoGenerator(
topic_config.get("message"), topic_config.get("file"))
self.__client.subscribe(topic_config.get("topic"))
diff --git a/src/protofiles/__init__.py b/src/protofiles/__init__.py
index 7f2f0fe..2def8af 100644
--- a/src/protofiles/__init__.py
+++ b/src/protofiles/__init__.py
@@ -1,8 +1,7 @@
-from os.path import dirname, basename, isfile, join
-import os
-import sys
-import glob
-modules = glob.glob(join(dirname(__file__), "*.py"))
+from os.path import dirname, basename, isfile, join, abspath, dirname
+from sys import path
+from glob import glob
+modules = glob(join(dirname(__file__), "*.py"))
__all__ = [basename(f)[:-3] for f in modules if isfile(f)
and not f.endswith('__init__.py')]
-sys.path.insert(0, os.path.abspath(os.path.dirname(__file__)))
+path.insert(0, abspath(dirname(__file__)))
From ffb2f0374c8f2b1cb99aa865c00948a25d6b6c5b Mon Sep 17 00:00:00 2001
From: Maciej Janicki
Date: Sun, 9 Jun 2024 17:16:48 +0200
Subject: [PATCH 6/7] Update: changed how csv columns should be named
---
readme.md | 2 +-
src/mqttprotogenerator.py | 7 +++----
2 files changed, 4 insertions(+), 5 deletions(-)
diff --git a/readme.md b/readme.md
index 07055fb..5868a63 100644
--- a/readme.md
+++ b/readme.md
@@ -135,7 +135,7 @@ message Message2 {
example csv file for Message1 would look like this:
```csv
-my_int,Message2.my_int
+my_int,nested_message.my_int
123,456
456,1312
123123,45123
diff --git a/src/mqttprotogenerator.py b/src/mqttprotogenerator.py
index 7e99c14..5532015 100644
--- a/src/mqttprotogenerator.py
+++ b/src/mqttprotogenerator.py
@@ -113,24 +113,23 @@ def construct_message(self, message_name: str, fields: dict) -> Message:
message_constructor = self.message_constructors[message_name]
new_message = message_constructor()
for field_descriptor in message_constructor.DESCRIPTOR.fields:
-
message_type = field_descriptor.message_type
if field_descriptor.message_type is not None:
passed_on_fields = dict()
for field in fields.keys():
- if message_type.name not in field:
+ if field_descriptor.name not in field:
continue
sub_fields = field.split('.')
try:
new_field_name = sub_fields[sub_fields.index(
- message_type.name)+1:]
+ field_descriptor.name)+1:]
except ValueError:
if ' ' in field:
MqttProtoGenerator.logger.error(
f"ERROR: Field {field} contains spaces!!! You should not do that!")
else:
MqttProtoGenerator.logger.error(
- f"ERROR: Field with key \'{field}\' not found in message \'{message_name}\'.")
+ f"ERROR: Field with key \'{field}\' not found \'{field_descriptor.name}\'.")
return new_message
passed_on_fields['.'.join(
new_field_name)] = fields[field]
From 4fb204272145b3b40018ab91817f0cfe06fb3993 Mon Sep 17 00:00:00 2001
From: Maciej Janicki
Date: Mon, 10 Jun 2024 20:09:32 +0200
Subject: [PATCH 7/7] Feature: abstract layer between data generation and
mqttsimulator itself
---
src/abstractdatagenerator.py | 6 ++
src/arbiter.py | 11 ++++
src/gui/ui.py | 15 +++--
...mdatagenerator.py => jsondatagenerator.py} | 8 ++-
src/mqttsim.py | 65 +++++--------------
...qttprotogenerator.py => protogenerator.py} | 32 ++++-----
6 files changed, 64 insertions(+), 73 deletions(-)
create mode 100644 src/abstractdatagenerator.py
create mode 100644 src/arbiter.py
rename src/{mqttsimdatagenerator.py => jsondatagenerator.py} (98%)
rename src/{mqttprotogenerator.py => protogenerator.py} (85%)
diff --git a/src/abstractdatagenerator.py b/src/abstractdatagenerator.py
new file mode 100644
index 0000000..87a7e87
--- /dev/null
+++ b/src/abstractdatagenerator.py
@@ -0,0 +1,6 @@
+class DataGenerator():
+ def __init__(self, config):
+ pass
+
+ def next_message(self) -> str:
+ pass
diff --git a/src/arbiter.py b/src/arbiter.py
new file mode 100644
index 0000000..1e0b0c2
--- /dev/null
+++ b/src/arbiter.py
@@ -0,0 +1,11 @@
+from abstractdatagenerator import DataGenerator
+from jsondatagenerator import JsonDataGenerator
+from protogenerator import ProtoDataGenerator
+
+def get_data_generator(config) -> DataGenerator:
+ if "data_format" in config:
+ return JsonDataGenerator(config)
+ elif "message" in config:
+ return ProtoDataGenerator(config)
+
+
diff --git a/src/gui/ui.py b/src/gui/ui.py
index faf7427..084c4ed 100644
--- a/src/gui/ui.py
+++ b/src/gui/ui.py
@@ -21,7 +21,7 @@
import icons.generated.icons
from gui.generated.addprototopicdialog import Ui_AddProtoTopicDialog
from gui.generated.choosetopicdialog import Ui_ChooseTopicDialog
-from mqttprotogenerator import MqttProtoGenerator
+from protogenerator import ProtoDataGenerator
class MqttSimTopicToolButton(QToolButton):
@@ -199,9 +199,10 @@ def __init__(self, topic_name: str, topic_data: dict):
self.setWindowIcon(QIcon(":/icons/mqtt.svg"))
def __set_topic_values(self, topic_name, topic_data) -> None:
- messages = MqttProtoGenerator.get_message_constructors()
+ messages = ProtoDataGenerator.get_message_constructors()
current_item = None
- for message_name in messages.keys():
+ message_names = sorted(list(messages.keys()), key=lambda s: (not 'Msg' in s, s))
+ for message_name in message_names:
new_item = QListWidgetItem(message_name)
self.message_list.addItem(new_item)
if message_name == topic_data["message"]:
@@ -303,8 +304,10 @@ def validate_input(topic_name, topic_config) -> bool:
return (len(topic_name) > 0 and topic_name not in self.__config.get_topics().keys())
add_topic_window = MqttSimAddProtoTopicWindow()
- messages = MqttProtoGenerator.get_message_constructors()
- for message_name in messages.keys():
+ messages = ProtoDataGenerator.get_message_constructors()
+
+ message_names = sorted(list(messages.keys()), key=lambda s: (not 'Msg' in s, s))
+ for message_name in message_names:
add_topic_window.message_list.addItem(message_name)
if add_topic_window.exec():
if add_topic_window.message_list.currentItem() is None:
@@ -319,7 +322,7 @@ def validate_input(topic_name, topic_config) -> bool:
"file": add_topic_window.file_line_edit.text()
}
if validate_input(topic_name, topic_config):
- uuid = self.__sim.add_proto_topic(topic_config)
+ uuid = self.__sim.add_topic(topic_config)
self.__add_topic_to_item_list(uuid)
else:
QMessageBox().critical(self, "Error!", "Invalid topic input.")
diff --git a/src/mqttsimdatagenerator.py b/src/jsondatagenerator.py
similarity index 98%
rename from src/mqttsimdatagenerator.py
rename to src/jsondatagenerator.py
index 590619b..524ef68 100644
--- a/src/mqttsimdatagenerator.py
+++ b/src/jsondatagenerator.py
@@ -5,10 +5,12 @@
from functools import partial
from datetime import datetime
from copy import copy
+from abstractdatagenerator import DataGenerator
-class MqttSimDataGenerator:
- def __init__(self, data_format: str):
- self.reinitalize(data_format)
+
+class JsonDataGenerator(DataGenerator):
+ def __init__(self, config: dict):
+ self.reinitalize(config.get("data_format"))
def next_message(self):
message = self.__format_str
diff --git a/src/mqttsim.py b/src/mqttsim.py
index 3c99348..fea2620 100644
--- a/src/mqttsim.py
+++ b/src/mqttsim.py
@@ -3,10 +3,10 @@
from threading import Thread
from datetime import datetime
from time import sleep
-from mqttsimdatagenerator import MqttSimDataGenerator
+from jsondatagenerator import JsonDataGenerator
+from protogenerator import ProtoDataGenerator
from uuid import uuid4
-from mqttprotogenerator import MqttProtoGenerator
-
+from arbiter import get_data_generator
class MqttSimConfig:
def __init__(self, path: str):
@@ -54,18 +54,10 @@ class MqttSim:
def __init__(self, config: MqttSimConfig, logger: any):
self.__logger = logger
self.__config = config
- self.__topic_data_generators = {
- topic_uuid: MqttSimDataGenerator(topic_config.get("data_format"))
- for topic_uuid, topic_config in self.__config.get_topics().items()
- if "data_format" in topic_config
- }
- MqttProtoGenerator.logger = logger
- self.__proto_topic_data_generators = {
- topic_uuid: MqttProtoGenerator(
- topic_config.get("message"), topic_config.get("file"))
- for topic_uuid, topic_config in self.__config.get_topics().items()
- if "message" in topic_config
- }
+ self.__topic_data_generators = dict()
+ ProtoDataGenerator.logger = logger
+ for topic_uuid, topic_config in self.__config.get_topics().items():
+ self.__topic_data_generators[topic_uuid] = get_data_generator(topic_config)
self.__setup_client()
self.__setup_publishing_thread()
@@ -99,7 +91,10 @@ def time_diff_in_seconds(time1, time2) -> int:
def __setup_client(self) -> None:
def on_message(client, userdata, message) -> None:
- self.__logger.info(f"Received message from broker {message.topic}.")
+ message_contents = str(message.payload)
+ message_contents = message_contents.replace('\n', ' ')
+ message_contents = message_contents[:3] + '[...]' + message_contents[-3:]
+ self.__logger.info(f"Received message from broker {message.topic}: {message_contents}.")
def on_connect(client, userdata, flags, rc) -> None:
topics = self.__config.get_topics()
@@ -170,18 +165,13 @@ def remove_topic(self, topic_uuid: str) -> None:
if topic_uuid in self.__topic_data_generators:
self.__logger.info(f'Removed topic: {topic_data.get("topic")} [uuid={topic_uuid}].')
del self.__topic_data_generators[topic_uuid]
- else:
- self.__logger.info(f'Removed topic: {topic_data.get("topic")} [uuid={topic_uuid}].')
- del self.__proto_topic_data_generators[topic_uuid]
# Adds topic to config (and saves it into config file).
# If publishing thread was already started, it will take the topic into account.
def add_topic(self, topic_config: dict) -> str:
uuid = self.__config.put_topic(topic_config)
self.__logger.info(f'Added topic: {topic_config.get("topic")} [uuid={uuid}].')
- self.__topic_data_generators[uuid] = MqttSimDataGenerator(
- topic_config.get("data_format")
- )
+ self.__topic_data_generators[uuid] = get_data_generator(topic_config)
return uuid
def get_logger(self) -> any:
@@ -193,34 +183,13 @@ def get_config(self) -> MqttSimConfig:
def edit(self, topic_uuid, new_data) -> None:
self.__config.put_topic(new_data, uuid=topic_uuid)
if topic_uuid in self.__topic_data_generators:
- self.__topic_data_generators[topic_uuid].reinitalize(
- new_data.get("data_format")
- )
- else:
- self.__proto_topic_data_generators[topic_uuid] = MqttProtoGenerator(
- new_data.get("message"), new_data.get("file"))
-
+ self.__topic_data_generators[topic_uuid] = get_data_generator(new_data)
def send_single_message(self, topic_uuid) -> None:
if not self.is_connected_to_broker():
self.__logger.error("Trying to send message when not connected to broker.")
return
topic_data = self.__config.get_topic_data(topic_uuid)
- if topic_uuid in self.__topic_data_generators:
- self.__logger.info(f'Publishing data on {topic_data.get("topic")} [uuid={topic_uuid}]...')
- message = self.__topic_data_generators.get(
- topic_uuid).next_message()
- self.__client.publish(topic_data.get("topic"), message)
- else:
- self.__logger.info(f'Publishing data on {topic_data.get("topic")} [uuid={topic_uuid}]...')
- gen = self.__proto_topic_data_generators[topic_uuid]
- proto_message = gen.get_next_message()
- self.__client.publish(
- topic_data.get("topic"), proto_message.SerializeToString())
-
- def add_proto_topic(self, topic_config: dict) -> None:
- uuid = self.__config.put_topic(topic_config)
- self.__logger.info(f'Added topic: {topic_config.get("topic")} [uuid={uuid}].')
- self.__proto_topic_data_generators[uuid] = MqttProtoGenerator(
- topic_config.get("message"), topic_config.get("file"))
- self.__client.subscribe(topic_config.get("topic"))
- return uuid
+ self.__logger.info(f'Publishing data on {topic_data.get("topic")} [uuid={topic_uuid}]...')
+ message = self.__topic_data_generators.get(
+ topic_uuid).next_message()
+ self.__client.publish(topic_data.get("topic"), message)
diff --git a/src/mqttprotogenerator.py b/src/protogenerator.py
similarity index 85%
rename from src/mqttprotogenerator.py
rename to src/protogenerator.py
index 5532015..5f67757 100644
--- a/src/mqttprotogenerator.py
+++ b/src/protogenerator.py
@@ -6,13 +6,17 @@
from string import ascii_lowercase
import pandas as pd
from os import stat
+from abstractdatagenerator import DataGenerator
-class MqttProtoGenerator():
+class ProtoDataGenerator(DataGenerator):
logger = None
- def __init__(self, message_name: str, message_file_path=''):
- self.message_constructors = MqttProtoGenerator.get_message_constructors()
+ def __init__(self, config):
+ self.reinitialize(config.get("message"), config.get("file"))
+
+ def reinitialize(self, message_name: str, message_file_path=''):
+ self.message_constructors = ProtoDataGenerator.get_message_constructors()
self.message_constructor = self.message_constructors[message_name]
self.constructed_messages = None
self.message_name = message_name
@@ -20,8 +24,7 @@ def __init__(self, message_name: str, message_file_path=''):
try:
self.file_time_stamp = stat(message_file_path).st_mtime
except FileNotFoundError:
- MqttProtoGenerator.logger.error(
- f"ERROR: File {message_file_path} not found. Defaulting to sending random messages")
+ ProtoDataGenerator.logger.error(f"ERROR: File {message_file_path} not found. Defaulting to sending random messages")
self.message_file_path = ''
return
@@ -48,7 +51,7 @@ def get_random_message(self):
message_type = field_descriptor.message_type
if field_descriptor.message_type is not None:
- gen = MqttProtoGenerator(message_type.name)
+ gen = ProtoDataGenerator(message_type.name)
m = gen.get_random_message()
attr = getattr(message, field_descriptor.name)
attr.CopyFrom(m)
@@ -73,12 +76,12 @@ def get_random_message(self):
randint(0, 1000))
return message
- def get_next_message(self):
+ def next_message(self):
ret = None
if self.constructed_messages is not None:
time_stamp = stat(self.message_file_path).st_mtime
if time_stamp != self.file_time_stamp:
- MqttProtoGenerator.logger.info(
+ ProtoDataGenerator.logger.info(
f"INFO: file {self.message_file_path} changed. Reconstructing messages")
self.constructed_messages = self.read_message_csv(
self.message_name, message_file=self.message_file_path)
@@ -90,15 +93,14 @@ def get_next_message(self):
1) % len(self.constructed_messages)
else:
ret = self.get_random_message()
- return ret
+ return ret.SerializeToString()
def read_message_csv(self, message_name: str, message_file: str) -> list:
df = None
try:
df = pd.read_csv(message_file)
except FileNotFoundError:
- MqttProtoGenerator.logger.error(
- f"ERROR: File {message_file} not found. Defaulting to sending random messages")
+ ProtoDataGenerator.logger.error(f"ERROR: File {message_file} not found. Defaulting to sending random messages")
return
messages = []
@@ -125,10 +127,9 @@ def construct_message(self, message_name: str, fields: dict) -> Message:
field_descriptor.name)+1:]
except ValueError:
if ' ' in field:
- MqttProtoGenerator.logger.error(
- f"ERROR: Field {field} contains spaces!!! You should not do that!")
+ ProtoDataGenerator.logger.error(f"ERROR: Field {field} contains spaces!!! You should not do that!")
else:
- MqttProtoGenerator.logger.error(
+ ProtoDataGenerator.logger.error(
f"ERROR: Field with key \'{field}\' not found \'{field_descriptor.name}\'.")
return new_message
passed_on_fields['.'.join(
@@ -139,8 +140,7 @@ def construct_message(self, message_name: str, fields: dict) -> Message:
continue
field_type = field_descriptor.type
if field_descriptor.name not in fields.keys():
- MqttProtoGenerator.logger.info(
- f"INFO: data for field \'{field_descriptor.name}\' not found when constructing message \'{message_name}\'.")
+ ProtoDataGenerator.logger.info(f"INFO: data for field \'{field_descriptor.name}\' not found when constructing message \'{message_name}\'.")
continue
# bolean
if field_type == 8: