server: replace carconnectivity with volkswagencarnet
Switches the VW API backend from carconnectivity + connector-volkswagen to volkswagencarnet (local fork with the cariad-hybrid-auth-fix patch applied), fixing authentication against the current CARIAD BFF endpoints. - requirements.txt: point at local volkswagencarnet git repo via git+file:// URL (editable path no longer needed now the patch is committed there) - we_connect.py: rewrite around the async volkswagencarnet Connection class; a persistent asyncio event loop held in _Session lets the rest of the code stay synchronous - data_model.py: remap all extractors to volkswagencarnet Vehicle properties and Paths constants; output shape preserved so apply_procedural and the GUI client continue to work unchanged - collect.py: swap carconnectivity error import, drop TemporaryAuthenticationError (no equivalent), fix vehicle.vin.value → vehicle.vin Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+5
-5
@@ -10,7 +10,7 @@ from .data_model import ALL_DOMAINS, collect_snapshot, apply_procedural
|
|||||||
from .storage_helpers import auto_out_path, load_last_24h_records, load_store, save_store
|
from .storage_helpers import auto_out_path, load_last_24h_records, load_store, save_store
|
||||||
from jaydiff.diff import diff_full as jay_diff_full
|
from jaydiff.diff import diff_full as jay_diff_full
|
||||||
|
|
||||||
from carconnectivity.errors import AuthenticationError, TemporaryAuthenticationError
|
from volkswagencarnet.vw_exceptions import AuthenticationError
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -26,18 +26,18 @@ def run(args: argparse.Namespace) -> None:
|
|||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
if args.dry:
|
if args.dry:
|
||||||
log.info("Dry-run mode — skipping CarConnectivity login")
|
log.info("Dry-run mode — skipping volkswagencarnet login")
|
||||||
wc = None
|
wc = None
|
||||||
vehicle = None
|
vehicle = None
|
||||||
vin = args.vin or "UNKNOWN"
|
vin = args.vin or "UNKNOWN"
|
||||||
else:
|
else:
|
||||||
log.info("Connecting via CarConnectivity…")
|
log.info("Connecting via volkswagencarnet…")
|
||||||
wc = we_connect.connect(args.username, args.password)
|
wc = we_connect.connect(args.username, args.password)
|
||||||
vehicle, err = we_connect.select_vehicle(wc, args.vin)
|
vehicle, err = we_connect.select_vehicle(wc, args.vin)
|
||||||
if err:
|
if err:
|
||||||
log.error(err)
|
log.error(err)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
vin = vehicle.vin.value
|
vin = vehicle.vin
|
||||||
|
|
||||||
log.info("Vehicle: %s", vin)
|
log.info("Vehicle: %s", vin)
|
||||||
|
|
||||||
@@ -98,7 +98,7 @@ def run(args: argparse.Namespace) -> None:
|
|||||||
log.info("Record #%d saved at %s", len(store["records"]), snapshot["ts"])
|
log.info("Record #%d saved at %s", len(store["records"]), snapshot["ts"])
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
raise
|
raise
|
||||||
except (AuthenticationError, TemporaryAuthenticationError):
|
except AuthenticationError:
|
||||||
log.warning("Authentication error — will retry at next interval")
|
log.warning("Authentication error — will retry at next interval")
|
||||||
except Exception:
|
except Exception:
|
||||||
log.exception("Collection failed — will retry at next interval")
|
log.exception("Collection failed — will retry at next interval")
|
||||||
|
|||||||
+169
-105
@@ -2,68 +2,83 @@ import logging
|
|||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
|
from volkswagencarnet.vw_const import Paths
|
||||||
|
from volkswagencarnet.vw_utilities import find_path
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
# ── value helpers ─────────────────────────────────────────────────────────────
|
# ── value helpers ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
def _str(attr) -> str:
|
|
||||||
"""Return the string value of a CarConnectivity attribute."""
|
|
||||||
return str(attr)
|
|
||||||
|
|
||||||
|
|
||||||
def _phys(value, unit: str) -> dict:
|
def _phys(value, unit: str) -> dict:
|
||||||
return {"value": value, "unit": unit}
|
return {"value": value, "unit": unit}
|
||||||
|
|
||||||
|
|
||||||
def _remaining_min(estimated_date_reached_attr) -> float | None:
|
|
||||||
"""Compute remaining minutes from an estimated_date_reached DateAttribute."""
|
|
||||||
edr = estimated_date_reached_attr.value
|
|
||||||
if edr is None:
|
|
||||||
return None
|
|
||||||
return max(0.0, (edr - datetime.now(timezone.utc)).total_seconds() / 60)
|
|
||||||
|
|
||||||
|
|
||||||
# ── domain extractors ─────────────────────────────────────────────────────────
|
# ── domain extractors ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
def extract_charging(vehicle) -> dict | None:
|
def extract_charging(vehicle) -> dict | None:
|
||||||
result: dict = {}
|
result: dict = {}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ch = vehicle.charging
|
result["state"] = vehicle.charging_state
|
||||||
result["state"] = _str(ch.state)
|
|
||||||
result["type"] = _str(ch.type)
|
|
||||||
try:
|
|
||||||
result["power"] = _phys(ch.power.value, "kW")
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
rem = _remaining_min(ch.estimated_date_reached)
|
|
||||||
if rem is not None:
|
|
||||||
result["remain"] = _phys(round(rem), "min")
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
log.debug("vehicle.charging unavailable", exc_info=True)
|
log.debug("charging.state unavailable", exc_info=True)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
cfg = vehicle.charging.settings
|
if vehicle.is_charger_type_supported:
|
||||||
result["settings"] = {
|
result["type"] = vehicle.charger_type
|
||||||
"target_level": _phys(cfg.target_level.value, "%"),
|
|
||||||
"maximum_current": _phys(cfg.maximum_current.value, "A"),
|
|
||||||
"auto_unlock": _str(cfg.auto_unlock),
|
|
||||||
}
|
|
||||||
except Exception:
|
except Exception:
|
||||||
log.debug("vehicle.charging.settings unavailable", exc_info=True)
|
pass
|
||||||
|
|
||||||
try:
|
try:
|
||||||
conn = vehicle.charging.connector
|
power = vehicle.charging_power
|
||||||
result["plugStatus"] = {
|
if power is not None:
|
||||||
"connection_state": _str(conn.connection_state),
|
result["power"] = _phys(power, "kW")
|
||||||
"lock_state": _str(conn.lock_state),
|
except Exception:
|
||||||
"external_power": _str(conn.external_power),
|
log.debug("charging.power unavailable", exc_info=True)
|
||||||
}
|
|
||||||
|
try:
|
||||||
|
remain = vehicle.charging_time_left
|
||||||
|
if remain is not None:
|
||||||
|
result["remain"] = _phys(remain, "min")
|
||||||
|
except Exception:
|
||||||
|
log.debug("charging.remain unavailable", exc_info=True)
|
||||||
|
|
||||||
|
settings: dict = {}
|
||||||
|
try:
|
||||||
|
target = vehicle.battery_target_charge_level
|
||||||
|
if target is not None:
|
||||||
|
settings["target_level"] = _phys(target, "%")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
max_a = vehicle.charge_max_ac_ampere
|
||||||
|
if max_a is not None:
|
||||||
|
settings["maximum_current"] = _phys(max_a, "A")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
auto_unlock = vehicle.auto_release_ac_connector_state
|
||||||
|
if auto_unlock is not None:
|
||||||
|
settings["auto_unlock"] = auto_unlock
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
if settings:
|
||||||
|
result["settings"] = settings
|
||||||
|
|
||||||
|
try:
|
||||||
|
plug: dict = {}
|
||||||
|
conn_state = find_path(vehicle.attrs, Paths.PLUG_CONN)
|
||||||
|
if conn_state is not None:
|
||||||
|
plug["connection_state"] = conn_state
|
||||||
|
lock_state = find_path(vehicle.attrs, Paths.PLUG_LOCK)
|
||||||
|
if lock_state is not None:
|
||||||
|
plug["lock_state"] = lock_state
|
||||||
|
ext_pwr = find_path(vehicle.attrs, Paths.PLUG_EXT_PWR)
|
||||||
|
if ext_pwr is not None:
|
||||||
|
plug["external_power"] = ext_pwr
|
||||||
|
if plug:
|
||||||
|
result["plugStatus"] = plug
|
||||||
except Exception:
|
except Exception:
|
||||||
log.debug("plugStatus unavailable", exc_info=True)
|
log.debug("plugStatus unavailable", exc_info=True)
|
||||||
|
|
||||||
@@ -74,40 +89,39 @@ def extract_climatisation(vehicle) -> dict | None:
|
|||||||
result: dict = {}
|
result: dict = {}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
cl = vehicle.climatization
|
state = vehicle.climatisation_state
|
||||||
result["state"] = _str(cl.state)
|
if state is not None:
|
||||||
try:
|
result["state"] = state
|
||||||
rem = _remaining_min(cl.estimated_date_reached)
|
|
||||||
if rem is not None:
|
|
||||||
result["remain"] = _phys(round(rem), "min")
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
except Exception:
|
except Exception:
|
||||||
log.debug("vehicle.climatization unavailable", exc_info=True)
|
log.debug("climatisation.state unavailable", exc_info=True)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
cs = vehicle.climatization.settings
|
remain = vehicle.electric_remaining_climatisation_time
|
||||||
result["settings"] = {
|
if remain is not None:
|
||||||
"target_temperature": _phys(cs.target_temperature.value, "degC"),
|
result["remain"] = _phys(remain, "min")
|
||||||
}
|
|
||||||
except Exception:
|
except Exception:
|
||||||
log.debug("climatization.settings unavailable", exc_info=True)
|
pass
|
||||||
|
|
||||||
try:
|
try:
|
||||||
wh = vehicle.window_heatings
|
target = vehicle.climatisation_target_temperature
|
||||||
result["window_heatings"] = {
|
if target is not None:
|
||||||
name: _str(win.heating_state)
|
result["settings"] = {"target_temperature": _phys(target, "degC")}
|
||||||
for name, win in wh.heatings.items()
|
|
||||||
}
|
|
||||||
except Exception:
|
except Exception:
|
||||||
log.debug("vehicle.window_heatings unavailable", exc_info=True)
|
log.debug("climatisation.settings unavailable", exc_info=True)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
result["environment"] = {
|
window_status = find_path(vehicle.attrs, Paths.CLIMATISATION_WINDOW_HEATING_STATUS)
|
||||||
"temperature_outside": _phys(vehicle.outside_temperature.value, "degC"),
|
if window_status:
|
||||||
}
|
heatings: dict = {}
|
||||||
|
for entry in window_status:
|
||||||
|
loc = entry.get("windowLocation")
|
||||||
|
state = entry.get("windowHeatingState")
|
||||||
|
if loc and state is not None:
|
||||||
|
heatings[loc] = state
|
||||||
|
if heatings:
|
||||||
|
result["window_heatings"] = heatings
|
||||||
except Exception:
|
except Exception:
|
||||||
log.debug("vehicle.outside_temperature unavailable", exc_info=True)
|
log.debug("window_heatings unavailable", exc_info=True)
|
||||||
|
|
||||||
return result or None
|
return result or None
|
||||||
|
|
||||||
@@ -116,23 +130,45 @@ def extract_electric_drive(vehicle) -> dict | None:
|
|||||||
result: dict = {}
|
result: dict = {}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ed = vehicle.get_electric_drive()
|
rng = vehicle.electric_range
|
||||||
result["range"] = _phys(ed.range.value, "km")
|
if rng is not None:
|
||||||
result["range_full"] = _phys(round(float(ed.range_estimated_full.value), 1), "km")
|
result["range"] = _phys(rng, "km")
|
||||||
try:
|
|
||||||
bat = ed.battery
|
|
||||||
result["battery"] = {
|
|
||||||
"soc": _phys(ed.level.value, "%"),
|
|
||||||
"temperature_max": _phys(round(float(bat.temperature_max.value) - 273.15, 1), "degC"),
|
|
||||||
"temperature_min": _phys(round(float(bat.temperature_min.value) - 273.15, 1), "degC"),
|
|
||||||
}
|
|
||||||
except Exception:
|
|
||||||
log.debug("electric_drive/battery unavailable", exc_info=True)
|
|
||||||
except Exception:
|
except Exception:
|
||||||
log.debug("electric_drive unavailable", exc_info=True)
|
log.debug("electric_range unavailable", exc_info=True)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
result["odometer"] = {"odometer": _phys(vehicle.odometer.value, "km")}
|
rng_full = vehicle.battery_cruising_range
|
||||||
|
if rng_full is not None:
|
||||||
|
result["range_full"] = _phys(round(float(rng_full), 1), "km")
|
||||||
|
except Exception:
|
||||||
|
log.debug("battery_cruising_range unavailable", exc_info=True)
|
||||||
|
|
||||||
|
battery: dict = {}
|
||||||
|
try:
|
||||||
|
soc = vehicle.battery_level
|
||||||
|
if soc is not None:
|
||||||
|
battery["soc"] = _phys(soc, "%")
|
||||||
|
except Exception:
|
||||||
|
log.debug("battery.soc unavailable", exc_info=True)
|
||||||
|
try:
|
||||||
|
tmax = vehicle.hv_battery_max_temperature
|
||||||
|
if tmax is not None:
|
||||||
|
battery["temperature_max"] = _phys(round(tmax, 1), "degC")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
tmin = vehicle.hv_battery_min_temperature
|
||||||
|
if tmin is not None:
|
||||||
|
battery["temperature_min"] = _phys(round(tmin, 1), "degC")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
if battery:
|
||||||
|
result["battery"] = battery
|
||||||
|
|
||||||
|
try:
|
||||||
|
odo = vehicle.distance
|
||||||
|
if odo is not None:
|
||||||
|
result["odometer"] = {"odometer": _phys(odo, "km")}
|
||||||
except Exception:
|
except Exception:
|
||||||
log.debug("odometer unavailable", exc_info=True)
|
log.debug("odometer unavailable", exc_info=True)
|
||||||
|
|
||||||
@@ -143,32 +179,43 @@ def extract_connectivity(vehicle) -> dict | None:
|
|||||||
result: dict = {}
|
result: dict = {}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
state = str(vehicle.connection_state)
|
is_online = vehicle.connection_state_is_online
|
||||||
result["state"] = state
|
is_active = vehicle.connection_state_is_active
|
||||||
|
if is_online:
|
||||||
|
result["state"] = "online"
|
||||||
|
elif is_active:
|
||||||
|
result["state"] = "active"
|
||||||
|
else:
|
||||||
|
result["state"] = "offline"
|
||||||
except Exception:
|
except Exception:
|
||||||
log.debug("connectivity.state unavailable", exc_info=True)
|
log.debug("connectivity.state unavailable", exc_info=True)
|
||||||
|
|
||||||
return result or None
|
return result or None
|
||||||
|
|
||||||
|
|
||||||
def extract_vehicle(vehicle) -> dict | None:
|
def extract_vehicle(vehicle) -> dict | None:
|
||||||
result: dict = {}
|
result: dict = {}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
state = str(vehicle.state)
|
overall = find_path(vehicle.attrs, Paths.ACCESS_OVERALL_STATUS)
|
||||||
result["state"] = state
|
if overall is not None:
|
||||||
|
result["state"] = overall
|
||||||
except Exception:
|
except Exception:
|
||||||
log.debug("vehicle.state unavailable", exc_info=True)
|
log.debug("vehicle.state unavailable", exc_info=True)
|
||||||
|
|
||||||
return result or None
|
return result or None
|
||||||
|
|
||||||
|
|
||||||
def extract_position(vehicle) -> dict | None:
|
def extract_position(vehicle) -> dict | None:
|
||||||
result: dict = {}
|
result: dict = {}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
pos = vehicle.position
|
pos = vehicle.position
|
||||||
result["lat"] = pos.latitude.value
|
lat = pos.get("lat")
|
||||||
result["lon"] = pos.longitude.value
|
lng = pos.get("lng")
|
||||||
|
if lat is not None and lat != "?" and lng is not None and lng != "?":
|
||||||
|
result["lat"] = lat
|
||||||
|
result["lon"] = lng # volkswagencarnet uses "lng" internally; we keep "lon" for compat
|
||||||
except Exception:
|
except Exception:
|
||||||
log.debug("position unavailable", exc_info=True)
|
log.debug("position unavailable", exc_info=True)
|
||||||
|
|
||||||
@@ -179,20 +226,29 @@ def extract_doors(vehicle) -> dict | None:
|
|||||||
result: dict = {}
|
result: dict = {}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
doors = vehicle.doors
|
lock_state = find_path(vehicle.attrs, Paths.ACCESS_DOOR_LOCK)
|
||||||
if doors.lock_state.value is not None:
|
if lock_state is not None:
|
||||||
result["lock_state"] = _str(doors.lock_state)
|
result["lock_state"] = lock_state
|
||||||
if doors.open_state.value is not None:
|
|
||||||
result["open_state"] = _str(doors.open_state)
|
doors_list = find_path(vehicle.attrs, Paths.ACCESS_DOORS) or []
|
||||||
door_entries = {}
|
door_entries: dict = {}
|
||||||
for name, door in doors.doors.items():
|
all_closed: bool | None = None
|
||||||
entry = {}
|
for door in doors_list:
|
||||||
if door.lock_state.value is not None:
|
name = door.get("name")
|
||||||
entry["lock_state"] = _str(door.lock_state)
|
status = door.get("status") or []
|
||||||
if door.open_state.value is not None:
|
if not name:
|
||||||
entry["open_state"] = _str(door.open_state)
|
continue
|
||||||
|
entry: dict = {}
|
||||||
|
if "closed" in status or "open" in status:
|
||||||
|
open_state = "closed" if "closed" in status else "open"
|
||||||
|
entry["open_state"] = open_state
|
||||||
|
all_closed = open_state == "closed" if all_closed is None else (all_closed and open_state == "closed")
|
||||||
|
if "locked" in status or "unlocked" in status:
|
||||||
|
entry["lock_state"] = "locked" if "locked" in status else "unlocked"
|
||||||
if entry:
|
if entry:
|
||||||
door_entries[name] = entry
|
door_entries[name] = entry
|
||||||
|
if all_closed is not None:
|
||||||
|
result["open_state"] = "closed" if all_closed else "open"
|
||||||
if door_entries:
|
if door_entries:
|
||||||
result["doors"] = door_entries
|
result["doors"] = door_entries
|
||||||
except Exception:
|
except Exception:
|
||||||
@@ -205,12 +261,20 @@ def extract_windows(vehicle) -> dict | None:
|
|||||||
result: dict = {}
|
result: dict = {}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if vehicle.windows.open_state.value is not None:
|
windows_list = find_path(vehicle.attrs, Paths.ACCESS_WINDOWS) or []
|
||||||
result["open_state"] = _str(vehicle.windows.open_state)
|
window_entries: dict = {}
|
||||||
window_entries = {}
|
all_closed: bool | None = None
|
||||||
for name, win in vehicle.windows.windows.items():
|
for win in windows_list:
|
||||||
if win.open_state.value is not None:
|
name = win.get("name")
|
||||||
window_entries[name] = {"open_state": _str(win.open_state)}
|
status = win.get("status") or []
|
||||||
|
if not name or "unsupported" in status:
|
||||||
|
continue
|
||||||
|
if "closed" in status or "open" in status:
|
||||||
|
open_state = "closed" if "closed" in status else "open"
|
||||||
|
window_entries[name] = {"open_state": open_state}
|
||||||
|
all_closed = open_state == "closed" if all_closed is None else (all_closed and open_state == "closed")
|
||||||
|
if all_closed is not None:
|
||||||
|
result["open_state"] = "closed" if all_closed else "open"
|
||||||
if window_entries:
|
if window_entries:
|
||||||
result["windows"] = window_entries
|
result["windows"] = window_entries
|
||||||
except Exception:
|
except Exception:
|
||||||
@@ -278,4 +342,4 @@ def apply_procedural(record: dict) -> dict:
|
|||||||
|
|
||||||
if proc:
|
if proc:
|
||||||
record["procedural"] = proc
|
record["procedural"] = proc
|
||||||
return record
|
return record
|
||||||
|
|||||||
@@ -1,3 +1,2 @@
|
|||||||
carconnectivity
|
volkswagencarnet @ git+file:///home/jens/work/repos/volkswagencarnet
|
||||||
carconnectivity-connector-volkswagen
|
|
||||||
jaydiff @ git+http://192.168.22.90:3001/jayfield/jaypy.git
|
jaydiff @ git+http://192.168.22.90:3001/jayfield/jaypy.git
|
||||||
|
|||||||
+48
-36
@@ -1,52 +1,64 @@
|
|||||||
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
import sys
|
|
||||||
|
|
||||||
try:
|
import aiohttp
|
||||||
from carconnectivity.carconnectivity import CarConnectivity
|
from volkswagencarnet.vw_connection import Connection
|
||||||
except ImportError:
|
from volkswagencarnet.vw_exceptions import AuthenticationError # noqa: F401 — re-exported for collect.py
|
||||||
print(
|
|
||||||
"carconnectivity is not installed. Run: "
|
|
||||||
"pip install carconnectivity carconnectivity-connector-volkswagen",
|
|
||||||
file=sys.stderr,
|
|
||||||
)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def connect(username: str, password: str) -> CarConnectivity:
|
class _Session:
|
||||||
config = {
|
"""Holds the asyncio event loop, aiohttp session, and Connection together."""
|
||||||
"carConnectivity": {
|
|
||||||
"connectors": [
|
def __init__(
|
||||||
{
|
self,
|
||||||
"type": "volkswagen",
|
loop: asyncio.AbstractEventLoop,
|
||||||
"config": {
|
session: aiohttp.ClientSession,
|
||||||
"username": username,
|
connection: Connection,
|
||||||
"password": password,
|
) -> None:
|
||||||
},
|
self._loop = loop
|
||||||
}
|
self._http_session = session
|
||||||
]
|
self.connection = connection
|
||||||
}
|
|
||||||
}
|
def run(self, coro):
|
||||||
cc = CarConnectivity(config=config)
|
return self._loop.run_until_complete(coro)
|
||||||
cc.startup()
|
|
||||||
cc.fetch_all()
|
@property
|
||||||
return cc
|
def vehicles(self):
|
||||||
|
return self.connection.vehicles
|
||||||
|
|
||||||
|
|
||||||
def select_vehicle(cc: CarConnectivity, vin: str | None):
|
def connect(username: str, password: str) -> _Session:
|
||||||
|
loop = asyncio.new_event_loop()
|
||||||
|
asyncio.set_event_loop(loop)
|
||||||
|
|
||||||
|
async def _do():
|
||||||
|
session = aiohttp.ClientSession(headers={"Connection": "keep-alive"})
|
||||||
|
conn = Connection(session, username, password)
|
||||||
|
ok = await conn.doLogin()
|
||||||
|
if not ok:
|
||||||
|
await session.close()
|
||||||
|
raise AuthenticationError("Login failed")
|
||||||
|
return session, conn
|
||||||
|
|
||||||
|
session, conn = loop.run_until_complete(_do())
|
||||||
|
return _Session(loop, session, conn)
|
||||||
|
|
||||||
|
|
||||||
|
def select_vehicle(wc: _Session, vin: str | None):
|
||||||
"""Return (vehicle, error_message). error_message is None on success."""
|
"""Return (vehicle, error_message). error_message is None on success."""
|
||||||
vehicles = list(cc.get_garage().list_vehicles())
|
vehicles = wc.vehicles
|
||||||
if not vehicles:
|
if not vehicles:
|
||||||
return None, "No vehicles found in this CarConnectivity account"
|
return None, "No vehicles found in this account"
|
||||||
if vin:
|
if vin:
|
||||||
vehicle = next((v for v in vehicles if v.vin.value == vin), None)
|
vehicle = next((v for v in vehicles if v.vin == vin), None)
|
||||||
if not vehicle:
|
if not vehicle:
|
||||||
available = [v.vin.value for v in vehicles]
|
available = [v.vin for v in vehicles]
|
||||||
return None, f"VIN {vin} not found. Available: {available}"
|
return None, f"VIN {vin} not found. Available: {available}"
|
||||||
return vehicle, None
|
return vehicle, None
|
||||||
return vehicles[0], None
|
return vehicles[0], None
|
||||||
|
|
||||||
|
|
||||||
def update(cc: CarConnectivity) -> None:
|
def update(wc: _Session) -> None:
|
||||||
cc.fetch_all()
|
wc.run(wc.connection.update())
|
||||||
|
|||||||
Reference in New Issue
Block a user