05 nov 2015

the SRRadio library contains the SRRF24 driver for the Nordic nRF24L01+ chip and modules and the SRFlock object for the Flock and Peep protocols.

the library source is available from my github repository.


Flock (and Peep) are user-level protocols for addressing and communicating between radio-connected controllers. it uses the SRRF24 library and many of the objects in the SRResource libary.

use the following to include the libraries and instantiate the object. the object isn't initialized until begin() is called.

#include #include #include SRFlock Flock;

Flock setup

int begin (); int begin (uint8_t cepin, uint8_t cspin); void LED (uint8_t pin);

begin() is common to all protocols and initializes state and the radio hardware. begin() returns true if the radio chip does not respond properly -- either not installed or a bad clone (when detectable). though see SRRF24.h for details, pin numbers can be specified her if not using the defaults. LED() optionally specifies a digital pin driving a status LED.

int packet(); char message();

these are the protocol method state machines. only one can be used at a time (invoked from loop()). packet() returns the number of received characters in the receive buffer (pointed to by getRxBuff()). this must be consumed/copied out before the next packet arrives or it will be overwritten. message() runs the message protocol. the return value is the ID of the most recently heard from client or flockBase. payload is delivered through the messaging system as a "call back" via the configured dispatcher(s).

Peep protocol is Flock with transmit-driven power management; the radio is turned off a configurable amount of time after the last write() call.

void setIdentity (char id); char getIdentity (); void setBaseID (char id); void addDispatcher (void (* func) (int, unsigned));

every node in the Flock/Peep network has an identity, an address consisting of a single ASCII character, A..Z (though a..z is acceptable) and @ for the base. identity @ designates that node as flockBase and changes the protocol machine behavior accordingly; the base-identity character can be changed with setBaseID() (rarely used).

addDispatcher() installs the dispatcher that accepts command::message pairs. the dispatcher is code you provide, and a usable example is given in the SRMessage documentation.

network tuning

void setRxAR (unsigned decisec); // acknowledge REQUEST rate, deciseconds void setRxAT (unsigned decisec); // receive TIMEOUT, deciseconds void setAckThresh (unsigned t); // outstanding-ack THRESHOLD, count

these three methods control mainly Flock operation. they determine how strong the sense of "connection" amongst the flock by tracking the time since the last successful packet receipt and how often to request channel acknowledgement. these have little effect on Peep which is intentionally sporadic and (unwilling, unable) to recieve packets.

the sense of "connection" is based solely on recent successful receipt of packets.

RxAR and RxAT

RxAT determines the time after which no valid packet receipt (addressed to "us") causes channel renegotiation. RxAT should be long relative to RxAR.

RxAR is the rate at which acknowledgement-request packets are transmitted, when no valid packet has been received. this should be short relative to RxAT. a good starting point is to allow for one or two ack-request packets at this rate to be lost; eg. RxAR requests an ack response every two seconds, with RxAT set to 4, 5, 6 seconds.

for the base, the meaning of RxAR is different, though the action is the same. when the paradigm for the flock is that the base is essentially a passive log of bird data, with only sporadic need to write to birds, then RxAR can be set larger than RxAT to effectively disable it; this leaves it up to the birds to maintain channel connection to meet their needs. (Peep takes this concept even further.) if however the flock paradigm is that the birds must be ready to respond quickly to commands from the base then a shorter RxAR is called for.

note that RxAR and RxAT are in deciseconds (tenths).

since Peep is bird-driven, eg. only when there is data to send to the base, not only is the bird's receiver turned off, after channel negotiation packet transmission and reception keeps the channel open for the time specified by PeepUpTime.


in addition to the no-packet-received timeout determined by RxAR and RxAT, ackThreshold sets a limit on channel (low) quality, eg. packet loss rate through a technique called ack balance. ack balance increases by one for every packet transmitted that contains an acknowledgement request; ack balance decreases by one with every valid packet received. if ack balance exceeds ackThreshold the channel is abandoned and renegotiated.

other methods

bool connected(); int status(); int write (); char * getRxBuff(); char * getTxBuff(); unsigned txMessages(); unsigned rxMessages(); unsigned txQueueCount ();

for birds, connected() indicates "recent" successful packet exchange with base. for base it means, as of this writing, that it has negotiated with at least one bird. (this is currently being expanded for base to have per-bird knowledge of connect status rather than in aggregate).

connected() indicates overall connection state, comprised of successful channel noegotiation, packet receipt success within the RxAR and RxAT parameters, and channel quality determined by ackThresh. all are covered below. connected() returns true when all criteria has been met. see HOW IT WORKS for description.

