"""Manual verification CLI for the real iSolarCloud client (both auth modes). USE_MOCK_ISOLARCLOUD=false and ISOLARCLOUD_AUTH_MODE ("v1" or "oauth2") must be set in .env along with the matching credentials -- see SETUP.md. Usage (v1): python scripts/verify_isolarcloud_cli.py login python scripts/verify_isolarcloud_cli.py plants python scripts/verify_isolarcloud_cli.py devices --ps-id 1234567 python scripts/verify_isolarcloud_cli.py history --hours 6 Usage (oauth2): python scripts/verify_isolarcloud_cli.py authorize-url # open the printed URL yourself, approve access, then copy the `code` # param from the (broken, expected) redirect URL: python scripts/verify_isolarcloud_cli.py exchange --code THE_CODE python scripts/verify_isolarcloud_cli.py plants python scripts/verify_isolarcloud_cli.py history --hours 6 """ from __future__ import annotations import argparse import json from datetime import datetime, timedelta, timezone from app.config import settings def main() -> None: parser = argparse.ArgumentParser() parser.add_argument( "action", choices=["login", "authorize-url", "exchange", "plants", "devices", "history"] ) parser.add_argument("--code", help="oauth2: the 'code' query param from the OAuth redirect") parser.add_argument("--ps-id", help="plant id; auto-detected (first plant) if omitted") parser.add_argument("--hours", type=int, default=3, help="for 'history': how far back to fetch") args = parser.parse_args() if settings.isolarcloud_auth_mode == "oauth2": from app.isolarcloud.oauth_client import ISolarCloudOAuthClient client = ISolarCloudOAuthClient() if args.action == "authorize-url": print(client.authorize_url()) return if args.action == "exchange": if not args.code: parser.error("--code is required for 'exchange'") auth_ps_list = client.exchange_code(args.code) print("Tokens stored. Authorized plants:", auth_ps_list) return else: from app.isolarcloud.client import DEVICE_TYPE_INVERTER, DEVICE_TYPE_METER, ISolarCloudClient client = ISolarCloudClient() if args.action == "login": client.login() print("Login succeeded, token acquired and cached.") return if args.action in ("authorize-url", "exchange"): parser.error(f"'{args.action}' is only valid with ISOLARCLOUD_AUTH_MODE=oauth2") if args.action == "plants": print(json.dumps(client.get_plant_list(), indent=2)) return ps_id = args.ps_id if not ps_id: plants = client.get_plant_list() if not plants: print("No plants visible to this account.") return ps_id = str(plants[0]["ps_id"]) print(f"(using first plant: {ps_id})") if args.action == "devices": if settings.isolarcloud_auth_mode == "oauth2": print(json.dumps(client.get_device_list(ps_id), indent=2)) return devices = client.get_device_list(ps_id) print(json.dumps(devices, indent=2)) meter = next((d for d in devices if int(d.get("device_type", -1)) == DEVICE_TYPE_METER), None) inverter = next((d for d in devices if int(d.get("device_type", -1)) == DEVICE_TYPE_INVERTER), None) print() print(f"Detected meter device: {meter.get('ps_key') if meter else 'NONE FOUND'}") print(f"Detected inverter device: {inverter.get('ps_key') if inverter else 'NONE FOUND'}") return if args.action == "history": until = datetime.now(timezone.utc) since = until - timedelta(hours=args.hours) points = client.fetch_interval_data(since, until) for p in points[:20]: print(p) print(f"... {len(points)} points total") if __name__ == "__main__": main()