redesign of simulated data functionality

This commit is contained in:
Aditya Pulipaka
2025-10-19 00:23:17 -05:00
parent 4be808d0da
commit 0595b810d5
3 changed files with 17331 additions and 43 deletions

16585
ALONSO_2023_MONZA_RACE Normal file

File diff suppressed because it is too large Load Diff

649
fetch_data.ipynb Normal file
View File

@@ -0,0 +1,649 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": 40,
"id": "9a9714f8",
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"core INFO \tLoading data for Italian Grand Prix - Race [v3.6.1]\n",
"req INFO \tUsing cached data for session_info\n",
"req INFO \tUsing cached data for driver_info\n",
"req INFO \tUsing cached data for session_info\n",
"req INFO \tUsing cached data for driver_info\n",
"req INFO \tUsing cached data for session_status_data\n",
"req INFO \tUsing cached data for lap_count\n",
"req INFO \tUsing cached data for track_status_data\n",
"req INFO \tUsing cached data for session_status_data\n",
"req INFO \tUsing cached data for lap_count\n",
"req INFO \tUsing cached data for track_status_data\n",
"req INFO \tUsing cached data for _extended_timing_data\n",
"req INFO \tUsing cached data for timing_app_data\n",
"core INFO \tProcessing timing data...\n",
"req INFO \tUsing cached data for _extended_timing_data\n",
"req INFO \tUsing cached data for timing_app_data\n",
"core INFO \tProcessing timing data...\n",
"req INFO \tUsing cached data for car_data\n",
"req INFO \tUsing cached data for car_data\n",
"req INFO \tUsing cached data for position_data\n",
"req INFO \tUsing cached data for position_data\n",
"req INFO \tUsing cached data for weather_data\n",
"req INFO \tUsing cached data for race_control_messages\n",
"core WARNING \tDriver 1 completed the race distance 06:25.888000 before the recorded end of the session.\n",
"core WARNING \tDriver 11 completed the race distance 06:19.824000 before the recorded end of the session.\n",
"core WARNING \tDriver 55 completed the race distance 06:14.695000 before the recorded end of the session.\n",
"core WARNING \tDriver 16 completed the race distance 06:14.511000 before the recorded end of the session.\n",
"core WARNING \tDriver 63 completed the race distance 06:07.860000 before the recorded end of the session.\n",
"core WARNING \tDriver 44 completed the race distance 05:48.209000 before the recorded end of the session.\n",
"req INFO \tUsing cached data for weather_data\n",
"req INFO \tUsing cached data for race_control_messages\n",
"core WARNING \tDriver 1 completed the race distance 06:25.888000 before the recorded end of the session.\n",
"core WARNING \tDriver 11 completed the race distance 06:19.824000 before the recorded end of the session.\n",
"core WARNING \tDriver 55 completed the race distance 06:14.695000 before the recorded end of the session.\n",
"core WARNING \tDriver 16 completed the race distance 06:14.511000 before the recorded end of the session.\n",
"core WARNING \tDriver 63 completed the race distance 06:07.860000 before the recorded end of the session.\n",
"core WARNING \tDriver 44 completed the race distance 05:48.209000 before the recorded end of the session.\n",
"core WARNING \tDriver 23 completed the race distance 05:40.782000 before the recorded end of the session.\n",
"core WARNING \tDriver 4 completed the race distance 05:40.439000 before the recorded end of the session.\n",
"core WARNING \tDriver 14 completed the race distance 05:39.594000 before the recorded end of the session.\n",
"core WARNING \tDriver 23 completed the race distance 05:40.782000 before the recorded end of the session.\n",
"core WARNING \tDriver 4 completed the race distance 05:40.439000 before the recorded end of the session.\n",
"core WARNING \tDriver 14 completed the race distance 05:39.594000 before the recorded end of the session.\n",
"core INFO \tFinished loading data for 20 drivers: ['1', '11', '55', '16', '63', '44', '23', '4', '14', '77', '40', '81', '2', '24', '10', '18', '27', '20', '31', '22']\n",
"core INFO \tFinished loading data for 20 drivers: ['1', '11', '55', '16', '63', '44', '23', '4', '14', '77', '40', '81', '2', '24', '10', '18', '27', '20', '31', '22']\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Created dataframe with 16584 rows\n",
"Total laps in race: 51.0\n",
"Laps covered: 1.0 to 51.0\n",
"Tire life range: 1.0 to 33.0 laps\n"
]
},
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>lap_number</th>\n",
" <th>total_laps</th>\n",
" <th>speed</th>\n",
" <th>overall_time</th>\n",
" <th>throttle</th>\n",
" <th>brake</th>\n",
" <th>tire_compound</th>\n",
" <th>tire_life_laps</th>\n",
" <th>track_temperature</th>\n",
" <th>rainfall</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>1.0</td>\n",
" <td>51.0</td>\n",
" <td>0.0</td>\n",
" <td>0 days 01:22:21.734000</td>\n",
" <td>23.0</td>\n",
" <td>False</td>\n",
" <td>MEDIUM</td>\n",
" <td>1.0</td>\n",
" <td>42.5</td>\n",
" <td>False</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>1.0</td>\n",
" <td>51.0</td>\n",
" <td>0.0</td>\n",
" <td>0 days 01:22:21.894000</td>\n",
" <td>23.0</td>\n",
" <td>False</td>\n",
" <td>MEDIUM</td>\n",
" <td>1.0</td>\n",
" <td>42.5</td>\n",
" <td>False</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2</th>\n",
" <td>1.0</td>\n",
" <td>51.0</td>\n",
" <td>4.0</td>\n",
" <td>0 days 01:22:22.214000</td>\n",
" <td>26.0</td>\n",
" <td>False</td>\n",
" <td>MEDIUM</td>\n",
" <td>1.0</td>\n",
" <td>42.5</td>\n",
" <td>False</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3</th>\n",
" <td>1.0</td>\n",
" <td>51.0</td>\n",
" <td>14.0</td>\n",
" <td>0 days 01:22:22.494000</td>\n",
" <td>24.0</td>\n",
" <td>False</td>\n",
" <td>MEDIUM</td>\n",
" <td>1.0</td>\n",
" <td>42.5</td>\n",
" <td>False</td>\n",
" </tr>\n",
" <tr>\n",
" <th>4</th>\n",
" <td>1.0</td>\n",
" <td>51.0</td>\n",
" <td>24.0</td>\n",
" <td>0 days 01:22:22.774000</td>\n",
" <td>24.0</td>\n",
" <td>False</td>\n",
" <td>MEDIUM</td>\n",
" <td>1.0</td>\n",
" <td>42.5</td>\n",
" <td>False</td>\n",
" </tr>\n",
" <tr>\n",
" <th>5</th>\n",
" <td>1.0</td>\n",
" <td>51.0</td>\n",
" <td>31.0</td>\n",
" <td>0 days 01:22:22.974000</td>\n",
" <td>26.0</td>\n",
" <td>False</td>\n",
" <td>MEDIUM</td>\n",
" <td>1.0</td>\n",
" <td>42.5</td>\n",
" <td>False</td>\n",
" </tr>\n",
" <tr>\n",
" <th>6</th>\n",
" <td>1.0</td>\n",
" <td>51.0</td>\n",
" <td>38.0</td>\n",
" <td>0 days 01:22:23.254000</td>\n",
" <td>36.0</td>\n",
" <td>False</td>\n",
" <td>MEDIUM</td>\n",
" <td>1.0</td>\n",
" <td>42.5</td>\n",
" <td>False</td>\n",
" </tr>\n",
" <tr>\n",
" <th>7</th>\n",
" <td>1.0</td>\n",
" <td>51.0</td>\n",
" <td>50.0</td>\n",
" <td>0 days 01:22:23.494000</td>\n",
" <td>41.0</td>\n",
" <td>False</td>\n",
" <td>MEDIUM</td>\n",
" <td>1.0</td>\n",
" <td>42.5</td>\n",
" <td>False</td>\n",
" </tr>\n",
" <tr>\n",
" <th>8</th>\n",
" <td>1.0</td>\n",
" <td>51.0</td>\n",
" <td>58.0</td>\n",
" <td>0 days 01:22:23.694000</td>\n",
" <td>44.0</td>\n",
" <td>False</td>\n",
" <td>MEDIUM</td>\n",
" <td>1.0</td>\n",
" <td>42.5</td>\n",
" <td>False</td>\n",
" </tr>\n",
" <tr>\n",
" <th>9</th>\n",
" <td>1.0</td>\n",
" <td>51.0</td>\n",
" <td>71.0</td>\n",
" <td>0 days 01:22:23.974000</td>\n",
" <td>55.0</td>\n",
" <td>False</td>\n",
" <td>MEDIUM</td>\n",
" <td>1.0</td>\n",
" <td>42.5</td>\n",
" <td>False</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" lap_number total_laps speed overall_time throttle brake \\\n",
"0 1.0 51.0 0.0 0 days 01:22:21.734000 23.0 False \n",
"1 1.0 51.0 0.0 0 days 01:22:21.894000 23.0 False \n",
"2 1.0 51.0 4.0 0 days 01:22:22.214000 26.0 False \n",
"3 1.0 51.0 14.0 0 days 01:22:22.494000 24.0 False \n",
"4 1.0 51.0 24.0 0 days 01:22:22.774000 24.0 False \n",
"5 1.0 51.0 31.0 0 days 01:22:22.974000 26.0 False \n",
"6 1.0 51.0 38.0 0 days 01:22:23.254000 36.0 False \n",
"7 1.0 51.0 50.0 0 days 01:22:23.494000 41.0 False \n",
"8 1.0 51.0 58.0 0 days 01:22:23.694000 44.0 False \n",
"9 1.0 51.0 71.0 0 days 01:22:23.974000 55.0 False \n",
"\n",
" tire_compound tire_life_laps track_temperature rainfall \n",
"0 MEDIUM 1.0 42.5 False \n",
"1 MEDIUM 1.0 42.5 False \n",
"2 MEDIUM 1.0 42.5 False \n",
"3 MEDIUM 1.0 42.5 False \n",
"4 MEDIUM 1.0 42.5 False \n",
"5 MEDIUM 1.0 42.5 False \n",
"6 MEDIUM 1.0 42.5 False \n",
"7 MEDIUM 1.0 42.5 False \n",
"8 MEDIUM 1.0 42.5 False \n",
"9 MEDIUM 1.0 42.5 False "
]
},
"execution_count": 40,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"\"\"\"\n",
"FastF1 Data Fetcher for HPC F1 AI Strategy System\n",
"\n",
"Downloads telemetry and race data from a specific F1 session to simulate\n",
"live telemetry streaming from a Raspberry Pi \"racecar\" to the HPC layer.\n",
"\n",
"Usage:\n",
" python fetch_race_data.py --year 2024 --race \"Monaco\" --driver VER --output data/\n",
"\"\"\"\n",
"import fastf1\n",
"import pandas as pd\n",
"\n",
"# 1. Load the session\n",
"session = fastf1.get_session(2023, 'Monza', 'R')\n",
"session.load(telemetry=True, laps=True, weather=True)\n",
"\n",
"# 2. Pick the driver\n",
"driver_laps = session.laps.pick_drivers('ALO')\n",
"\n",
"# Get total number of laps in the race (maximum lap number from all drivers)\n",
"total_laps = session.laps['LapNumber'].max()\n",
"\n",
"# 3. Collect all telemetry data with lap information\n",
"telemetry_list = []\n",
"\n",
"for lap_idx in driver_laps.index:\n",
" lap = driver_laps.loc[lap_idx]\n",
" lap_number = lap['LapNumber']\n",
" tire_compound = lap['Compound']\n",
" tire_life = lap['TyreLife'] # Number of laps on current tires\n",
" \n",
" # Get telemetry for this lap\n",
" car_data = lap.get_car_data()\n",
" \n",
" if car_data is not None and len(car_data) > 0:\n",
" # Add lap number, tire compound, and tire life to each telemetry point\n",
" car_data['LapNumber'] = lap_number\n",
" car_data['Compound'] = tire_compound\n",
" car_data['TyreLife'] = tire_life\n",
" telemetry_list.append(car_data)\n",
"\n",
"# 4. Combine all telemetry data\n",
"all_telemetry = pd.concat(telemetry_list, ignore_index=True)\n",
"\n",
"# 5. Get weather data\n",
"weather = session.weather_data\n",
"\n",
"# 6. Merge telemetry with weather based on timestamp\n",
"# First, ensure both have SessionTime column\n",
"all_telemetry['SessionTime'] = pd.to_timedelta(all_telemetry['SessionTime'])\n",
"weather['SessionTime'] = pd.to_timedelta(weather['Time'])\n",
"\n",
"# Merge using merge_asof for time-based joining\n",
"all_telemetry = all_telemetry.sort_values('SessionTime')\n",
"weather = weather.sort_values('SessionTime')\n",
"\n",
"merged_data = pd.merge_asof(\n",
" all_telemetry,\n",
" weather[['SessionTime', 'TrackTemp', 'Rainfall']],\n",
" on='SessionTime',\n",
" direction='nearest'\n",
")\n",
"\n",
"# 7. Create final dataframe with requested columns\n",
"final_df = pd.DataFrame({\n",
" 'lap_number': merged_data['LapNumber'],\n",
" 'total_laps': total_laps, # Total laps in the race\n",
" 'speed': merged_data['Speed'],\n",
" 'overall_time': merged_data['SessionTime'],\n",
" 'throttle': merged_data['Throttle'],\n",
" 'brake': merged_data['Brake'],\n",
" 'tire_compound': merged_data['Compound'],\n",
" 'tire_life_laps': merged_data['TyreLife'], # Number of laps on current tires\n",
" 'track_temperature': merged_data['TrackTemp'],\n",
" 'rainfall': merged_data['Rainfall']\n",
"})\n",
"\n",
"print(f\"Created dataframe with {len(final_df)} rows\")\n",
"print(f\"Total laps in race: {total_laps}\")\n",
"print(f\"Laps covered: {final_df['lap_number'].min()} to {final_df['lap_number'].max()}\")\n",
"print(f\"Tire life range: {final_df['tire_life_laps'].min()} to {final_df['tire_life_laps'].max()} laps\")\n",
"final_df.head(10)\n"
]
},
{
"cell_type": "code",
"execution_count": 41,
"id": "45d27f05",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Dataframe Info:\n",
"Total telemetry points: 16584\n",
"\n",
"Column types:\n",
"lap_number float64\n",
"total_laps float64\n",
"speed float64\n",
"overall_time timedelta64[ns]\n",
"throttle float64\n",
"brake bool\n",
"tire_compound object\n",
"tire_life_laps float64\n",
"track_temperature float64\n",
"rainfall bool\n",
"dtype: object\n",
"\n",
"Basic statistics:\n"
]
},
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>lap_number</th>\n",
" <th>total_laps</th>\n",
" <th>speed</th>\n",
" <th>overall_time</th>\n",
" <th>throttle</th>\n",
" <th>tire_life_laps</th>\n",
" <th>track_temperature</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>count</th>\n",
" <td>16584.000000</td>\n",
" <td>16584.0</td>\n",
" <td>16584.000000</td>\n",
" <td>16584</td>\n",
" <td>16584.000000</td>\n",
" <td>16584.000000</td>\n",
" <td>16584.000000</td>\n",
" </tr>\n",
" <tr>\n",
" <th>mean</th>\n",
" <td>25.891341</td>\n",
" <td>51.0</td>\n",
" <td>235.570188</td>\n",
" <td>0 days 01:59:34.577446394</td>\n",
" <td>72.291546</td>\n",
" <td>15.339243</td>\n",
" <td>42.908816</td>\n",
" </tr>\n",
" <tr>\n",
" <th>std</th>\n",
" <td>14.710977</td>\n",
" <td>0.0</td>\n",
" <td>76.948906</td>\n",
" <td>0 days 00:21:30.065940875</td>\n",
" <td>40.561237</td>\n",
" <td>8.558018</td>\n",
" <td>0.897756</td>\n",
" </tr>\n",
" <tr>\n",
" <th>min</th>\n",
" <td>1.000000</td>\n",
" <td>51.0</td>\n",
" <td>0.000000</td>\n",
" <td>0 days 01:22:21.734000</td>\n",
" <td>0.000000</td>\n",
" <td>1.000000</td>\n",
" <td>40.800000</td>\n",
" </tr>\n",
" <tr>\n",
" <th>25%</th>\n",
" <td>13.000000</td>\n",
" <td>51.0</td>\n",
" <td>180.000000</td>\n",
" <td>0 days 01:40:53.558000</td>\n",
" <td>40.000000</td>\n",
" <td>8.000000</td>\n",
" <td>42.500000</td>\n",
" </tr>\n",
" <tr>\n",
" <th>50%</th>\n",
" <td>26.000000</td>\n",
" <td>51.0</td>\n",
" <td>245.000000</td>\n",
" <td>0 days 01:59:31.222000</td>\n",
" <td>100.000000</td>\n",
" <td>15.000000</td>\n",
" <td>43.100000</td>\n",
" </tr>\n",
" <tr>\n",
" <th>75%</th>\n",
" <td>39.000000</td>\n",
" <td>51.0</td>\n",
" <td>309.000000</td>\n",
" <td>0 days 02:18:13.365000</td>\n",
" <td>100.000000</td>\n",
" <td>21.000000</td>\n",
" <td>43.600000</td>\n",
" </tr>\n",
" <tr>\n",
" <th>max</th>\n",
" <td>51.000000</td>\n",
" <td>51.0</td>\n",
" <td>351.000000</td>\n",
" <td>0 days 02:36:49.228000</td>\n",
" <td>100.000000</td>\n",
" <td>33.000000</td>\n",
" <td>44.400000</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" lap_number total_laps speed overall_time \\\n",
"count 16584.000000 16584.0 16584.000000 16584 \n",
"mean 25.891341 51.0 235.570188 0 days 01:59:34.577446394 \n",
"std 14.710977 0.0 76.948906 0 days 00:21:30.065940875 \n",
"min 1.000000 51.0 0.000000 0 days 01:22:21.734000 \n",
"25% 13.000000 51.0 180.000000 0 days 01:40:53.558000 \n",
"50% 26.000000 51.0 245.000000 0 days 01:59:31.222000 \n",
"75% 39.000000 51.0 309.000000 0 days 02:18:13.365000 \n",
"max 51.000000 51.0 351.000000 0 days 02:36:49.228000 \n",
"\n",
" throttle tire_life_laps track_temperature \n",
"count 16584.000000 16584.000000 16584.000000 \n",
"mean 72.291546 15.339243 42.908816 \n",
"std 40.561237 8.558018 0.897756 \n",
"min 0.000000 1.000000 40.800000 \n",
"25% 40.000000 8.000000 42.500000 \n",
"50% 100.000000 15.000000 43.100000 \n",
"75% 100.000000 21.000000 43.600000 \n",
"max 100.000000 33.000000 44.400000 "
]
},
"execution_count": 41,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# Display dataframe info and sample statistics\n",
"print(\"Dataframe Info:\")\n",
"print(f\"Total telemetry points: {len(final_df)}\")\n",
"print(f\"\\nColumn types:\")\n",
"print(final_df.dtypes)\n",
"print(f\"\\nBasic statistics:\")\n",
"final_df.describe()\n"
]
},
{
"cell_type": "code",
"execution_count": 42,
"id": "2fbcd2f9",
"metadata": {},
"outputs": [],
"source": [
"final_df.to_csv(\"ALONSO_2023_MONZA_RACE\")"
]
},
{
"cell_type": "code",
"execution_count": 43,
"id": "729fb12e",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Tire compound usage throughout the race:\n",
" lap_number tire_compound tire_life_laps\n",
"0 1.0 MEDIUM 1.0\n",
"1 2.0 MEDIUM 2.0\n",
"2 3.0 MEDIUM 3.0\n",
"3 4.0 MEDIUM 4.0\n",
"4 5.0 MEDIUM 5.0\n",
"5 6.0 MEDIUM 6.0\n",
"6 7.0 MEDIUM 7.0\n",
"7 8.0 MEDIUM 8.0\n",
"8 9.0 MEDIUM 9.0\n",
"9 10.0 MEDIUM 10.0\n",
"10 11.0 MEDIUM 11.0\n",
"11 12.0 MEDIUM 12.0\n",
"12 13.0 MEDIUM 13.0\n",
"13 14.0 MEDIUM 14.0\n",
"14 15.0 MEDIUM 15.0\n",
"15 16.0 MEDIUM 16.0\n",
"16 17.0 MEDIUM 17.0\n",
"17 18.0 MEDIUM 18.0\n",
"18 19.0 MEDIUM 19.0\n",
"19 20.0 MEDIUM 20.0\n",
"20 21.0 MEDIUM 21.0\n",
"21 22.0 HARD 4.0\n",
"22 23.0 HARD 5.0\n",
"23 24.0 HARD 6.0\n",
"24 25.0 HARD 7.0\n",
"25 26.0 HARD 8.0\n",
"26 27.0 HARD 9.0\n",
"27 28.0 HARD 10.0\n",
"28 29.0 HARD 11.0\n",
"29 30.0 HARD 12.0\n",
"30 31.0 HARD 13.0\n",
"31 32.0 HARD 14.0\n",
"32 33.0 HARD 15.0\n",
"33 34.0 HARD 16.0\n",
"34 35.0 HARD 17.0\n",
"35 36.0 HARD 18.0\n",
"36 37.0 HARD 19.0\n",
"37 38.0 HARD 20.0\n",
"38 39.0 HARD 21.0\n",
"39 40.0 HARD 22.0\n",
"40 41.0 HARD 23.0\n",
"41 42.0 HARD 24.0\n",
"42 43.0 HARD 25.0\n",
"43 44.0 HARD 26.0\n",
"44 45.0 HARD 27.0\n",
"45 46.0 HARD 28.0\n",
"46 47.0 HARD 29.0\n",
"47 48.0 HARD 30.0\n",
"48 49.0 HARD 31.0\n",
"49 50.0 HARD 32.0\n",
"50 51.0 HARD 33.0\n"
]
}
],
"source": [
"# Show tire compound changes and stint information\n",
"print(\"Tire compound usage throughout the race:\")\n",
"tire_changes = final_df.groupby(['lap_number', 'tire_compound', 'tire_life_laps']).size().reset_index(name='count')\n",
"tire_changes = tire_changes.groupby(['lap_number', 'tire_compound', 'tire_life_laps']).first().reset_index()[['lap_number', 'tire_compound', 'tire_life_laps']]\n",
"print(tire_changes.drop_duplicates())\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d9ebc90c",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "base",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.13.5"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -1,96 +1,137 @@
"""
Raspberry Pi Telemetry Stream Simulator
Replays downloaded FastF1 data as if it's coming from a live Raspberry Pi sensor.
Sends data to the HPC simulation layer via HTTP POST.
Reads the ALONSO_2023_MONZA_RACE CSV file row by row and simulates
live telemetry streaming from a Raspberry Pi sensor.
Sends data to the HPC simulation layer via HTTP POST at intervals
determined by the time differences between consecutive rows.
Usage:
python simulate_pi_stream.py --data data/race_data/VER_telemetry.json --speed 1.0
python simulate_pi_stream.py --data ALONSO_2023_MONZA_RACE --speed 1.0
"""
import argparse
import json
import time
import sys
from pathlib import Path
from typing import Dict, List, Any
from typing import Dict, Any
import pandas as pd
import requests
def load_telemetry(filepath: Path) -> List[Dict[str, Any]]:
"""Load telemetry data from JSON file."""
with open(filepath, 'r') as f:
data = json.load(f)
print(f"✓ Loaded {len(data)} telemetry points from {filepath}")
def load_telemetry_csv(filepath: Path) -> pd.DataFrame:
"""Load telemetry data from CSV file."""
df = pd.read_csv(filepath, index_col=0)
# Convert overall_time to timedelta if it's not already
if df['overall_time'].dtype == 'object':
df['overall_time'] = pd.to_timedelta(df['overall_time'])
print(f"✓ Loaded {len(df)} telemetry points from {filepath}")
print(f" Laps: {df['lap_number'].min():.0f}{df['lap_number'].max():.0f}")
print(f" Duration: {df['overall_time'].iloc[-1]}")
return df
def row_to_json(row: pd.Series) -> Dict[str, Any]:
"""Convert a DataFrame row to a JSON-compatible dictionary."""
data = {
'lap_number': int(row['lap_number']) if pd.notna(row['lap_number']) else None,
'total_laps': int(row['total_laps']) if pd.notna(row['total_laps']) else None,
'speed': float(row['speed']) if pd.notna(row['speed']) else 0.0,
'throttle': float(row['throttle']) if pd.notna(row['throttle']) else 0.0,
'brake': bool(row['brake']),
'tire_compound': str(row['tire_compound']) if pd.notna(row['tire_compound']) else 'UNKNOWN',
'tire_life_laps': float(row['tire_life_laps']) if pd.notna(row['tire_life_laps']) else 0.0,
'track_temperature': float(row['track_temperature']) if pd.notna(row['track_temperature']) else 0.0,
'rainfall': bool(row['rainfall'])
}
return data
def simulate_stream(
telemetry: List[Dict[str, Any]],
df: pd.DataFrame,
endpoint: str,
speed: float = 1.0,
start_lap: int = 1,
end_lap: int = None
):
"""
Simulate live telemetry streaming.
Simulate live telemetry streaming based on actual time intervals in the data.
Args:
telemetry: List of telemetry points
df: DataFrame with telemetry data
endpoint: HPC API endpoint URL
speed: Playback speed multiplier (1.0 = real-time, 2.0 = 2x speed)
start_lap: Starting lap number
end_lap: Ending lap number (None = all laps)
"""
# Filter by lap range
filtered = [p for p in telemetry if p['lap'] >= start_lap]
filtered_df = df[df['lap_number'] >= start_lap].copy()
if end_lap:
filtered = [p for p in filtered if p['lap'] <= end_lap]
filtered_df = filtered_df[filtered_df['lap_number'] <= end_lap].copy()
if not filtered:
if len(filtered_df) == 0:
print("❌ No telemetry points in specified lap range")
return
# Reset index for easier iteration
filtered_df = filtered_df.reset_index(drop=True)
print(f"\n🏁 Starting telemetry stream simulation")
print(f" Endpoint: {endpoint}")
print(f" Laps: {start_lap}{end_lap or 'end'}")
print(f" Speed: {speed}x")
print(f" Points: {len(filtered)}")
print(f" Duration: {filtered[-1]['timestamp_ms'] / 1000.0:.1f}s\n")
print(f" Points: {len(filtered_df)}")
start_time = time.time()
start_ts = filtered[0]['timestamp_ms']
total_duration = (filtered_df['overall_time'].iloc[-1] - filtered_df['overall_time'].iloc[0]).total_seconds()
print(f" Duration: {total_duration:.1f}s (real-time) → {total_duration / speed:.1f}s (playback)\n")
sent_count = 0
error_count = 0
current_lap = start_lap
try:
for i, point in enumerate(filtered):
# Calculate when this point should be sent
point_offset = (point['timestamp_ms'] - start_ts) / 1000.0 / speed
target_time = start_time + point_offset
for i in range(len(filtered_df)):
row = filtered_df.iloc[i]
# Wait until the right time
sleep_time = target_time - time.time()
if sleep_time > 0:
time.sleep(sleep_time)
# Calculate sleep time based on time difference to next row
if i < len(filtered_df) - 1:
next_row = filtered_df.iloc[i + 1]
time_diff = (next_row['overall_time'] - row['overall_time']).total_seconds()
sleep_time = time_diff / speed
# Ensure positive sleep time
if sleep_time < 0:
sleep_time = 0
else:
sleep_time = 0
# Convert row to JSON
telemetry_point = row_to_json(row)
# Send telemetry point
try:
response = requests.post(
endpoint,
json=point,
json=telemetry_point,
timeout=2.0
)
if response.status_code == 200:
sent_count += 1
if sent_count % 100 == 0:
elapsed = time.time() - start_time
progress = (i + 1) / len(filtered) * 100
print(f" 📡 Lap {point['lap']}: {sent_count} points sent "
f"({progress:.1f}% complete, {elapsed:.1f}s elapsed)")
# Print progress updates
if row['lap_number'] > current_lap:
current_lap = row['lap_number']
progress = (i + 1) / len(filtered_df) * 100
print(f" 📡 Lap {int(current_lap)}: {sent_count} points sent "
f"({progress:.1f}% complete)")
elif sent_count % 500 == 0:
progress = (i + 1) / len(filtered_df) * 100
print(f" 📡 Lap {int(row['lap_number'])}: {sent_count} points sent "
f"({progress:.1f}% complete)")
else:
error_count += 1
print(f" ⚠ HTTP {response.status_code}: {response.text[:50]}")
@@ -98,35 +139,47 @@ def simulate_stream(
except requests.RequestException as e:
error_count += 1
if error_count % 10 == 0:
print(f" ⚠ Connection error ({error_count} total): {e}")
print(f" ⚠ Connection error ({error_count} total): {str(e)[:50]}")
# Sleep until next point should be sent
if sleep_time > 0:
time.sleep(sleep_time)
print(f"\n✅ Stream complete!")
print(f" Sent: {sent_count} points")
print(f" Errors: {error_count}")
print(f" Duration: {time.time() - start_time:.1f}s")
except KeyboardInterrupt:
print(f"\n⏸ Stream interrupted by user")
print(f" Sent: {sent_count}/{len(filtered)} points")
print(f" Sent: {sent_count}/{len(filtered_df)} points")
def main():
parser = argparse.ArgumentParser(
description="Simulate Raspberry Pi telemetry streaming"
description="Simulate Raspberry Pi telemetry streaming from CSV data"
)
parser.add_argument("--data", type=str, required=True, help="Path to telemetry JSON file")
parser.add_argument("--data", type=str, default="ALONSO_2023_MONZA_RACE",
help="Path to telemetry CSV file")
parser.add_argument("--endpoint", type=str, default="http://localhost:8000/telemetry",
help="HPC API endpoint")
parser.add_argument("--speed", type=float, default=1.0, help="Playback speed (1.0 = real-time)")
parser.add_argument("--speed", type=float, default=1.0,
help="Playback speed (1.0 = real-time, 10.0 = 10x speed)")
parser.add_argument("--start-lap", type=int, default=1, help="Starting lap number")
parser.add_argument("--end-lap", type=int, default=None, help="Ending lap number")
args = parser.parse_args()
try:
telemetry = load_telemetry(Path(args.data))
# Handle relative paths from the project root
data_path = Path(args.data)
if not data_path.is_absolute():
# Try relative to script location first
script_dir = Path(__file__).parent.parent
data_path = script_dir / args.data
df = load_telemetry_csv(data_path)
simulate_stream(
telemetry,
df,
args.endpoint,
args.speed,
args.start_lap,
@@ -134,6 +187,7 @@ def main():
)
except FileNotFoundError:
print(f"❌ File not found: {args.data}")
print(f" Tried: {data_path}")
sys.exit(1)
except Exception as e:
print(f"❌ Error: {e}")