sofapython 0.0.1rc1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,162 @@
1
+ Metadata-Version: 2.4
2
+ Name: sofapython
3
+ Version: 0.0.1rc1
4
+ Summary: Unofficial protocol library and man-in-the-middle proxy for Sofabaton X1 / X1S / X2 universal remote hubs
5
+ Project-URL: Homepage, https://github.com/m3tac0de/home-assistant-sofabaton-x1s
6
+ Project-URL: Issues, https://github.com/m3tac0de/home-assistant-sofabaton-x1s/issues
7
+ Project-URL: Documentation, https://github.com/m3tac0de/home-assistant-sofabaton-x1s/tree/main/docs
8
+ Author: m3tac0de
9
+ License: MIT
10
+ License-File: LICENSE
11
+ Keywords: home-automation,mdns,proxy,sofabaton,universal-remote,x1,x1s,x2
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Home Automation
21
+ Classifier: Topic :: Software Development :: Libraries
22
+ Requires-Python: >=3.11
23
+ Requires-Dist: zeroconf>=0.131
24
+ Description-Content-Type: text/markdown
25
+
26
+ # sofapython
27
+
28
+ Unofficial Python library for **Sofabaton X1 / X1S / X2** universal remote
29
+ hubs: a reverse-engineered protocol implementation and a man-in-the-middle
30
+ **proxy** that sits between the hub and the official mobile app.
31
+
32
+ This is the protocol engine extracted from the
33
+ [Home Assistant Sofabaton X1S integration](https://github.com/m3tac0de/home-assistant-sofabaton-x1s);
34
+ the integration is its reference consumer.
35
+
36
+ > **Disclaimer:** this project is not affiliated with or endorsed by
37
+ > Sofabaton. The protocol was reverse-engineered from network captures;
38
+ > behavior may break with future hub firmware.
39
+
40
+ ## What it does
41
+
42
+ - **Proxy** a physical hub: the library advertises itself via mDNS exactly
43
+ like a real hub, the official app connects to it, and every frame is
44
+ relayed, decoded and observable. The hub keeps working with the app while
45
+ your application gets full visibility and control.
46
+ - **Catalogs**: read activities, devices, buttons, commands, macros and
47
+ favorites from the hub's wire protocol.
48
+ - **Control**: send button/command presses, switch activities, trigger
49
+ find-my-remote.
50
+ - **Provisioning** (protocol side): create/update/delete devices and
51
+ activities, including virtual WiFi/IP devices.
52
+ - **Backup / restore**: export and restore hub configuration.
53
+ - **Events**: subscribe to hub/app connection state, activity changes,
54
+ OTA progress and catalog updates.
55
+
56
+ Deliberately **out of scope**: executing the HTTP callbacks that virtual
57
+ WiFi/IP devices define (e.g. a Roku-style ECP listener). The library carries
58
+ the protocol artifacts for those features so applications can build them on
59
+ top — the Home Assistant integration does exactly that.
60
+
61
+ ## Install
62
+
63
+ ```
64
+ pip install sofapython
65
+ ```
66
+
67
+ Python 3.11+. The only dependency is
68
+ [python-zeroconf](https://pypi.org/project/zeroconf/) (mDNS advertising and
69
+ hub discovery).
70
+
71
+ ## Quickstart
72
+
73
+ Find a hub, then proxy it. Blocking work runs in the event loop's
74
+ executor and callbacks (plain functions or coroutines) are delivered on
75
+ the loop, so application code never touches the engine threads:
76
+
77
+ ```python
78
+ import asyncio
79
+ from sofapython import AsyncX1Proxy, async_discover_hubs
80
+
81
+ async def main():
82
+ hubs = await async_discover_hubs(timeout=5.0) # physical hubs; proxies filtered
83
+ hub = hubs[0]
84
+
85
+ proxy = AsyncX1Proxy(
86
+ real_hub_ip=hub.host,
87
+ mdns_instance=hub.name,
88
+ mdns_txt=hub.txt, # carries HVER -> X1/X1S/X2 classification
89
+ hub_version=hub.hub_version,
90
+ )
91
+ proxy.on_activity_change(lambda new, old, name: print(f"activity -> {name}"))
92
+
93
+ async with proxy:
94
+ await proxy.wait_until_controllable() # own the hub (see below)
95
+ activities = await proxy.activities() # {id: {name, active, ...}}
96
+ for dev_id in await proxy.devices():
97
+ print(dev_id, await proxy.commands(dev_id)) # {code: label}
98
+
99
+ await proxy.start_activity(next(iter(activities)))
100
+
101
+ asyncio.run(main())
102
+ ```
103
+
104
+ The reads return data directly — a cached result comes back immediately,
105
+ otherwise the call fetches from the hub and awaits completion. The whole
106
+ app-builder surface is a handful of coroutines: `activities()`,
107
+ `devices()`, `commands(dev)`, `buttons(ent)`, `macros(act)`,
108
+ `favorites(act)` to read; `press(ent, button)`, `start_activity(act)`,
109
+ `stop_activity(act)`, `find_remote()` to control.
110
+
111
+ ### Two modes
112
+
113
+ The proxy sits transparently between the hub and the official app, which
114
+ gives it two distinct modes:
115
+
116
+ - **Observe** — the app is connected through the proxy. You watch
117
+ activity changes, connects and OTA events in real time, but the app
118
+ owns the hub, so you can't issue commands. Gate on
119
+ `await proxy.wait_connected()`.
120
+ - **Control** — no app attached; the proxy owns the hub, so reads fetch
121
+ fresh and commands/backup work. Gate on
122
+ `await proxy.wait_until_controllable()`.
123
+
124
+ `start()` only spawns the transport; the connect handshake happens
125
+ afterwards, so await the matching readiness primitive before reading or
126
+ acting (otherwise a read raises with the reason — hub not connected, or
127
+ an app holds it).
128
+
129
+ A synchronous core (`X1Proxy`, `discover_hubs`) is also available for
130
+ scripts and REPL use; the async class is a facade over it (its raw
131
+ `get_*` snapshot getters are reachable via `proxy.sync`).
132
+
133
+ A CLI ships as a console script:
134
+
135
+ ```
136
+ sofapython discover # scan the LAN for hubs
137
+ sofapython run --hub 192.168.1.50 # proxy + interactive shell
138
+ x1> status
139
+ x1> activities
140
+ x1> send 101 POWER_ON
141
+ ```
142
+
143
+ Runnable examples — discovery, watching a live session (observe mode),
144
+ taking control of a hub, reading per-entity detail
145
+ (commands/macros/favorites), schema-versioned backup/restore, and
146
+ building an HTTP callback listener on top of the library — live in
147
+ [`sofapython/examples/`](https://github.com/m3tac0de/home-assistant-sofabaton-x1s/tree/main/sofapython/examples).
148
+
149
+ ## Stability
150
+
151
+ Names importable from the package root — `from sofapython import ...`,
152
+ the set listed in `sofapython.__all__` — are the supported API and follow
153
+ semver. Everything else (`sofapython.opcode_handlers`, frame parsing,
154
+ wire schemas, the `proxy_*` mixin modules) is internal and may change
155
+ between minor releases. The library raises stdlib exceptions
156
+ (`ValueError` for malformed/unclassifiable input, `RuntimeError` /
157
+ `TimeoutError` for transport and ack failures); there are no custom
158
+ exception types. Until 1.0, pin a minor version.
159
+
160
+ ## License
161
+
162
+ MIT — see [LICENSE](https://github.com/m3tac0de/home-assistant-sofabaton-x1s/blob/main/LICENSE).
@@ -0,0 +1,39 @@
1
+ sofapython/__init__.py,sha256=mEJz7o-mpRi7wit1tpy4RF9XblQU0AowuH7x6O1xzMs,4091
2
+ sofapython/ack.py,sha256=CCE1ph2FaZKdbY3_sQc0_m5Xei7Alyk7WC4797ZKh7M,2021
3
+ sofapython/aio.py,sha256=iuOOk9VxYx3iycs45Nz2hW9tOe5gOfOoG-6LOi1ajfU,19947
4
+ sofapython/backup_export.py,sha256=EwXMU6eLOwXAfiuGZ6Lft3_ffLbwRsJOfqgQ-KLbH4o,17897
5
+ sofapython/blob_decoders.py,sha256=6RiZLSVAacnsT8U0mrwRzb5F3ZzhWrKIWnfdv0A1oQ8,30509
6
+ sofapython/cli.py,sha256=Ux2wzPxHDuJ-BW8UAxdy4xJw6kUuPEbrXZVeQ2oApIQ,16297
7
+ sofapython/commands.py,sha256=4QqKURFv6BUJu8g9hkL31gCFV2JyNJ_WWpa9i3AcgR8,42747
8
+ sofapython/deframer.py,sha256=amMFAVUzhZyG_1-BpEwSNQaQT-m8rmLHZgLevlctPJ0,2448
9
+ sofapython/device_create.py,sha256=Ej0J0j0z6uDPc9dnyxwVrauAjyqIf8Uem4VQ6VpSwv8,46651
10
+ sofapython/devices.py,sha256=CObuF6kouXuG49e2CTMJqw5pSgMsIgSTJvjcbOMRJpE,20101
11
+ sofapython/discovery.py,sha256=C-Ut7OpM0PRNxhg51zo856YXf-toXs-LI7IspZ3tBzs,10258
12
+ sofapython/frame_handlers.py,sha256=RVqfiPfurdqJOqQ7HN3-7mwNRKtFVTzb_gRzJK6ytnQ,4479
13
+ sofapython/hub_listener.py,sha256=VA4Uygig4rOQ8jFwmh_ERQdmBzzaNXGPZsWddozXrRk,8425
14
+ sofapython/hub_logging.py,sha256=r46r53D_UnNFK5KQQb_X8jG1Do6L7ErQzCE4Ykfg9Io,5884
15
+ sofapython/hub_versions.py,sha256=eyvY4J32eFO5TE_YWsgWuGaPuFg3yRWQx5W7goPR1eQ,4036
16
+ sofapython/inputs.py,sha256=uHPeXjIyi3C8irUGSH7LIas09LTkjKlFJ0kwopXK2v8,18613
17
+ sofapython/macros.py,sha256=L2D2ZKt3QaEY6RE4Y5ZLodXJg5rXtVn2qhq2zTonb_Q,24364
18
+ sofapython/notify_demuxer.py,sha256=W7ibe2hiHwT6WBFpn4DS6TwESl1jvdhwlp2RvOtgo7Y,14570
19
+ sofapython/opcode_handlers.py,sha256=OkqNW1vQOeBAviZJAU6l6rTH3tXdmN3K_F8vVaEeKbY,61830
20
+ sofapython/protocol_const.py,sha256=uvDa4qmS6dRcAQgfHtowFzVup5RUm2WeZcreuRKP9ZE,24067
21
+ sofapython/proxy_ack_waiters.py,sha256=O3UyQ9hQU1Uy2_lb0_YGrztNtoKyyTrlof8U7ljznms,27302
22
+ sofapython/proxy_activity_ops.py,sha256=hyBU_lXSPhVHdFS3kzQmQ7ACdScTWDmniL-hkjAI6PU,36728
23
+ sofapython/proxy_backup.py,sha256=9CluWy_Ik54Gu7PrlGS5jqoR_AqFTSCsvAD6HaKlgQE,21330
24
+ sofapython/proxy_backup_export.py,sha256=TuzZdN1pcCZacvSYAQgrETS1laIr6Jn4HnF12LLRmvc,18547
25
+ sofapython/proxy_catalog.py,sha256=5Cop0eJrhejXW_LzvgJgcUKtFYPybMxEu0fK4_a6r8o,36066
26
+ sofapython/proxy_frame_decode.py,sha256=DfEqhQl6Oujo-JbTtPxwLJUe_C8xHjOh0fj7qeyW14s,9595
27
+ sofapython/proxy_ir_blob.py,sha256=E7WBQdJmX_g06t83f-Ze8fU4s6-yHM34WBQyRVtgVZw,25604
28
+ sofapython/proxy_restore.py,sha256=yPT7XwCSFtNE5r0u6_MPvKhfo-ADcdqU5I6DzwI1TVk,84890
29
+ sofapython/proxy_wifi_device.py,sha256=YSpLpIrjp9e7Z0SdCoKP7ET9fg5r1zXCtjVtZwb2Yqk,41828
30
+ sofapython/state_helpers.py,sha256=4hMAk7y_f7hU5Iai2wwEvNdpCRipuQ_dD4u_L4FmBik,26268
31
+ sofapython/transport_bridge.py,sha256=iPSZLQfzpSUfcqUBvZ5zKUnYqhAb70LIHTmSaYuY370,31795
32
+ sofapython/version.py,sha256=i9_xSxMzj7E9hJXj9QgXIWml3dOcpEl0mW0IqARoJuE,196
33
+ sofapython/wire_schema.py,sha256=Nq8W62toS6GNMeH8EiOMwnwHCkC297MJ5C7yiyMIHX0,5542
34
+ sofapython/x1_proxy.py,sha256=GN1ggPCzJmFoZb_sTle5TMlkNQjjDgnXNFAHSxcNjgE,67237
35
+ sofapython-0.0.1rc1.dist-info/METADATA,sha256=IqG_hTZTtfy71R1FZvLrK16xPmko0bArkscdXWmSp1k,6669
36
+ sofapython-0.0.1rc1.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
37
+ sofapython-0.0.1rc1.dist-info/entry_points.txt,sha256=gnkMusJA3Bhc6kJFWq503XZu4kg17dko3emZXHWEvdU,51
38
+ sofapython-0.0.1rc1.dist-info/licenses/LICENSE,sha256=NLcQ2crTlHYykQmFVhdgRs8I--N67dOjyHOfm4-j7sA,1065
39
+ sofapython-0.0.1rc1.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.30.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ sofapython = sofapython.cli:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 m3tac0de
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.