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.
- sofapython/__init__.py +136 -0
- sofapython/ack.py +79 -0
- sofapython/aio.py +552 -0
- sofapython/backup_export.py +507 -0
- sofapython/blob_decoders.py +806 -0
- sofapython/cli.py +447 -0
- sofapython/commands.py +1273 -0
- sofapython/deframer.py +73 -0
- sofapython/device_create.py +1174 -0
- sofapython/devices.py +534 -0
- sofapython/discovery.py +315 -0
- sofapython/frame_handlers.py +131 -0
- sofapython/hub_listener.py +242 -0
- sofapython/hub_logging.py +152 -0
- sofapython/hub_versions.py +112 -0
- sofapython/inputs.py +501 -0
- sofapython/macros.py +669 -0
- sofapython/notify_demuxer.py +434 -0
- sofapython/opcode_handlers.py +1655 -0
- sofapython/protocol_const.py +633 -0
- sofapython/proxy_ack_waiters.py +660 -0
- sofapython/proxy_activity_ops.py +943 -0
- sofapython/proxy_backup.py +504 -0
- sofapython/proxy_backup_export.py +486 -0
- sofapython/proxy_catalog.py +915 -0
- sofapython/proxy_frame_decode.py +227 -0
- sofapython/proxy_ir_blob.py +676 -0
- sofapython/proxy_restore.py +2004 -0
- sofapython/proxy_wifi_device.py +1101 -0
- sofapython/state_helpers.py +713 -0
- sofapython/transport_bridge.py +876 -0
- sofapython/version.py +4 -0
- sofapython/wire_schema.py +164 -0
- sofapython/x1_proxy.py +1833 -0
- sofapython-0.0.1rc1.dist-info/METADATA +162 -0
- sofapython-0.0.1rc1.dist-info/RECORD +39 -0
- sofapython-0.0.1rc1.dist-info/WHEEL +4 -0
- sofapython-0.0.1rc1.dist-info/entry_points.txt +2 -0
- sofapython-0.0.1rc1.dist-info/licenses/LICENSE +21 -0
sofapython/__init__.py
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"""sofapython — unofficial Sofabaton X1/X1S/X2 protocol library and proxy.
|
|
2
|
+
|
|
3
|
+
This module is the curated public API: every name exported here (see
|
|
4
|
+
``__all__``) is semver-stable for the ``sofapython`` distribution.
|
|
5
|
+
Submodule internals (``opcode_handlers``, frame parsing, wire schemas,
|
|
6
|
+
the ``proxy_*`` mixins, ...) remain importable but are NOT a stable
|
|
7
|
+
surface and may change between minor releases.
|
|
8
|
+
|
|
9
|
+
The library raises stdlib exceptions (``ValueError`` for unclassifiable
|
|
10
|
+
or malformed input, ``RuntimeError``/``TimeoutError`` for transport and
|
|
11
|
+
ack failures) rather than custom exception types.
|
|
12
|
+
|
|
13
|
+
In-tree, this package doubles as ``custom_components.sofabaton_x1s.lib``
|
|
14
|
+
for the Home Assistant integration; the wheel build remaps it to the
|
|
15
|
+
top-level ``sofapython`` package (see pyproject.toml).
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from .version import __version__ # noqa: F401
|
|
19
|
+
|
|
20
|
+
# Protocol constants (ButtonName, BUTTONNAME_BY_CODE, DEVICE_CLASS_*,
|
|
21
|
+
# opcode helpers, ...). Star re-export predates the curated API and is
|
|
22
|
+
# kept for backward compatibility; protocol_const defines __all__.
|
|
23
|
+
from . import protocol_const as _protocol_const
|
|
24
|
+
from .protocol_const import * # noqa: F401,F403
|
|
25
|
+
|
|
26
|
+
# Hub-variant classification and shared defaults.
|
|
27
|
+
from .hub_versions import ( # noqa: F401
|
|
28
|
+
ACTIVITY_BACKUP_SCHEMA_VERSION,
|
|
29
|
+
DEFAULT_HUB_LISTEN_BASE,
|
|
30
|
+
DEFAULT_PROXY_UDP_PORT,
|
|
31
|
+
DEVICE_BACKUP_SCHEMA_VERSION,
|
|
32
|
+
HUB_BUNDLE_SCHEMA_VERSION,
|
|
33
|
+
HUB_VERSION_BY_HVER,
|
|
34
|
+
HUB_VERSION_X1,
|
|
35
|
+
HUB_VERSION_X1S,
|
|
36
|
+
HUB_VERSION_X2,
|
|
37
|
+
HVER_BY_HUB_VERSION,
|
|
38
|
+
HVER_X1,
|
|
39
|
+
HVER_X1S,
|
|
40
|
+
HVER_X2,
|
|
41
|
+
MDNS_SERVICE_TYPE_BY_VERSION,
|
|
42
|
+
MDNS_SERVICE_TYPE_X1,
|
|
43
|
+
MDNS_SERVICE_TYPE_X2,
|
|
44
|
+
MDNS_SERVICE_TYPES,
|
|
45
|
+
PROXY_TXT_KEY,
|
|
46
|
+
PROXY_TXT_VALUE,
|
|
47
|
+
classify_hub_version,
|
|
48
|
+
is_proxy_advertisement,
|
|
49
|
+
mdns_service_type_for_props,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
# Per-hub logging helper. LogTag/HubLogger remain importable from
|
|
53
|
+
# sofapython.hub_logging but are internal plumbing, not public API.
|
|
54
|
+
from .hub_logging import get_hub_logger # noqa: F401
|
|
55
|
+
|
|
56
|
+
# The proxy engine.
|
|
57
|
+
from .x1_proxy import X1Proxy # noqa: F401
|
|
58
|
+
|
|
59
|
+
# LAN discovery of physical hubs.
|
|
60
|
+
from .discovery import ( # noqa: F401
|
|
61
|
+
DEFAULT_DISCOVERY_TIMEOUT,
|
|
62
|
+
DiscoveredHub,
|
|
63
|
+
HubBrowser,
|
|
64
|
+
decode_txt_properties,
|
|
65
|
+
discover_hubs,
|
|
66
|
+
normalize_advertisement,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
# Device catalog records and provisioning flows.
|
|
70
|
+
from .devices import DeviceConfig, device_config_from_backup, parse_device_record # noqa: F401
|
|
71
|
+
from .device_create import ( # noqa: F401
|
|
72
|
+
DeviceCreateRequest,
|
|
73
|
+
DeviceCreateResult,
|
|
74
|
+
run_device_create,
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
# Step/ack result types surfaced by proxy operations.
|
|
78
|
+
from .ack import AckOutcome, InputsBurstResult, SendStepResult # noqa: F401
|
|
79
|
+
|
|
80
|
+
# Asyncio facade over the threaded core.
|
|
81
|
+
from .aio import AsyncHubBrowser, AsyncX1Proxy, async_discover_hubs # noqa: F401
|
|
82
|
+
|
|
83
|
+
_CURATED = [
|
|
84
|
+
"__version__",
|
|
85
|
+
# hub_versions
|
|
86
|
+
"ACTIVITY_BACKUP_SCHEMA_VERSION",
|
|
87
|
+
"DEFAULT_HUB_LISTEN_BASE",
|
|
88
|
+
"DEFAULT_PROXY_UDP_PORT",
|
|
89
|
+
"DEVICE_BACKUP_SCHEMA_VERSION",
|
|
90
|
+
"HUB_BUNDLE_SCHEMA_VERSION",
|
|
91
|
+
"HUB_VERSION_BY_HVER",
|
|
92
|
+
"HUB_VERSION_X1",
|
|
93
|
+
"HUB_VERSION_X1S",
|
|
94
|
+
"HUB_VERSION_X2",
|
|
95
|
+
"HVER_BY_HUB_VERSION",
|
|
96
|
+
"HVER_X1",
|
|
97
|
+
"HVER_X1S",
|
|
98
|
+
"HVER_X2",
|
|
99
|
+
"MDNS_SERVICE_TYPE_BY_VERSION",
|
|
100
|
+
"MDNS_SERVICE_TYPE_X1",
|
|
101
|
+
"MDNS_SERVICE_TYPE_X2",
|
|
102
|
+
"MDNS_SERVICE_TYPES",
|
|
103
|
+
"PROXY_TXT_KEY",
|
|
104
|
+
"PROXY_TXT_VALUE",
|
|
105
|
+
"classify_hub_version",
|
|
106
|
+
"is_proxy_advertisement",
|
|
107
|
+
"mdns_service_type_for_props",
|
|
108
|
+
# hub_logging
|
|
109
|
+
"get_hub_logger",
|
|
110
|
+
# proxy
|
|
111
|
+
"X1Proxy",
|
|
112
|
+
# discovery
|
|
113
|
+
"DEFAULT_DISCOVERY_TIMEOUT",
|
|
114
|
+
"DiscoveredHub",
|
|
115
|
+
"HubBrowser",
|
|
116
|
+
"decode_txt_properties",
|
|
117
|
+
"discover_hubs",
|
|
118
|
+
"normalize_advertisement",
|
|
119
|
+
# devices / provisioning
|
|
120
|
+
"DeviceConfig",
|
|
121
|
+
"device_config_from_backup",
|
|
122
|
+
"parse_device_record",
|
|
123
|
+
"DeviceCreateRequest",
|
|
124
|
+
"DeviceCreateResult",
|
|
125
|
+
"run_device_create",
|
|
126
|
+
# ack results
|
|
127
|
+
"AckOutcome",
|
|
128
|
+
"InputsBurstResult",
|
|
129
|
+
"SendStepResult",
|
|
130
|
+
# asyncio facade
|
|
131
|
+
"AsyncHubBrowser",
|
|
132
|
+
"AsyncX1Proxy",
|
|
133
|
+
"async_discover_hubs",
|
|
134
|
+
]
|
|
135
|
+
|
|
136
|
+
__all__ = list(getattr(_protocol_const, "__all__", [])) + _CURATED
|
sofapython/ack.py
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""Typed outcomes for hub ack waits.
|
|
2
|
+
|
|
3
|
+
Every ``wait_for_*`` site distinguishes three states:
|
|
4
|
+
|
|
5
|
+
* ``acked`` -- the hub answered, and the answer was a success ack.
|
|
6
|
+
* ``rejected`` -- the hub answered explicitly, but the answer was a
|
|
7
|
+
rejection (e.g. ``STATUS_ACK`` carrying a non-zero status byte).
|
|
8
|
+
* ``timeout`` -- the hub did not answer within the wait window.
|
|
9
|
+
|
|
10
|
+
Conflating the latter two leads to fail-slow behaviour during multi-step
|
|
11
|
+
sequences: the orchestration spins out the full per-step timeout rather
|
|
12
|
+
than aborting at the first hub-side refusal. The dataclasses below give
|
|
13
|
+
callers a uniform way to branch.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from dataclasses import dataclass
|
|
19
|
+
from enum import Enum
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class AckOutcome(Enum):
|
|
23
|
+
"""Three-way classification of an ack wait."""
|
|
24
|
+
|
|
25
|
+
acked = "acked"
|
|
26
|
+
rejected = "rejected"
|
|
27
|
+
timeout = "timeout"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass(frozen=True, slots=True)
|
|
31
|
+
class SendStepResult:
|
|
32
|
+
"""Outcome of a single ``_send_step`` exchange."""
|
|
33
|
+
|
|
34
|
+
outcome: AckOutcome
|
|
35
|
+
ack_opcode: int | None = None
|
|
36
|
+
ack_payload: bytes | None = None
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def ok(self) -> bool:
|
|
40
|
+
return self.outcome is AckOutcome.acked
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
def rejected(self) -> bool:
|
|
44
|
+
return self.outcome is AckOutcome.rejected
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def timed_out(self) -> bool:
|
|
48
|
+
return self.outcome is AckOutcome.timeout
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@dataclass(frozen=True, slots=True)
|
|
52
|
+
class InputsBurstResult:
|
|
53
|
+
"""Outcome of :meth:`X1Proxy.wait_for_activity_inputs_burst`.
|
|
54
|
+
|
|
55
|
+
``payloads`` is populated on :attr:`AckOutcome.acked` and is empty
|
|
56
|
+
on rejection or timeout.
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
outcome: AckOutcome
|
|
60
|
+
payloads: tuple[bytes, ...] = ()
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def ok(self) -> bool:
|
|
64
|
+
return self.outcome is AckOutcome.acked
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def rejected(self) -> bool:
|
|
68
|
+
return self.outcome is AckOutcome.rejected
|
|
69
|
+
|
|
70
|
+
@property
|
|
71
|
+
def timed_out(self) -> bool:
|
|
72
|
+
return self.outcome is AckOutcome.timeout
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
__all__ = [
|
|
76
|
+
"AckOutcome",
|
|
77
|
+
"InputsBurstResult",
|
|
78
|
+
"SendStepResult",
|
|
79
|
+
]
|