status is a secondary state machine solely for displaying runtime status to humans. it returns:

0was, is disconnected (no change)
1was connected, now disconnected (change)
2was, is connected (no change)
3was, is connected (no change)
4radio was just powered on (change)
5radio was just powered off (change)

write() transmits the packet or message(s) in the transmit buffer, or enqueues them if not immediately deliverable. please refer to section XXXXXXXXXXXXXXXXXX for packet format requirements. up to four packets can be queued for delivery. packets are written to the radio directly if connected() returns true. packets are dequeued (sent) whwn connected() becomes true, checked for by the state machine. txQueueCount() returns the number of packets remaining in the queue, 0 when all are sent.

getRxBuff() and getTxBuff() return a pointer to the current recieve or transmit buffers, respectively. received radio data (when the state machine method returns status indicating available) is delivered to userland in rxBuff. being a pointer constructs such as Serial.println (Flock.getRxBuff()) or Flock.getRxBuff()[i] work fine.

getTxBuff() returns a pointer to the current -- next available -- transmit buffer. this is a pointer into the queue and is valid until write() is called, so do not save the return value between write invokations.

txMessages() and rxMessages() return the number of message packets sent and received, respectively. these increment with every successful radio write or read. they are both reset to 0 by the powerUp() method.

Flock and Peep messaging

Flock and Peep send and receive SR Messages, with the addition of a radio address header information and an optional enhancement character. Flock instantiates SRMessage internally and provides it's own methods to access it. most are pass-throughs to the internally instantiated object's methods but there are a few simple differences described below. please refer to SRMessage documentation for information not present here.

int messageBegin (); int messageTo (char dest, bool ack); int messageAdd (char c, unsigned nnn); int messageChar (char c); int messageEnd();

Flock provides it's own buffer to build messages in (the available transmit buffer, available via getTxBuff(), see above) therefore messageBegin() takes no arguments. channel addressing is not part of the SRMessage system and so Flock/Peep have the messageTo() method to add destination (specified) and source (implicit; identity) address, and the channel-specific optional acknowledgement request. commands and arguments are added to the message, as necessary, via messageAdd(). messageEnd() is unique to Flock, and in essence invokes Flock.write() to deliver or enqueue the message just biult.

void setPeepConnectTime (unsigned decisec); void setPeepUpTime (unsigned decisec);

above are the Peep protocol tuning methods. PeepConnectTime (decisec) is the maximum allowed time for the protocol to establish a connection to the base, and PeepUpTime (decisec) is how long the protocol remains up and connected before powering down. the latter is the time window in which flockBase can communicate with the Peep client. the window will remain open as long as Peep accepts or sends packets.

uint8_t getAckBalance (); uint8_t * getChanErrorMap (); uint8_t getAckThresh(); int getTxQueueDepth ();

above are operational status routines of minor utility. getChanErrorMap() returns a pointer to the 16-byte bad-channel bit map. the (possible) 128 channels, numbered 0..127, map to the bit address in the little-endian array; eg. getChanErrorMap()[0] & 1 is channel 1, and getChanErrorMap()[1] & 128 is channel 16.

void setPromiscuous(bool yup); void dynamicChannelEnable(); void dynamicChannelDisable(); void nextChannel(); void setFlockLoopRate (unsigned n);

getting down to the dregs here. setPromiscuous() meddles with the protocol engine such that every packet appears to be ours. useful for debugging i think (or was). i don't think i've ever disabled dynamic channels, it probably seemed a good idea at the time. nextChannel() is just possibly of use for the flockBase adapter, as a command from a desktop application, to force channel renegotiation based upon the superior statistics-gathering ability of a larger machine. setFlockLoopRate() determines the state machine's events-checking time. it defaults to something small, like 3 (milliseconds) and there is little utility in changing it.

void setFixedAddress (uint64_t t, uint64_t r); void setSelfAddress (uint64_t base, uint8_t t, uint8_t r); void setSelfAddress (uint8_t t, uint8_t r); void setChannel (uint8_t channel); void setMinChannel (uint8_t channel); void setMaxChannel (uint8_t channel); void setDataRate (uint8_t n); void setPALevel (uint8_t n); void powerDown (); void powerUp (); bool powerState (); uint8_t getChannel(); uint8_t getRxPower(); uint8_t readRegister (uint8_t r);

the above are all "pass through" functions to the underlying radio object. it may be a bad idea to mess with them. i use them for probing and tweaking the protocol during development, fiddle with at your own peril. if you come up with new hacks by all means let me know.


Website contents, unless otherwise specified, © 2023 by Tom Jennings is licensed under CC BY-SA 4.0