diff --git a/docs/mesh-proto.md b/docs/mesh-proto.md deleted file mode 100644 index aa5a84df9..000000000 --- a/docs/mesh-proto.md +++ /dev/null @@ -1,20 +0,0 @@ -TODO: -* reread the radiohead mesh implementation -* read about general mesh flooding solutions -* reread the disaster radio protocol docs - -good description of batman protocol: https://www.open-mesh.org/projects/open-mesh/wiki/BATMANConcept - -interesting paper on lora mesh: https://portal.research.lu.se/portal/files/45735775/paper.pdf -It seems like DSR might be the algorithm used by RadioheadMesh. DSR is described in https://tools.ietf.org/html/rfc4728 -https://en.wikipedia.org/wiki/Dynamic_Source_Routing - -broadcast solution: -Use naive flooding at first (FIXME - do some math for a 20 node, 3 hop mesh. A single flood will require a max of 20 messages sent) -Then move to MPR later (http://www.olsr.org/docs/report_html/node28.html). Use altitude and location as heursitics in selecting the MPR set - -compare to db sync algorithm? - -what about never flooding gps broadcasts. instead only have them go one hop in the common case, but if any node X is looking at the position of Y on their gui, then send a unicast to Y asking for position update. Y replies. - -If Y were to die, at least the neighbor nodes of Y would have their last known position of Y. diff --git a/docs/software/mesh-alg.md b/docs/software/mesh-alg.md index d33052ff7..d90d85c9c 100644 --- a/docs/software/mesh-alg.md +++ b/docs/software/mesh-alg.md @@ -1,73 +1,95 @@ # Mesh broadcast algorithm -FIXME - instead look for standard solutions. this approach seems really suboptimal, because too many nodes will try to rebroast. If +FIXME - instead look for standard solutions. this approach seems really suboptimal, because too many nodes will try to rebroast. If all else fails could always use the stock Radiohead solution - though super inefficient. great source of papers and class notes: http://www.cs.jhu.edu/~cs647/ TODO: -* DONE reread the radiohead mesh implementation - hop to hop acknoledgement seems VERY expensive but otherwise it seems like DSR -* DONE read about mesh routing solutions (DSR and AODV) -* DONE read about general mesh flooding solutions (naive, MPR, geo assisted) -* DONE reread the disaster radio protocol docs - seems based on Babel (which is AODVish) -* possibly dash7? https://www.slideshare.net/MaartenWeyn1/dash7-alliance-protocol-technical-presentation https://github.com/MOSAIC-LoPoW/dash7-ap-open-source-stack - does the opensource stack implement multihop routing? flooding? their discussion mailing list looks dead-dead -* update duty cycle spreadsheet for our typical usecase -* generalize naive flooding on top of radiohead or disaster.radio? (and fix radiohead to use my new driver) -a description of DSR: https://tools.ietf.org/html/rfc4728 +- DONE reread the radiohead mesh implementation - hop to hop acknoledgement seems VERY expensive but otherwise it seems like DSR +- DONE read about mesh routing solutions (DSR and AODV) +- DONE read about general mesh flooding solutions (naive, MPR, geo assisted) +- DONE reread the disaster radio protocol docs - seems based on Babel (which is AODVish) +- possibly dash7? https://www.slideshare.net/MaartenWeyn1/dash7-alliance-protocol-technical-presentation https://github.com/MOSAIC-LoPoW/dash7-ap-open-source-stack - does the opensource stack implement multihop routing? flooding? their discussion mailing list looks dead-dead +- update duty cycle spreadsheet for our typical usecase +- generalize naive flooding on top of radiohead or disaster.radio? (and fix radiohead to use my new driver) + +a description of DSR: https://tools.ietf.org/html/rfc4728 good slides here: https://www.slideshare.net/ashrafmath/dynamic-source-routing good description of batman protocol: https://www.open-mesh.org/projects/open-mesh/wiki/BATMANConcept interesting paper on lora mesh: https://portal.research.lu.se/portal/files/45735775/paper.pdf -It seems like DSR might be the algorithm used by RadioheadMesh. DSR is described in https://tools.ietf.org/html/rfc4728 +It seems like DSR might be the algorithm used by RadioheadMesh. DSR is described in https://tools.ietf.org/html/rfc4728 https://en.wikipedia.org/wiki/Dynamic_Source_Routing broadcast solution: -Use naive flooding at first (FIXME - do some math for a 20 node, 3 hop mesh. A single flood will require a max of 20 messages sent) -Then move to MPR later (http://www.olsr.org/docs/report_html/node28.html). Use altitude and location as heursitics in selecting the MPR set +Use naive flooding at first (FIXME - do some math for a 20 node, 3 hop mesh. A single flood will require a max of 20 messages sent) +Then move to MPR later (http://www.olsr.org/docs/report_html/node28.html). Use altitude and location as heursitics in selecting the MPR set compare to db sync algorithm? -what about never flooding gps broadcasts. instead only have them go one hop in the common case, but if any node X is looking at the position of Y on their gui, then send a unicast to Y asking for position update. Y replies. +what about never flooding gps broadcasts. instead only have them go one hop in the common case, but if any node X is looking at the position of Y on their gui, then send a unicast to Y asking for position update. Y replies. If Y were to die, at least the neighbor nodes of Y would have their last known position of Y. ## approach 1 -* send all broadcasts with a TTL -* periodically(?) do a survey to find the max TTL that is needed to fully cover the current network. -* to do a study first send a broadcast (maybe our current initial user announcement?) with TTL set to one (so therefore no one will rebroadcast our request) -* survey replies are sent unicast back to us (and intervening nodes will need to keep the route table that they have built up based on past packets) -* count the number of replies to this TTL 1 attempt. That is the number of nodes we can reach without any rebroadcasts -* repeat the study with a TTL of 2 and then 3. stop once the # of replies stops going up. -* it is important for any node to do listen before talk to prevent stomping on other rebroadcasters... -* For these little networks I bet a max TTL would never be higher than 3? +- send all broadcasts with a TTL +- periodically(?) do a survey to find the max TTL that is needed to fully cover the current network. +- to do a study first send a broadcast (maybe our current initial user announcement?) with TTL set to one (so therefore no one will rebroadcast our request) +- survey replies are sent unicast back to us (and intervening nodes will need to keep the route table that they have built up based on past packets) +- count the number of replies to this TTL 1 attempt. That is the number of nodes we can reach without any rebroadcasts +- repeat the study with a TTL of 2 and then 3. stop once the # of replies stops going up. +- it is important for any node to do listen before talk to prevent stomping on other rebroadcasters... +- For these little networks I bet a max TTL would never be higher than 3? ## approach 2 -* send a TTL1 broadcast, the replies let us build a list of the nodes (stored as a bitvector?) that we can see (and their rssis) -* we then broadcast out that bitvector (also TTL1) asking "can any of ya'll (even indirectly) see anyone else?" -* if a node can see someone I missed (and they are the best person to see that node), they reply (unidirectionally) with the missing nodes and their rssis (other nodes might sniff (and update their db) based on this reply but they don't have to) -* given that the max number of nodes in this mesh will be like 20 (for normal cases), I bet globally updating this db of "nodenums and who has the best rssi for packets from that node" would be useful -* once the global DB is shared, when a node wants to broadcast, it just sends out its broadcast . the first level receivers then make a decision "am I the best to rebroadcast to someone who likely missed this packet?" if so, rebroadcast +- send a TTL1 broadcast, the replies let us build a list of the nodes (stored as a bitvector?) that we can see (and their rssis) +- we then broadcast out that bitvector (also TTL1) asking "can any of ya'll (even indirectly) see anyone else?" +- if a node can see someone I missed (and they are the best person to see that node), they reply (unidirectionally) with the missing nodes and their rssis (other nodes might sniff (and update their db) based on this reply but they don't have to) +- given that the max number of nodes in this mesh will be like 20 (for normal cases), I bet globally updating this db of "nodenums and who has the best rssi for packets from that node" would be useful +- once the global DB is shared, when a node wants to broadcast, it just sends out its broadcast . the first level receivers then make a decision "am I the best to rebroadcast to someone who likely missed this packet?" if so, rebroadcast ## approach 3 -* when a node X wants to know other nodes positions, it broadcasts its position with want_replies=true. Then each of the nodes that received that request broadcast their replies (possibly by using special timeslots?) -* all nodes constantly update their local db based on replies they witnessed. -* after 10s (or whatever) if node Y notices that it didn't hear a reply from node Z (that Y has heard from recently ) to that initial request, that means Z never heard the request from X. Node Y will reply to X on Z's behalf. -* could this work for more than one hop? Is more than one hop needed? Could it work for sending messages (i.e. for a msg sent to Z with want-reply set). +- when a node X wants to know other nodes positions, it broadcasts its position with want_replies=true. Then each of the nodes that received that request broadcast their replies (possibly by using special timeslots?) +- all nodes constantly update their local db based on replies they witnessed. +- after 10s (or whatever) if node Y notices that it didn't hear a reply from node Z (that Y has heard from recently ) to that initial request, that means Z never heard the request from X. Node Y will reply to X on Z's behalf. +- could this work for more than one hop? Is more than one hop needed? Could it work for sending messages (i.e. for a msg sent to Z with want-reply set). ## approach 4 look into the literature for this idea specifically. -* don't view it as a mesh protocol as much as a "distributed db unification problem". When nodes talk to nearby nodes they work together -to update their nodedbs. Each nodedb would have a last change date and any new changes that only one node has would get passed to the -other node. This would nicely allow distant nodes to propogate their position to all other nodes (eventually). -* handle group messages the same way, there would be a table of messages and time of creation. -* when a node has a new position or message to send out, it does a broadcast. All the adjacent nodes update their db instantly (this handles 90% of messages I'll bet). -* Occasionally a node might broadcast saying "anyone have anything newer than time X?" If someone does, they send the diffs since that date. -* essentially everything in this variant becomes broadcasts of "request db updates for >time X - for _all_ or for a particular nodenum" and nodes sending (either due to request or because they changed state) "here's a set of db updates". Every node is constantly trying to -build the most recent version of reality, and if some nodes are too far, then nodes closer in will eventually forward their changes to the distributed db. -* construct non ambigious rules for who broadcasts to request db updates. ideally the algorithm should nicely realize node X can see most other nodes, so they should just listen to all those nodes and minimize the # of broadcasts. the distributed picture of nodes rssi could be useful here? -* possibly view the BLE protocol to the radio the same way - just a process of reconverging the node/msgdb database. +- don't view it as a mesh protocol as much as a "distributed db unification problem". When nodes talk to nearby nodes they work together + to update their nodedbs. Each nodedb would have a last change date and any new changes that only one node has would get passed to the + other node. This would nicely allow distant nodes to propogate their position to all other nodes (eventually). +- handle group messages the same way, there would be a table of messages and time of creation. +- when a node has a new position or message to send out, it does a broadcast. All the adjacent nodes update their db instantly (this handles 90% of messages I'll bet). +- Occasionally a node might broadcast saying "anyone have anything newer than time X?" If someone does, they send the diffs since that date. +- essentially everything in this variant becomes broadcasts of "request db updates for >time X - for _all_ or for a particular nodenum" and nodes sending (either due to request or because they changed state) "here's a set of db updates". Every node is constantly trying to + build the most recent version of reality, and if some nodes are too far, then nodes closer in will eventually forward their changes to the distributed db. +- construct non ambigious rules for who broadcasts to request db updates. ideally the algorithm should nicely realize node X can see most other nodes, so they should just listen to all those nodes and minimize the # of broadcasts. the distributed picture of nodes rssi could be useful here? +- possibly view the BLE protocol to the radio the same way - just a process of reconverging the node/msgdb database. + +# Old notes + +FIXME, merge into the above: + + +good description of batman protocol: https://www.open-mesh.org/projects/open-mesh/wiki/BATMANConcept + +interesting paper on lora mesh: https://portal.research.lu.se/portal/files/45735775/paper.pdf +It seems like DSR might be the algorithm used by RadioheadMesh. DSR is described in https://tools.ietf.org/html/rfc4728 +https://en.wikipedia.org/wiki/Dynamic_Source_Routing + +broadcast solution: +Use naive flooding at first (FIXME - do some math for a 20 node, 3 hop mesh. A single flood will require a max of 20 messages sent) +Then move to MPR later (http://www.olsr.org/docs/report_html/node28.html). Use altitude and location as heursitics in selecting the MPR set + +compare to db sync algorithm? + +what about never flooding gps broadcasts. instead only have them go one hop in the common case, but if any node X is looking at the position of Y on their gui, then send a unicast to Y asking for position update. Y replies. + +If Y were to die, at least the neighbor nodes of Y would have their last known position of Y. diff --git a/proto b/proto index fc4214e34..793d3e65c 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit fc4214e34dc90689b5efd4657bfdce63ca9add24 +Subproject commit 793d3e65ca66c3a0914e74a285a729429952a042 diff --git a/src/MeshRadio.cpp b/src/MeshRadio.cpp index 7bf2ce45d..a5d1d9d62 100644 --- a/src/MeshRadio.cpp +++ b/src/MeshRadio.cpp @@ -24,8 +24,7 @@ separated by 2.16 MHz with respect to the adjacent channels. Channel zero starts /// Sometimes while debugging it is useful to set this false, to disable rf95 accesses bool useHardware = true; -MeshRadio::MeshRadio(MemoryPool &_pool, PointerQueue &_rxDest) - : radioIf(_pool, _rxDest), sendPacketObserver(this, &MeshRadio::send) // , manager(radioIf) +MeshRadio::MeshRadio() : sendPacketObserver(this, &MeshRadio::send) // , manager(radioIf) { myNodeInfo.num_channels = NUM_CHANNELS; @@ -150,7 +149,7 @@ void MeshRadio::loop() DEBUG_MSG("ERROR! Bug! Tx packet took too long to send, forcing radio into rx mode\n"); radioIf.setModeRx(); if (radioIf.sendingPacket) { // There was probably a packet we were trying to send, free it - radioIf.pool.release(radioIf.sendingPacket); + packetPool.release(radioIf.sendingPacket); radioIf.sendingPacket = NULL; } recordCriticalError(ErrTxWatchdog); diff --git a/src/MeshRadio.h b/src/MeshRadio.h index 7e39e0c32..84c4bea33 100644 --- a/src/MeshRadio.h +++ b/src/MeshRadio.h @@ -76,7 +76,7 @@ class MeshRadio /** pool is the pool we will alloc our rx packets from * rxDest is where we will send any rx packets, it becomes receivers responsibility to return packet to the pool */ - MeshRadio(MemoryPool &pool, PointerQueue &rxDest); + MeshRadio(); bool init(); diff --git a/src/MeshService.cpp b/src/MeshService.cpp index 26b0d1f2e..ce7fa697b 100644 --- a/src/MeshService.cpp +++ b/src/MeshService.cpp @@ -44,15 +44,9 @@ FIXME in the initial proof of concept we just skip the entire want/deny flow and MeshService service; -// I think this is right, one packet for each of the three fifos + one packet being currently assembled for TX or RX -#define MAX_PACKETS \ - (MAX_RX_TOPHONE + MAX_RX_FROMRADIO + MAX_TX_QUEUE + \ - 2) // max number of packets which can be in flight (either queued from reception or queued for sending) +#include "Router.h" -#define MAX_RX_FROMRADIO \ - 4 // max number of packets destined to our queue, we dispatch packets quickly so it doesn't need to be big - -MeshService::MeshService() : toPhoneQueue(MAX_RX_TOPHONE), packetPool(MAX_PACKETS), fromRadioQueue(MAX_RX_FROMRADIO) +MeshService::MeshService() : toPhoneQueue(MAX_RX_TOPHONE) { // assert(MAX_RX_TOPHONE == 32); // FIXME, delete this, just checking my clever macro } @@ -62,6 +56,7 @@ void MeshService::init() nodeDB.init(); gpsObserver.observe(&gps); + packetReceivedObserver.observe(&router.notifyPacketReceived); // No need to call this here, our periodic task will fire quite soon // sendOwnerPeriod(); @@ -81,7 +76,7 @@ void MeshService::sendOurOwner(NodeNum dest, bool wantReplies) } /// handle a user packet that just arrived on the radio, return NULL if we should not process this packet at all -MeshPacket *MeshService::handleFromRadioUser(MeshPacket *mp) +const MeshPacket *MeshService::handleFromRadioUser(const MeshPacket *mp) { bool wasBroadcast = mp->to == NODENUM_BROADCAST; bool isCollision = mp->from == myNodeInfo.my_node_num; @@ -93,7 +88,6 @@ MeshPacket *MeshService::handleFromRadioUser(MeshPacket *mp) if (weWin) { DEBUG_MSG("NOTE! Received a nodenum collision and we are vetoing\n"); - releaseToPool(mp); // discard it mp = NULL; sendOurOwner(); // send our owner as a _broadcast_ because that other guy is mistakenly using our nodenum @@ -121,7 +115,7 @@ MeshPacket *MeshService::handleFromRadioUser(MeshPacket *mp) return mp; } -void MeshService::handleIncomingPosition(MeshPacket *mp) +void MeshService::handleIncomingPosition(const MeshPacket *mp) { if (mp->has_payload && mp->payload.has_position) { DEBUG_MSG("handled incoming position time=%u\n", mp->payload.position.time); @@ -140,12 +134,10 @@ void MeshService::handleIncomingPosition(MeshPacket *mp) } } -void MeshService::handleFromRadio(MeshPacket *mp) +int MeshService::handleFromRadio(const MeshPacket *mp) { powerFSM.trigger(EVENT_RECEIVED_PACKET); // Possibly keep the node from sleeping - mp->rx_time = gps.getValidTime(); // store the arrival timestamp for the phone - // If it is a position packet, perhaps set our clock (if we don't have a GPS of our own, otherwise wait for that to work) if (!gps.isConnected) handleIncomingPosition(mp); @@ -170,24 +162,17 @@ void MeshService::handleFromRadio(MeshPacket *mp) if (d) releaseToPool(d); } - assert(toPhoneQueue.enqueue(mp, 0)); // FIXME, instead of failing for full queue, delete the oldest mssages + + MeshPacket *copied = packetPool.allocCopy(*mp); + assert(toPhoneQueue.enqueue(copied, 0)); // FIXME, instead of failing for full queue, delete the oldest mssages if (mp->payload.want_response) sendNetworkPing(mp->from); } else { DEBUG_MSG("Not delivering vetoed User message\n"); } -} -void MeshService::handleFromRadio() -{ - MeshPacket *mp; - uint32_t oldFromNum = fromNum; - while ((mp = fromRadioQueue.dequeuePtr(0)) != NULL) { - handleFromRadio(mp); - } - if (oldFromNum != fromNum) // We don't want to generate extra notifies for multiple new packets - fromNumChanged.notifyObservers(fromNum); + return 0; } uint32_t sendOwnerCb() @@ -202,7 +187,10 @@ Periodic sendOwnerPeriod(sendOwnerCb); /// Do idle processing (mostly processing messages which have been queued from the radio) void MeshService::loop() { - handleFromRadio(); + if (oldFromNum != fromNum) { // We don't want to generate extra notifies for multiple new packets + fromNumChanged.notifyObservers(fromNum); + oldFromNum = true; + } // occasionally send our owner info sendOwnerPeriod.loop(); @@ -236,7 +224,9 @@ void MeshService::handleToRadio(std::string s) bool loopback = false; // if true send any packet the phone sends back itself (for testing) if (loopback) { - MeshPacket *mp = packetPool.allocCopy(r.variant.packet); + const MeshPacket *mp = &r.variant.packet; + // no need to copy anymore because handle from radio assumes it should _not_ delete + // packetPool.allocCopy(r.variant.packet); handleFromRadio(mp); // handleFromRadio will tell the phone a new packet arrived } diff --git a/src/MeshService.h b/src/MeshService.h index 235801211..d8349a55b 100644 --- a/src/MeshService.h +++ b/src/MeshService.h @@ -6,6 +6,7 @@ #include "MemoryPool.h" #include "MeshRadio.h" +#include "MeshTypes.h" #include "Observer.h" #include "PointerQueue.h" #include "mesh.pb.h" @@ -17,6 +18,8 @@ class MeshService { CallbackObserver gpsObserver = CallbackObserver(this, &MeshService::onGPSChanged); + CallbackObserver packetReceivedObserver = + CallbackObserver(this, &MeshService::handleFromRadio); /// received packets waiting for the phone to process them /// FIXME, change to a DropOldestQueue and keep a count of the number of dropped packets to ensure @@ -27,13 +30,10 @@ class MeshService /// The current nonce for the newest packet which has been queued for the phone uint32_t fromNum = 0; + /// Updated in loop() to detect when fromNum changes + uint32_t oldFromNum = 0; + public: - MemoryPool packetPool; - - /// Packets which have just arrived from the radio, ready to be processed by this service and possibly - /// forwarded to the phone. - PointerQueue fromRadioQueue; - /// Called when some new packets have arrived from one of the radios Observable fromNumChanged; @@ -90,18 +90,15 @@ class MeshService /// returns 0 to allow futher processing int onGPSChanged(void *arg); - /// handle all the packets that just arrived from the mesh radio - void handleFromRadio(); - - /// Handle a packet that just arrived from the radio. We will either eventually enqueue the message to the phone or return it - /// to the free pool - void handleFromRadio(MeshPacket *p); + /// Handle a packet that just arrived from the radio. This method does _not_ free the provided packet. If it needs + /// to keep the packet around it makes a copy + int handleFromRadio(const MeshPacket *p); /// handle a user packet that just arrived on the radio, return NULL if we should not process this packet at all - MeshPacket *handleFromRadioUser(MeshPacket *mp); + const MeshPacket *handleFromRadioUser(const MeshPacket *mp); /// look at inbound packets and if they contain a position with time, possibly set our clock - void handleIncomingPosition(MeshPacket *mp); + void handleIncomingPosition(const MeshPacket *mp); }; extern MeshService service; diff --git a/src/MeshTypes.h b/src/MeshTypes.h index ea31f6c19..13ead0463 100644 --- a/src/MeshTypes.h +++ b/src/MeshTypes.h @@ -2,6 +2,8 @@ // low level types +#include "MemoryPool.h" +#include "mesh.pb.h" #include typedef uint8_t NodeNum; @@ -10,4 +12,7 @@ typedef uint8_t NodeNum; #define ERRNO_OK 0 #define ERRNO_UNKNOWN 32 // pick something that doesn't conflict with RH_ROUTER_ERROR_UNABLE_TO_DELIVER -typedef int ErrorCode; \ No newline at end of file +typedef int ErrorCode; + +/// Alloc and free packets to our global, ISR safe pool +extern MemoryPool packetPool; \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 758a6a824..905c1c00a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -193,7 +193,8 @@ void axp192Init() #endif } -void getMacAddr(uint8_t *dmac) { +void getMacAddr(uint8_t *dmac) +{ #ifndef NO_ESP32 assert(esp_efuse_mac_get_default(dmac) == ESP_OK); #else @@ -203,7 +204,7 @@ void getMacAddr(uint8_t *dmac) { dmac[3] = 0xef; dmac[4] = 0x01; dmac[5] = 0x02; // FIXME, macaddr stuff needed for NRF52 -#endif +#endif } const char *getDeviceName() @@ -220,6 +221,8 @@ const char *getDeviceName() static MeshRadio *radio = NULL; +#include "Router.h" + void setup() { // Debug @@ -261,7 +264,7 @@ void setup() // Don't init display if we don't have one or we are waking headless due to a timer event if (wakeCause == ESP_SLEEP_WAKEUP_TIMER) ssd1306_found = false; // forget we even have the hardware -#endif +#endif // Initialize the screen first so we can show the logo while we start up everything else. if (ssd1306_found) @@ -278,7 +281,8 @@ void setup() #ifndef NO_ESP32 // MUST BE AFTER service.init, so we have our radio config settings (from nodedb init) - radio = new MeshRadio(service.packetPool, service.fromRadioQueue); + radio = new MeshRadio(); + router.addInterface(&radio->radioIf); #endif if (radio && !radio->init()) diff --git a/src/mesh.pb.c b/src/mesh.pb.c index e8c0faa28..b4e21ebde 100644 --- a/src/mesh.pb.c +++ b/src/mesh.pb.c @@ -15,6 +15,9 @@ PB_BIND(Data, Data, 2) PB_BIND(User, User, AUTO) +PB_BIND(RouteDiscovery, RouteDiscovery, AUTO) + + PB_BIND(SubPacket, SubPacket, 2) diff --git a/src/mesh.pb.h b/src/mesh.pb.h index 48b6ad9fe..dda10e710 100644 --- a/src/mesh.pb.h +++ b/src/mesh.pb.h @@ -32,6 +32,10 @@ typedef enum _ChannelSettings_ModemConfig { } ChannelSettings_ModemConfig; /* Struct definitions */ +typedef struct _RouteDiscovery { + pb_callback_t route; +} RouteDiscovery; + typedef struct _ChannelSettings { int32_t tx_power; ChannelSettings_ModemConfig modem_config; @@ -175,6 +179,7 @@ typedef struct _ToRadio { #define Position_init_default {0, 0, 0, 0, 0} #define Data_init_default {_Data_Type_MIN, {0, {0}}} #define User_init_default {"", "", "", {0}} +#define RouteDiscovery_init_default {{{NULL}, NULL}} #define SubPacket_init_default {false, Position_init_default, false, Data_init_default, false, User_init_default, 0} #define MeshPacket_init_default {0, 0, false, SubPacket_init_default, 0, 0, 0} #define ChannelSettings_init_default {0, _ChannelSettings_ModemConfig_MIN, {0}, ""} @@ -188,6 +193,7 @@ typedef struct _ToRadio { #define Position_init_zero {0, 0, 0, 0, 0} #define Data_init_zero {_Data_Type_MIN, {0, {0}}} #define User_init_zero {"", "", "", {0}} +#define RouteDiscovery_init_zero {{{NULL}, NULL}} #define SubPacket_init_zero {false, Position_init_zero, false, Data_init_zero, false, User_init_zero, 0} #define MeshPacket_init_zero {0, 0, false, SubPacket_init_zero, 0, 0, 0} #define ChannelSettings_init_zero {0, _ChannelSettings_ModemConfig_MIN, {0}, ""} @@ -200,6 +206,7 @@ typedef struct _ToRadio { #define ToRadio_init_zero {0, {MeshPacket_init_zero}} /* Field tags (for use in manual encoding/decoding) */ +#define RouteDiscovery_route_tag 2 #define ChannelSettings_tx_power_tag 1 #define ChannelSettings_modem_config_tag 3 #define ChannelSettings_psk_tag 4 @@ -289,6 +296,11 @@ X(a, STATIC, SINGULAR, FIXED_LENGTH_BYTES, macaddr, 4) #define User_CALLBACK NULL #define User_DEFAULT NULL +#define RouteDiscovery_FIELDLIST(X, a) \ +X(a, CALLBACK, REPEATED, INT32, route, 2) +#define RouteDiscovery_CALLBACK pb_default_field_callback +#define RouteDiscovery_DEFAULT NULL + #define SubPacket_FIELDLIST(X, a) \ X(a, STATIC, OPTIONAL, MESSAGE, position, 1) \ X(a, STATIC, OPTIONAL, MESSAGE, data, 3) \ @@ -401,6 +413,7 @@ X(a, STATIC, ONEOF, MESSAGE, (variant,packet,variant.packet), 1) extern const pb_msgdesc_t Position_msg; extern const pb_msgdesc_t Data_msg; extern const pb_msgdesc_t User_msg; +extern const pb_msgdesc_t RouteDiscovery_msg; extern const pb_msgdesc_t SubPacket_msg; extern const pb_msgdesc_t MeshPacket_msg; extern const pb_msgdesc_t ChannelSettings_msg; @@ -416,6 +429,7 @@ extern const pb_msgdesc_t ToRadio_msg; #define Position_fields &Position_msg #define Data_fields &Data_msg #define User_fields &User_msg +#define RouteDiscovery_fields &RouteDiscovery_msg #define SubPacket_fields &SubPacket_msg #define MeshPacket_fields &MeshPacket_msg #define ChannelSettings_fields &ChannelSettings_msg @@ -431,6 +445,7 @@ extern const pb_msgdesc_t ToRadio_msg; #define Position_size 46 #define Data_size 256 #define User_size 72 +/* RouteDiscovery_size depends on runtime parameters */ #define SubPacket_size 383 #define MeshPacket_size 426 #define ChannelSettings_size 44 diff --git a/src/rf95/CustomRF95.cpp b/src/rf95/CustomRF95.cpp index 6e158cf89..a2a7b17aa 100644 --- a/src/rf95/CustomRF95.cpp +++ b/src/rf95/CustomRF95.cpp @@ -11,10 +11,7 @@ #define MAX_RHPACKETLEN 251 static uint8_t radiobuf[MAX_RHPACKETLEN]; -CustomRF95::CustomRF95(MemoryPool &_pool, PointerQueue &_rxDest) - : RH_RF95(NSS_GPIO, RF95_IRQ_GPIO), RadioInterface(_pool, _rxDest), txQueue(MAX_TX_QUEUE) -{ -} +CustomRF95::CustomRF95() : RH_RF95(NSS_GPIO, RF95_IRQ_GPIO), txQueue(MAX_TX_QUEUE) {} bool CustomRF95::canSleep() { @@ -58,7 +55,7 @@ ErrorCode CustomRF95::send(MeshPacket *p) ErrorCode res = txQueue.enqueue(p, 0) ? ERRNO_OK : ERRNO_UNKNOWN; if (res != ERRNO_OK) // we weren't able to queue it, so we must drop it to prevent leaks - pool.release(p); + packetPool.release(p); return res; } @@ -76,7 +73,7 @@ void CustomRF95::handleInterrupt() if (sendingPacket) // Were we sending? { // We are done sending that packet, release it - pool.releaseFromISR(sendingPacket, &higherPriWoken); + packetPool.releaseFromISR(sendingPacket, &higherPriWoken); sendingPacket = NULL; // DEBUG_MSG("Done with send\n"); } @@ -94,7 +91,7 @@ void CustomRF95::handleInterrupt() // DEBUG_MSG("Received packet from mesh src=0x%x,dest=0x%x,id=%d,len=%d rxGood=%d,rxBad=%d,freqErr=%d,snr=%d\n", // srcaddr, destaddr, id, rxlen, rf95.rxGood(), rf95.rxBad(), freqerr, snr); - MeshPacket *mp = pool.allocZeroed(); + MeshPacket *mp = packetPool.allocZeroed(); SubPacket *p = &mp->payload; @@ -113,12 +110,12 @@ void CustomRF95::handleInterrupt() } if (!pb_decode_from_bytes(payload, payloadLen, SubPacket_fields, p)) { - pool.releaseFromISR(mp, &higherPriWoken); + packetPool.releaseFromISR(mp, &higherPriWoken); } else { // parsing was successful, queue for our recipient mp->has_payload = true; - assert(rxDest.enqueueFromISR(mp, &higherPriWoken)); // NOWAIT - fixme, if queue is full, delete older messages + deliverToReceiverISR(mp, &higherPriWoken); } clearRxBuf(); // This message accepted and cleared diff --git a/src/rf95/CustomRF95.h b/src/rf95/CustomRF95.h index 9f4e6b65c..65e49f057 100644 --- a/src/rf95/CustomRF95.h +++ b/src/rf95/CustomRF95.h @@ -19,7 +19,7 @@ class CustomRF95 : public RH_RF95, public RadioInterface /** pool is the pool we will alloc our rx packets from * rxDest is where we will send any rx packets, it becomes receivers responsibility to return packet to the pool */ - CustomRF95(MemoryPool &pool, PointerQueue &rxDest); + CustomRF95(); /** * Return true if we think the board can go to sleep (i.e. our tx queue is empty, we are not sending or receiving) diff --git a/src/rf95/MemoryPool.h b/src/rf95/MemoryPool.h index a0033d9d8..89c514c90 100644 --- a/src/rf95/MemoryPool.h +++ b/src/rf95/MemoryPool.h @@ -31,6 +31,7 @@ template class MemoryPool ~MemoryPool() { delete[] buf; } /// Return a queable object which has been prefilled with zeros. Panic if no buffer is available + /// Note: this method is safe to call from regular OR ISR code T *allocZeroed() { T *p = allocZeroed(0); @@ -40,7 +41,7 @@ template class MemoryPool } /// Return a queable object which has been prefilled with zeros - allow timeout to wait for available buffers (you probably - /// don't want this version) + /// don't want this version). T *allocZeroed(TickType_t maxWait) { T *p = dead.dequeuePtr(maxWait); @@ -65,7 +66,7 @@ template class MemoryPool { assert(dead.enqueue(p, 0)); assert(p >= buf && - (size_t) (p - buf) < + (size_t)(p - buf) < maxElements); // sanity check to make sure a programmer didn't free something that didn't come from this pool } @@ -74,7 +75,7 @@ template class MemoryPool { assert(dead.enqueueFromISR(p, higherPriWoken)); assert(p >= buf && - (size_t) (p - buf) < + (size_t)(p - buf) < maxElements); // sanity check to make sure a programmer didn't free something that didn't come from this pool } }; diff --git a/src/rf95/RadioInterface.cpp b/src/rf95/RadioInterface.cpp index 1406646b8..bd8bb1310 100644 --- a/src/rf95/RadioInterface.cpp +++ b/src/rf95/RadioInterface.cpp @@ -2,14 +2,21 @@ #include "NodeDB.h" #include "assert.h" #include "configuration.h" +#include #include #include -RadioInterface::RadioInterface(MemoryPool &_pool, PointerQueue &_rxDest) : pool(_pool), rxDest(_rxDest) {} +RadioInterface::RadioInterface() {} ErrorCode SimRadio::send(MeshPacket *p) { DEBUG_MSG("SimRadio.send\n"); - pool.release(p); + packetPool.release(p); return ERRNO_OK; +} + +void RadioInterface::deliverToReceiverISR(MeshPacket *p, BaseType_t *higherPriWoken) +{ + assert(rxDest); + assert(rxDest->enqueueFromISR(p, higherPriWoken)); // NOWAIT - fixme, if queue is full, delete older messages } \ No newline at end of file diff --git a/src/rf95/RadioInterface.h b/src/rf95/RadioInterface.h index 452b8841c..48f205851 100644 --- a/src/rf95/RadioInterface.h +++ b/src/rf95/RadioInterface.h @@ -16,43 +16,38 @@ class RadioInterface { friend class MeshRadio; // for debugging we let that class touch pool + PointerQueue *rxDest = NULL; protected: - MemoryPool &pool; - PointerQueue &rxDest; - MeshPacket *sendingPacket = NULL; // The packet we are currently sending + + /** + * Enqueue a received packet for the registered receiver + */ + void deliverToReceiverISR(MeshPacket *p, BaseType_t *higherPriWoken); + public: /** pool is the pool we will alloc our rx packets from * rxDest is where we will send any rx packets, it becomes receivers responsibility to return packet to the pool */ - RadioInterface(MemoryPool &pool, PointerQueue &rxDest); + RadioInterface(); /** - * Return true if we think the board can go to sleep (i.e. our tx queue is empty, we are not sending or receiving) - * - * This method must be used before putting the CPU into deep or light sleep. + * Set where to deliver received packets. This method should only be used by the Router class */ - virtual bool canSleep() { return true; } + void setReceiver(PointerQueue *_rxDest) { rxDest = _rxDest; } - /// Prepare hardware for sleep. Call this _only_ for deep sleep, not needed for light sleep. - /// return true for success - virtual bool sleep() { return true; } - - /// Send a packet (possibly by enquing in a private fifo). This routine will - /// later free() the packet to pool. This routine is not allowed to stall because it is called from - /// bluetooth comms code. If the txmit queue is empty it might return an error + /** + * Send a packet (possibly by enquing in a private fifo). This routine will + * later free() the packet to pool. This routine is not allowed to stall. + * If the txmit queue is full it might return an error + */ virtual ErrorCode send(MeshPacket *p) = 0; }; class SimRadio : public RadioInterface { public: - /** pool is the pool we will alloc our rx packets from - * rxDest is where we will send any rx packets, it becomes receivers responsibility to return packet to the pool - */ - SimRadio(MemoryPool &_pool, PointerQueue &_rxDest) : RadioInterface(_pool, _rxDest) {} - virtual ErrorCode send(MeshPacket *p); // methods from radiohead diff --git a/src/rf95/Router.cpp b/src/rf95/Router.cpp new file mode 100644 index 000000000..715ab712b --- /dev/null +++ b/src/rf95/Router.cpp @@ -0,0 +1,72 @@ +#include "Router.h" +#include "configuration.h" +#include "mesh-pb-constants.h" + +/** + * Router todo + * + * Implement basic interface and use it elsewhere in app + * Add naive flooding mixin (& drop duplicate rx broadcasts), add tools for sending broadcasts with incrementing sequence #s + * Add an optional adjacent node only 'send with ack' mixin. If we timeout waiting for the ack, call handleAckTimeout(packet) + * Add DSR mixin + * + **/ + +#define MAX_RX_FROMRADIO \ + 4 // max number of packets destined to our queue, we dispatch packets quickly so it doesn't need to be big + +// I think this is right, one packet for each of the three fifos + one packet being currently assembled for TX or RX +#define MAX_PACKETS \ + (MAX_RX_TOPHONE + MAX_RX_FROMRADIO + MAX_TX_QUEUE + \ + 2) // max number of packets which can be in flight (either queued from reception or queued for sending) + +MemoryPool packetPool(MAX_PACKETS); + +Router router; + +/** + * Constructor + * + * Currently we only allow one interface, that may change in the future + */ +Router::Router() : fromRadioQueue(MAX_RX_FROMRADIO) {} + +/** + * do idle processing + * Mostly looking in our incoming rxPacket queue and calling handleReceived. + */ +void Router::loop() +{ + MeshPacket *mp; + while ((mp = fromRadioQueue.dequeuePtr(0)) != NULL) { + handleReceived(mp); + } +} + +/** + * Send a packet on a suitable interface. This routine will + * later free() the packet to pool. This routine is not allowed to stall. + * If the txmit queue is full it might return an error + */ +ErrorCode Router::send(MeshPacket *p) +{ + assert(iface); + return iface->send(p); +} + +#include "GPS.h" + +/** + * Handle any packet that is received by an interface on this node. + * Note: some packets may merely being passed through this node and will be forwarded elsewhere. + */ +void Router::handleReceived(MeshPacket *p) +{ + // FIXME, this class shouldn't EVER need to know about the GPS, move getValidTime() into a non gps dependent function + // Also, we should set the time from the ISR and it should have msec level resolution + p->rx_time = gps.getValidTime(); // store the arrival timestamp for the phone + + DEBUG_MSG("Notifying observers of received packet\n"); + notifyPacketReceived.notifyObservers(p); + packetPool.release(p); +} \ No newline at end of file diff --git a/src/rf95/Router.h b/src/rf95/Router.h new file mode 100644 index 000000000..00f346666 --- /dev/null +++ b/src/rf95/Router.h @@ -0,0 +1,70 @@ +#pragma once + +#include "MemoryPool.h" +#include "MeshTypes.h" +#include "Observer.h" +#include "PointerQueue.h" +#include "RadioInterface.h" +#include "mesh.pb.h" +#include + + + +/** + * A mesh aware router that supports multiple interfaces. + */ +class Router +{ + private: + RadioInterface *iface; + + /// Packets which have just arrived from the radio, ready to be processed by this service and possibly + /// forwarded to the phone. + PointerQueue fromRadioQueue; + + public: + /// Local services that want to see _every_ packet this node receives can observe this. + /// Observers should always return 0 and _copy_ any packets they want to keep for use later (this packet will be getting + /// freed) + Observable notifyPacketReceived; + + /** + * Constructor + * + */ + Router(); + + /** + * Currently we only allow one interface, that may change in the future + */ + void addInterface(RadioInterface *_iface) + { + iface = _iface; + iface->setReceiver(&fromRadioQueue); + } + + /** + * do idle processing + * Mostly looking in our incoming rxPacket queue and calling handleReceived. + */ + void loop(); + + /** + * Send a packet on a suitable interface. This routine will + * later free() the packet to pool. This routine is not allowed to stall. + * If the txmit queue is full it might return an error + */ + virtual ErrorCode send(MeshPacket *p); + + private: + /** + * Called from loop() + * Handle any packet that is received by an interface on this node. + * Note: some packets may merely being passed through this node and will be forwarded elsewhere. + * + * Note: this method will free the provided packet + */ + void handleReceived(MeshPacket *p); +}; + +extern Router router; \ No newline at end of file