@@ -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
236240Using SECoP Commands
237241--------------------
238242
239243Command 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
280272Return 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