SNAPpy Scripting Tips

The following are some helpful tips (sometimes learned from painful lessons) for developing custom SNAPpy scripts:

Beware of Case Sensitivity

In SNAPpy (as with Python), identifiers are case sensitive - foo is not the same as Foo.

SNAPpy is a dynamically-typed language, so it is perfectly legal to create a new variable on-the-fly. In the following SNAPpy code snippet two variables are created, and foo still has the original value of 2:

foo = 2
Foo = "The Larch"

Case sensitivity applies to function names as well as variable names:

linkQuality = getlq()  # ERROR! Unless you have defined your own function
linkQuality = getLq()  # Probably what you want

Beware of Accidental Local Variables

In SNAPpy (as with Python), all functions can read global variables, but you need to use the “global” keyword in your functions if you want to write to them:

count = 4  # create global count and set it to 4

def bumpCount():
    count = count + 1  # global count will still equal 4

def bumpCountTry2():
    global count       # needed to avoid creating a local version of count
    count = count + 1  # will actually increment global count

Don’t Cut Yourself Off (Packet Serial)

Portal talks to its “bridge” (directly connected) device using a packet serial protocol. SNAPpy scripts can change both the UART and Packet Serial settings.

This means you can be talking to a device from Portal and then upload a script into that device that starts using that same serial port – or even just the same SNAP engine pins – for some other function (for example, for printing script text output or as an externally triggered sleep interrupt). Portal will no longer be able to communicate with that node serially.

Serial Output Takes Time

In the following example, there is likely not enough time for the text to make it all the way out of the device (particularly at slower baud rates) before the sleep() command shuts off the device:

def goodNightMessage():
    print "imagine a very long and important message here"
    sleep(...)  # sleep parameters vary per platform

One possible solution would be to invoke the sleep() function from the HOOK_100MS hook event. First, create a new global by adding it to the top of your script:

goodNightCountDown = 0

Then, change the goodNightMessage() function to:

def goodNightMessage():
    global goodNightCountDown

    print "imagine a very long and important message here"
    goodNightCountDown = 500 # actual number of milliseconds may vary

Finally, add this logic to the handler for HOOK_100MS

@setHook(HOOK_100MS)
def callEvery100ms(tick):
    global goodNightCountDown

    if goodNightCountDown != 0:
        if goodNightCountDown <= 100: # timebase is 100 ms
            goodNightCountDown = 0
            sleep(...)                # sleep parameters vary per platform
        else:
            goodNightCountDown -= 100

SNAP Engines Do Not Have a Lot of RAM

SNAPpy scripts should avoid generating a flood of text output all at once, because there is nowhere to buffer the output and the excess text will be truncated. Instead, generate the composite output in small pieces (for example, one line at a time), triggering the next step of the process using the HOOK_STDOUT event.

SNAPpy Numbers Are Integers

2/3 = 0 in SNAPpy. As in all fixed-point systems, you can work around this by scaling your internal calculations up by a factor of 10, 100, etc. You then scale your final result down before presenting it to the user.

SNAPpy integers are 16-bit numbers and have a numeric range of -32768 to +32767. Remember that 32767 + 1 = -32768, and be careful that any intermediate math computations do not exceed this range, as the resulting overflow value will be incorrect.

A side-effect of SNAPpy integers being signed is that negative numbers shifted right are still negative, because the sign bit is preserved. You might expect 0x8000 >> 1 = 0x4000, but it is 0xC000. You can use a bitwise and operator (&) to clear the sign bit after a shift:

myInt = myInt >> 1
myInt = myInt & 0x7FFF

Pay Attention to Script Output

Any SNAPpy script errors that occur can be printed to the previously configured STDOUT destination, such as serial port 1. If your script is not behaving as expected, be sure and check the output for any errors that may be reported. If the device having the errors is a remote one (you cannot see its script output), remember that you can invoke the “Intercept STDOUT” action from the Node Info tab for that device. The error messages will then appear in the Portal event log, depending on the preferences specified in Portal.

Don’t Define Functions Twice

In SNAPpy (as with Python), defining a function that already exists counts as a re-definition of that function. Any script code that used to invoke the old function will now be invoking the replacement function instead. Using meaningful function names will help alleviate this.

SNAPpy Has Limited Dynamic Memory

