Skip to content

Commit 20f4b02

Browse files
authored
Fix support for map testing (#15)
1 parent d9c86b5 commit 20f4b02

File tree

6 files changed

+106
-68
lines changed

6 files changed

+106
-68
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
__pycache__

python/command_registry.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from enum import Enum
44

55
from .can_frame import CANFrame, CANPacket, CANFrameScanner, CANIDFormat
6-
from .signals import Command, Scaling
6+
from .signals import Command, Enumeration, Scaling, SignalSet
77

88
class ServiceType(Enum):
99
SERVICE_21 = 0x21
@@ -71,12 +71,14 @@ def _extract_service_22_commands(self, can_id: str, data: bytes) -> List[Command
7171
values = {}
7272
remaining_data = data
7373
for signal in command.signals:
74-
if isinstance(signal.format, Scaling):
75-
try:
74+
try:
75+
if isinstance(signal.format, Scaling):
7676
value = signal.format.decode_value(remaining_data)
77-
values[signal.id] = value
78-
except Exception as e:
79-
print(f"Error decoding signal {signal.id}: {e}")
77+
elif isinstance(signal.format, Enumeration):
78+
value = signal.format.decode_value(remaining_data)
79+
values[signal.id] = value
80+
except Exception as e:
81+
print(f"Error decoding signal {signal.id}: {e}")
8082

8183
responses.append(CommandResponse(command, remaining_data, values))
8284

@@ -110,7 +112,10 @@ def _extract_service_21_commands(self, can_id: str, data: bytes) -> List[Command
110112
for signal in command.signals:
111113
if isinstance(signal.format, Scaling):
112114
try:
113-
value = signal.format.decode_value(remaining_data)
115+
if isinstance(signal.format, Scaling):
116+
value = signal.format.decode_value(remaining_data)
117+
elif isinstance(signal.format, Enumeration):
118+
value = signal.format.decode_value(remaining_data)
114119
values[signal.id] = value
115120
except Exception as e:
116121
print(f"Error decoding signal {signal.id}: {e}")

python/signals.py

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,13 +141,21 @@ class EnumerationValue:
141141
@dataclass(frozen=True)
142142
class Enumeration:
143143
bit_length: int
144-
map: Dict[int, EnumerationValue]
144+
map: Dict[str, EnumerationValue]
145145
bit_offset: int = 0
146146

147+
def __hash__(self) -> int:
148+
# Convert the map to a tuple of sorted items to make it hashable
149+
map_items = tuple(sorted(
150+
(k, hash(v))
151+
for k, v in self.map.items()
152+
))
153+
return hash((self.bit_length, map_items, self.bit_offset))
154+
147155
@staticmethod
148156
def from_json(data: Dict) -> 'Enumeration':
149157
value_map = {
150-
k: EnumerationValue(**v) if isinstance(v, dict) else EnumerationValue(str(v), str(v))
158+
str(k): EnumerationValue(**v) if isinstance(v, dict) else EnumerationValue(str(v), str(v))
151159
for k, v in data['map'].items()
152160
}
153161
return Enumeration(
@@ -156,6 +164,22 @@ def from_json(data: Dict) -> 'Enumeration':
156164
map=value_map
157165
)
158166

167+
def decode_value(self, data: bytes) -> str:
168+
"""Decode an enumerated value from bytes."""
169+
# First extract the raw value as an integer
170+
raw_value = 0
171+
for i in range(self.bit_length):
172+
byte_idx = (self.bit_offset + i) // 8
173+
bit_idx = 7 - ((self.bit_offset + i) % 8) # MSB format
174+
if byte_idx < len(data) and (data[byte_idx] & (1 << bit_idx)) != 0:
175+
raw_value |= 1 << (self.bit_length - i - 1)
176+
177+
# Convert to string and look up in map
178+
str_value = str(raw_value)
179+
if str_value in self.map:
180+
return self.map[str_value].value
181+
return str_value # Return raw value as string if no mapping exists
182+
159183
@dataclass(frozen=True)
160184
class Signal:
161185
id: str

python/signals_testing.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import pytest
2-
from typing import Dict, Any, Optional
2+
from typing import Any, Dict, Optional, Union
33

44
from .can_frame import CANIDFormat
55
from .command_registry import decode_obd_response
@@ -8,7 +8,7 @@
88
def obd_testrunner(
99
signalset_json: str,
1010
response_hex: str,
11-
expected_values: Dict[str, float],
11+
expected_values: Dict[str, Union[float, str]], # Updated type hint
1212
can_id_format: CANIDFormat = CANIDFormat.ELEVEN_BIT,
1313
extended_addressing_enabled: Optional[bool] = None
1414
):
@@ -17,7 +17,7 @@ def obd_testrunner(
1717
Args:
1818
signalset_json: JSON string containing the signal set definition
1919
response_hex: Hex string of the OBD response
20-
expected_values: Dictionary mapping signal IDs to their expected values
20+
expected_values: Dictionary mapping signal IDs to their expected values (numbers or strings)
2121
"""
2222
signalset = SignalSet.from_json(signalset_json)
2323
actual_values = decode_obd_response(
@@ -31,5 +31,9 @@ def obd_testrunner(
3131
for signal_id, expected_value in expected_values.items():
3232
assert signal_id in actual_values, f"Signal {signal_id} not found in decoded response"
3333
actual_value = actual_values[signal_id]
34-
assert pytest.approx(actual_value) == expected_value, \
35-
f"Signal {signal_id} value mismatch: got {actual_value}, expected {expected_value}"
34+
if isinstance(expected_value, (int, float)):
35+
assert pytest.approx(actual_value) == expected_value, \
36+
f"Signal {signal_id} value mismatch: got {actual_value}, expected {expected_value}"
37+
else:
38+
assert actual_value == expected_value, \
39+
f"Signal {signal_id} value mismatch: got {actual_value}, expected {expected_value}"

python/test_f150.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77
REPO_ROOT = os.path.abspath(os.path.dirname(__file__))
88

99
TEST_CASES = [
10+
# 2024 model years
11+
# Gear
12+
("7E804621E1203", {"F150_GEAR": "3"}),
13+
1014
# 2021 model year
1115
# Target vehicle speed - -61.2 km/h
1216
("76C0562A224FF56", {"F150_CC_TGT_VSS": -61.2}),

python/testdata/ford-f-150.json

Lines changed: 54 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,55 @@
11
{ "commands": [
2-
{ "hdr": "720", "rax": "728", "cmd": {"22": "404C"}, "freq": 5,
3-
"signals": [
4-
{"id": "F150_ODO", "path": "Trips", "fmt": { "len": 24, "max": 1677721, "div": 10, "unit": "kilometers" }, "name": "Odometer (Instrument panel)", "suggestedMetric": "odometer"}
5-
]},
6-
{ "hdr": "726", "rax": "72E", "cmd": {"22": "2813"}, "freq": 5,
7-
"signals": [
8-
{"id": "F150_TP_FL", "path": "Tires", "fmt": { "len": 16, "max": 3276, "div": 20, "unit": "psi" }, "name": "Front left tire pressure", "suggestedMetric": "frontLeftTirePressure"}
9-
]},
10-
{ "hdr": "726", "rax": "72E", "cmd": {"22": "2814"}, "freq": 5,
11-
"signals": [
12-
{"id": "F150_TP_FR", "path": "Tires", "fmt": { "len": 16, "max": 3276, "div": 20, "unit": "psi" }, "name": "Front right tire pressure", "suggestedMetric": "frontRightTirePressure"}
13-
]},
14-
{ "hdr": "726", "rax": "72E", "cmd": {"22": "2815"}, "freq": 5,
15-
"signals": [
16-
{"id": "F150_TP_RRO", "path": "Tires", "fmt": { "len": 16, "max": 3276, "div": 20, "unit": "psi" }, "name": "Rear right outer tire pressure", "suggestedMetric": "rearRightTirePressure"}
17-
]},
18-
{ "hdr": "726", "rax": "72E", "cmd": {"22": "2816"}, "freq": 5,
19-
"signals": [
20-
{"id": "F150_TP_RLO", "path": "Tires", "fmt": { "len": 16, "max": 3276, "div": 20, "unit": "psi" }, "name": "Rear left outer tire pressure", "suggestedMetric": "rearLeftTirePressure"}
21-
]},
22-
{ "hdr": "726", "rax": "72E", "cmd": {"22": "2817"}, "freq": 5,
23-
"signals": [
24-
{"id": "F150_TP_RRI", "path": "Tires", "fmt": { "len": 16, "max": 3276, "div": 20, "unit": "psi" }, "name": "Rear right inner tire pressure"}
25-
]},
26-
{ "hdr": "726", "rax": "72E", "cmd": {"22": "2818"}, "freq": 5,
27-
"signals": [
28-
{"id": "F150_TP_RLI", "path": "Tires", "fmt": { "len": 16, "max": 3276, "div": 20, "unit": "psi" }, "name": "Rear left inner tire pressure"}
29-
]},
30-
{ "hdr": "760", "rax": "768", "cmd": {"22": "3201"}, "freq": 0.25,
31-
"signals": [
32-
{"id": "F150_STEER_ANGLE", "path": "Control", "fmt": { "len": 16, "max": 1638, "min": -1638, "div": 20, "add": -1638.4, "unit": "degrees" }, "name": "Steering wheel angle (ABS)", "description": "Positive is rotated to the left, negative is rotated to the right."}
33-
]},
34-
{ "hdr": "764", "cmd": {"22": "A224"}, "freq": 0.25,
35-
"signals": [
36-
{"id": "F150_CC_TGT_VSS", "path": "Cruise control", "fmt": { "len": 16, "max": 736, "min": -736, "mul": 3.6, "div": 10, "sign": true, "unit": "kilometersPerHour" }, "name": "Target vehicle speed", "description": "The speed of the vehicle ahead of you."}
37-
]},
38-
{ "hdr": "7DF", "cmd": {"22": "1E12"}, "freq": 2,
39-
"signals": [
40-
{"id": "F150_GEAR", "path": "Engine", "fmt": { "len": 8, "max": 255, "unit": "scalar" }, "name": "Current gear", "description": "The automatic transmission gear. Numbers aren't yet mapped to named gears."}
41-
]},
42-
{ "hdr": "7DF", "cmd": {"22": "1E1C"}, "freq": 1,
43-
"signals": [
44-
{"id": "F150_TOT", "path": "Engine", "fmt": { "len": 16, "max": 255, "div": 16, "unit": "celsius" }, "name": "Transmission oil temperature"}
45-
]},
46-
{ "hdr": "7DF", "cmd": {"22": "1E23"}, "freq": 2,
47-
"signals": [
48-
{"id": "F150_GEAR_SHFT", "path": "Engine", "fmt": { "len": 8, "max": 255, "unit": "scalar" }, "name": "Shift gear"}
49-
]},
50-
{ "hdr": "7E0", "rax": "7E8", "cmd": {"22": "F42F"}, "freq": 1,
51-
"signals": [
52-
{"id": "F150_FLI", "path": "Fuel", "fmt": { "len": 8, "max": 255, "mul": 100, "div": 255, "unit": "percent" }, "name": "Fuel level", "suggestedMetric": "fuelTankLevel"}
53-
]}
54-
]
55-
}
2+
{ "hdr": "720", "rax": "728", "cmd": {"22": "404C"}, "freq": 5,
3+
"signals": [
4+
{"id": "F150_ODO", "path": "Trips", "fmt": { "len": 24, "max": 1677721, "div": 10, "unit": "kilometers" }, "name": "Odometer (Instrument panel)", "suggestedMetric": "odometer"}
5+
]},
6+
{ "hdr": "726", "rax": "72E", "cmd": {"22": "2813"}, "freq": 5,
7+
"signals": [
8+
{"id": "F150_TP_FL", "path": "Tires", "fmt": { "len": 16, "max": 3276, "div": 20, "unit": "psi" }, "name": "Front left tire pressure", "suggestedMetric": "frontLeftTirePressure"}
9+
]},
10+
{ "hdr": "726", "rax": "72E", "cmd": {"22": "2814"}, "freq": 5,
11+
"signals": [
12+
{"id": "F150_TP_FR", "path": "Tires", "fmt": { "len": 16, "max": 3276, "div": 20, "unit": "psi" }, "name": "Front right tire pressure", "suggestedMetric": "frontRightTirePressure"}
13+
]},
14+
{ "hdr": "726", "rax": "72E", "cmd": {"22": "2815"}, "freq": 5,
15+
"signals": [
16+
{"id": "F150_TP_RRO", "path": "Tires", "fmt": { "len": 16, "max": 3276, "div": 20, "unit": "psi" }, "name": "Rear right outer tire pressure", "suggestedMetric": "rearRightTirePressure"}
17+
]},
18+
{ "hdr": "726", "rax": "72E", "cmd": {"22": "2816"}, "freq": 5,
19+
"signals": [
20+
{"id": "F150_TP_RLO", "path": "Tires", "fmt": { "len": 16, "max": 3276, "div": 20, "unit": "psi" }, "name": "Rear left outer tire pressure", "suggestedMetric": "rearLeftTirePressure"}
21+
]},
22+
{ "hdr": "726", "rax": "72E", "cmd": {"22": "2817"}, "freq": 5,
23+
"signals": [
24+
{"id": "F150_TP_RRI", "path": "Tires", "fmt": { "len": 16, "max": 3276, "div": 20, "unit": "psi" }, "name": "Rear right inner tire pressure"}
25+
]},
26+
{ "hdr": "726", "rax": "72E", "cmd": {"22": "2818"}, "freq": 5,
27+
"signals": [
28+
{"id": "F150_TP_RLI", "path": "Tires", "fmt": { "len": 16, "max": 3276, "div": 20, "unit": "psi" }, "name": "Rear left inner tire pressure"}
29+
]},
30+
{ "hdr": "760", "rax": "768", "cmd": {"22": "3201"}, "freq": 0.25,
31+
"signals": [
32+
{"id": "F150_STEER_ANGLE", "path": "Control", "fmt": { "len": 16, "max": 1638, "min": -1638, "div": 20, "add": -1638.4, "unit": "degrees" }, "name": "Steering wheel angle (ABS)", "description": "Positive is rotated to the left, negative is rotated to the right."}
33+
]},
34+
{ "hdr": "764", "cmd": {"22": "A224"}, "freq": 0.25,
35+
"signals": [
36+
{"id": "F150_CC_TGT_VSS", "path": "Cruise control", "fmt": { "len": 16, "max": 736, "min": -736, "mul": 3.6, "div": 10, "sign": true, "unit": "kilometersPerHour" }, "name": "Target vehicle speed", "description": "The speed of the vehicle ahead of you."}
37+
]},
38+
{ "hdr": "7DF", "cmd": {"22": "1E1C"}, "freq": 1,
39+
"signals": [
40+
{"id": "F150_TOT", "path": "Engine", "fmt": { "len": 16, "max": 255, "div": 16, "unit": "celsius" }, "name": "Transmission oil temperature"}
41+
]},
42+
{ "hdr": "7DF", "cmd": {"22": "1E23"}, "freq": 2,
43+
"signals": [
44+
{"id": "F150_GEAR_SHFT", "path": "Engine", "fmt": { "len": 8, "max": 255, "unit": "scalar" }, "name": "Shift gear"}
45+
]},
46+
{ "hdr": "7E0", "rax": "7E8", "cmd": {"22": "1E12"}, "freq": 2,
47+
"signals": [
48+
{"id": "F150_GEAR", "path": "Engine", "fmt": { "len": 8, "map": {"1":{"description":"First gear","value":"1"},"10":{"description":"Manual","value":"Manul"},"2":{"description":"Second gear","value":"2"},"3":{"description":"Third gear","value":"3"},"4":{"description":"Fourth gear","value":"4"},"46":{"description":"Drive","value":"DRIVE"},"5":{"description":"Fifth gear","value":"5"},"50":{"description":"Neutral","value":"NEUTRAL"},"6":{"description":"Sixth gear","value":"6"},"60":{"description":"Reverse","value":"REVERSE"},"7":{"description":"Seventh gear","value":"7"},"70":{"description":"Park","value":"PARK"},"8":{"description":"Eighth gear","value":"8"},"9":{"description":"Ninth gear","value":"9"}} }, "name": "Current gear", "description": "The automatic transmission gear."}
49+
]},
50+
{ "hdr": "7E0", "rax": "7E8", "cmd": {"22": "F42F"}, "freq": 1,
51+
"signals": [
52+
{"id": "F150_FLI", "path": "Fuel", "fmt": { "len": 8, "max": 255, "mul": 100, "div": 255, "unit": "percent" }, "name": "Fuel level", "suggestedMetric": "fuelTankLevel"}
53+
]}
54+
]
55+
}

0 commit comments

Comments
 (0)