dynamic nodenum assignment now works

1.2-legacy
geeksville 2020-02-08 12:42:54 -08:00
rodzic b262492c75
commit 422e213d2a
7 zmienionych plików z 148 dodań i 130 usunięć

Wyświetl plik

@ -32,13 +32,6 @@
* never enter deep sleep while connected to USB power (but still go to other low power modes)
* when main cpu is idle (in loop), turn cpu clock rate down and/or activate special sleep modes. We want almost everything shutdown until it gets an interrupt.
# dynamic nodenum assignment tasks
we currently do the following crap solution:
hardwire nodenums based on macaddr. when node boots it broadcasts its Owner info (which includes our macaddr). If any node receives Owner messages, the other nodes reply with their owner info.
Really should instead do something like: new node sends its owner info as a provisional request. If any other node shows that nodenum in use by a different macaddr, they reply with NodeDeny.
If the node doesn't get denied within X seconds it then sends the info as a non provisional message (and other nodes update their node db)
But fixme, think about this and look for standard solutions - it will have problems when meshes separate change and then rejoin.
# Pre-beta priority
@ -51,7 +44,6 @@ until the phone pulls those packets. Ever so often power on bluetooth just so w
* do hibernation mode to get power draw down to 2.5uA https://lastminuteengineers.com/esp32-sleep-modes-power-consumption/
* make sure main cpu is not woken for packets with bad crc or not addressed to this node - do that in the radio hw
* enable fast init inside the gps chip
* dynamically select node nums
* triple check fcc compliance
* allow setting full radio params from android
* pick channel center frequency based on name? "dolphin" would hash to 900Mhz, "cat" to 905MHz etc? Or is that too opaque?
@ -112,3 +104,4 @@ until the phone pulls those packets. Ever so often power on bluetooth just so w
* save our node db on entry to sleep
* fix the logo
* sent/received packets (especially if a node was just reset) have variant of zero sometimes - I think there is a bug (race-condtion?) in the radio send/rx path.
* DONE dynamic nodenum assignment tasks

Wyświetl plik

@ -95,6 +95,8 @@ ErrorCode MeshRadio::send(MeshPacket *p)
ErrorCode MeshRadio::sendTo(NodeNum dest, const uint8_t *buf, size_t len)
{
// We must do this before each send, because we might have just changed our nodenum
manager.setThisAddress(nodeDB.getNodeNum()); // Note: we must do this here, because the nodenum isn't inited at constructor time.
assert(len <= 251); // Make sure we don't overflow the tiny max packet size
@ -109,7 +111,7 @@ ErrorCode MeshRadio::sendTo(NodeNum dest, const uint8_t *buf, size_t len)
if(res == ERRNO_OK)
manager.waitPacketSent();
DEBUG_MSG("mesh sendTo %d bytes to 0x%x (%u msecs)\n", len, dest, millis() - start);
DEBUG_MSG("mesh sendTo %d bytes to 0x%x (%lu msecs)\n", len, dest, millis() - start);
return res;
}

Wyświetl plik

