Skip to content

Commit d2a914d

Browse files
committed
Add: Extraction of VIDA scripts
These VIDA scripts directly implement some functionality of the program interacting with the vehicles. From this, one can learn about the usage of the individual CAN messages.
1 parent 4e6b116 commit d2a914d

File tree

6 files changed

+115
-6
lines changed

6 files changed

+115
-6
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ sql/__pycache__/
22
ecu/
33
csv/
44
images/
5+
extracted_scripts/

README.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ The Python scripts use duckdb and pandas to work with the data.
99
### Extracting data from the database
1010

1111
Depending on wether PowerShell 7 and the SQLServer module are installed, you can either use `generate_csv.ps1` or `generate_csv.bat`.
12-
The PowerShell script takes significantly longer than the Batch script does to generate, but is capable of outputting completely correct files.
12+
The PowerShell scripts can take longer than the Batch script does to generate, but is capable of outputting completely correct files.
1313
The PowerShell script uses the `UseQuotes` option on `Export-Csv` which was introduced in PowerShell 7. On Windows 7 Professional the last working version of PowerShell 7 is 7.2.9.
1414
The SQL credentials are already written into the scripts and won't need to be changed.
1515

@@ -33,3 +33,11 @@ The CSV file will contain all known CAN parameters, their localized name, their
3333

3434
The conversion methods found in the outputted CSV file can be applied with the `scripts/evaluate_conversion.py` script.
3535
It parses the expression into a simple executable AST that supports the feature set required for the expressions from the VIDA database.
36+
37+
### Extracting scripts
38+
39+
The VIDA database contains XML-based scripts that can read and write data from and to the car.
40+
These use the CAN protocol and are used to perform certain actions, but can also be used to figure out how the protocol works.
41+
Reading these scripts can allow us to figure out how the CAN-based protocol works and how we can make use of it.
42+
43+
The `sql/extract_scripts.ps1` can be used to extract all script metadata into CSV files and the actual scripts into decompressed XML files.

scripts/filter_scripts.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import sys
2+
import duckdb
3+
import pandas as pd
4+
from read_csv import get_csv, DatabaseFile
5+
from vin_decoder import decode_vin, Vehicle
6+
7+
def get_filtered_scripts_for_profile(profile: str) -> list[str]:
8+
"""
9+
Returns all script IDs available for given profile
10+
"""
11+
get_csv(DatabaseFile.script_profile_map)
12+
return duckdb.sql(f"""
13+
SELECT fkScript FROM script_profile_map
14+
WHERE TRIM(fkProfile) = '{profile}'
15+
""").df()['fkScript'].tolist()
16+
17+
18+
def get_filtered_scripts(profiles: list[str]) -> pd.DataFrame:
19+
"""
20+
Returns all script IDs available for all given profiles.
21+
This is intended where one vehicle is represented by multiple profiles,
22+
not for querying scripts for multiple vehicles.
23+
"""
24+
get_csv(DatabaseFile.script_profile_map)
25+
profiles_df = pd.DataFrame(profiles, columns=['fkProfile'])
26+
return duckdb.sql("""
27+
SELECT DISTINCT fkScript FROM script_profile_map
28+
INNER JOIN profiles_df ON profiles_df.fkProfile=TRIM(script_profile_map.fkProfile)
29+
""").df()
30+
31+
32+
def get_script_descriptions(scripts: pd.DataFrame) -> pd.DataFrame:
33+
get_csv(DatabaseFile.script_content)
34+
return duckdb.sql("""
35+
SELECT DISTINCT scripts.fkScript,script_content.DisplayText FROM scripts
36+
INNER JOIN script_content ON script_content.fkScript=scripts.fkScript
37+
""").df()
38+
39+
40+
def get_script_description(script_id: str) -> str:
41+
get_csv(DatabaseFile.script_content)
42+
return duckdb.sql(f"SELECT fkScript,DisplayText FROM script_content WHERE fkScript='{script_id}'").df()['DisplayText'].iloc[0]
43+
44+
45+
if __name__ == '__main__':
46+
profiles = []
47+
48+
if len(profiles) == 0 and len(sys.argv) == 1:
49+
print("Usage: filter_scripts.py <VIN>")
50+
else:
51+
if len(profiles) == 0:
52+
vehicle = decode_vin(sys.argv[1], 1002)
53+
profiles = vehicle.get_vehicle_profiles()
54+
55+
scripts = get_filtered_scripts(profiles)
56+
scripts = get_script_descriptions(scripts)
57+
duckdb.write_csv(scripts, "ecu/scripts.csv")