Functions that manipulate strings (concatenation, slicing, subscripting, chr()) all pull from a small pool of dynamic (reusable) string buffers.

You still do not have unlimited string space and can run out if you try to keep too many strings. See each platform’s section in the SNAP Reference Manual for a breakdown of how many string buffers are available and what size those buffers are.

Use the Supported Form of Import

In SNAPpy scripts you should use the form:

from moduleName import *
from synapse.moduleName import *
from moduleName import specificFunction

Portal Speaks Python Too

SNAPpy scripts are a very powerful tool, but the SNAPpy language is only a small modified subset of full-blown Python.

In some cases, you may be able to take advantage of Portal’s more powerful capabilities by having SNAPpy scripts (running on remote devices) invoke routines contained within Portal scripts. This applies not only to the scripting language differences but also to the additional hardware a desktop platform adds.

As an example, even though a device has no graphics display, it can still generate a plot of link quality over time by using a code snippet like the following:

rpc("\x00\x00\x01", "plotlq", localAddr(), getLq())

For this to do anything useful, Portal must also have loaded a script containing the following definition:

def plotlq(who, lq):
    logData(who, lq, 256)

The device will report the data, and Portal will plot the data on its Data Logger pane. It wouldn’t take much additional code to instead save the data to a text file or a database, or even to include other GUI libraries for your own custom visualizations.

Be Careful Using Multicast RPC

If all devices hear the question at the same time, they will all answer at the same time. If you have more than a few devices, you will need to coordinate their responses if you poll them via a multicast RPC call. Here are some possible solutions:

  1. NV18 - Collision Avoidance inserts some random delay (up to 20 ms) when responding to multicast requests, to assist in overcoming this.
  2. NV16 - Carrier Sense and NV17 - Collision Detect help ensure you do not have too many devices talking at the same time.
  3. SNAP 2.6 introduced a directed multicast message that has a built-in delay factor. By providing an empty string for the destination addresses, it will behave the same as a multicast message; however, you will still be able to take advantage of the new built-in delay feature.
  4. Application-level control of when your device responds to a request.

Debugging

SNAPpy application development offers an unprecedented level of interactivity in embedded programming. Using Portal, you can quickly upload code, test, adjust, and try again. Some tips and techniques for debugging:

  • Make use of the “print” statement to verify control flow and values. (Be sure to connect STDIO to a UART or Intercept STDOUT with Portal.)
  • When using Portal’s Intercept feature, you’ll get source line-number information and symbolic error-codes.
  • Invoke “unit-test” script functions by executing them directly from the Snappy Modules Tree in Portal’s Node Info panel.
  • Use the included SNAP Sniffer to observe the RPC calls between devices.

Recovering an Unresponsive Node

As with any programming language, there are going to be ways you can put your devices into a state where they do not respond. Setting a device to spend all of its time asleep, having an endless loop in a script, enabling encryption with a mistyped key, or turning off the radio and disconnecting the UARTs are all very effective ways to make your SNAP devices unresponsive.

How to best recover an unresponsive device depends on the cause of the problem. If the problem is the result of runaway code (sleeping, looping, disabling UARTS, or turning off the radio), then “Erase SNAPpy Image” from the Node Info pane will not work, because it requires communication with the device. In these cases, you can usually use Portal’s “Erase SNAPpy Image…” feature from the Options menu to regain access to your device. This Portal feature interrupts the device before the script has a chance to start running – before it can put the device to sleep, fall into its stuck loop, or otherwise make the device unresponsive.

In the case of a lost encryption key or an unknown channel/network ID, or one of many other combinations that may arise, Portal’s “Factory Default NV Params…” feature, also from the Options menu, resets the device back to the state it was in when delivered. (If you have intentionally changed any parameters, such as device name, channel, network ID, various timeouts, etc., you will need to reset them once you have access to the device.) Reconfiguring your device after resetting the default NV parameters can be more involved than simply correcting a script and reloading it, so if you are not sure why your device is unresponsive it may be best to try clearing its SNAPpy image first.

If these fail to recover access to your device, the “big hammer” approach is to reload the device’s firmware, which you can also do from Portal’s Options menu. Note that if the NV parameters are mis-set, reloading the firmware will not recover access to the device, as it does not explicitly reset the parameters in the process. All three of these options require that you have a direct serial connection to the device.

For more on the use of these functions, refer to the Portal Reference Manual.