chipKIT® Development Platform

Inspired by Arduino™

nRF24L01 2.4GHz library

Created Mon, 22 Dec 2014 20:57:39 +0000 by majenko


majenko

Mon, 22 Dec 2014 20:57:39 +0000

I have started work on a small library for running the cheap chinese clones of the nRF24L01 2,4GHz transceivers as a mesh on chipKIT boards.

[url]https://github.com/MajenkoLibraries/nrf24l01[/url]

I only have 2 boards at the moment (for testing), but 10 more on the way. When they arrive I will be giving the mesh side of things a proper workout.

The boards I mean are these ones:

You can get them for 99p from China - I mean, how stupid is that?


djgardn2

Mon, 22 Dec 2014 22:09:43 +0000

Very nice, I look forward to giving your library a try. I have found these little nRF boards pretty useful and very well priced.

Would you mind me posting my modified version of this library ([url]https://github.com/tmrh20/RF24[/url])in this post also?

If not I can make a separate post and attach the zip files. You're welcome to use this and modify it more to your style and expertise.

I used an existing library and only took things out in order to make the new version of the library to function without errors. I'm sure things could be modified better but it is fully functionally except for any printf "print" statements.


majenko

Mon, 22 Dec 2014 22:20:56 +0000

By all means :)

I am looking at making my library the first of a number of similar libraries that form the physical mesh layer of a larger infrastructure. Kind of like Ethernet and WiFi are to IP. At the moment it just sends raw packets from station to station, but I intend to write another layer on top of them to give a more network-like experience.


djgardn2

Mon, 22 Dec 2014 22:38:11 +0000

Very neat sounding, I look forward to hearing and seeing updates about your projects.

Here is a link to the original library found from here [url]https://github.com/tmrh20/RF24[/url] (modified PIC32 version attached in this post below)

All functions work except for any of the print functions, you can call them just nothing will return the code is wrapped with a #if ! defined(PIC32MX) #endif statement currently.

If an example sketch calls "#include "printf.h" be sure to comment that out and I commented out all the following printf statements throughout the sketch also. I didn't have much uses for those so simplest way was to just comment them all out.

All credit goes to those before me putting together this library and I simply modified it to get it to function and compile.

Here is a link to a project I put together using these nRF modules in anyone is interested too [url]http://www.chipkit.net/forum/viewtopic.php?f=15&t=3115[/url]


jmlynesjr

Tue, 23 Dec 2014 02:10:57 +0000

Majenko:

Can you pass on the ordering info for the Chinese modules?

Thanks. James


majenko

Tue, 23 Dec 2014 10:07:19 +0000

Just ask eBay for "nRF24L01" and you get offered hundreds of them.


pito

Tue, 23 Dec 2014 15:53:28 +0000

I would rather search the "nrf24L01+" as it seems those with + a newer and better. They are cheap as they are not easy to use in reality.. Also the header is 2mm pitch I think..


majenko

Tue, 23 Dec 2014 16:00:22 +0000

I get the same boards up with that search. They're not real nrf24L01+ but a clone. The nrf24L01+ is just an RF SoC anyway, so how it operates is down to the firmware on it. The normal firmware they are sold with gives a simple SPI interface to it. These other RF SoC chips have the same SPI interface with the same register set and method of operation, so they seem to be pretty much compatible with them.

I don't know what the difference between the nrf24L01 and nrf24L01+ is, but I know the nrf24L01 is considerably better than the nrf2401, with auto ACK/Retry the biggest feature (which my library uses) to make a much more reliable link.


pito

Tue, 23 Dec 2014 16:06:13 +0000

Nordic does not recommend the old version but the + one. It can do "250kb" mode for longer ranges and probably has got a better fware. The stuff works good when used inside a wireless mouse or keyboard, provided the distance between your shoulder and your fingers is less than 3m :)


majenko

Tue, 23 Dec 2014 16:29:09 +0000

That all?! I had a reliable link running between my bedroom and the greenhouse last night - distance of about 20m plus walls. These boards are supposedly able to do 250kbps at a range of 100m line of sight.


pito

Tue, 23 Dec 2014 16:53:42 +0000

maniacbug did a lot with the module in past.. http://maniacbug.github.io/RF24Network/ https://maniacbug.wordpress.com/2012/03/30/rf24network/#more-325


majenko

Tue, 23 Dec 2014 17:11:45 +0000

