From 50e9e18060b638b6d3291ccab3a02f5e781c14bb Mon Sep 17 00:00:00 2001 From: Logan Simonsen Date: Thu, 26 Feb 2026 17:08:11 +1300 Subject: [PATCH 1/4] Added IP & Port changing Added option to change the port and IP used in the GUI --- main.py | 50 +++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 45 insertions(+), 5 deletions(-) diff --git a/main.py b/main.py index b6ae5c5..f0fe705 100644 --- a/main.py +++ b/main.py @@ -49,6 +49,10 @@ from struct import unpack import csv +# Defaults +tele_IP_addr = "0.0.0.0" +tele_port = 5607 + logging.basicConfig( level=logging.DEBUG, format="%(asctime)s [%(levelname)s] %(message)s", @@ -126,13 +130,13 @@ def to_dict(self): return {prop: getattr(self, prop) for prop in self.get_props()} class TelemetryReceiver(QObject): + global tele_IP_addr, tele_port data_received = Signal(dict) log_message = Signal(str) - def __init__(self, ip="0.0.0.0", port=5607): + + def __init__(self): super().__init__() - self.ip = ip - self.port = port self._running = False self.sock = None self.thread = None @@ -140,6 +144,8 @@ def __init__(self, ip="0.0.0.0", port=5607): def start(self): if self._running: return + self.ip = tele_IP_addr + self.port = tele_port self._running = True self.thread = threading.Thread(target=self._listen_loop, daemon=True) self.thread.start() @@ -249,7 +255,7 @@ def __init__(self): self.setWindowTitle("FH5 Telemetry") self.setStyleSheet("background-color: #121212; color: #808080;") - self.receiver = TelemetryReceiver(ip="0.0.0.0", port=5607) + self.receiver = TelemetryReceiver() self.receiver.data_received.connect(self.buffer_data) self.receiver.log_message.connect(self.log) @@ -300,6 +306,22 @@ def __init__(self): controls.addStretch() + + # Ip / Port Changer + ip_input = QLabel("IP:Port") + ip_input.setAlignment(Qt.AlignLeft) + controls.addWidget(ip_input) + self.changeIP = QLineEdit("127.0.0.1:5500") + self.changeIP.setFixedWidth(100) + self.changeIP.setPlaceholderText("IP:Port") + controls.addWidget(self.changeIP) + + self.btn_changeIP = QPushButton("Apply") + self.btn_changeIP.clicked.connect(self.update_IP_port) + controls.addWidget(self.btn_changeIP) + + controls.addStretch() + self.btn_clear_logs = QPushButton("Clear Logs") self.btn_clear_logs.clicked.connect(self.log_panel.clear) controls.addWidget(self.btn_clear_logs) @@ -514,6 +536,24 @@ def stop(self): chart.data.clear() chart.add_values([]) + def update_IP_port(self, ): + global tele_IP_addr, tele_port + try: + ip_port = self.changeIP.text().split(":") + if len(ip_port) != 2: + raise ValueError("Invalid format. Use IP:Port") + print(f"IP port input: {ip_port}") + tele_IP_addr = ip_port[0] + tele_port = int(ip_port[1]) + log.info(f"Telemetry IP/Port changed to {tele_IP_addr}:{tele_port}") + self.log(f"Telemetry IP/Port changed to {tele_IP_addr}:{tele_port}") + if self.receiver._running: + self.receiver.stop() + self.receiver.start() + except Exception as e: + log.error(f"Failed to change IP/Port: {e}") + self.log(f"Failed to change IP/Port: {e}") + def toggle_logging(self, checked): if checked: self._start_logging() @@ -565,4 +605,4 @@ def closeEvent(self, event): app = QApplication(sys.argv) window = ForzaTelemetryApp() window.showMaximized() - sys.exit(app.exec()) \ No newline at end of file + sys.exit(app.exec()) From 9d4cf08ce39e269e88871f8cbaa4203b505f97ef Mon Sep 17 00:00:00 2001 From: Logan Simonsen Date: Thu, 26 Feb 2026 17:09:39 +1300 Subject: [PATCH 2/4] Update main.py --- main.py | 48 ++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 44 insertions(+), 4 deletions(-) diff --git a/main.py b/main.py index b6ae5c5..f0a2b74 100644 --- a/main.py +++ b/main.py @@ -49,6 +49,10 @@ from struct import unpack import csv +# Defaults +tele_IP_addr = "0.0.0.0" +tele_port = 5607 + logging.basicConfig( level=logging.DEBUG, format="%(asctime)s [%(levelname)s] %(message)s", @@ -126,13 +130,13 @@ def to_dict(self): return {prop: getattr(self, prop) for prop in self.get_props()} class TelemetryReceiver(QObject): + global tele_IP_addr, tele_port data_received = Signal(dict) log_message = Signal(str) - def __init__(self, ip="0.0.0.0", port=5607): + + def __init__(self): super().__init__() - self.ip = ip - self.port = port self._running = False self.sock = None self.thread = None @@ -140,6 +144,8 @@ def __init__(self, ip="0.0.0.0", port=5607): def start(self): if self._running: return + self.ip = tele_IP_addr + self.port = tele_port self._running = True self.thread = threading.Thread(target=self._listen_loop, daemon=True) self.thread.start() @@ -249,7 +255,7 @@ def __init__(self): self.setWindowTitle("FH5 Telemetry") self.setStyleSheet("background-color: #121212; color: #808080;") - self.receiver = TelemetryReceiver(ip="0.0.0.0", port=5607) + self.receiver = TelemetryReceiver() self.receiver.data_received.connect(self.buffer_data) self.receiver.log_message.connect(self.log) @@ -300,6 +306,22 @@ def __init__(self): controls.addStretch() + + # Ip / Port Changer + ip_input = QLabel("IP:Port") + ip_input.setAlignment(Qt.AlignLeft) + controls.addWidget(ip_input) + self.changeIP = QLineEdit("127.0.0.1:5500") + self.changeIP.setFixedWidth(100) + self.changeIP.setPlaceholderText("IP:Port") + controls.addWidget(self.changeIP) + + self.btn_changeIP = QPushButton("Apply") + self.btn_changeIP.clicked.connect(self.update_IP_port) + controls.addWidget(self.btn_changeIP) + + controls.addStretch() + self.btn_clear_logs = QPushButton("Clear Logs") self.btn_clear_logs.clicked.connect(self.log_panel.clear) controls.addWidget(self.btn_clear_logs) @@ -514,6 +536,24 @@ def stop(self): chart.data.clear() chart.add_values([]) + def update_IP_port(self, ): + global tele_IP_addr, tele_port + try: + ip_port = self.changeIP.text().split(":") + if len(ip_port) != 2: + raise ValueError("Invalid format. Use IP:Port") + print(f"IP port input: {ip_port}") + tele_IP_addr = ip_port[0] + tele_port = int(ip_port[1]) + log.info(f"Telemetry IP/Port changed to {tele_IP_addr}:{tele_port}") + self.log(f"Telemetry IP/Port changed to {tele_IP_addr}:{tele_port}") + if self.receiver._running: + self.receiver.stop() + self.receiver.start() + except Exception as e: + log.error(f"Failed to change IP/Port: {e}") + self.log(f"Failed to change IP/Port: {e}") + def toggle_logging(self, checked): if checked: self._start_logging() From da6766314d7d8cf1020c712f42fb382622d3c9a3 Mon Sep 17 00:00:00 2001 From: Logan Simonsen Date: Thu, 26 Feb 2026 17:47:03 +1300 Subject: [PATCH 3/4] Updated readme to relfect new changes + added icon to window --- .gitignore | 3 ++- README.md | 3 +-- main.py | 4 +++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 1104fc6..527dc4f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ misc/ -output/ \ No newline at end of file +output/ +telemetry.log \ No newline at end of file diff --git a/README.md b/README.md index 896718d..6b53b4b 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ - 📊 **Live Graphs**: Real-time charts for engine, suspension, input, etc. - 💾 **CSV Logging**: Save sessions to file for playback and later review. - ⏪ **Replay Mode**: Open saved CSV files to view logged data with graph control. -- 🔧 **User Controls**: Adjustable sample count, start/stop, open/clear logs, etc. +- 🔧 **User Controls**: Adjustable sample count, start/stop, open/clear logs, change IP/port, etc. ## 📷 Preview @@ -96,5 +96,4 @@ See [`LICENSE`](./LICENSE) for full details. - Torque conversion (not entirely accurate right now) - Fix for speed, suspension, and velocity charts (jittering at idle/standstill) -- Input to choose port? - UI Improvements \ No newline at end of file diff --git a/main.py b/main.py index f0fe705..0d9965e 100644 --- a/main.py +++ b/main.py @@ -45,7 +45,7 @@ QTabWidget, QLabel, QTextEdit, QLineEdit, QGridLayout, QSlider, QFileDialog ) from PySide6.QtCharts import QChart, QChartView, QLineSeries, QValueAxis -from PySide6.QtGui import QPainter +from PySide6.QtGui import QPainter, QIcon from struct import unpack import csv @@ -254,6 +254,8 @@ def __init__(self): super().__init__() self.setWindowTitle("FH5 Telemetry") self.setStyleSheet("background-color: #121212; color: #808080;") + self.setWindowIcon(QIcon("./img/logo.ico")) + self.receiver = TelemetryReceiver() self.receiver.data_received.connect(self.buffer_data) From e5c83e2be6424e75da8aded1b98415f387a2e4a4 Mon Sep 17 00:00:00 2001 From: Logan Simonsen Date: Thu, 26 Feb 2026 21:26:27 +1300 Subject: [PATCH 4/4] Added Imperial / Metric toggle in code + Minor refactoring --- main.py | 39 +++++++++++++++++++++++++++------------ 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/main.py b/main.py index 0d9965e..52563f7 100644 --- a/main.py +++ b/main.py @@ -50,8 +50,8 @@ import csv # Defaults -tele_IP_addr = "0.0.0.0" -tele_port = 5607 +tele_IP_addr = "192.168.1.9" +tele_port = 5500 logging.basicConfig( level=logging.DEBUG, @@ -242,11 +242,11 @@ def add_values(self, vals): self.series.replace(points) if self.data: - mn, mx = min(self.data), max(self.data) - if mn == mx: - mn -= 0.1 - mx += 0.1 - self.axis_y.setRange(mn, mx) + minimum, maximum = min(self.data), max(self.data) + if minimum == maximum: + minimum -= 0.1 + maximum += 0.1 + self.axis_y.setRange(minimum, maximum) self.axis_x.setRange(0, len(self.data)) class ForzaTelemetryApp(QWidget): @@ -313,7 +313,7 @@ def __init__(self): ip_input = QLabel("IP:Port") ip_input.setAlignment(Qt.AlignLeft) controls.addWidget(ip_input) - self.changeIP = QLineEdit("127.0.0.1:5500") + self.changeIP = QLineEdit("") self.changeIP.setFixedWidth(100) self.changeIP.setPlaceholderText("IP:Port") controls.addWidget(self.changeIP) @@ -386,13 +386,28 @@ def _setup_charts(self): def buffer_data(self, data: dict): self.sample_count += 1 + imperial_toggle = False # Set to False to show speed in metric instead of imperial def scale_controls(val): return val * 100 / 255 def norm_steer(val): return val * 100 / 127 - def to_mph(val): return val * 2.23694 - def to_hp(val): return val / 745.7 + def to_mph(val): + if imperial_toggle: + return val * 2.23694 # MPH + else: + return val * 3.6 # KPH + def to_hp(val): + if imperial_toggle: + return val / 745.7 # HP + else: + return val / 1000 # KW def clamp_zero(val): return max(val, 0) - + def to_psi(val): + if imperial_toggle: + return val # PSI + else: + return val / 14.504 # BAR + + patch_map = { 'accel': scale_controls, 'brake': scale_controls, @@ -402,7 +417,7 @@ def clamp_zero(val): return max(val, 0) 'speed': to_mph, 'power': to_hp, 'torque': clamp_zero, - 'boost': clamp_zero + 'boost': to_psi, } raw_snapshot = {}