-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathconfig.py
More file actions
208 lines (185 loc) · 7.47 KB
/
config.py
File metadata and controls
208 lines (185 loc) · 7.47 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
# /opt/racecam/config.py
import configparser
import logging
from pathlib import Path
# --- File Paths ---
CONFIG_FILE_PATH = "/opt/racecam/stream.conf"
ASSETS_DIR = "/opt/racecam/assets"
GAUGE_FACE_IMAGE = f"{ASSETS_DIR}/gauge-face.png"
GAUGE_NEEDLE_IMAGE = f"{ASSETS_DIR}/gauge-needle.png"
GAUGE_HUB_IMAGE = f"{ASSETS_DIR}/gauge-hub.png"
DRIVER_HEADSHOT_IMAGE = f"{ASSETS_DIR}/driver-headshot.png"
CODRIVER_HEADSHOT_IMAGE = f"{ASSETS_DIR}/codriver-headshot.png"
HEART_ICON_IMAGE = f"{ASSETS_DIR}/heart-icon.png"
G_FORCE_BG_IMAGE = f"{ASSETS_DIR}/g-force-bg.png"
GAUGE_FONT_PATH = "/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf"
# --- Hardware Devices ---
VIDEO_DEVICE = "/dev/video3"
# --- Stream Configuration ---
SCREEN_WIDTH = 1920
SCREEN_HEIGHT = 1080
FRAME_RATE = "60/1"
GRAPHICS_UPDATE_RATE_HZ = 10
# --- Telemetry Configuration ---
DRIVER_NAME = "Chris Phares"
CODRIVER_NAME = "Nichole Phares"
MAX_RPM = 5500
RPM_ANGLE_START = -179
RPM_ANGLE_END = 0
RPM_ANGLE_RANGE = RPM_ANGLE_END - RPM_ANGLE_START
MAX_G_FORCE = 1.5
MAX_BOOST_PSI = 40.0
MAX_WATER_TEMP_F = 240.0
MAX_OIL_PRESS_PSI = 100.0
# --- Stream URLs ---
STREAM_URLS = {
'youtube': 'rtmp://a.rtmp.youtube.com/live2',
'twitch': 'rtmp://live.twitch.tv/app',
'facebook': 'rtmps://live-api-s.facebook.com:443/rtmp'
}
# --- Graphics Configuration ---
V_BAR_WIDTH_WIDE = 32
V_BAR_WIDTH_NARROW = 21
MAX_V_BAR_HEIGHT = 153
V_BAR_BORDER = 2
V_BAR_BG_COLOR = (0, 0, 0, 120) # Darker semi-transparent
V_BAR_BORDER_COLOR = (255, 255, 255, 200) # Opaque white
GAUGE_ASSET_SIZE = 220
GAUGE_CANVAS_SIZE = 312
G_FORCE_METER_SIZE = 240
HEADSHOT_SIZE = 240
HEART_ICON_SIZE = 32
# --- Positioning ---
BOTTOM_BAR_HEIGHT = 240
BOTTOM_BAR_Y = SCREEN_HEIGHT - BOTTOM_BAR_HEIGHT
CENTER_Y_ON_BAR = BOTTOM_BAR_Y + (BOTTOM_BAR_HEIGHT // 2)
# Center Box for Bars and Gear
CENTER_BOX_WIDTH = 240
CENTER_BOX_HEIGHT = 240
CENTER_BOX_X = ((SCREEN_WIDTH - CENTER_BOX_WIDTH) // 2) - 120
CENTER_BOX_Y = BOTTOM_BAR_Y
# Bar positions are now relative to the center box
BAR_SPACING = 15
TOTAL_BARS_WIDTH = (2 * V_BAR_WIDTH_WIDE) + (3 * V_BAR_WIDTH_NARROW) + (5 * BAR_SPACING)
BARS_START_X = CENTER_BOX_X + (CENTER_BOX_WIDTH - TOTAL_BARS_WIDTH) // 2 - (V_BAR_BORDER * 2)
BOOST_BAR_X = BARS_START_X
WATER_BAR_X = BOOST_BAR_X + V_BAR_WIDTH_NARROW + BAR_SPACING
OIL_BAR_X = WATER_BAR_X + V_BAR_WIDTH_NARROW + BAR_SPACING
BRAKE_BAR_X = OIL_BAR_X + V_BAR_WIDTH_NARROW + (BAR_SPACING * 2) + (V_BAR_BORDER * 2)
THROTTLE_BAR_X = BRAKE_BAR_X + V_BAR_WIDTH_WIDE + BAR_SPACING
GAUGE_CENTER_X = (1920 // 2) + 120
GAUGE_CENTER_Y = CENTER_Y_ON_BAR
G_FORCE_METER_CENTER_X = GAUGE_CENTER_X + 240
G_FORCE_METER_CENTER_Y = CENTER_Y_ON_BAR
V_BAR_BASE_Y = CENTER_BOX_Y + CENTER_BOX_HEIGHT - 20
GEARS = ['P', 'R', 'N', 'D', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10']
GEAR_INDICATOR_Y_POS = CENTER_BOX_Y + 20
# Align the gear indicator with the center of the new box
GEAR_INDICATOR_START_X = CENTER_BOX_X + (CENTER_BOX_WIDTH // 2) - ( (len(GEARS) -1) * 25 // 2)
GEAR_INDICATOR_SPACING = 25
def get_stream_settings():
"""Reads streaming settings from the configuration file."""
settings = {
'service': 'youtube', # default
'stream_key': '',
'custom_url': ''
}
try:
config = get_config()
if config.has_section('streaming'):
settings['service'] = config.get('streaming', 'service', fallback='youtube')
settings['stream_key'] = config.get('streaming', 'stream_key', fallback='')
settings['custom_url'] = config.get('streaming', 'custom_url', fallback='')
# For backward compatibility from when only youtube was an option
elif config.has_section('youtube'):
settings['stream_key'] = config.get('youtube', 'stream_key', fallback='')
return settings
except Exception as e:
logging.error(f"Error reading stream settings from config file: {e}"); return None
def probe_audio_device():
"""Probes /proc/asound/cards for the wm8904audio codec."""
try:
with open('/proc/asound/cards', 'r') as f:
for line in f:
if 'wm8904audio' in line:
card_index = line.strip().split()[0]
device_str = f"hw:{card_index},0"
logging.info(f"Found wm8904audio codec at card index {card_index}. Using device '{device_str}'.")
return device_str
except Exception as e:
logging.error(f"Could not probe for audio devices: {e}")
logging.error("Could not find wm8904audio sound card in /proc/asound/cards.")
return None
def check_assets():
"""Checks if all required asset files exist."""
asset_paths = [
GAUGE_FACE_IMAGE, GAUGE_NEEDLE_IMAGE, GAUGE_HUB_IMAGE,
DRIVER_HEADSHOT_IMAGE, HEART_ICON_IMAGE, G_FORCE_BG_IMAGE
]
return all(Path(p).is_file() for p in asset_paths)
# --- Smart Configuration Loading ---
_config = configparser.ConfigParser()
_last_mtime = 0
def get_config():
"""
Returns the globally loaded config object.
Reloads the file from disk if it has been modified.
"""
global _config, _last_mtime
try:
current_mtime = Path(CONFIG_FILE_PATH).stat().st_mtime
if current_mtime > _last_mtime:
logging.info("Configuration file has changed. Reloading.")
_config = configparser.ConfigParser()
_config.read(CONFIG_FILE_PATH)
_last_mtime = current_mtime
except FileNotFoundError:
if _last_mtime == 0: # Only log the error on the first attempt
logging.error(f"Configuration file not found at '{CONFIG_FILE_PATH}'")
_config = configparser.ConfigParser() # Return an empty config to prevent crashes
return _config
def get_gear_indicator_status():
"""Reads the gear indicator status from the configuration file."""
config = get_config()
try:
return config.getboolean('gear_indicator', 'enabled', fallback=True)
except Exception as e:
logging.error(f"Error reading gear indicator status from config file: {e}"); return None
def get_enabled_gears():
"""Reads the enabled status for each gear from the configuration file."""
config = get_config()
enabled_gears = []
# If the section doesn't exist, all gears are enabled by default.
if not config.has_section('gears'):
return GEARS
for gear in GEARS:
if config.getboolean('gears', gear, fallback=True):
enabled_gears.append(gear)
return enabled_gears
OVERLAY_ELEMENTS = [
'driver_info',
'codriver_info',
'rpm_gauge',
'pedal_bars',
'aux_bars',
'g_force_meter'
]
def get_overlay_element_status(element_name):
"""Reads the enabled status for a specific overlay element from the config file."""
config = get_config()
if element_name not in OVERLAY_ELEMENTS:
logging.warning(f"Checked for unknown overlay element: {element_name}")
return False
# Default to True if the section or key doesn't exist.
return config.getboolean('overlay_elements', element_name, fallback=True)
def get_mumble_settings():
"""Reads Mumble client settings from the configuration file."""
config = get_config() # Correctly uses the global config now
settings = {
'enabled': config.getboolean('mumble', 'enabled', fallback=False),
'host': config.get('mumble', 'host', fallback=''),
'port': config.getint('mumble', 'port', fallback=64738),
'user': config.get('mumble', 'user', fallback='Racecam'),
'password': config.get('mumble', 'password', fallback='')
}
return settings