Sounds similar to what I'm looking at doing, but his is specific to these modules. I'm looking at mine being more generic, and encompassing other ways of linking things together, including RS-232, RS-485, bluetooth, all sorts.


pito

Wed, 24 Dec 2014 12:31:11 +0000

Something like Cosa..


majenko

Wed, 24 Dec 2014 13:11:07 +0000

No, Cosa is an entire OO programming framework, not a multi-layer protocol stack.


pito

Mon, 29 Dec 2014 13:43:47 +0000

Interesting discussions on RF24Ls. http://forum.arduino.cc/index.php?topic=240624.0 http://forum.arduino.cc/index.php?topic=62222.90 Happy to know your experience with the modules then.


jmlynesjr

Fri, 02 Jan 2015 20:42:13 +0000

Majenko:

Nice library! I found the modules on Ebay. Basically 10 for $8 plus shipping.

In queuePacket you do:

enablePipe(0, addr);

Why did you choose to hard code the pipe number?

Also what is the purpose of the _bc array?

James


majenko

Fri, 02 Jan 2015 20:56:31 +0000

That is all for the broadcast support. All devices are set up with two pipes - one (pipe 1) is the normal data pipe, the other (pipe 0) is the broadcast pipe.

All the devices have the same address set for pipe 0 (the _bc array holds the broadcast address), which is grouped by the network number (the first of the 5 bytes of the address). That way you can have up to 255 networks each with its own separate broadcast domain.

Due to the way the auto ack/resend works pipe 0 has to be set to the same address as you're sending to, so constantly having to switch pipe 0 between _bc (when idle) and the destination address (when sending). And it has to be pipe 0, you get no choice in that.


jmlynesjr

Sat, 10 Jan 2015 02:29:25 +0000

Mark:

I think I started off 2015 in a haze....., but maybe I'm starting to understand a little.

After multiple read-throughs of the tutorial, datasheet, and library code, I have a better appreciation of your library. That said, I still have a few questions and comments:

Theory of Operations

(Need help here)

Per each node... There's one transmit address that can be changed as needed to talk to different nodes as required by the application.

There are up to six receive addresses(pipes 0-5) active on the same channel. Packets arriving over these pipes get stuffed into the receive FIFO as they arrive. Seems some receive packet dispatch code will be needed to route the packets to the proper pipe processing code.

Master/slave handshaking code required for bidirectional data transfer.

Addressing Scheme

You configure for 5 byte addresses(ad0-ad4) where you interpret ad0 as a "mesh" id. Do you interpret ad4 as 256 node addresses or more like a "socket" address on the node defined by ad1-ad3. Where a "socket" would be implemented by pipes 0-6.

Interrupt Scheme

