sds200 0.8.2__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.
- sds200/__init__.py +145 -0
- sds200/audio.py +77 -0
- sds200/cli.py +946 -0
- sds200/commands.py +222 -0
- sds200/completion.py +96 -0
- sds200/device.py +83 -0
- sds200/discovery.py +264 -0
- sds200/events.py +37 -0
- sds200/exceptions.py +42 -0
- sds200/fallback.py +438 -0
- sds200/models.py +442 -0
- sds200/monitor.py +69 -0
- sds200/network.py +660 -0
- sds200/parser.py +141 -0
- sds200/profiles.py +479 -0
- sds200/py.typed +0 -0
- sds200/radio.py +810 -0
- sds200/reliability.py +190 -0
- sds200/scanner.py +88 -0
- sds200/state.py +83 -0
- sds200/trace.py +31 -0
- sds200/transport.py +453 -0
- sds200/xml_protocol.py +78 -0
- sds200-0.8.2.dist-info/METADATA +397 -0
- sds200-0.8.2.dist-info/RECORD +28 -0
- sds200-0.8.2.dist-info/WHEEL +4 -0
- sds200-0.8.2.dist-info/entry_points.txt +2 -0
- sds200-0.8.2.dist-info/licenses/LICENSE +17 -0
sds200/__init__.py
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
from .audio import AudioChunk, AudioStream, AudioTransport
|
|
2
|
+
from .commands import (
|
|
3
|
+
GetChargeStatus,
|
|
4
|
+
GetFirmware,
|
|
5
|
+
GetModel,
|
|
6
|
+
GetScannerInfo,
|
|
7
|
+
GetSquelch,
|
|
8
|
+
GetStatus,
|
|
9
|
+
GetVolume,
|
|
10
|
+
SetSquelch,
|
|
11
|
+
SetVolume,
|
|
12
|
+
StartScannerInfoPush,
|
|
13
|
+
)
|
|
14
|
+
from .device import (
|
|
15
|
+
DEFAULT_SDS200_PATTERN,
|
|
16
|
+
DEFAULT_SDS_PATTERN,
|
|
17
|
+
ScannerDevice,
|
|
18
|
+
discover_scanners,
|
|
19
|
+
)
|
|
20
|
+
from .discovery import (
|
|
21
|
+
DEFAULT_DISCOVERY_WORKERS,
|
|
22
|
+
NetworkScanner,
|
|
23
|
+
discover_network_scanners,
|
|
24
|
+
local_ipv4_networks,
|
|
25
|
+
)
|
|
26
|
+
from .exceptions import (
|
|
27
|
+
CommandRejectedError,
|
|
28
|
+
SDS200Error,
|
|
29
|
+
SDSScannerError,
|
|
30
|
+
UnsupportedScannerFeatureError,
|
|
31
|
+
UnsupportedScannerModelError,
|
|
32
|
+
)
|
|
33
|
+
from .fallback import FallbackTransport, TransportCandidate
|
|
34
|
+
from .models import (
|
|
35
|
+
ChargeStatus,
|
|
36
|
+
FirmwareResponse,
|
|
37
|
+
HealthSummary,
|
|
38
|
+
ModelResponse,
|
|
39
|
+
Packet,
|
|
40
|
+
RadioEvent,
|
|
41
|
+
RadioHealth,
|
|
42
|
+
ScannerInfo,
|
|
43
|
+
ScannerNode,
|
|
44
|
+
StatusResponse,
|
|
45
|
+
ValueResponse,
|
|
46
|
+
)
|
|
47
|
+
from .network import DEFAULT_UDP_PORT, UdpDatagramDecoder, UdpTransport
|
|
48
|
+
from .profiles import (
|
|
49
|
+
TRANSPORT_PREFERENCES,
|
|
50
|
+
ConnectionProfile,
|
|
51
|
+
ProfileKind,
|
|
52
|
+
ProfileRepairResult,
|
|
53
|
+
ProfileStore,
|
|
54
|
+
TransportPreference,
|
|
55
|
+
profile_from_discovery,
|
|
56
|
+
repair_profile,
|
|
57
|
+
)
|
|
58
|
+
from .radio import SDS200, SDSScanner
|
|
59
|
+
from .reliability import HealthHistory, HealthThresholds, ReconnectPolicy
|
|
60
|
+
from .scanner import (
|
|
61
|
+
SUPPORTED_SCANNER_MODELS,
|
|
62
|
+
ScannerCapabilities,
|
|
63
|
+
ScannerModel,
|
|
64
|
+
capabilities_for_model,
|
|
65
|
+
infer_model_from_device_name,
|
|
66
|
+
normalize_model_name,
|
|
67
|
+
)
|
|
68
|
+
from .state import RadioState, RadioStateSnapshot, StateChange
|
|
69
|
+
from .transport import (
|
|
70
|
+
ControlTransport,
|
|
71
|
+
SerialTransport,
|
|
72
|
+
TransportDiagnostic,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
__all__ = [
|
|
76
|
+
"AudioChunk",
|
|
77
|
+
"AudioStream",
|
|
78
|
+
"AudioTransport",
|
|
79
|
+
"ChargeStatus",
|
|
80
|
+
"CommandRejectedError",
|
|
81
|
+
"ConnectionProfile",
|
|
82
|
+
"ControlTransport",
|
|
83
|
+
"DEFAULT_DISCOVERY_WORKERS",
|
|
84
|
+
"DEFAULT_SDS_PATTERN",
|
|
85
|
+
"DEFAULT_SDS200_PATTERN",
|
|
86
|
+
"DEFAULT_UDP_PORT",
|
|
87
|
+
"FallbackTransport",
|
|
88
|
+
"FirmwareResponse",
|
|
89
|
+
"GetChargeStatus",
|
|
90
|
+
"GetFirmware",
|
|
91
|
+
"GetModel",
|
|
92
|
+
"GetScannerInfo",
|
|
93
|
+
"GetSquelch",
|
|
94
|
+
"GetStatus",
|
|
95
|
+
"GetVolume",
|
|
96
|
+
"HealthHistory",
|
|
97
|
+
"HealthSummary",
|
|
98
|
+
"HealthThresholds",
|
|
99
|
+
"ModelResponse",
|
|
100
|
+
"NetworkScanner",
|
|
101
|
+
"Packet",
|
|
102
|
+
"ProfileKind",
|
|
103
|
+
"ProfileRepairResult",
|
|
104
|
+
"ProfileStore",
|
|
105
|
+
"RadioEvent",
|
|
106
|
+
"RadioHealth",
|
|
107
|
+
"RadioState",
|
|
108
|
+
"RadioStateSnapshot",
|
|
109
|
+
"ReconnectPolicy",
|
|
110
|
+
"SDS200",
|
|
111
|
+
"SDS200Error",
|
|
112
|
+
"SDSScanner",
|
|
113
|
+
"SDSScannerError",
|
|
114
|
+
"SUPPORTED_SCANNER_MODELS",
|
|
115
|
+
"ScannerCapabilities",
|
|
116
|
+
"ScannerDevice",
|
|
117
|
+
"ScannerInfo",
|
|
118
|
+
"ScannerModel",
|
|
119
|
+
"ScannerNode",
|
|
120
|
+
"SerialTransport",
|
|
121
|
+
"SetSquelch",
|
|
122
|
+
"SetVolume",
|
|
123
|
+
"StartScannerInfoPush",
|
|
124
|
+
"StateChange",
|
|
125
|
+
"StatusResponse",
|
|
126
|
+
"TRANSPORT_PREFERENCES",
|
|
127
|
+
"TransportCandidate",
|
|
128
|
+
"TransportPreference",
|
|
129
|
+
"TransportDiagnostic",
|
|
130
|
+
"UdpDatagramDecoder",
|
|
131
|
+
"UdpTransport",
|
|
132
|
+
"UnsupportedScannerFeatureError",
|
|
133
|
+
"UnsupportedScannerModelError",
|
|
134
|
+
"ValueResponse",
|
|
135
|
+
"capabilities_for_model",
|
|
136
|
+
"discover_network_scanners",
|
|
137
|
+
"discover_scanners",
|
|
138
|
+
"infer_model_from_device_name",
|
|
139
|
+
"local_ipv4_networks",
|
|
140
|
+
"normalize_model_name",
|
|
141
|
+
"profile_from_discovery",
|
|
142
|
+
"repair_profile",
|
|
143
|
+
]
|
|
144
|
+
|
|
145
|
+
__version__ = "0.8.2"
|
sds200/audio.py
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Callable
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
from datetime import UTC, datetime
|
|
6
|
+
from typing import Protocol, Self, runtime_checkable
|
|
7
|
+
|
|
8
|
+
from .events import EventBus
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass(frozen=True, slots=True)
|
|
12
|
+
class AudioChunk:
|
|
13
|
+
"""Opaque audio bytes received from an audio transport.
|
|
14
|
+
|
|
15
|
+
Codec and packet framing intentionally remain transport-specific until
|
|
16
|
+
a scanner audio protocol is implemented and validated against hardware.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
data: bytes
|
|
20
|
+
received_at: datetime = field(default_factory=lambda: datetime.now(UTC))
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
AudioChunkHandler = Callable[[AudioChunk], None]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@runtime_checkable
|
|
27
|
+
class AudioTransport(Protocol):
|
|
28
|
+
"""Lifecycle contract for future USB, RTP, or RTSP audio transports."""
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def endpoint(self) -> str: ...
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def running(self) -> bool: ...
|
|
35
|
+
|
|
36
|
+
def start(self, handler: AudioChunkHandler) -> None: ...
|
|
37
|
+
|
|
38
|
+
def stop(self) -> None: ...
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class AudioStream:
|
|
42
|
+
"""Transport-independent audio event stream.
|
|
43
|
+
|
|
44
|
+
Audio is deliberately separate from :class:`sds200.SDSScanner`, so control
|
|
45
|
+
failover and protocol parsing cannot be destabilized by audio work.
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
def __init__(self, transport: AudioTransport) -> None:
|
|
49
|
+
self.transport = transport
|
|
50
|
+
self.events = EventBus()
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def endpoint(self) -> str:
|
|
54
|
+
return self.transport.endpoint
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def running(self) -> bool:
|
|
58
|
+
return self.transport.running
|
|
59
|
+
|
|
60
|
+
def on_chunk(self, callback: AudioChunkHandler) -> Callable[[], None]:
|
|
61
|
+
return self.events.subscribe("chunk", callback)
|
|
62
|
+
|
|
63
|
+
def start(self) -> None:
|
|
64
|
+
self.transport.start(self._receive_chunk)
|
|
65
|
+
|
|
66
|
+
def stop(self) -> None:
|
|
67
|
+
self.transport.stop()
|
|
68
|
+
|
|
69
|
+
def __enter__(self) -> Self:
|
|
70
|
+
self.start()
|
|
71
|
+
return self
|
|
72
|
+
|
|
73
|
+
def __exit__(self, *_: object) -> None:
|
|
74
|
+
self.stop()
|
|
75
|
+
|
|
76
|
+
def _receive_chunk(self, chunk: AudioChunk) -> None:
|
|
77
|
+
self.events.emit("chunk", chunk)
|