Programming

Change list

Contents

Application API

The standard API exported from the kernel can be included with '#include "interface.h"'

Tasks and timers

The totally minimal OS simply consists of a timer queue - clients can queue events to occur some time in the future - there are 32kHz/256 timer ticks per second (we provide a define HZ which is 32000/256 - 1 second) - this timer runs with the rest of the chip in it's very deep sleep mode (check out the power management section below).

A 'task' is a queueable element, it must be stored in __xdata, it contains a pointer to some code and some task queue bookkeeping (these should be initialised to 0) - create a task with something like:


        void task_callback_routine(task_t  *);
        __xdata task task_block = {task_callback_routine,0,0,0};

Queue a task with:


        queue_task(&task_block, HZ/2);  // call task_callback_routine(&task_block) 1/2 a second from now

The maximum delay you can queue is 65534 ticks - about 8.7 minutes (the limit of the hardware sleep timer) - any more and you need to queue a long delay and count yourself

You can cancel a task with:


        cancel_task(&task_block);

A common thing to do is to call queue_task() with a 0 timeout which means call it ASAP (actually it goes after all the other tasks about to be called). We have a short cut for that queue_task_0(&task_block) which also solves the problem of making calling it from interrupts safely (and SDCC's static storage of call arguments)

Task callback routines are called with the task block that was called out as a parameter - they've been removed from the list and it's quit safe (and normal) to call queue_task() on them from within a callback routine.

A task will not be interrupted by another task (even one queued with time 0 by an interrupt). There is no preemption here, one tiny stack that has to be shared by everyone, local variables are expensive in this architecture so use global variables - a state machine kicked by app calls and task callbacks is the preferred way to go.

You should spend minimal time in tasks, as little as possible, get in do something, get out ASAP, don't spin to delay time, queue another task instead.

Applications

The basic idea behind applications is that we need a mechanism for call backs from the kernel to user provided code. This works well as a clean interface for software updateable applications.

A CPU can have a small kernel and an application - the kernel is fixed at manufacturing time while the application portion can be reliably updated over the radio at any time - the kernel uses a small portion of the available flash memory (8k is the goal) - the rest is available for applications for standalone apps they're linked and the app gets the remaining 24k, software updateable apps need room for two copies of ~12k each - half for the currently running app, the other half for an upgrading version.

At boot time the kernel runs a CRC check on the app image with the highest version number, if it passes it becomes the running app, if the CRC fails the other app image is tested and if neither passes a dummy app is installed (updates can still occur).

Apps have a standard code header:


        typedef struct code_hdr {
                unsigned long    crc;
                unsigned int     len;           // CRC starts here, len is length to the end
                unsigned char    arch;          // should be different for every board
                unsigned char    code_base;     // should be different for every type of code that runs on a board
                unsigned char    version[2];    // version of that code bit 0 of byte 0 is the even/odd bit
                unsigned char    data[1];       // first byte of instruction executed in app
        } code_hdr;

A loadable app is valid if a CRC32 for len bytes from 'arch' matches the CRC in the header. The entry point (code) to the application starts at data[].

The build system automatically creates the code_hdr structure for you, populates its fields, including the CRC and hooks the entry point up to my_app() - it also generates a hook that initialises initialised data areas for you. Standalone apps have a code header with dummy CRCs and lengths but real application code, naked kernels have a dummy code header and null application code.

An app consists of a single event routine, it's passed an event type, if required there is global information qualifying some events. Like everything get in get out, as fast as you can - these calls are always made from task callout routines:


        unsigned int
        my_app(unsigned char op)        // app MUST be called "my_app"
        {
                case APP_INIT:
                        //
                        //      called once when an app originally starts, set up your hardware, any
                        //      interrupts start tasks to perform periodic operations
			/	think of this as the main() of your program
                        //
                        return 0;       // return ignored
                case APP_GET_MAC:
                        //
                        //      set the mac address here if you want to use something other than
                        //      the default, otherwise do nothing
                        //
                        rf_set_mac(&mac[0]);
                        return 0;
                case APP_GET_KEY:
                        extern u8 __data rtx_key;
                        //
                        //      this is a request for you to set the 16 byte aes key number
                        //      selected by rtx_key by calling rf_set_key();
                        //
                        rf_set_key(&aes_keys[rtx_key][0]);
                        return 0;
                case APP_GET_SUOTA_KEY:
                        //
                        //      this is a request for you to set the 16 byte aes key number
                        //      for suota
                        //
                        rf_set_key(&suota_key);
                        return 0;
                case APP_RCV_PACKET:
                        {
                                // these are defined in interface.h, locations may change
                                extern u8 __data rx_len;
                                extern packet __xdata  * __data rx_packet;
                                extern u8 __xdata  * __data rx_mac;
                                extern __bit rx_crypto;
                                extern __bit rx_broadcast;
                                extern u8 rtx_key;

                                //
                                // rx_len bytes are stored pointed to by rx_packet;
                                //
                                // if rx_crypto is set the packet was encrypted
                                //      (and passed, bad crypto'd packets are discarded
                                //      if it was encrypted rtx_key contains the key used
                                //      to decrypt it)
                                //
                                //  if rx_broadcast is true the packet was sent as a broadcast
                                //
                                //  rx_mac is the mac address of the packet sender
                                //
                                //  when called the receiver is still running but can only buffer
                                //      a packet or two (depends on length) so be quick
                                //
                        }
                        return 0;       // ignored
                case APP_WAKE:
                        // power wake (not yet implemented)
                        return 0;       // ignored
                case APP_KEY:           // only on playa boards - capacitive key presses/releases
			return 0
                case APP_SUOTA_START:
                        return 0;       // 2 LSB:       0 means reboot after SUOTA
                                        //              1 means no reboot, call init again
                                        //              2 means no reboot, data has been saved call with APP_SUOTA_DONE to recover
                                        // if bit 2 is set the first 1k of the old code will be erased before
                                        //              control is passed to the new code - put your keys there
                case APP_SUOTA_DONE:	// suota has completed and we're back
			return 0;

                default:
                        return 0;       // for future comparability return 0 for undefined selectors
			
               }
        }

