Skip to content

Commit 668bdd0

Browse files
authored
feat: SDK support for new diagnostics endpoints
feat: SDK support for new diagnostics endpoints
2 parents fa8dc9c + 9535000 commit 668bdd0

File tree

6 files changed

+154
-0
lines changed

6 files changed

+154
-0
lines changed

REFERENCE.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,42 @@ Returns a list of all the service records performed on the vehicle, filtered by
383383

384384
---
385385

386+
### `diagnostic_system_status(self)`
387+
388+
Retrieve the status of various diagnostic systems in the vehicle.
389+
390+
#### Return
391+
392+
| Value | Type | Description |
393+
| :---------------------------- | :--------------------- | :------------------------------------------------------------------- |
394+
| `DiagnosticSystemStatus` | typing.NamedTuple | The returned object with diagnostic system statuses data |
395+
| `DiagnosticSystemStatus.systems` | List[Dict] | List of system statuses, each with `system_id`, `status`, and `description` |
396+
| `DiagnosticSystemStatus.meta` | collections.namedtuple | Smartcar response headers (`request_id`, `data_age`, and/or `unit_system`) |
397+
398+
Each system entry contains:
399+
- `system_id` (String): Unique identifier for the system.
400+
- `status` (String): Status of the system, either "OK" or "ALERT".
401+
- `description` (String, optional): Additional context or description for the status, if any.
402+
403+
---
404+
405+
### `diagnostic_trouble_codes(self)`
406+
407+
Retrieve active diagnostic trouble codes (DTCs) for the vehicle.
408+
409+
#### Return
410+
411+
| Value | Type | Description |
412+
| :------------------------- | :--------------------- | :------------------------------------------------------- |
413+
| `DiagnosticTroubleCodes` | typing.NamedTuple | The returned object with active diagnostic trouble codes |
414+
| `DiagnosticTroubleCodes.active_codes` | List[Dict] | List of active DTCs, each with `code` and `timestamp` |
415+
| `DiagnosticTroubleCodes.meta` | collections.namedtuple | Smartcar response headers (`request_id`, `data_age`, and/or `unit_system`) |
416+
417+
Each trouble code entry contains:
418+
- `code` (String): The DTC code representing the issue.
419+
- `timestamp` (String, optional): ISO 8601 timestamp when the code was triggered. May be `null` if unavailable.
420+
421+
---
386422
### `attributes(self)`
387423

388424
Returns a single vehicle object, containing identifying information.

smartcar/helpers.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,8 @@ def format_path_and_attribute_for_batch(raw_path: str) -> tuple:
121121
"tires/pressure": "tire_pressure",
122122
"": "attributes",
123123
"security": "lock_status",
124+
"diagnostics/system_status": "diagnostic_system_status",
125+
"diagnostics/dtcs": "diagnostic_trouble_codes",
124126
}
125127
formatted_path = raw_path[1:] if raw_path[0] == "/" else raw_path
126128
formatted_attribute = mapper.get(formatted_path, formatted_path)

smartcar/types.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,25 @@ def format_capabilities(capabilities_list: List[dict]) -> List[Capability]:
186186

187187
ChargeLimit = NamedTuple("ChargeLimit", [("limit", float), ("meta", namedtuple)])
188188

189+
DiagnosticSystem = NamedTuple(
190+
"DiagnosticSystem",
191+
[("system_id", str), ("status", str), ("description", Optional[str])],
192+
)
193+
194+
DiagnosticSystemStatus = NamedTuple(
195+
"DiagnosticSystemStatus",
196+
[("systems", List[DiagnosticSystem]), ("meta", namedtuple)],
197+
)
198+
199+
DiagnosticTroubleCode = NamedTuple(
200+
"DiagnosticTroubleCode", [("code", str), ("timestamp", Optional[datetime.datetime])]
201+
)
202+
203+
DiagnosticTroubleCodes = NamedTuple(
204+
"DiagnosticTroubleCodes",
205+
[("active_codes", List[DiagnosticTroubleCode]), ("meta", namedtuple)],
206+
)
207+
189208

190209
class ServiceCost:
191210
total_cost: Optional[float] = None
@@ -431,6 +450,24 @@ def select_named_tuple(path: str, response_or_dict) -> NamedTuple:
431450
elif path == "service/history":
432451
return ServiceHistory(data, headers)
433452

