{"id":50919137,"url":"https://github.com/jesserockz/aiosolax-uart","last_synced_at":"2026-06-16T18:02:04.706Z","repository":{"id":360287202,"uuid":"1235189217","full_name":"jesserockz/aiosolax-uart","owner":"jesserockz","description":"Async library to read SolaX inverters over UART (Pocket USB protocol)","archived":false,"fork":false,"pushed_at":"2026-05-25T19:26:48.000Z","size":138,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-25T20:33:13.461Z","etag":null,"topics":["async","asyncio","esphome","home-assistant","inverter","pocket-usb","python","serial","solax","uart"],"latest_commit_sha":null,"homepage":"https://pypi.org/project/aiosolax-uart/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jesserockz.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":"CLA.md"},"funding":{"github":"jesserockz"}},"created_at":"2026-05-11T05:07:10.000Z","updated_at":"2026-05-25T19:26:52.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/jesserockz/aiosolax-uart","commit_stats":null,"previous_names":["jesserockz/aiosolax-uart"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/jesserockz/aiosolax-uart","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jesserockz%2Faiosolax-uart","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jesserockz%2Faiosolax-uart/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jesserockz%2Faiosolax-uart/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jesserockz%2Faiosolax-uart/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jesserockz","download_url":"https://codeload.github.com/jesserockz/aiosolax-uart/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jesserockz%2Faiosolax-uart/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34417416,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-16T02:00:06.860Z","response_time":126,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["async","asyncio","esphome","home-assistant","inverter","pocket-usb","python","serial","solax","uart"],"created_at":"2026-06-16T18:02:03.783Z","updated_at":"2026-06-16T18:02:04.701Z","avatar_url":"https://github.com/jesserockz.png","language":"Python","funding_links":["https://github.com/sponsors/jesserockz"],"categories":[],"sub_categories":[],"readme":"# aiosolax-uart\n\n[![PyPI version](https://img.shields.io/pypi/v/aiosolax-uart.svg)](https://pypi.org/project/aiosolax-uart/)\n[![Python versions](https://img.shields.io/pypi/pyversions/aiosolax-uart.svg)](https://pypi.org/project/aiosolax-uart/)\n[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE)\n[![Test](https://github.com/jesserockz/aiosolax-uart/actions/workflows/test.yml/badge.svg)](https://github.com/jesserockz/aiosolax-uart/actions/workflows/test.yml)\n[![codecov](https://codecov.io/gh/jesserockz/aiosolax-uart/graph/badge.svg)](https://codecov.io/gh/jesserockz/aiosolax-uart)\n[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)\n[![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit)](https://github.com/pre-commit/pre-commit)\n\nAsync Python library to read SolaX inverters over the **Pocket USB protocol** — the byte-level protocol that the SolaX Pocket WiFi / Pocket USB dongle speaks. Any inverter that accepts a Pocket dongle should be reachable with this library, whether the user connects a real dongle or replaces it with a USB-to-TTL adapter (CP2102, CH340, FT232, …) or an ESPHome serial proxy.\n\nThe wire protocol is `AA 55` framing at 9600 baud, 8N1. Originally reverse-engineered by [xdubx](https://github.com/xdubx/Solax-Pocket-USB-reverse-engineering); hybrid-inverter extensions contributed by [70p4z](https://github.com/xdubx/Solax-Pocket-USB-reverse-engineering/issues/4).\n\n## Install\n\n```bash\npip install aiosolax-uart\n```\n\n## Quickstart\n\n```python\nimport asyncio\nfrom aiosolax_uart import SolaxClient\n\nasync def main() -\u003e None:\n    async with SolaxClient(\"/dev/ttyUSB0\") as client:\n        info = await client.get_device_info()\n        print(f\"Connected: {info.model.name} (serial {info.inverter_serial})\")\n\n        live = await client.get_live_data()\n        print(f\"AC power: {live.grid_power} W, today: {live.energy_today} kWh\")\n        if live.import_power is not None:\n            print(\n                f\"House grid: imp={live.import_power}W \"\n                f\"exp={live.export_power}W self={live.self_consumption_power}W\"\n            )\n        if live.battery_soc is not None:\n            print(f\"Battery: {live.battery_soc}% @ {live.battery_voltage}V\")\n\nasyncio.run(main())\n```\n\nThe library also works against an [ESPHome serial proxy](https://esphome.io/components/serial_proxy.html) — pass an `esphome://host[:port]/?port_name=...\u0026noise_psk=...` URL in place of `/dev/ttyUSB0`.\n\n## Supported inverters\n\nThe library handles two payload families. Which fields are populated on a given inverter depends on what that inverter actually reports.\n\n| Family | Models | Status |\n|---|---|---|\n| X1 grid-tie | X1 Mini (G2/G3), X1 Air, X1 Boost (G3.3/G4), X1 Smart | **Verified** (X1 with CT clamp, May 2026) |\n| X1 Hybrid | X1 Hybrid G3 / G4.1+ | Decoder present, **unverified** — needs hardware testers |\n| X3 grid-tie | X3 Mega G2, X3 Mic/Pro G2, X3 Forth | Same wire protocol, **unverified** |\n| X3 Hybrid | X3 Hybrid G2 / G4.2+ | Decoder present, **unverified** — needs hardware testers |\n\nIf you can run `await client.get_device_info()` against your inverter and get a sensible response, please open an issue with the output so the model code can be added to the [`MODELS` registry](src/aiosolax_uart/models.py).\n\n## API\n\n### `SolaxClient`\n\n- `SolaxClient(port: str, *, dongle_serial: str = \"AIOSOLAX01\", baudrate: int = 9600)`\n- `async connect() -\u003e None` — open serial, register with the inverter\n- `async close() -\u003e None`\n- `async get_device_info() -\u003e DeviceInfo` — inverter serial, model code, dongle serial. Cached for use by `get_live_data()`.\n- `async get_live_data() -\u003e LiveData` — instantaneous values + lifetime energy totals. Dispatches to the right decoder using the cached model code.\n- Use as `async with SolaxClient(...) as client:` to handle setup/teardown.\n\n### Data models\n\n- [`DeviceInfo`](src/aiosolax_uart/models.py) — static device info plus a `.model` property that looks up the entry in `MODELS`.\n- [`LiveData`](src/aiosolax_uart/models.py) — every field that any inverter family can report. Common fields (AC, PV, temperature, energy_total, energy_today, runtime) are always populated; CT, battery, EPS and RTC fields are `None` when not available.\n\n### Model registry\n\n```python\nfrom aiosolax_uart import MODELS, lookup_model\n\nMODELS[5000]  # InverterModel(code=5000, name='X1 (5kW)', family=InverterFamily.X1_GRID_TIE)\nlookup_model(0xFFFF)  # InverterModel(code=65535, name='Unknown (65535)', family=InverterFamily.UNKNOWN)\n```\n\n## Protocol reference\n\nFrame layout: `AA 55 [total_size] [ctrl] [func] [payload...] [chk_lo chk_hi]` with a 16-bit little-endian additive checksum over all preceding bytes.\n\n| Direction | ctrl | func | Description |\n|---|---|---|---|\n| Host → | 0x02 | 0x01 | Register dongle (10-char ASCII serial) |\n| Host → | 0x01 | 0x05 | Read inverter serial / model code |\n| Inv ← | 0x01 | 0x85 | Serial response (40-byte payload) |\n| Host → | 0x01 | 0x0C | Read live data |\n| Inv ← | 0x01 | 0x8C | Live-data response (200 / 210+ bytes depending on family) |\n\nThe handshake is: open serial → broadcast register frame → wait for the inverter to echo it back → poll live data on a cadence.\n\n## Development\n\n```bash\nuv sync\nuv run pytest\n```\n\n## Contributing\n\nIf your inverter isn't in the `MODELS` registry yet:\n\n1. Run a small script that prints `await client.get_device_info()` against your inverter.\n2. Open an issue with the model code and the inverter's name (from the sticker).\n\nIf you have a hybrid inverter, the offsets in `_decode_hybrid_extras()` need verification. A 220-byte capture of `await client.get_live_data()` plus your battery's known voltage/SoC right when you took the capture would let us validate the offsets.\n\n## License\n\nApache 2.0\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjesserockz%2Faiosolax-uart","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjesserockz%2Faiosolax-uart","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjesserockz%2Faiosolax-uart/lists"}