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)

SNAPstack 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 via SNAPstack 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). SNAPstack 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.

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

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. A directed multicast message 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.

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.