Sending/receiving packets

We have a bunch of routines for sending packets and controlling the RF interface:


        void rf_receive_on();   // turns the receiver on
        void rf_receive_off();  // turns the receiver off (saves a lot of power)

        void rf_set_transmit_power(unsigned char power); // sets output power
                // valid values:        XMT_POWER_NEG_3DB       -3dB
                //                      XMT_POWER_0DB           0dB
                //                      XMT_POWER_4DB           +4dB
                //                      XMT_POWER_MAXDB         MAX

        void rf_set_channel(unsigned char channel);   // channel 11-26

        void rf_set_key(unsigned char __xdata *key);  // set the 16-byte send/receive crypto key from xdata
        void rf_set_key(unsigned char __code *key);   // set the 16-byte send/receive crypto key from flash
        void rf_set_mac(unsigned char __xdata *key);  // set the 8-byte mac

        #define NO_CRYPTO 0xff
        extern unsigned char __data rtx_key;
        void rf_send(packet __xdata* pkt, unsigned char len, unsigned char crypto_key, unsigned char __xdata*mac);

rf_send() sends a packet pkt of length len, and applies crypto if requested using the supplied crypto_key number - use NO_CRYPTO if you don't want the packet encrypted - key numbers must be less than or equal to 0x7f.

You cannot send if the RF receiver is off (ie you have called rf_receive_off()), the results are undefined.

Key changes are done at the app level whenever the RF layer needs to switch keys it will call your app with APP_GET_KEY - in response you must call rf_set_key() using the value in rtx_key to choose a key (it's quite valid to just return a single key to all calls if that's what you need).

No guarantee is provided that the datagram will be received. The packet must be stored in __xdata space.

The mac argument can be NULL, if so a broadcast packet will be sent, otherwise a 64-bit MAC address is required.

Note: unlike traditional ethernet 802.15.4 expects there to be an awful lot of devices, it uses 64-bit/8-byte addresses

