Skip to content

Commit b95d349

Browse files
committed
tutorials: Modernize the basic Python tutorial for Syntalos 2.0
1 parent b002a29 commit b95d349

File tree

1 file changed

+48
-34
lines changed

1 file changed

+48
-34
lines changed

content/tutorials/03_script-control.md

Lines changed: 48 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@ We will change that in the next step.
2424
In order to control other modules from our Python script module, we need it to have an output
2525
port to even emit data. Double-click the Python module or click on *Settings* after selecting it).
2626

27-
A new window opens which lets you edit the Python code. We ignore that for now and click on
28-
*Ports → Edit* in the window's main menu. A new *Port Editor* dialog opens, which allows for adding
29-
new ports to our script module.
27+
A new window opens which lets you edit the Python code. We ignore that for now and click on the
28+
*Edit Ports* button in the window's toolbar at the top. A new *Port Editor* dialog opens, which allows
29+
for adding new ports to our script module.
3030
Since we want to add an output port, click on *Add Output Port*.
3131

3232
In the next step, you will be asked which kind of data the output port emits. Select `ControlCommand`
@@ -42,22 +42,25 @@ input port of your *Audio Source* as usual!
4242

4343
## 3. Module coding basics
4444

45-
Now we can look at the Python script itself! The default script is quite large due to its many annotations, but
46-
using it is really quite simple: 4 functions exists that Syntalos may call at different stages of an experiment run.
45+
Now we can look at the Python script itself! The default script is larger than it needs to be due to annotations
46+
to help you get started. Writing the script is a lot simpler than it may first appear though:
47+
4 functions exists that Syntalos may call at different stages of an experiment run.
4748
The `prepare()` function is called when the experimence is started, but before all modules are ready, the
48-
`start()` function is called immediately before data starts being acquired, `loop` is called continuously during
49-
the experiment until the function returns `False`, and `stop()` is called when the experiment is stopped.
50-
We are primarily concerned with the `loop()` function here, as we do not need to prepare any data or device
51-
in our script. Interaction with Syntalos happens via the `syio` module, which is imported by default.
49+
`start()` function is called immediately before data starts being acquired, `run` is called when the experiment
50+
is started and mainly handles communication with Syntalos, and `stop()` is called when the experiment is stopped.
51+
We are primarily concerned with the `prepare()` function here, as we do not need to prepare any data or device
52+
in our script. Interaction with Syntalos happens via the `syntalos_mlink` module, which is imported by default
53+
as `syl`.
5254

5355
First, we need to get a Python reference to the `control-out` output port that we just defined in the port editor.
54-
This needs to happen before even `prepare()` is called, so we put it at the top of the Python file:
56+
This should happen before even `prepare()` is called, so we put it at the top of the Python file:
5557

5658
```python
57-
oport = sy.get_output_port('control-out')
59+
oport = syl.get_output_port('control-out')
5860
```
5961

60-
Using `get_output_port()` with the internal port name, we now can emit messages on this port.
62+
The `get_output_port()` method takes the internal port name as parameter, that we previously defined in the port editor.
63+
With the reference we obtained on the output port, we can now can emit messages on this port.
6164

6265
## 4. Controlling a module
6366

@@ -67,45 +70,56 @@ This is useful for example if you only want to record a video for a selected amo
6770
For now though, we just want to emit a 2 sec audio cue every 5 seconds. This can be done with this simple snippet
6871
of Python code (this is the whole script file):
6972

70-
```python {linenos=table,hl_lines=[10,14]}
71-
import syio as sy
72-
from syio import InputWaitResult, ControlCommand, ControlCommandKind
73+
```python {linenos=table,hl_lines=[9,14,16,21]}
74+
import syntalos_mlink as syl
7375

7476

75-
oport = sy.get_output_port('control-out')
77+
oport = syl.get_output_port('control-out')
7678

7779

78-
def loop() -> bool:
79-
ctl = ControlCommand()
80-
ctl.kind = ControlCommandKind.START
80+
def start():
81+
"""Called by Syntalos immediately when the experiment is started."""
82+
send_beep()
83+
84+
85+
def send_beep():
86+
ctl = syl.ControlCommand()
87+
ctl.kind = syl.ControlCommandKind.START
8188
ctl.duration = 2000 # run for 2 sec
82-
while True:
83-
oport.submit(ctl)
84-
sy.wait_sec(5)
85-
if not sy.check_running():
86-
return False
89+
oport.submit(ctl)
90+
91+
if not syl.is_running():
92+
return False
8793

88-
return True
94+
# run again in 5 sec
95+
syl.schedule_delayed_call(5 * 1000, send_beep)
8996
```
9097

91-
The `loop()` function is called permanently while the experiment runs. We first define a `ControlCommand` that we want to
98+
The `start()` function is called immediately when the experiment is started and has to complete immediately.
99+
To launch our custom code, we define a function called `send_beep()` that we run once the experiment was started.
100+
101+
In `send_beep()` we first define a `ControlCommand` that we want to
92102
send to the *Audio Source*, and tell it to be of kind `START` and instruct it to hold that state for `2000` milliseconds
93-
before falling back to its previous state.
94-
Then, we just loop endlessly and submit the control command on our predefined output port `oport`, wait 5 seconds and then
95-
repeat the process.
96-
Any datatypes you can use with output ports, and commands you can use on input ports can be found in the
103+
before falling back to its previous state (via `ctl.duration`).
104+
105+
Using `oport.submit(ctl)`, we send this command out on the previously defined output port `oport`.
106+
If we are no longer running (if `syl.is_running()` returns `False`), we stop doing anything. If we are still running though,
107+
we instruct Syntalos to call `send_beep()` again in 5 seconds, via `syl.schedule_delayed_call()`.
108+
109+
Datatypes you can use with output ports, and commands you can use on input ports can be found in the
97110
[syntalos_mlink API documentation]({{< ref "/docs/pysy-mlink-api" >}}) for reference.
98111

99112
{{< callout type="info" >}}
100113
While using Python's own wait functions, like `time.sleep()`, is possible for delays, it is recommended to use
101-
functions from `syio` for that purpose. That way Syntalos knows about the waiting state of the module,
114+
functions from `syntalos_mlink` for that purpose. That way Syntalos knows about the waiting state of the module,
102115
and can disrupt a sleeping module to stop it instead of waiting for it. It also allows Syntalos to make smarter
103116
scheduling and queueing decisions.
104117
{{< /callout >}}
105118

106-
By calling `sy.check_running()` in our endless loop, we can check if the Syntalos experiment is still running, and
107-
terminate voluntarily in case it is not. Otherwise, Syntalos will interrupt script execution if a script does not react
108-
in time to a stop request.
119+
While checking whether we are still running with `syl.is_running()` is not strictly necessary, it allows for a cleaner
120+
shutdown procedure when Syntalos interrupts the running code at the end of the experiment.
121+
An alternative to this check would be to implement the `stop()` function, and stop emitting new control commands once that
122+
function has been called.
109123

110124
## 5. Run it!
111125

0 commit comments

Comments
 (0)