Merge remote-tracking branch 'root/master' into nrf52

# Conflicts:
#	docs/software/nrf52-TODO.md
#	src/mesh/RadioLibInterface.cpp
#	src/mesh/mesh.pb.h
This commit is contained in:
geeksville
2020-05-22 11:12:22 -07:00
27 changed files with 729 additions and 162 deletions

View File

@@ -153,6 +153,13 @@ void PowerFSM_setup()
powerFSM.add_transition(&stateDARK, &stateON, EVENT_PRESS, NULL, "Press");
powerFSM.add_transition(&stateON, &stateON, EVENT_PRESS, screenPress, "Press"); // reenter On to restart our timers
// Handle critically low power battery by forcing deep sleep
powerFSM.add_transition(&stateBOOT, &stateSDS, EVENT_LOW_BATTERY, NULL, "LowBat");
powerFSM.add_transition(&stateLS, &stateSDS, EVENT_LOW_BATTERY, NULL, "LowBat");
powerFSM.add_transition(&stateNB, &stateSDS, EVENT_LOW_BATTERY, NULL, "LowBat");
powerFSM.add_transition(&stateDARK, &stateSDS, EVENT_LOW_BATTERY, NULL, "LowBat");
powerFSM.add_transition(&stateON, &stateSDS, EVENT_LOW_BATTERY, NULL, "LowBat");
powerFSM.add_transition(&stateDARK, &stateON, EVENT_BLUETOOTH_PAIR, NULL, "Bluetooth pairing");
powerFSM.add_transition(&stateON, &stateON, EVENT_BLUETOOTH_PAIR, NULL, "Bluetooth pairing");

View File

@@ -13,6 +13,7 @@
#define EVENT_BLUETOOTH_PAIR 7
#define EVENT_NODEDB_UPDATED 8 // NodeDB has a big enough change that we think you should turn on the screen
#define EVENT_CONTACT_FROM_PHONE 9 // the phone just talked to us over bluetooth
#define EVENT_LOW_BATTERY 10 // Battery is critically low, go to sleep
extern Fsm powerFSM;

View File

@@ -119,16 +119,8 @@ void axp192Init()
DEBUG_MSG("DCDC3: %s\n", axp.isDCDC3Enable() ? "ENABLE" : "DISABLE");
DEBUG_MSG("Exten: %s\n", axp.isExtenEnable() ? "ENABLE" : "DISABLE");
axp.setChargeControlCur(AXP1XX_CHARGE_CUR_1320MA); // actual limit (in HW) on the tbeam is 450mA
#if 0
// cribbing from https://github.com/m5stack/M5StickC/blob/master/src/AXP192.cpp to fix charger to be more like 300ms.
// I finally found an english datasheet. Will look at this later - but suffice it to say the default code from TTGO has 'issues'
axp.adc1Enable(0xff, 1); // turn on all adcs
uint8_t val = 0xc2;
axp._writeByte(0x33, 1, &val); // Bat charge voltage to 4.2, Current 280mA
val = 0b11110010;
// Set ADC sample rate to 200hz
// axp._writeByte(0x84, 1, &val);
// Not connected
//val = 0xfc;
@@ -191,6 +183,8 @@ uint32_t axpDebugRead()
Periodic axpDebugOutput(axpDebugRead);
#endif
#define MIN_BAT_MILLIVOLTS 3690 // millivolts. 10% per https://blog.ampow.com/lipo-voltage-chart/
/// loop code specific to ESP32 targets
void esp32Loop()
{
@@ -231,5 +225,10 @@ void esp32Loop()
readPowerStatus();
axp.clearIRQ();
}
if (powerStatus.haveBattery && !powerStatus.usb &&
axp.getBattVoltage() < MIN_BAT_MILLIVOLTS) // If we have a battery at all and it is less than 10% full, force deep sleep
powerFSM.trigger(EVENT_LOW_BATTERY);
#endif // T_BEAM_V10
}

View File

@@ -87,7 +87,7 @@ void UBloxGPS::doTask()
// Hmmm my fix type reading returns zeros for fix, which doesn't seem correct, because it is still sptting out positions
// turn off for now
// fixtype = ublox.getFixType();
DEBUG_MSG("fix type %d\n", fixtype);
// DEBUG_MSG("fix type %d\n", fixtype);
// DEBUG_MSG("sec %d\n", ublox.getSecond());
// DEBUG_MSG("lat %d\n", ublox.getLatitude());

View File

@@ -33,7 +33,7 @@
#include "error.h"
#include "power.h"
// #include "rom/rtc.h"
#include "FloodingRouter.h"
#include "ReliableRouter.h"
#include "main.h"
#include "screen.h"
#include "sleep.h"
@@ -53,7 +53,7 @@ meshtastic::PowerStatus powerStatus;
bool ssd1306_found;
bool axp192_found;
FloodingRouter realRouter;
ReliableRouter realRouter;
Router &router = realRouter; // Users of router don't care what sort of subclass implements that API
// -----------------------------------------------------------------------------