scripts/read_csv.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@ class DatabaseFile(str, enum.Enum):
2626
t162 = 'carcom/T162_ProfileValue'
2727
t163 = 'carcom/T163_ProfileValueType'
2828
t191 = 'carcom/T191_TextData'
29+
script = 'DiagSwdlRepository/Script'
30+
script_car_function = 'DiagSwdlRepository/ScriptCarFunction'
31+
script_content = 'DiagSwdlRepository/ScriptContent'
32+
script_profile_map = 'DiagSwdlRepository/ScriptProfileMap'
33+
script_type = 'DiagSwdlRepository/ScriptType'
34+
script_variant = 'DiagSwdlRepository/ScriptVariant'
2935

3036
def get_csv(csv_file: DatabaseFile) -> duckdb.DuckDBPyRelation:
3137
"""
@@ -34,6 +40,6 @@ def get_csv(csv_file: DatabaseFile) -> duckdb.DuckDBPyRelation:
3440
if csv_file in loaded_csv_dictionary:
3541
return loaded_csv_dictionary[csv_file]
3642
else:
37-
loaded_csv_dictionary[csv_file] = duckdb.read_csv(f'csv/{csv_file}.csv', encoding='utf-8')
43+
loaded_csv_dictionary[csv_file] = duckdb.read_csv(f'csv/{csv_file}.csv', header=True, encoding='utf-8')
3844
duckdb.register(csv_file.name, loaded_csv_dictionary[csv_file])
3945
return loaded_csv_dictionary[csv_file]

sql/extract_scripts.ps1

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
. $PSScriptRoot\sql_server.ps1
2+
3+
New-Item -ItemType Directory -Path 'csv/DiagSwdlRepository/' -ErrorAction SilentlyContinue
4+
5+
# First, extract metadata into CSV files
6+
$db_tables = 'Script','ScriptCarFunction','ScriptProfileMap','ScriptType','ScriptVariant'
7+
foreach ($table in $db_tables) {
8+
Write-Output "Writing CSV '$($table)'..."
9+
$file_path = 'csv/DiagSwdlRepository/' + $table + '.csv'
10+
$rows = Invoke-Sqlcmd @parameters -Query @"
11+
set nocount on;
12+
SELECT * FROM DiagSwdlRepository.dbo.$($table)
13+
"@
14+
$rows | Export-Csv -Encoding UTF8 -UseQuotes AsNeeded -NoTypeInformation -path $file_path
15+
}
16+
17+
# Now, extract the ScriptContent table but write the binary data into separate files
18+
$target_language = 15 # This is en-US. For values, check DiagSwdlRepository.dbo.Language
19+
$rows = Invoke-Sqlcmd @parameters -MaxBinaryLength 10000000 -Query @"
20+
SELECT * FROM DiagSwdlRepository.dbo.ScriptContent
21+
WHERE fkLanguage=$($target_language)
22+
"@
23+
24+
$rows | Export-Csv -Encoding UTF8 -UseQuotes AsNeeded -NoTypeInformation -path 'csv/DiagSwdlRepository/ScriptContent.csv'
25+
26+
# The methodology of extracting the scripts I found here: http://www.stevediraddo.com/2019/01/13/volvo-canbus-tinkering/
27+
New-Item -ItemType Directory -Path 'extracted_scripts/' -ErrorAction SilentlyContinue
28+
foreach ($row in $rows) {
29+
Write-Output "Writing script '$($row.fkScript)'..."
30+
31+
# Write the zip file data to a file
32+
# TODO: Using MemoryStream and ZipArchive, it should be possible to do this in memory with .NET
33+
$zip_file = "$PWD\extracted_scripts\$($row.fkScript).zip"
34+
[System.IO.File]::WriteAllBytes($zip_file, $row.XmlDataCompressed)
35+
36+
# Extract the script XML from the archive, and give it an xml ending
37+
Expand-Archive -Path $zip_file -DestinationPath "$PWD\extracted_scripts"
38+
Rename-Item -Path "$PWD\extracted_scripts\$($row.fkScript)" -NewName "$PWD\extracted_scripts\$($row.fkScript).xml"
39+
Remove-Item -Path $zip_file
40+
}

sql/generate_csv.ps1

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,7 @@ foreach ($dbname in $dbnames) {
1616

1717
# Make sure the target directory exists
1818
$folder = 'csv/' + $dbname + '/'
19-
if (!(Test-Path -PathType container $folder))
20-
{
21-
New-Item -ItemType Directory -Path $folder | Out-Null
22-
}
19+
New-Item -ItemType Directory -Path $folder -ErrorAction SilentlyContinue
2320

2421
# Write the contents of each table as CSVs
2522
# TODO: This script takes VERY long to execute. Use threading?

0 commit comments

Comments
 (0)