2026-01-14 14:44:01 +01:00
|
|
|
import sys
|
|
|
|
|
import os
|
|
|
|
|
import json
|
|
|
|
|
import math
|
|
|
|
|
from PySide6.QtGui import QGuiApplication
|
|
|
|
|
from PySide6.QtQml import QQmlApplicationEngine
|
|
|
|
|
from PySide6.QtCore import QObject, Property, Signal, Slot, QTimer
|
|
|
|
|
|
|
|
|
|
# Try importing w1thermsensor, handle failure for dev environments
|
|
|
|
|
try:
|
|
|
|
|
from w1thermsensor import W1ThermSensor, Sensor
|
|
|
|
|
HAS_W1 = True
|
|
|
|
|
except ImportError:
|
|
|
|
|
HAS_W1 = False
|
|
|
|
|
print("Warning: w1thermsensor not found. Running in simulation mode.")
|
|
|
|
|
|
|
|
|
|
class Backend(QObject):
|
|
|
|
|
leftTempChanged = Signal()
|
|
|
|
|
rightTempChanged = Signal()
|
|
|
|
|
|
|
|
|
|
def __init__(self, demo_mode=False):
|
|
|
|
|
super().__init__()
|
|
|
|
|
self.demo_mode = demo_mode
|
|
|
|
|
self._left_temp = 0.0
|
|
|
|
|
self._right_temp = 0.0
|
|
|
|
|
|
|
|
|
|
self.left_sensor = None
|
|
|
|
|
self.right_sensor = None
|
|
|
|
|
|
|
|
|
|
# Load Config
|
|
|
|
|
self.config = self.load_config()
|
|
|
|
|
|
|
|
|
|
# Initialize Sensors
|
|
|
|
|
self.init_sensors()
|
|
|
|
|
|
|
|
|
|
# Timer for polling
|
|
|
|
|
self.timer = QTimer()
|
|
|
|
|
self.timer.timeout.connect(self.update_temps)
|
|
|
|
|
self.timer.start(2000) # Poll every 2 seconds
|
|
|
|
|
|
|
|
|
|
def load_config(self):
|
2026-01-14 14:58:30 +01:00
|
|
|
# Determine path based on run mode (Script vs Frozen/Compiled)
|
|
|
|
|
if getattr(sys, 'frozen', False):
|
|
|
|
|
# If run as a compiled exe, look in the same directory as the executable
|
|
|
|
|
application_path = os.path.dirname(sys.executable)
|
|
|
|
|
else:
|
|
|
|
|
# If run as a script, look in the directory of the script
|
|
|
|
|
application_path = os.path.dirname(os.path.abspath(__file__))
|
|
|
|
|
|
|
|
|
|
config_path = os.path.join(application_path, "config.json")
|
2026-01-14 14:44:01 +01:00
|
|
|
if os.path.exists(config_path):
|
|
|
|
|
try:
|
|
|
|
|
with open(config_path, 'r') as f:
|
|
|
|
|
return json.load(f)
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"Error loading config: {e}")
|
|
|
|
|
return {}
|
|
|
|
|
|
|
|
|
|
def init_sensors(self):
|
|
|
|
|
if not HAS_W1:
|
|
|
|
|
print("W1ThermSensor not installed.")
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
print(f"Loading sensors using config: {self.config}")
|
|
|
|
|
try:
|
|
|
|
|
available_sensors = W1ThermSensor.get_available_sensors()
|
|
|
|
|
print(f"Available W1 sensors: {[s.id for s in available_sensors]}")
|
|
|
|
|
|
|
|
|
|
# Map Left Sensor
|
|
|
|
|
left_id = self.config.get("left_sensor_id")
|
|
|
|
|
if left_id:
|
|
|
|
|
try:
|
|
|
|
|
# Check if ID exists in available to be sure
|
|
|
|
|
found = next((s for s in available_sensors if s.id == left_id), None)
|
|
|
|
|
if found:
|
|
|
|
|
self.left_sensor = found
|
|
|
|
|
print(f"✅ Successfully bound Left Sensor to {left_id}")
|
|
|
|
|
else:
|
|
|
|
|
print(f"❌ Configured Left Sensor {left_id} not found in available list!")
|
|
|
|
|
# Try direct init anyway just in case
|
|
|
|
|
self.left_sensor = W1ThermSensor(sensor_id=left_id)
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"❌ Failed to bind Left Sensor {left_id}: {e}")
|
|
|
|
|
|
|
|
|
|
# Auto-assign fallback
|
|
|
|
|
if not self.left_sensor and len(available_sensors) > 0:
|
|
|
|
|
self.left_sensor = available_sensors[0]
|
|
|
|
|
print(f"⚠️ Auto-assigned Left Sensor to {self.left_sensor.id}")
|
|
|
|
|
|
|
|
|
|
# Map Right Sensor
|
|
|
|
|
right_id = self.config.get("right_sensor_id")
|
|
|
|
|
if right_id:
|
|
|
|
|
try:
|
|
|
|
|
self.right_sensor = W1ThermSensor(sensor_id=right_id)
|
|
|
|
|
print(f"Bound Right Sensor to {right_id}")
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"Failed to bind Right Sensor {right_id}: {e}")
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"Error initializing sensors: {e}")
|
|
|
|
|
|
|
|
|
|
def update_temps(self):
|
|
|
|
|
# Simulation counter
|
|
|
|
|
if not hasattr(self, '_sim_step'):
|
|
|
|
|
self._sim_step = 0
|
|
|
|
|
self._sim_step += 0.1
|
|
|
|
|
|
|
|
|
|
# Left
|
|
|
|
|
if self.left_sensor:
|
|
|
|
|
try:
|
|
|
|
|
t = self.left_sensor.get_temperature()
|
|
|
|
|
print(f"Left Sensor ({self.left_sensor.id}) Read: {t}°C")
|
|
|
|
|
self._set_left_temp(t)
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"Error reading left sensor: {e}")
|
|
|
|
|
elif self.demo_mode:
|
|
|
|
|
# Simulation
|
|
|
|
|
print("Left Sensor Missing - Simulating...")
|
|
|
|
|
sim_val = 5 + 10 * math.sin(self._sim_step)
|
|
|
|
|
self._set_left_temp(sim_val)
|
|
|
|
|
|
|
|
|
|
# Right
|
|
|
|
|
if self.right_sensor:
|
|
|
|
|
try:
|
|
|
|
|
t = self.right_sensor.get_temperature()
|
|
|
|
|
self._set_right_temp(t)
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"Error reading right sensor: {e}")
|
|
|
|
|
elif self.demo_mode:
|
|
|
|
|
# Simulation
|
|
|
|
|
sim_val = 10 + 10 * math.cos(self._sim_step)
|
|
|
|
|
self._set_right_temp(sim_val)
|
|
|
|
|
|
|
|
|
|
@Property(float, notify=leftTempChanged)
|
|
|
|
|
def leftTemp(self):
|
|
|
|
|
return self._left_temp
|
|
|
|
|
|
|
|
|
|
def _set_left_temp(self, val):
|
|
|
|
|
if abs(self._left_temp - val) > 0.1:
|
|
|
|
|
self._left_temp = val
|
|
|
|
|
self.leftTempChanged.emit()
|
|
|
|
|
|
|
|
|
|
@Property(float, notify=rightTempChanged)
|
|
|
|
|
def rightTemp(self):
|
|
|
|
|
return self._right_temp
|
|
|
|
|
|
|
|
|
|
def _set_right_temp(self, val):
|
|
|
|
|
if abs(self._right_temp - val) > 0.1:
|
|
|
|
|
self._right_temp = val
|
|
|
|
|
self.rightTempChanged.emit()
|
|
|
|
|
|
|
|
|
|
def main():
|
|
|
|
|
# Redirect output to log.txt
|
|
|
|
|
sys.stdout = open("log.txt", "w", buffering=1)
|
|
|
|
|
sys.stderr = sys.stdout
|
|
|
|
|
print("--- Volvo Display Log ---")
|
|
|
|
|
|
|
|
|
|
# Check for demo mode
|
|
|
|
|
demo_mode = "--demo" in sys.argv
|
|
|
|
|
if demo_mode:
|
|
|
|
|
print("Starting in DEMO MODE (Simulation Enabled)")
|
|
|
|
|
|
|
|
|
|
app = QGuiApplication(sys.argv)
|
|
|
|
|
engine = QQmlApplicationEngine()
|
|
|
|
|
|
|
|
|
|
backend = Backend(demo_mode=demo_mode)
|
|
|
|
|
engine.rootContext().setContextProperty("backend", backend)
|
|
|
|
|
|
|
|
|
|
# Get absolute path to main.qml
|
|
|
|
|
current_dir = os.path.dirname(os.path.abspath(__file__))
|
|
|
|
|
qml_file = os.path.join(current_dir, "main.qml")
|
|
|
|
|
|
|
|
|
|
engine.load(qml_file)
|
|
|
|
|
if not engine.rootObjects():
|
|
|
|
|
sys.exit(-1)
|
|
|
|
|
|
|
|
|
|
sys.exit(app.exec())
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
main()
|