diff --git a/docker-compose.yml b/docker-compose.yml index d68544d643355c49b148a7b36cc8dc93ea6da7ec..8f0fc583c6acfada4dcd6fd706cee9225d87d5ac 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -41,7 +41,7 @@ services: image: eclipse-mosquitto ports: - '0.0.0.0:1883:1883' - - '0.0.0.0:9001:9001' + - '0.0.0.0:443:443' volumes: - ./mosquitto/config/mosquitto.conf:/mosquitto/config/mosquitto.conf:ro restart: always diff --git a/mosquitto/config/mosquitto.conf b/mosquitto/config/mosquitto.conf index f2c19455bb5803a740551be9205af05f2bbda726..bb96d2beea665d546664decc6eedd9c17053888c 100644 --- a/mosquitto/config/mosquitto.conf +++ b/mosquitto/config/mosquitto.conf @@ -1,6 +1,6 @@ allow_anonymous true listener 1883 -listener 9001 +listener 443 protocol websockets persistence true persistence_location /mosquitto/data/ \ No newline at end of file diff --git a/serial_to_mqtt_bridge/Dockerfile b/serial_to_mqtt_bridge/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..fc7eb347a86933d25e74e83997bcfdb5e5d1262d --- /dev/null +++ b/serial_to_mqtt_bridge/Dockerfile @@ -0,0 +1,8 @@ +FROM python:3.9 +# Or any preferred Python version. +WORKDIR /src +COPY serial_to_mqtt.py /src/serial_to_mqtt.py +COPY requirements.txt /src/requirements.txt +RUN pip install -r requirements.txt +CMD ["python3", "-u", "/src/serial_to_mqtt.py", "--serial_port", "sim", "--mqtt_broker", "ws://mosquitto", "--mqtt_port", "443"] +# Or enter the name of your unique directory and parameter set. diff --git a/serial_to_mqtt_bridge/_version.py b/serial_to_mqtt_bridge/_version.py new file mode 100644 index 0000000000000000000000000000000000000000..2bf0baf1d49242665e61b0ee63bdbf5f2354e901 --- /dev/null +++ b/serial_to_mqtt_bridge/_version.py @@ -0,0 +1,2 @@ +# placeholder will be overwritten by PyInstaller +version = "" \ No newline at end of file diff --git a/serial_to_mqtt_bridge/config.yaml b/serial_to_mqtt_bridge/config.yaml index e4edc643944d487e82095bc1980e10734649b3c5..a04955e2295e7526f0c2be03d9fc3b985491e77f 100644 --- a/serial_to_mqtt_bridge/config.yaml +++ b/serial_to_mqtt_bridge/config.yaml @@ -1,8 +1,7 @@ mqtt: - broker: "localhost" - port: 1883 + broker: "rocket.mwave.eti.pg.gda.pl" + port: 443 topic: "serial2MQTT" - serial: baud_rate: 115200 diff --git a/serial_to_mqtt_bridge/create_exe.bat b/serial_to_mqtt_bridge/create_exe.bat index 105ee7ffd842b374ee99e78f18523ba8c518bb1e..172b9d4439e16f7ee996acf2bfc3da2cc5035412 100644 --- a/serial_to_mqtt_bridge/create_exe.bat +++ b/serial_to_mqtt_bridge/create_exe.bat @@ -19,36 +19,47 @@ if errorlevel 1 ( pip install pyinstaller ) -:: Check if required packages are installed -pip show paho-mqtt >nul 2>&1 -if errorlevel 1 ( - echo Installing required packages... - pip install -r requirements.txt -) +:: Install all required packages +echo Installing required packages... +pip install -r requirements.txt + +:: Get git SHA +for /f %%i in ('git rev-parse --short HEAD') do set GIT_SHA=%%i +echo Building version: %GIT_SHA% -:: Generate spec files -echo Generating spec files... -python -m PyInstaller --onefile --name serial_to_mqtt serial_to_mqtt.py -python -m PyInstaller --onefile --name serial_to_mqtt_sim serial_to_mqtt_sim.py +:: Update _version.py with git SHA +echo Updating _version.py with git SHA... +python -c "with open('_version.py', 'w') as f: f.write('version = \"%GIT_SHA%\"')" -:: Compile programs -echo Compiling programs... +:: Generate spec file +echo Generating spec file... +if not exist favicon.ico ( + echo Warning: serial_to_mqtt.ico not found. Creating spec without custom icon. + python -m PyInstaller --name serial_to_mqtt --onefile --windowed serial_to_mqtt.py +) else ( + echo Using serial_to_mqtt.ico as application icon... + python -m PyInstaller --name serial_to_mqtt --onefile --windowed --icon=favicon.ico serial_to_mqtt.py +) + +:: Compile program +echo Compiling program... +set PYTHONPATH=%PYTHONPATH%;%CD% python -m PyInstaller serial_to_mqtt.spec -python -m PyInstaller serial_to_mqtt_sim.spec -:: Move exe files to main directory -echo Moving exe files to main directory... -move /Y dist\serial_to_mqtt.exe .\serial_to_mqtt.exe -move /Y dist\serial_to_mqtt_sim.exe .\serial_to_mqtt_sim.exe +:: Move exe file to main directory +echo Moving exe file to main directory... +move /Y dist\serial_to_mqtt.exe .\susGatewayApp.exe :: Clean temporary folders echo Cleaning temporary folders... rmdir /S /Q build rmdir /S /Q dist del /F /Q serial_to_mqtt.spec -del /F /Q serial_to_mqtt_sim.spec -echo Binaries created successfully: -echo - serial_to_mqtt.exe -echo - serial_to_mqtt_sim.exe -pause \ No newline at end of file +:: Restore default version in _version.py +echo Restoring default version in _version.py... +python -c "with open('_version.py', 'w') as f: f.write('# placeholder will be overwritten by PyInstaller\nversion = \"\"')" + +echo Binary created successfully: +echo - serial_to_mqtt.exe (version: %GIT_SHA%) +pause \ No newline at end of file diff --git a/serial_to_mqtt_bridge/favicon.ico b/serial_to_mqtt_bridge/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..7b4d580e3ef757a9c8ae67ccd2e3064d934bb90a Binary files /dev/null and b/serial_to_mqtt_bridge/favicon.ico differ diff --git a/serial_to_mqtt_bridge/requirements.txt b/serial_to_mqtt_bridge/requirements.txt index 808abc055cda42e56c1f4fa0d51f4c263f101294..0c05005dfe94a54783eb0853168a2f154a15b010 100644 --- a/serial_to_mqtt_bridge/requirements.txt +++ b/serial_to_mqtt_bridge/requirements.txt @@ -1,4 +1,4 @@ -paho-mqtt -pyserial -argparse -PyYAML \ No newline at end of file +paho-mqtt==1.6.1 +pyyaml==6.0.1 +pyserial==3.5 +pywin32==306 \ No newline at end of file diff --git a/serial_to_mqtt_bridge/serial_to_mqtt.py b/serial_to_mqtt_bridge/serial_to_mqtt.py index 06a7e4f62a957c32f44c2e1c9a07b78c85d931dc..7a839c269683dcc52c91a8d473d802ed9b82d22a 100755 --- a/serial_to_mqtt_bridge/serial_to_mqtt.py +++ b/serial_to_mqtt_bridge/serial_to_mqtt.py @@ -6,6 +6,14 @@ import argparse import yaml import os import serial.tools.list_ports +import ssl +import random +import tkinter as tk +from tkinter import ttk, messagebox, scrolledtext +import threading +import queue +import subprocess +import sys def list_serial_ports(): """ @@ -29,6 +37,15 @@ def list_serial_ports(): print("-" * 50) port_list.append(port.device) + # Add simulation option at the end + sim_index = len(ports) + 1 + print(f"{sim_index}. Port: sim") + print(" Description: Simulation mode") + print(" Manufacturer: Internal") + print(" Hardware ID: N/A") + print("-" * 50) + port_list.append("sim") + return port_list def select_serial_port(): @@ -79,93 +96,404 @@ def load_config(file_path="config.yaml"): return {} # MQTT connection initialization -client = mqtt.Client() - -def connect_mqtt(broker, port): - client.connect(broker, port, 60) - client.loop_start() +def create_mqtt_client(broker_url): + """ + Creates MQTT client with appropriate transport based on broker URL prefix + """ + if broker_url.startswith('mqtt://'): + print("Using MQTT protocol...") + return mqtt.Client(mqtt.CallbackAPIVersion.VERSION1) + elif broker_url.startswith('ws://'): + print("Using WebSocket protocol without TLS...") + return mqtt.Client(mqtt.CallbackAPIVersion.VERSION1, transport="websockets") + elif broker_url.startswith('wss://'): + print("Using WebSocket protocol with TLS...") + return mqtt.Client(mqtt.CallbackAPIVersion.VERSION1, transport="websockets") + else: + print("No protocol prefix found, using MQTT protocol...") + return mqtt.Client(mqtt.CallbackAPIVersion.VERSION1) -# Function to read lines from file cyclically -def cyclic_file_reader(file_path): - while True: - with open(file_path, 'r') as file: - for line in file: - yield line.strip() # Yield each line, stripped of whitespace +def connect_mqtt(broker, port, username=None, password=None): + """ + Connect to MQTT broker with optional authentication and TLS + """ + try: + print(f"Attempting to connect to MQTT broker {broker}:{port}...") + + # Create client with appropriate transport + global client + client = create_mqtt_client(broker) + + # Configure WebSocket if needed + if broker.startswith(('ws://', 'wss://')): + client.ws_set_options(path="/ws") + + # Configure TLS if using wss:// + if broker.startswith('wss://'): + print("Using TLS for secure connection...") + client.tls_set(tls_version=ssl.PROTOCOL_TLSv1_2) + client.tls_insecure_set(True) + else: + print("Using unencrypted connection...") + + # Authentication configuration only if username and password are provided + if username and password and username.strip() and password.strip(): + print("Using MQTT authentication...") + client.username_pw_set(username, password) + else: + print("Connecting without MQTT authentication...") + + # Enable logging + client.enable_logger() + + # Remove protocol prefix from broker address if present + broker_address = broker.split('://')[-1] + + # Connect with error handling + try: + client.connect(broker_address, port, 60) + client.loop_start() + print("Successfully connected to MQTT broker") + except Exception as e: + print(f"Failed to connect to MQTT broker: {str(e)}") + raise + + except Exception as e: + print(f"Error setting up MQTT client: {str(e)}") + raise -# Main function to handle serial data or send lines from file if no port is provided -def main(serial_port=None, baud_rate=115200, file_path="sim_data.txt", mqtt_broker="192.168.52.5", mqtt_port=1883, mqtt_topic="serial2MQTT", sim_interval=1): - connect_mqtt(mqtt_broker, mqtt_port) +def generate_sim_data(): + """ + Generates random simulation data similar to create_sim_data.py + """ + # Define the valid range for `char` + valid_chars = list(range(13, 25)) + [37, 38] # From 13 to 24 and 37, 38 - if serial_port.lower() != "sim": - # If serial port is provided, read from it - ser = serial.Serial(serial_port, baud_rate, timeout=1) - try: + # Generate random values for fields + rssi = random.randint(45, 92) # RSSI value between -92 and -45 + char = random.choice(valid_chars) # Randomly choose from valid char values + channel = random.randint(37, 39) # Channel value between 37 and 39 + tx_power = 8 # Fixed tx_power value + temp = int(random.randint(0, 4095)) # Temperature value between 0 and 1800 + pH = int(random.randint(0, 4095)) # pH value between 0 and 1800 + pHRaw = int(pH/8) # pH value between 0 and 1800 + battery = int(random.randint(0, 4095)) # pH value between 0 and 1800 + + # Format the line according to the InfluxDB requirements + return (f"dongle_t=c000d0009,beacon_t=c000beac0004 " + f"rssi={rssi},char={char},channel={channel},tx_power={tx_power}," + f"temp={temp},pH={pH},pHRaw={pHRaw},battery={battery},timestamp=") + +def cyclic_data_generator(file_path): + """ + Generator that either reads data from file cyclically or generates random data + """ + try: + if os.path.exists(file_path): + print(f"Reading simulation data from file: {file_path}") while True: - # Read data from the serial port - if ser.in_waiting > 0: - line = ser.readline().decode('utf-8').strip() - print(f"Received from serial port: {line}") - print(f"Sent to MQTT broker={mqtt_broker} in topic={mqtt_topic} msg={line}") - # Send data to MQTT - client.publish(mqtt_topic, line) - time.sleep(0.1) # Short delay - - except KeyboardInterrupt: - print("Program terminated.") - finally: - # Close serial connection - ser.close() - client.loop_stop() - client.disconnect() - else: - # If simulation mode is selected, send lines from the file cyclically - file_reader = cyclic_file_reader(file_path) - cnt=0 - try: + with open(file_path, 'r') as file: + for line in file: + line = line.strip() + if line: # Skip empty lines + yield line + else: + print(f"Warning: File '{file_path}' not found. Generating random simulation data...") while True: - # Get the next line from the file - line = next(file_reader) - cnt = cnt +1 - msg=mqtt_topic+","+line+str(cnt) - client.publish(msg) - print(f"Sent to MQTT broker={mqtt_broker} in topic={mqtt_topic} msg={msg}") - time.sleep(sim_interval) # Use interval from YAML config + yield generate_sim_data() + except Exception as e: + print(f"Error in data generator: {str(e)}") + raise + +def get_version(): + """Get the version from the executable file info""" + try: + if getattr(sys, 'frozen', False): + # JeĹli jesteĹmy w skompilowanym exe + from _version import version + return version + else: + # W trybie deweloperskim + return "dev" + except Exception as e: + # Zwracamy bĹÄ d razem z wersjÄ + return f"N/A (Error: {str(e)})" + +class SerialToMQTTGUI: + def __init__(self, root): + self.root = root + self.root.title("Serial to MQTT Bridge") + self.root.geometry("800x600") + + # Variables + self.is_running = False + self.serial_port = None + self.mqtt_client = None + self.data_queue = queue.Queue() + + # Load configuration + self.config = load_config() + + self.create_widgets() + self.setup_layout() + + def create_widgets(self): + # Serial Port Frame + self.serial_frame = ttk.LabelFrame(self.root, text="Serial Port Settings", padding="5") + + self.port_var = tk.StringVar() + self.port_combo = ttk.Combobox(self.serial_frame, textvariable=self.port_var) + self.refresh_ports() + + self.refresh_btn = ttk.Button(self.serial_frame, text="Refresh Ports", command=self.refresh_ports) + self.baud_var = tk.StringVar(value=str(self.config.get('serial', {}).get('baud_rate', 115200))) + self.baud_label = ttk.Label(self.serial_frame, text="Baud Rate:") + self.baud_entry = ttk.Entry(self.serial_frame, textvariable=self.baud_var, width=10) + + # MQTT Settings Frame + self.mqtt_frame = ttk.LabelFrame(self.root, text="MQTT Settings", padding="5") + + self.broker_var = tk.StringVar(value=self.config.get('mqtt', {}).get('broker', "wss://rocket.mwave.eti.pg.gda.pl")) + self.broker_label = ttk.Label(self.mqtt_frame, text="Broker:") + self.broker_entry = ttk.Entry(self.mqtt_frame, textvariable=self.broker_var, width=40) + + self.port_mqtt_var = tk.StringVar(value=str(self.config.get('mqtt', {}).get('port', 443))) + self.port_mqtt_label = ttk.Label(self.mqtt_frame, text="Port:") + self.port_mqtt_entry = ttk.Entry(self.mqtt_frame, textvariable=self.port_mqtt_var, width=10) + + self.topic_var = tk.StringVar(value=self.config.get('mqtt', {}).get('topic', "serial2MQTT")) + self.topic_label = ttk.Label(self.mqtt_frame, text="Topic:") + self.topic_entry = ttk.Entry(self.mqtt_frame, textvariable=self.topic_var, width=40) + + self.username_var = tk.StringVar(value=self.config.get('mqtt', {}).get('username', "")) + self.username_label = ttk.Label(self.mqtt_frame, text="Username:") + self.username_entry = ttk.Entry(self.mqtt_frame, textvariable=self.username_var, width=20) + + self.password_var = tk.StringVar(value=self.config.get('mqtt', {}).get('password', "")) + self.password_label = ttk.Label(self.mqtt_frame, text="Password:") + self.password_entry = ttk.Entry(self.mqtt_frame, textvariable=self.password_var, show="*", width=20) + + # Simulation Settings Frame + self.sim_frame = ttk.LabelFrame(self.root, text="Simulation Settings", padding="5") + + self.sim_interval_var = tk.StringVar(value=str(self.config.get('sim', {}).get('interval', 1))) + self.sim_interval_label = ttk.Label(self.sim_frame, text="Interval (s):") + self.sim_interval_entry = ttk.Entry(self.sim_frame, textvariable=self.sim_interval_var, width=10) + + self.file_path_var = tk.StringVar(value=self.config.get('sim', {}).get('file_path', "sim_data.txt")) + self.file_path_label = ttk.Label(self.sim_frame, text="Data File:") + self.file_path_entry = ttk.Entry(self.sim_frame, textvariable=self.file_path_var, width=40) + + # Control Buttons + self.control_frame = ttk.Frame(self.root) + self.start_btn = ttk.Button(self.control_frame, text="Start", command=self.start) + self.stop_btn = ttk.Button(self.control_frame, text="Stop", command=self.stop, state="disabled") + + # Log Display + self.log_frame = ttk.LabelFrame(self.root, text="Log", padding="5") + self.log_text = scrolledtext.ScrolledText(self.log_frame, height=15) + + # Dashboard Link + self.dashboard_frame = ttk.Frame(self.root) + self.dashboard_label = ttk.Label(self.dashboard_frame, text="View data in Grafana dashboard:") + self.dashboard_link = ttk.Label(self.dashboard_frame, text="https://sustronics.mwave.eti.pg.gda.pl/d/1Mar-DTiz/sustronicspilot3-3", foreground="blue", cursor="hand2") + self.dashboard_link.bind("<Button-1>", lambda e: self.open_dashboard()) + self.dashboard_link.bind("<Enter>", lambda e: self.dashboard_link.configure(underline=True)) + self.dashboard_link.bind("<Leave>", lambda e: self.dashboard_link.configure(underline=False)) + + # Git Info Frame + self.git_frame = ttk.Frame(self.root) + git_sha = get_version() + self.git_label = ttk.Label(self.git_frame, text=f"Version: {git_sha}", foreground="gray") + # JeĹli wystÄ piĹ bĹÄ d, logujemy go + if "Error:" in git_sha: + self.log(git_sha) + + def setup_layout(self): + # Serial Port Frame + self.serial_frame.grid(row=0, column=0, padx=5, pady=5, sticky="ew") + self.port_combo.grid(row=0, column=0, padx=5, pady=5) + self.refresh_btn.grid(row=0, column=1, padx=5, pady=5) + self.baud_label.grid(row=0, column=2, padx=5, pady=5) + self.baud_entry.grid(row=0, column=3, padx=5, pady=5) + + # MQTT Settings Frame + self.mqtt_frame.grid(row=1, column=0, padx=5, pady=5, sticky="ew") + self.broker_label.grid(row=0, column=0, padx=5, pady=5) + self.broker_entry.grid(row=0, column=1, columnspan=2, padx=5, pady=5) + self.port_mqtt_label.grid(row=0, column=3, padx=5, pady=5) + self.port_mqtt_entry.grid(row=0, column=4, padx=5, pady=5) + + self.topic_label.grid(row=1, column=0, padx=5, pady=5) + self.topic_entry.grid(row=1, column=1, columnspan=2, padx=5, pady=5) + + self.username_label.grid(row=2, column=0, padx=5, pady=5) + self.username_entry.grid(row=2, column=1, padx=5, pady=5) + self.password_label.grid(row=2, column=2, padx=5, pady=5) + self.password_entry.grid(row=2, column=3, padx=5, pady=5) + + # Simulation Settings Frame + self.sim_frame.grid(row=2, column=0, padx=5, pady=5, sticky="ew") + self.sim_interval_label.grid(row=0, column=0, padx=5, pady=5) + self.sim_interval_entry.grid(row=0, column=1, padx=5, pady=5) + self.file_path_label.grid(row=0, column=2, padx=5, pady=5) + self.file_path_entry.grid(row=0, column=3, columnspan=2, padx=5, pady=5) + + # Control Buttons + self.control_frame.grid(row=3, column=0, padx=5, pady=5) + self.start_btn.grid(row=0, column=0, padx=5) + self.stop_btn.grid(row=0, column=1, padx=5) + + # Dashboard Link + self.dashboard_frame.grid(row=4, column=0, padx=5, pady=5) + self.dashboard_label.grid(row=0, column=0, padx=5) + self.dashboard_link.grid(row=0, column=1, padx=5) + + # Git Info + self.git_frame.grid(row=5, column=0, padx=5, pady=5) + self.git_label.grid(row=0, column=0, padx=5) + + # Log Display + self.log_frame.grid(row=6, column=0, padx=5, pady=5, sticky="nsew") + self.log_text.grid(row=0, column=0, padx=5, pady=5, sticky="nsew") + + # Configure grid weights + self.root.grid_rowconfigure(6, weight=1) + self.root.grid_columnconfigure(0, weight=1) + + def refresh_ports(self): + ports = list_serial_ports() + if ports: + self.port_combo['values'] = ports + if not self.port_var.get(): + self.port_combo.set(ports[0]) + else: + self.port_combo['values'] = ['sim'] + self.port_combo.set('sim') + + def log(self, message): + self.log_text.insert(tk.END, f"{message}\n") + self.log_text.see(tk.END) + + def start(self): + try: + # Get values from GUI + port = self.port_var.get() + baud_rate = int(self.baud_var.get()) + broker = self.broker_var.get() + mqtt_port = int(self.port_mqtt_var.get()) + topic = self.topic_var.get() + username = self.username_var.get() + password = self.password_var.get() + sim_interval = int(self.sim_interval_var.get()) + file_path = self.file_path_var.get() + + # Connect to MQTT + connect_mqtt(broker, mqtt_port, username, password) + + # Start the main thread + self.is_running = True + self.start_btn.config(state="disabled") + self.stop_btn.config(state="normal") + + # Start the main processing thread + self.main_thread = threading.Thread( + target=self.main_loop, + args=(port, baud_rate, file_path, broker, mqtt_port, topic, sim_interval, username, password) + ) + self.main_thread.daemon = True + self.main_thread.start() + + self.log("Started successfully") + + except Exception as e: + messagebox.showerror("Error", f"Failed to start: {str(e)}") + self.stop() + + def stop(self): + self.is_running = False + self.start_btn.config(state="normal") + self.stop_btn.config(state="disabled") + + if self.serial_port: + try: + self.serial_port.close() + except: + pass - except KeyboardInterrupt: - print("Program terminated.") + if self.mqtt_client: + try: + self.mqtt_client.loop_stop() + self.mqtt_client.disconnect() + except: + pass + + self.log("Stopped") + + def main_loop(self, serial_port, baud_rate, file_path, mqtt_broker, mqtt_port, mqtt_topic, sim_interval, mqtt_username, mqtt_password): + """ + Main processing loop for both serial and simulation modes + """ + try: + if serial_port.lower() != "sim": + # Serial port mode + try: + self.serial_port = serial.Serial(serial_port, baud_rate, timeout=1) + self.log(f"Connected to serial port: {serial_port}") + + while self.is_running: + if self.serial_port.in_waiting > 0: + try: + line = self.serial_port.readline().decode('utf-8').strip() + if line: # Skip empty lines + self.log(f"Received from serial port: {line}") + client.publish(mqtt_topic, line) + self.log(f"Sent to MQTT broker={mqtt_broker} in topic={mqtt_topic}, msg={line}") + except Exception as e: + self.log(f"Error processing serial data: {str(e)}") + time.sleep(0.1) + + except Exception as e: + self.log(f"Error in serial port connection: {str(e)}") + finally: + if self.serial_port: + self.serial_port.close() + self.log("Serial port closed") + else: + # Simulation mode + try: + data_generator = cyclic_data_generator(file_path) + cnt = 0 + self.log("Started simulation mode") + + while self.is_running: + try: + line = next(data_generator) + cnt += 1 + msg = f"{mqtt_topic},{line}{cnt}" + client.publish(mqtt_topic, msg) + self.log(f"Sent to MQTT broker={mqtt_broker} in topic={mqtt_topic}, msg={msg}") + time.sleep(sim_interval) + except Exception as e: + self.log(f"Error generating simulation data: {str(e)}") + break + + except Exception as e: + self.log(f"Error in simulation setup: {str(e)}") + + except Exception as e: + self.log(f"Critical error in main loop: {str(e)}") finally: - client.loop_stop() - client.disconnect() + self.stop() + + def open_dashboard(self): + import webbrowser + webbrowser.open("https://sustronics.mwave.eti.pg.gda.pl/d/1Mar-DTiz/sustronicspilot3-3?orgId=2&refresh=1m") + +def main(): + root = tk.Tk() + app = SerialToMQTTGUI(root) + root.mainloop() if __name__ == "__main__": - # Load configuration from YAML - config = load_config() - - # Argument parser for serial port, baud rate, and file path - parser = argparse.ArgumentParser(description="Read data from serial port and publish to MQTT.") - parser.add_argument("--serial_port", type=str, help="Serial port to connect to (e.g., COM3) or 'sim' for simulation mode") - parser.add_argument("--baud_rate", type=int, default=config.get('serial', {}).get('baud_rate', 115200), help="Baud rate for serial communication") - parser.add_argument("--file_path", type=str, default=config.get('sim', {}).get('file_path', "sim_data.txt"), help="Path to the file with data for MQTT") - parser.add_argument("--mqtt_broker", type=str, default=config.get('mqtt', {}).get('broker', "192.168.52.5"), help="MQTT broker address") - parser.add_argument("--mqtt_port", type=int, default=config.get('mqtt', {}).get('port', 1883), help="MQTT port") - parser.add_argument("--mqtt_topic", type=str, default=config.get('mqtt', {}).get('topic', "serial2MQTT"), help="MQTT topic to publish data") - parser.add_argument("--sim_interval", type=int, default=config.get('sim', {}).get('interval', 1), help="Interval in seconds for sending data from file") - args = parser.parse_args() - - # If port is not provided as argument, let user select it - if not args.serial_port: - args.serial_port = select_serial_port() - if not args.serial_port: - print("No port selected. Program will terminate.") - exit(1) - - # Run main with configuration from YAML and/or command line arguments - main( - serial_port=args.serial_port, - baud_rate=args.baud_rate, - file_path=args.file_path, - mqtt_broker=args.mqtt_broker, - mqtt_port=args.mqtt_port, - mqtt_topic=args.mqtt_topic, - sim_interval=args.sim_interval - ) + main() diff --git a/serial_to_mqtt_bridge/serial_to_mqtt_sim.exe b/serial_to_mqtt_bridge/serial_to_mqtt_sim.exe deleted file mode 100644 index e81340b199bdac3804277e7ea656f1666929fe07..0000000000000000000000000000000000000000 Binary files a/serial_to_mqtt_bridge/serial_to_mqtt_sim.exe and /dev/null differ diff --git a/serial_to_mqtt_bridge/serial_to_mqtt_sim.py b/serial_to_mqtt_bridge/serial_to_mqtt_sim.py deleted file mode 100755 index 8ed50b4ded3bd6f7f916d0a9e2eba8e99e595817..0000000000000000000000000000000000000000 --- a/serial_to_mqtt_bridge/serial_to_mqtt_sim.py +++ /dev/null @@ -1,125 +0,0 @@ -#!/usr/bin/env python -import paho.mqtt.client as mqtt -import time -import argparse -import yaml -import os -import random - -# Built-in simulation data -SIM_DATA = [ - "dongle_t=c000d0009,beacon_t=c000beac0004 rssi=49,char=22,channel=37,tx_power=8,temp=928,pH=372,pHRaw=46,battery=3002,timestamp=", - "dongle_t=c000d0009,beacon_t=c000beac0004 rssi=46,char=22,channel=39,tx_power=8,temp=3914,pH=3391,pHRaw=423,battery=2158,timestamp=", - "dongle_t=c000d0009,beacon_t=c000beac0004 rssi=60,char=38,channel=37,tx_power=8,temp=1645,pH=600,pHRaw=75,battery=512,timestamp=", - "dongle_t=c000d0009,beacon_t=c000beac0004 rssi=69,char=21,channel=39,tx_power=8,temp=833,pH=1331,pHRaw=166,battery=259,timestamp=", - "dongle_t=c000d0009,beacon_t=c000beac0004 rssi=51,char=20,channel=37,tx_power=8,temp=1574,pH=2811,pHRaw=351,battery=405,timestamp=", - "dongle_t=c000d0009,beacon_t=c000beac0004 rssi=85,char=24,channel=39,tx_power=8,temp=1390,pH=914,pHRaw=114,battery=523,timestamp=", - "dongle_t=c000d0009,beacon_t=c000beac0004 rssi=50,char=22,channel=39,tx_power=8,temp=667,pH=2074,pHRaw=259,battery=2569,timestamp=", - "dongle_t=c000d0009,beacon_t=c000beac0004 rssi=48,char=24,channel=37,tx_power=8,temp=4090,pH=1586,pHRaw=198,battery=3991,timestamp=", - "dongle_t=c000d0009,beacon_t=c000beac0004 rssi=46,char=20,channel=39,tx_power=8,temp=2689,pH=1329,pHRaw=166,battery=479,timestamp=", - "dongle_t=c000d0009,beacon_t=c000beac0004 rssi=76,char=22,channel=38,tx_power=8,temp=2734,pH=3961,pHRaw=495,battery=3025,timestamp=" -] - -def generate_sim_data(): - """ - Generates random simulation data in the same format as the original data - """ - valid_chars = list(range(13, 25)) + [37, 38] - rssi = random.randint(45, 92) - char = random.choice(valid_chars) - channel = random.randint(37, 39) - tx_power = 8 - temp = random.randint(0, 4095) - pH = random.randint(0, 4095) - pHRaw = int(pH/8) - battery = random.randint(0, 4095) - - return (f"dongle_t=c000d0009,beacon_t=c000beac0004 " - f"rssi={rssi},char={char},channel={channel},tx_power={tx_power}," - f"temp={temp},pH={pH},pHRaw={pHRaw},battery={battery},timestamp=") - -# Load default configuration from YAML file -def load_config(file_path="config.yaml"): - """ - Load configuration from YAML file. - If file doesn't exist or there's an error, return empty dict. - Default values are handled in argument parser. - """ - if not os.path.exists(file_path): - print(f"Warning: Configuration file '{file_path}' not found. Using default values from command line arguments.") - return {} - - try: - with open(file_path, 'r') as file: - return yaml.safe_load(file) or {} - except Exception as e: - print(f"Warning: Error reading configuration file '{file_path}': {str(e)}. Using default values from command line arguments.") - return {} - -# MQTT connection initialization -client = mqtt.Client() - -def connect_mqtt(broker, port): - client.connect(broker, port, 60) - client.loop_start() - -def cyclic_data_generator(): - """ - Generator that yields data from SIM_DATA cyclically - """ - while True: - for data in SIM_DATA: - yield data - -def random_data_generator(): - """ - Generator that yields random simulation data - """ - while True: - yield generate_sim_data() - -# Main function to handle simulation data -def main(use_random_data=False, mqtt_broker="localhost", mqtt_port=1883, mqtt_topic="serial2MQTT", sim_interval=1): - connect_mqtt(mqtt_broker, mqtt_port) - - # Choose data generator based on mode - data_generator = random_data_generator() if use_random_data else cyclic_data_generator() - cnt = 0 - - try: - while True: - # Get the next data line - line = next(data_generator) - cnt = cnt + 1 - msg = mqtt_topic + "," + line + str(cnt) - client.publish(mqtt_topic,msg) - print(f"Sent to MQTT broker={mqtt_broker} in topic={mqtt_topic} msg={msg}") - time.sleep(sim_interval) - - except KeyboardInterrupt: - print("Program terminated.") - finally: - client.loop_stop() - client.disconnect() - -if __name__ == "__main__": - # Load configuration from YAML - config = load_config() - - # Argument parser for configuration - parser = argparse.ArgumentParser(description="Simulate serial data and publish to MQTT.") - parser.add_argument("--random", action="store_true", help="Use random data instead of predefined simulation data") - parser.add_argument("--mqtt_broker", type=str, default=config.get('mqtt', {}).get('broker', "localhost"), help="MQTT broker address") - parser.add_argument("--mqtt_port", type=int, default=config.get('mqtt', {}).get('port', 1883), help="MQTT port") - parser.add_argument("--mqtt_topic", type=str, default=config.get('mqtt', {}).get('topic', "serial2MQTT"), help="MQTT topic to publish data") - parser.add_argument("--sim_interval", type=int, default=config.get('sim', {}).get('interval', 1), help="Interval in seconds for sending data") - args = parser.parse_args() - - # Run main with configuration from YAML and/or command line arguments - main( - use_random_data=args.random, - mqtt_broker=args.mqtt_broker, - mqtt_port=args.mqtt_port, - mqtt_topic=args.mqtt_topic, - sim_interval=args.sim_interval - ) \ No newline at end of file diff --git a/serial_to_mqtt_bridge/serial_to_mqtt.exe b/serial_to_mqtt_bridge/susGatewayApp.exe similarity index 70% rename from serial_to_mqtt_bridge/serial_to_mqtt.exe rename to serial_to_mqtt_bridge/susGatewayApp.exe index 4b7bc4f522f449d3be072b527729cd47d6ec882a..e7d106a64dc7649e37d3ab13425bde7706b95266 100644 Binary files a/serial_to_mqtt_bridge/serial_to_mqtt.exe and b/serial_to_mqtt_bridge/susGatewayApp.exe differ diff --git a/serial_to_mqtt_bridge/sustronics_logo_lowres.png b/serial_to_mqtt_bridge/sustronics_logo_lowres.png new file mode 100644 index 0000000000000000000000000000000000000000..9503d089fa8c3d8e6426c108f35a9f7273c888bf Binary files /dev/null and b/serial_to_mqtt_bridge/sustronics_logo_lowres.png differ