From bedf5ed646c079741dbd7208095d0673dac0a124 Mon Sep 17 00:00:00 2001 From: Jens Ahrensfeld Date: Tue, 26 May 2026 13:52:06 +0200 Subject: [PATCH] collect: move vin, interval, host, port, log_dir to credentials file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove --vin, --interval/-i, --output/-o, --host, --port from argparse. All are now read from the credentials JSON (interval_s, host, port, log_dir with sensible defaults). CLI flags -u/-p/-d still override the file; log_dir replaces the old --output/auto-logs logic — logs_dir is always used, no fixed output path option. Co-Authored-By: Claude Sonnet 4.6 --- collect.py | 159 +++++++++++++++++++++-------------------------------- 1 file changed, 63 insertions(+), 96 deletions(-) diff --git a/collect.py b/collect.py index 2670e6f..b2534f0 100644 --- a/collect.py +++ b/collect.py @@ -8,42 +8,39 @@ the WeConnect domain hierarchy: domain → status-object → field. Usage examples -------------- -# Credentials from env vars, all defaults: +# Credentials file with all settings: + python collect.py -c credentials/alex.json + +# Override username/password via env vars: export WC_USER=me@example.com WC_PASS=secret - python collect.py + python collect.py -c credentials/alex.json -# Credentials from a JSON file (output auto-named under ./logs/): - python collect.py -c creds.json -i 600 - -# Credentials from a JSON file, explicit output path: - python collect.py -c creds.json -i 600 -o id3.json - -# Custom domains, 10-minute interval: - python collect.py -u me@example.com -p secret \\ - -d charging,measurements,readiness -i 600 -o id3.json - -# Everything, verbose, keep last 1 000 records: - python collect.py -d all -v --max-records 1000 +# Override domains on the CLI: + python collect.py -c credentials/alex.json -d charging,measurements Available domains: charging, climatisation, measurements, readiness, parking, access -Push server ------------ -The collector listens on --host/--port (default 0.0.0.0:9999). -Each connected TCP client receives every new snapshot as a single -line of JSON (newline-delimited) the moment it is collected. -Connect with: nc localhost 9999 or any TCP client that reads lines. - Credentials file format (JSON) ------------------------------- { - "username": "me@example.com", - "password": "secret", - "vin": "WVWZZZE1ZMP123456", - "domains": ["charging", "measurements", "readiness"] + "credentials": { + "username": "me@example.com", + "password": "secret" + }, + "vin": "WVWZZZE1ZMP123456", + "domains": ["charging", "measurements", "readiness"], + "interval_s": 300, + "host": "0.0.0.0", + "port": 9999, + "log_dir": "./logs" } -All four keys are optional; domains may also be a comma-separated string. -Explicit CLI flags and env vars take precedence. + +Push server +----------- +The collector listens on host/port from the credentials file (default 0.0.0.0:9999). +Each connected TCP client receives every new snapshot as a single +line of JSON (newline-delimited) the moment it is collected. +Connect with: nc localhost 9999 or any TCP client that reads lines. """ import argparse @@ -85,19 +82,14 @@ def run(args: argparse.Namespace) -> None: vin = vehicle.vin.value log.info("Vehicle: %s", vin) - if args.output: - out = Path(args.output) - logs_dir = None - else: - logs_dir = Path("logs") - logs_dir.mkdir(exist_ok=True) - out = auto_out_path(logs_dir, vin) + logs_dir = Path(args.log_dir) + logs_dir.mkdir(parents=True, exist_ok=True) + out = auto_out_path(logs_dir, vin) push_clients, push_lock, push_store_ref = network.start_push_server(args.host, args.port) - if logs_dir is not None: - push_store_ref["preloaded"] = load_last_24h_records(logs_dir, vin) - if push_store_ref["preloaded"]: - log.info("Pre-loaded %d record(s) from the last 24 h", len(push_store_ref["preloaded"])) + push_store_ref["preloaded"] = load_last_24h_records(logs_dir, vin) + if push_store_ref["preloaded"]: + log.info("Pre-loaded %d record(s) from the last 24 h", len(push_store_ref["preloaded"])) current_day = datetime.now(timezone.utc).date() store = load_store(out, vin, args.interval) @@ -111,14 +103,13 @@ def run(args: argparse.Namespace) -> None: try: while True: - if logs_dir is not None: - today = datetime.now(timezone.utc).date() - if today != current_day: - out = auto_out_path(logs_dir, vin) - store = load_store(out, vin, args.interval) - push_store_ref["current"] = store - current_day = today - log.info("Midnight UTC rotation → %s", out) + today = datetime.now(timezone.utc).date() + if today != current_day: + out = auto_out_path(logs_dir, vin) + store = load_store(out, vin, args.interval) + push_store_ref["current"] = store + current_day = today + log.info("Midnight UTC rotation → %s", out) try: wc.update() @@ -152,67 +143,36 @@ def main() -> None: creds.add_argument( "-c", "--credentials", metavar="FILE", - help=( - "JSON file with keys: username, password, vin, domains. " - "Values are overridden by explicit CLI flags and env vars." - ), + help="JSON file with connection settings (see format above).", ) creds.add_argument( "-u", "--username", default=os.environ.get("WC_USER"), - help="WeConnect / MyVolkswagen email", + help="WeConnect / MyVolkswagen email (overrides credentials file)", ) creds.add_argument( "-p", "--password", default=os.environ.get("WC_PASS"), - help="WeConnect password", + help="WeConnect password (overrides credentials file)", ) - p.add_argument("--vin", help="Vehicle VIN (uses first vehicle when omitted)") p.add_argument( "-d", "--domains", default=None, metavar="DOMAIN[,DOMAIN…]", help=( - "Comma-separated domains to collect, or 'all'. " + "Comma-separated domains to collect, or 'all' (overrides credentials file). " f"Available: {', '.join(ALL_DOMAINS)}. " "Default: charging,measurements,readiness" ), ) - p.add_argument( - "-i", "--interval", - type=int, - default=300, - metavar="SECONDS", - help="Collection interval in seconds (default: 300)", - ) - p.add_argument( - "-o", "--output", - default=None, - metavar="FILE", - help="Output file (default: logs/YYYY_MM_DD_HH_MM_SS_.json)", - ) p.add_argument( "--max-records", type=int, default=None, metavar="N", - help="Keep only the last N records in the file (default: unlimited)", + help="Keep only the last N records per file (default: unlimited)", ) - - srv = p.add_argument_group("push server") - srv.add_argument( - "--host", - default="0.0.0.0", - help="Bind address for the push server (default: 0.0.0.0)", - ) - srv.add_argument( - "--port", - type=int, - default=9999, - help="TCP port for the push server (default: 9999)", - ) - p.add_argument("-v", "--verbose", action="store_true", help="Enable debug logging") p.add_argument( "--list-domains", @@ -226,6 +186,7 @@ def main() -> None: print("\n".join(ALL_DOMAINS)) sys.exit(0) + creds_data: dict = {} if args.credentials: creds_path = Path(args.credentials) if not creds_path.exists(): @@ -234,26 +195,32 @@ def main() -> None: creds_data = json.loads(creds_path.read_text()) except json.JSONDecodeError as exc: p.error(f"Invalid JSON in credentials file: {exc}") - if args.username is None: - args.username = creds_data.get("username") - if args.password is None: - args.password = creds_data.get("password") - if args.vin is None: - args.vin = creds_data.get("vin") - if args.domains is None: - raw_domains = creds_data.get("domains") - if isinstance(raw_domains, list): - args.domains = ",".join(raw_domains) - else: - args.domains = raw_domains + + # username / password: env / CLI flag > credentials.username > credentials file root + nested = creds_data.get("credentials", {}) + if args.username is None: + args.username = nested.get("username") or creds_data.get("username") + if args.password is None: + args.password = nested.get("password") or creds_data.get("password") + + args.vin = creds_data.get("vin") if args.domains is None: - args.domains = "charging,measurements,readiness" + raw_domains = creds_data.get("domains") + if isinstance(raw_domains, list): + args.domains = ",".join(raw_domains) + else: + args.domains = raw_domains or "charging,measurements,readiness" + + args.interval = int(creds_data.get("interval_s", 300)) + args.host = creds_data.get("host", "0.0.0.0") + args.port = int(creds_data.get("port", 9999)) + args.log_dir = creds_data.get("log_dir", "./logs") if not args.username or not args.password: p.error( "Username and password are required. " - "Pass them with -u/-p or set WC_USER / WC_PASS environment variables." + "Provide them in the credentials file or via -u/-p / WC_USER / WC_PASS." ) run(args)