firmware/src/NodeDB.cpp

293 lines
8.2 KiB
C++
Raw Normal View History

2020-02-03 17:13:19 +00:00
#include <Arduino.h>
#include <assert.h>
2020-02-07 17:36:15 +00:00
#include "FS.h"
#include "SPIFFS.h"
2020-02-03 17:13:19 +00:00
#include <pb_encode.h>
#include <pb_decode.h>
#include "configuration.h"
2020-02-03 17:13:19 +00:00
#include "mesh-pb-constants.h"
#include "NodeDB.h"
2020-02-06 16:18:20 +00:00
#include "GPS.h"
2020-02-03 17:13:19 +00:00
NodeDB nodeDB;
2020-02-04 17:00:17 +00:00
2020-02-07 17:36:15 +00:00
// we have plenty of ram so statically alloc this tempbuf (for now)
DeviceState devicestate;
MyNodeInfo &myNodeInfo = devicestate.my_node;
RadioConfig &radioConfig = devicestate.radio;
ChannelSettings &channelSettings = radioConfig.channel_settings;
2020-02-07 17:36:15 +00:00
#define FS SPIFFS
2020-02-04 17:00:17 +00:00
/**
*
* Normally userids are unique and start with +country code to look like Signal phone numbers.
* But there are some special ids used when we haven't yet been configured by a user. In that case
* we use !macaddr (no colons).
*/
2020-02-07 17:36:15 +00:00
User &owner = devicestate.owner;
2020-02-04 17:00:17 +00:00
static uint8_t ourMacAddr[6];
2020-02-03 17:13:19 +00:00
2020-02-07 17:36:15 +00:00
NodeDB::NodeDB() : nodes(devicestate.node_db), numNodes(&devicestate.node_db_count)
2020-02-04 17:00:17 +00:00
{
}
2020-02-07 17:36:15 +00:00
void NodeDB::init()
{
2020-02-04 17:00:17 +00:00
2020-02-07 17:36:15 +00:00
// init our devicestate with valid flags so protobuf writing/reading will work
devicestate.has_my_node = true;
devicestate.has_radio = true;
devicestate.has_owner = true;
2020-02-12 21:31:09 +00:00
devicestate.has_radio = true;
devicestate.radio.has_channel_settings = true;
devicestate.radio.has_preferences = true;
2020-02-07 17:36:15 +00:00
devicestate.node_db_count = 0;
devicestate.receive_queue_count = 0;
2020-02-04 17:00:17 +00:00
// Init our blank owner info to reasonable defaults
2020-02-08 20:42:54 +00:00
esp_efuse_mac_get_default(ourMacAddr);
2020-02-04 17:00:17 +00:00
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));
2020-02-08 20:42:54 +00:00
// 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]);
2020-02-06 18:58:19 +00:00
sprintf(owner.long_name, "Unknown %02x%02x", ourMacAddr[4], ourMacAddr[5]);
sprintf(owner.short_name, "?%02X", ourMacAddr[5]);
2020-02-08 20:42:54 +00:00
// Crummy guess at our nodenum
pickNewNodeNum();
2020-02-04 17:00:17 +00:00
// Include our owner in the node db under our nodenum
2020-02-04 21:47:42 +00:00
NodeInfo *info = getOrCreateNode(getNodeNum());
2020-02-04 17:00:17 +00:00
info->user = owner;
info->has_user = true;
2020-02-06 16:18:20 +00:00
info->last_seen = 0; // haven't heard a real message yet
2020-02-07 17:36:15 +00:00
if (!FS.begin(true)) // FIXME - do this in main?
{
DEBUG_MSG("ERROR SPIFFS Mount Failed\n");
// FIXME - report failure to phone
}
// saveToDisk();
loadFromDisk();
DEBUG_MSG("NODENUM=0x%x, dbsize=%d\n", myNodeInfo.my_node_num, *numNodes);
}
2020-02-08 20:42:54 +00:00
// 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;
}
2020-02-07 17:36:15 +00:00
const char *preffile = "/db.proto";
const char *preftmp = "/db.proto.tmp";
void NodeDB::loadFromDisk()
{
static DeviceState scratch;
2020-02-07 17:36:15 +00:00
File f = FS.open(preffile);
if (f)
{
DEBUG_MSG("Loading saved preferences\n");
pb_istream_t stream = {&readcb, &f, DeviceState_size};
2020-02-12 21:31:09 +00:00
//DEBUG_MSG("Preload channel name=%s\n", channelSettings.name);
2020-02-08 15:55:12 +00:00
memset(&scratch, 0, sizeof(scratch));
if (!pb_decode(&stream, DeviceState_fields, &scratch))
2020-02-07 17:36:15 +00:00
{
DEBUG_MSG("Error: can't decode protobuf %s\n", PB_GET_ERROR(&stream));
// FIXME - report failure to phone
}
2020-02-08 20:42:54 +00:00
else
{
if (scratch.version < DeviceState_Version_Minimum)
DEBUG_MSG("Warn: devicestate is old, discarding\n");
else
2020-02-12 21:31:09 +00:00
{
devicestate = scratch;
2020-02-12 21:31:09 +00:00
}
//DEBUG_MSG("Postload channel name=%s\n", channelSettings.name);
}
2020-02-07 17:36:15 +00:00
f.close();
}
else
{
DEBUG_MSG("No saved preferences found\n");
}
}
void NodeDB::saveToDisk()
{
File f = FS.open(preftmp, "w");
if (f)
{
DEBUG_MSG("Writing preferences\n");
2020-02-12 21:31:09 +00:00
pb_ostream_t stream = {&writecb, &f, SIZE_MAX, 0};
//DEBUG_MSG("Presave channel name=%s\n", channelSettings.name);
2020-02-07 17:36:15 +00:00
devicestate.version = DeviceState_Version_Current;
2020-02-07 17:36:15 +00:00
if (!pb_encode(&stream, DeviceState_fields, &devicestate))
{
DEBUG_MSG("Error: can't write protobuf %s\n", PB_GET_ERROR(&stream));
// FIXME - report failure to phone
}
f.close();
// brief window of risk here ;-)
2020-02-12 21:31:09 +00:00
if (!FS.remove(preffile))
DEBUG_MSG("Warning: Can't remove old pref file\n");
if (!FS.rename(preftmp, preffile))
DEBUG_MSG("Error: can't rename new pref file\n");
2020-02-07 17:36:15 +00:00
}
else
{
DEBUG_MSG("ERROR: can't write prefs\n"); // FIXME report to app
}
2020-02-04 17:00:17 +00:00
}
2020-02-04 05:03:20 +00:00
const NodeInfo *NodeDB::readNextInfo()
{
2020-02-07 17:36:15 +00:00
if (readPointer < *numNodes)
2020-02-04 05:03:20 +00:00
return &nodes[readPointer++];
else
return NULL;
}
/// Given a node, return how many seconds in the past (vs now) that we last heard from it
uint32_t sinceLastSeen(const NodeInfo *n)
{
uint32_t now = gps.getTime() / 1000;
int delta = (int)(now - n->last_seen);
if (delta < 0) // our clock must be slightly off still - not set from GPS yet
delta = 0;
return delta;
}
#define NUM_ONLINE_SECS (60 * 2) // 2 hrs to consider someone offline
2020-02-12 21:31:09 +00:00
size_t NodeDB::getNumOnlineNodes()
{
size_t numseen = 0;
// FIXME this implementation is kinda expensive
2020-02-12 21:31:09 +00:00
for (int i = 0; i < *numNodes; i++)
if (sinceLastSeen(&nodes[i]) < NUM_ONLINE_SECS)
numseen++;
return numseen;
}
2020-02-03 17:13:19 +00:00
/// given a subpacket sniffed from the network, update our DB state
/// we updateGUI and updateGUIforNode if we think our this change is big enough for a redraw
void NodeDB::updateFrom(const MeshPacket &mp)
{
if (mp.has_payload)
{
const SubPacket &p = mp.payload;
DEBUG_MSG("Update DB node 0x%x for variant %d\n", mp.from, p.which_variant);
2020-02-03 17:13:19 +00:00
2020-02-08 20:42:54 +00:00
int oldNumNodes = *numNodes;
NodeInfo *info = getOrCreateNode(mp.from);
2020-02-03 17:13:19 +00:00
2020-02-08 20:42:54 +00:00
if (oldNumNodes != *numNodes)
updateGUI = true; // we just created a nodeinfo
2020-02-04 05:03:20 +00:00
info->last_seen = gps.getTime() / 1000; // we keep time in seconds, not msecs
2020-02-03 17:13:19 +00:00
2020-02-08 20:42:54 +00:00
switch (p.which_variant)
{
case SubPacket_position_tag:
info->position = p.variant.position;
info->has_position = true;
updateGUIforNode = info;
break;
2020-02-07 17:36:15 +00:00
2020-02-08 20:42:54 +00:00
case SubPacket_user_tag:
{
DEBUG_MSG("old user %s/%s/%s\n", info->user.id, info->user.long_name, info->user.short_name);
2020-02-07 17:36:15 +00:00
2020-02-08 20:42:54 +00:00
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
2020-02-07 17:36:15 +00:00
2020-02-08 20:42:54 +00:00
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;
2020-02-03 17:13:19 +00:00
2020-02-08 20:42:54 +00:00
if (changed)
{
// We just created a user for the first time, store our DB
saveToDisk();
2020-02-03 17:13:19 +00:00
}
2020-02-08 20:42:54 +00:00
break;
}
default:
break; // Ignore other packet types
2020-02-03 17:13:19 +00:00
}
}
}
/// Find a node in our DB, return null for missing
NodeInfo *NodeDB::getNode(NodeNum n)
{
2020-02-07 17:36:15 +00:00
for (int i = 0; i < *numNodes; i++)
2020-02-03 17:13:19 +00:00
if (nodes[i].num == n)
return &nodes[i];
return NULL;
}
/// Find a node in our DB, create an empty NodeInfo if missing
NodeInfo *NodeDB::getOrCreateNode(NodeNum n)
{
NodeInfo *info = getNode(n);
2020-02-04 17:00:17 +00:00
if (!info)
2020-02-03 17:13:19 +00:00
{
// add the node
2020-02-07 17:36:15 +00:00
assert(*numNodes < MAX_NUM_NODES);
info = &nodes[(*numNodes)++];
2020-02-03 17:13:19 +00:00
// everything is missing except the nodenum
memset(info, 0, sizeof(*info));
info->num = n;
}
return info;
}