Can you explain the isrObject scheme? Is this for supporting multiple (up to 8) radio modules(I assume on separate channels) on one chipKIT board? Is this limited to the hardware interrupt pins(INT0-INT4 on the UNO(I assume there's chipKITs that support 8 hardware interrupts))? Do you think this would work with pin change interrupts?

You set intr(_intr) to 1. Is that a code for INT1 or a pin number?

Current Code

It looks like you are configuring for max retries and max retry delay.

The constructor calls _spi->begin(). In BlinkSender.ino you also call spi.begin(). Is this a duplicate call? Where's the best place for this call? I think in the setup() function rather than in the constructor.

queuePacket sets the pipe0 address equal to the transmit address(I now know it's required for auto ack), where does the pipe0 address ever get set back to the _bc address?(ans-in the isr when TX done and RX reenabled)

It seems the example BlinkReceiver.ino doesn't know(or care) which pipe the packet was received over. Not reading the RX_P_NO bits in the status register.

Examples

Do you have any example code for an application where the slave node sends back a data packet? "Two way communication with payload in both directions"

Do you have any example code for an application that uses pipes 2-5?

Potential Enhancements

queuePacket, readPacket, and enablePipe hard code the packet size as 32. Make this an argument to the constructor and these functions.

enablePipe is coded for pipes 0 and 1. Enhance this function for pipes 2-5. Pipes 2-5 addresses are the same as pipe 1 except for the 8bit LSB.

Expand the constructor to support pipes 2-5.

getStatus returns the last known status. Enhance to read and then return the current status(NOP command).

Thanks for your efforts! James


majenko

Sat, 10 Jan 2015 11:44:46 +0000

I had intended this to work like a Layer 2 protocol, like Ethernet. In that:

  • There is no concept of sockets
  • There is no handshaking or packet-wise acknowledgement
  • A node has only 2 addresses, it's own, and broadcast

Over that, eventually, would be laid a more IP-like protocol which includes sockets, routing, etc.

There's one transmit address that can be changed as needed to talk to different nodes as required by the application.

That is correct.

There are up to six receive addresses(pipes 0-5) active on the same channel. Packets arriving over these pipes get stuffed into the receive FIFO as they arrive. Seems some receive packet dispatch code will be needed to route the packets to the proper pipe processing code.

Yes, but only 0 and 1 are used, for broadcast and unicast.

Master/slave handshaking code required for bidirectional data transfer.

That comes in the higher layer protocol.

You configure for 5 byte addresses(ad0-ad4) where you interpret ad0 as a "mesh" id. Do you interpret ad4 as 256 node addresses or more like a "socket" address on the node defined by ad1-ad3. Where a "socket" would be implemented by pipes 0-6.

ad4 is the mesh number, yes. ad0-3 is the full node address within that mesh. There is no fixed numbering scheme within it - it just allows for (32^2)-1 possible nodes within a mesh. Like Ethernet allows for (48^2)-1 nodes on a single network segment. It performs the same function as the MAC address on Ethernet, but with the concept of a logical mesh number to separate out different networks in the same locale.

Can you explain the isrObject scheme? Is this for supporting multiple (up to 8) radio modules(I assume on separate channels) on one chipKIT board? Is this limited to the hardware interrupt pins(INT0-INT4 on the UNO(I assume there's chipKITs that support 8 hardware interrupts))? Do you think this would work with pin change interrupts?

8 is a number I picked. It could be increased, it could be decreased. It seemed a good number at the time. It should be adaptable to CN, yes.

You set intr(_intr) to 1. Is that a code for INT1 or a pin number?

That's INT1 - I just use attachInterrupt() which uses the INT number not the pin number.

It looks like you are configuring for max retries and max retry delay.

Yep. I may add methods to tweak that at a later date.

The constructor calls _spi->begin(). In BlinkSender.ino you also call spi.begin(). Is this a duplicate call? Where's the best place for this call? I think in the setup() function rather than in the constructor.

The DSPI::begin() method is armoured. Only the first call has any effect. Calling it again does nothing at all. It doesn't matter which calls it - the library or the sketch, as long as at least one of them calls it.

queuePacket sets the pipe0 address equal to the transmit address(I now know it's required for auto ack), where does the pipe0 address ever get set back to the _bc address?(ans-in the isr when TX done and RX reenabled)

Yep. It remains set as the target address until either the ACK is received, or it times out, at which point it goes back to _bc.

It seems the example BlinkReceiver.ino doesn't know(or care) which pipe the packet was received over. Not reading the RX_P_NO bits in the status register.

That's right. I may add a little code to handle broadcast packets differently (not sure how it would work with lots of nodes sending ACKs) but other than that it doesn't care about the pipe, no.

Do you have any example code for an application where the slave node sends back a data packet? "Two way communication with payload in both directions"

No. It would just be a case of the receiver doing the same as what the sender does. I need to add some functionality (which isn't there yet) to get the sending address the last packet came from, so you know where to send a packet back to. It's kind of like how UDP/IP works.

Do you have any example code for an application that uses pipes 2-5?

Nope. They're not used, and not intended to be used. I have pondered adding the ability to add some alias addresses using the other pipes, but that would really be best done in the higher layer protocol.

queuePacket, readPacket, and enablePipe hard code the packet size as 32. Make this an argument to the constructor and these functions.

The queue sizes at both ends have to match. If they don't, you don't get any communication. To stop novices falling into that trap I hard coded it. 32 bytes is not much anyway, and it's the maximum you're allowed.

enablePipe is coded for pipes 0 and 1. Enhance this function for pipes 2-5. Pipes 2-5 addresses are the same as pipe 1 except for the 8bit LSB.

Could do I suppose (see alias addresses above), though as I say this is really something the higher level protocol would be handling.

getStatus returns the last known status. Enhance to read and then return the current status(NOP command).

Yeah, that is something I have been meaning to do.


jmlynesjr

Sun, 11 Jan 2015 00:14:04 +0000

Mark:

Thanks for the answers to my long winded list of questions.

James


jmlynesjr

Tue, 13 Jan 2015 02:36:13 +0000

Mark:

I didn't see anything in the datasheet on how the receiver determines the return address to send the auto ACK to.

I suppose that two way communications would require the transmit address to be included in the data packet so the slave could respond with a reply data packet. I guess this could be application specific if it doesn't make sense to add something to the library.

James


majenko

Tue, 13 Jan 2015 10:42:16 +0000

It doesn't have a clue where the packet came from. It's quite cunning actually...

When you send the packet out you set pipe 0 to have the destination address. Once the packet has finished transmitting the module automatically switches into RX mode.

The receiving module then sends out its ACK signal, but it sends it out to itself. Pipe 0 on the transmitter can then receive it.

Because the whole system is half duplex pipe 0 won't receive the packet you sent out because that module is in transmit mode. The receiver is the only one to receive it. Then when it sends out its ACK the receiver is in transmit mode and the transmitter is in receive mode, so the transmitter is the only one to receive the ACK.

Like I said, it's cunning :)


jmlynesjr

Tue, 13 Jan 2015 16:50:06 +0000

Mark:

Cool idea! Wish they would have spelled that out in the datasheet.

So, does the slave follow up the ACK packet immediately with a full data packet also addressed to itsself while the master stays in receive mode waiting on the full data packet? It would save having to put the master's address in the outbound data packet. The master would then have to turn around an ACK back to the slave to complete the transaction.

Would have to do the _bc address reset differently on both ends.

I see the light! :D

James


majenko

Tue, 13 Jan 2015 17:06:04 +0000

The name's Matt by the way :P

It could be arranged to send out a packet immediately if you wanted I guess. It does mean you'd be restricted to always having a packet ready to respond with immediately you have received one. You can't afford to wait while you do stuff really, as the system would effectively be locked up until you have finished and responded with the packet.

Better to send the address as part of the packet and respond when you're ready.

Just getting it going with my new UltraNano board so I can play with having lots of nodes around the place - try out the networking capability properly then.


jmlynesjr

Tue, 13 Jan 2015 17:18:01 +0000

Matt:

So sorry about getting your name wrong! Brain fart....

Think I agree with including the return address in the data packet. That gives the designer the most control over the interaction. If space was an issue, you could drop down to a 3 byte address.

James


majenko

Tue, 13 Jan 2015 17:35:35 +0000

That's ok John, I have brain farts all the time ;)

If space were that much of an issue you'd be better off upgrading to something more powerful, like WiFi... 32 bytes is plenty for transmitting an address, a couple of temperature values, wind speed, water depth, whatever....


majenko

Tue, 13 Jan 2015 18:46:42 +0000

Well, just put together 3 UltraNano prototypes and plugged in an nRF module, and I have now had 1 transmitting blink instructions to 2 different receivers. It makes one receiver blink once, then the other blink twice. Works beautifully.


jmlynesjr

Tue, 13 Jan 2015 18:48:20 +0000

Agree, Larry.... :)

I just want to send short commands from hand held controllers to my security lights. 32 bytes are plenty.

At any rate much better than a SCADA system I worked on where a message was two 12-bit segments(PERT 26/31). And that system monitored >10,000 sensors.

James


jmlynesjr

Wed, 14 Jan 2015 21:24:29 +0000

Matt: I am playing with the library. I added a getCarrier method(compiles but untested). In nrf24l01.h I added:

uint8_t getCarrier();

In nrf24l01.cpp I added:

uint8_t nrf24l01::getCarrier() {
    uint8_t carrier = 0;
    regRead(REG_CD, &carrier, 1);
    return carrier & 0x01;
}

When verifying my scanner code the DSPI.h file as included by nrf24l01.h is not being found. If I add an #include <DSPI.h> to my code it verifies correctly. The DSPI example sketches verify correctly. Any idea what's happening here?

I expanded on your description of the Auto ACK scheme for my own documentation. Feel free to include a version in your github Doc directory.

I would appreciate it if in your "spare" time you would try to run my scanner code and see if it actually does anything. Thanks in advance.

James

// Name:		nrf24l01scanner.pde
// Author:		James M. Lynes, Jr.
// Created:		January 13, 2015
// Modified By:		James M. Lynes, Jr.
// Last Modified:	January 14, 2015
// Change Log:		1/13/2015 - Program Created
//			1/14/2015 - Fix several typos and compile errors
//				  - Added getCarrier method to the library
// Description:		Sketch to scan channels 0-83(2400-2483MHz USA band limits) for activity.
//				Selects a channel and looks for the carrier detect bit set.
//				Loops through all channels. Prints results.	
//

#define START_CHANNEL 0					// 2400 MHz USA band limits
#define END_CHANNEL 83					// 2483 MHz

#include &lt;nrf24l01.h&gt;					// Nordic nRF24L01 library(Matt Jenkins-majenko)

DSPI0 spi;						// Create an SPI object(Digilent library)
							// SS pin 10, MOSI pin 11, MISO pin 12, SCK pin 13
nrf24l01 rf(spi, 10, 9, 1);				// Create an RF object
							// NCS pin 10, CE pin 9, INT1 pin 2 

void setup() {
    spi.begin();					// Start the SPI interface
    rf.begin(2, 10, 0, 0, 1, 0);			// Start the RF module interface
							// mesh 2, address 10,0,0,1, channel 0
    Serial.begin(115200);				// Open a serial link(via USB) to the host PC

    Serial.println("Starting nRF24L01 Channel Scanner");
    Serial.println("---------------------------------\n");
}

void loop() {
    Serial.println("Channel   Status");
    Serial.println("-------   ------\n");

    uint8_t buf[32];					// Max length packet buffer
    uint8_t carrier;					// Carrier detect flag
    String txt;						// Channel status text

    for(int i = START_CHANNEL; i &lt; END_CHANNEL; i++) {
        rf.setChannel(i);				// Set channel number to test
	delay(1);					// Delay to sample channel activity
       carrier =  rf.getCarrier();			// Read the carrier detect register(LSB)
        txt = "Clear";
        if(carrier) {
            txt = "Busy";
        }
        Serial.print("    ");
        Serial.print(i);
        Serial.print("        ");
        Serial.println(txt);
    }
}
Nordic nRF24L01 Auto Acknowledgement Scheme
-------------------------------------------
(Matt Jenkins &amp; James Lynes - January 14, 2015)

The Nordic nRF24L01 radio module supports an Auto ACK mode to off-load the message verification task from the host processor. The datasheet discusses how to use this mode, however, there is no discussion of how this mode is implemented. Specifically, how does the receiver know the address of the transmitter that the ACK message needs to be sent back to?

The reality is that the receiver doesn't have a clue where the packet came from. It's quite cunning actually..

When the transmitting node sends out a packet it must set the pipe 0 address to the receiver's destination address. Once the packet has finished transmitting the transmitter automatically switches into RX mode to receive the ACK message.

The receiving module then sends out its ACK signal, but it sends it out ***TO ITSELF***(It's own node address)! Pipe 0 on the transmitter can then receive it having previously been set to the receiver's address.

Because the whole system is half duplex, pipe 0 on the receiver won't receive the ACK packet it sent out to itself because that module is now in TX mode(it's not listening on any pipes at that moment). The original transmitter is the only node that can receive it. 

The original transmitter then switches back to TX mode and resets pipe 0 back to it's original address. The original receiver switches back to RX mode.

For an application requiring bidirectional data packets("Two way communication with payload in both directions"), we think including the return address in the outbound packet provides the designer with the most flexibility in controlling the timing of data exchange at the expense of using 5 bytes of the 32 byte packet for the return address. This allows the receiver time to process sensors before having to send the response.

It's cunning :)

majenko

Wed, 14 Jan 2015 22:20:47 +0000

I suggest you fork the library on Github then do your work in that fork, You can then create a pull request and I can merge your changes into the main repo.


majenko

Wed, 14 Jan 2015 22:26:44 +0000

Oh, and if you're using MPIDE then yes, you'll have to add DSPI.h to your sketch.


jmlynesjr

Thu, 15 Jan 2015 02:50:19 +0000

Have only uploaded to github so far. Guess it's time to learn the other half of the process. :)

And yes, still using MPIDE, so will make the addition.

James


jmlynesjr

Fri, 23 Jan 2015 01:30:47 +0000

Received two modules today from the great Ebay.

Now time to do something with them.

James


jmlynesjr

Fri, 23 Jan 2015 17:05:47 +0000

Matt: In queuePacket I see:

uint8_t stat = 0;
regRead(REG_FIFO_STATUS, &amp;stat, 1);

I don't see stat being used anywhere. can these lines be removed?

Also, I'm going to add a broadcast function based on queuePacket.

James


majenko

Fri, 23 Jan 2015 17:30:51 +0000

Yeah, I think those lines were leftover from when I was trying to make it more asynchronous with the built-in fifo, but I gave up on that idea when I realised it was unworkable with the way I'm switching pipes around and things. So I had to make it blocking unfortunately. Maybe one day I'll work out how to do it.

For the broadcast you might like to make it so it doesn't do the auto-ack, since acks would be coming in from many stations at once, and that might just get messy. If you can do it so the acks don't matter if they don't get through that would be good.


jmlynesjr

Fri, 23 Jan 2015 23:01:38 +0000

I actually had a note to ask you about Auto ACK on broadcast, but I think I had convinced myself that you weren't setting EN_AA for pipe 0. I see now that it gets set in enablePipe.

So, I'll add a regClr for EN_AA bit 0 in the broadcast function. And also delete the extra code listed above.

Thinking of building stripboard adapters for the four modules I received so I can play with them on my bread boards. Can fit 18 on one vector board. Won't build that many. [attachment=0]print.png[/attachment]

(Graphic via: Fritzing->pdf->Gimp->crop->png) James


jmlynesjr

Sat, 24 Jan 2015 21:07:01 +0000

Matt: A question about the interrupt pin definition/usage...

nrf24l01::nrf24l01(DSPI &amp;spi, int csn, int ce, int intr)

_intr = intr;

pinMode(_intr, INPUT);

attachInterrupt(_intr, nrf24l01_isrHandler0, FALLING);

So, is intr an interrupt like INT1 or the pin number of INT1, which is pin 2 on my UNO32?

James


majenko

Sat, 24 Jan 2015 21:31:29 +0000

It is always the internal PIC32 interrupt number. 1 = INT1, 2 = INT2, etc.

You can blame Arduino for that.


jmlynesjr

Mon, 26 Jan 2015 01:17:38 +0000

So, the pinMode(_intr, INPUT) is not needed?

James


majenko

Tue, 27 Jan 2015 10:57:08 +0000

You might well be right there :)


jmlynesjr

Tue, 27 Jan 2015 18:54:49 +0000

Ok. I'll take it out and give it a try when my parts come in to make the proto adapters.

Until then, I think I'm out of questions. :)

James


majenko

Mon, 14 Sep 2015 19:02:07 +0000

Digging up an old thread for this :)

I have taken my nRF24L01 library and injected a small shot of methamphetamine.

I have turned it into a proper Mesh system with node hopping and everything.

  • I have defined a new base class "L2" which defines the interface to a Layer 2 interface device.

  • My nRF24L01 library has been changed to conform to this interface

  • I have added a Mesh library that turns any number of L2 devices into a mesh of somewhat large proportions.

  • Any node can send packets directly to any neighbouring node.

  • All nodes broadcast their own node IDE and a list of nodes they know how to get to to neighbouring nodes.

  • Any node will forward packets to the least-cost next hop node to get to a node that is not a direct neighbour.

That means, if you have three nodes: node 1 can communicate to both node 2 and node 3, but nodes 2 and 3 are too far apart to communicate, packets can still be sent from node 2 to node 3 by sending them to node 1 first.

This is all completely transparent and automatic.

Three nodes all near each other:

My host ID: 2
Devices:
    7D:7A:19:A7:96
Directly connected hosts:
    1 on EB:A5:31:52:4D via 7D:7A:19:A7:96
    3 on 25:7C:E3:0B:31 via 7D:7A:19:A7:96
Known remote hosts:

Move nodes 2 and 3 so they can't communicate with each other, and this happens:

My host ID: 2                                            
Devices:                                                 
    7D:7A:19:A7:96
Directly connected hosts:
    1 on EB:A5:31:52:4D via 7D:7A:19:A7:96
Known remote hosts:
    3 via 1 (cost 2)

Fully automatic network adjustment and healing with no need to do anything :) Add a node to the network and it's instantly part of the forwarding and routing strategy for the whole mesh.

I intend to add support for other layer 2 devices, including a point-to-point serial connection (kind of like PPP or SLIP in the IP world).

It's still in its infancy, but if you want to play with it, here it is:

https://github.com/MajenkoLibraries/Mesh