The packet format we use is optional - you can use anything you like - however of you want to use software update over the air you'll have to play nice and use it since the SUOTA protocol uses it.


        typedef struct packet {
                u8      type;
                u8      arch;
                u8      code_base;
                u8      version[2];
                u8      data[1];
        } packet;

        //  type        packet type - if you want to choose your own choose values over 0x40
        //              if you use crypto with private keys for all packets you wont collide
        //              as you'll never see anyone else's packets
        //  arch        hardware object we support - we'll allocate a different value
	     //		for each unique board	
	     //  code_base	code type 
        //  version[2]  version of software we support - start at one and increment
			for every new 
        //  data[N]     data payload - you only have ~100 bytes 802.11 packets are tiny

However if you are using SUOTA and suota_enabled is true rf_send() will assume you are using our format and will fill in arch, code_base, and version for you from the code header of the application you have loaded otherwise all the bytes are yours to play with.

The App interface is used to receive packets and will also poll for a crypto key and mac address during system start up.

If you're using the playa macless protocol the first 3 bytes of the data packet are used for TTL and to stop multiple retransmission of broadcast packets.


        typedef struct broadcast_filter {
                u8      hops;
                u8      uniq[2];        // uniq filter
        } broadcast_filter;
        //  hops        used to stop packets from living forever in the net
        //  uniq[2]     unique packet filter (for routing) - if you set hops to 0 these
        //              bytes are available to you

A received packet is announced to an application through an APP_RCV_PACKET event


        case APP_RCV_PACKET:
                {
                        // these are defined in interface.h, locations may change
                        extern u8 __data rx_len;
                        extern packet __xdata  * __data rx_packet;
                        extern u8 __xdata  * __data rx_mac;
                        extern __bit rx_crypto;
                        extern __bit rx_broadcast;
                        extern u8 rtx_key;

                        //
                        // rx_len bytes are stored pointed to by rx_packet;
                        //
                        // if rx_crypto is set the packet was encrypted
                        //      (and passed, bad crypto'd packets are discarded
                        //      if it was encrypted rtx_key contains the key used
                        //      to decrypt it)
                        //
                        //  if rx_broadcast is true the packet was sent as a broadcast
                        //
                        //  rx_mac is the mac address of the packet sender
                        //
                        //  when called the receiver is still running but can only buffer
                        //      a packet or two (depends on length) so be quick
                        //
                 }
                 return 0;       // ignored

Global variables carry information about the incoming packet rx_packet points to the data payload, rx_len is its length. If rx_broadcast is set it was a broadcast packet. rx_mac points to the 8 byte mac address of the sending system. If rx_crypto was set then the packet was encrypted with the key given in rtx_key.

The standard kernel load has some unused space in the low interrupt vectors, we sneak a node specific 4 bytes at address 0x000e and another 4 at 0x0016 - this is a default value - it can be overridden by an application at any time - in particular when an APP_GET_MAC event is received. The code loader GUI program has a feature that can be used to generate unique MAC addresses by patching these bytes in kernels as they are written to new hardware.

Encryption

Our hardware implements AES-128, it uses 16-byte keys

Encryption is optional, but there's more to in than just keeping secrets - the crypto sum used on each packet provides a far better protection than the usually CRC32. Packets are discarded if received with bad crypto, you'll never see them - that means that two networks can happily share the same RF frequency if they use different crypto keys

Key Management

Packets carry a small integer identifying their key - values can be in the range 0x00-0x7f - the underlying crypto hardware carries a cached key, the global variable rtx_key carries the integer for the currently cached key - you can invalidate the cache by setting rtx_key to NO_CRYPTO.

If you try and send a packet, or a packet arrives with a key that doesn't match the currently cached key then your application will receive an APP_GET_KEY event - when you receive one of these call rf_set(&key); to set the current key to your key that corresponds to rtx_key. If your key is stored in flash use rf_set_c(&key); instead.

So crypto is easy - install identical keys at both ends, implement the APP_GET_KEY event to set them when requested and start passing your key number(s) in the crypto_key parameter of rf_send().

