diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 1f68c6f2a0..d9af32d749 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -450,6 +450,23 @@ bool MyMesh::allowPacketForward(const mesh::Packet *packet) { return true; } +bool MyMesh::isNextHopNeighbor(const mesh::Packet* packet) { +#if MAX_NEIGHBOURS + // TODO: check prefs to see if fuzzy pathing is enabled + // TODO: only consider neighbors that have been heard in a configurable amount of time + // TODO: make min SNR configurable + const int8_t min_snr = 0; // *4.f = actual snr value + for (int i = 0; i < MAX_NEIGHBOURS; ++i) { + auto neighbor = &neighbours[i]; + if (neighbor->heard_timestamp > 0 && neighbor->snr >= min_snr && neighbor->id.isHashMatch(packet->path, packet->getPathHashSize())) { + return true; + } + } +#endif + +return false; +} + const char *MyMesh::getLogDateTime() { static char tmp[32]; uint32_t now = getRTCClock()->getCurrentTime(); @@ -480,7 +497,7 @@ void MyMesh::logRx(mesh::Packet *pkt, int len, float score) { if (f) { f.print(getLogDateTime()); f.printf(": RX, len=%d (type=%d, route=%s, payload_len=%d) SNR=%d RSSI=%d score=%d", len, - pkt->getPayloadType(), pkt->isRouteDirect() ? "D" : "F", pkt->payload_len, + pkt->getPayloadType(), pkt->isRouteDirect() ? (pkt->isRouteFuzzy() ? "Z" : "D") : "F", pkt->payload_len, (int)_radio->getLastSNR(), (int)_radio->getLastRSSI(), (int)(score * 1000)); if (pkt->getPayloadType() == PAYLOAD_TYPE_PATH || pkt->getPayloadType() == PAYLOAD_TYPE_REQ || @@ -506,7 +523,7 @@ void MyMesh::logTx(mesh::Packet *pkt, int len) { if (f) { f.print(getLogDateTime()); f.printf(": TX, len=%d (type=%d, route=%s, payload_len=%d)", len, pkt->getPayloadType(), - pkt->isRouteDirect() ? "D" : "F", pkt->payload_len); + pkt->isRouteDirect() ? (pkt->isRouteFuzzy() ? "Z" : "D") : "F", pkt->payload_len); if (pkt->getPayloadType() == PAYLOAD_TYPE_PATH || pkt->getPayloadType() == PAYLOAD_TYPE_REQ || pkt->getPayloadType() == PAYLOAD_TYPE_RESPONSE || pkt->getPayloadType() == PAYLOAD_TYPE_TXT_MSG) { @@ -525,7 +542,7 @@ void MyMesh::logTxFail(mesh::Packet *pkt, int len) { if (f) { f.print(getLogDateTime()); f.printf(": TX FAIL!, len=%d (type=%d, route=%s, payload_len=%d)\n", len, pkt->getPayloadType(), - pkt->isRouteDirect() ? "D" : "F", pkt->payload_len); + pkt->isRouteDirect() ? (pkt->isRouteFuzzy() ? "Z" : "D") : "F", pkt->payload_len); f.close(); } } diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index 8ed0317e69..c75b80ec91 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -136,6 +136,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { } bool allowPacketForward(const mesh::Packet* packet) override; + bool isNextHopNeighbor(const mesh::Packet* packet) override; const char* getLogDateTime() override; void logRxRaw(float snr, float rssi, const uint8_t raw[], int len) override; diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp index 2fb80be24c..c5f3ba645a 100644 --- a/examples/simple_room_server/MyMesh.cpp +++ b/examples/simple_room_server/MyMesh.cpp @@ -214,7 +214,7 @@ void MyMesh::logRx(mesh::Packet *pkt, int len, float score) { if (f) { f.print(getLogDateTime()); f.printf(": RX, len=%d (type=%d, route=%s, payload_len=%d) SNR=%d RSSI=%d score=%d", len, - pkt->getPayloadType(), pkt->isRouteDirect() ? "D" : "F", pkt->payload_len, + pkt->getPayloadType(), pkt->isRouteDirect() ? (pkt->isRouteFuzzy() ? "Z" : "D") : "F", pkt->payload_len, (int)_radio->getLastSNR(), (int)_radio->getLastRSSI(), (int)(score * 1000)); if (pkt->getPayloadType() == PAYLOAD_TYPE_PATH || pkt->getPayloadType() == PAYLOAD_TYPE_REQ || @@ -233,7 +233,7 @@ void MyMesh::logTx(mesh::Packet *pkt, int len) { if (f) { f.print(getLogDateTime()); f.printf(": TX, len=%d (type=%d, route=%s, payload_len=%d)", len, pkt->getPayloadType(), - pkt->isRouteDirect() ? "D" : "F", pkt->payload_len); + pkt->isRouteDirect() ? (pkt->isRouteFuzzy() ? "Z" : "D") : "F", pkt->payload_len); if (pkt->getPayloadType() == PAYLOAD_TYPE_PATH || pkt->getPayloadType() == PAYLOAD_TYPE_REQ || pkt->getPayloadType() == PAYLOAD_TYPE_RESPONSE || pkt->getPayloadType() == PAYLOAD_TYPE_TXT_MSG) { @@ -251,7 +251,7 @@ void MyMesh::logTxFail(mesh::Packet *pkt, int len) { if (f) { f.print(getLogDateTime()); f.printf(": TX FAIL!, len=%d (type=%d, route=%s, payload_len=%d)\n", len, pkt->getPayloadType(), - pkt->isRouteDirect() ? "D" : "F", pkt->payload_len); + pkt->isRouteDirect() ? (pkt->isRouteFuzzy() ? "Z" : "D") : "F", pkt->payload_len); f.close(); } } diff --git a/src/Dispatcher.cpp b/src/Dispatcher.cpp index 9d7a11131d..408e48954b 100644 --- a/src/Dispatcher.cpp +++ b/src/Dispatcher.cpp @@ -219,7 +219,7 @@ void Dispatcher::checkRecv() { #if MESH_PACKET_LOGGING Serial.print(getLogDateTime()); Serial.printf(": RX, len=%d (type=%d, route=%s, payload_len=%d) SNR=%d RSSI=%d score=%d time=%d", - pkt->getRawLength(), pkt->getPayloadType(), pkt->isRouteDirect() ? "D" : "F", pkt->payload_len, + pkt->getRawLength(), pkt->getPayloadType(), pkt->isRouteDirect() ? (pkt->isRouteFuzzy() ? "Z" : "D") : "F", pkt->payload_len, (int)pkt->getSNR(), (int)_radio->getLastRSSI(), (int)(score*1000), air_time); static uint8_t packet_hash[MAX_HASH_SIZE]; @@ -340,7 +340,7 @@ void Dispatcher::checkSend() { #if MESH_PACKET_LOGGING Serial.print(getLogDateTime()); Serial.printf(": TX, len=%d (type=%d, route=%s, payload_len=%d)", - len, outbound->getPayloadType(), outbound->isRouteDirect() ? "D" : "F", outbound->payload_len); + len, outbound->getPayloadType(), outbound->isRouteDirect() ? (outbound->isRouteFuzzy() ? "Z" : "D") : "F", outbound->payload_len); if (outbound->getPayloadType() == PAYLOAD_TYPE_PATH || outbound->getPayloadType() == PAYLOAD_TYPE_REQ || outbound->getPayloadType() == PAYLOAD_TYPE_RESPONSE || outbound->getPayloadType() == PAYLOAD_TYPE_TXT_MSG) { Serial.printf(" [%02X -> %02X]\n", (uint32_t)outbound->payload[1], (uint32_t)outbound->payload[0]); diff --git a/src/Mesh.cpp b/src/Mesh.cpp index 7252974a92..1bd170f934 100644 --- a/src/Mesh.cpp +++ b/src/Mesh.cpp @@ -14,6 +14,9 @@ void Mesh::loop() { bool Mesh::allowPacketForward(const mesh::Packet* packet) { return false; // by default, Transport NOT enabled } +bool Mesh::isNextHopNeighbor(const mesh::Packet* packet) { + return false; // by default, neighbor tracking not enabled +} uint32_t Mesh::getRetransmitDelay(const mesh::Packet* packet) { uint32_t t = (_radio->getEstAirtimeFor(packet->getRawLength()) * 52 / 50) / 2; @@ -72,7 +75,7 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) { return ACTION_RELEASE; } - if (pkt->isRouteDirect() && pkt->getPathHashCount() > 0) { + if (pkt->isRouteDirect() && pkt->getPathHashCount() > 0 && !pkt->isPathHashOnlyFuzzy()) { // check for 'early received' ACK if (pkt->getPayloadType() == PAYLOAD_TYPE_ACK) { int i = 0; @@ -83,25 +86,35 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) { } } - if (self_id.isHashMatch(pkt->path, pkt->getPathHashSize()) && allowPacketForward(pkt)) { - if (pkt->getPayloadType() == PAYLOAD_TYPE_MULTIPART) { - return forwardMultipartDirect(pkt); - } else if (pkt->getPayloadType() == PAYLOAD_TYPE_ACK) { - if (!_tables->hasSeen(pkt)) { // don't retransmit! - removeSelfFromPath(pkt); - routeDirectRecvAcks(pkt, 0); + if (allowPacketForward(pkt)) { + bool is_next_hop = self_id.isHashMatch(pkt->path, pkt->getPathHashSize()); + bool is_fuzzy_hop = !is_next_hop && pkt->isRouteFuzzy() && isNextHopNeighbor(pkt); + if (is_next_hop || is_fuzzy_hop) { + if (pkt->getPayloadType() == PAYLOAD_TYPE_MULTIPART) { + return forwardMultipartDirect(pkt); + } else if (pkt->getPayloadType() == PAYLOAD_TYPE_ACK) { + if (!_tables->hasSeen(pkt)) { // don't retransmit! + removeSelfFromPath(pkt); + routeDirectRecvAcks(pkt, 0); + } + return ACTION_RELEASE; } - return ACTION_RELEASE; - } - if (!_tables->hasSeen(pkt)) { - removeSelfFromPath(pkt); + if (!_tables->hasSeen(pkt)) { + removeSelfFromPath(pkt); + + uint32_t d = getDirectRetransmitDelay(pkt); - uint32_t d = getDirectRetransmitDelay(pkt); - return ACTION_RETRANSMIT_DELAYED(0, d); // Routed traffic is HIGHEST priority + if (is_next_hop) { + return ACTION_RETRANSMIT_DELAYED(0, d); // Routed traffic is HIGHEST priority + } + else { + return ACTION_RETRANSMIT_DELAYED(2, d); // Fuzzy traffic is high-ish priority + } + } } + return ACTION_RELEASE; // this node is NOT the next hop (OR this packet has already been forwarded), so discard. } - return ACTION_RELEASE; // this node is NOT the next hop (OR this packet has already been forwarded), so discard. } if (pkt->isRouteFlood() && filterRecvFloodPacket(pkt)) return ACTION_RELEASE; diff --git a/src/Mesh.h b/src/Mesh.h index d53d6d25fa..532daf224e 100644 --- a/src/Mesh.h +++ b/src/Mesh.h @@ -55,6 +55,11 @@ class Mesh : public Dispatcher { */ virtual bool allowPacketForward(const Packet* packet); + /** + * \brief Check whether this packet's next hop is a neighbor of ours. + */ + virtual bool isNextHopNeighbor(const Packet* packet); + /** * \returns number of milliseconds delay to apply to retransmitting the given packet. */ diff --git a/src/MeshCore.h b/src/MeshCore.h index b4c57faf32..005d96e134 100644 --- a/src/MeshCore.h +++ b/src/MeshCore.h @@ -21,6 +21,10 @@ #define MAX_PATH_SIZE 64 #define MAX_TRANS_UNIT 255 +// Denotes that everything after this prefix can be used in a "fuzzy" repeat. +// Repeated to match the path length (so 2-byte paths would use two of these, 3-byte would use 3). +#define FUZZY_PATH_PREFIX 0xFF + #if MESH_DEBUG && ARDUINO #include #define MESH_DEBUG_PRINT(F, ...) Serial.printf("DEBUG: " F, ##__VA_ARGS__) diff --git a/src/Packet.cpp b/src/Packet.cpp index aad3e2f48e..59972fbd2b 100644 --- a/src/Packet.cpp +++ b/src/Packet.cpp @@ -49,6 +49,35 @@ void Packet::calculatePacketHash(uint8_t* hash) const { sha.finalize(hash, MAX_HASH_SIZE); } +bool Packet::isRouteFuzzy() const { + // Only direct routes make sense for fuzzy use + if (!isRouteDirect()) { + return false; + } + + // Trace is a weird situation to handle, not supported + if (getPayloadType() == PAYLOAD_TYPE_TRACE) { + return false; + } + + if (!isValidPathLen(path_len)) { + return false; + } + + uint8_t hash_size = getPathHashSize(); + uint8_t hash_count = getPathHashCount(); + + uint8_t fuzzy_prefix[3]; // TODO: is there a constant somewhere for the max # of prefix bytes? + memset(fuzzy_prefix, FUZZY_PATH_PREFIX, 3); + + if (memcmp(fuzzy_prefix, &path[hash_size * (hash_count - 1)], hash_size) == 0) + { + return true; + } + + return false; +} + uint8_t Packet::writeTo(uint8_t dest[]) const { uint8_t i = 0; dest[i++] = header; diff --git a/src/Packet.h b/src/Packet.h index 0886a06c4e..23356be57b 100644 --- a/src/Packet.h +++ b/src/Packet.h @@ -63,6 +63,7 @@ class Packet { bool isRouteFlood() const { return getRouteType() == ROUTE_TYPE_FLOOD || getRouteType() == ROUTE_TYPE_TRANSPORT_FLOOD; } bool isRouteDirect() const { return getRouteType() == ROUTE_TYPE_DIRECT || getRouteType() == ROUTE_TYPE_TRANSPORT_DIRECT; } + bool isRouteFuzzy() const; bool hasTransportCodes() const { return getRouteType() == ROUTE_TYPE_TRANSPORT_FLOOD || getRouteType() == ROUTE_TYPE_TRANSPORT_DIRECT; } @@ -79,6 +80,7 @@ class Packet { uint8_t getPathHashSize() const { return (path_len >> 6) + 1; } uint8_t getPathHashCount() const { return path_len & 63; } uint8_t getPathByteLen() const { return getPathHashCount() * getPathHashSize(); } + bool isPathHashOnlyFuzzy() const { return getPathHashCount() == 1 && isRouteFuzzy(); } void setPathHashCount(uint8_t n) { path_len &= ~63; path_len |= n; } void setPathHashSizeAndCount(uint8_t sz, uint8_t n) { path_len = ((sz - 1) << 6) | (n & 63); }