rtls-sdk 0.2.0__tar.gz → 0.4.0__tar.gz

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.
Files changed (101) hide show
  1. {rtls_sdk-0.2.0 → rtls_sdk-0.4.0}/CHANGELOG.md +155 -1
  2. {rtls_sdk-0.2.0 → rtls_sdk-0.4.0}/PKG-INFO +2 -1
  3. {rtls_sdk-0.2.0 → rtls_sdk-0.4.0}/prompts/README.md +6 -0
  4. {rtls_sdk-0.2.0 → rtls_sdk-0.4.0}/pyproject.toml +2 -1
  5. {rtls_sdk-0.2.0 → rtls_sdk-0.4.0}/src/rtls_sdk/__init__.py +56 -0
  6. {rtls_sdk-0.2.0 → rtls_sdk-0.4.0}/src/rtls_sdk/_client.py +10 -0
  7. rtls_sdk-0.4.0/src/rtls_sdk/_ws.py +390 -0
  8. {rtls_sdk-0.2.0 → rtls_sdk-0.4.0}/src/rtls_sdk/errors.py +53 -0
  9. {rtls_sdk-0.2.0 → rtls_sdk-0.4.0}/src/rtls_sdk/models/__init__.py +11 -0
  10. rtls_sdk-0.4.0/src/rtls_sdk/models/lsb.py +129 -0
  11. rtls_sdk-0.4.0/src/rtls_sdk/models/network.py +43 -0
  12. rtls_sdk-0.4.0/src/rtls_sdk/models/node_params.py +51 -0
  13. rtls_sdk-0.4.0/src/rtls_sdk/models/schedule.py +30 -0
  14. rtls_sdk-0.4.0/src/rtls_sdk/models/ws/__init__.py +76 -0
  15. rtls_sdk-0.4.0/src/rtls_sdk/models/ws/_base.py +53 -0
  16. rtls_sdk-0.4.0/src/rtls_sdk/models/ws/alarm.py +34 -0
  17. rtls_sdk-0.4.0/src/rtls_sdk/models/ws/notify.py +29 -0
  18. rtls_sdk-0.4.0/src/rtls_sdk/models/ws/ota.py +23 -0
  19. rtls_sdk-0.4.0/src/rtls_sdk/models/ws/param_p.py +25 -0
  20. rtls_sdk-0.4.0/src/rtls_sdk/models/ws/position.py +41 -0
  21. rtls_sdk-0.4.0/src/rtls_sdk/models/ws/sensor.py +23 -0
  22. rtls_sdk-0.4.0/src/rtls_sdk/models/ws/user_msg.py +28 -0
  23. rtls_sdk-0.4.0/src/rtls_sdk/models/ws/zone_event.py +30 -0
  24. {rtls_sdk-0.2.0 → rtls_sdk-0.4.0}/src/rtls_sdk/resources/__init__.py +10 -0
  25. rtls_sdk-0.4.0/src/rtls_sdk/resources/lsb.py +373 -0
  26. rtls_sdk-0.4.0/src/rtls_sdk/resources/network.py +54 -0
  27. {rtls_sdk-0.2.0 → rtls_sdk-0.4.0}/src/rtls_sdk/resources/nodes.py +53 -1
  28. rtls_sdk-0.4.0/src/rtls_sdk/resources/schedules.py +92 -0
  29. rtls_sdk-0.4.0/src/rtls_sdk/resources/user_messages.py +76 -0
  30. rtls_sdk-0.4.0/src/rtls_sdk/resources/ws.py +133 -0
  31. {rtls_sdk-0.2.0 → rtls_sdk-0.4.0}/.gitignore +0 -0
  32. {rtls_sdk-0.2.0 → rtls_sdk-0.4.0}/LICENSE +0 -0
  33. {rtls_sdk-0.2.0 → rtls_sdk-0.4.0}/README.md +0 -0
  34. {rtls_sdk-0.2.0 → rtls_sdk-0.4.0}/src/rtls_sdk/_auth.py +0 -0
  35. {rtls_sdk-0.2.0 → rtls_sdk-0.4.0}/src/rtls_sdk/_envelope.py +0 -0
  36. {rtls_sdk-0.2.0 → rtls_sdk-0.4.0}/src/rtls_sdk/_http.py +0 -0
  37. {rtls_sdk-0.2.0 → rtls_sdk-0.4.0}/src/rtls_sdk/_logging.py +0 -0
  38. {rtls_sdk-0.2.0 → rtls_sdk-0.4.0}/src/rtls_sdk/_pagination.py +0 -0
  39. {rtls_sdk-0.2.0 → rtls_sdk-0.4.0}/src/rtls_sdk/_query.py +0 -0
  40. {rtls_sdk-0.2.0 → rtls_sdk-0.4.0}/src/rtls_sdk/_time.py +0 -0
  41. {rtls_sdk-0.2.0 → rtls_sdk-0.4.0}/src/rtls_sdk/compounds/__init__.py +0 -0
  42. {rtls_sdk-0.2.0 → rtls_sdk-0.4.0}/src/rtls_sdk/compounds/auth.py +0 -0
  43. {rtls_sdk-0.2.0 → rtls_sdk-0.4.0}/src/rtls_sdk/compounds/context.py +0 -0
  44. {rtls_sdk-0.2.0 → rtls_sdk-0.4.0}/src/rtls_sdk/compounds/groups.py +0 -0
  45. {rtls_sdk-0.2.0 → rtls_sdk-0.4.0}/src/rtls_sdk/compounds/nodes.py +0 -0
  46. {rtls_sdk-0.2.0 → rtls_sdk-0.4.0}/src/rtls_sdk/compounds/reports.py +0 -0
  47. {rtls_sdk-0.2.0 → rtls_sdk-0.4.0}/src/rtls_sdk/compounds/system.py +0 -0
  48. {rtls_sdk-0.2.0 → rtls_sdk-0.4.0}/src/rtls_sdk/compounds/tags.py +0 -0
  49. {rtls_sdk-0.2.0 → rtls_sdk-0.4.0}/src/rtls_sdk/compounds/users.py +0 -0
  50. {rtls_sdk-0.2.0 → rtls_sdk-0.4.0}/src/rtls_sdk/compounds/zones.py +0 -0
  51. {rtls_sdk-0.2.0 → rtls_sdk-0.4.0}/src/rtls_sdk/models/_base.py +0 -0
  52. {rtls_sdk-0.2.0 → rtls_sdk-0.4.0}/src/rtls_sdk/models/alarm.py +0 -0
  53. {rtls_sdk-0.2.0 → rtls_sdk-0.4.0}/src/rtls_sdk/models/anchor.py +0 -0
  54. {rtls_sdk-0.2.0 → rtls_sdk-0.4.0}/src/rtls_sdk/models/area.py +0 -0
  55. {rtls_sdk-0.2.0 → rtls_sdk-0.4.0}/src/rtls_sdk/models/association.py +0 -0
  56. {rtls_sdk-0.2.0 → rtls_sdk-0.4.0}/src/rtls_sdk/models/bulk.py +0 -0
  57. {rtls_sdk-0.2.0 → rtls_sdk-0.4.0}/src/rtls_sdk/models/company.py +0 -0
  58. {rtls_sdk-0.2.0 → rtls_sdk-0.4.0}/src/rtls_sdk/models/csv_blob.py +0 -0
  59. {rtls_sdk-0.2.0 → rtls_sdk-0.4.0}/src/rtls_sdk/models/floorplan.py +0 -0
  60. {rtls_sdk-0.2.0 → rtls_sdk-0.4.0}/src/rtls_sdk/models/group.py +0 -0
  61. {rtls_sdk-0.2.0 → rtls_sdk-0.4.0}/src/rtls_sdk/models/heatmap.py +0 -0
  62. {rtls_sdk-0.2.0 → rtls_sdk-0.4.0}/src/rtls_sdk/models/import_result.py +0 -0
  63. {rtls_sdk-0.2.0 → rtls_sdk-0.4.0}/src/rtls_sdk/models/node.py +0 -0
  64. {rtls_sdk-0.2.0 → rtls_sdk-0.4.0}/src/rtls_sdk/models/notification.py +0 -0
  65. {rtls_sdk-0.2.0 → rtls_sdk-0.4.0}/src/rtls_sdk/models/position.py +0 -0
  66. {rtls_sdk-0.2.0 → rtls_sdk-0.4.0}/src/rtls_sdk/models/project.py +0 -0
  67. {rtls_sdk-0.2.0 → rtls_sdk-0.4.0}/src/rtls_sdk/models/pws.py +0 -0
  68. {rtls_sdk-0.2.0 → rtls_sdk-0.4.0}/src/rtls_sdk/models/report.py +0 -0
  69. {rtls_sdk-0.2.0 → rtls_sdk-0.4.0}/src/rtls_sdk/models/session_context.py +0 -0
  70. {rtls_sdk-0.2.0 → rtls_sdk-0.4.0}/src/rtls_sdk/models/site.py +0 -0
  71. {rtls_sdk-0.2.0 → rtls_sdk-0.4.0}/src/rtls_sdk/models/subscriber.py +0 -0
  72. {rtls_sdk-0.2.0 → rtls_sdk-0.4.0}/src/rtls_sdk/models/system.py +0 -0
  73. {rtls_sdk-0.2.0 → rtls_sdk-0.4.0}/src/rtls_sdk/models/system_health.py +0 -0
  74. {rtls_sdk-0.2.0 → rtls_sdk-0.4.0}/src/rtls_sdk/models/tag.py +0 -0
  75. {rtls_sdk-0.2.0 → rtls_sdk-0.4.0}/src/rtls_sdk/models/tag_template.py +0 -0
  76. {rtls_sdk-0.2.0 → rtls_sdk-0.4.0}/src/rtls_sdk/models/user.py +0 -0
  77. {rtls_sdk-0.2.0 → rtls_sdk-0.4.0}/src/rtls_sdk/models/zone.py +0 -0
  78. {rtls_sdk-0.2.0 → rtls_sdk-0.4.0}/src/rtls_sdk/models/zone_event.py +0 -0
  79. {rtls_sdk-0.2.0 → rtls_sdk-0.4.0}/src/rtls_sdk/py.typed +0 -0
  80. {rtls_sdk-0.2.0 → rtls_sdk-0.4.0}/src/rtls_sdk/resources/_base.py +0 -0
  81. {rtls_sdk-0.2.0 → rtls_sdk-0.4.0}/src/rtls_sdk/resources/alarms.py +0 -0
  82. {rtls_sdk-0.2.0 → rtls_sdk-0.4.0}/src/rtls_sdk/resources/anchors.py +0 -0
  83. {rtls_sdk-0.2.0 → rtls_sdk-0.4.0}/src/rtls_sdk/resources/areas.py +0 -0
  84. {rtls_sdk-0.2.0 → rtls_sdk-0.4.0}/src/rtls_sdk/resources/associations.py +0 -0
  85. {rtls_sdk-0.2.0 → rtls_sdk-0.4.0}/src/rtls_sdk/resources/auth.py +0 -0
  86. {rtls_sdk-0.2.0 → rtls_sdk-0.4.0}/src/rtls_sdk/resources/companies.py +0 -0
  87. {rtls_sdk-0.2.0 → rtls_sdk-0.4.0}/src/rtls_sdk/resources/context.py +0 -0
  88. {rtls_sdk-0.2.0 → rtls_sdk-0.4.0}/src/rtls_sdk/resources/events.py +0 -0
  89. {rtls_sdk-0.2.0 → rtls_sdk-0.4.0}/src/rtls_sdk/resources/floorplans.py +0 -0
  90. {rtls_sdk-0.2.0 → rtls_sdk-0.4.0}/src/rtls_sdk/resources/groups.py +0 -0
  91. {rtls_sdk-0.2.0 → rtls_sdk-0.4.0}/src/rtls_sdk/resources/logger.py +0 -0
  92. {rtls_sdk-0.2.0 → rtls_sdk-0.4.0}/src/rtls_sdk/resources/messaging.py +0 -0
  93. {rtls_sdk-0.2.0 → rtls_sdk-0.4.0}/src/rtls_sdk/resources/notifications.py +0 -0
  94. {rtls_sdk-0.2.0 → rtls_sdk-0.4.0}/src/rtls_sdk/resources/projects.py +0 -0
  95. {rtls_sdk-0.2.0 → rtls_sdk-0.4.0}/src/rtls_sdk/resources/reports.py +0 -0
  96. {rtls_sdk-0.2.0 → rtls_sdk-0.4.0}/src/rtls_sdk/resources/sites.py +0 -0
  97. {rtls_sdk-0.2.0 → rtls_sdk-0.4.0}/src/rtls_sdk/resources/subscribers.py +0 -0
  98. {rtls_sdk-0.2.0 → rtls_sdk-0.4.0}/src/rtls_sdk/resources/system.py +0 -0
  99. {rtls_sdk-0.2.0 → rtls_sdk-0.4.0}/src/rtls_sdk/resources/tags.py +0 -0
  100. {rtls_sdk-0.2.0 → rtls_sdk-0.4.0}/src/rtls_sdk/resources/users.py +0 -0
  101. {rtls_sdk-0.2.0 → rtls_sdk-0.4.0}/src/rtls_sdk/resources/zones.py +0 -0
