Skip to content

Commit a9bb7a4

Browse files
author
Peter Braun
committed
docs
1 parent 51429a3 commit a9bb7a4

File tree

4 files changed

+54
-109
lines changed

4 files changed

+54
-109
lines changed

README.md

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,6 @@
88

99
![secop-ophyd-arch](https://github.com/user-attachments/assets/cd82cfbe-68dc-4b3c-b872-5b1b7c7db82a)
1010

11-
## Documentation
11+
SECoP-Ophyd acts as a bridge between SECoP-enabled hardware and Bluesky's ophyd layer. It uses [Frappy](https://github.com/SampleEnvironment/frappy) to communicate with SECoP nodes over TCP, automatically generating [ophyd-async](https://blueskyproject.io/ophyd-async/main/index.html) device objects from the node's descriptive data. These devices can then be used in [Bluesky plans](https://blueskyproject.io/bluesky/main/tutorial.html#the-run-engine-and-plans) just like any other ophyd device, enabling seamless integration with EPICS, Tango, and other control system backends.
1212

13-
| Resource | Link |
14-
| :-------------: | :----------------------------------------------------------: |
15-
| Documentation | <https://sampleenvironment.github.io/secop-ophyd/> |
16-
| Demo Repository | <https://codebase.helmholtz.cloud/rock-it-secop/secop-sim> |
17-
| PyPI | <https://pypi.org/project/secop-ophyd> |
18-
| Issue Tracker | <https://github.com/SampleEnvironment/secop-ophyd/issues> |
19-
20-
## Further Information
21-
22-
- [SECoP Specification](https://sampleenvironment.github.io/secop-site/)
23-
- [Frappy (Framework for implementing SEC nodes)](https://github.com/SampleEnvironment/frappy)
24-
- [Ophyd-async Documentation](https://blueskyproject.io/ophyd-async/main/index.html)
25-
- [Bluesky Project](https://blueskyproject.io/)
13+
For more information, see the [full documentation](https://sampleenvironment.github.io/secop-ophyd/).

docs/source/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
.. include:: ../../README.md
22
:parser: myst_parser.sphinx_
3+
:end-before: For more information
34

45
How the documentation is structured
56
------------------------------------

docs/source/tutorial.rst

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -384,22 +384,3 @@ Complete Catalysis Experiment
384384
385385
# Execute the experiment
386386
RE(catalysis_experiment())
387-
388-
Key Features Demonstrated
389-
--------------------------
390-
391-
This tutorial showcased:
392-
393-
1. **Dynamic device generation** - No manual device class definition needed
394-
2. **Type hints and autocompletion** - Generated class files for better development
395-
3. **SECoP commands as plans** - Seamless integration with Bluesky
396-
4. **Complex experimental logic** - Temperature ramping, gas switching, and data acquisition
397-
5. **Metadata integration** - Adding experimental context to runs
398-
6. **Status handling** - Waiting for operations to complete
399-
400-
Next Steps
401-
----------
402-
403-
- Explore the :doc:`user_guide` for more advanced patterns
404-
- Check the :doc:`reference` for complete API documentation
405-
- Try integrating your own SECoP devices

docs/source/user_guide.rst

Lines changed: 51 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ The :class:`~secop_ophyd.SECoPDevices.SECoPNodeDevice` constructor accepts the f
7979

8080
- ``loglevel`` (optional): Control logging verbosity. Options: ``"DEBUG"``, ``"INFO"``, ``"WARNING"``, ``"ERROR"``, ``"CRITICAL"``
8181

82-
- ``logdir`` (optional): Directory path for storing log files. If ``None``, logs only to console.
82+
- ``logdir`` (optional): Directory path for storing log files. If ``None``, the default logdir ``.secop-ophyd/`` is set.
8383

8484
**Examples:**
8585

@@ -153,11 +153,11 @@ A SECoP-Ophyd device follows this structure:
153153
.. code-block:: text
154154
155155
SECoPNodeDevice (Node)
156-
├── module_1 (Module)
156+
├── SECoPMoveableDevice (Module)
157157
│ ├── parameter_a (Signal)
158158
│ ├── parameter_b (Signal)
159159
│ └── command_x (Method)
160-
└── module_2 (Module)
160+
└── SECoPReadableDevice (Module)
161161
├── parameter_c (Signal)
162162
└── command_y (Method)
163163
@@ -168,29 +168,13 @@ Example with a temperature controller:
168168
device
169169
├── temperature_controller
170170
│ ├── value # Current temperature (readable)
171-
│ ├── target # Target temperature (readable/settable)
172-
│ ├── ramp # Ramp rate (readable/settable)
171+
│ ├── target # Target temperature (readable/writable)
172+
│ ├── ramp # Ramp rate (readable/writable)
173173
│ ├── status # Device status
174174
│ └── reset() # Reset command
175175
└── pressure_sensor
176176
└── value # Current pressure (readable)
177177
178-
Exploring Device Structure
179-
~~~~~~~~~~~~~~~~~~~~~~~~~~~
180-
181-
Without generated class files, explore the device interactively:
182-
183-
.. code-block:: python
184-
185-
# List all modules
186-
print(device.component_names)
187-
188-
# Access a module
189-
temp_module = getattr(device, 'temperature_controller')
190-
191-
# List module's parameters
192-
print(temp_module.component_names)
193-
194178
Working with Signals
195179
--------------------
196180

@@ -214,42 +198,63 @@ Read single values:
214198
Setting Signals
215199
~~~~~~~~~~~~~~~
216200

217-
Use ``abs_set`` for absolute positioning:
201+
There's an important distinction when setting values on movable devices (SECoP modules with the ``Drivable`` interface class):
202+
203+
**Module-level vs Signal-level Setting**
204+
205+
For a movable device like a temperature controller, you can set the target it should drive toward at two levels:
206+
207+
1. **Module-level** (preferred): ``device.temperature_controller.set(value)``
208+
209+
- Sets the target parameter AND waits for the device to reach the setpoint
210+
- Returns a status that completes only when the module returns to IDLE state
211+
- Use this when you want to wait for the operation to complete
212+
213+
2. **Signal-level**: ``device.temperature_controller.target.set(value)``
214+
215+
- Only sets the target parameter value
216+
- Returns immediately once the parameter is written
217+
- Use this for setting config parameters
218+
219+
**Examples:**
218220

219221
.. code-block:: python
220222
221223
from bluesky.plan_stubs import mv, abs_set
222224
223-
# Simple set
224-
RE(abs_set(device.temperature.target, 300))
225+
# Module-level set - waits until temperature is reached
226+
RE(abs_set(device.temperature_controller, 300))
227+
228+
# Signal-level set - returns immediately after setting target
229+
RE(abs_set(device.temperature_controller.target, 300))
230+
231+
# Explicitly control wait behavior
232+
RE(abs_set(device.temperature_controller, 300, wait=True)) # Wait for completion
233+
RE(abs_set(device.temperature_controller, 300, wait=False)) # Don't wait
234+
225235
226-
# Set with wait=False (non-blocking)
227-
RE(abs_set(device.temperature.target, 300, wait=False))
236+
.. note::
228237

229-
# Set multiple parameters
230-
RE(mv(
231-
device.temp1.target, 300,
232-
device.temp2.target, 350,
233-
device.pressure.target, 1000
234-
))
238+
For ``Drivable`` modules, prefer module-level ``set()`` to ensure operations complete before proceeding. For simple parameter updates on ``Readable`` or ``Writable`` modules, use signal-level ``set()``.
235239

236240
Using SECoP Commands
237241
--------------------
238242

239243
Command Basics
240244
~~~~~~~~~~~~~~
241245

242-
SECoP commands are methods that return Bluesky plan generators:
246+
SECoP commands are wrapped as bluesky plans. Use ``RE()`` to execute them. Commands should return immediately.
247+
For some long-running operations, the device will go into a BUSY state, and you may want to wait until the device returns to IDLE state.
243248

244249
.. code-block:: python
245250
246251
# Call a command without arguments
247252
RE(device.module.reset())
248253
249254
# Call a command with arguments
250-
RE(device.module.configure(mode='auto', setpoint=100))
255+
RE(device.module.configure(arg = {'mode': 'auto', 'setpoint': 100}))
251256
252-
All commands accept a ``wait_for_idle`` parameter:
257+
All commands plans accept a ``wait_for_idle`` parameter:
253258

254259
.. code-block:: python
255260
@@ -259,23 +264,10 @@ All commands accept a ``wait_for_idle`` parameter:
259264
# Wait for device to return to IDLE state
260265
RE(device.module.command(arg=value, wait_for_idle=True))
261266
262-
Command Arguments
263-
~~~~~~~~~~~~~~~~~
267+
.. note::
264268

265-
Commands can accept various data types:
269+
For commands that trigger long operations, set ``wait_for_idle=True`` to ensure the plan waits until the operation completes.
266270

267-
.. code-block:: python
268-
269-
# Simple types
270-
RE(device.motor.move(position=100.5))
271-
272-
# Structured data (dicts)
273-
RE(device.controller.configure(
274-
arg={'param1': 'value', 'param2': 123, 'flag': True}
275-
))
276-
277-
# Arrays
278-
RE(device.controller.set_profile(points=[0, 10, 20, 30]))
279271

280272
Return Values
281273
~~~~~~~~~~~~~
@@ -291,21 +283,6 @@ Capture command return values:
291283
292284
info = RE(get_status())
293285
294-
Status and Busy States
295-
~~~~~~~~~~~~~~~~~~~~~~~
296-
297-
SECoP devices communicate their state through status codes. When a command
298-
or parameter change triggers a long operation, the device enters a BUSY state:
299-
300-
.. code-block:: python
301-
302-
def move_and_wait():
303-
# Start movement (device goes BUSY)
304-
yield from device.motor.move(position=100, wait_for_idle=True)
305-
# This line executes only after device returns to IDLE
306-
print("Movement complete!")
307-
308-
RE(move_and_wait())
309286
310287
311288
Class File Generation
@@ -374,17 +351,6 @@ Always use ``init_devices()`` context manager:
374351
# Bad - no cleanup
375352
device = SECoPNodeDevice('localhost:10800')
376353
377-
Avoid Hardcoded URIs
378-
~~~~~~~~~~~~~~~~~~~~
379-
380-
Use configuration files or environment variables:
381-
382-
.. code-block:: python
383-
384-
import os
385-
386-
node_uri = os.environ.get('SECOP_NODE_URI', 'localhost:10800')
387-
device = SECoPNodeDevice(node_uri)
388354
389355
Wait Strategies
390356
~~~~~~~~~~~~~~~
@@ -423,3 +389,12 @@ Use SECoP devices alongside EPICS, Tango, or other protocols:
423389
# Use together in plans
424390
RE(scan([tango_detector], epics_motor, 0, 10, 11))
425391
RE(mv(secop_temp.target, 300))
392+
393+
394+
Further Resources
395+
-----------------
396+
397+
- `SECoP Specification <https://sampleenvironment.github.io/secop-site/>`_
398+
- `Ophyd-async Documentation <https://blueskyproject.io/ophyd-async/>`_
399+
- `Bluesky Documentation <https://blueskyproject.io/bluesky/>`_
400+
- `Frappy (SECoP Framework) <https://github.com/SampleEnvironment/frappy>`_

0 commit comments

Comments
 (0)