|
1 | 1 | # Duet Software Framework Python Bindings |
2 | 2 |
|
3 | | -This is also availabe as a [pip package on pypi](https://pypi.org/project/dsf-python/) |
| 3 | +`dsf-python` provides Python bindings for the Duet Software Framework control server. |
| 4 | +It exposes the DSF socket protocol as Python connection classes, typed object model |
| 5 | +classes, command builders, and helpers for custom HTTP endpoints. |
4 | 6 |
|
5 | | -Find out more about [Duet Software Framework](https://github.com/Duet3D/DuetSoftwareFramework). |
| 7 | +This project is also published on [PyPI](https://pypi.org/project/dsf-python/). |
6 | 8 |
|
7 | | -Examples of the [Duet Software Framework Python Bindings](https://github.com/Duet3D/dsf-python/tree/main/examples). |
| 9 | +Useful links: |
8 | 10 |
|
9 | | -Get in touch with the community at [Duet Software Framework Forum](https://forum.duet3d.com/category/31/dsf-development) for bug reports, discussion and any kind of exchange. |
| 11 | +- [Duet Software Framework](https://github.com/Duet3D/DuetSoftwareFramework) |
| 12 | +- [dsf-python examples](https://github.com/Duet3D/dsf-python/tree/main/examples) |
| 13 | +- [Duet Software Framework forum](https://forum.duet3d.com/category/31/dsf-development) |
| 14 | + |
| 15 | +## What The Library Contains |
| 16 | + |
| 17 | +The top-level package is organised around the main DSF workflows. |
| 18 | + |
| 19 | +- `dsf.connections`: socket-based client connections for commands, subscriptions, and code interception |
| 20 | +- `dsf.commands`: request payload builders for low-level DSF commands |
| 21 | +- `dsf.object_model`: the typed DSF object model and related enums |
| 22 | +- `dsf.http`: helpers for custom HTTP and WebSocket endpoints exposed through DSF |
| 23 | +- `dsf.exceptions`: shared exception types raised by connection classes |
10 | 24 |
|
11 | 25 | ## Installation |
12 | | -This package contains a `setup.py` so it can be installed with `python3 setup.py install`. |
13 | 26 |
|
14 | | -## Usage |
15 | | -See included `examples/` folder for various use cases. |
| 27 | +The package can be installed from source: |
| 28 | + |
| 29 | +```bash |
| 30 | +python3 setup.py install |
| 31 | +``` |
| 32 | + |
| 33 | +Or with `pip`: |
| 34 | + |
| 35 | +```bash |
| 36 | +python3 -m pip install dsf-python |
| 37 | +``` |
| 38 | + |
| 39 | +Most code using this library must run on a system with Duet Software Framework |
| 40 | +installed and with permission to access the DSF UNIX socket. |
| 41 | + |
| 42 | +## Quick Start |
| 43 | + |
| 44 | +### Run A Simple Command |
| 45 | + |
| 46 | +Use `CommandConnection` to send general-purpose commands such as G-code or to |
| 47 | +request the full object model on demand. |
| 48 | + |
| 49 | +```python |
| 50 | +from dsf.connections import CommandConnection |
| 51 | + |
| 52 | +connection = CommandConnection() |
| 53 | +connection.connect() |
| 54 | + |
| 55 | +response = connection.perform_simple_code("M115") |
| 56 | +print(response.result) |
| 57 | + |
| 58 | +connection.close() |
| 59 | +``` |
| 60 | + |
| 61 | +### Read The Object Model Once |
| 62 | + |
| 63 | +```python |
| 64 | +from dsf.connections import CommandConnection |
| 65 | + |
| 66 | +connection = CommandConnection() |
| 67 | +connection.connect() |
| 68 | + |
| 69 | +object_model = connection.get_object_model() |
| 70 | +print(object_model.state.status) |
| 71 | +print(object_model.move.axes[0].letter) |
| 72 | + |
| 73 | +connection.close() |
| 74 | +``` |
| 75 | + |
| 76 | +### Subscribe To Object Model Updates |
16 | 77 |
|
17 | | -For patch subscriptions, `BaseConnection.has_data_available()` can be used to poll a |
18 | | -subscription socket without blocking on `receive_json()` or `get_object_model_patch()`. |
19 | | -This is useful when object model updates arrive less frequently than another data source. |
| 78 | +Use `SubscribeConnection` when you want DSF to push object model updates over a |
| 79 | +subscription socket. |
20 | 80 |
|
21 | 81 | ```python |
22 | 82 | from dsf.connections import SubscribeConnection, SubscriptionMode |
23 | 83 |
|
24 | 84 | subscription = SubscribeConnection(SubscriptionMode.PATCH) |
25 | 85 | subscription.connect() |
| 86 | + |
26 | 87 | object_model = subscription.get_object_model() |
27 | 88 |
|
28 | 89 | while True: |
29 | | - if subscription.has_data_available(): |
30 | | - object_model.update_from_json(subscription.get_object_model_patch()) |
| 90 | + object_model = subscription.get_object_model() |
| 91 | +``` |
| 92 | + |
| 93 | +In `SubscriptionMode.PATCH`, the first `get_object_model()` call reads the full |
| 94 | +model. Later calls reuse an internal cached model and apply one queued patch if |
| 95 | +available. |
| 96 | + |
| 97 | +## Connections |
| 98 | + |
| 99 | +### CommandConnection |
| 100 | + |
| 101 | +`CommandConnection` is the general-purpose connection type. It is used for: |
| 102 | + |
| 103 | +- sending simple G-code |
| 104 | +- requesting the full object model |
| 105 | +- working with files, packages, plugins, and user sessions through DSF commands |
| 106 | +- issuing lower-level requests through the command helpers in `dsf.commands` |
| 107 | + |
| 108 | +Choose this connection when you want request-response behaviour and do not need |
| 109 | +streamed updates. |
| 110 | + |
| 111 | +### SubscribeConnection |
| 112 | + |
| 113 | +`SubscribeConnection` receives streamed object model updates from DSF. It supports: |
| 114 | + |
| 115 | +- `SubscriptionMode.FULL`: every update is a full object model |
| 116 | +- `SubscriptionMode.PATCH`: every update is a partial JSON fragment |
| 117 | + |
| 118 | +#### Key-Based Callbacks |
| 119 | + |
| 120 | +`SubscribeConnection.subscribe_to_keys()` can run callbacks synchronously when |
| 121 | +selected object model paths appear in a patch update processed by `get_object_model()`. |
| 122 | + |
| 123 | +Key paths use dot notation and can include list indexes: |
| 124 | + |
| 125 | +- `state.upTime` |
| 126 | +- `heat.heaters.0.current` |
| 127 | +- `move.axes.2.userPosition` |
| 128 | + |
| 129 | +Use `^` in a list position to match any changed index: |
| 130 | + |
| 131 | +- `heat.heaters.^.current` |
| 132 | +- `tools.^.state` |
| 133 | + |
| 134 | +Callbacks always receive keyword arguments: |
| 135 | + |
| 136 | +- `key`: the subscribed key path that matched |
| 137 | +- `data`: the changed value found at that path |
| 138 | +- `indices`: a tuple of matched wildcard indexes, or `None` when the key does not use `^` |
| 139 | + |
| 140 | +```python |
| 141 | +from dsf.connections import SubscribeConnection, SubscriptionMode |
| 142 | + |
| 143 | + |
| 144 | +def handle_change(*, key, data, indices): |
| 145 | + print(key, data, indices) |
| 146 | + |
| 147 | + |
| 148 | +subscription = SubscribeConnection(SubscriptionMode.PATCH) |
| 149 | +subscription.connect() |
| 150 | +subscription.get_object_model() |
| 151 | + |
| 152 | +unsubscribe = subscription.subscribe_to_keys( |
| 153 | + ["heat.heaters.0.current", "state.upTime"], |
| 154 | + handle_change, |
| 155 | +) |
| 156 | + |
| 157 | +while True: |
| 158 | + object_model = subscription.get_object_model() |
| 159 | +``` |
| 160 | + |
| 161 | +Wildcard example: |
| 162 | + |
| 163 | +```python |
| 164 | +def handle_any_heater(*, key, data, indices): |
| 165 | + print(key, indices, data) |
| 166 | + |
| 167 | + |
| 168 | +subscription.subscribe_to_keys( |
| 169 | + ["heat.heaters.^.current"], |
| 170 | + handle_any_heater, |
| 171 | +) |
| 172 | +``` |
| 173 | + |
| 174 | +### InterceptConnection |
| 175 | + |
| 176 | +`InterceptConnection` is used for custom code handling and code interception. Use it |
| 177 | +when a plugin needs to receive G/M/T-code events before or after the firmware handles them. |
| 178 | + |
| 179 | +Typical use cases include: |
| 180 | + |
| 181 | +- implementing custom M-codes |
| 182 | +- inspecting or rewriting commands |
| 183 | +- reacting to executed code notifications |
| 184 | + |
| 185 | +See `examples/custom_m_codes.py` for a complete example. |
| 186 | + |
| 187 | +## Object Model |
| 188 | + |
| 189 | +The `dsf.object_model` package mirrors the DSF object model in typed Python classes. |
| 190 | +It lets you work with structured properties instead of manually traversing raw JSON. |
| 191 | + |
| 192 | +Examples: |
| 193 | + |
| 194 | +```python |
| 195 | +print(object_model.boards[0].name) |
| 196 | +print(object_model.state.up_time) |
| 197 | +print(object_model.move.axes[0].letter) |
| 198 | +print(object_model.tools[0].state) |
| 199 | +``` |
| 200 | + |
| 201 | +Object model instances support JSON-driven updates: |
| 202 | + |
| 203 | +```python |
| 204 | +object_model.update_from_json({"state": {"upTime": 1234}}) |
| 205 | +print(object_model.state.up_time) |
31 | 206 | ``` |
| 207 | + |
| 208 | +This is the mechanism used internally when patch subscriptions are applied. |
| 209 | + |
| 210 | +## Commands |
| 211 | + |
| 212 | +The `dsf.commands` package contains low-level command builders for code execution, |
| 213 | +file access, plugins, packages, object model manipulation, and other DSF protocol |
| 214 | +requests. |
| 215 | + |
| 216 | +Most users should start with the higher-level methods on `CommandConnection` or |
| 217 | +`BaseCommandConnection`. Reach for `dsf.commands` directly when you need explicit |
| 218 | +control over the payload sent to DSF. |
| 219 | + |
| 220 | +## Custom HTTP Endpoints |
| 221 | + |
| 222 | +The `dsf.http` module contains helpers for custom HTTP and WebSocket endpoints. |
| 223 | +This is useful when a DSF plugin needs to expose an HTTP route that is handled by |
| 224 | +Python code. |
| 225 | + |
| 226 | +Key types include: |
| 227 | + |
| 228 | +- `HttpEndpointUnixSocket` |
| 229 | +- `HttpEndpointConnection` |
| 230 | +- `ReceivedHttpRequest` |
| 231 | +- `HttpResponseType` |
| 232 | + |
| 233 | +See `examples/custom_http_endpoint.py` for a practical example. |
| 234 | + |
| 235 | +## Included Examples |
| 236 | + |
| 237 | +The `examples/` directory demonstrates the main workflows supported by the library. |
| 238 | + |
| 239 | +- `send_simple_code.py`: send G-code over a command connection |
| 240 | +- `subscribe_object_model.py`: subscribe to object model updates |
| 241 | +- `custom_m_codes.py`: intercept and implement custom M-codes |
| 242 | +- `custom_http_endpoint.py`: serve a custom HTTP endpoint through DSF |
| 243 | + |
| 244 | +## API Reference |
| 245 | + |
| 246 | +The Sphinx docs expose the package API for the major public modules: |
| 247 | + |
| 248 | +- `dsf` |
| 249 | +- `dsf.connections` |
| 250 | +- `dsf.commands` |
| 251 | +- `dsf.object_model` |
| 252 | +- `dsf.http` |
| 253 | + |
| 254 | +## Development Notes |
| 255 | + |
| 256 | +The library talks to DSF using the configured UNIX socket path. By default this is |
| 257 | +resolved from the DSF config and falls back to `/run/dsf/dcs.sock`. |
| 258 | + |
| 259 | +If you are extending the library itself, the test suite under `tests/` contains |
| 260 | +mock socket servers and object model fixtures that are useful for validating new |
| 261 | +connection behaviour. |
0 commit comments