@ -20,8 +20,22 @@ a node number and keeping the current nodedb.
*/
MeshService service;
/* Broadcast when a newly powered mesh node wants to find a node num it can use
The algoritm is as follows:
* when a node starts up, it broadcasts their user and the normal flow is for all other nodes to reply with their User as well (so the new node can build its node db)
* If a node ever receives a User (not just the first broadcast) message where the sender node number equals our node number, that indicates a collision has occurred and the following steps should happen:
If the receiving node (that was already in the mesh)'s macaddr is LOWER than the new User who just tried to sign in: it gets to keep its nodenum. We send a broadcast message
of OUR User (we use a broadcast so that the other node can receive our message, considering we have the same id - it also serves to let observers correct their nodedb) - this case is rare so it should be okay.
If any node receives a User where the macaddr is GTE than their local macaddr, they have been vetoed and should pick a new random nodenum (filtering against whatever it knows about the nodedb) and
rebroadcast their User.
FIXME in the initial proof of concept we just skip the entire want/deny flow and just hand pick node numbers at first.
*/
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)
@ -61,39 +75,77 @@ void MeshService::sendOurOwner(NodeNum dest)
sendToMesh(p);
}
void MeshService::handleFromRadio()
{
MeshPacket *mp;
uint32_t oldFromNum = fromNum;
while ((mp = fromRadioQueue.dequeuePtr(0)) != NULL)
{
nodeDB.updateFrom(*mp); // update our DB state based off sniffing every RX packet from the radio
if(mp->has_payload && mp->payload.which_variant == SubPacket_user_tag && mp->to == NODENUM_BROADCAST) {
// Someone just sent us a User, reply with our Owner
DEBUG_MSG("Received broadcast Owner from 0x%x, replying with our owner\n", mp->from);
sendOurOwner(mp->from);
if (mp->has_payload && mp->payload.which_variant == SubPacket_user_tag)
{
bool wasBroadcast = mp->to == NODENUM_BROADCAST;
bool isCollision = mp->from == myNodeInfo.my_node_num;
String lcd = String("Joined: ") + mp->payload.variant.user.long_name + "\n";
screen_print(lcd.c_str());
// we win if we have a lower macaddr
bool weWin = memcmp(&owner.macaddr, &mp->payload.variant.user.macaddr, sizeof(owner.macaddr)) < 0;
if (isCollision)
{
if (weWin)
{
DEBUG_MSG("NOTE! Received a nodenum collision and we are vetoing\n");
packetPool.release(mp); // discard it
mp = NULL;
sendOurOwner(); // send our owner as a _broadcast_ because that other guy is mistakenly using our nodenum
}
else
{
// we lost, we need to try for a new nodenum!
DEBUG_MSG("NOTE! Received a nodenum collision we lost, so picking a new nodenum\n");
nodeDB.updateFrom(*mp); // update the DB early - before trying to repick (so we don't select the same node number again)
nodeDB.pickNewNodeNum();
sendOurOwner(); // broadcast our new attempt at a node number
}
}
else if (wasBroadcast)
{
// If we haven't yet abandoned the packet and it was a broadcast, reply (just to them) with our User record so they can build their DB
// Someone just sent us a User, reply with our Owner
DEBUG_MSG("Received broadcast Owner from 0x%x, replying with our owner\n", mp->from);
sendOurOwner(mp->from);
String lcd = String("Joined: ") + mp->payload.variant.user.long_name + "\n";
screen_print(lcd.c_str());
}
}
fromNum++;
// If we veto a received User packet, we don't put it into the DB or forward it to the phone (to prevent confusing it)
if (mp)
{
nodeDB.updateFrom(*mp); // update our DB state based off sniffing every RX packet from the radio
if(toPhoneQueue.numFree() == 0) {
DEBUG_MSG("NOTE: tophone queue is full, discarding oldest\n");
MeshPacket *d = toPhoneQueue.dequeuePtr(0);
if(d)
releaseToPool(d);
fromNum++;
if (toPhoneQueue.numFree() == 0)
{
DEBUG_MSG("NOTE: tophone queue is full, discarding oldest\n");
MeshPacket *d = toPhoneQueue.dequeuePtr(0);
if (d)
releaseToPool(d);
}
assert(toPhoneQueue.enqueue(mp, 0) == pdTRUE); // FIXME, instead of failing for full queue, delete the oldest mssages
}
assert(toPhoneQueue.enqueue(mp, 0) == pdTRUE); // FIXME, instead of failing for full queue, delete the oldest mssages
else
DEBUG_MSG("Dropping vetoed User message\n");
}
if (oldFromNum != fromNum) // We don't want to generate extra notifies for multiple new packets
bluetoothNotifyFromNum(fromNum);
}
/// Do idle processing (mostly processing messages which have been queued from the radio)
void MeshService::loop()
{
@ -103,8 +155,9 @@ void MeshService::loop()
// FIXME, don't send user this often, but for now it is useful for testing
static uint32_t lastsend;
uint32_t now = millis();
if(now - lastsend > 20 * 1000) {
uint32_t now = millis();
if (now - lastsend > 20 * 1000)
{
lastsend = now;
sendOurOwner();
}

Wyświetl plik

@ -31,27 +31,12 @@ User &owner = devicestate.owner;
static uint8_t ourMacAddr[6];
/**
* get our starting (provisional) nodenum from flash. But check first if anyone else is using it, by trying to send a message to it (arping)
*/
static NodeNum getDesiredNodeNum()
{
esp_efuse_mac_get_default(ourMacAddr);
// FIXME not the right way to guess node numes
uint8_t r = ourMacAddr[5];
assert(r != 0xff && r != 0); // It better not be the broadcast address or zero
return r;
}
NodeDB::NodeDB() : nodes(devicestate.node_db), numNodes(&devicestate.node_db_count)
{
}
void NodeDB::init()
{
// Crummy guess at our nodenum
myNodeInfo.my_node_num = getDesiredNodeNum();
// init our devicestate with valid flags so protobuf writing/reading will work
devicestate.has_my_node = true;
@ -61,13 +46,19 @@ void NodeDB::init()
devicestate.receive_queue_count = 0;
// Init our blank owner info to reasonable defaults
esp_efuse_mac_get_default(ourMacAddr);
sprintf(owner.id, "!%02x%02x%02x%02x%02x%02x", ourMacAddr[0],
ourMacAddr[1], ourMacAddr[2], ourMacAddr[3], ourMacAddr[4], ourMacAddr[5]);
memcpy(owner.macaddr, ourMacAddr, sizeof(owner.macaddr));
// make each node start with ad different random seed (but okay that the sequence is the same each boot)
randomSeed((ourMacAddr[2] << 24L) | (ourMacAddr[3] << 16L) | (ourMacAddr[4] << 8L) | ourMacAddr[5]);
sprintf(owner.long_name, "Unknown %02x%02x", ourMacAddr[4], ourMacAddr[5]);
sprintf(owner.short_name, "?%02X", ourMacAddr[5]);
// FIXME, read owner info from flash
// Crummy guess at our nodenum
pickNewNodeNum();
// Include our owner in the node db under our nodenum
NodeInfo *info = getOrCreateNode(getNodeNum());
@ -87,6 +78,30 @@ void NodeDB::init()
DEBUG_MSG("NODENUM=0x%x, dbsize=%d\n", myNodeInfo.my_node_num, *numNodes);
}
// We reserve a few nodenums for future use
#define NUM_RESERVED 4
/**
* get our starting (provisional) nodenum from flash.
*/
void NodeDB::pickNewNodeNum()
{
// FIXME not the right way to guess node numes
uint8_t r = ourMacAddr[5];
if (r == 0xff || r < NUM_RESERVED)
r = NUM_RESERVED; // don't pick a reserved node number
NodeInfo *found;
while ((found = getNode(r)) && memcmp(found->user.macaddr, owner.macaddr, sizeof(owner.macaddr)))
{
NodeNum n = random(NUM_RESERVED, NODENUM_BROADCAST); // try a new random choice
DEBUG_MSG("NOTE! Our desired nodenum 0x%x is in use, so trying for 0x%x\n", r, n);
r = n;
}
myNodeInfo.my_node_num = r;
}
const char *preffile = "/db.proto";
const char *preftmp = "/db.proto.tmp";
@ -106,8 +121,9 @@ void NodeDB::loadFromDisk()
DEBUG_MSG("Error: can't decode protobuf %s\n", PB_GET_ERROR(&stream));
// FIXME - report failure to phone
}
else {
if(scratch.version < DeviceState_Version_Minimum)
else
{
if (scratch.version < DeviceState_Version_Minimum)
DEBUG_MSG("Warn: devicestate is old, discarding\n");
else
devicestate = scratch;
@ -164,46 +180,44 @@ void NodeDB::updateFrom(const MeshPacket &mp)
{
const SubPacket &p = mp.payload;
DEBUG_MSG("Update DB node 0x%x for variant %d\n", mp.from, p.which_variant);
if (p.which_variant != SubPacket_want_node_tag) // we don't create nodeinfo records for someone that is just trying to claim a nodenum
int oldNumNodes = *numNodes;
NodeInfo *info = getOrCreateNode(mp.from);
if (oldNumNodes != *numNodes)
updateGUI = true; // we just created a nodeinfo
info->last_seen = gps.getTime();
switch (p.which_variant)
{
int oldNumNodes = *numNodes;
NodeInfo *info = getOrCreateNode(mp.from);
case SubPacket_position_tag:
info->position = p.variant.position;
info->has_position = true;
updateGUIforNode = info;
break;
if (oldNumNodes != *numNodes)
updateGUI = true; // we just created a nodeinfo
case SubPacket_user_tag:
{
DEBUG_MSG("old user %s/%s/%s\n", info->user.id, info->user.long_name, info->user.short_name);
info->last_seen = gps.getTime();
bool changed = memcmp(&info->user, &p.variant.user, sizeof(info->user)); // Both of these blocks start as filled with zero so I think this is okay
switch (p.which_variant)
info->user = p.variant.user;
DEBUG_MSG("updating changed=%d user %s/%s/%s\n", changed, info->user.id, info->user.long_name, info->user.short_name);
info->has_user = true;
updateGUIforNode = info;
if (changed)
{
case SubPacket_position_tag:
info->position = p.variant.position;
info->has_position = true;
updateGUIforNode = info;
break;
case SubPacket_user_tag:
{
DEBUG_MSG("old user %s/%s/%s\n", info->user.id, info->user.long_name, info->user.short_name);
bool changed = memcmp(&info->user, &p.variant.user, sizeof(info->user)); // Both of these blocks start as filled with zero so I think this is okay
info->user = p.variant.user;
DEBUG_MSG("updating changed=%d user %s/%s/%s\n", changed, info->user.id, info->user.long_name, info->user.short_name);
info->has_user = true;
updateGUIforNode = info;
if (changed)
{
// We just created a user for the first time, store our DB
saveToDisk();
}
break;
// We just created a user for the first time, store our DB
saveToDisk();
}
break;
}
default:
break; // Ignore other packet types
}
default:
break; // Ignore other packet types
}
}
}

Wyświetl plik

@ -59,6 +59,9 @@ public:
/// Allow the bluetooth layer to read our next nodeinfo record, or NULL if done reading
const NodeInfo *readNextInfo();
/// pick a provisional nodenum we hope no one is using
void pickNewNodeNum();
private:
/// Find a node in our DB, return null for missing
NodeInfo *getNode(NodeNum n);

Wyświetl plik

@ -15,12 +15,6 @@ PB_BIND(Data, Data, AUTO)
PB_BIND(User, User, AUTO)
PB_BIND(WantNodeNum, WantNodeNum, AUTO)
PB_BIND(DenyNodeNum, DenyNodeNum, AUTO)
PB_BIND(SubPacket, SubPacket, AUTO)

Wyświetl plik

@ -29,8 +29,8 @@ typedef enum _RadioConfig_ModemConfig {
typedef enum _DeviceState_Version {
DeviceState_Version_Unset = 0,
DeviceState_Version_Minimum = 1,
DeviceState_Version_Current = 1
DeviceState_Version_Minimum = 2,
DeviceState_Version_Current = 2
} DeviceState_Version;
/* Struct definitions */
@ -40,10 +40,6 @@ typedef struct _Data {
Data_payload_t payload;
} Data;
typedef struct _DenyNodeNum {
pb_byte_t macaddr[6];
} DenyNodeNum;
typedef struct _MyNodeInfo {
int32_t my_node_num;
} MyNodeInfo;
@ -75,11 +71,6 @@ typedef struct _User {
pb_byte_t macaddr[6];
} User;
typedef struct _WantNodeNum {
uint32_t desired_nodenum;
pb_byte_t macaddr[6];
} WantNodeNum;
typedef struct _NodeInfo {
int32_t num;
bool has_user;
@ -97,8 +88,6 @@ typedef struct _SubPacket {
uint64_t time;
Data data;
User user;
WantNodeNum want_node;
DenyNodeNum deny_node;
} variant;
} SubPacket;
@ -157,8 +146,6 @@ 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 WantNodeNum_init_default {0, {0}}
#define DenyNodeNum_init_default {{0}}
#define SubPacket_init_default {0, {Position_init_default}}
#define MeshPacket_init_default {0, 0, false, SubPacket_init_default}
#define RadioConfig_init_default {0, 0, 0, 0, _RadioConfig_ModemConfig_MIN, {0, {0}}, 0, 0}
@ -170,8 +157,6 @@ 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 WantNodeNum_init_zero {0, {0}}
#define DenyNodeNum_init_zero {{0}}
#define SubPacket_init_zero {0, {Position_init_zero}}
#define MeshPacket_init_zero {0, 0, false, SubPacket_init_zero}
#define RadioConfig_init_zero {0, 0, 0, 0, _RadioConfig_ModemConfig_MIN, {0, {0}}, 0, 0}
@ -184,7 +169,6 @@ typedef struct _ToRadio {
/* Field tags (for use in manual encoding/decoding) */
#define Data_typ_tag 1
#define Data_payload_tag 2
#define DenyNodeNum_macaddr_tag 1
#define MyNodeInfo_my_node_num_tag 1
#define Position_latitude_tag 1
#define Position_longitude_tag 2
@ -203,8 +187,6 @@ typedef struct _ToRadio {
#define User_long_name_tag 2
#define User_short_name_tag 3
#define User_macaddr_tag 4
#define WantNodeNum_desired_nodenum_tag 1
#define WantNodeNum_macaddr_tag 2
#define NodeInfo_num_tag 1
#define NodeInfo_user_tag 2
#define NodeInfo_position_tag 3
@ -214,8 +196,6 @@ typedef struct _ToRadio {
#define SubPacket_time_tag 2
#define SubPacket_data_tag 3
#define SubPacket_user_tag 4
#define SubPacket_want_node_tag 5
#define SubPacket_deny_node_tag 6
#define MeshPacket_from_tag 1
#define MeshPacket_to_tag 2
#define MeshPacket_payload_tag 3
@ -253,31 +233,16 @@ X(a, STATIC, SINGULAR, FIXED_LENGTH_BYTES, macaddr, 4)
#define User_CALLBACK NULL
#define User_DEFAULT NULL
#define WantNodeNum_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, UINT32, desired_nodenum, 1) \
X(a, STATIC, SINGULAR, FIXED_LENGTH_BYTES, macaddr, 2)
#define WantNodeNum_CALLBACK NULL
#define WantNodeNum_DEFAULT NULL
#define DenyNodeNum_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, FIXED_LENGTH_BYTES, macaddr, 1)
#define DenyNodeNum_CALLBACK NULL
#define DenyNodeNum_DEFAULT NULL
#define SubPacket_FIELDLIST(X, a) \
X(a, STATIC, ONEOF, MESSAGE, (variant,position,variant.position), 1) \
X(a, STATIC, ONEOF, UINT64, (variant,time,variant.time), 2) \
X(a, STATIC, ONEOF, MESSAGE, (variant,data,variant.data), 3) \
X(a, STATIC, ONEOF, MESSAGE, (variant,user,variant.user), 4) \
X(a, STATIC, ONEOF, MESSAGE, (variant,want_node,variant.want_node), 5) \
X(a, STATIC, ONEOF, MESSAGE, (variant,deny_node,variant.deny_node), 6)
X(a, STATIC, ONEOF, MESSAGE, (variant,user,variant.user), 4)
#define SubPacket_CALLBACK NULL
#define SubPacket_DEFAULT NULL
#define SubPacket_variant_position_MSGTYPE Position
#define SubPacket_variant_data_MSGTYPE Data
#define SubPacket_variant_user_MSGTYPE User
#define SubPacket_variant_want_node_MSGTYPE WantNodeNum
#define SubPacket_variant_deny_node_MSGTYPE DenyNodeNum
#define MeshPacket_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, INT32, from, 1) \
@ -346,8 +311,6 @@ 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 WantNodeNum_msg;
extern const pb_msgdesc_t DenyNodeNum_msg;
extern const pb_msgdesc_t SubPacket_msg;
extern const pb_msgdesc_t MeshPacket_msg;
extern const pb_msgdesc_t RadioConfig_msg;
@ -361,8 +324,6 @@ extern const pb_msgdesc_t ToRadio_msg;
#define Position_fields &Position_msg
#define Data_fields &Data_msg
#define User_fields &User_msg
#define WantNodeNum_fields &WantNodeNum_msg
#define DenyNodeNum_fields &DenyNodeNum_msg
#define SubPacket_fields &SubPacket_msg
#define MeshPacket_fields &MeshPacket_msg
#define RadioConfig_fields &RadioConfig_msg
@ -376,8 +337,6 @@ extern const pb_msgdesc_t ToRadio_msg;
#define Position_size 42
#define Data_size 205
#define User_size 72
#define WantNodeNum_size 14
#define DenyNodeNum_size 8
#define SubPacket_size 208
#define MeshPacket_size 233
#define RadioConfig_size 70