diff --git a/README.md b/README.md index a733b3e..f19c002 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # we_monitor -Periodically polls a VW WeConnect vehicle via the `weconnect` library, stores +Periodically polls a VW vehicle via the `carconnectivity` library, stores timestamped records as JSON, and pushes live data to connected TCP clients. A PyQt5 GUI client visualises the data in real time. @@ -14,7 +14,7 @@ server/ data_model.py — domain extractors, collect_snapshot() network.py — TCP push server (accept loop + broadcast) storage_helpers.py — load / save JSON store, midnight rotation helpers - we_connect.py — WeConnect login and vehicle selection + we_connect.py — CarConnectivity login and vehicle selection log_config.py — logging setup utils/ jay_diff.py — JSON diff / merge library @@ -25,7 +25,7 @@ requirements.txt ## Requirements ``` -pip install weconnect PyQt5 pyqtgraph +pip install carconnectivity carconnectivity-connector-volkswagen PyQt5 pyqtgraph ``` Or use the installer which also sets up a systemd user service: @@ -38,7 +38,7 @@ Or use the installer which also sets up a systemd user service: ## Collector (`collect.py`) -Connects to WeConnect, polls selected domains at a configurable interval, and +Connects via CarConnectivity, polls selected domains at a configurable interval, and appends timestamped records to a rotating JSON log. Simultaneously runs a TCP push server so clients receive every new snapshot the moment it is collected. @@ -68,7 +68,7 @@ All connection and collection settings live in a single JSON file: "password": "secret" }, "vin": "WVWZZZE1ZMP000000", - "domains": ["charging", "measurements", "readiness"], + "domains": ["charging", "electric_drive", "connectivity"], "interval_s": 300, "host": "0.0.0.0", "port": 9999, @@ -79,10 +79,10 @@ All connection and collection settings live in a single JSON file: | Key | Default | Description | |-----|---------|-------------| -| `credentials.username` | — | WeConnect / MyVolkswagen email | -| `credentials.password` | — | WeConnect password | +| `credentials.username` | — | MyVolkswagen email | +| `credentials.password` | — | MyVolkswagen password | | `vin` | first vehicle | Target VIN | -| `domains` | `["charging","measurements","readiness"]` | Domains to collect (list or `"all"`) | +| `domains` | `["charging","electric_drive","connectivity"]` | Domains to collect (list or `"all"`) | | `interval_s` | `300` | Collection interval in seconds | | `host` | `"0.0.0.0"` | Push server bind address | | `port` | `9999` | Push server TCP port | @@ -104,12 +104,12 @@ CLI flags override the corresponding credentials-file value when given. | `--log-dir DIR` | `log_dir` | Directory for rotating log files | | `--max-records N` | — | Keep only the last N records per file (default: unlimited) | | `--diff` | `diff` | Enable diff-mode on the push server | -| `--dry` | — | Skip WeConnect login; run push server only (useful for GUI testing) | +| `--dry` | — | Skip CarConnectivity login; run push server only (useful for GUI testing) | | `-v` | — | Verbose / debug logging | | `--list-domains` | — | Print available domains and exit | -**Available domains:** `charging`, `climatisation`, `measurements`, -`readiness`, `parking`, `access` +**Available domains:** `charging`, `climatisation`, `electric_drive`, +`connectivity`, `vehicle`, `position`, `doors` ### Log rotation @@ -129,10 +129,12 @@ Log files are named `YYYY_MM_DD_HH_MM_SS_.json` and contain: { "ts": "2025-05-25T10:00:00+00:00", "charging": { - "batteryStatus": { - "currentSOC": { "value": 80, "unit": "%" }, - "cruisingRangeElectric": { "value": 310, "unit": "km" } - } + "state": "readyForCharging", + "power": { "value": 11.0, "unit": "kW" } + }, + "electric_drive": { + "range": { "value": 310, "unit": "km" }, + "battery": { "soc": { "value": 80, "unit": "%" } } } } ] @@ -259,14 +261,14 @@ pretty-printed JSON with a running record counter. ### `collect.py` Entry point for the collector daemon. Parses CLI / credentials-file arguments, -establishes the WeConnect session, starts the push server, and runs the main +establishes the CarConnectivity session, starts the push server, and runs the main poll loop. ### `server/data_model.py` `collect_snapshot(vehicle, domains) → dict` -Iterates over the requested domains on a `weconnect.Vehicle` and extracts every -`Status` attribute into a flat-ish dict keyed by domain and status-object name. +Iterates over the requested domains on a CarConnectivity vehicle object and extracts +fields into a flat dict keyed by domain. Physical values are normalised to `{"value": …, "unit": …}`. Returns a record with a top-level `"ts"` ISO-8601 timestamp. @@ -307,8 +309,8 @@ the last 24 hours, and returns them sorted by timestamp. ### `server/we_connect.py` -`connect(username, password) → WeConnect` -Authenticates and returns a connected `WeConnect` instance. +`connect(username, password) → CarConnectivity` +Authenticates and returns a connected `CarConnectivity` instance. `select_vehicle(wc, vin) → (vehicle, error)` Returns the target vehicle (first available if `vin` is empty) or an error string. diff --git a/client.py b/client.py index 843e344..f16c278 100644 --- a/client.py +++ b/client.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 """ -WeConnect push-server test client. +CarConnectivity push-server test client. Connects to the collect.py TCP push server and prints each incoming snapshot to stdout as pretty-printed JSON. @@ -17,7 +17,7 @@ import json import socket import sys -from utils.jay_diff import jay_merge_full +from jaydiff.merge import merge_full as jay_merge_full def main() -> None: diff --git a/collect.py b/collect.py index 4d28476..00f5d5c 100644 --- a/collect.py +++ b/collect.py @@ -1,10 +1,10 @@ #!/usr/bin/env python3 """ -WeConnect vehicle data collector. +CarConnectivity vehicle data collector. -Periodically fetches selected domains from a VW WeConnect vehicle and -appends timestamped records to a JSON file. The JSON structure mirrors -the WeConnect domain hierarchy: domain → status-object → field. +Periodically fetches selected domains from a VW vehicle via CarConnectivity +and appends timestamped records to a JSON file. The JSON structure mirrors +the domain hierarchy: domain → status-object → field. Usage examples -------------- @@ -18,7 +18,7 @@ Usage examples # Override domains on the CLI: python collect.py -c credentials/alex.json -d charging,measurements -Available domains: charging, climatisation, measurements, readiness, parking, access +Available domains: charging, climatisation, electric_drive, connectivity, vehicle, position, doors Credentials file format (JSON) ------------------------------- @@ -53,10 +53,10 @@ from datetime import datetime, timezone from pathlib import Path 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.storage_helpers import auto_out_path, load_last_24h_records, load_store, save_store -from utils.jay_diff import jay_diff_full +from jaydiff.diff import diff_full as jay_diff_full log = logging.getLogger(__name__) @@ -74,12 +74,12 @@ def run(args: argparse.Namespace) -> None: sys.exit(1) if args.dry: - log.info("Dry-run mode — skipping WeConnect login") + log.info("Dry-run mode — skipping CarConnectivity login") wc = None vehicle = None vin = args.vin or "UNKNOWN" else: - log.info("Connecting to WeConnect…") + log.info("Connecting via CarConnectivity…") wc = we_connect.connect(args.username, args.password) vehicle, err = we_connect.select_vehicle(wc, args.vin) if err: @@ -132,7 +132,7 @@ def run(args: argparse.Namespace) -> None: log.info("Midnight UTC rotation → %s", out) try: - wc.update() + we_connect.update(wc) snapshot = collect_snapshot(vehicle, domains) apply_procedural(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"]) except KeyboardInterrupt: raise - except AuthentificationError: - log.warning("Authentication error — re-logging in before next interval") - try: - wc.login() - except Exception: - log.exception("Re-login failed") + except (AuthenticationError, TemporaryAuthenticationError): + log.warning("Authentication error — will retry at next interval") except Exception: log.exception("Collection failed — will retry at next interval") @@ -180,12 +176,12 @@ def main() -> None: creds.add_argument( "-u", "--username", default=os.environ.get("WC_USER"), - help="WeConnect / MyVolkswagen email (overrides credentials file)", + help="MyVolkswagen email (overrides credentials file)", ) creds.add_argument( "-p", "--password", default=os.environ.get("WC_PASS"), - help="WeConnect password (overrides credentials file)", + help="MyVolkswagen password (overrides credentials file)", ) p.add_argument( @@ -195,7 +191,7 @@ def main() -> None: help=( "Comma-separated domains to collect, or 'all' (overrides credentials file). " f"Available: {', '.join(ALL_DOMAINS)}. " - "Default: charging,measurements,readiness" + "Default: charging,electric_drive,connectivity" ), ) p.add_argument( @@ -239,7 +235,7 @@ def main() -> None: p.add_argument( "--dry", 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( "--list-domains", @@ -277,7 +273,7 @@ def main() -> None: if isinstance(raw_domains, list): args.domains = ",".join(raw_domains) else: - args.domains = raw_domains or "charging,measurements,readiness" + args.domains = raw_domains or "charging,electric_drive,connectivity" if args.interval is None: args.interval = int(creds_data.get("interval_s", 300)) diff --git a/gui_client.py b/gui_client.py index 44217af..2051581 100644 --- a/gui_client.py +++ b/gui_client.py @@ -39,7 +39,7 @@ from PyQt5.QtWidgets import ( import pyqtgraph as pg -from utils.jay_diff import jay_merge_full +from jaydiff.merge import merge_full as jay_merge_full pg.setConfigOption("background", "#12121e") pg.setConfigOption("foreground", "#aaaacc") @@ -94,20 +94,19 @@ _SETTINGS_PATH = Path.home() / ".config" / "we_monitor" / "plot_settings.json" # Maps full dotted trace key → short display label used in chips, legends and # cursor readout. Falls back to the last path component for unknown keys. _TRACE_LABELS: dict[str, str] = { - "charging.chargingStatus.chargePower": "charge power", - "charging.chargingStatus.remainingChargingTimeToComplete": "time to full", - "charging.batteryStatus.currentSOC": "SOC", - "charging.batteryStatus.cruisingRangeElectric": "range", - "charging.chargingSettings.targetSOC": "target SOC", - "climatisation.climatisationStatus.remainingClimatisationTime": "clim. time", - "climatisation.climatisationSettings.targetTemperature": "target temp", - "measurements.odometerStatus.odometer": "odometer", - "measurements.rangeStatus.electricRange": "elec. range", - "measurements.rangeStatus.totalRange": "total range", - "measurements.temperatureBatteryStatus.temperatureHvBatteryMax": "batt. temp max", - "measurements.temperatureBatteryStatus.temperatureHvBatteryMin": "batt. temp min", - "measurements.temperatureOutsideStatus.temperatureOutside": "outside temp", - "procedural.range_at_100": "range 100%" + "charging.power": "charge power", + "charging.remain": "time to full", + "charging.settings.target_level": "target SOC", + "climatisation.remain": "clim. time", + "climatisation.settings.target_temperature": "target temp", + "climatisation.environment.temperature_outside": "outside temp", + "electric_drive.battery.soc": "SOC", + "electric_drive.range": "range", + "electric_drive.range_full": "range (full)", + "electric_drive.battery.temperature_max": "batt. temp max", + "electric_drive.battery.temperature_min": "batt. temp min", + "electric_drive.odometer.odometer": "odometer", + "procedural.range_at_100": "range 100%", } diff --git a/install.sh b/install.sh index 2657ece..75d7b21 100755 --- a/install.sh +++ b/install.sh @@ -69,7 +69,7 @@ if [[ ! -f "$CREDS_FILE" ]]; then "password": "secret" }, "vin": "", - "domains": ["charging", "measurements", "readiness"], + "domains": ["charging", "electric_drive", "connectivity"], "interval_s": 300, "host": "0.0.0.0", "port": 9999, @@ -93,7 +93,7 @@ mkdir -p "$SYSTEMD_DIR" cat > "$SERVICE_FILE" < str: - return str(v.value) if hasattr(v, "value") else str(v) +def _str(attr) -> str: + """Return the string value of a CarConnectivity attribute.""" + return str(attr) def _phys(value, unit: str) -> dict: 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 ───────────────────────────────────────────────────────── def extract_charging(vehicle) -> dict | None: - try: - d = vehicle.domains["charging"] - except KeyError: - return None - result: dict = {} try: - cs = d["chargingStatus"] - result["chargingStatus"] = { - "chargingState": _str(cs.chargingState), - "chargePower": _phys(cs.chargePower_kW.value, "kW"), - "remainingChargingTimeToComplete": _phys(cs.remainingChargingTimeToComplete_min.value, "min"), - "chargeType": _str(cs.chargeType), - } + ch = vehicle.charging + 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: - log.debug("chargingStatus unavailable", exc_info=True) + log.debug("vehicle.charging unavailable", exc_info=True) try: - bs = d["batteryStatus"] - result["batteryStatus"] = { - "currentSOC": _phys(bs.currentSOC_pct.value, "%"), - "cruisingRangeElectric": _phys(bs.cruisingRangeElectric_km.value, "km"), + cfg = vehicle.charging.settings + result["settings"] = { + "target_level": _phys(cfg.target_level.value, "%"), + "maximum_current": _phys(cfg.maximum_current.value, "A"), + "auto_unlock": _str(cfg.auto_unlock), } except Exception: - log.debug("charging/batteryStatus unavailable", exc_info=True) + log.debug("vehicle.charging.settings unavailable", exc_info=True) try: - cfg = d["chargingSettings"] - result["chargingSettings"] = { - "targetSOC": _phys(cfg.targetSOC_pct.value, "%"), - "maxChargeCurrentAC": _str(cfg.maxChargeCurrentAC), - "autoUnlockPlugWhenCharged": _str(cfg.autoUnlockPlugWhenCharged), - } - except Exception: - log.debug("chargingSettings unavailable", exc_info=True) - - try: - ps = d["plugStatus"] + conn = vehicle.charging.connector result["plugStatus"] = { - "plugConnectionState": _str(ps.plugConnectionState), - "plugLockState": _str(ps.plugLockState), - "externalPower": _str(ps.externalPower), + "connection_state": _str(conn.connection_state), + "lock_state": _str(conn.lock_state), + "external_power": _str(conn.external_power), } except Exception: log.debug("plugStatus unavailable", exc_info=True) @@ -68,171 +71,143 @@ def extract_charging(vehicle) -> dict | None: def extract_climatisation(vehicle) -> dict | None: - try: - d = vehicle.domains["climatisation"] - except KeyError: - return None - result: dict = {} try: - cl = d["climatisationStatus"] - result["climatisationStatus"] = { - "climatisationState": _str(cl.climatisationState), - "remainingClimatisationTime": _phys(cl.remainingClimatisationTime_min.value, "min"), - } + cl = vehicle.climatization + result["state"] = _str(cl.state) + try: + rem = _remaining_min(cl.estimated_date_reached) + if rem is not None: + result["remain"] = _phys(round(rem), "min") + except Exception: + pass except Exception: - log.debug("climatisationStatus unavailable", exc_info=True) + log.debug("vehicle.climatization unavailable", exc_info=True) try: - cs = d["climatisationSettings"] - result["climatisationSettings"] = { - "targetTemperature": _phys(cs.targetTemperature_C.value, "degC"), - "unitInCar": _str(cs.unitInCar), + cs = vehicle.climatization.settings + result["settings"] = { + "target_temperature": _phys(cs.target_temperature.value, "degC"), } except Exception: - log.debug("climatisationSettings unavailable", exc_info=True) + log.debug("climatization.settings unavailable", exc_info=True) try: - wh = d["windowHeatingStatus"] - result["windowHeatingStatus"] = { - name: _str(win.windowHeatingState) - for name, win in wh.windows.items() + wh = vehicle.window_heatings + result["window_heatings"] = { + name: _str(win.heating_state) + for name, win in wh.heatings.items() } except Exception: - log.debug("windowHeatingStatus unavailable", exc_info=True) + log.debug("vehicle.window_heatings unavailable", exc_info=True) + + try: + result["environment"] = { + "temperature_outside": _phys(vehicle.outside_temperature.value, "degC"), + } + except Exception: + log.debug("vehicle.outside_temperature unavailable", exc_info=True) return result or None -def extract_measurements(vehicle) -> dict | None: - try: - d = vehicle.domains["measurements"] - except KeyError: - return None - +def extract_electric_drive(vehicle) -> dict | None: result: dict = {} try: - od = d["odometerStatus"] - result["odometerStatus"] = {"odometer": _phys(od.odometer.value, "km")} + ed = vehicle.get_electric_drive() + result["range"] = _phys(ed.range.value, "km") + result["range_full"] = _phys(round(float(ed.range_estimated_full.value), 1), "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: - log.debug("odometerStatus unavailable", exc_info=True) + log.debug("electric_drive unavailable", exc_info=True) try: - rs = d["rangeStatus"] - result["rangeStatus"] = { - "electricRange": _phys(rs.electricRange.value, "km"), - "totalRange": _phys(rs.totalRange_km.value, "km"), - } + result["odometer"] = {"odometer": _phys(vehicle.odometer.value, "km")} except Exception: - log.debug("rangeStatus unavailable", exc_info=True) - - try: - bs = d["temperatureBatteryStatus"] - result["temperatureBatteryStatus"] = { - "temperatureHvBatteryMax": _phys(float(bs.temperatureHvBatteryMax_K.value - 273.15), "degC"), - "temperatureHvBatteryMin": _phys(float(bs.temperatureHvBatteryMin_K.value - 273.15), "degC"), - } - except Exception: - log.debug("measurements/temperatureBatteryStatus unavailable", exc_info=True) - - try: - temp = d["temperatureOutsideStatus"] - result["temperatureOutsideStatus"] = { - "temperatureOutside": _phys(temp.temperatureOutside_C.value, "degC"), - } - except Exception: - log.debug("temperatureOutsideStatus unavailable", exc_info=True) + log.debug("odometer unavailable", exc_info=True) return result or None -def extract_readiness(vehicle) -> dict | None: - try: - d = vehicle.domains["readiness"] - except KeyError: - return None - +def extract_connectivity(vehicle) -> dict | None: result: dict = {} try: - rs = d["readinessStatus"] - conn = rs.connectionState - warn = rs.connectionWarning - result["readinessStatus"] = { - "connectionState": { - "isOnline": conn.isOnline.value, - "isActive": conn.isActive.value, - }, - "connectionWarning": { - "insufficientBatteryLevelWarning": warn.insufficientBatteryLevelWarning.value, - "insufficientBatteryLevelError": warn.insufficientBatteryLevelError.value, - }, - } + state = str(vehicle.connection_state) + result["state"] = state except Exception: - log.debug("readinessStatus unavailable", exc_info=True) + log.debug("connectivity.state unavailable", exc_info=True) + + return result or None + +def extract_vehicle(vehicle) -> dict | None: + result: dict = {} + + try: + state = str(vehicle.state) + result["state"] = state + except Exception: + log.debug("vehicle.state unavailable", exc_info=True) + + return result or None + +def extract_position(vehicle) -> dict | None: + result: dict = {} + + try: + pos = vehicle.position + result["lat"] = pos.latitude.value + result["lon"] = pos.longitude.value + + except Exception: + log.debug("position unavailable", exc_info=True) return result or None -def extract_parking(vehicle) -> dict | None: - try: - d = vehicle.domains["parking"] - except KeyError: - return None - +def extract_doors(vehicle) -> dict | None: result: dict = {} try: - pos = d["parkingPosition"] - result["parkingPosition"] = { - "lat": pos.lat.value, - "lon": pos.lon.value, - } - except Exception: - log.debug("parkingPosition unavailable", exc_info=True) - - return result or None - - -def extract_access(vehicle) -> dict | None: - try: - d = vehicle.domains["access"] - except KeyError: - return None - - result: dict = {} - - try: - acc = d["accessStatus"] - result["accessStatus"] = { - "overallStatus": _str(acc.overallStatus), + doors = vehicle.doors + result["doors"] = { + "overallState": _str(doors.lock_state), "doors": { name: { - "lockState": _str(door.lockState), - "openState": _str(door.openState), + "lockState": _str(door.lock_state), + "openState": _str(door.open_state), } - for name, door in acc.doors.items() + for name, door in doors.doors.items() }, "windows": { - name: {"openState": _str(win.openState)} - for name, win in acc.windows.items() + name: {"openState": _str(win.open_state)} + for name, win in vehicle.windows.windows.items() }, } except Exception: - log.debug("accessStatus unavailable", exc_info=True) + log.debug("vehicle.doors unavailable", exc_info=True) return result or None -ALL_DOMAINS: dict[str, callable] = { +ALL_DOMAINS: dict[str, Callable] = { "charging": extract_charging, "climatisation": extract_climatisation, - "measurements": extract_measurements, - "readiness": extract_readiness, - "parking": extract_parking, - "access": extract_access, + "electric_drive": extract_electric_drive, + "connectivity": extract_connectivity, + "vehicle": extract_vehicle, + "position": extract_position, + "doors": extract_doors, } @@ -265,8 +240,8 @@ def apply_procedural(record: dict) -> dict: """ proc: dict = {} - range_at_soc = _get(record, "measurements", "rangeStatus", "totalRange", "value") - soc = _get(record, "charging", "batteryStatus", "currentSOC", "value") + range_at_soc = _get(record, "electric_drive", "range", "value") + soc = _get(record, "electric_drive", "battery", "soc", "value") if range_at_soc is not None and soc: proc["range_at_100"] = _phys(round(100 * float(range_at_soc) / float(soc), 1), "km") @@ -276,11 +251,11 @@ def apply_procedural(record: dict) -> dict: # format so the GUI picks up the field automatically. # # Example — usable energy estimated from SOC and battery capacity: - # soc = _get(record, "charging", "batteryStatus", "currentSOC", "value") + # soc = _get(record, "electric_drive", "battery", "soc", "value") # if soc is not None: # proc["energy_stored"] = {"value": round(soc / 100 * 77.0, 1), "unit": "kWh"} # ───────────────────────────────────────────────────────────────────── if proc: record["procedural"] = proc - return record + return record \ No newline at end of file diff --git a/server/network.py b/server/network.py index b7703b0..805d756 100644 --- a/server/network.py +++ b/server/network.py @@ -3,7 +3,7 @@ import logging import socket import threading -from utils.jay_diff import jay_diff_full +from jaydiff.diff import diff_full as jay_diff_full log = logging.getLogger(__name__) diff --git a/server/we_connect.py b/server/we_connect.py index 61fa8f6..453e445 100644 --- a/server/we_connect.py +++ b/server/we_connect.py @@ -2,35 +2,51 @@ import logging import sys try: - from weconnect import weconnect + from carconnectivity.carconnectivity import CarConnectivity 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) log = logging.getLogger(__name__) -def connect(username: str, password: str): - wc = weconnect.WeConnect( - username=username, - password=password, - updateAfterLogin=False, - loginOnInit=True, - ) - wc.login() - wc.update() - return wc +def connect(username: str, password: str) -> CarConnectivity: + config = { + "carConnectivity": { + "connectors": [ + { + "type": "volkswagen", + "config": { + "username": username, + "password": password, + }, + } + ] + } + } + 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.""" - vehicles = list(wc.vehicles.values()) + vehicles = list(cc.get_garage().list_vehicles()) if not vehicles: - return None, "No vehicles found in this WeConnect account" + return None, "No vehicles found in this CarConnectivity account" if vin: vehicle = next((v for v in vehicles if v.vin.value == vin), None) if not vehicle: available = [v.vin.value for v in vehicles] return None, f"VIN {vin} not found. Available: {available}" return vehicle, None - return vehicles[0], None \ No newline at end of file + return vehicles[0], None + + +def update(cc: CarConnectivity) -> None: + cc.fetch_all() \ No newline at end of file diff --git a/utils/__init__.py b/utils/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/utils/jay_diff.py b/utils/jay_diff.py deleted file mode 100644 index a678d8b..0000000 --- a/utils/jay_diff.py +++ /dev/null @@ -1,579 +0,0 @@ -from copy import deepcopy - - -def jay_diff_rev(_old, _new): - delta = {} - for k in _new.keys(): - if k in _old: - if isinstance(_new[k], dict): - res = jay_diff_rev(_old[k], _new[k]) - if len(res.keys()): - delta[k] = res - else: - _old[k] = _new[k] - delta[k] = _new[k] - - return delta - - -def jay_diff_add(_old, _new): - __old = deepcopy(_old) - delta = jay_diff_rev(__old, _new) - return delta - - -def jay_diff_del(_old, _new): - __new = deepcopy(_new) - delta = jay_diff_rev(__new, _old) - return delta - - -def jay_diff_upd(_old, _new, add_if_not_exist=False): - delta = {} - for k in _new.keys(): - if k in _old: - if isinstance(_new[k], dict): - res = jay_diff_upd(_old[k], _new[k], add_if_not_exist) - if len(res.keys()): - delta[k] = res - elif _new[k] != _old[k]: - delta[k] = _new[k] - elif add_if_not_exist: - delta[k] = _new[k] - - return delta - - -def jay_diff_upd_add(_old, _new): - return jay_diff_upd(_old, _new, add_if_not_exist=True) - - -def jay_diff_full(_old, _new, combine_upd_add=True): - diff = {} - if combine_upd_add: - res = jay_diff_upd_add(_old, _new) - if len(res.keys()): - diff['update_add'] = res - res = jay_diff_del(_old, _new) - if len(res.keys()): - diff['delete'] = res - else: - res = jay_diff_upd(_old, _new) - if len(res.keys()): - diff['update'] = res - res = jay_diff_add(_old, _new) - if len(res.keys()): - diff['add'] = res - res = jay_diff_del(_old, _new) - if len(res.keys()): - diff['delete'] = res - - return diff - - -def jay_merge_update(_old, _new): - for k, v in _new.items(): - if isinstance(v, dict): - r = jay_merge_update(_old.get(k, {}), v) - _old[k] = r - else: - _old[k] = _new[k] - return _old - - -def jay_merge_add(_old, _new): - for k, v in _new.items(): - if isinstance(v, dict): - r = jay_merge_add(_old.get(k, {}), v) - if k in _old: - _old[k].update(r) - else: - _old[k] = r - else: - _old[k] = _new[k] - return _old - - -def jay_merge_delete(_old, _new): - for k, v in _new.items(): - if isinstance(v, dict): - jay_merge_delete(_old[k], _new[k]) - if len(_old[k].keys()) == 0: - del _old[k] - else: - if k in _old: - del _old[k] - - return _old - - -def jay_merge_full(_old, _diff): - _is_diff = False - res = _old - if "update" in _diff: - res = jay_merge_update(res, _diff['update']) - _is_diff = True - if "update_add" in _diff: - res = jay_merge_update(res, _diff['update_add']) - _is_diff = True - if "add" in _diff: - res = jay_merge_add(res, _diff['add']) - _is_diff = True - if "delete" in _diff: - res = jay_merge_delete(res, _diff['delete']) - _is_diff = True - - if not _is_diff and not _old: - res = _diff - - return res - -def main() -> None: - result = jay_diff_add({}, {'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}}) - assert result == {'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}} - - result = jay_diff_add({'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'}, {'a': 'hamburger', 'b': 'fries', 'c': 'coke'}) - assert result == {} - - result = jay_diff_add({'a': 'hamburger', 'b': 'fries', 'c': 'pepsi', 'd': 'icecream'}, {'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'}) - assert result == {} - - result = jay_diff_add({'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'}, {'a': 'hamburger', 'd': 'icecream', 'c': 'coke'}) - assert result == {'d':'icecream'} - - result = jay_diff_add({'a': 'hamburger', 'c': 'pepsi', 'd': 'icecream'}, {'a': 'hamburger', 'b': 'fries', 'c': 'coke'}) - assert result == {'b':'fries'} - - result = jay_diff_add({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke'}}, {'c': {'f': 'coffee'}}) - assert result == {'c': {'f': 'coffee'}} - - result = jay_diff_add({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a': 'hamburger', 'b': 'fries', 'c': {'f': 'coffee'}}) - assert result == {} - - result = jay_diff_del({'a': 'hamburger', 'c': 'pepsi', 'd': 'icecream'}, {'a': 'hamburger', 'b': 'fries', 'c': 'coke'}) - assert result == {'d':'icecream'} - - result = jay_diff_del({'a': 'hamburger', 'b': 'fries', 'c': 'coke'}, {'a': 'hamburger', 'b': 'fries', 'c': 'coke'}) - assert result == {} - - result = jay_diff_del({'a': 'hamburger', 'b': 'fries', 'c': 'coke'}, {'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'}) - assert result == {} - - result = jay_diff_del({'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'}, {'a': 'hamburger', 'b': 'fries'}) - assert result == {'c':'pepsi'} - - result = jay_diff_del({'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'}, {'a': 'hamburger', 'b': 'fries', 'c': 'coke'}) - assert result == {} - - result = jay_diff_del({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a': 'hamburger', 'b': 'fries'}) - assert result == {'c': {'e': 'coke', 'f': 'coffee'}} - - result = jay_diff_del({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'c': {'e': 'coke'}}) - assert result == {'a':'hamburger', 'b':'fries', 'c': {'f': 'coffee'}} - - result = jay_diff_del({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a':'hamburger', 'b':'fries', 'c': {'e': 'pepsi', 'f': 'coffee'}}) - assert result == {} - - result = jay_diff_upd({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a':'hotdog', 'b':'potatoes'}) - assert result == {'a':'hotdog', 'b':'potatoes'} - - result = jay_diff_upd({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'c': {'e': 'pepsi'}}) - assert result == {'c': {'e': 'pepsi'}} - - result = jay_diff_upd({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a':'hotdog', 'b':'fries'}) - assert result == {'a':'hotdog'} - - result = jay_diff_upd({'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'}, {'a': 'hamburger', 'd': 'icecream', 'c': 'coke'}) - assert result == {'c':'coke'} - - result = jay_diff_upd({'a': 'hamburger', 'c': 'pepsi', 'd': 'icecream'}, {'a': 'hamburger', 'b': 'fries', 'c': 'coke'}) - assert result == {'c':'coke'} - - result = jay_diff_upd({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke'}}, {'c': {'f': 'coffee'}}) - assert result == {} - - result = jay_diff_upd({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a':'hamburger', 'b':'fries', 'c': {'e': 'pepsi', 'f': 'coffee'}}) - assert result == {'c': {'e': 'pepsi'}} - - # Update/add - result = jay_diff_upd_add({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a':'hotdog', 'b':'potatoes', 'd': 'icecream'}) - assert result == {'a':'hotdog', 'b':'potatoes', 'd': 'icecream'} - - result = jay_diff_upd_add({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'c': {'e': 'pepsi', 'g': 'tea', 'h': 'cake'}, 'b':'potatoes'}) - assert result == {'b':'potatoes', 'c': {'e': 'pepsi', 'g': 'tea', 'h': 'cake'}} - - result = jay_diff_upd_add({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a':'hotdog', 'b':'fries'}) - assert result == {'a':'hotdog'} - - result = jay_diff_upd_add({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a':'hamburger', 'b':'fries', 'c': {'e': 'pepsi', 'f': 'coffee'}}) - assert result == {'c': {'e': 'pepsi'}} - - ##################### - result = jay_diff_upd_add({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a':'hotdog', 'b':'potatoes'}) - assert result == {'a':'hotdog', 'b':'potatoes'} - - result = jay_diff_upd_add({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'c': {'e': 'pepsi'}}) - assert result == {'c': {'e': 'pepsi'}} - - result = jay_diff_upd_add({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a':'hotdog', 'b':'fries'}) - assert result == {'a':'hotdog'} - - result = jay_diff_upd_add({'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'}, {'a': 'hamburger', 'd': 'icecream', 'c': 'coke'}) - assert result == {'c': 'coke', 'd': 'icecream'} - - result = jay_diff_upd_add({'a': 'hamburger', 'c': 'pepsi', 'd': 'icecream'}, {'a': 'hamburger', 'b': 'fries', 'c': 'coke'}) - assert result == {'b': 'fries', 'c': 'coke'} - - result = jay_diff_upd_add({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke'}}, {'c': {'f': 'coffee'}}) - assert result == {'c': {'f': 'coffee'}} - - result = jay_diff_upd_add({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a':'hamburger', 'b':'fries', 'c': {'e': 'pepsi', 'f': 'coffee'}}) - assert result == {'c': {'e': 'pepsi'}} - - result = jay_diff_upd_add({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a':'hamburger', 'b':'fries', 'c': {'e': 'pepsi', 'f': 'coffee', 'h': 'cake'}}) - assert result == {'c': {'e': 'pepsi', 'h': 'cake'}} - - # jay_diff_full - combine_upd_add = False - - result = jay_diff_full({}, {'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, combine_upd_add) - assert result == {'add': {'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}}} - - result = jay_diff_full({'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'}, {'a': 'hamburger', 'b': 'fries', 'c': 'coke'}, combine_upd_add) - assert result == {'update': {'c': 'coke'}} - - result = jay_diff_full({'a': 'hamburger', 'b': 'fries', 'c': 'pepsi', 'd': 'icecream'}, {'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'}, combine_upd_add) - assert result == {'delete': {'d': 'icecream'}} - - result = jay_diff_full({'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'}, {'a': 'hamburger', 'd': 'icecream', 'c': 'coke'}, combine_upd_add) - assert result == {'add': {'d': 'icecream'}, 'delete': {'b': 'fries'}, 'update': {'c': 'coke'}} - - result = jay_diff_full({'a': 'hamburger', 'c': 'pepsi', 'd': 'icecream'}, {'a': 'hamburger', 'b': 'fries', 'c': 'coke'}, combine_upd_add) - assert result == {'add': {'b': 'fries'}, 'delete': {'d': 'icecream'}, 'update': {'c': 'coke'}} - - result = jay_diff_full({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke'}}, {'c': {'f': 'coffee'}}, combine_upd_add) - assert result == {'add': {'c': {'f': 'coffee'}}, 'delete': {'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke'}}} - - result = jay_diff_full({'a': 'hamburger', 'c': 'pepsi', 'd': 'icecream'}, {'a': 'hamburger', 'b': 'fries', 'c': 'coke'}, combine_upd_add) - assert result == {'add': {'b': 'fries'}, 'delete': {'d': 'icecream'}, 'update': {'c': 'coke'}} - - result = jay_diff_full({'a': 'hamburger', 'b': 'fries', 'c': 'coke'}, {'a': 'hamburger', 'b': 'fries', 'c': 'coke'}, combine_upd_add) - assert result == {} - - result = jay_diff_full({'a': 'hamburger', 'b': 'fries', 'c': 'coke'}, {'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'}, combine_upd_add) - assert result == {'update': {'c': 'pepsi'}} - - result = jay_diff_full({'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'}, {'a': 'hamburger', 'b': 'fries'}, combine_upd_add) - assert result == {'delete': {'c': 'pepsi'}} - - result = jay_diff_full({'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'}, {'a': 'hamburger', 'b': 'fries', 'c': 'coke'}, combine_upd_add) - assert result == {'update': {'c': 'coke'}} - - result = jay_diff_full({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a': 'hamburger', 'b': 'fries'}, combine_upd_add) - assert result == {'delete': {'c': {'e': 'coke', 'f': 'coffee'}}} - - result = jay_diff_full({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a': 'hamburger', 'b': 'fries', 'c': {'f': 'coffee'}}, combine_upd_add) - assert result == {'delete': {'c': {'e': 'coke'}}} - - result = jay_diff_full({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'c': {'e': 'coke'}}, combine_upd_add) - assert result == {'delete': {'a': 'hamburger', 'b': 'fries', 'c': {'f': 'coffee'}}} - - result = jay_diff_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a':'hotdog', 'b':'potatoes'}, combine_upd_add) - assert result == {'delete': {'c': {'e': 'coke', 'f': 'coffee'}}, 'update': {'a': 'hotdog', 'b': 'potatoes'}} - - result = jay_diff_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'c': {'e': 'pepsi'}}, combine_upd_add) - assert result == {'delete': {'a': 'hamburger', 'b': 'fries', 'c': {'f': 'coffee'}}, 'update': {'c': {'e': 'pepsi'}}} - - result = jay_diff_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a':'hotdog', 'b':'fries'}, combine_upd_add) - assert result == {'delete': {'c': {'e': 'coke', 'f': 'coffee'}}, 'update': {'a': 'hotdog'}} - - result = jay_diff_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a':'hamburger', 'b':'fries', 'c': {'e': 'pepsi', 'f': 'coffee'}}, combine_upd_add) - assert result == {'update': {'c': {'e': 'pepsi'}}} - - result = jay_diff_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a':'hotdog', 'b':'potatoes', 'd': 'icecream'}, combine_upd_add) - assert result == {'add': {'d': 'icecream'}, 'delete': {'c': {'e': 'coke', 'f': 'coffee'}}, 'update': {'a': 'hotdog', 'b': 'potatoes'}} - - result = jay_diff_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'c': {'e': 'pepsi', 'g': 'tea', 'h': 'cake'}, 'b':'potatoes'}, combine_upd_add) - assert result == {'add': {'c': {'g': 'tea', 'h': 'cake'}}, 'delete': {'a': 'hamburger', 'c': {'f': 'coffee'}}, 'update': {'b': 'potatoes', 'c': {'e': 'pepsi'}}} - - result = jay_diff_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a':'hotdog', 'b':'fries'}, combine_upd_add) - assert result == {'delete': {'c': {'e': 'coke', 'f': 'coffee'}}, 'update': {'a': 'hotdog'}} - - result = jay_diff_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a':'hamburger', 'b':'fries', 'c': {'e': 'pepsi', 'f': 'coffee'}}, combine_upd_add) - assert result == {'update': {'c': {'e': 'pepsi'}}} - - result = jay_diff_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a':'hamburger', 'b':'fries', 'c': {'e': 'pepsi', 'f': 'coffee', 'h': 'cake'}}, combine_upd_add) - assert result == {'add': {'c': {'h': 'cake'}}, 'update': {'c': {'e': 'pepsi'}}} - - # ToDo: jay_diff_full - combine_upd_add = True - - result = jay_diff_full({}, {'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, combine_upd_add) - assert result == {'update_add': {'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}}} - - result = jay_diff_full({'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'}, {'a': 'hamburger', 'b': 'fries', 'c': 'coke'}, combine_upd_add) - assert result == {'update_add': {'c': 'coke'}} - - result = jay_diff_full({'a': 'hamburger', 'b': 'fries', 'c': 'pepsi', 'd': 'icecream'}, {'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'}, combine_upd_add) - assert result == {'delete': {'d': 'icecream'}} - - result = jay_diff_full({'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'}, {'a': 'hamburger', 'd': 'icecream', 'c': 'coke'}, combine_upd_add) - assert result == {'delete': {'b': 'fries'}, 'update_add': {'c': 'coke', 'd': 'icecream'}} - - result = jay_diff_full({'a': 'hamburger', 'c': 'pepsi', 'd': 'icecream'}, {'a': 'hamburger', 'b': 'fries', 'c': 'coke'}, combine_upd_add) - assert result == {'delete': {'d': 'icecream'}, 'update_add': {'b': 'fries', 'c': 'coke'}} - - result = jay_diff_full({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke'}}, {'c': {'f': 'coffee'}}, combine_upd_add) - assert result == {'update_add': {'c': {'f': 'coffee'}}, 'delete': {'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke'}}} - - result = jay_diff_full({'a': 'hamburger', 'c': 'pepsi', 'd': 'icecream'}, {'a': 'hamburger', 'b': 'fries', 'c': 'coke'}, combine_upd_add) - assert result == {'delete': {'d': 'icecream'}, 'update_add': {'b': 'fries', 'c': 'coke'}} - - result = jay_diff_full({'a': 'hamburger', 'b': 'fries', 'c': 'coke'}, {'a': 'hamburger', 'b': 'fries', 'c': 'coke'}, combine_upd_add) - assert result == {} - - result = jay_diff_full({'a': 'hamburger', 'b': 'fries', 'c': 'coke'}, {'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'}, combine_upd_add) - assert result == {'update_add': {'c': 'pepsi'}} - - result = jay_diff_full({'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'}, {'a': 'hamburger', 'b': 'fries'}, combine_upd_add) - assert result == {'delete': {'c': 'pepsi'}} - - result = jay_diff_full({'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'}, {'a': 'hamburger', 'b': 'fries', 'c': 'coke'}, combine_upd_add) - assert result == {'update_add': {'c': 'coke'}} - - result = jay_diff_full({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a': 'hamburger', 'b': 'fries'}, combine_upd_add) - assert result == {'delete': {'c': {'e': 'coke', 'f': 'coffee'}}} - - result = jay_diff_full({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a': 'hamburger', 'b': 'fries', 'c': {'f': 'coffee'}}, combine_upd_add) - assert result == {'delete': {'c': {'e': 'coke'}}} - - result = jay_diff_full({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'c': {'e': 'coke'}}, combine_upd_add) - assert result == {'delete': {'a': 'hamburger', 'b': 'fries', 'c': {'f': 'coffee'}}} - - result = jay_diff_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a':'hotdog', 'b':'potatoes'}, combine_upd_add) - assert result == {'delete': {'c': {'e': 'coke', 'f': 'coffee'}}, 'update_add': {'a': 'hotdog', 'b': 'potatoes'}} - - result = jay_diff_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'c': {'e': 'pepsi'}}, combine_upd_add) - assert result == {'delete': {'a': 'hamburger', 'b': 'fries', 'c': {'f': 'coffee'}}, 'update_add': {'c': {'e': 'pepsi'}}} - - result = jay_diff_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a':'hotdog', 'b':'fries'}, combine_upd_add) - assert result == {'delete': {'c': {'e': 'coke', 'f': 'coffee'}}, 'update_add': {'a': 'hotdog'}} - - result = jay_diff_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a':'hamburger', 'b':'fries', 'c': {'e': 'pepsi', 'f': 'coffee'}}, combine_upd_add) - assert result == {'update_add': {'c': {'e': 'pepsi'}}} - - result = jay_diff_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a':'hotdog', 'b':'potatoes', 'd': 'icecream'}, combine_upd_add) - assert result == {'delete': {'c': {'e': 'coke', 'f': 'coffee'}}, 'update_add': {'a': 'hotdog', 'b': 'potatoes', 'd': 'icecream'}} - - result = jay_diff_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'c': {'e': 'pepsi', 'g': 'tea', 'h': 'cake'}, 'b':'potatoes'}, combine_upd_add) - assert result == {'delete': {'a': 'hamburger', 'c': {'f': 'coffee'}}, 'update_add': {'b': 'potatoes', 'c': {'e': 'pepsi', 'g': 'tea', 'h': 'cake'}}} - - result = jay_diff_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a':'hotdog', 'b':'fries'}, combine_upd_add) - assert result == {'delete': {'c': {'e': 'coke', 'f': 'coffee'}}, 'update_add': {'a': 'hotdog'}} - - result = jay_diff_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a':'hamburger', 'b':'fries', 'c': {'e': 'pepsi', 'f': 'coffee'}}, combine_upd_add) - assert result == {'update_add': {'c': {'e': 'pepsi'}}} - - result = jay_diff_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a':'hamburger', 'b':'fries', 'c': {'e': 'pepsi', 'f': 'coffee', 'h': 'cake'}}, combine_upd_add) - assert result == {'update_add': {'c': {'e': 'pepsi', 'h': 'cake'}}} - - # Merge - result = jay_merge_add({}, {'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}}) - assert result == {'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}} - - result = jay_merge_add({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'d': 'icecream'}) - assert result == {'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}, 'd':'icecream'} - - result = jay_merge_add({'a': 'hamburger', 'b': 'fries', 'c': {'f': 'coffee'}}, {'d': 'icecream'}) - assert result == {'a':'hamburger', 'b':'fries', 'c': {'f': 'coffee'}, 'd':'icecream'} - - result = jay_merge_add({'a': 'hamburger', 'b': 'fries', 'c': {'f': 'coffee'}, 'd': 'icecream'}, {'d': 'icecream'}) - assert result == {'a':'hamburger', 'b':'fries', 'c': {'f': 'coffee'}, 'd':'icecream'} - - result = jay_merge_delete({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}, 'd': 'icecream'}, {'d': 'icecream'}) - assert result == {'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}} - - result = jay_merge_delete({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'c': {'f': 'coffee'}}) - assert result == {'a':'hamburger', 'b':'fries', 'c': {'e': 'coke'}} - - result = jay_merge_delete({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'c': {'e': 'coke'}}) - assert result == {'a':'hamburger', 'b':'fries', 'c': {'f': 'coffee'}} - - # jay_merge_full - # the no-jaydiff case - result = jay_merge_full({}, {'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}}) - assert result == {'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}} - - # combine_upd_add = False - result = jay_merge_full({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'add': {'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}}}) - assert result == {'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}} - - result = jay_merge_full({'a': 'hamburger', 'b': 'fries', 'c': 'coke'}, {'update': {'c': 'pepsi'}}) - assert result == {'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'} - - result = jay_merge_full({'a': 'hamburger', 'b': 'fries', 'c': 'pepsi', 'd': 'icecream'}, {'delete': {'d': 'icecream'}}) - assert result == {'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'} - - result = jay_merge_full({'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'}, {'add': {'d': 'icecream'}, 'delete': {'b': 'fries'}, 'update': {'c': 'coke'}}) - assert result == {'a': 'hamburger', 'd': 'icecream', 'c': 'coke'} - - result = jay_merge_full({'a': 'hamburger', 'c': 'pepsi', 'd': 'icecream'}, {'add': {'b': 'fries'}, 'delete': {'d': 'icecream'}, 'update': {'c': 'coke'}}) - assert result == {'a': 'hamburger', 'b': 'fries', 'c': 'coke'} - - result = jay_merge_full({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke'}}, {'add': {'c': {'f': 'coffee'}}, 'delete': {'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke'}}}) - assert result == {'c': {'f': 'coffee'}} - - result = jay_merge_full({'a': 'hamburger', 'c': 'pepsi', 'd': 'icecream'}, {'add': {'b': 'fries'}, 'delete': {'d': 'icecream'}, 'update': {'c': 'coke'}}) - assert result == {'a': 'hamburger', 'b': 'fries', 'c': 'coke'} - - result = jay_merge_full({'a': 'hamburger', 'b': 'fries', 'c': 'coke'}, {}) - assert result == {'a': 'hamburger', 'b': 'fries', 'c': 'coke'} - - result = jay_merge_full({'a': 'hamburger', 'b': 'fries', 'c': 'coke'}, {'update': {'c': 'pepsi'}}) - assert result == {'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'} - - result = jay_merge_full({'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'}, {'delete': {'c': 'pepsi'}}) - assert result == {'a': 'hamburger', 'b': 'fries'} - - result = jay_merge_full({'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'}, {'update': {'c': 'coke'}}) - assert result == {'a': 'hamburger', 'b': 'fries', 'c': 'coke'} - - result = jay_merge_full({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'delete': {'c': {'e': 'coke', 'f': 'coffee'}}}) - assert result == {'a': 'hamburger', 'b': 'fries'} - - result = jay_merge_full({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'delete': {'c': {'e': 'coke'}}}) - assert result == {'a': 'hamburger', 'b': 'fries', 'c': {'f': 'coffee'}} - - result = jay_merge_full({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'delete': {'a': 'hamburger', 'b': 'fries', 'c': {'f': 'coffee'}}}) - assert result == {'c': {'e': 'coke'}} - - result = jay_merge_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'delete': {'c': {'e': 'coke', 'f': 'coffee'}}, 'update': {'a': 'hotdog', 'b': 'potatoes'}}) - assert result == {'a':'hotdog', 'b':'potatoes'} - - result = jay_merge_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'delete': {'a': 'hamburger', 'b': 'fries', 'c': {'f': 'coffee'}}, 'update': {'c': {'e': 'pepsi'}}}) - assert result == {'c': {'e': 'pepsi'}} - - result = jay_merge_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'delete': {'c': {'e': 'coke', 'f': 'coffee'}}, 'update': {'a': 'hotdog'}}) - assert result == {'a':'hotdog', 'b':'fries'} - - result = jay_merge_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'update': {'c': {'e': 'pepsi'}}}) - assert result == {'a':'hamburger', 'b':'fries', 'c': {'e': 'pepsi', 'f': 'coffee'}} - - result = jay_merge_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'add': {'d': 'icecream'}, 'delete': {'c': {'e': 'coke', 'f': 'coffee'}}, 'update': {'a': 'hotdog', 'b': 'potatoes'}}) - assert result == {'a':'hotdog', 'b':'potatoes', 'd': 'icecream'} - - result = jay_merge_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'add': {'c': {'g': 'tea', 'h': 'cake'}}, 'delete': {'a': 'hamburger', 'c': {'f': 'coffee'}}, 'update': {'b': 'potatoes', 'c': {'e': 'pepsi'}}}) - assert result == {'c': {'e': 'pepsi', 'g': 'tea', 'h': 'cake'}, 'b':'potatoes'} - - result = jay_merge_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'delete': {'c': {'e': 'coke', 'f': 'coffee'}}, 'update': {'a': 'hotdog'}}) - assert result == {'a':'hotdog', 'b':'fries'} - - result = jay_merge_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'update': {'c': {'e': 'pepsi'}}}) - assert result == {'a':'hamburger', 'b':'fries', 'c': {'e': 'pepsi', 'f': 'coffee'}} - - result = jay_merge_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'add': {'c': {'h': 'cake'}}, 'update': {'c': {'e': 'pepsi'}}}) - assert result == {'a':'hamburger', 'b':'fries', 'c': {'e': 'pepsi', 'f': 'coffee', 'h': 'cake'}} - - # ToDo: jay_merge_full - # combine_upd_add = True - - result = jay_merge_full({}, {'update_add': {'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}}}) - assert result == {'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}} - - result = jay_merge_full({'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'}, {'update_add': {'c': 'coke'}}) - assert result == {'a': 'hamburger', 'b': 'fries', 'c': 'coke'} - - result = jay_merge_full({'a': 'hamburger', 'b': 'fries', 'c': 'pepsi', 'd': 'icecream'}, {'delete': {'d': 'icecream'}}) - assert result == {'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'} - - result = jay_merge_full({'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'}, {'delete': {'b': 'fries'}, 'update_add': {'c': 'coke', 'd': 'icecream'}}) - assert result == {'a': 'hamburger', 'd': 'icecream', 'c': 'coke'} - - result = jay_merge_full({'a': 'hamburger', 'c': 'pepsi', 'd': 'icecream'}, {'delete': {'d': 'icecream'}, 'update_add': {'b': 'fries', 'c': 'coke'}}) - assert result == {'a': 'hamburger', 'b': 'fries', 'c': 'coke'} - - result = jay_merge_full({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke'}}, {'update_add': {'c': {'f': 'coffee'}}, 'delete': {'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke'}}}) - assert result == {'c': {'f': 'coffee'}} - - result = jay_merge_full({'a': 'hamburger', 'c': 'pepsi', 'd': 'icecream'}, {'delete': {'d': 'icecream'}, 'update_add': {'b': 'fries', 'c': 'coke'}}) - assert result == {'a': 'hamburger', 'b': 'fries', 'c': 'coke'} - - result = jay_merge_full({'a': 'hamburger', 'b': 'fries', 'c': 'coke'}, {}) - assert result == {'a': 'hamburger', 'b': 'fries', 'c': 'coke'} - - result = jay_merge_full({'a': 'hamburger', 'b': 'fries', 'c': 'coke'}, {'update_add': {'c': 'pepsi'}}) - assert result == {'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'} - - result = jay_merge_full({'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'}, {'delete': {'c': 'pepsi'}}) - assert result == {'a': 'hamburger', 'b': 'fries'} - - result = jay_merge_full({'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'}, {'update_add': {'c': 'coke'}}) - assert result == {'a': 'hamburger', 'b': 'fries', 'c': 'coke'} - - result = jay_merge_full({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'delete': {'c': {'e': 'coke', 'f': 'coffee'}}}) - assert result == {'a': 'hamburger', 'b': 'fries'} - - result = jay_merge_full({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'delete': {'c': {'e': 'coke'}}}) - assert result == {'a': 'hamburger', 'b': 'fries', 'c': {'f': 'coffee'}} - - result = jay_merge_full({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'delete': {'a': 'hamburger', 'b': 'fries', 'c': {'f': 'coffee'}}}) - assert result == {'c': {'e': 'coke'}} - - result = jay_merge_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'delete': {'c': {'e': 'coke', 'f': 'coffee'}}, 'update_add': {'a': 'hotdog', 'b': 'potatoes'}}) - assert result == {'a':'hotdog', 'b':'potatoes'} - - result = jay_merge_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'delete': {'a': 'hamburger', 'b': 'fries', 'c': {'f': 'coffee'}}, 'update_add': {'c': {'e': 'pepsi'}}}) - assert result == {'c': {'e': 'pepsi'}} - - result = jay_merge_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'delete': {'c': {'e': 'coke', 'f': 'coffee'}}, 'update_add': {'a': 'hotdog'}}) - assert result == {'a':'hotdog', 'b':'fries'} - - result = jay_merge_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'update_add': {'c': {'e': 'pepsi'}}}) - assert result == {'a':'hamburger', 'b':'fries', 'c': {'e': 'pepsi', 'f': 'coffee'}} - - result = jay_merge_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'delete': {'c': {'e': 'coke', 'f': 'coffee'}}, 'update_add': {'a': 'hotdog', 'b': 'potatoes', 'd': 'icecream'}}) - assert result == {'a':'hotdog', 'b':'potatoes', 'd': 'icecream'} - - result = jay_merge_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'delete': {'a': 'hamburger', 'c': {'f': 'coffee'}}, 'update_add': {'b': 'potatoes', 'c': {'e': 'pepsi', 'g': 'tea', 'h': 'cake'}}}) - assert result == {'b':'potatoes', 'c': {'e': 'pepsi', 'g': 'tea', 'h': 'cake'}} - - result = jay_merge_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'delete': {'c': {'e': 'coke', 'f': 'coffee'}}, 'update_add': {'a': 'hotdog'}}) - assert result == {'a':'hotdog', 'b':'fries'} - - result = jay_merge_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'update_add': {'c': {'e': 'pepsi'}}}) - assert result == {'a':'hamburger', 'b':'fries', 'c': {'e': 'pepsi', 'f': 'coffee'}} - - result = jay_merge_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'update_add': {'c': {'e': 'pepsi', 'h': 'cake'}}}) - assert result == {'a':'hamburger', 'b':'fries', 'c': {'e': 'pepsi', 'f': 'coffee', 'h': 'cake'}} - - result = jay_merge_full({}, {'add': {'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}}}) - assert result == {'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}} - - result = jay_merge_full({}, {'add': {'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': {'coffee': {'h': 'black'}}}}}) - assert result == {'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': {'coffee': {'h': 'black'}}}} - - result = jay_merge_full({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': {'coffee': {'h': 'black'}}}}, {'add': {'c': {'f': {'coffee': {'j': 'sugar'}}}}}) - assert result == {'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': {'coffee': {'h': 'black', 'j': 'sugar'}}}} - - old = {'charging': { - 'batteryStatus': {'value': {'carCapturedTimestamp': '2024-10-19T05:34:34Z', 'cruisingRangeElectric_km': 192}}, - 'chargingStatus': {'value': {'carCapturedTimestamp': '2024-10-19T05:34:34Z'}}, - 'chargingSettings': {'value': {'carCapturedTimestamp': '2024-10-19T05:34:30Z'}}, - 'plugStatus': {'value': {'carCapturedTimestamp': '2024-10-19T05:34:29Z'}}}} - update = {'charging': { - 'batteryStatus': {'value': {'carCapturedTimestamp': '2024-10-19T05:35:46Z', 'cruisingRangeElectric_km': 191}}, - 'chargingStatus': {'value': {'carCapturedTimestamp': '2024-10-19T05:35:46Z'}}}} - add = {'charging': { - 'batteryStatus': {'value': {'currentSOC_pct': 55}}}, - 'fuelStatus': {'rangeStatus': {'value': {'primaryEngine': {'currentSOC_pct': 55}}}}, - 'measurements': {'fuelLevelStatus': {'value': {'currentSOC_pct': 55}}}} - - delete = {'charging': { - 'chargingSettings': {'value': {'carCapturedTimestamp': '2024-10-19T05:34:30Z'}}, - 'plugStatus': {'value': {'carCapturedTimestamp': '2024-10-19T05:34:29Z'}}}} - - result = jay_merge_full(old, {'update':update, 'add':add, 'delete':delete}) - assert result == old - -if __name__ == '__main__': - main()