-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathac-python-app-example.py
More file actions
357 lines (272 loc) · 10.8 KB
/
ac-python-app-example.py
File metadata and controls
357 lines (272 loc) · 10.8 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
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
"""
ac-python-app-example
==================
A simple example Assetto Corsa Python application demonstrating:
- Basic UI creation (labels, buttons)
- Event handling (button clicks)
- Configuration management (INI files)
- Telemetry data access (AC API and shared memory)
- Timer-based updates with different frequencies
Author: LeoLTM
License: MIT
"""
import sys
import os
import platform
import configparser
# Standard AC imports
import ac
import acsys
# ============================================================================
# PLATFORM DETECTION AND LIBRARY LOADING
# ============================================================================
# Detect system architecture and load appropriate _ctypes.pyd
if platform.architecture()[0] == "64bit":
sysdir = os.path.dirname(__file__) + '/stdlib64'
else:
sysdir = os.path.dirname(__file__) + '/stdlib'
# Add the platform-specific directory to the path
sys.path.insert(0, sysdir)
os.environ['PATH'] = os.environ['PATH'] + ";."
# Import shared memory interface
info = None
try:
from lib.sim_info import info
except Exception as e:
pass # Will log error in acMain
# ============================================================================
# GLOBAL VARIABLES
# ============================================================================
# App window
appWindow = None
# UI Elements
label_speed = None
label_gear = None
label_lap = None
label_extra = None
button_toggle = None
# App state
lapcount = 0
speed_unit = "kph" # Can be "kph" or "mph"
show_extra_info = True
# Timers for different update frequencies
timer_high = 0.0 # For high-frequency updates (speed, gear)
timer_medium = 0.0 # For medium-frequency updates (lap count)
# Configuration
config = None
config_path = "apps/python/ac-python-app-example/settings.ini"
high_freq_interval = 0.0167 # ~60 FPS
medium_freq_interval = 0.5 # 2 Hz
# ============================================================================
# CONFIGURATION MANAGEMENT
# ============================================================================
def load_config():
"""Load configuration from settings.ini file."""
global config, speed_unit, show_extra_info, high_freq_interval, medium_freq_interval
config = configparser.ConfigParser()
try:
config.read(config_path)
# Load display settings
if config.has_section('Display'):
speed_unit = config.get('Display', 'speed_unit', fallback='kph')
show_extra_info = config.getboolean('Display', 'show_extra_info', fallback=True)
# Load update frequencies
if config.has_section('Updates'):
high_freq_interval = config.getfloat('Updates', 'high_frequency', fallback=0.0167)
medium_freq_interval = config.getfloat('Updates', 'medium_frequency', fallback=0.5)
ac.log("ac-python-app-example: Configuration loaded successfully")
except Exception as e:
ac.log("ac-python-app-example: Error loading config: {}".format(str(e)))
# Use defaults if config fails to load
def save_config():
"""Save configuration to settings.ini file."""
global config, speed_unit, show_extra_info
if config is None:
config = configparser.ConfigParser()
try:
# Update config values
if not config.has_section('Display'):
config.add_section('Display')
config.set('Display', 'speed_unit', speed_unit)
config.set('Display', 'show_extra_info', '1' if show_extra_info else '0')
# Write to file
with open(config_path, 'w') as configfile:
config.write(configfile)
ac.log("ac-python-app-example: Configuration saved successfully")
except Exception as e:
ac.log("ac-python-app-example: Error saving config: {}".format(str(e)))
# ============================================================================
# HELPER FUNCTIONS
# ============================================================================
def format_time(milliseconds):
"""
Format time in milliseconds to MM:SS.mmm format.
Args:
milliseconds: Time in milliseconds
Returns:
Formatted time string
"""
if milliseconds <= 0:
return "--:--.---"
minutes = int(milliseconds / 60000)
seconds = int((milliseconds % 60000) / 1000)
millis = int(milliseconds % 1000)
return "{:02d}:{:02d}.{:03d}".format(minutes, seconds, millis)
def format_gear(gear):
"""
Format gear for display.
Args:
gear: Gear number (0 = R, 1 = N, 2+ = 1st, 2nd, etc.)
Returns:
Formatted gear string
"""
if gear == 0:
return "R"
elif gear == 1:
return "N"
else:
return str(gear - 1)
def convert_speed(speed_kmh):
"""
Convert speed based on current unit setting.
Args:
speed_kmh: Speed in km/h
Returns:
Converted speed value
"""
if speed_unit == "mph":
return speed_kmh * 0.621371
return speed_kmh
# ============================================================================
# EVENT HANDLERS
# ============================================================================
def on_button_toggle(*args):
"""
Handle button click to toggle speed units between KPH and MPH.
This demonstrates basic event handling and UI updates.
"""
global speed_unit
# Toggle speed unit
if speed_unit == "kph":
speed_unit = "mph"
ac.setText(button_toggle, "Switch to KPH")
ac.console("ac-python-app-example: Switched to MPH")
else:
speed_unit = "kph"
ac.setText(button_toggle, "Switch to MPH")
ac.console("ac-python-app-example: Switched to KPH")
# Save the new setting
save_config()
# ============================================================================
# MAIN AC FUNCTIONS
# ============================================================================
def acMain(ac_version):
"""
Main initialization function called by Assetto Corsa.
Sets up the app window and all UI elements.
Args:
ac_version: AC version number
Returns:
App name string
"""
global appWindow, label_speed, label_gear, label_lap, label_extra, button_toggle, info
# Create main app window FIRST
appWindow = ac.newApp("ac-python-app-example")
ac.setSize(appWindow, 200, 200)
# Log initialization
ac.log("ac-python-app-example: Initializing app (version: {})".format(ac_version))
ac.console("ac-python-app-example: App started")
# Check if shared memory is available
if info is None:
ac.log("ac-python-app-example: WARNING - sim_info not available, some features will be disabled")
ac.console("ac-python-app-example: Shared memory not available")
# Load configuration
load_config()
# Create speed label
label_speed = ac.addLabel(appWindow, "Speed: --- km/h")
ac.setPosition(label_speed, 10, 35)
ac.setFontSize(label_speed, 16)
# Create gear label
label_gear = ac.addLabel(appWindow, "Gear: -")
ac.setPosition(label_gear, 10, 60)
ac.setFontSize(label_gear, 16)
# Create lap count label
label_lap = ac.addLabel(appWindow, "Lap: 0")
ac.setPosition(label_lap, 10, 85)
ac.setFontSize(label_lap, 16)
# Create extra info label (using shared memory data)
label_extra = ac.addLabel(appWindow, "Fuel: --- L")
ac.setPosition(label_extra, 10, 110)
ac.setFontSize(label_extra, 14)
ac.setFontColor(label_extra, 0.8, 0.8, 0.8, 1.0)
# Create toggle button for speed units
button_toggle = ac.addButton(appWindow, "Switch to MPH")
ac.setPosition(button_toggle, 10, 145)
ac.setSize(button_toggle, 180, 30)
ac.addOnClickedListener(button_toggle, on_button_toggle)
ac.log("ac-python-app-example: UI elements created successfully")
return "ac-python-app-example"
def acUpdate(deltaT):
"""
Update function called every frame by Assetto Corsa.
Uses timers to control update frequency for different data.
Args:
deltaT: Time since last call in seconds
"""
global timer_high, timer_medium, lapcount
# Update timers
timer_high += deltaT
timer_medium += deltaT
# HIGH FREQUENCY UPDATES (~60 FPS)
# Update speed and gear frequently for smooth display
if timer_high >= high_freq_interval:
timer_high = 0.0
try:
# Get speed using AC API
speed_kmh = ac.getCarState(0, acsys.CS.SpeedKMH)
speed_display = convert_speed(speed_kmh)
unit_text = speed_unit.upper()
ac.setText(label_speed, "Speed: {:.0f} {}".format(speed_display, unit_text))
# Get gear using shared memory for demonstration (fallback to AC API if unavailable)
if info is not None:
gear = info.physics.gear
else:
# Fallback: AC API doesn't have direct gear access, so use a placeholder
gear = 2 # Will display as "1"
gear_text = format_gear(gear)
ac.setText(label_gear, "Gear: {}".format(gear_text))
except Exception as e:
ac.log("ac-python-app-example: Error in high frequency update: {}".format(str(e)))
# MEDIUM FREQUENCY UPDATES (2 Hz)
# Update lap count and fuel less frequently
if timer_medium >= medium_freq_interval:
timer_medium = 0.0
try:
# Get lap count using AC API
laps = ac.getCarState(0, acsys.CS.LapCount)
if laps > lapcount:
lapcount = laps
ac.console("Boileac-python-app-examplerplate: Completed lap {}".format(lapcount))
ac.setText(label_lap, "Lap: {}".format(lapcount))
# Get fuel using shared memory (if available)
if info is not None:
fuel = info.physics.fuel
ac.setText(label_extra, "Fuel: {:.1f} L".format(fuel))
else:
ac.setText(label_extra, "Fuel: N/A")
except Exception as e:
ac.log("ac-python-app-example: Error in medium frequency update: {}".format(str(e)))
def acShutdown():
"""
Cleanup function called when the session ends.
Save configuration and perform any necessary cleanup.
"""
ac.log("ac-python-app-example: Shutting down")
ac.console("ac-python-app-example: App closed")
# Save configuration
save_config()
# ============================================================================
# ENTRY POINT
# ============================================================================
if __name__ == "__main__":
ac.log("ac-python-app-example: Module loaded as main")