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 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
+ ]