diff --git a/load_controller/gui.py b/load_controller/gui.py
index 12443bf..965f7c5 100644
--- a/load_controller/gui.py
+++ b/load_controller/gui.py
@@ -1,7 +1,7 @@
 import os.path
 import time
 
-from PySide6.QtCore import QTimer, QObject
+from PySide6.QtCore import QTimer, QObject, Signal
 from PySide6.QtWidgets import QFileDialog
 from PySide6.QtQml import QQmlApplicationEngine
 
@@ -17,7 +17,7 @@ class GUI(QObject):
     _start_pause_btn: QObject
     _stop_btn: QObject
 
-    def __init__(self, profile_handler: ProfileHandler):
+    def __init__(self, profile_handler: ProfileHandler, temperaturesUpdated: Signal):
         super().__init__(None)
 
         self._engine = QQmlApplicationEngine()
@@ -42,6 +42,8 @@ class GUI(QObject):
         self._update_timer.timeout.connect(self._update)
         self._update_timer.start(100)
 
+        temperaturesUpdated.connect(self._updateTemperatures)
+
     def _choose_profile(self):
         dlg = QFileDialog()
         dlg.setFileMode(QFileDialog.FileMode.ExistingFile)
@@ -79,3 +81,7 @@ class GUI(QObject):
         if self._profile_handler.state == ProfileState.RUNNING:
             dt = time.time() - self._profile_handler.profile_start
             self._win.setProperty("profileTime", dt)
+
+    def _updateTemperatures(self, temperatures: list[int]):
+        self._win.setProperty("temp_min", min(temperatures))
+        self._win.setProperty("temp_max", max(temperatures))
diff --git a/load_controller/temperatures.py b/load_controller/temperatures.py
new file mode 100644
index 0000000..ac0a75b
--- /dev/null
+++ b/load_controller/temperatures.py
@@ -0,0 +1,35 @@
+import sys
+import serial
+import struct
+
+from PySide6.QtCore import QObject, Signal
+
+START_OF_TEMPS = bytes((0xFF, 0xFF))
+N_SENSORS = 12
+TEMP_QUANT = 0.0625  # °C/quant
+
+
+class Temperatures(QObject):
+    temperaturesUpdated = Signal(list)
+
+    _temps: list[float]
+
+    def __init__(self, uart_path: str):
+        super().__init__(parent=None)
+        self.dev = serial.Serial(uart_path, 115200)
+        self._temps = [0.0] * N_SENSORS
+
+    def run(self):
+        while True:
+            self.dev.read_until(START_OF_TEMPS)
+            data = self.dev.read(N_SENSORS * 2)
+            temps = struct.unpack(f">{N_SENSORS}h", data)
+            for i, t in enumerate(temps):
+                if (t & 0x0F) != 0:
+                    print(
+                        f"WARN: temperature had a non-zero least-significant nibble: {t:04x}",
+                        file=sys.stderr,
+                    )
+                else:
+                    self._temps[i] = (t >> 4) * TEMP_QUANT
+            self.temperaturesUpdated.emit(self._temps)
diff --git a/main.py b/main.py
index a04faa0..d77da49 100755
--- a/main.py
+++ b/main.py
@@ -9,21 +9,28 @@ from PySide6.QtWidgets import QApplication
 from load_controller.gui import GUI
 from load_controller.load import Load
 from load_controller.profile_handler import ProfileHandler
+from load_controller.temperatures import Temperatures
 
 
 def main(argv: list[str]) -> int:
     app = QApplication(sys.argv)
     profile_handler = ProfileHandler()
-    gui = GUI(profile_handler)
 
     load = Load(argv[1], profile_handler)
+    temps = Temperatures(argv[2])
+    temp_thread = QThread()
+    temps.moveToThread(temp_thread)
+    temp_thread.started.connect(temps.run)
+    temp_thread.start()
+
+    gui = GUI(profile_handler, temps.temperaturesUpdated)
 
     return app.exec()
 
 
 if __name__ == "__main__":
-    if len(sys.argv) != 3:
-        print(f"Usage: {sys.argv[0]} LOAD-PORT BMS-PORT", file=sys.stderr)
+    if len(sys.argv) != 4:
+        print(f"Usage: {sys.argv[0]} LOAD-PORT TEMP-PORT BMS-PORT", file=sys.stderr)
         sys.exit(os.EX_USAGE)
 
     sys.exit(main(sys.argv))