Event-Driven Programming

Applications in SNAPpy often have several activities going on concurrently. How is this possible, with only one CPU on the SNAP engine? In SNAPpy, the illusion of concurrency is achieved through event-driven programming. This means that most built-in SNAPpy functions run quickly to completion and almost never “block” or “loop” waiting for something. External events will trigger SNAPpy functions.

Warning

Notice the word almost in that last paragraph. As a quick counter-example, if you call the pulsePin() function with a negative duration, then by using that parameter you have requested a blocking pulse - the call to pulsePin() will not return until the requested pulse has been generated. This means it is very important that your SNAPpy functions also run quickly to completion!

As an example of what not to do, consider the following code snippet:

while readPin(BUTTON_PIN) != BUTTON_PRESSED:
    pass

print "Button is now pressed"

Instead of monopolizing the CPU like this, your script should use SNAPpy’s monitorPin() and HOOK_GPIN functionality.

To understand why hard loops like the one shown above are so bad, take a look at this flowchart.

../../../_images/Flowchart-Basic-Reversed.png

Note

The flowchart is not exhaustive and only shows high-level processing! The remainder of the flowchart is chopped off at this point. It was only being used to make a point, not to show all of SNAP’s internal workflow.

If you focus your attention on the left side of the flowchart, you will recognize that SNAP itself uses a software architecture commonly referred to as one big loop. SNAPcore is written in C and is quickly able to monitor the radio, check for GPIO transitions, perform mesh routing, etc.

Now focus your attention on the highlighted blocks on the right side of the flowchart. These show some of the times when the SNAPpy virtual machine might be busy executing portions of your SNAPpy script (those associated with HOOK_xxx handlers, as well as user-defined RPC calls).

While the device is busy interpreting the SNAPpy script, the other functions (the ones not highlighted) are not getting a chance to run. SNAPcore cannot be checking timers or watching for input signals while it is busy running one of your SNAPpy functions.

To give a specific example, if one of your RPC handlers takes too long to run, then the HOOK_1MS handler will not be running at the correct time, because it had to wait. If your script hogs the CPU enough, you will not get the correct quantity of timer hooks – SNAP sets a flag indicating that a timer hook needs to be invoked, but it does not queue them up. So, if you have a function that takes several milliseconds to run to completion, upon completion SNAP will only see that the flag is set and will only advance its internal millisecond “tick” counter by one tick.

Note

Time-triggered event handlers must run quickly, finishing well before the next time period occurs. To ensure this, keep your timer handlers concise. There is no guarantee that a timing handler will run precisely on schedule. If a SNAPpy function is running when the time hook would otherwise occur, the running code will not be interrupted to run the timer hook code.

Be sure to “hook” the correct event. For example, HOOK_STDIN lets SNAP devices process incoming serial data. HOOK_STDOUT lets SNAP devices know when a previous “print” statement has been completed.

Also, be sure that the routine you are using for your event processing accepts the appropriate parameters, whether it actually uses them or not.

Warning

Prior to SNAP 2.6, it is not recommended that you use non-public functions for hooked events, as scripts with a large number of constants and/or functions (public and non-public) can cause the hooks to invoke the wrong script at run-time. SNAP 2.6 eliminated this issue, so you can safely hook a non-public function without concern about the incorrect function executing at run-time.