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-06 16:18:20 +00:00
|
|
|
#include "GPS.h"
|
2020-03-19 02:15:51 +00:00
|
|
|
#include "NodeDB.h"
|
2020-02-23 02:02:44 +00:00
|
|
|
#include "PowerFSM.h"
|
2020-03-19 02:15:51 +00:00
|
|
|
#include "configuration.h"
|
2020-03-24 20:33:24 +00:00
|
|
|
#include "error.h"
|
2020-03-19 02:15:51 +00:00
|
|
|
#include "mesh-pb-constants.h"
|
|
|
|
#include <pb_decode.h>
|
|
|
|
#include <pb_encode.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;
|
2020-02-11 19:56:48 +00:00
|
|
|
ChannelSettings &channelSettings = radioConfig.channel_settings;
|
2020-02-07 17:36:15 +00:00
|
|
|
|
2020-03-03 21:46:11 +00:00
|
|
|
/*
|
2020-03-19 02:15:51 +00:00
|
|
|
DeviceState versions used to be defined in the .proto file but really only this function cares. So changed to a
|
2020-03-03 21:46:11 +00:00
|
|
|
#define here.
|
|
|
|
*/
|
|
|
|
|
2020-03-18 21:59:30 +00:00
|
|
|
#define DEVICESTATE_CUR_VER 6
|
2020-03-03 21:46:11 +00:00
|
|
|
#define DEVICESTATE_MIN_VER DEVICESTATE_CUR_VER
|
|
|
|
|
2020-02-07 17:36:15 +00:00
|
|
|
#define FS SPIFFS
|
|
|
|
|
2020-03-19 02:15:51 +00:00
|
|
|
/**
|
|
|
|
*
|
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-03-19 02:15:51 +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-03-18 20:29:22 +00:00
|
|
|
devicestate.has_radio = false;
|
2020-02-12 21:31:09 +00:00
|
|
|
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-22 22:56:19 +00:00
|
|
|
radioConfig.preferences.send_owner_interval = 4; // per sw-design.md
|
|
|
|
radioConfig.preferences.position_broadcast_secs = 15 * 60;
|
2020-02-24 04:08:20 +00:00
|
|
|
radioConfig.preferences.wait_bluetooth_secs = 120;
|
2020-02-22 22:56:19 +00:00
|
|
|
radioConfig.preferences.screen_on_secs = 30;
|
2020-03-03 21:31:44 +00:00
|
|
|
radioConfig.preferences.mesh_sds_timeout_secs = 2 * 60 * 60;
|
2020-03-05 00:10:48 +00:00
|
|
|
radioConfig.preferences.phone_sds_timeout_sec = 2 * 60 * 60;
|
2020-03-03 21:31:44 +00:00
|
|
|
radioConfig.preferences.sds_secs = 365 * 24 * 60 * 60; // one year
|
2020-02-22 22:56:19 +00:00
|
|
|
radioConfig.preferences.ls_secs = 60 * 60;
|
|
|
|
radioConfig.preferences.phone_timeout_secs = 15 * 60;
|
|
|
|
|
2020-02-12 22:07:06 +00:00
|
|
|
#ifdef GPS_RX_PIN
|
|
|
|
// some hardware defaults to have a built in GPS
|
|
|
|
myNodeInfo.has_gps = true;
|
|
|
|
#endif
|
2020-03-03 16:23:58 +00:00
|
|
|
strncpy(myNodeInfo.region, xstr(HW_VERSION), sizeof(myNodeInfo.region));
|
|
|
|
strncpy(myNodeInfo.firmware_version, xstr(APP_VERSION), sizeof(myNodeInfo.firmware_version));
|
|
|
|
strncpy(myNodeInfo.hw_model, HW_VENDOR, sizeof(myNodeInfo.hw_model));
|
2020-02-12 22:07:06 +00:00
|
|
|
|
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-03-19 02:15:51 +00:00
|
|
|
sprintf(owner.id, "!%02x%02x%02x%02x%02x%02x", ourMacAddr[0], ourMacAddr[1], ourMacAddr[2], ourMacAddr[3], ourMacAddr[4],
|
|
|
|
ourMacAddr[5]);
|
2020-02-04 17:00:17 +00:00
|
|
|
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-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
|
|
|
|
|
|
|
|
/**
|
2020-03-19 02:15:51 +00:00
|
|
|
* get our starting (provisional) nodenum from flash.
|
2020-02-08 20:42:54 +00:00
|
|
|
*/
|
|
|
|
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;
|
2020-03-19 02:15:51 +00:00
|
|
|
while ((found = getNode(r)) && memcmp(found->user.macaddr, owner.macaddr, sizeof(owner.macaddr))) {
|
2020-02-08 20:42:54 +00:00
|
|
|
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()
|
|
|
|
{
|
2020-02-08 15:41:04 +00:00
|
|
|
static DeviceState scratch;
|
|
|
|
|
2020-02-07 17:36:15 +00:00
|
|
|
File f = FS.open(preffile);
|
2020-03-19 02:15:51 +00:00
|
|
|
if (f) {
|
2020-02-07 17:36:15 +00:00
|
|
|
DEBUG_MSG("Loading saved preferences\n");
|
|
|
|
pb_istream_t stream = {&readcb, &f, DeviceState_size};
|
|
|
|
|
2020-03-19 02:15:51 +00:00
|
|
|
// DEBUG_MSG("Preload channel name=%s\n", channelSettings.name);
|
2020-02-12 21:31:09 +00:00
|
|
|
|
2020-02-08 15:55:12 +00:00
|
|
|
memset(&scratch, 0, sizeof(scratch));
|
2020-03-19 02:15:51 +00:00
|
|
|
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-03-19 02:15:51 +00:00
|
|
|
} else {
|
2020-03-03 21:46:11 +00:00
|
|
|
if (scratch.version < DEVICESTATE_MIN_VER)
|
2020-02-08 15:41:04 +00:00
|
|
|
DEBUG_MSG("Warn: devicestate is old, discarding\n");
|
2020-03-19 02:15:51 +00:00
|
|
|
else {
|
2020-03-18 21:59:30 +00:00
|
|
|
DEBUG_MSG("Loaded saved preferences version %d\n", scratch.version);
|
2020-02-08 15:41:04 +00:00
|
|
|
devicestate = scratch;
|
2020-02-12 21:31:09 +00:00
|
|
|
}
|
|
|
|
|
2020-03-19 02:15:51 +00:00
|
|
|
// DEBUG_MSG("Postload channel name=%s\n", channelSettings.name);
|
2020-02-08 15:41:04 +00:00
|
|
|
}
|
2020-02-07 17:36:15 +00:00
|
|
|
|
|
|
|
f.close();
|
2020-03-19 02:15:51 +00:00
|
|
|
} else {
|
2020-02-07 17:36:15 +00:00
|
|
|
DEBUG_MSG("No saved preferences found\n");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void NodeDB::saveToDisk()
|
|
|
|
{
|
|
|
|
File f = FS.open(preftmp, "w");
|
2020-03-19 02:15:51 +00:00
|
|
|
if (f) {
|
2020-02-07 17:36:15 +00:00
|
|
|
DEBUG_MSG("Writing preferences\n");
|
2020-02-12 21:31:09 +00:00
|
|
|
|
|
|
|
pb_ostream_t stream = {&writecb, &f, SIZE_MAX, 0};
|
|
|
|
|
2020-03-19 02:15:51 +00:00
|
|
|
// DEBUG_MSG("Presave channel name=%s\n", channelSettings.name);
|
2020-02-07 17:36:15 +00:00
|
|
|
|
2020-03-03 21:46:11 +00:00
|
|
|
devicestate.version = DEVICESTATE_CUR_VER;
|
2020-03-19 02:15:51 +00:00
|
|
|
if (!pb_encode(&stream, DeviceState_fields, &devicestate)) {
|
2020-02-07 17:36:15 +00:00
|
|
|
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-03-19 02:15:51 +00:00
|
|
|
} else {
|
2020-02-07 17:36:15 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2020-02-12 19:52:53 +00:00
|
|
|
/// Given a node, return how many seconds in the past (vs now) that we last heard from it
|
|
|
|
uint32_t sinceLastSeen(const NodeInfo *n)
|
|
|
|
{
|
2020-02-19 18:53:09 +00:00
|
|
|
uint32_t now = gps.getTime();
|
2020-02-12 19:52:53 +00:00
|
|
|
|
2020-02-19 18:53:09 +00:00
|
|
|
uint32_t last_seen = n->position.time;
|
|
|
|
int delta = (int)(now - last_seen);
|
2020-02-12 19:52:53 +00:00
|
|
|
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()
|
|
|
|
{
|
2020-02-12 19:52:53 +00:00
|
|
|
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)
|
2020-02-12 19:52:53 +00:00
|
|
|
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)
|
|
|
|
{
|
2020-03-19 02:15:51 +00:00
|
|
|
if (mp.has_payload) {
|
2020-02-03 17:13:19 +00:00
|
|
|
const SubPacket &p = mp.payload;
|
2020-02-19 19:35:34 +00:00
|
|
|
DEBUG_MSG("Update DB node 0x%x for variant %d, rx_time=%u\n", mp.from, p.which_variant, mp.rx_time);
|
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
|
|
|
|
2020-03-19 02:15:51 +00:00
|
|
|
if (mp.rx_time) { // if the packet has a valid timestamp use it to update our last_seen
|
2020-02-19 18:53:09 +00:00
|
|
|
info->has_position = true; // at least the time is valid
|
|
|
|
info->position.time = mp.rx_time;
|
|
|
|
}
|
2020-02-03 17:13:19 +00:00
|
|
|
|
2020-03-19 02:15:51 +00:00
|
|
|
switch (p.which_variant) {
|
|
|
|
case SubPacket_position_tag: {
|
2020-02-19 18:53:09 +00:00
|
|
|
// we carefully preserve the old time, because we always trust our local timestamps more
|
|
|
|
uint32_t oldtime = info->position.time;
|
2020-02-08 20:42:54 +00:00
|
|
|
info->position = p.variant.position;
|
2020-02-19 18:53:09 +00:00
|
|
|
info->position.time = oldtime;
|
2020-02-08 20:42:54 +00:00
|
|
|
info->has_position = true;
|
|
|
|
updateGUIforNode = info;
|
|
|
|
break;
|
2020-02-19 18:53:09 +00:00
|
|
|
}
|
2020-02-07 17:36:15 +00:00
|
|
|
|
2020-03-19 02:15:51 +00:00
|
|
|
case SubPacket_data_tag: {
|
2020-02-13 03:58:44 +00:00
|
|
|
// Keep a copy of the most recent text message.
|
2020-03-19 02:15:51 +00:00
|
|
|
if (p.variant.data.typ == Data_Type_CLEAR_TEXT) {
|
|
|
|
DEBUG_MSG("Received text msg from=0%0x, msg=%.*s\n", mp.from, p.variant.data.payload.size,
|
|
|
|
p.variant.data.payload.bytes);
|
|
|
|
if (mp.to == NODENUM_BROADCAST || mp.to == nodeDB.getNodeNum()) {
|
2020-02-18 00:32:51 +00:00
|
|
|
// We only store/display messages destined for us.
|
|
|
|
devicestate.rx_text_message = mp;
|
|
|
|
devicestate.has_rx_text_message = true;
|
|
|
|
updateTextMessage = true;
|
2020-02-25 18:29:37 +00:00
|
|
|
powerFSM.trigger(EVENT_RECEIVED_TEXT_MSG);
|
2020-02-18 00:32:51 +00:00
|
|
|
}
|
2020-02-13 03:58:44 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2020-03-19 02:15:51 +00:00
|
|
|
case SubPacket_user_tag: {
|
2020-02-08 20:42:54 +00:00
|
|
|
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-03-19 02:15:51 +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;
|
2020-02-03 17:13:19 +00:00
|
|
|
|
2020-03-19 02:15:51 +00:00
|
|
|
if (changed) {
|
2020-02-13 03:58:44 +00:00
|
|
|
updateGUIforNode = info;
|
2020-02-23 02:02:44 +00:00
|
|
|
powerFSM.trigger(EVENT_NODEDB_UPDATED);
|
2020-02-13 03:58:44 +00:00
|
|
|
|
2020-02-18 00:27:29 +00:00
|
|
|
// Not really needed - we will save anyways when we go to sleep
|
2020-02-13 03:58:44 +00:00
|
|
|
// We just changed something important about the user, store our DB
|
2020-02-18 00:27:29 +00:00
|
|
|
// 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
|
2020-02-19 04:06:01 +00:00
|
|
|
/// NOTE: This function might be called from an ISR
|
2020-02-03 17:13:19 +00:00
|
|
|
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-03-19 02:15:51 +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;
|
2020-03-24 20:33:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Record an error that should be reported via analytics
|
|
|
|
void recordCriticalError(CriticalErrorCode code, uint32_t address)
|
|
|
|
{
|
|
|
|
myNodeInfo.error_code = code;
|
|
|
|
myNodeInfo.error_address = address;
|
|
|
|
myNodeInfo.error_count++;
|
|
|
|
}
|