From 247431141e3d682091e10a11fdd56f9026c92440 Mon Sep 17 00:00:00 2001 From: TMRh20 Date: Thu, 26 Jun 2014 20:54:40 -0600 Subject: [PATCH] Add Pandora's box routing - Cleaned up prev changes a bit - Added what I like to call Pandora's box routing: Nodes are able to write network payloads directly to any host, who will then route the payloads as required. If sending to the host that will receive the payload, a radio ACK will be sent directly to the sender. If sending to a host that is not the recipient, it will be forwarded to the correct host, and a network ACK will be sent by the final delivering node, The network ack will traverse the network just as any other message, and will fail if the main link to the sending node is down. This allows failover nodes to be put in place. Failover nodes should not have any children, OR should have spare pipes available. Node 014 will always write to pipe1 for pandora routing, and node 023 will always write to pipe 2, so any failover nodes should have pipes 1 and 2 available if used for both. Duplicate payloads are still an issue when network acks fail and are retried, so can be filtered out by the receiver based on the network header id number if required. --- RF24Network.cpp | 102 +++++++++++++++++--------------- RF24Network.h | 67 ++++++++++++++++++--- RPi/RF24Network/RF24Network.cpp | 85 ++++++++++++++++---------- RPi/RF24Network/RF24Network.h | 18 +++++- 4 files changed, 179 insertions(+), 93 deletions(-) diff --git a/RF24Network.cpp b/RF24Network.cpp index 47c6d7d1..b43db246 100644 --- a/RF24Network.cpp +++ b/RF24Network.cpp @@ -21,12 +21,7 @@ uint16_t RF24NetworkHeader::next_id = 1; uint64_t pipe_address( uint16_t node, uint8_t pipe ); bool is_valid_address( uint16_t node ); uint32_t nFails = 0, nOK=0; -uint8_t addrLen = 0; -/*uint8_t errBuffer[32],errPipe; -uint16_t errNode; -bool errRetry = 0; -*/ /******************************************************************/ #if !defined (DUAL_HEAD_RADIO) RF24Network::RF24Network( RF24& _radio ): radio(_radio), next_frame(frame_queue) @@ -60,9 +55,9 @@ void RF24Network::begin(uint8_t _channel, uint16_t _node_address ) uint8_t retryVar = ((node_address % 6) *2) + 3; radio.setRetries(retryVar, 15); txTimeout = 100; - routeTimeout = 200; + routeTimeout = txTimeout+25; // About 2.5ms max delay per node at optimal routing speeds, 10 nodes maximum hop + max retry time for auto-ack - printf("Retries: %d, txTimeout: %d",retryVar,txTimeout); + printf_P(PSTR("Retries: %d, txTimeout: %d"),retryVar,txTimeout); #if defined (DUAL_HEAD_RADIO) radio1.setChannel(_channel); radio1.setDataRate(RF24_1MBPS); @@ -112,21 +107,22 @@ uint8_t RF24Network::update(void) continue; } - uint8_t res = header.reserved; + uint8_t res = header.type; // Is this for us? if ( header.to_node == node_address ){ if(res == NETWORK_ACK){ // If received a routing payload, (Network ACK) discard it, and indicate what it was. - #ifdef DEBUG_ROUTING - printf_P(PSTR("MAC: Network ACK Rcvd")); + #ifdef SERIAL_DEBUG_ROUTING + printf_P(PSTR("MAC: Network ACK Rcvd\n")); #endif return NETWORK_ACK; } - + //printf("enQ\n"); // Add it to the buffer of frames for us enqueue(); - + nOK++; }else{ + //printf("route"); write(header.to_node,1); //Send it on, indicate it is a routed payload } @@ -235,9 +231,17 @@ size_t RF24Network::read(RF24NetworkHeader& header,void* message, size_t maxlen) return bufsize; } +/******************************************************************/ +bool RF24Network::write(RF24NetworkHeader& header,const void* message, size_t len){ + return _write(header,message,len,070); +} +/******************************************************************/ +bool RF24Network::write(RF24NetworkHeader& header,const void* message, size_t len, uint16_t writeDirect){ + return _write(header,message,len,writeDirect); +} /******************************************************************/ -bool RF24Network::write(RF24NetworkHeader& header,const void* message, size_t len) +bool RF24Network::_write(RF24NetworkHeader& header,const void* message, size_t len, uint16_t writeDirect) { // Fill out the header header.from_node = node_address; @@ -260,32 +264,42 @@ bool RF24Network::write(RF24NetworkHeader& header,const void* message, size_t le return enqueue(); else // Otherwise send it out over the air - return write(header.to_node,0); - + if(writeDirect != 070){ + if(header.to_node == writeDirect){ + return write(writeDirect,2); + }else{ + return write(writeDirect,3); + } + }else{ + return write(header.to_node,0); + } } /******************************************************************/ -bool RF24Network::write(uint16_t to_node, bool routed) +bool RF24Network::write(uint16_t to_node, uint8_t directTo) // Direct To: 0 = First Payload, standard routing, 1=routed payload, 2=directRoute to host, 3=directRoute to Route { bool ok = false; - + bool multicast = 0; // Radio ACK requested = 0 + // Throw it away if it's not a valid address if ( !is_valid_address(to_node) ) return false; - // First, stop listening so we can talk. - //radio.stopListening(); // Where do we send this? By default, to our parent uint16_t send_node = parent_node; // On which pipe uint8_t send_pipe = parent_pipe; - + + if(directTo>1){ + send_node = to_node; + } + // If the node is a direct child, - if ( is_direct_child(to_node) ) + else if ( is_direct_child(to_node) ) { // Send directly send_node = to_node; @@ -301,39 +315,26 @@ bool RF24Network::write(uint16_t to_node, bool routed) send_node = direct_child_route_to(to_node); send_pipe = 0; } - bool multicast = 0; // Network ACK requested - if(!routed && send_node != to_node ){ - frame_buffer[7] = NETWORK_ACK_REQUEST; - #ifdef SERIAL_DEBUG_ROUTING - printf("Req Net Ack\n"); - #endif - } - if(send_node != to_node){ - multicast = 1; //No ACK requested ( Use manual network ACK ) - } + + if( ( send_node != to_node) || frame_buffer[6] == NETWORK_ACK || directTo == 3){ + multicast = 1; + } + IF_SERIAL_DEBUG(printf_P(PSTR("%lu: MAC Sending to 0%o via 0%o on pipe %x\n\r"),millis(),to_node,send_node,send_pipe)); -#if !defined (DUAL_HEAD_RADIO) - // First, stop listening so we can talk - radio.stopListening(); -#endif - ok=write_to_pipe(send_node, send_pipe, multicast); #ifdef SERIAL_DEBUG_ROUTING - if(ok == 0){ - printf_P(PSTR("%lu: MAC Send fail to 0%o via 0%o on pipe %x\n\r"),millis(),to_node,send_node,send_pipe); - } + if(!ok){ printf_P(PSTR("%lu: MAC Send fail to 0%o via 0%o on pipe %x\n\r"),millis(),to_node,send_node,send_pipe); } #endif - if(routed && ok && send_node == to_node && frame_buffer[7] != NETWORK_ACK){ - uint16_t from = frame_buffer[0] | (frame_buffer[1] << 8) ; - frame_buffer[7] = NETWORK_ACK; - frame_buffer[2] = frame_buffer[0]; frame_buffer[3] = frame_buffer[1]; - write(from,1); + if( directTo == 1 && ok && !multicast ){ + frame_buffer[6] = NETWORK_ACK; // Set the payload type to NETWORK_ACK + frame_buffer[2] = frame_buffer[0]; frame_buffer[3] = frame_buffer[1]; // Change the 'to' address to the 'from' address + write(frame_buffer[0] | (frame_buffer[1] << 8),1); // Send it back as a routed message #if defined (SERIAL_DEBUG_ROUTING) - printf("MAC: Route OK to 0%o ACK sent to 0%o\n",to_node,from); + printf_P(PSTR("MAC: Route OK to 0%o ACK sent to 0%o\n"),to_node,frame_buffer[0] | (frame_buffer[1] << 8)); #endif } @@ -357,11 +358,11 @@ bool RF24Network::write(uint16_t to_node, bool routed) radio.startListening(); #endif - if(send_node != to_node && !routed){ + if( (send_node != to_node && directTo==0) || directTo == 3 ){ uint32_t reply_time = millis(); while( update() != NETWORK_ACK){ if(millis() - reply_time > routeTimeout){ - #if def SERIAL_DEBUG_ROUTING + #ifdef SERIAL_DEBUG_ROUTING printf_P(PSTR("%lu: MAC Network ACK fail from 0%o on pipe %x\n\r"),millis(),to_node,send_pipe); #endif ok=0; @@ -371,8 +372,9 @@ bool RF24Network::write(uint16_t to_node, bool routed) } if(ok == true){ - nOK++; + //nOK++; }else{ nFails++; + //printf("Fail to %o",to_node); } return ok; } @@ -388,8 +390,10 @@ bool RF24Network::write_to_pipe( uint16_t node, uint8_t pipe, bool multicast ) #if !defined (DUAL_HEAD_RADIO) // Open the correct pipe for writing. - radio.openWritingPipe(out_pipe); + // First, stop listening so we can talk + radio.stopListening(); + radio.openWritingPipe(out_pipe); radio.writeFast(frame_buffer, frame_size,multicast); ok = radio.txStandBy(txTimeout); diff --git a/RF24Network.h b/RF24Network.h index 148a58df..d2a53a5f 100644 --- a/RF24Network.h +++ b/RF24Network.h @@ -150,7 +150,8 @@ class RF24Network * @return Whether the message was successfully received */ bool write(RF24NetworkHeader& header,const void* message, size_t len); - + bool write(RF24NetworkHeader& header,const void* message, size_t len, uint16_t writeDirect); + /** * Sleep this node - Still Under Development * @note NEW - Nodes can now be slept while the radio is not actively transmitting. This must be manually enabled by uncommenting @@ -242,7 +243,7 @@ class RF24Network private: void open_pipes(void); uint16_t find_node( uint16_t current_node, uint16_t target_node ); - bool write(uint16_t,bool routed); + bool write(uint16_t, uint8_t directTo); bool write_to_pipe( uint16_t node, uint8_t pipe, bool multicast ); bool enqueue(void); @@ -251,7 +252,8 @@ class RF24Network uint16_t direct_child_route_to( uint16_t node ); uint8_t pipe_to_descendant( uint16_t node ); void setup_address(void); - + bool _write(RF24NetworkHeader& header,const void* message, size_t len, uint16_t writeDirect); + private: RF24& radio; /**< Underlying radio driver, provides link/physical layers */ @@ -268,9 +270,8 @@ class RF24Network uint16_t parent_node; /**< Our parent's node address */ uint8_t parent_pipe; /**< The pipe our parent uses to listen to us */ uint16_t node_mask; /**< The bits which contain signfificant node address information */ - #define NETWORK_ACK_REQUEST 128 #define NETWORK_ACK 129 - + }; /** @@ -337,13 +338,14 @@ class RF24Network * @section Purpose Purpose/Goal * * Original: Create an alternative to ZigBee radios for Arduino communication. + * * New: Enhance the current functionality for maximum efficiency, reliability, and speed * * Xbees are excellent little radios, backed up by a mature and robust standard * protocol stack. They are also expensive. * - * For many Arduino uses, they seem like overkill. So I am working to build - * an alternative using nRF24L01 radios. Modules are available for less than + * For many Arduino uses, they seem like overkill. So I am working to improve the current + * standard for nRF24L01 radios. The best RF24 modules are available for less than * $6 from many sources. With the RF24Network layer, I hope to cover many * common communication scenarios. * @@ -352,10 +354,12 @@ class RF24Network * @section Features Features * * The layer provides: - * @li New (2014): Dual headed operation: The use of dual radios for busy routing nodes or the master node enhances throughput and decreases errors. See the Tuning section. + * @li New (2014): Network ACKs: Efficient acknowledgement of network-wide transmissions, via dynamic radio acks and network protocol acks. + * @li New (2014): Updated addressing standard for optimal radio transmission. * @li New (2014): Extended timeouts and staggered timeout intervals. The new txTimeout variable allows fully automated extended timeout periods via auto-retry/auto-reUse of payloads. * @li New (2014): Optimization to the core library provides improvements to reliability, speed and efficiency. See https://tmrh20.github.io/RF24 for more info. * @li New (2014): Built in sleep mode using interrupts. (Still under development. (Enable via RF24Network_config.h)) + * @li New (2014): Dual headed operation: The use of dual radios for busy routing nodes or the master node enhances throughput and decreases errors. See the Tuning section. * @li Host Addressing. Each node has a logical address on the local network. * @li Message Forwarding. Messages can be sent from one node to any other, and * this layer will get them there no matter how many hops it takes. @@ -470,11 +474,56 @@ class RF24Network * @li Base node. The top of the tree node with no parents, only children. Typically this node * will bridge to another kind of network like Ethernet. ZigBee calls it a Co-ordinator node. * + * + * + * * @page Tuning Performance and Data Loss: Tuning the Network - * Tips and examples for tuning the network and Dual-head operation. + * Tips and examples for tuning the network and general operation. * * Topology * + * @section General Understanding Radio Communication and Topology + * When a transmission takes place from one radio module to another, the receiving radio will communicate + * back to the sender with an acknowledgement (ACK) packet, to indicate success. If the sender does not + * receive an ACK, the radio automatically engages in a series of timed retries, at set intervals. The + * radios use techniques like addressing and numbering of payloads to manage this, but it is all done + * automatically, out of sight from the user. + * + * When working over a radio network, some of these automated techniques can actually hinder data transmission. + * Retrying failed payloads over and over on a radio network can hinder communication for nearby nodes, or + * reduce throughput and errors on routing nodes. + * + * Radios in this network are linked by addresses assigned to pipes. Each radio can listen + * to 6 addresses on 6 pipes, therefore each radio has a parent pipe and 5 child pipes, which are used + * to form a tree structure. Nodes communicate directly with their parent and children nodes. Any other + * traffic to or from a node must be routed through the network. + * + * @section Network Routing + * + * Routing of traffic is handled invisibly to the user. If the network is constructed appropriately, nodes + * will route traffic automatically as required. Data transmission generally has one of two requirements, + * either data that fails to transmit can be discarded as new data arrives, or sending can be retried as + * required until complete success or failure. + * + * The new routing protocol allows this to be managed at the application level as the data requires, with + * defaults assigned specifically to allow maximum efficiency and throughput from the RF level to the + * network and application level. If routing data between parent and child nodes (marked by direct links on + * the topology image above) the network uses built-in acknowledgement and retry functions of the chip to + * prevent data loss. When payloads are sent to other nodes, they need to be routed. Routing is managed using + * a combination of built in ACK requests, and software driven network ACKs. This allows all routing nodes to + * forward data very quickly, with only the final routing node confirming delivery and sending back an + * acknowledgement. + * + * Example: Node 00 sends to node 01. The nodes will use the built in auto-retry and auto-ack functions.
+ * Exmaple: Node 00 sends to node 011. Node 00 will send to node 01, and request -no radio ACK-. Node 01 will + * forward the message to 011 and request an auto radio ACK. If delivery was successful, node 01 will also + * forward a message back to node 00, (noACK) indicating success. + * + * Old Functionality: Node 00 sends to node 011 using auto-ack. Node 00 first sends to 01, 01 acknowledges. + * Node 01 forwards the payload to 011 using auto-ack. If the payload fails between 01 and 011, node 00 has + * no way of knowing. The new method uses the same amount of traffic to accomplish more. + * + * * @section TuningOverview Tuning Overview * The RF24 radio modules are generally only capable of either sending or receiving data at any given * time, but have built-in auto-retry mechanisms to prevent the loss of data. These values are adjusted diff --git a/RPi/RF24Network/RF24Network.cpp b/RPi/RF24Network/RF24Network.cpp index ffffbe29..04297604 100644 --- a/RPi/RF24Network/RF24Network.cpp +++ b/RPi/RF24Network/RF24Network.cpp @@ -56,7 +56,8 @@ void RF24Network::begin(uint8_t _channel, uint16_t _node_address ) uint8_t retryVar = (((node_address % 6)+1) *2) + 3; radio.setRetries(retryVar, 15); txTimeout = retryVar * 17; - + routeTimeout = txTimeout+25; + // Setup our address helper cache setup_address(); @@ -101,7 +102,7 @@ uint8_t RF24Network::update(void) } - uint8_t res = header.reserved; + uint8_t res = header.type; // Is this for us? if ( header.to_node == node_address ){ if(res == NETWORK_ACK){ @@ -110,7 +111,7 @@ uint8_t RF24Network::update(void) // Add it to the buffer of frames for us enqueue(); - + }else{ // Relay it as a routed message write(header.to_node,1); @@ -218,9 +219,17 @@ size_t RF24Network::read(RF24NetworkHeader& header,void* message, size_t maxlen) return bufsize; } +/******************************************************************/ +bool RF24Network::write(RF24NetworkHeader& header,const void* message, size_t len){ + return _write(header,message,len,070); +} +/******************************************************************/ +bool RF24Network::write(RF24NetworkHeader& header,const void* message, size_t len, uint16_t writeDirect){ + return _write(header,message,len,writeDirect); +} /******************************************************************/ -bool RF24Network::write(RF24NetworkHeader& header,const void* message, size_t len) +bool RF24Network::_write(RF24NetworkHeader& header,const void* message, size_t len, uint16_t writeDirect) { // Fill out the header header.from_node = node_address; @@ -242,16 +251,25 @@ bool RF24Network::write(RF24NetworkHeader& header,const void* message, size_t le // Just queue it in the received queue return enqueue(); else - // Otherwise send it out over the air - return write(header.to_node,0); + if(writeDirect != 070){ + if(header.to_node == writeDirect){ + return write(writeDirect,2); + }else{ + return write(writeDirect,3); + } + }else{ + // Otherwise send it out over the air + return write(header.to_node,0); + } } /******************************************************************/ -bool RF24Network::write(uint16_t to_node, bool routed) +bool RF24Network::write(uint16_t to_node, uint8_t directTo) { bool ok = false; - + bool multicast = 0; // Radio ACK requested = 0 + // Throw it away if it's not a valid address if ( !is_valid_address(to_node) ) return false; @@ -263,9 +281,14 @@ bool RF24Network::write(uint16_t to_node, bool routed) uint16_t send_node = parent_node; // On which pipe uint8_t send_pipe = parent_pipe; - + + if(directTo>1){ + send_node = to_node; + send_pipe = parent_pipe; + } + // If the node is a direct child, - if ( is_direct_child(to_node) ) + else if ( is_direct_child(to_node) ) { // Send directly send_node = to_node; @@ -281,37 +304,34 @@ bool RF24Network::write(uint16_t to_node, bool routed) send_node = direct_child_route_to(to_node); send_pipe = 0; } + - bool multicast = 0; - if(!routed && send_node != to_node ){ // If sending original message, and message needs to be routed - frame_buffer[7] = NETWORK_ACK_REQUEST; // Request a manual ACK payload - multicast = 1; // Send via multicast since message is routed, a manual network-ACK will be sent if successful - } - if(send_node != to_node){ - multicast = 1; //No ACK requested + if( ( send_node != to_node) || frame_buffer[6] == NETWORK_ACK || directTo == 3){ + multicast = 1; } + IF_SERIAL_DEBUG(printf_P(PSTR("%d: MAC Sending to 0%o via 0%o on pipe %x\n\r"),millis(),to_node,send_node,send_pipe)); - // First, stop listening so we can talk - radio.stopListening(); + // Put the frame on the pipe ok = write_to_pipe( send_node, send_pipe, multicast ); - +//printf("Multi %d\n",multicast); - if(routed && ok && send_node == to_node && frame_buffer[7] != NETWORK_ACK){ - uint16_t from = frame_buffer[0] | (frame_buffer[1] << 8) ; - frame_buffer[7] = NETWORK_ACK; + #if defined (SERIAL_DEBUG_ROUTING) || defined(SERIAL_DEBUG) + if(!ok){ printf_P(PSTR("%u: MAC Send fail to 0%o via 0%o on pipe %x\n\r"),millis(),to_node,send_node,send_pipe); } + #endif + + if( directTo == 1 && ok && !multicast ){ + frame_buffer[6] = NETWORK_ACK; frame_buffer[2] = frame_buffer[0]; frame_buffer[3] = frame_buffer[1]; - write(from,1); + write(frame_buffer[0] | (frame_buffer[1] << 8),1); #if defined (SERIAL_DEBUG_ROUTING) - printf("NET: ACK to %o \n",from); + printf("MAC: Route OK to 0%o ACK sent to 0%o\n",to_node,frame_buffer[0] | (frame_buffer[1] << 8)); #endif } - #if defined (SERIAL_DEBUG_ROUTING) || defined(SERIAL_DEBUG) - if(!ok){ printf_P(PSTR("%u: MAC Send fail to 0%o via 0%o on pipe %x\n\r"),millis(),to_node,send_node,send_pipe); } - #endif + // NOT NEEDED anymore. Now all reading pipes are open to start. #if 0 @@ -326,11 +346,10 @@ bool RF24Network::write(uint16_t to_node, bool routed) // Now, continue listening radio.startListening(); - if(send_node != to_node && !routed && ok){ + if( (send_node != to_node && directTo==0) || directTo == 3 ){ uint32_t reply_time = millis(); - //bool pOK = 1; while( update() != NETWORK_ACK){ - if(millis() - reply_time > 500){ + if(millis() - reply_time > routeTimeout){ ok=0; #ifdef SERIAL_DEBUG_ROUTING printf_P(PSTR("%u: MAC Network ACK fail from 0%o via 0%o on pipe %x\n\r"),millis(),to_node,send_node,send_pipe); @@ -354,7 +373,9 @@ bool RF24Network::write_to_pipe( uint16_t node, uint8_t pipe, bool multicast ) bool ok = false; uint64_t out_pipe = pipe_address( node, pipe ); - + + // First, stop listening so we can talk + radio.stopListening(); // Open the correct pipe for writing. radio.openWritingPipe(out_pipe); diff --git a/RPi/RF24Network/RF24Network.h b/RPi/RF24Network/RF24Network.h index a2afd51d..cfcd768f 100644 --- a/RPi/RF24Network/RF24Network.h +++ b/RPi/RF24Network/RF24Network.h @@ -153,7 +153,7 @@ class RF24Network * @return Whether the message was successfully received */ bool write(RF24NetworkHeader& header,const void* message, size_t len); - + bool write(RF24NetworkHeader& header,const void* message, size_t len, uint16_t writeDirect); /** * This node's parent address * @@ -173,10 +173,21 @@ class RF24Network unsigned long txTimeout; + /** + * @note: Optimization: This new value defaults to 200 milliseconds. + * This only affects payloads that are routed by one or more nodes. + * This specifies how long to wait for an ack from across the network. + * Radios routing directly to their parent or children nodes do not + * utilize this value. + */ + + uint16_t routeTimeout; + + protected: void open_pipes(void); uint16_t find_node( uint16_t current_node, uint16_t target_node ); - bool write(uint16_t, bool routed); + bool write(uint16_t, uint8_t directTo); bool write_to_pipe( uint16_t node, uint8_t pipe, bool multicast ); bool enqueue(void); @@ -185,7 +196,8 @@ class RF24Network uint16_t direct_child_route_to( uint16_t node ); uint8_t pipe_to_descendant( uint16_t node ); void setup_address(void); - + bool _write(RF24NetworkHeader& header,const void* message, size_t len, uint16_t writeDirect); + private: RF24& radio; /**< Underlying radio driver, provides link/physical layers */ uint16_t node_address; /**< Logical node address of this unit, 1 .. UINT_MAX */