@@ -37,33 +37,28 @@ You can also add an output port of type `TableRow` named `table-out` for later u
37
37
The we create some boilerplate code for the Python module, which does nothing, for now:
38
38
39
39
``` python
40
- import syio as sy
41
- from syio import InputWaitResult
40
+ import syntalos_mlink as syl
41
+
42
42
43
43
fm_iport = sy.get_input_port(' firmata-in' )
44
44
fm_oport = sy.get_input_port(' firmatactl-out' )
45
45
tab_oport = sy.get_input_port(' table-out' )
46
46
47
47
48
- def prepare ():
49
- pass
48
+ def prepare () -> bool :
49
+ """ This function is called before a run is started.
50
+ You can use it for (slow) initializations."""
51
+ return True
50
52
51
53
52
54
def start ():
55
+ """ This function is called immediately when a run is started.
56
+ This function should complete extremely quickly."""
53
57
pass
54
58
55
59
56
- def loop () -> bool :
57
- # wait for new input to arrive
58
- wait_result = sy.await_new_input()
59
- if wait_result == InputWaitResult.CANCELLED :
60
- return False
61
-
62
- # return True, so the loop function is called again when new data is available
63
- return True
64
-
65
-
66
60
def stop ():
61
+ """ This function is called once a run is stopped."""
67
62
pass
68
63
```
69
64
@@ -113,47 +108,50 @@ when we sent the command to get the LED to blink.
113
108
114
109
This is the code we need to achieve that:
115
110
116
- ``` python {linenos=table,hl_lines=[11,17,23,31]}
117
- import syio as sy
118
- from syio import InputWaitResult, ControlCommand, ControlCommandKind
111
+ ``` python {linenos=table,hl_lines=[10,16,24,33]}
112
+ import syntalos_mlink as syl
119
113
120
114
121
115
# constants
122
116
LED_DURATION_MSEC = 250
123
117
LED_INTERVAL_MSEC = 2000
124
118
125
119
126
- fm_iport = sy .get_input_port(' firmata-in' )
127
- fm_oport = sy .get_input_port(' firmatactl-out' )
128
- tab_oport = sy .get_input_port(' table-out' )
120
+ fm_iport = syl .get_input_port(' firmata-in' )
121
+ fm_oport = syl .get_input_port(' firmatactl-out' )
122
+ tab_oport = syl .get_input_port(' table-out' )
129
123
130
124
131
- def prepare ():
125
+ def prepare () -> bool :
132
126
# set table header and save filename
133
127
tab_oport.set_metadata_value(' table_header' , [' Time' , ' Event' ])
134
128
tab_oport.set_metadata_value(' data_name_proposal' , ' events/led_status' )
135
129
130
+ return True
131
+
136
132
137
133
def start ():
138
134
# set pin 8 as LED output pin
139
135
fm_oport.firmata_register_digital_pin(8 , ' led1' , True )
140
136
137
+ # start sending our pulse command periodically
138
+ trigger_led_pulse()
141
139
142
- def loop () -> bool :
143
- # loop forever, as we do not need to read any input data
144
- while True :
145
- tab_oport.submit([sy.time_since_start_msec(),
146
- ' led-pulse' ])
147
- fm_oport.firmata_submit_digital_pulse(' led1' , LED_DURATION_MSEC )
148
140
149
- sy.wait(LED_INTERVAL_MSEC )
150
- if not sy.check_running():
151
- break
141
+ def trigger_led_pulse ():
142
+ tab_oport.submit([syl.time_since_start_msec(),
143
+ ' led-pulse' ])
144
+ fm_oport.firmata_submit_digital_pulse(' led1' , LED_DURATION_MSEC )
152
145
153
- # ensure LED is off
154
- fm_oport.firmata_submit_digital_value( ' led1 ' , False )
146
+ if not syl.is_running():
147
+ return False
155
148
156
- return False
149
+ # run this function again after some delay
150
+ syl.schedule_delayed_call(LED_INTERVAL_MSEC , send_beep)
151
+
152
+ def stop ():
153
+ # ensure LED is off once we stop
154
+ fm_oport.firmata_submit_digital_value(' led1' , False )
157
155
```
158
156
159
157
Initially, in line 10, we need to fetch references to our input/output ports (using only the latter for now), so we
@@ -168,28 +166,30 @@ we first register pin `8` on the Arduino as digital output pin (adjust this if y
168
166
This example uses convenience methods to handle digital pins. For example, the call to
169
167
` firmata_register_digital_pin() ` on the Firmata control port could also be written as:
170
168
``` python
171
- ctl = sy .new_firmatactl_with_id_name(sy .FirmataCommandKind.NEW_DIG_PIN , 8 , ' led1' )
169
+ ctl = syl .new_firmatactl_with_id_name(syl .FirmataCommandKind.NEW_DIG_PIN , 8 , ' led1' )
172
170
ctl.is_output = True
173
171
fm_oport.submit(ctl)
174
172
```
175
173
Not every action has convenience methods, but the most common operations do.
176
174
{{< /callout >}}
177
175
178
- Then, in the ` loop() ` function the actual logic happens to make the LED blink. Normally, this function is called
179
- by Syntalos constantly when new data arrives. But since we do not need to wait for incoming data, we first just enter
180
- an endless ` while ` loop.
181
- In it, we send a new table row to the * Table* module for storage & display, using the ` sy.time_since_start_msec() ` function
176
+ Then, we launch our custom function ` trigger_led_pulse() ` where the actual logic happens to make the LED blink.
177
+ In it, we send a new table row to the * Table* module for storage & display, using the ` syl.time_since_start_msec() ` function
182
178
to get the current time since the experiment run was started and naming the event ` led-pulse ` . You should see these two values
183
179
show up in the table later. Then, we actually send a message to the * Firmata IO* module to instruct it to set the LED pin ` HIGH `
184
- for the time ` LED_DURATION_MSEC ` . Then we wait using ` sy.wait(LED_INTERVAL_MSEC) ` until we repeat the process again, and exit
185
- the loop when the experiment is stopped.
180
+ for the time ` LED_DURATION_MSEC ` .
181
+
182
+ To introduce some delay before sending another such command, we instruct the ` trigger_led_pulse() ` function to be called again
183
+ after ` LED_INTERVAL_MSEC ` via ` syl.schedule_delayed_call(LED_INTERVAL_MSEC, send_beep) ` .
184
+ This is repeated until the experiment has been stopped by the user.
186
185
187
186
{{< callout type="warning" >}}
188
187
Keep in mid that when submitting data on a port, you are ** not** calling the respective task immediately - you are
189
188
merely enqueueing an instructions for the other module to act upon at a later time.
190
189
Realistically, Syntalos will execute the queued action instantly with little delay, but Syntalos can not make any
191
- real-time guarantees. If you need those, consider using dedicated hardware or an FPGA, and control those components
192
- with Syntalos instead.
190
+ real-time guarantees for inter-module communication.
191
+ If you need those, consider using dedicated hardware or an FPGA, and control those components with Syntalos instead.
192
+ This will give you predictable and reliable latencies.
193
193
{{< /callout >}}
194
194
195
195
If you hit the * Run* button, the experiment should run and the LED should blink for 250 msec every 2 sec.
@@ -201,25 +201,29 @@ We assume you have a switch placed on one Ardino pin, and an LED on another for
201
201
202
202
The code we need for this looks very similar to our previous one:
203
203
204
- ``` python {linenos=table,hl_lines=[22,30,36,47]}
205
- import syio as sy
206
- from syio import InputWaitResult, ControlCommand, ControlCommandKind
204
+ ``` python {linenos=table,hl_lines=[19,26,40]}
205
+ import syntalos_mlink as syl
207
206
208
207
209
208
# constants
210
209
LED_DURATION_MSEC = 500
211
210
212
211
213
- fm_iport = sy .get_input_port(' firmata-in' )
214
- fm_oport = sy .get_input_port(' firmatactl-out' )
215
- tab_oport = sy .get_input_port(' table-out' )
212
+ fm_iport = syl .get_input_port(' firmata-in' )
213
+ fm_oport = syl .get_input_port(' firmatactl-out' )
214
+ tab_oport = syl .get_input_port(' table-out' )
216
215
217
216
218
- def prepare ():
217
+ def prepare () -> bool :
219
218
# set table header and save filename
220
219
tab_oport.set_metadata_value(' table_header' , [' Time' , ' Event' ])
221
220
tab_oport.set_metadata_value(' data_name_proposal' , ' events/led_status' )
222
221
222
+ # call a function once new data was received on this input port
223
+ fm_iport.on_data = on_new_firmata_data
224
+
225
+ return True
226
+
223
227
224
228
def start ():
225
229
# set pin 7 as input pin
@@ -229,50 +233,42 @@ def start():
229
233
fm_oport.firmata_register_digital_pin(8 , ' led1' , True )
230
234
231
235
232
- def loop () -> bool :
233
- # wait for new input to arrive
234
- if sy.await_new_input() == InputWaitResult.CANCELLED :
235
- # the run has been cancelled (by the user or an error)
236
- return False
236
+ def on_new_firmata_data (data ):
237
+ if data is None :
238
+ return
237
239
238
- while True :
239
- data = fm_iport.next()
240
- if data is None :
241
- # no more data, exit
242
- break
243
-
244
- # we are only interested in digital input
245
- if not data.is_digital:
246
- continue
247
- # we only want to look at the 'switch' pin
248
- if data.pin_name != ' switch' :
249
- continue
250
-
251
- if data.value:
252
- tab_oport.submit([sy.time_since_start_msec(),
253
- ' switch-on' ])
254
- fm_oport.firmata_submit_digital_pulse(' led1' , LED_DURATION_MSEC )
255
- else :
256
- tab_oport.submit([sy.time_since_start_msec(),
257
- ' switch-off' ])
258
-
259
- # return True, so this function is called again
260
- return True
240
+ # we are only interested in digital input
241
+ if not data.is_digital:
242
+ return
243
+ # we only want to look at the 'switch' pin
244
+ if data.pin_name != ' switch' :
245
+ return
246
+
247
+ if data.value:
248
+ tab_oport.submit([syl.time_since_start_msec(),
249
+ ' switch-on' ])
250
+ fm_oport.firmata_submit_digital_pulse(' led1' , LED_DURATION_MSEC )
251
+ else :
252
+ tab_oport.submit([syl.time_since_start_msec(),
253
+ ' switch-off' ])
254
+
255
+
256
+ def stop ():
257
+ # ensure LED is off once we stop
258
+ fm_oport.firmata_submit_digital_value(' led1' , False )
261
259
```
262
260
263
- In ` start() ` we additionally register pin ` 7 ` as an input pin this time, while all the other changes are in the ` loop() `
264
- function. There, we initially just wait for new input to arrive. The ` sy.await_new_input() ` call will return if there was
265
- new data to process on * any* of the Python script modules' input ports. In this case we have only one input port, but of we
266
- had more than one we would now need to check all input ports for new data. Since there might also be more than one data block,
267
- we enter a ` while ` loop and pull new data from the input port using ` fm_iport.next() ` until no more data is available.
261
+ In ` start() ` we now additionally register pin ` 7 ` as an input pin this time.
262
+ We also need to read input from our Firmata device now, for which we registered ` fm_iport ` as input port.
263
+ Every input port in Syntalos' Python interface has a ` on_data ` property which you can assign a custom function to,
264
+ to be called when new data is received. We assign our own ` on_new_firmata_data() ` function in this example.
268
265
269
- Next, we check if we have data from the right, registered block by checking if the pin is digital and if it is our ` switch ` labelled
270
- pin. We ignore any other data (there should not be any, but just in case...).
266
+ In ` on_new_firmata_data() ` , we first check if the received ` data ` is valid (it may be ` None ` to signal that a run
267
+ is being stopped right now). Next, we check if we have data from the right pin by checking if it is is digital and
268
+ if it is the pin that we previously labelled "` switch ` ". We ignore any other data (there should not be any, but just in case...).
271
269
Then, if we receive a ` True ` value, we command the LED to blink for half a second and log that fact in our table, otherwise
272
270
we just log the fact that the switch is off.
273
271
274
- Finally, we let the ` loop() ` function return ` True ` , so it is called again soon.
275
-
276
272
Upon running this project, you should see the LED flash briefly once you push the button, and see the state of the button logged
277
273
in the table displayed by the * Table* module.
278
274
0 commit comments