@@ -7,7 +7,161 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
- ## [0.2.0] — 2026-05-12
10
+ ## [0.4.0] — 2026-05-19
11
+
12
+ ### Added
13
+
14
+ #### Schedules, LSB, Network resources (ported from desktop site-plan)
15
+
16
+ - `client.schedules` — CRUD on `/api/v2/tdoa/schedules/`: `list`,
17
+ `get`, `create`, `update`, `delete`. The per-slot table is carried
18
+ as an opaque `content` blob so callers can round-trip both v1 and
19
+ v2 schedule wire formats without an SDK release. New
20
+ `rtls_sdk.Schedule` model.
21
+ - `client.lsb` — read/write the Location Synchronization Bus
22
+ settings on `/api/v2/tdoa/settings/`: `get`, `update`. Settings
23
+ include the active-schedule pointer, autostart flag, and per-anchor
24
+ sync roles (master / distributor / client). The server upserts on
25
+ POST keyed by `project_uid` in the body. The `values` payload is
26
+ exposed as `dict[str, Any]` to support both `lsb_version=1` (`sfd`,
27
+ `autostart_devices`) and `lsb_version=2` (`masters`,
28
+ `distributors`) shapes side-by-side. The SDK also merges the
29
+ top-level `tdoa_version` / `lsb_version` fields from the response
30
+ into the model. New `rtls_sdk.LsbSettings` model.
31
+ - `client.network` — `/api/v2/network_settings/` read/update for the
32
+ per-project service-mode toggle (`enabled`, `duration`,
33
+ `devices_in_service_mode`). New `rtls_sdk.NetworkSettings` and
34
+ `rtls_sdk.ServiceMode` models; null device lists coerce to `[]`.
35
+ - Live tests under `tests/live/test_schedules.py`,
36
+ `tests/live/test_lsb.py`, `tests/live/test_network.py`. The
37
+ service-mode toggle test is guarded by
38
+ `RTLS_LIVE_ALLOW_SERVICE_MODE_TOGGLE=1` because it affects real
39
+ hardware.
40
+ - Docs: `docs/entities/schedules.md`, `lsb.md`, `network.md`.
41
+
42
+ #### LSB start/stop orchestration
43
+
44
+ - `client.lsb.start(masters, *, bcs=1, tdoas=0, timeout=9.0,
45
+ resend_interval=3.0, check_interval=0.7)` — push `sync_slot` + `sf`
46
+ to each master and poll `hw_params` until every node echoes back
47
+ the configured values. Raises `LsbConvergenceError` (with the
48
+ unresolved MAC list) on timeout. Faithful port of the desktop
49
+ site-plan's start flow.
50
+ - `client.lsb.stop(macs, *, timeout=15.0, …)` — send the all-zero
51
+ `sf` "stop trick" to each master and poll until every node reports
52
+ `sf.value.sfd == 0` (or `addr` empty / `"0000"`).
53
+ - New `LsbMasterSpec` dataclass (`mac`, `addr`, `slot`, `sfd`) for
54
+ the `start()` argument.
55
+ - New `LsbConvergenceError` exception carrying `unresolved: list[str]`.
56
+
57
+ #### LSB settings accessors and configured-start convenience
58
+
59
+ - `LsbSettings.masters()` and `LsbSettings.distributors()` — typed
60
+ views over the opaque `values` dict that smooth the v1/v2 fork.
61
+ Return frozen `LsbMasterEntry` / `LsbDistributorEntry` dataclasses
62
+ (also exported at the top level). The wire shape of `values` stays
63
+ an opaque dict for forward-compatibility.
64
+ - `client.lsb.start_configured(*, tdoas, bcs=None, …)` — convenience
65
+ start: reads LSB settings, joins anchors + nodes, derives each
66
+ master's MAC and 4-char hex addr (from `node.node_id`), then
67
+ delegates to `client.lsb.start([...])`. Raises `RuntimeError` with
68
+ a clear message when resolution fails (no masters configured, no
69
+ registered node for an anchor's MAC, etc.). `bcs` defaults to
70
+ `max(slot) + 1` matching site-plan.
71
+
72
+ #### Node parameter primitives
73
+
74
+ - `client.nodes.send_param(mac, **plist)` — POSTs to
75
+ `/api/v2/win_message_que/create.json/`. Accepts any plist keys
76
+ (`sf`, `sync_slot`, `nwk_cfg`, `srv_mode`, `tdoa_schedule`,
77
+ `tdoa_schd`, `p`). Multiple keys per call supported. MAC is
78
+ normalised.
79
+ - `client.nodes.read_params(macs, fields, *, force_update=False)` —
80
+ GETs `/api/v2/hw_params.json` with repeated `mac=` and `fields=`
81
+ query parameters (matching the desktop client's wire format).
82
+ Returns `list[NodeParams]`. Server-side `"pending"` strings on
83
+ each field coerce to `None` so callers always see `dict | None`.
84
+ - New `rtls_sdk.NodeParams` model.
85
+
86
+ ## [0.3.0] — 2026-05-13
87
+
88
+ ### Documentation
89
+
90
+ - New `entities/streaming.md` documenting `client.ws`, the eight
91
+ typed channel models, reconnects, heartbeats, backpressure, and a
92
+ REST → WS round-trip recipe. Reference pages (`resources.md`,
93
+ `models.md`, `errors.md`) now list the WS surface. Catalog and
94
+ quickstart link to the new page.
95
+
96
+ ### Added
97
+
98
+ #### Typed WS channel models (M18)
99
+
100
+ - `rtls_sdk.models.ws` — new sub-package with one pydantic model per
101
+ decoded channel: `PositionMessage` (pos), `NotifyMessage` (notify),
102
+ `AlarmMessage` (alarm), `ZoneEventMessage` (zone_event),
103
+ `SensorMessage` (sensor), `OtaMessage` (ota), `ParamPMessage`
104
+ (param_p), `UserMsgMessage` (user_msg). All eight inherit from
105
+ `WsChannelEnvelope` and carry the full wire dict on `.raw` for
106
+ forward-compat field access.
107
+ - `client.ws.subscribe(...)` now yields typed messages instead of the
108
+ M17 untyped envelope; iteration patterns like
109
+ `match msg: case PositionMessage(x=x, y=y): ...` work directly. The
110
+ yield type narrowed from `WsChannelMessage` to `WsChannelEnvelope`.
111
+ - Unknown channels and decode failures fall back to
112
+ `WsChannelMessage` (now a pydantic model, was a frozen dataclass) so
113
+ the iterator never dies on a single bad frame. Validation errors
114
+ log at WARNING with the channel name.
115
+ - New `include_heartbeats: bool = False` kwarg on
116
+ `client.ws.subscribe(...)`. By default, `notify` frames with
117
+ `action == "HB"` are dropped at the reader; opt-in callers receive
118
+ them as `NotifyMessage(action="HB")`. Heartbeat drops do not count
119
+ toward `session.messages_dropped` — that counter remains
120
+ backpressure-only.
121
+
122
+ ### Changed
123
+
124
+ - `WsChannelMessage` moved from `rtls_sdk._ws` to
125
+ `rtls_sdk.models.ws`; the import path under the top-level package
126
+ (`from rtls_sdk import WsChannelMessage`) is unchanged.
127
+
128
+ #### WebSocket subscription scaffold (M17)
129
+
130
+ - `client.ws` — new `WsAPI` sub-client wrapping the JSON-RPC 2.0
131
+ subscriptions-manager. `ws.list_channels()` returns the server's
132
+ advertised channel inventory in a one-shot dial (no auth required).
133
+ `ws.subscribe(channels, *, project_uid=None, reconnect=True,
134
+ queue_max=1024)` opens a long-lived session that is both a context
135
+ manager and a blocking iterator of `WsChannelMessage` envelopes.
136
+ Naming is transport-specific on purpose — future streaming
137
+ protocols (MQTT, Kafka) would land as siblings (`client.mqtt`,
138
+ `client.kafka`) rather than crowd a generic `client.stream`
139
+ namespace.
140
+ - `WsChannelMessage(channel, raw)` — untyped pass-through for any
141
+ channel; typed channel models land in M18.
142
+ - Reader runs on a daemon thread behind a bounded queue. Backpressure
143
+ drops the oldest message and bumps `session.messages_dropped`.
144
+ Auto-reconnect with capped exponential backoff
145
+ `(1, 2, 4, 8, 15, 30, 30, 30)` seconds on unclean disconnects;
146
+ re-subscribes the current channel set. Auth failure during reconnect
147
+ surfaces as `WsAuthError` from the iterator and stops further
148
+ attempts.
149
+ - New error classes `WsError`, `WsAuthError`, and `WsProtocolError`
150
+ (all subclasses of `RtlsError`).
151
+ - New dependency: `websockets >= 12.0`.
152
+
153
+ #### User messages REST (M16)
154
+
155
+ - `client.user_messages.send(tag_uid, hex)` — new `UserMessagesAPI`
156
+ sub-client; sends a hex payload to a tag's badge over the WIN
157
+ gateway. Wire endpoint: `POST /api/v2/user_msg/`. Independent of
158
+ `client.messaging` (which targets the inbox endpoint at
159
+ `/api/v2/messaging`); the two surfaces stay separate because the
160
+ endpoints serve different features on the wire (UDP frame vs. inbox
161
+ row) and share no storage. Client-side validation rejects malformed
162
+ hex before any network I/O.
163
+
164
+ ## [0.2.0] — 2026-05-13
11
165
 
12
166
  ### Added
13
167
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rtls-sdk
3
- Version: 0.2.0
3
+ Version: 0.4.0
4
4
  Summary: Python SDK for the RTLS REST API
5
5
  Project-URL: Homepage, https://github.com/rpplabs/rtls-sdk
6
6
  Project-URL: Documentation, https://github.com/rpplabs/rtls-sdk/tree/master/docs
@@ -21,6 +21,7 @@ Classifier: Typing :: Typed
21
21
  Requires-Python: >=3.10
22
22
  Requires-Dist: httpx>=0.27
23
23
  Requires-Dist: pydantic>=2.5
24
+ Requires-Dist: websockets>=12.0
24
25
  Provides-Extra: dev
25
26
  Requires-Dist: mypy>=1.8; extra == 'dev'
26
27
  Requires-Dist: pytest-cov>=4.1; extra == 'dev'
@@ -17,6 +17,12 @@ These prompts implement the design in `/Users/yzhbankov/rtls/rtls-sdk/DESIGN.md`
17
17
  | 13 | `13-m11-entity-docs.md` | M11 | Docs-only: reorganize user documentation around **entities**. New `docs/entities/` tree (one page per entity + catalog index) replaces the workflow-oriented guides. Answers "what can I do with X?" on one page. |
18
18
  | 14 | `14-m12-schema-audit-mac-fix.md` | M12 | Schema-correctness audit. Fix three real bugs caught by reading the server's Joi schemas directly: MAC format is bare 12-hex (not colon-separated); `tags.create` does not accept `mac_address` (server silently strips); add live MAC round-trip test as a regression gate. |
19
19
  | 15 | `15-m13-final-audit.md` | M13 | **Check-only.** Full ship-readiness audit across code quality, documentation site, internal consistency, wire-shape correctness, deep doc-quality analysis (with persona-driven proposals), and release artifacts. Produces `AUDIT.md` at the repo root with a verdict (ready / not ready), itemised blockers, repo-wide markdown link validation, and a prioritised list of documentation improvement proposals. Does not modify code or docs — a separate milestone applies any accepted fixes. |
20
+ | 16 | `16-m14-e2e-live-test.md` | M14 | Full-stack live E2E test (`tests/live/test_m14_full_stack.py`). Builds the entity tree top-down, lists, updates each kind, deletes bottom-up. Fix-test-retry discipline — surfaces SDK bugs and fixes them in-flight. |
21
+ | 17 | `17-m15-user-msg-websocket-research.md` | M15 | **Research-only.** Investigates the server-side `user_msg` REST endpoint and the JSON-RPC WebSocket protocol (subscriptions-manager). Produces `prompts/17-m15-research.md` with wire shapes, channel inventory, discrepancies, and concrete SDK extension signatures. |
22
+ | 18 | `18-m16-user-msg-rest.md` | M16 | `messaging.send_to_tag(tag_uid, hex)` — POST `/api/v2/user_msg/` sends a hex payload to a tag's badge over the WIN gateway. Unit + integration + live test. |
23
+ | 19 | `19-m17-ws-scaffold.md` | M17 | WebSocket sub-client `client.stream`: connect, list channels, subscribe / unsubscribe, blocking-iterator over `RawChannelMessage`, heartbeat passthrough, reconnect with backoff. Dependency: `websockets`. |
24
+ | 20 | `20-m18-ws-typed-models.md` | M18 | Typed channel models (`PositionMessage`, `AlarmMessage`, `NotifyMessage`, `ZoneEventMessage`, `SensorMessage`, `OtaMessage`, `ParamPMessage`, `UserMsgMessage`) + discriminated-union decode dispatch + `include_heartbeats` flag (default off). |
25
+ | 21 | `21-m19-ws-live-gate.md` | M19 | End-to-end live gate: REST `send_to_tag` → WS `user_msg` notify round-trip. Five-consecutive-pass stability requirement. |
20
26
 
21
27
  ## Inputs each prompt reads
22
28
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "rtls-sdk"
7
- version = "0.2.0"
7
+ version = "0.4.0"
8
8
  description = "Python SDK for the RTLS REST API"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -24,6 +24,7 @@ classifiers = [
24
24
  dependencies = [
25
25
  "httpx>=0.27",
26
26
  "pydantic>=2.5",
27
+ "websockets>=12.0",
27
28
  ]
28
29
 
29
30
  [project.optional-dependencies]
@@ -18,6 +18,7 @@ from .errors import (
18
18
  AuthenticationError,
19
19
  Conflict,
20
20
  ConnectionError,
21
+ LsbConvergenceError,
21
22
  NotFound,
22
23
  PartialFailureError,
23
24
  PermissionDenied,
@@ -28,6 +29,9 @@ from .errors import (
28
29
  RtlsError,
29
30
  ServerError,
30
31
  ValidationError,
32
+ WsAuthError,
33
+ WsError,
34
+ WsProtocolError,
31
35
  )
32
36
  from .models import (
33
37
  Alarm,
@@ -43,7 +47,12 @@ from .models import (
43
47
  Group,
44
48
  Host,
45
49
  ImportResult,
50
+ LsbDistributorEntry,
51
+ LsbMasterEntry,
52
+ LsbSettings,
53
+ NetworkSettings,
46
54
  Node,
55
+ NodeParams,
47
56
  NotificationProfile,
48
57
  NotificationType,
49
58
  Position,
@@ -51,6 +60,8 @@ from .models import (
51
60
  Report,
52
61
  ReportType,
53
62
  Role,
63
+ Schedule,
64
+ ServiceMode,
54
65
  Site,
55
66
  Subscriber,
56
67
  SubscriberAddresses,
@@ -66,14 +77,49 @@ from .models import (
66
77
  Zone,
67
78
  ZoneEvent,
68
79
  )
80
+ from .models.ws import (
81
+ AlarmMessage,
82
+ NotifyMessage,
83
+ OtaMessage,
84
+ ParamPMessage,
85
+ PositionMessage,
86
+ SensorMessage,
87
+ UserMsgMessage,
88
+ WsChannelEnvelope,
89
+ WsChannelMessage,
90
+ ZoneEventMessage,
91
+ )
92
+ from .resources import LsbAPI, NetworkAPI, SchedulesAPI, UserMessagesAPI, WsAPI
93
+ from .resources.lsb import LsbMasterSpec
94
+ from .resources.ws import WsSession
69
95
 
70
96
  __all__ = [ # noqa: RUF022 — grouped logically, not alphabetically
71
97
  # Client
72
98
  "RtlsClient",
99
+ # Resource sub-clients
100
+ "LsbAPI",
101
+ "LsbMasterSpec",
102
+ "NetworkAPI",
103
+ "SchedulesAPI",
104
+ "UserMessagesAPI",
105
+ "WsAPI",
106
+ # WebSocket types
107
+ "AlarmMessage",
108
+ "NotifyMessage",
109
+ "OtaMessage",
110
+ "ParamPMessage",
111
+ "PositionMessage",
112
+ "SensorMessage",
113
+ "UserMsgMessage",
114
+ "WsChannelEnvelope",
115
+ "WsChannelMessage",
116
+ "WsSession",
117
+ "ZoneEventMessage",
73
118
  # Errors
74
119
  "AuthenticationError",
75
120
  "Conflict",
76
121
  "ConnectionError",
122
+ "LsbConvergenceError",
77
123
  "NotFound",
78
124
  "PartialFailureError",
79
125
  "PermissionDenied",
@@ -84,6 +130,9 @@ __all__ = [ # noqa: RUF022 — grouped logically, not alphabetically
84
130
  "RtlsError",
85
131
  "ServerError",
86
132
  "ValidationError",
133
+ "WsAuthError",
134
+ "WsError",
135
+ "WsProtocolError",
87
136
  # Models
88
137
  "Alarm",
89
138
  "Anchor",
@@ -98,7 +147,12 @@ __all__ = [ # noqa: RUF022 — grouped logically, not alphabetically
98
147
  "Group",
99
148
  "Host",
100
149
  "ImportResult",
150
+ "LsbDistributorEntry",
151
+ "LsbMasterEntry",
152
+ "LsbSettings",
153
+ "NetworkSettings",
101
154
  "Node",
155
+ "NodeParams",
102
156
  "NotificationProfile",
103
157
  "NotificationType",
104
158
  "Position",
@@ -106,6 +160,8 @@ __all__ = [ # noqa: RUF022 — grouped logically, not alphabetically
106
160
  "Report",
107
161
  "ReportType",
108
162
  "Role",
163
+ "Schedule",
164
+ "ServiceMode",
109
165
  "Site",
110
166
  "Subscriber",
111
167
  "SubscriberAddresses",
@@ -30,16 +30,21 @@ from .resources import (
30
30
  FloorplansAPI,
31
31
  GroupsAPI,
32
32
  LoggerAPI,
33
+ LsbAPI,
33
34
  MessagingAPI,
35
+ NetworkAPI,
34
36
  NodesAPI,
35
37
  NotificationsAPI,
36
38
  ProjectsAPI,
37
39
  ReportsAPI,
40
+ SchedulesAPI,
38
41
  SitesAPI,
39
42
  SubscribersAPI,
40
43
  SystemAPI,
41
44
  TagsAPI,
45
+ UserMessagesAPI,
42
46
  UsersAPI,
47
+ WsAPI,
43
48
  ZonesAPI,
44
49
  )
45
50
 
@@ -178,8 +183,13 @@ class RtlsClient:
178
183
  self.system = SystemAPI(self)
179
184
  self.auth = AuthAPI(self)
180
185
  self.messaging = MessagingAPI(self)
186
+ self.user_messages = UserMessagesAPI(self)
187
+ self.ws = WsAPI(self)
181
188
  self.logger = LoggerAPI(self)
182
189
  self.context = ContextAPI(self)
190
+ self.schedules = SchedulesAPI(self)
191
+ self.lsb = LsbAPI(self)
192
+ self.network = NetworkAPI(self)
183
193
 
184
194
  # -- public scope helpers --------------------------------------------
185
195