80
src/mesh/DSRRouter.cpp Normal file
View File

@@ -0,0 +1,80 @@
#include "DSRRouter.h"
#include "configuration.h"
/* when we receive any packet
- sniff and update tables (especially useful to find adjacent nodes). Update user, network and position info.
- if we need to route() that packet, resend it to the next_hop based on our nodedb.
- if it is broadcast or destined for our node, deliver locally
- handle routereply/routeerror/routediscovery messages as described below
- then free it
routeDiscovery
- if we've already passed through us (or is from us), then it ignore it
- use the nodes already mentioned in the request to update our routing table
- if they were looking for us, send back a routereply
- if max_hops is zero and they weren't looking for us, drop (FIXME, send back error - I think not though?)
- if we receive a discovery packet, we use it to populate next_hop (if needed) towards the requester (after decrementing max_hops)
- if we receive a discovery packet, and we have a next_hop in our nodedb for that destination we send a (reliable) we send a route
reply towards the requester
when sending any reliable packet
- if timeout doing retries, send a routeError (nak) message back towards the original requester. all nodes eavesdrop on that
packet and update their route caches.
when we receive a routereply packet
- update next_hop on the node, if the new reply needs fewer hops than the existing one (we prefer shorter paths). fixme, someday
use a better heuristic
when we receive a routeError packet
- delete the route for that failed recipient, restartRouteDiscovery()
- if we receive routeerror in response to a discovery,
- fixme, eventually keep caches of possible other routes.
*/
void DSRRouter::sniffReceived(const MeshPacket *p)
{
// FIXME, update nodedb
// Handle route discovery packets (will be a broadcast message)
if (p->decoded.which_payload == SubPacket_request_tag) {
// FIXME - always start request with the senders nodenum
if (weAreInRoute(p->decoded.request)) {
DEBUG_MSG("Ignoring a route request that contains us\n");
} else {
updateRoutes(p->decoded.request, false); // Update our routing tables based on the route that came in so far on this request
if (p->decoded.dest == getNodeNum()) {
// They were looking for us, send back a route reply (the sender address will be first in the list)
sendRouteReply(p->decoded.request);
} else {
// They were looking for someone else, forward it along (as a zero hop broadcast)
NodeNum nextHop = getNextHop(p->decoded.dest);
if (nextHop) {
// in our route cache, reply to the requester (the sender address will be first in the list)
sendRouteReply(p->decoded.request, nextHop);
} else {
// Not in our route cache, rebroadcast on their behalf (after adding ourselves to the request route)
resendRouteRequest(p);
}
}
}
}
// Handle regular packets
if (p->to == getNodeNum()) { // Destined for us (at least for this hop)
// We need to route this packet
if (p->decoded.dest != p->to) {
// FIXME
}
}
return ReliableRouter::sniffReceived(p);
}

39
src/mesh/DSRRouter.h Normal file
View File

@@ -0,0 +1,39 @@
#include "ReliableRouter.h"
class DSRRouter : public ReliableRouter
{
protected:
/**
* Every (non duplicate) packet this node receives will be passed through this method. This allows subclasses to
* update routing tables etc... based on what we overhear (even for messages not destined to our node)
*/
virtual void sniffReceived(const MeshPacket *p);
private:
/**
* Does our node appear in the specified route
*/
bool weAreInRoute(const RouteDiscovery &route);
/**
* Given a DSR route, use that route to update our DB of possible routes
**/
void updateRoutes(const RouteDiscovery &route, bool reverse);
/**
* send back a route reply (the sender address will be first in the list)
*/
void sendRouteReply(const RouteDiscovery &route, NodeNum toAppend = 0);
/**
* Given a nodenum return the next node we should forward to if we want to reach that node.
*
* @return 0 if no route found
*/
NodeNum getNextHop(NodeNum dest);
/** Not in our route cache, rebroadcast on their behalf (after adding ourselves to the request route)
*/
void resendRouteRequest(const MeshPacket *p);
};

View File

