bouwmeester-lockbox-api 0.1.0__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.
- bouwmeester_lockbox_api-0.1.0.dist-info/METADATA +193 -0
- bouwmeester_lockbox_api-0.1.0.dist-info/RECORD +9 -0
- bouwmeester_lockbox_api-0.1.0.dist-info/WHEEL +5 -0
- bouwmeester_lockbox_api-0.1.0.dist-info/licenses/LICENSE +661 -0
- bouwmeester_lockbox_api-0.1.0.dist-info/top_level.txt +1 -0
- lockbox/__init__.py +31 -0
- lockbox/_hub.py +82 -0
- lockbox/client.py +253 -0
- lockbox/configuration.py +312 -0
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: bouwmeester-lockbox-api
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python client for the Bouwmeester Lab Lockbox REST API
|
|
5
|
+
License-Expression: AGPL-3.0-only
|
|
6
|
+
Project-URL: Homepage, https://github.com/Bouwmeester-Lab/Lockbox-PythonApi
|
|
7
|
+
Project-URL: Repository, https://github.com/Bouwmeester-Lab/Lockbox-PythonApi
|
|
8
|
+
Project-URL: Issues, https://github.com/Bouwmeester-Lab/Lockbox-PythonApi/issues
|
|
9
|
+
Requires-Python: >=3.11
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Requires-Dist: httpx>=0.27
|
|
13
|
+
Requires-Dist: signalrcore==1.0.2
|
|
14
|
+
Provides-Extra: dev
|
|
15
|
+
Requires-Dist: pytest; extra == "dev"
|
|
16
|
+
Requires-Dist: ruff; extra == "dev"
|
|
17
|
+
Dynamic: license-file
|
|
18
|
+
|
|
19
|
+
# Lockbox Python API
|
|
20
|
+
|
|
21
|
+
Python 3.11+ synchronous controls for the Lockbox server. Install from this
|
|
22
|
+
directory with `python -m pip install .` (or `python -m pip install -e ".[dev]"`
|
|
23
|
+
for development).
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
from lockbox import Server, SlopePreference
|
|
27
|
+
|
|
28
|
+
with Server("http://lockboxcontrol.internal:5106") as server:
|
|
29
|
+
box = server.get_lockbox(1) # Choose an ID from server.list_lockboxes().
|
|
30
|
+
box.on_coarse_resonance_found(lambda: print("Coarse resonance found"))
|
|
31
|
+
box.on_fine_resonance_found(lambda: print("Fine resonance found"))
|
|
32
|
+
box.on_lockbox_freeze_ready(lambda: print("Ready to freeze"))
|
|
33
|
+
|
|
34
|
+
box.set_setpoint(0.0)
|
|
35
|
+
box.set_low_high_threshold(-100, 100, SlopePreference.POSITIVE)
|
|
36
|
+
box.lock()
|
|
37
|
+
input("Press Enter to close the client connections... ")
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
`Server` owns one HTTP client and two SignalR connections: `/Hubs/Status` for
|
|
41
|
+
events and `/Hubs/Run` for setpoint and threshold commands. It uses the server's
|
|
42
|
+
JSON hub protocol. Entering the context connects both hubs; exiting closes all
|
|
43
|
+
connections without changing the hardware. There are no reconnects or retries.
|
|
44
|
+
`Server(url, timeout=10.0)` sets HTTP timeouts and the time allowed for hub
|
|
45
|
+
handshake readiness and command completion, not for physical device operations.
|
|
46
|
+
Supply the server root URL, including any hosting path prefix, without `/api`.
|
|
47
|
+
|
|
48
|
+
`list_lockboxes()` and `get_lockbox(id)` reuse objects by ID, preserving callbacks
|
|
49
|
+
while refreshing their name, IP and MAC metadata. Objects are registered for
|
|
50
|
+
status routing automatically. `connect_to_status_hub(box)` also registers manually
|
|
51
|
+
created objects; repeating registration of the same object is harmless.
|
|
52
|
+
|
|
53
|
+
## Commands
|
|
54
|
+
|
|
55
|
+
All command methods return `None` after HTTP success or Run hub invocation
|
|
56
|
+
completion. They do not wait for a status or physical completion. Failed HTTP
|
|
57
|
+
responses raise `Exception(response.text)`; hub errors use the server-provided
|
|
58
|
+
error message. Transport errors propagate.
|
|
59
|
+
|
|
60
|
+
| Method | Argument convention |
|
|
61
|
+
| --- | --- |
|
|
62
|
+
| `set_gain(gain)` | Input gain, at least 1 |
|
|
63
|
+
| `set_demodulation_cutoff(frequency)` | Hz |
|
|
64
|
+
| `set_demodulation_phase(phase)` | Radians |
|
|
65
|
+
| `set_modulation_amplitude(amplitude)` | Integer DAC codes, 0–524287 |
|
|
66
|
+
| `set_demodulation_amplitude(amplitude)` | Server amplitude scale, 0–100 |
|
|
67
|
+
| `set_setpoint(setpoint)` | Native device setpoint units |
|
|
68
|
+
| `set_pid(p, i, d)` | Native device PID coefficients |
|
|
69
|
+
| `set_threshold(threshold)` | Integer reflection threshold |
|
|
70
|
+
| `set_low_high_threshold(low, high, slope_preference=SlopePreference.NONE)` | Integer error-signal limits; enum values `NONE`, `NEGATIVE`, `POSITIVE` |
|
|
71
|
+
| `set_fine_output_code(code)`, `set_coarse_output_code(code)` | Signed DAC codes, -524287–524287 |
|
|
72
|
+
| `lock()`, `unlock()` | Normal lock; unlock without starting a scan |
|
|
73
|
+
| `freeze()`, `unfreeze()` | Both toggle freeze through the same endpoint |
|
|
74
|
+
| `reload_configuration()` | Apply the server's saved configuration |
|
|
75
|
+
|
|
76
|
+
Setters change runtime values, not saved configuration. Validation is left to
|
|
77
|
+
the server, except conversion of the slope enum. Slope preference is forwarded
|
|
78
|
+
as the server enum value (0, 1, or 2). The current firmware `ThresholdsEndpoint`
|
|
79
|
+
updates limits but does not read the slope query parameter; actual slope behavior
|
|
80
|
+
depends on firmware support. This package does not change firmware.
|
|
81
|
+
|
|
82
|
+
The freeze endpoint may translate a firmware rejection (including not-ready 409)
|
|
83
|
+
into a server 500. The client reports the response it receives.
|
|
84
|
+
|
|
85
|
+
## Callbacks
|
|
86
|
+
|
|
87
|
+
Register callbacks after entering the context. Each registration stores one
|
|
88
|
+
zero-argument function; registering again replaces it.
|
|
89
|
+
|
|
90
|
+
| Registration | Incoming Arduino status |
|
|
91
|
+
| --- | --- |
|
|
92
|
+
| `on_coarse_resonance_found(callback)` | `CoarseResonanceFound` |
|
|
93
|
+
| `on_fine_resonance_found(callback)` | `FineResonanceFound` |
|
|
94
|
+
| `on_lock_lost(callback)` | `Unlocked` |
|
|
95
|
+
| `on_lockbox_frozen(callback)` | `FrozenEnabled` |
|
|
96
|
+
| `on_lockbox_unfrozen(callback)` | `FrozenDisabled` |
|
|
97
|
+
| `on_lockbox_freeze_ready(callback)` | `DriftEstimateReady` |
|
|
98
|
+
|
|
99
|
+
`on_lock_acquired` and `on_lockbox_recentering` raise `NotImplementedError`
|
|
100
|
+
because dedicated statuses do not exist. HTTP results never trigger callbacks.
|
|
101
|
+
Unregistered devices and unrelated statuses are ignored.
|
|
102
|
+
|
|
103
|
+
Functions run directly on the status receiver thread, without arguments or custom
|
|
104
|
+
exception recovery. They may issue commands using the separate HTTP/Run connections.
|
|
105
|
+
While a callback runs, it delays further status delivery: do not wait inside it
|
|
106
|
+
for another callback. There is no replay, ordering contract, or experiment scheduler.
|
|
107
|
+
|
|
108
|
+
## Saved configuration
|
|
109
|
+
|
|
110
|
+
Fetch an independent snapshot, edit its supported fields, and explicitly save it:
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
from lockbox import Server, PidControllerType, SlopePreference
|
|
114
|
+
|
|
115
|
+
with Server("http://lockboxcontrol.internal:5106") as server:
|
|
116
|
+
box = server.get_lockbox(1)
|
|
117
|
+
config = box.get_configuration()
|
|
118
|
+
config.pid.proportional_gain = 2.0
|
|
119
|
+
config.pid.controller_type = PidControllerType.FILTERED_PID
|
|
120
|
+
config.demodulation.set_point = 0.1
|
|
121
|
+
config.lock_thresholds.out_of_lock_threshold = 80
|
|
122
|
+
config.lock_thresholds.ms_without_lock = 250
|
|
123
|
+
config.lock_thresholds.delock_smoothing_constant = 0.25
|
|
124
|
+
config.coarse_scan.scan_min = -2000
|
|
125
|
+
config.coarse_scan.scan_max = 2000
|
|
126
|
+
config.fine_scan.scan_time_ms = 1500
|
|
127
|
+
if config.pdh is not None:
|
|
128
|
+
config.pdh.low_limit = -100.5
|
|
129
|
+
config.pdh.high_limit = 100.25
|
|
130
|
+
config.pdh.slope_preference = SlopePreference.POSITIVE
|
|
131
|
+
box.save_configuration(config)
|
|
132
|
+
|
|
133
|
+
# Apply only when wanted:
|
|
134
|
+
box.reload_configuration()
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
`get_configuration()` reads `GET /api/LockboxConfigurations/arduino/{id}` on
|
|
138
|
+
every call. It returns a `LockboxConfiguration` with typed, snake_case sections:
|
|
139
|
+
`pid`, `demodulation`, `lock_thresholds`, `pdh`, `coarse_scan`, `fine_scan`,
|
|
140
|
+
`coarse_refinement`, `fine_refinement`, and top-level `modulation_amplitude`.
|
|
141
|
+
An absent optional PDH section is `None`. Runtime setters do not modify snapshots.
|
|
142
|
+
`PidControllerType` has `SIMPLE_PI` and `FILTERED_PID`; slope settings reuse
|
|
143
|
+
`SlopePreference.NONE`, `NEGATIVE`, and `POSITIVE`.
|
|
144
|
+
|
|
145
|
+
All lock-threshold and coarse/fine scan settings are editable. IDs, device
|
|
146
|
+
identity, section references, `notch_filters`, and
|
|
147
|
+
`filter_selections` are read-only. Filter selections expose `notch_filter_id`,
|
|
148
|
+
`enabled`, and the associated read-only `notch_filter` definition when present.
|
|
149
|
+
Snapshots update existing records only; a missing configuration raises the normal
|
|
150
|
+
HTTP error. They cannot create records or replace section identities.
|
|
151
|
+
|
|
152
|
+
`save_configuration(config)` returns `None` after these eight sequential writes
|
|
153
|
+
(seven when PDH is absent):
|
|
154
|
+
|
|
155
|
+
| Saved settings | Endpoint |
|
|
156
|
+
| --- | --- |
|
|
157
|
+
| PID coefficients, controller type, derivative cutoff | `PUT /api/PidConfigurations/{pidId}/persist` |
|
|
158
|
+
| Demodulation cutoff, phase, amplitude, setpoint | `PUT /api/DemodulationConfigurations/{id}/persist` |
|
|
159
|
+
| Modulation amplitude | `PUT /api/PidConfigurations/{pidId}/modulation` |
|
|
160
|
+
| All lock-threshold settings | `PUT /api/LockThresholdSettings/{id}` |
|
|
161
|
+
| PDH low/high limits and slope, when present | `PUT /api/PdhSettings/{id}` |
|
|
162
|
+
| Coarse scan limits and time | `PUT /api/ScanSettings/{coarseId}` |
|
|
163
|
+
| Fine scan limits and time | `PUT /api/ScanSettings/{fineId}` |
|
|
164
|
+
| Coarse and fine refinement | `PUT /api/PidConfigurations/{pidId}/refinement` |
|
|
165
|
+
|
|
166
|
+
Saving does not apply hardware settings or modify filters. It sends all supported
|
|
167
|
+
values, including unchanged ones. Fetch again before editing if another client
|
|
168
|
+
may have changed the saved configuration; there is no conflict detection.
|
|
169
|
+
|
|
170
|
+
The snapshot's device/server identity, required linked records, and payloads are
|
|
171
|
+
checked before any writes, including both required scan records. Nonfinite or
|
|
172
|
+
unserializable payloads are rejected before writing. PDH limits accept decimal
|
|
173
|
+
values and are transmitted without integer conversion. Range and ordering
|
|
174
|
+
validation is performed by the server. PDH is skipped only when both its section
|
|
175
|
+
and linked ID are absent; a linked ID with a missing section is an error.
|
|
176
|
+
|
|
177
|
+
**Saving is not atomic.** A failed response stops subsequent requests and raises
|
|
178
|
+
`Exception(response.text)`; earlier successful writes remain saved. There is no
|
|
179
|
+
rollback or retry. Other server-side validation errors can therefore leave a
|
|
180
|
+
partially saved configuration. The ordinary PID update endpoint is not used
|
|
181
|
+
because it also applies changes to hardware.
|
|
182
|
+
|
|
183
|
+
This version requires the server's resource-style PUT endpoints for
|
|
184
|
+
`LockThresholdSettings`, `PdhSettings`, and `ScanSettings`. There is no fallback
|
|
185
|
+
to the old integer-only threshold-saving endpoint. Notch-filter editing remains
|
|
186
|
+
excluded; definitions, selections, and enabled flags are preserved unchanged.
|
|
187
|
+
Runtime setters still use their existing interfaces; fractional PDH support here
|
|
188
|
+
applies to saved configuration.
|
|
189
|
+
|
|
190
|
+
## Development
|
|
191
|
+
|
|
192
|
+
Run `python -m pytest` and `python -m ruff check src tests` from this directory.
|
|
193
|
+
Tests use mock HTTP responses and hub connections; they do not operate hardware.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
bouwmeester_lockbox_api-0.1.0.dist-info/licenses/LICENSE,sha256=ILBn-G3jdarm2w8oOrLmXeJNU3czuJvVhDLBASWdhM8,34522
|
|
2
|
+
lockbox/__init__.py,sha256=N2Ko0lVQCj_seVOSsQ_80fmkq0tgHdHG58ArP21QY9A,682
|
|
3
|
+
lockbox/_hub.py,sha256=qMJIfFwxFj-nVSGVvmo_b_ZUy1uqUb4AwwNemR9-bSo,2963
|
|
4
|
+
lockbox/client.py,sha256=PlzHQB-GjriMgjw82XIByEsMulMs4pXAoJ7ho-c_6uc,9490
|
|
5
|
+
lockbox/configuration.py,sha256=HYc8lM-AH-s5FxsDGqkCfTTRNHKg3oTGaMLmr3PYFdg,9953
|
|
6
|
+
bouwmeester_lockbox_api-0.1.0.dist-info/METADATA,sha256=avb6Mz5gXOf1CVxWiuNL7S5oOQxA3vYjBkoLCLJ5xxE,9660
|
|
7
|
+
bouwmeester_lockbox_api-0.1.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
8
|
+
bouwmeester_lockbox_api-0.1.0.dist-info/top_level.txt,sha256=_iFppj6wKh_GTFFFdDcJbbJ0dIoQWhI6Ng2r6Tr11As,8
|
|
9
|
+
bouwmeester_lockbox_api-0.1.0.dist-info/RECORD,,
|