STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
rename_hashes.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2"""
3rename_hashes.py
4Renames legacy numeric Elodin CSV files to their actual string component names.
5Elodin DB uses a 64-bit FNV-1a hash algorithm for component string names.
6Before the ControllerService was updated to broadcast set_component_name packets,
7the database exported CONTROLLER fields exclusively under their hash numbers.
8This script computes the FNV-1a hash of all known controller variables and renames
9matching files in the given export directory.
10"""
11
12import sys
13import os
14from pathlib import Path
15
16
17def fnv1a_64(data: bytes) -> int:
18 hash_val = 0xCBF29CE484222325
19 prime = 0x100000001B3
20 for byte in data:
21 hash_val ^= byte
22 hash_val = (hash_val * prime) & 0xFFFFFFFFFFFFFFFF
23 return hash_val
24
25
26def main():
27 if len(sys.argv) < 2:
28 print("Usage: ./rename_hashes.py <export_dir>")
29 sys.exit(1)
30
31 export_dir = Path(sys.argv[1])
32 if not export_dir.exists():
33 print(f"Error: Directory {export_dir} not found.")
34 sys.exit(1)
35
36 # All known controller fields that might be missing their names in old runs.
37 controller_fields = [
38 "CONTROLLER.actuation.timestamp_ns",
39 "CONTROLLER.actuation.duty_F",
40 "CONTROLLER.actuation.duty_O",
41 "CONTROLLER.actuation.u_F_on",
42 "CONTROLLER.actuation.u_O_on",
43 "CONTROLLER.actuation.valid",
44 "CONTROLLER.diagnostics.timestamp_ns",
45 "CONTROLLER.diagnostics.F_ref",
46 "CONTROLLER.diagnostics.MR_ref",
47 "CONTROLLER.diagnostics.F_estimated",
48 "CONTROLLER.diagnostics.MR_estimated",
49 "CONTROLLER.diagnostics.P_ch",
50 "CONTROLLER.diagnostics.cost",
51 "CONTROLLER.diagnostics.solver_iters",
52 "CONTROLLER.diagnostics.safety_filtered",
53 "CONTROLLER.diagnostics.cutoff_active",
54 "CONTROLLER.measurement.timestamp_ns",
55 "CONTROLLER.measurement.P_copv",
56 "CONTROLLER.measurement.P_reg",
57 "CONTROLLER.measurement.P_u_fuel",
58 "CONTROLLER.measurement.P_u_ox",
59 "CONTROLLER.measurement.P_d_fuel",
60 "CONTROLLER.measurement.P_d_ox",
61 "CONTROLLER.measurement.P_ch_mp1",
62 "CONTROLLER.measurement.P_ch_mp2",
63 "CONTROLLER.state.timestamp_ns",
64 "CONTROLLER.state.from_state",
65 "CONTROLLER.state.to_state",
66 "CONTROLLER.state.reason",
67 "CONTROLLER.fire.timestamp_ns",
68 "CONTROLLER.fire.fire_active",
69 "CONTROLLER.fire.duty_F",
70 "CONTROLLER.fire.duty_O",
71 ]
72
73 # Pre-compute hashes mapping to names
74 hash_to_name = {}
75 for name in controller_fields:
76 h = fnv1a_64(name.encode("utf-8"))
77 # The FNV hash in rust might be signed or unsigned matching.
78 # Python computes unsigned 64-bit int. Elodin exports it as a signed or unsigned string.
79 # Let's map both strings just in case!
80 unsigned_str = str(h)
81 signed_h = h - (1 << 64) if h >= (1 << 63) else h
82 signed_str = str(signed_h)
83 hash_to_name[unsigned_str] = name
84 hash_to_name[signed_str] = name
85
86 renamed_count = 0
87 for file_path in export_dir.glob("*.csv"):
88 filename = file_path.stem # e.g. "1399096562196883797"
89 if filename in hash_to_name:
90 real_name = hash_to_name[filename]
91 new_path = export_dir / f"{real_name}.csv"
92
93 # Read and rename the column inside the CSV too!
94 # Since elodin-db flatten makes the column name the hash as well.
95 try:
96 content = file_path.read_text(encoding="utf-8")
97 lines = content.splitlines()
98 if len(lines) > 0:
99 header = lines[0]
100 # Replace the hash with the real name in the header
101 new_header = header.replace(filename, real_name)
102 lines[0] = new_header
103
104 new_path.write_text("\n".join(lines) + "\n", encoding="utf-8")
105 file_path.unlink() # delete old file
106 print(f"✅ Renamed {filename}.csv -> {real_name}.csv")
107 renamed_count += 1
108 except Exception as e:
109 print(f"Failed to process {file_path.name}: {e}")
110
111 print(f"\nSuccessfully recovered {renamed_count} controller variables!")
112
113
114if __name__ == "__main__":
115 main()
int fnv1a_64(bytes data)