collect: migrate from weconnect to CarConnectivity + VW connector
Replace the weconnect library with carconnectivity and carconnectivity-connector-volkswagen. The connector is configured via a dict passed to CarConnectivity(); startup()/fetch_all() replace login()/update(). All domain extractors in data_model.py are rewritten for the new object model (vehicle.charging.*, vehicle.get_electric_drive(), vehicle.climatization.*, etc.). Remaining time fields are now derived from estimated_date_reached DateAttributes. Battery temperatures are read directly in °C (no Kelvin conversion). Auth error handling drops the manual re-login since the connector manages token refresh internally. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+13
-17
@@ -1,10 +1,10 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""
|
"""
|
||||||
WeConnect vehicle data collector.
|
CarConnectivity vehicle data collector.
|
||||||
|
|
||||||
Periodically fetches selected domains from a VW WeConnect vehicle and
|
Periodically fetches selected domains from a VW vehicle via CarConnectivity
|
||||||
appends timestamped records to a JSON file. The JSON structure mirrors
|
and appends timestamped records to a JSON file. The JSON structure mirrors
|
||||||
the WeConnect domain hierarchy: domain → status-object → field.
|
the domain hierarchy: domain → status-object → field.
|
||||||
|
|
||||||
Usage examples
|
Usage examples
|
||||||
--------------
|
--------------
|
||||||
@@ -53,7 +53,7 @@ from datetime import datetime, timezone
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from server import log_config, network, we_connect
|
from server import log_config, network, we_connect
|
||||||
from weconnect.errors import AuthentificationError
|
from carconnectivity.errors import AuthenticationError, TemporaryAuthenticationError
|
||||||
from server.data_model import ALL_DOMAINS, collect_snapshot, apply_procedural
|
from server.data_model import ALL_DOMAINS, collect_snapshot, apply_procedural
|
||||||
from server.storage_helpers import auto_out_path, load_last_24h_records, load_store, save_store
|
from server.storage_helpers import auto_out_path, load_last_24h_records, load_store, save_store
|
||||||
from utils.jay_diff import jay_diff_full
|
from utils.jay_diff import jay_diff_full
|
||||||
@@ -74,12 +74,12 @@ def run(args: argparse.Namespace) -> None:
|
|||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
if args.dry:
|
if args.dry:
|
||||||
log.info("Dry-run mode — skipping WeConnect login")
|
log.info("Dry-run mode — skipping CarConnectivity login")
|
||||||
wc = None
|
wc = None
|
||||||
vehicle = None
|
vehicle = None
|
||||||
vin = args.vin or "UNKNOWN"
|
vin = args.vin or "UNKNOWN"
|
||||||
else:
|
else:
|
||||||
log.info("Connecting to WeConnect…")
|
log.info("Connecting via CarConnectivity…")
|
||||||
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:
|
||||||
@@ -132,7 +132,7 @@ def run(args: argparse.Namespace) -> None:
|
|||||||
log.info("Midnight UTC rotation → %s", out)
|
log.info("Midnight UTC rotation → %s", out)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
wc.update()
|
we_connect.update(wc)
|
||||||
snapshot = collect_snapshot(vehicle, domains)
|
snapshot = collect_snapshot(vehicle, domains)
|
||||||
apply_procedural(snapshot)
|
apply_procedural(snapshot)
|
||||||
store["records"].append(snapshot)
|
store["records"].append(snapshot)
|
||||||
@@ -146,12 +146,8 @@ 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 AuthentificationError:
|
except (AuthenticationError, TemporaryAuthenticationError):
|
||||||
log.warning("Authentication error — re-logging in before next interval")
|
log.warning("Authentication error — will retry at next interval")
|
||||||
try:
|
|
||||||
wc.login()
|
|
||||||
except Exception:
|
|
||||||
log.exception("Re-login failed")
|
|
||||||
except Exception:
|
except Exception:
|
||||||
log.exception("Collection failed — will retry at next interval")
|
log.exception("Collection failed — will retry at next interval")
|
||||||
|
|
||||||
@@ -180,12 +176,12 @@ def main() -> None:
|
|||||||
creds.add_argument(
|
creds.add_argument(
|
||||||
"-u", "--username",
|
"-u", "--username",
|
||||||
default=os.environ.get("WC_USER"),
|
default=os.environ.get("WC_USER"),
|
||||||
help="WeConnect / MyVolkswagen email (overrides credentials file)",
|
help="MyVolkswagen email (overrides credentials file)",
|
||||||
)
|
)
|
||||||
creds.add_argument(
|
creds.add_argument(
|
||||||
"-p", "--password",
|
"-p", "--password",
|
||||||
default=os.environ.get("WC_PASS"),
|
default=os.environ.get("WC_PASS"),
|
||||||
help="WeConnect password (overrides credentials file)",
|
help="MyVolkswagen password (overrides credentials file)",
|
||||||
)
|
)
|
||||||
|
|
||||||
p.add_argument(
|
p.add_argument(
|
||||||
@@ -239,7 +235,7 @@ def main() -> None:
|
|||||||
p.add_argument(
|
p.add_argument(
|
||||||
"--dry",
|
"--dry",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
help="Skip WeConnect login and data collection; run push server only (for GUI testing)",
|
help="Skip CarConnectivity login and data collection; run push server only (for GUI testing)",
|
||||||
)
|
)
|
||||||
p.add_argument(
|
p.add_argument(
|
||||||
"--list-domains",
|
"--list-domains",
|
||||||
|
|||||||
+3
-2
@@ -1,3 +1,4 @@
|
|||||||
weconnect
|
carconnectivity
|
||||||
|
carconnectivity-connector-volkswagen
|
||||||
PyQt5
|
PyQt5
|
||||||
pyqtgraph
|
pyqtgraph
|
||||||
+74
-90
@@ -6,60 +6,73 @@ log = logging.getLogger(__name__)
|
|||||||
|
|
||||||
# ── value helpers ─────────────────────────────────────────────────────────────
|
# ── value helpers ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
def _str(v) -> str:
|
def _str(attr) -> str:
|
||||||
return str(v.value) if hasattr(v, "value") else str(v)
|
"""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:
|
||||||
try:
|
|
||||||
d = vehicle.domains["charging"]
|
|
||||||
except KeyError:
|
|
||||||
return None
|
|
||||||
|
|
||||||
result: dict = {}
|
result: dict = {}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
cs = d["chargingStatus"]
|
ch = vehicle.charging
|
||||||
result["chargingStatus"] = {
|
status: dict = {
|
||||||
"chargingState": _str(cs.chargingState),
|
"chargingState": _str(ch.state),
|
||||||
"chargePower": _phys(cs.chargePower_kW.value, "kW"),
|
"chargeType": _str(ch.type),
|
||||||
"remainingChargingTimeToComplete": _phys(cs.remainingChargingTimeToComplete_min.value, "min"),
|
|
||||||
"chargeType": _str(cs.chargeType),
|
|
||||||
}
|
}
|
||||||
|
try:
|
||||||
|
status["chargePower"] = _phys(ch.power.value, "kW")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
rem = _remaining_min(ch.estimated_date_reached)
|
||||||
|
if rem is not None:
|
||||||
|
status["remainingChargingTimeToComplete"] = _phys(round(rem), "min")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
result["chargingStatus"] = status
|
||||||
except Exception:
|
except Exception:
|
||||||
log.debug("chargingStatus unavailable", exc_info=True)
|
log.debug("chargingStatus unavailable", exc_info=True)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
bs = d["batteryStatus"]
|
ed = vehicle.get_electric_drive()
|
||||||
result["batteryStatus"] = {
|
result["batteryStatus"] = {
|
||||||
"currentSOC": _phys(bs.currentSOC_pct.value, "%"),
|
"currentSOC": _phys(ed.level.value, "%"),
|
||||||
"cruisingRangeElectric": _phys(bs.cruisingRangeElectric_km.value, "km"),
|
"cruisingRangeElectric": _phys(ed.range.value, "km"),
|
||||||
}
|
}
|
||||||
except Exception:
|
except Exception:
|
||||||
log.debug("charging/batteryStatus unavailable", exc_info=True)
|
log.debug("charging/batteryStatus unavailable", exc_info=True)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
cfg = d["chargingSettings"]
|
cfg = vehicle.charging.settings
|
||||||
result["chargingSettings"] = {
|
result["chargingSettings"] = {
|
||||||
"targetSOC": _phys(cfg.targetSOC_pct.value, "%"),
|
"targetSOC": _phys(cfg.target_level.value, "%"),
|
||||||
"maxChargeCurrentAC": _str(cfg.maxChargeCurrentAC),
|
"maxChargeCurrentAC": _phys(cfg.maximum_current.value, "A"),
|
||||||
"autoUnlockPlugWhenCharged": _str(cfg.autoUnlockPlugWhenCharged),
|
"autoUnlockPlugWhenCharged": _str(cfg.auto_unlock),
|
||||||
}
|
}
|
||||||
except Exception:
|
except Exception:
|
||||||
log.debug("chargingSettings unavailable", exc_info=True)
|
log.debug("chargingSettings unavailable", exc_info=True)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ps = d["plugStatus"]
|
conn = vehicle.charging.connector
|
||||||
result["plugStatus"] = {
|
result["plugStatus"] = {
|
||||||
"plugConnectionState": _str(ps.plugConnectionState),
|
"plugConnectionState": _str(conn.connection_state),
|
||||||
"plugLockState": _str(ps.plugLockState),
|
"plugLockState": _str(conn.lock_state),
|
||||||
"externalPower": _str(ps.externalPower),
|
"externalPower": _str(conn.external_power),
|
||||||
}
|
}
|
||||||
except Exception:
|
except Exception:
|
||||||
log.debug("plugStatus unavailable", exc_info=True)
|
log.debug("plugStatus unavailable", exc_info=True)
|
||||||
@@ -68,36 +81,34 @@ def extract_charging(vehicle) -> dict | None:
|
|||||||
|
|
||||||
|
|
||||||
def extract_climatisation(vehicle) -> dict | None:
|
def extract_climatisation(vehicle) -> dict | None:
|
||||||
try:
|
|
||||||
d = vehicle.domains["climatisation"]
|
|
||||||
except KeyError:
|
|
||||||
return None
|
|
||||||
|
|
||||||
result: dict = {}
|
result: dict = {}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
cl = d["climatisationStatus"]
|
cl = vehicle.climatization
|
||||||
result["climatisationStatus"] = {
|
status: dict = {"climatisationState": _str(cl.state)}
|
||||||
"climatisationState": _str(cl.climatisationState),
|
try:
|
||||||
"remainingClimatisationTime": _phys(cl.remainingClimatisationTime_min.value, "min"),
|
rem = _remaining_min(cl.estimated_date_reached)
|
||||||
}
|
if rem is not None:
|
||||||
|
status["remainingClimatisationTime"] = _phys(round(rem), "min")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
result["climatisationStatus"] = status
|
||||||
except Exception:
|
except Exception:
|
||||||
log.debug("climatisationStatus unavailable", exc_info=True)
|
log.debug("climatisationStatus unavailable", exc_info=True)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
cs = d["climatisationSettings"]
|
cs = vehicle.climatization.settings
|
||||||
result["climatisationSettings"] = {
|
result["climatisationSettings"] = {
|
||||||
"targetTemperature": _phys(cs.targetTemperature_C.value, "degC"),
|
"targetTemperature": _phys(cs.target_temperature.value, "degC"),
|
||||||
"unitInCar": _str(cs.unitInCar),
|
|
||||||
}
|
}
|
||||||
except Exception:
|
except Exception:
|
||||||
log.debug("climatisationSettings unavailable", exc_info=True)
|
log.debug("climatisationSettings unavailable", exc_info=True)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
wh = d["windowHeatingStatus"]
|
wh = vehicle.window_heatings
|
||||||
result["windowHeatingStatus"] = {
|
result["windowHeatingStatus"] = {
|
||||||
name: _str(win.windowHeatingState)
|
name: _str(win.heating_state)
|
||||||
for name, win in wh.windows.items()
|
for name, win in wh.heatings.items()
|
||||||
}
|
}
|
||||||
except Exception:
|
except Exception:
|
||||||
log.debug("windowHeatingStatus unavailable", exc_info=True)
|
log.debug("windowHeatingStatus unavailable", exc_info=True)
|
||||||
@@ -106,41 +117,35 @@ def extract_climatisation(vehicle) -> dict | None:
|
|||||||
|
|
||||||
|
|
||||||
def extract_measurements(vehicle) -> dict | None:
|
def extract_measurements(vehicle) -> dict | None:
|
||||||
try:
|
|
||||||
d = vehicle.domains["measurements"]
|
|
||||||
except KeyError:
|
|
||||||
return None
|
|
||||||
|
|
||||||
result: dict = {}
|
result: dict = {}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
od = d["odometerStatus"]
|
result["odometerStatus"] = {"odometer": _phys(vehicle.odometer.value, "km")}
|
||||||
result["odometerStatus"] = {"odometer": _phys(od.odometer.value, "km")}
|
|
||||||
except Exception:
|
except Exception:
|
||||||
log.debug("odometerStatus unavailable", exc_info=True)
|
log.debug("odometerStatus unavailable", exc_info=True)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
rs = d["rangeStatus"]
|
ed = vehicle.get_electric_drive()
|
||||||
|
electric_range = ed.range.value
|
||||||
result["rangeStatus"] = {
|
result["rangeStatus"] = {
|
||||||
"electricRange": _phys(rs.electricRange.value, "km"),
|
"electricRange": _phys(electric_range, "km"),
|
||||||
"totalRange": _phys(rs.totalRange_km.value, "km"),
|
"totalRange": _phys(electric_range, "km"),
|
||||||
}
|
}
|
||||||
except Exception:
|
except Exception:
|
||||||
log.debug("rangeStatus unavailable", exc_info=True)
|
log.debug("rangeStatus unavailable", exc_info=True)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
bs = d["temperatureBatteryStatus"]
|
bat = vehicle.get_electric_drive().battery
|
||||||
result["temperatureBatteryStatus"] = {
|
result["temperatureBatteryStatus"] = {
|
||||||
"temperatureHvBatteryMax": _phys(float(bs.temperatureHvBatteryMax_K.value - 273.15), "degC"),
|
"temperatureHvBatteryMax": _phys(bat.temperature_max.value, "degC"),
|
||||||
"temperatureHvBatteryMin": _phys(float(bs.temperatureHvBatteryMin_K.value - 273.15), "degC"),
|
"temperatureHvBatteryMin": _phys(bat.temperature_min.value, "degC"),
|
||||||
}
|
}
|
||||||
except Exception:
|
except Exception:
|
||||||
log.debug("measurements/temperatureBatteryStatus unavailable", exc_info=True)
|
log.debug("measurements/temperatureBatteryStatus unavailable", exc_info=True)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
temp = d["temperatureOutsideStatus"]
|
|
||||||
result["temperatureOutsideStatus"] = {
|
result["temperatureOutsideStatus"] = {
|
||||||
"temperatureOutside": _phys(temp.temperatureOutside_C.value, "degC"),
|
"temperatureOutside": _phys(vehicle.outside_temperature.value, "degC"),
|
||||||
}
|
}
|
||||||
except Exception:
|
except Exception:
|
||||||
log.debug("temperatureOutsideStatus unavailable", exc_info=True)
|
log.debug("temperatureOutsideStatus unavailable", exc_info=True)
|
||||||
@@ -149,25 +154,14 @@ def extract_measurements(vehicle) -> dict | None:
|
|||||||
|
|
||||||
|
|
||||||
def extract_readiness(vehicle) -> dict | None:
|
def extract_readiness(vehicle) -> dict | None:
|
||||||
try:
|
|
||||||
d = vehicle.domains["readiness"]
|
|
||||||
except KeyError:
|
|
||||||
return None
|
|
||||||
|
|
||||||
result: dict = {}
|
result: dict = {}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
rs = d["readinessStatus"]
|
conn_state = str(vehicle.connection_state)
|
||||||
conn = rs.connectionState
|
|
||||||
warn = rs.connectionWarning
|
|
||||||
result["readinessStatus"] = {
|
result["readinessStatus"] = {
|
||||||
"connectionState": {
|
"connectionState": {
|
||||||
"isOnline": conn.isOnline.value,
|
"isOnline": conn_state == "online",
|
||||||
"isActive": conn.isActive.value,
|
"isActive": conn_state in ("online", "reachable"),
|
||||||
},
|
|
||||||
"connectionWarning": {
|
|
||||||
"insufficientBatteryLevelWarning": warn.insufficientBatteryLevelWarning.value,
|
|
||||||
"insufficientBatteryLevelError": warn.insufficientBatteryLevelError.value,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
except Exception:
|
except Exception:
|
||||||
@@ -177,18 +171,13 @@ def extract_readiness(vehicle) -> dict | None:
|
|||||||
|
|
||||||
|
|
||||||
def extract_parking(vehicle) -> dict | None:
|
def extract_parking(vehicle) -> dict | None:
|
||||||
try:
|
|
||||||
d = vehicle.domains["parking"]
|
|
||||||
except KeyError:
|
|
||||||
return None
|
|
||||||
|
|
||||||
result: dict = {}
|
result: dict = {}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
pos = d["parkingPosition"]
|
pos = vehicle.position
|
||||||
result["parkingPosition"] = {
|
result["parkingPosition"] = {
|
||||||
"lat": pos.lat.value,
|
"lat": pos.latitude.value,
|
||||||
"lon": pos.lon.value,
|
"lon": pos.longitude.value,
|
||||||
}
|
}
|
||||||
except Exception:
|
except Exception:
|
||||||
log.debug("parkingPosition unavailable", exc_info=True)
|
log.debug("parkingPosition unavailable", exc_info=True)
|
||||||
@@ -197,27 +186,22 @@ def extract_parking(vehicle) -> dict | None:
|
|||||||
|
|
||||||
|
|
||||||
def extract_access(vehicle) -> dict | None:
|
def extract_access(vehicle) -> dict | None:
|
||||||
try:
|
|
||||||
d = vehicle.domains["access"]
|
|
||||||
except KeyError:
|
|
||||||
return None
|
|
||||||
|
|
||||||
result: dict = {}
|
result: dict = {}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
acc = d["accessStatus"]
|
doors = vehicle.doors
|
||||||
result["accessStatus"] = {
|
result["accessStatus"] = {
|
||||||
"overallStatus": _str(acc.overallStatus),
|
"overallStatus": _str(doors.lock_state),
|
||||||
"doors": {
|
"doors": {
|
||||||
name: {
|
name: {
|
||||||
"lockState": _str(door.lockState),
|
"lockState": _str(door.lock_state),
|
||||||
"openState": _str(door.openState),
|
"openState": _str(door.open_state),
|
||||||
}
|
}
|
||||||
for name, door in acc.doors.items()
|
for name, door in doors.doors.items()
|
||||||
},
|
},
|
||||||
"windows": {
|
"windows": {
|
||||||
name: {"openState": _str(win.openState)}
|
name: {"openState": _str(win.open_state)}
|
||||||
for name, win in acc.windows.items()
|
for name, win in vehicle.windows.windows.items()
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
except Exception:
|
except Exception:
|
||||||
@@ -283,4 +267,4 @@ def apply_procedural(record: dict) -> dict:
|
|||||||
|
|
||||||
if proc:
|
if proc:
|
||||||
record["procedural"] = proc
|
record["procedural"] = proc
|
||||||
return record
|
return record
|
||||||
+32
-16
@@ -2,35 +2,51 @@ import logging
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from weconnect import weconnect
|
from carconnectivity.carconnectivity import CarConnectivity
|
||||||
except ImportError:
|
except ImportError:
|
||||||
print("weconnect is not installed. Run: pip install weconnect", file=sys.stderr)
|
print(
|
||||||
|
"carconnectivity is not installed. Run: "
|
||||||
|
"pip install carconnectivity carconnectivity-connector-volkswagen",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def connect(username: str, password: str):
|
def connect(username: str, password: str) -> CarConnectivity:
|
||||||
wc = weconnect.WeConnect(
|
config = {
|
||||||
username=username,
|
"carConnectivity": {
|
||||||
password=password,
|
"connectors": [
|
||||||
updateAfterLogin=False,
|
{
|
||||||
loginOnInit=True,
|
"type": "volkswagen",
|
||||||
)
|
"config": {
|
||||||
wc.login()
|
"username": username,
|
||||||
wc.update()
|
"password": password,
|
||||||
return wc
|
},
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cc = CarConnectivity(config=config)
|
||||||
|
cc.startup()
|
||||||
|
cc.fetch_all()
|
||||||
|
return cc
|
||||||
|
|
||||||
|
|
||||||
def select_vehicle(wc, vin: str | None):
|
def select_vehicle(cc: CarConnectivity, 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(wc.vehicles.values())
|
vehicles = list(cc.get_garage().list_vehicles())
|
||||||
if not vehicles:
|
if not vehicles:
|
||||||
return None, "No vehicles found in this WeConnect account"
|
return None, "No vehicles found in this CarConnectivity 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.value == vin), None)
|
||||||
if not vehicle:
|
if not vehicle:
|
||||||
available = [v.vin.value for v in vehicles]
|
available = [v.vin.value 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:
|
||||||
|
cc.fetch_all()
|
||||||
Reference in New Issue
Block a user