Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,18 @@
# v0.4.0

* Added several tests
* Added TPMS status (`vehicle.tires.system_status`)
* Added GPS coordinates (`vehicle.position`)
* Added oil life remaining (`vehicle.oil_life_remaining`)
* Added seatbelt status (`vehicle.seatbelts`)
* Refactored tires to include TPMS status for each tire (`vehicle.tires.front_right.status`)

# v0.3.0

* Added `Pressure.bar` unit
* Fix URLs in pyproject.toml
* Fix door logic

# v0.3.0

* Added `Pressure.bar` unit
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,7 @@ Contributions welcome! Please [open an issue](https://github.com/austind/vehicle
nox -r # The -r flag re-uses virtualenvs for faster re-runs
```
1. [Open a pull request](https://github.com/austind/vehiclepass/compare)

## Credits

Based on [IOS shortcuts](https://www.reddit.com/r/f150/comments/s6cs3r/siri_shortcuts_for_fordpass_fixed/) originally developed by [u/d4v3y0rk](https://reddit.com/u/d4v3y0rk) and [/u/tinybtg](https://reddit.com/u/tinybtg), and currently maintained by [Brandon Grant](https://brandongrant.dev/).
21 changes: 9 additions & 12 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,13 @@ name = "vehiclepass"
version = "0.3.0"
description = "A Python API to manage your FordPass-enabled vehicle."
readme = "README.md"
authors = [
{ name = "Austin de Coup-Crank", email = "austindcc@gmail.com" }
]
authors = [{ name = "Austin de Coup-Crank", email = "austindcc@gmail.com" }]
requires-python = ">=3.9"
dependencies = [
"httpx>=0.28.1",
"pydantic>=2.11.1",
"python-dotenv>=1.1.0",
"pydantic>=2.11.1",
"pydantic-extra-types>=2.10.3",
"python-dotenv>=1.1.0",
]

classifiers = [
Expand Down Expand Up @@ -70,17 +69,15 @@ select = [
"D", # pydocstyle
]

exclude = [
".git",
".venv",
"__pycache__",
"build",
"dist",
]
exclude = [".git", ".venv", "__pycache__", "build", "dist"]

[tool.ruff.lint.isort]
known-first-party = ["vehiclepass"]
combine-as-imports = true

[tool.ruff.lint.pydocstyle]
convention = "google"

[tool.ruff.lint.per-file-ignores]
"tests/*" = ["B009"]
"./hack.py" = ["ALL"]
1 change: 1 addition & 0 deletions src/vehiclepass/_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@
VehicleCommand = Literal["remoteStart", "cancelRemoteStart", "lock", "unlock"]
AlarmStatus = Literal["ARMED", "DISARMED"]
GearLeverPosition = Literal["PARK", "REVERSE", "NEUTRAL", "DRIVE", "MANUAL"]
TirePressureStatus = Literal["NORMAL", "LOW", "HIGH"]
3 changes: 3 additions & 0 deletions src/vehiclepass/constants.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Constants."""

import os
from pathlib import Path

from dotenv import load_dotenv

Expand Down Expand Up @@ -51,3 +52,5 @@
f"VEHICLEPASS_DEFAULT_TIME_UNIT: Invalid unit: {DEFAULT_TIME_UNIT}. "
"Valid units are: s, m, h, ms, human_readable"
)

MOCK_RESPONSES_DIR = Path(__file__).parent.parent.parent / "tests" / "fixtures" / "responses"
2 changes: 1 addition & 1 deletion src/vehiclepass/doors.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def __init__(self, vehicle: "Vehicle") -> None:
vehicle: Parent vehicle object

Raises:
StatusError: If status_data is None or empty
None
"""
self._vehicle = vehicle
self._doors = {}
Expand Down
36 changes: 36 additions & 0 deletions src/vehiclepass/seatbelts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
"""Seatbelt status."""

import logging
from typing import TYPE_CHECKING

if TYPE_CHECKING:
from vehiclepass.vehicle import Vehicle

logger = logging.getLogger(__name__)


class SeatBelts:
"""Seatbelt status."""

def __init__(self, vehicle: "Vehicle"):
"""Initialize seatbelt status readings from status data and dynamically create properties.

Args:
vehicle: Parent vehicle object

Raises:
None
"""
self._vehicle = vehicle
self._seatbelts = {}
self._seatbelt_status = self._vehicle._get_metric_value("seatBeltStatus", list)

for seatbelt in self._seatbelt_status:
position = seatbelt.get("vehicleOccupantRole", "").lower()
self._seatbelts[position] = seatbelt["value"]
setattr(self, position, seatbelt["value"])

def __repr__(self) -> str:
"""Return string representation showing available seatbelts."""
positions = list(self._seatbelts.keys())
return f"SeatBelts(seats={positions})"
30 changes: 0 additions & 30 deletions src/vehiclepass/tire_pressure.py

This file was deleted.

83 changes: 83 additions & 0 deletions src/vehiclepass/tires.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
"""Tire pressure reading for all vehicle tires."""

import logging
from typing import TYPE_CHECKING

from vehiclepass._types import TirePressureStatus
from vehiclepass.errors import StatusError
from vehiclepass.units import Pressure

if TYPE_CHECKING:
from vehiclepass.vehicle import Vehicle

logger = logging.getLogger(__name__)


class Tire:
"""Represents tire status for a single tire."""

def __init__(self, vehicle: "Vehicle", tire_position: str, pressure: Pressure, status: TirePressureStatus) -> None:
"""Initialize tire status for a single tire."""
self._vehicle = vehicle
self._tire_position = tire_position
self.pressure = pressure
self.status = status

def __repr__(self):
"""Return string representation showing tire position and pressure."""
return f"Tire(position={self._tire_position}, pressure={self.pressure})"


class Tires:
"""Represents tire status for all vehicle tires."""

def __init__(self, vehicle: "Vehicle") -> None:
"""Initialize tire status from status data and dynamically create properties.

Args:
vehicle: The parent Vehicle object
"""
self._vehicle = vehicle
self._tires = {}
for tire in self._vehicle._get_metric_value("tirePressure", list):
tire_position = tire["vehicleWheel"].lower()
logger.debug("Tire position: %s", tire_position)
self._tires[tire_position] = {"pressure": Pressure.from_kilopascals(tire["value"])}

for tire in self._vehicle._get_metric_value("tirePressureStatus", list):
tire_position = tire["vehicleWheel"].lower()
self._tires[tire_position].update({"status": tire["value"]})

for tire_position, data in self._tires.items():
setattr(
self,
tire_position,
Tire(
self._vehicle,
tire_position,
pressure=data["pressure"],
# Seems to trigger a mypy bug
status=data["status"], # type: ignore
),
)

@property
def system_status(self) -> str:
"""Get the status of the tire pressure monitoring system."""
try:
return self._vehicle._get_metric_value("tirePressureSystemStatus", list)[0]["value"]
except (IndexError, KeyError) as exc:
raise StatusError("Tire pressure monitoring system status not found") from exc

@property
def data(self) -> dict:
"""Get the pressure and status data for all tires."""
return {
tire_position: {"pressure": tire["pressure"].data, "status": tire["status"]}
for tire_position, tire in self._tires.items()
}

def __repr__(self):
"""Return string representation showing available tire positions."""
positions, values = zip(*self._tires.items())
return f"Tires(positions={positions}, values={values})"
Loading