@@ -2,9 +2,7 @@
#include "configuration.h"
#include "mesh-pb-constants.h"
static bool supportFlooding = true; // Sometimes to simplify debugging we want jusT simple broadcast only
FloodingRouter::FloodingRouter() : toResend(MAX_NUM_NODES) {}
FloodingRouter::FloodingRouter() {}
/**
* Send a packet on a suitable interface. This routine will
@@ -13,9 +11,8 @@ FloodingRouter::FloodingRouter() : toResend(MAX_NUM_NODES) {}
*/
ErrorCode FloodingRouter::send(MeshPacket *p)
{
// We update our table of recent broadcasts, even for messages we send
if (supportFlooding)
wasSeenRecently(p);
// Add any messages _we_ send to the seen message list (so we will ignore all retransmissions we see)
wasSeenRecently(p); // FIXME, move this to a sniffSent method
return Router::send(p);
}
@@ -29,28 +26,30 @@ ErrorCode FloodingRouter::send(MeshPacket *p)
*/
void FloodingRouter::handleReceived(MeshPacket *p)
{
if (supportFlooding) {
if (wasSeenRecently(p)) {
DEBUG_MSG("Ignoring incoming floodmsg, because we've already seen it\n");
packetPool.release(p);
} else {
if (p->to == NODENUM_BROADCAST) {
if (p->id != 0) {
MeshPacket *tosend = packetPool.allocCopy(*p); // keep a copy because we will be sending it
if (wasSeenRecently(p)) {
DEBUG_MSG("Ignoring incoming msg, because we've already seen it\n");
packetPool.release(p);
} else {
// If a broadcast, possibly _also_ send copies out into the mesh.
// (FIXME, do something smarter than naive flooding here)
if (p->to == NODENUM_BROADCAST && p->hop_limit > 0) {
if (p->id != 0) {
MeshPacket *tosend = packetPool.allocCopy(*p); // keep a copy because we will be sending it
DEBUG_MSG("Rebroadcasting received floodmsg to neighbors, fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id);
// Note: we are careful to resend using the original senders node id
// We are careful not to call our hooked version of send() - because we don't want to check this again
Router::send(tosend);
tosend->hop_limit--; // bump down the hop count
} else {
DEBUG_MSG("Ignoring a simple (0 hop) broadcast\n");
}
DEBUG_MSG("Rebroadcasting received floodmsg to neighbors, fr=0x%x,to=0x%x,id=%d,hop_limit=%d\n", p->from, p->to,
p->id, tosend->hop_limit);
// Note: we are careful to resend using the original senders node id
// We are careful not to call our hooked version of send() - because we don't want to check this again
Router::send(tosend);
} else {
DEBUG_MSG("Ignoring a simple (0 id) broadcast\n");
}
// handle the packet as normal
Router::handleReceived(p);
}
} else
// handle the packet as normal
Router::handleReceived(p);
}
}

View File

@@ -27,14 +27,9 @@
Any entries in recentBroadcasts that are older than X seconds (longer than the
max time a flood can take) will be discarded.
*/
class FloodingRouter : public Router, private PacketHistory
class FloodingRouter : public Router, protected PacketHistory
{
private:
/**
* Packets we've received that we need to resend after a short delay
*/
PointerQueue<MeshPacket> toResend;
public:
/**
* Constructor

View File

@@ -46,8 +46,6 @@ MeshService service;
#include "Router.h"
#define NUM_PACKET_ID 255 // 0 is consider invalid
static uint32_t sendOwnerCb()
{
service.sendOurOwner();
@@ -57,23 +55,6 @@ static uint32_t sendOwnerCb()
static Periodic sendOwnerPeriod(sendOwnerCb);
/// Generate a unique packet id
// FIXME, move this someplace better
PacketId generatePacketId()
{
static uint32_t i; // Note: trying to keep this in noinit didn't help for working across reboots
static bool didInit = false;
if (!didInit) {
didInit = true;
i = random(0, NUM_PACKET_ID +
1); // pick a random initial sequence number at boot (to prevent repeated reboots always starting at 0)
}
i++;
return (i % NUM_PACKET_ID) + 1; // return number between 1 and 255
}
MeshService::MeshService() : toPhoneQueue(MAX_RX_TOPHONE)
{
// assert(MAX_RX_TOPHONE == 32); // FIXME, delete this, just checking my clever macro
@@ -90,7 +71,7 @@ void MeshService::init()
void MeshService::sendOurOwner(NodeNum dest, bool wantReplies)
{
MeshPacket *p = allocForSending();
MeshPacket *p = router.allocForSending();
p->to = dest;
p->decoded.want_response = wantReplies;
p->decoded.which_payload = SubPacket_user_tag;
@@ -265,32 +246,13 @@ void MeshService::sendToMesh(MeshPacket *p)
DEBUG_MSG("Providing time to mesh %u\n", p->decoded.position.time);
}
// If the phone sent a packet just to us, don't send it out into the network
if (p->to == nodeDB.getNodeNum()) {
DEBUG_MSG("Dropping locally processed message\n");
// Note: We might return !OK if our fifo was full, at that point the only option we have is to drop it
if (router.sendLocal(p) != ERRNO_OK) {
DEBUG_MSG("No radio was able to send packet, discarding...\n");
releaseToPool(p);
} else {
// Note: We might return !OK if our fifo was full, at that point the only option we have is to drop it
if (router.send(p) != ERRNO_OK) {
DEBUG_MSG("No radio was able to send packet, discarding...\n");
releaseToPool(p);
}
}
}
MeshPacket *MeshService::allocForSending()
{
MeshPacket *p = packetPool.allocZeroed();
p->which_payload = MeshPacket_decoded_tag; // Assume payload is decoded at start.
p->from = nodeDB.getNodeNum();
p->to = NODENUM_BROADCAST;
p->id = generatePacketId();
p->rx_time = getValidTime(); // Just in case we process the packet locally - make sure it has a valid timestamp
return p;
}
void MeshService::sendNetworkPing(NodeNum dest, bool wantReplies)
{
NodeInfo *node = nodeDB.getNode(nodeDB.getNodeNum());
@@ -310,7 +272,7 @@ void MeshService::sendOurPosition(NodeNum dest, bool wantReplies)
assert(node->has_position);
// Update our local node info with our position (even if we don't decide to update anyone else)
MeshPacket *p = allocForSending();
MeshPacket *p = router.allocForSending();
p->to = dest;
p->decoded.which_payload = SubPacket_position_tag;
p->decoded.position = node->position;
@@ -324,7 +286,7 @@ int MeshService::onGPSChanged(void *unused)
// DEBUG_MSG("got gps notify\n");
// Update our local node info with our position (even if we don't decide to update anyone else)
MeshPacket *p = allocForSending();
MeshPacket *p = router.allocForSending();
p->decoded.which_payload = SubPacket_position_tag;
Position &pos = p->decoded.position;

View File

@@ -67,9 +67,6 @@ class MeshService
/// The owner User record just got updated, update our node DB and broadcast the info into the mesh
void reloadOwner() { sendOurOwner(); }
/// Allocate and return a meshpacket which defaults as send to broadcast from the current node.
MeshPacket *allocForSending();
/// Called when the user wakes up our GUI, normally sends our latest location to the mesh (if we have it), otherwise at least
/// sends our owner
void sendNetworkPing(NodeNum dest, bool wantReplies = false);

View File

@@ -14,6 +14,18 @@ typedef uint8_t PacketId; // A packet sequence number
#define ERRNO_NO_INTERFACES 33
#define ERRNO_UNKNOWN 32 // pick something that doesn't conflict with RH_ROUTER_ERROR_UNABLE_TO_DELIVER
/**
* the max number of hops a message can pass through, used as the default max for hop_limit in MeshPacket.
*
* We reserve 3 bits in the header so this could be up to 7, but given the high range of lora and typical usecases, keeping
* maxhops to 3 should be fine for a while. This also serves to prevent routing/flooding attempts to be attempted for
* too long.
**/
#define HOP_MAX 7
/// We normally just use max 3 hops for sending reliable messages
#define HOP_RELIABLE 3
typedef int ErrorCode;
/// Alloc and free packets to our global, ISR safe pool

View File

@@ -14,7 +14,7 @@ PacketHistory::PacketHistory()
/**
* Update recentBroadcasts and return true if we have already seen this packet
*/
bool PacketHistory::wasSeenRecently(const MeshPacket *p)
bool PacketHistory::wasSeenRecently(const MeshPacket *p, bool withUpdate)
{
if (p->id == 0) {
DEBUG_MSG("Ignoring message with zero id\n");
@@ -30,10 +30,11 @@ bool PacketHistory::wasSeenRecently(const MeshPacket *p)
recentPackets.erase(recentPackets.begin() + i); // delete old record
} else {
if (r.id == p->id && r.sender == p->from) {
DEBUG_MSG("Found existing broadcast record for fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id);
DEBUG_MSG("Found existing packet record for fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id);
// Update the time on this record to now
r.rxTimeMsec = now;
if (withUpdate)
r.rxTimeMsec = now;
return true;
}
@@ -42,12 +43,14 @@ bool PacketHistory::wasSeenRecently(const MeshPacket *p)
}
// Didn't find an existing record, make one
PacketRecord r;
r.id = p->id;
r.sender = p->from;
r.rxTimeMsec = now;
recentPackets.push_back(r);
DEBUG_MSG("Adding broadcast record for fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id);
if (withUpdate) {
PacketRecord r;
r.id = p->id;
r.sender = p->from;
r.rxTimeMsec = now;
recentPackets.push_back(r);
DEBUG_MSG("Adding packet record for fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id);
}
return false;
}

View File

@@ -61,6 +61,8 @@ class PacketHistory
/**
* Update recentBroadcasts and return true if we have already seen this packet
*
* @param withUpdate if true and not found we add an entry to recentPackets
*/
bool wasSeenRecently(const MeshPacket *p);
bool wasSeenRecently(const MeshPacket *p, bool withUpdate = true);
};

View File

@@ -115,8 +115,9 @@ size_t RadioInterface::beginSending(MeshPacket *p)
h->from = p->from;
h->to = p->to;
h->flags = 0;
h->id = p->id;
assert(p->hop_limit <= HOP_MAX);
h->flags = p->hop_limit | (p->want_ack ? PACKET_FLAGS_WANT_ACK_MASK : 0);
// if the sender nodenum is zero, that means uninitialized
assert(h->from);

View File

@@ -11,12 +11,22 @@
#define MAX_RHPACKETLEN 256
#define PACKET_FLAGS_HOP_MASK 0x07
#define PACKET_FLAGS_WANT_ACK_MASK 0x08
/**
* This structure has to exactly match the wire layout when sent over the radio link. Used to keep compatibility
* wtih the old radiohead implementation.
*/
typedef struct {
uint8_t to, from, id, flags;
uint8_t to, from, id;
/**
* Usage of flags:
*
* The bottom three bits of flags are use to store hop_limit when sent over the wire.
**/
uint8_t flags;
} PacketHeader;
typedef enum {

View File

@@ -2,7 +2,6 @@
#include "MeshTypes.h"
#include "OSTimer.h"
#include "mesh-pb-constants.h"
#include <NodeDB.h> // FIXME, this class shouldn't need to look into nodedb
#include <configuration.h>
#include <pb_decode.h>
#include <pb_encode.h>
@@ -284,28 +283,31 @@ void RadioLibInterface::handleReceiveInterrupt()
rxBad++;
} else {
const PacketHeader *h = (PacketHeader *)radiobuf;
uint8_t ourAddr = nodeDB.getNodeNum();
rxGood++;
if (h->to != 255 && h->to != ourAddr) {
DEBUG_MSG("ignoring packet not sent to us\n");
} else {
MeshPacket *mp = packetPool.allocZeroed();
mp->from = h->from;
mp->to = h->to;
mp->id = h->id;
addReceiveMetadata(mp);
// Note: we deliver _all_ packets to our router (i.e. our interface is intentionally promiscuous).
// This allows the router and other apps on our node to sniff packets (usually routing) between other
// nodes.
MeshPacket *mp = packetPool.allocZeroed();
mp->which_payload = MeshPacket_encrypted_tag; // Mark that the payload is still encrypted at this point
assert(payloadLen <= (int32_t)sizeof(mp->encrypted.bytes));
memcpy(mp->encrypted.bytes, payload, payloadLen);
mp->encrypted.size = payloadLen;
mp->from = h->from;
mp->to = h->to;
mp->id = h->id;
assert(HOP_MAX <= PACKET_FLAGS_HOP_MASK); // If hopmax changes, carefully check this code
mp->hop_limit = h->flags & PACKET_FLAGS_HOP_MASK;
mp->want_ack = !!(h->flags & PACKET_FLAGS_WANT_ACK_MASK);
DEBUG_MSG("Lora RX interrupt from=0x%x, id=%u\n", mp->from, mp->id);
addReceiveMetadata(mp);
deliverToReceiver(mp);
}
mp->which_payload = MeshPacket_encrypted_tag; // Mark that the payload is still encrypted at this point
assert(payloadLen <= sizeof(mp->encrypted.bytes));
memcpy(mp->encrypted.bytes, payload, payloadLen);
mp->encrypted.size = payloadLen;
DEBUG_MSG("Lora RX interrupt from=0x%x, id=%u\n", mp->from, mp->id);
deliverToReceiver(mp);
}
}
}
@@ -313,7 +315,7 @@ void RadioLibInterface::handleReceiveInterrupt()
/** start an immediate transmit */
void RadioLibInterface::startSend(MeshPacket *txp)
{
DEBUG_MSG("Starting low level send from=0x%x, id=%u!\n", txp->from, txp->id);
DEBUG_MSG("Starting low level send from=0x%x, id=%u, want_ack=%d\n", txp->from, txp->id, txp->want_ack);
setStandby(); // Cancel any already in process receives
size_t numbytes = beginSending(txp);

178
src/mesh/ReliableRouter.cpp Normal file
View File

@@ -0,0 +1,178 @@
#include "ReliableRouter.h"
#include "MeshTypes.h"
#include "configuration.h"
#include "mesh-pb-constants.h"
// ReliableRouter::ReliableRouter() {}
/**
* If the message is want_ack, then add it to a list of packets to retransmit.
* If we run out of retransmissions, send a nak packet towards the original client to indicate failure.
*/
ErrorCode ReliableRouter::send(MeshPacket *p)
{
if (p->want_ack) {
// If someone asks for acks on broadcast, we need the hop limit to be at least one, so that first node that receives our
// message will rebroadcast
if (p->to == NODENUM_BROADCAST && p->hop_limit == 0)
p->hop_limit = 1;
auto copy = packetPool.allocCopy(*p);
startRetransmission(copy);
}
return FloodingRouter::send(p);
}
/**
* If we receive a want_ack packet (do not check for wasSeenRecently), send back an ack (this might generate multiple ack sends in
* case the our first ack gets lost)
*
* If we receive an ack packet (do check wasSeenRecently), clear out any retransmissions and
* forward the ack to the application layer.
*
* If we receive a nak packet (do check wasSeenRecently), clear out any retransmissions
* and forward the nak to the application layer.
*
* Otherwise, let superclass handle it.
*/
void ReliableRouter::handleReceived(MeshPacket *p)
{
NodeNum ourNode = getNodeNum();
if (p->from == ourNode && p->to == NODENUM_BROADCAST) {
DEBUG_MSG("Received someone rebroadcasting for us fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id);
// We are seeing someone rebroadcast one of our broadcast attempts.
// If this is the first time we saw this, cancel any retransmissions we have queued up and generate an internal ack for
// the original sending process.
if (stopRetransmission(p->from, p->id)) {
DEBUG_MSG("Someone is retransmitting for us, generate implicit ack\n");
sendAckNak(true, p->from, p->id);
}
} else if (p->to == ourNode) { // ignore ack/nak/want_ack packets that are not address to us (for now)
if (p->want_ack) {
sendAckNak(true, p->from, p->id);
}
if (perhapsDecode(p)) {
// If the payload is valid, look for ack/nak
PacketId ackId = p->decoded.which_ack == SubPacket_success_id_tag ? p->decoded.ack.success_id : 0;
PacketId nakId = p->decoded.which_ack == SubPacket_fail_id_tag ? p->decoded.ack.fail_id : 0;
// we are careful to only read/update wasSeenRecently _after_ confirming this is an ack (to not mess
// up broadcasts)
if ((ackId || nakId) && !wasSeenRecently(p, false)) {
if (ackId) {
DEBUG_MSG("Received a ack=%d, stopping retransmissions\n", ackId);
stopRetransmission(p->to, ackId);
} else {
DEBUG_MSG("Received a nak=%d, stopping retransmissions\n", nakId);
stopRetransmission(p->to, nakId);
}
}
}
}
// handle the packet as normal
FloodingRouter::handleReceived(p);
}
/**
* Send an ack or a nak packet back towards whoever sent idFrom
*/
void ReliableRouter::sendAckNak(bool isAck, NodeNum to, PacketId idFrom)
{
auto p = allocForSending();
p->hop_limit = 0; // Assume just immediate neighbors for now
p->to = to;
DEBUG_MSG("Sending an ack=0x%x,to=0x%x,idFrom=%d,id=%d\n", isAck, to, idFrom, p->id);
if (isAck) {
p->decoded.ack.success_id = idFrom;
p->decoded.which_ack = SubPacket_success_id_tag;
} else {
p->decoded.ack.fail_id = idFrom;
p->decoded.which_ack = SubPacket_fail_id_tag;
}
sendLocal(p); // we sometimes send directly to the local node
}
#define NUM_RETRANSMISSIONS 3
PendingPacket::PendingPacket(MeshPacket *p)
{
packet = p;
numRetransmissions = NUM_RETRANSMISSIONS - 1; // We subtract one, because we assume the user just did the first send
setNextTx();
}
/**
* Stop any retransmissions we are doing of the specified node/packet ID pair
*/
bool ReliableRouter::stopRetransmission(NodeNum from, PacketId id)
{
auto key = GlobalPacketId(from, id);
return stopRetransmission(key);
}
bool ReliableRouter::stopRetransmission(GlobalPacketId key)
{
auto old = pending.find(key); // If we have an old record, someone messed up because id got reused
if (old != pending.end()) {
auto numErased = pending.erase(key);
assert(numErased == 1);
packetPool.release(old->second.packet);
return true;
} else
return false;
}
/**
* Add p to the list of packets to retransmit occasionally. We will free it once we stop retransmitting.
*/
void ReliableRouter::startRetransmission(MeshPacket *p)
{
auto id = GlobalPacketId(p);
auto rec = PendingPacket(p);
stopRetransmission(p->from, p->id);
pending[id] = rec;
}
/**
* Do any retransmissions that are scheduled (FIXME - for the time being called from loop)
*/
void ReliableRouter::doRetransmissions()
{
uint32_t now = millis();
// FIXME, we should use a better datastructure rather than walking through this map.
// for(auto el: pending) {
for (auto it = pending.begin(), nextIt = it; it != pending.end(); it = nextIt) {
++nextIt; // we use this odd pattern because we might be deleting it...
auto &p = it->second;
// FIXME, handle 51 day rolloever here!!!
if (p.nextTxMsec <= now) {
if (p.numRetransmissions == 0) {
DEBUG_MSG("Reliable send failed, returning a nak fr=0x%x,to=0x%x,id=%d\n", p.packet->from, p.packet->to,
p.packet->id);
sendAckNak(false, p.packet->from, p.packet->id);
stopRetransmission(it->first);
} else {
DEBUG_MSG("Sending reliable retransmission fr=0x%x,to=0x%x,id=%d, tries left=%d\n", p.packet->from, p.packet->to,
p.packet->id, p.numRetransmissions);
// Note: we call the superclass version because we don't want to have our version of send() add a new
// retransmission record
FloodingRouter::send(packetPool.allocCopy(*p.packet));
// Queue again
--p.numRetransmissions;
p.setNextTx();
}
}
}
}

122
src/mesh/ReliableRouter.h Normal file
View File

@@ -0,0 +1,122 @@
#pragma once
#include "FloodingRouter.h"
#include "PeriodicTask.h"
#include <unordered_map>
/**
* An identifier for a globalally unique message - a pair of the sending nodenum and the packet id assigned
* to that message
*/
struct GlobalPacketId {
NodeNum node;
PacketId id;
bool operator==(const GlobalPacketId &p) const { return node == p.node && id == p.id; }
GlobalPacketId(const MeshPacket *p)
{
node = p->from;
id = p->id;
}
GlobalPacketId(NodeNum _from, PacketId _id)
{
node = _from;
id = _id;
}
};
/**
* A packet queued for retransmission
*/
struct PendingPacket {
MeshPacket *packet;
/** The next time we should try to retransmit this packet */
uint32_t nextTxMsec;
/** Starts at NUM_RETRANSMISSIONS -1(normally 3) and counts down. Once zero it will be removed from the list */
uint8_t numRetransmissions;
/** True if we have started trying to find a route - for DSR usage
* While trying to find a route we don't actually send the data packet. We just leave it here pending until
* we have a route or we've failed to find one.
*/
bool wantRoute = false;
PendingPacket() {}
PendingPacket(MeshPacket *p);
void setNextTx() { nextTxMsec = millis() + random(20 * 1000, 22 * 1000); }
};
class GlobalPacketIdHashFunction
{
public:
size_t operator()(const GlobalPacketId &p) const { return (hash<NodeNum>()(p.node)) ^ (hash<PacketId>()(p.id)); }
};
/**
* This is a mixin that extends Router with the ability to do (one hop only) reliable message sends.
*/
class ReliableRouter : public FloodingRouter
{
private:
unordered_map<GlobalPacketId, PendingPacket, GlobalPacketIdHashFunction> pending;
public:
/**
* Constructor
*
*/
// ReliableRouter();
/**
* 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);
/** Do our retransmission handling */
virtual void loop()
{
doRetransmissions();
FloodingRouter::loop();
}
protected:
/**
* 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
*/
virtual void handleReceived(MeshPacket *p);
private:
/**
* Send an ack or a nak packet back towards whoever sent idFrom
*/
void sendAckNak(bool isAck, NodeNum to, PacketId idFrom);
/**
* Stop any retransmissions we are doing of the specified node/packet ID pair
*
* @return true if we found and removed a transmission with this ID
*/
bool stopRetransmission(NodeNum from, PacketId id);
bool stopRetransmission(GlobalPacketId p);
/**
* Add p to the list of packets to retransmit occasionally. We will free it once we stop retransmitting.
*/
void startRetransmission(MeshPacket *p);
/**
* Do any retransmissions that are scheduled (FIXME - for the time being called from loop)
*/
void doRetransmissions();
};

View File

@@ -3,6 +3,7 @@
#include "GPS.h"
#include "configuration.h"
#include "mesh-pb-constants.h"
#include <NodeDB.h>
/**
* Router todo
@@ -43,6 +44,49 @@ void Router::loop()
}
}
#define NUM_PACKET_ID 255 // 0 is consider invalid
/// Generate a unique packet id
// FIXME, move this someplace better
PacketId generatePacketId()
{
static uint32_t i; // Note: trying to keep this in noinit didn't help for working across reboots
static bool didInit = false;
if (!didInit) {
didInit = true;
i = random(0, NUM_PACKET_ID +
1); // pick a random initial sequence number at boot (to prevent repeated reboots always starting at 0)
}
i++;
return (i % NUM_PACKET_ID) + 1; // return number between 1 and 255
}
MeshPacket *Router::allocForSending()
{
MeshPacket *p = packetPool.allocZeroed();
p->which_payload = MeshPacket_decoded_tag; // Assume payload is decoded at start.
p->from = nodeDB.getNodeNum();
p->to = NODENUM_BROADCAST;
p->hop_limit = HOP_RELIABLE;
p->id = generatePacketId();
p->rx_time = getValidTime(); // Just in case we process the packet locally - make sure it has a valid timestamp
return p;
}
ErrorCode Router::sendLocal(MeshPacket *p)
{
if (p->to == nodeDB.getNodeNum()) {
DEBUG_MSG("Enqueuing internal message for the receive queue\n");
fromRadioQueue.enqueue(p);
return ERRNO_OK;
} else
return send(p);
}
/**
* Send a packet on a suitable interface. This routine will
* later free() the packet to pool. This routine is not allowed to stall.
@@ -50,6 +94,12 @@ void Router::loop()
*/
ErrorCode Router::send(MeshPacket *p)
{
assert(p->to != nodeDB.getNodeNum()); // should have already been handled by sendLocal
// Never set the want_ack flag on broadcast packets sent over the air.
if (p->to == NODENUM_BROADCAST)
p->want_ack = false;
// If the packet hasn't yet been encrypted, do so now (it might already be encrypted if we are just forwarding it)
assert(p->which_payload == MeshPacket_encrypted_tag ||
@@ -80,6 +130,46 @@ ErrorCode Router::send(MeshPacket *p)
}
}
/**
* Every (non duplicate) packet this node receives will be passed through this method. This allows subclasses to
* update routing tables etc... based on what we overhear (even for messages not destined to our node)
*/
void Router::sniffReceived(const MeshPacket *p)
{
DEBUG_MSG("FIXME-update-db Sniffing packet fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id);
}
bool Router::perhapsDecode(MeshPacket *p)
{
if (p->which_payload == MeshPacket_decoded_tag)
return true; // If packet was already decoded just return
assert(p->which_payload == MeshPacket_encrypted_tag);
// FIXME - someday don't send routing packets encrypted. That would allow us to route for other channels without
// being able to decrypt their data.
// Try to decrypt the packet if we can
static uint8_t bytes[MAX_RHPACKETLEN];
memcpy(bytes, p->encrypted.bytes,
p->encrypted.size); // we have to copy into a scratch buffer, because these bytes are a union with the decoded protobuf
crypto->decrypt(p->from, p->id, p->encrypted.size, bytes);
// Take those raw bytes and convert them back into a well structured protobuf we can understand
if (!pb_decode_from_bytes(bytes, p->encrypted.size, SubPacket_fields, &p->decoded)) {
DEBUG_MSG("Invalid protobufs in received mesh packet!\n");
return false;
} else {
// parsing was successful
p->which_payload = MeshPacket_decoded_tag;
return true;
}
}
NodeNum Router::getNodeNum()
{
return nodeDB.getNodeNum();
}
/**
* 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.
@@ -90,24 +180,16 @@ void Router::handleReceived(MeshPacket *p)
// Also, we should set the time from the ISR and it should have msec level resolution
p->rx_time = getValidTime(); // store the arrival timestamp for the phone
assert(p->which_payload ==
MeshPacket_encrypted_tag); // I _think_ the only thing that pushes to us is raw devices that just received packets
// Try to decrypt the packet if we can
static uint8_t bytes[MAX_RHPACKETLEN];
memcpy(bytes, p->encrypted.bytes,
p->encrypted.size); // we have to copy into a scratch buffer, because these bytes are a union with the decoded protobuf
crypto->decrypt(p->from, p->id, p->encrypted.size, bytes);
// Take those raw bytes and convert them back into a well structured protobuf we can understand
if (!pb_decode_from_bytes(bytes, p->encrypted.size, SubPacket_fields, &p->decoded)) {
DEBUG_MSG("Invalid protobufs in received mesh packet, discarding.\n");
} else {
if (perhapsDecode(p)) {
// parsing was successful, queue for our recipient
p->which_payload = MeshPacket_decoded_tag;
DEBUG_MSG("Notifying observers of received packet fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id);
notifyPacketReceived.notifyObservers(p);
sniffReceived(p);
if (p->to == NODENUM_BROADCAST || p->to == getNodeNum()) {
DEBUG_MSG("Notifying observers of received packet fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id);
notifyPacketReceived.notifyObservers(p);
}
}
packetPool.release(p);

View File

@@ -44,8 +44,21 @@ class Router
* do idle processing
* Mostly looking in our incoming rxPacket queue and calling handleReceived.
*/
void loop();
virtual void loop();
/**
* Works like send, but if we are sending to the local node, we directly put the message in the receive queue
*/
ErrorCode sendLocal(MeshPacket *p);
/// Allocate and return a meshpacket which defaults as send to broadcast from the current node.
MeshPacket *allocForSending();
/**
* @return our local nodenum */
NodeNum getNodeNum();
protected:
/**
* Send a packet on a suitable interface. This routine will
* later free() the packet to pool. This routine is not allowed to stall.
@@ -53,15 +66,32 @@ class Router
*/
virtual ErrorCode send(MeshPacket *p);
protected:
/**
* 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
* Note: this packet will never be called for messages sent/generated by this node.
* Note: this method will free the provided packet.
*/
virtual void handleReceived(MeshPacket *p);
/**
* Every (non duplicate) packet this node receives will be passed through this method. This allows subclasses to
* update routing tables etc... based on what we overhear (even for messages not destined to our node)
*/
virtual void sniffReceived(const MeshPacket *p);
/**
* Remove any encryption and decode the protobufs inside this packet (if necessary).
*
* @return true for success, false for corrupt packet.
*/
bool perhapsDecode(MeshPacket *p);
};
extern Router &router;
extern Router &router;
/// Generate a unique packet id
// FIXME, move this someplace better
PacketId generatePacketId();

View File

@@ -100,7 +100,14 @@ class Screen : public PeriodicTask
void setup();
/// Turns the screen on/off.
void setOn(bool on) { enqueueCmd(CmdItem{.cmd = on ? Cmd::SET_ON : Cmd::SET_OFF}); }
void setOn(bool on)
{
if (!on)
handleSetOn(
false); // We handle off commands immediately, because they might be called because the CPU is shutting down
else
enqueueCmd(CmdItem{.cmd = on ? Cmd::SET_ON : Cmd::SET_OFF});
}
/// Handles a button press.
void onPress() { enqueueCmd(CmdItem{.cmd = Cmd::ON_PRESS}); }