For testing it's easy to choose a fixed, simple key, but when the time comes to build production code you need a nice random key, I use the following command to make them - for extra randomness seed it with some log file that changes rapidly.


	openssl rand -hex -rand /var/log/syslog 16

Power Management

The kernel automatically powers down the CPU when it is idle, when there are no interrupts to service and no tasks to execute the CPU is put into the lowest power state possible. We support two power states - IDLE and PM2 - PM2 is a very low power state, in it the main CPU clock is turned off, in this mode the CPU is pulling ~1uA, it's the mode you want to put the CPU in to make your battery last a long time.

In PM2 only the timer (used for task scheduling) and external interrupts can be used to wake the CPU - other peripherals (serial/rf/etc) are shutdown. The low power sleep timer used by our task scheduler is limited to a maximum sleep time of about 8.7 minutes, if you want to wait all day (or all night) create a task that counts in units of 8 minutes or so.

There are two conditions that must be met before the task scheduler will start using PM2 state rather than the higher power idle state when it has nothing to do:

By default at application startup time the RF receiver is on (for SUOTA) and sys_active is cleared.

Software Update

Basics

Software Update Over The Air (SUOTA) - allows us to replace software on devices in an arbitrary network of boards. Each board contains information in the code header of the current application identifying the code running in it - if the global variable suota_enabled is set then the RF subsystem inspects all incoming successfully encrypted packets and if it sees a header in one that indicates that a neighbouring node has a newer version of the current application it triggers a software update exchange with the neighbour.

Update Conditions

More specificly the default behaviour when suota_enabled is set:

At system startup if a valid SUOTA application is already loaded suota_enabled is cleared - if you want SUOTA for you app you must explicitly set suota_enabled when you handle an APP_INIT or APP_SUOTA_DONE event.

SUOTA encryption

For both reliability and security reasons all SUOTA traffic is encrypted - actual SUOTA traffic uses a specific SUOTA key with a key index outside the normal 0x00-0x7f range - kernels are compiled with a specific SUOTA key - you can change this default by editing kernel/suota_key.c. Applications can override the default SUOTA key by setting the global variable suota_key_required - if this is set applications will receive an APP_GET_SUOTA_KEY event and should load their bespoke SUOTA key with rf_set_key() or rf_set_key_c()

Application images

We reserve space for two copies of applications, that limits sizes - but we don't have enough RAM available to hold an image of a new application while upgrading to a new application, instead we place the non-changing kernel in low flash memory and split the rest (on flash page boundaries) between the current app and the next (or currently) updating one

The 8051 is an old CPU design, in particular it has no long distance relative branch or subroutine call instructions - this means that code must be compiled to load at a specific base address - by convention we build applications with even numbered version to load at one address, and odd numbered addresses at another - under normal behaviour, switching from code of one version to the next one, new code is loaded into the correct place. But the world isn't perfect, sometimes a node may miss an update because it moved out of range - we support upgrading from even numbered versions to even numbered ones and odd numbered to odd ones - it's slightly less reliable than a simple update, involves 2 flash copies and requires more system resources (2 1k xdata buffers).

The last thing we want is to turn a board into a brick with a failed application load, the SUOTA code is pretty paranoid, it will give up if it thinks something bad has happened - the only truly unrecoverable error that can occur is when the internal 8051 flash has failed, or if we're doing the copy at the end of an even->even or odd->odd upgrade when power fails. When these failure happen you'll see a CPU reset and come back either with the previous application in place, or as a naked kernel that will load the next app it sees advertised.

At system startup time the kernel probes for applications - it looks for valid images, in the right place, with good CRCs, the correct header for the hardware - if it finds two it chooses the ones with the largest version number.

Application Continuity

Sometimes you want to create an upgradeable app that has persistent data - during the upgrade process when a good application image has been captured the system reaches a point where it must switch to the new application, it indicates that this is about to happen by sending an APP_SUOTA_START event to the application, the app returns a value encoding how it wants to handle the process:

Sometimes you want to pass data from one app to a new one - in this case we provide a mechanism to squirrel away some data high in xdata sram when you receive an APP_SUOTA_START event and then recover it when you receive an APP_SUOTA_DONE event


	unsigned char __xdata *suota_allocate_save_space(u8 v);
	unsigned char suota_get_save_size();
	unsigned char __xdata *suota_get_save_space();

Call suota_allocate_save_space() from an APP_SUOTA_START event to allocate a location to store data - keep it as small as possible - suota needs the first 2k of the current app space to perform the next upgrade step (over and above the kernel's usage) there's a chance that this space will overlap your current data so copy it carefully (it's allocated at the end of xdata memory).

Alternatively you can allocate a save space from an APP_INIT and always keep your data there

From an APP_SUOTA_DONE event call suota_get_save_space() to get a pointer to your save space and suota_get_save_size() to get it's size - make sure that any new app leaves enough space at the end of xdata memory (plus 1 byte) for this space otherwise the initialisation of the new app will trash the saved data).

Playing nicely with others

In order for SUOTA to work well boards must be able to receive periodic notifications of the state of neighbouring board's software versions. If your app already periodically broadcasts something then you don't need to do anything special, the SUOTA info piggybacking in the packet headers will do the work for you, if it doesn't you should send periodic NULL broadcast packets when your board is awake.

Interrupts

We've exposed all the CPU's interrupt vectors not already used by the kernel to you - you can install your own handlers from your app's APP_INIT handler by simply storing a pointer to them - they are found in the variables:


	extern void (* __pdata uart_rx_0_vect) ();
	extern void (* __pdata uart_rx_1_vect) ();
	extern void (* __pdata uart_tx_0_vect) ();
	extern void (* __pdata uart_tx_1_vect) ();
	extern void (* __pdata p0_vect) ();
	extern void (* __pdata p1_vect) ();
	extern void (* __pdata p2_vect) ();
	extern void (* __pdata t2_vect) ();
	extern void (* __pdata t3_vect) ();
	extern void (* __pdata t4_vect) ();
	extern void (* __pdata adc_vect) ();
	extern void (* __pdata aec_vect) ();
	extern void (* __pdata dma_vect) ();

In our kernel the usual way to handle an interrupt is to do the minimal amount of work to clear the interrupt then call queue_task_0(&handler_task) to queue some code to do the heavy lifting.

The serial gateway app has an example of how this is done for a serial driver, the rest is up to you

There are some non-obvious things that need to be saved when you take an interrupt:

Serial Debug

We provide some minimal polled serial code for simple debug:

SDCC provides things like printf()/etc which use the provider putchar() - they are large and unwieldy and may use up all the small amount of space that's available to you

If you want samples of interrupt driven serial code that you can steal they can be found in the serial gateway application code in serial/serial_app.c

GPIOs

CC2533 chips have 19 gpio pins - they are organised in 2 8-bit ports P0 and P1 and 1 3-bit port P2. You can access can access them directly from C by name or the component bits as P0_0, P0_1, ... P0_7, P1_0, ... etc

If you're used to Arduino style access to digital IO ports you can use the macros provided for digitalRead(), digitalWrite() and pinMode() for simple GPIO access - instead of a port number use the P(port, bit number) macro - for example "digitalWrite(P(1,5), HIGH)" makes the same code as "P1_5 = 1"

Drivers

We have a tiny kernel, it doesn't have a driver model as such, instead we have a small number of software modules for driving devices we've implemented that can optionally be compiled into the kernel

Daylight sensor

Define DRV_DAYLIGHT in the Makefile

This is a simple daylight sensor - a pullup resistor and a phototransistor sensed through a digital GPIO port can be read with the daylight() call.

WS2811 based LED drivers

Define DRV_LEDS in the Makefile

WS2811/2812 LED are RGB are becoming ubiquitous, you see them in cheap LED strips from China, in Adafruit's NeoPixels, and our playa board. It has a, umm, challenging, protocol to drive by bit-banging.

We're releasing the code we use for the playa board, it drives 2 strings of 1 pixel each, and a voltage up converter - it's a tightly tuned piece of assembly code, you'll have to tweak it to drive longer strips. We use an in-memory buffer for the two pixels that is a 6 byte array GGRRBB - leds_rgb(&array[0]) powers up the voltage up converter and sends data to the two pixels, rgb_off() powers it down (you should send black to it first).

Capacitive buttons

Define DRV_KEYS in the Makefile

The playa board has 4 keys - they are arranged in 2 pairs - implemented by two PCB plates with a 5.1M resistor between them - the GPIO's they're wired to are connected to a hardware timer that measures the discharge time of the PCB plates through the 5.1M resistor.

A downside of this circuit is that the way that the CC2533's GPIOs work, the keyboard scanner messes with the simple serial output we use for debug, you may lose characters while scanning.

keys_on() turns on the keyboard scanner, keys_off() turns it off - when we see a key change state your app receives an APP_KEY event - the global variable key contains the key number of the key that changed, and key_down tells you whether it's been pressed or released.

Memory organisation

The 8051 has a history behind it - nominally it supports 3 memory address spaces:

The CC2533 maps all the ram spaces into one unified 4k byte block, the 'fast data' space is located at the end of the 4k space, it can be accessed as __xdata. In practice this means that there's only 3.75k worth of memory available to you in __xdata

Add to that the kernel takes some of each of the spaces for itself - largely ~600 bytes of __xdata space for networking and crypto buffers.

here's a very rough estimate of how much space is available: SpaceTotal AvailableKernel UseAvailable for use __data104 bytes24 bytes80 bytes __bits128 bits8 bits120 bits __pdata256 bytes101 bytes151 bytes __xdata3.5k bytes6092975 bytes

These numbers will change depending on which options the compiler is built with.

Compiler Issues

We use SDCC as a compiler, it's open source and does an OK job of making code in most cases - we compile using the 'medium' memory model and optimise for size - even so the 8051 is a sad target, if you're running out of flash space breaking into assembly can reduce the size of code by 50%.

The 8051 has very poor support for memory addressing, this means that C structures are expensive, and access to variables local to a subroutine (local variables and parameters) are so expensive that the compiler converts them to static and stores them in __pdata - so avoid recursion - there are also issues with interrupts which may cause unexpected recursion - queue_task_0() is provided to help get around the most common case.

The first parameter to a subroutine is passed through registers rather than a static memory location - single parameter subroutines tend to be faster

SDCC also supports untyped pointers - 3 byte entities that could point to xdata, pdata, fast data or flash memory, not only are they larger but they're also much slower - some normal C library routines like memcpy() and memset() are expensive (in space and time) because they are coded for the generic case.

So always type your variables - tell the compiler where you want to put data and where pointers point to - the SDCC manual has some examples but here are some basics:

	int __xdata fred;	// an integer stored in __xdata
	__bit mike;		// a boolean stored in __bit (quite efficient!)
	struct joe __pdata j;	// a structure stored in __pdata
	char __code * __data cp;// a pointer to a string stored in flash,
				// the pointer is stored in fast sram

C strings created by the compiler always end up in __code/flash

Some kernel routines used typed pointers for example we offer rf_set_key() to set keys from __xdata, for rf_set_key_c() for data stored in __code/flash. Also putstr() displays a constant string from __code/flash, not from a variable buffer.

There's no room for a heap - don't use malloc() or free(), instead allocate your storage staticly

Build system

Our build system is the simple Makefile in the top level directory of the source tree - it contains examples of how to build a kernel, a standalone applications and a SUOTA application

SDCC uses different naming conventions for files:

we also add:

There are two types of build:

We recommend you use standalone apps until you are ready to freeze your kernel - then use SUOTA builds

The basic process for building a kernel or building a standalone app involves:

To build SUOTA loadable code (look at the instructions in the Makefile for a checklist):

The top level Makefile already does all this for you, no need to actually write this stuff yourself.

You have a couple of options, you can create a new subdirectory in the build tree and put your app in there, but we recommend that for a SUOTA app you put our release tree somewhere, and make a separate development tree elsewhere. Start by copying the Makefile from the top of the release tree, and the samples app sample_app/app.c into your new directory and edit them to suit - instructions on how do do this are contained in the Makefile itself. Type 'make' to build a kernel and program it into your board. Then, with a serial gateway attached type "make push" to build an app and push it to a board.

The Programmer GUI

The instructions for downloading and building the programmer GUI program 'ccload' are available in the "Getting Started" tutorial.

The GUI program has lots of options - here's a quick rundown:

The "Connect" button opens the serial device specified, halts the CPU, and extracts information about its model and memory.

There are two select/load/read/write sections - the upper one is for loading the .ihx (Intel hex format) files used for the kernel and standalone apps, this code is usually loaded at the start of memory at 0, the lower one is for the .suota files (in a proprietary binary format) used for SUOTA applications loaded in high memory.

Typically you use the "Select" button to choose a file, then "Load", "Write", and "Run" every time you compile a new version of your program.

Debugging:

Status:

Sections seldom used:

The GUI program remembers a much of the information you enter when it is changed - you can safely run multiple copies of the GUI but, since this default data is shared, you need to be careful as you may not get the default values you expect when you start.

Standalone applications

Standalone apps consist of an app compiled directly into the kernel, they can be loaded by the programmer - you use a standalone app as a quick way to do testing before you freeze your kernel and start using SUOTA, or you can always use a standalone app because you want to make something larger than the 12k that's available to a SUOTA app.

The Makefile has a couple of examples of how to build standalone apps, including the serial gateway which is such an app

Internet access

A network of devices is a bit useless without being able to get data in and out to the wider internet. While we don't support IP (or any routing, that's up to you) we do have a version of our software that can be loaded into a dev board (serial.ihx) and which allows users to talk to it over a serial connection. The interface library can be found in serial/packet_interface.cpp and serial/packet_interface.h - there are C++ and plain C bindings available. The interface also supports a scriptable interface.

You can build the packet_loader host application which uses this library and will run on a host system and provide a SUOTA server serving multiple different types of boards, or you can build your own server that can do SUOTA as well as providing your own gateway to anything you like.

The packet_loader program takes one argument - the serial port that should be opened - it defaults to /dev/USB0. If you give it the -i script flag it will open the script file and execute commands from it (for example it could set up crypto keys and SUOTA files) then sit there forever responding to incoming packets. If you use -I script it will send the commands and then quit immediately.

Alternatively the -x suota_file flag allows you to push a single copy of a new set of firmware to a network, you need to have attached the crypto key to the suota file using the fix_crc -k ... command during the build process - if you use the - flag then you can also use the -c channel and -m mac flags

If you don't give the -i, -I or -x flags it will open an interactive session and allow you to send and receive interactive commands.

In this API crypto keys are 16 bytes - mac addresses are 8 bytes (not the traditional 6). In the scripting interface You can optionally insert colons to delineate bytes in keys and mac addresses.

The access library (include "serial/packet_interface.h") allows your host application to create multiple independent gateways (using different serial ports) by instantiating rf_interface(char *port_name, rf_rcv callback) objects, the following methods are supported:

C bindings for all these are all available - just add rf_ to the names above and add an initial first parameter of type rf_handle - create one with rf_open(char *port_name, rf_rcv callback) and dispose of it with rf_close(handle)

If you specify a rf_rcv callback hook when you create a rf_interface object the callback will be called whenever an incoming packet is received - parameters are:

There's an internal script interpreter - you can call command() to execute a single command and initialise() or initialize() to execute commands from a file. The scripted commands are:

You cans use 'u' to set up a responder daemon to push SUOTA firmware to boards, and 'U' to generate null broadcast messages to trigger updates in remote nodes

In addition when you use the packet_loader host app interactively it supports the above commands as well as the following commands:

You can use a board as a channel sniffer by variously enabling the autodump, promiscuous, and maybe raw modes

We expect programers to take the code provided and include it in a host specific gateway application. Provide a callback routine to handle packet traffic from a network and send() or send_crypto() to send it. Use the initialise() method to load keys/mac addresses/channels from script files, and also potentially to set up SUOTA (see the examples in the suota_test directory) to update nodes.