"""Read the live EasyEDA component catalog through the installed API bridge. Archive exact device-search and device-detail responses for fan selection. This does not edit the EasyEDA project or claim that catalog entries are stocked. """ import argparse from datetime import datetime, timezone import json from pathlib import Path import sys import requests ROOT = Path(__file__).resolve().parents[1] def bridge(session): for port in range(49620, 49630): base = f'http://127.0.0.1:{port}' try: response = session.get(base + '/health', timeout=.5) health = response.json() except (requests.RequestException, ValueError): continue if health.get('service') == 'easyeda-bridge': return base, health raise RuntimeError('Installed EasyEDA API bridge not reachable on 49620-49629') def main(): parser = argparse.ArgumentParser(description=__doc__) sub = parser.add_subparsers(dest='action', required=True) sub.add_parser('probe') search = sub.add_parser('search') search.add_argument('keywords', nargs='+') search.add_argument('--page', type=int, default=1) search.add_argument('--limit', type=int, default=100) search.add_argument('--category', nargs=2, metavar=('PRIMARY_UUID', 'SECONDARY_UUID')) detail = sub.add_parser('detail') detail.add_argument('uuid') detail.add_argument('--library') args = parser.parse_args() session = requests.Session() session.trust_env = False base, health = bridge(session) if args.action == 'probe': print(json.dumps(health, ensure_ascii=False, indent=2)) return 0 if health.get('edaConnected') else 2 if not health.get('edaConnected'): raise RuntimeError('Bridge is healthy but no EasyEDA API Gateway window is connected') # Reading the public catalog does not require selecting or editing a project. window = health.get('activeWindowId') if not window: raise RuntimeError('No active EasyEDA window; select a client window first') if args.action == 'search': assert 1 <= args.limit <= 100 and args.page >= 1 category = json.dumps({'libraryUuid': '0819f05c4eef4c71ace90d822a990e87', 'libraryType': '3', 'primaryClassificationUuid': args.category[0], 'secondaryClassificationUuid': args.category[1]}) if args.category else 'undefined' queries = [(key, 'return await eda.lib_Device.search(' + json.dumps('' if key == '*' else key) + f', undefined, {category}, undefined, {args.limit}, {args.page});') for key in args.keywords] else: queries = [(args.uuid, 'return await eda.lib_Device.get(' + json.dumps(args.uuid) + ', ' + (json.dumps(args.library) if args.library else 'undefined') + ');')] output = ROOT/'out/fan-catalog' output.mkdir(exist_ok=True) for query, code in queries: response = session.post(base+'/execute', json={'code': code, 'windowId': window}, timeout=40) response.raise_for_status() result = response.json() if result.get('success') is False or result.get('error'): raise RuntimeError(str(result)) timestamp = datetime.now(timezone.utc) record = {'retrieved_at_utc': timestamp.isoformat(), 'source': 'Live EasyEDA lib_Device API', 'window_id': window, 'operation': args.action, 'query': query, 'page': getattr(args, 'page', None), 'category': getattr(args, 'category', None), 'response': result} path = output/(timestamp.strftime('%Y%m%dT%H%M%S%f')+'-'+args.action+'.json') path.write_text(json.dumps(record, indent=2, ensure_ascii=False)+'\n', encoding='utf-8') print(path.relative_to(ROOT)) data = result.get('result') if isinstance(data, list): print('Results:', len(data), flush=True) for part in data: print(json.dumps({k: part.get(k) for k in ['name', 'uuid', 'supplierId', 'description']}, ensure_ascii=False), flush=True) else: print(json.dumps(data, ensure_ascii=False), flush=True) return 0 if __name__ == '__main__': try: raise SystemExit(main()) except (RuntimeError, requests.RequestException) as exc: print(str(exc), file=sys.stderr) raise SystemExit(2)