453+
elif path == "diagnostics/system_status":
454+
systems = [
455+
DiagnosticSystem(
456+
system_id=item["systemId"],
457+
status=item["status"],
458+
description=item.get("description"),
459+
)
460+
for item in data["systems"]
461+
]
462+
return DiagnosticSystemStatus(systems=systems, meta=headers)
463+
464+
elif path == "diagnostics/dtcs":
465+
active_codes = [
466+
DiagnosticTroubleCode(code=item["code"], timestamp=item.get("timestamp"))
467+
for item in data["activeCodes"]
468+
]
469+
return DiagnosticTroubleCodes(active_codes=active_codes, meta=headers)
470+
434471
elif path == "permissions":
435472
return Permissions(
436473
data["permissions"],

smartcar/vehicle.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,44 @@ def service_history(
219219
response = helpers.requester("GET", url, headers=headers, params=params)
220220
return types.select_named_tuple(path, response)
221221

222+
def diagnostic_system_status(self) -> types.DiagnosticSystemStatus:
223+
"""
224+
GET Vehicle.diagnostic_system_status
225+
226+
Returns:
227+
DiagnosticSystemStatus: NamedTuple("DiagnosticSystemStatus", [
228+
("systems", List[DiagnosticSystem]),
229+
("meta", namedtuple)
230+
])
231+
232+
Raises:
233+
SmartcarException
234+
"""
235+
path = "diagnostics/system_status"
236+
url = self._format_url(path)
237+
headers = self._get_headers()
238+
response = helpers.requester("GET", url, headers=headers)
239+
return types.select_named_tuple(path, response)
240+
241+
def diagnostic_trouble_codes(self) -> types.DiagnosticTroubleCodes:
242+
"""
243+
GET Vehicle.diagnostic_trouble_codes
244+
245+
Returns:
246+
DiagnosticTroubleCodes: NamedTuple("DiagnosticTroubleCodes", [
247+
("active_codes", List[DiagnosticTroubleCode]),
248+
("meta", namedtuple)
249+
])
250+
251+
Raises:
252+
SmartcarException
253+
"""
254+
path = "diagnostics/dtcs"
255+
url = self._format_url(path)
256+
headers = self._get_headers()
257+
response = helpers.requester("GET", url, headers=headers)
258+
return types.select_named_tuple(path, response)
259+
222260
def location(self) -> types.Location:
223261
"""
224262
GET Vehicle.location

tests/conftest.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@ def access_ford(client):
163163
"required:control_charge",
164164
"control_navigation",
165165
"read_service_history",
166+
"read_diagnostics",
166167
]
167168
),
168169
"FORD",

tests/e2e/test_vehicle.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,46 @@ def test_service_history(ford_car):
170170
assert response._fields == ("items", "meta")
171171

172172

173+
def test_diagnostic_system_status(ford_car):
174+
diagnostic_status = ford_car.diagnostic_system_status()
175+
assert diagnostic_status is not None
176+
assert isinstance(diagnostic_status, types.DiagnosticSystemStatus)
177+
assert diagnostic_status._fields == ("systems", "meta")
178+
179+
for system in diagnostic_status.systems:
180+
assert isinstance(system, types.DiagnosticSystem)
181+
182+
183+
def test_diagnostic_trouble_codes(ford_car):
184+
dtc_response = ford_car.diagnostic_trouble_codes()
185+
assert dtc_response is not None
186+
assert isinstance(dtc_response, types.DiagnosticTroubleCodes)
187+
assert dtc_response._fields == ("active_codes", "meta")
188+
189+
for code in dtc_response.active_codes:
190+
assert isinstance(code, types.DiagnosticTroubleCode)
191+
192+
193+
def test_batch_diagnostics(ford_car):
194+
batch_response = ford_car.batch(["/diagnostics/system_status", "/diagnostics/dtcs"])
195+
assert batch_response is not None
196+
assert batch_response._fields == (
197+
"diagnostic_system_status",
198+
"diagnostic_trouble_codes",
199+
"meta",
200+
)
201+
202+
diagnostic_status = batch_response.diagnostic_system_status()
203+
assert diagnostic_status is not None
204+
for system in diagnostic_status.systems:
205+
assert isinstance(system, types.DiagnosticSystem)
206+
207+
dtc_response = batch_response.diagnostic_trouble_codes()
208+
assert dtc_response is not None
209+
for code in dtc_response.active_codes:
210+
assert isinstance(code, types.DiagnosticTroubleCode)
211+
212+
173213
def test_batch_success(chevy_volt):
174214
batch = chevy_volt.batch(
175215
[

0 commit comments

Comments
 (0)