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"
|
|
|
|
|
2020-05-10 02:08:04 +00:00
|
|
|
#include "CryptoEngine.h"
|
2020-02-06 16:18:20 +00:00
|
|
|
#include "GPS.h"
|
2020-09-25 19:52:08 +00:00
|
|
|
#include "MeshRadio.h"
|
2020-03-19 02:15:51 +00:00
|
|
|
#include "NodeDB.h"
|
2020-06-03 20:15:45 +00:00
|
|
|
#include "PacketHistory.h"
|
2020-02-23 02:02:44 +00:00
|
|
|
#include "PowerFSM.h"
|
2020-10-07 23:28:57 +00:00
|
|
|
#include "RTC.h"
|
2020-06-03 20:15:45 +00:00
|
|
|
#include "Router.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"
|
2020-09-25 19:52:08 +00:00
|
|
|
#include "meshwifi/meshwifi.h"
|
2020-11-19 01:25:02 +00:00
|
|
|
#include "FSCommon.h"
|
2020-03-19 02:15:51 +00:00
|
|
|
#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)
|
2020-06-21 21:11:38 +00:00
|
|
|
EXT_RAM_ATTR DeviceState devicestate;
|
2020-02-07 17:36:15 +00:00
|
|
|
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-12-17 02:53:29 +00:00
|
|
|
/** The current change # for radio settings. Starts at 0 on boot and any time the radio settings
|
|
|
|
* might have changed is incremented. Allows others to detect they might now be on a new channel.
|
|
|
|
*/
|
|
|
|
uint32_t radioGeneration;
|
|
|
|
|
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-08-12 17:42:25 +00:00
|
|
|
#define DEVICESTATE_CUR_VER 11
|
2020-03-03 21:46:11 +00:00
|
|
|
#define DEVICESTATE_MIN_VER DEVICESTATE_CUR_VER
|
|
|
|
|
2020-11-19 01:25:02 +00:00
|
|
|
|
2020-04-15 03:22:27 +00:00
|
|
|
|
|
|
|
// FIXME - move this somewhere else
|
|
|
|
extern void getMacAddr(uint8_t *dmac);
|
2020-02-07 17:36:15 +00:00
|
|
|
|
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-05-22 00:21:44 +00:00
|
|
|
/**
|
|
|
|
* The node number the user is currently looking at
|
|
|
|
* 0 if none
|
|
|
|
*/
|
|
|
|
NodeNum displayedNodeNum;
|
|
|
|
|
2020-12-14 12:50:01 +00:00
|
|
|
/// A usable (but bigger) version of the channel name in the channelSettings object
|
|
|
|
const char *channelName;
|
|
|
|
|
|
|
|
/// A usable psk - which has been constructed based on the (possibly short psk) in channelSettings
|
|
|
|
static uint8_t activePSK[32];
|
|
|
|
static uint8_t activePSKSize;
|
|
|
|
|
2020-08-12 18:04:03 +00:00
|
|
|
/**
|
|
|
|
* Generate a short suffix used to disambiguate channels that might have the same "name" entered by the human but different PSKs.
|
|
|
|
* The ideas is that the PSK changing should be visible to the user so that they see they probably messed up and that's why they
|
|
|
|
their nodes
|
|
|
|
* aren't talking to each other.
|
|
|
|
*
|
2020-12-15 05:14:36 +00:00
|
|
|
* This string is of the form "#name-X".
|
2020-08-12 18:04:03 +00:00
|
|
|
*
|
2020-12-15 05:14:36 +00:00
|
|
|
* Where X is either:
|
|
|
|
* (for custom PSKS) a letter from A to Z (base26), and formed by xoring all the bytes of the PSK together,
|
|
|
|
* OR (for the standard minimially secure PSKs) a number from 0 to 9.
|
2020-08-12 18:04:03 +00:00
|
|
|
*
|
|
|
|
* This function will also need to be implemented in GUI apps that talk to the radio.
|
|
|
|
*
|
|
|
|
* https://github.com/meshtastic/Meshtastic-device/issues/269
|
|
|
|
*/
|
|
|
|
const char *getChannelName()
|
|
|
|
{
|
|
|
|
static char buf[32];
|
|
|
|
|
2020-12-15 05:14:36 +00:00
|
|
|
char suffix;
|
|
|
|
if(channelSettings.psk.size != 1) {
|
|
|
|
// We have a standard PSK, so generate a letter based hash.
|
|
|
|
uint8_t code = 0;
|
|
|
|
for (int i = 0; i < activePSKSize; i++)
|
|
|
|
code ^= activePSK[i];
|
2020-08-12 18:04:03 +00:00
|
|
|
|
2020-12-15 05:14:36 +00:00
|
|
|
suffix = 'A' + (code % 26);
|
|
|
|
} else {
|
|
|
|
suffix = '0' + channelSettings.psk.bytes[0];
|
|
|
|
}
|
|
|
|
|
|
|
|
snprintf(buf, sizeof(buf), "#%s-%c", channelName, suffix);
|
2020-08-12 18:04:03 +00:00
|
|
|
return buf;
|
|
|
|
}
|
|
|
|
|
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-09-19 18:19:42 +00:00
|
|
|
bool NodeDB::resetRadioConfig()
|
2020-02-07 17:36:15 +00:00
|
|
|
{
|
2020-09-19 18:19:42 +00:00
|
|
|
bool didFactoryReset = false;
|
|
|
|
|
2020-12-17 02:53:29 +00:00
|
|
|
radioGeneration++;
|
|
|
|
|
2020-05-10 02:08:04 +00:00
|
|
|
/// 16 bytes of random PSK for our _public_ default channel that all devices power up on (AES128)
|
2020-03-30 23:05:28 +00:00
|
|
|
static const uint8_t defaultpsk[] = {0xd4, 0xf1, 0xbb, 0x3a, 0x20, 0x29, 0x07, 0x59,
|
|
|
|
0xf0, 0xbc, 0xff, 0xab, 0xcf, 0x4e, 0x69, 0xbf};
|
|
|
|
|
2020-09-19 18:19:42 +00:00
|
|
|
if (radioConfig.preferences.factory_reset) {
|
|
|
|
DEBUG_MSG("Performing factory reset!\n");
|
|
|
|
installDefaultDeviceState();
|
|
|
|
didFactoryReset = true;
|
2020-10-06 01:43:00 +00:00
|
|
|
} else if (!channelSettings.psk.size) {
|
|
|
|
DEBUG_MSG("Setting default preferences!\n");
|
|
|
|
|
2020-03-31 00:02:41 +00:00
|
|
|
radioConfig.has_channel_settings = true;
|
|
|
|
radioConfig.has_preferences = true;
|
2020-03-30 23:05:28 +00:00
|
|
|
|
|
|
|
// radioConfig.modem_config = RadioConfig_ModemConfig_Bw125Cr45Sf128; // medium range and fast
|
2020-09-19 18:19:42 +00:00
|
|
|
// channelSettings.modem_config = ChannelSettings_ModemConfig_Bw500Cr45Sf128; // short range and fast, but wide
|
|
|
|
// bandwidth so incompatible radios can talk together
|
2020-03-30 23:05:28 +00:00
|
|
|
channelSettings.modem_config = ChannelSettings_ModemConfig_Bw125Cr48Sf4096; // slow and long range
|
|
|
|
|
2020-08-12 17:42:25 +00:00
|
|
|
channelSettings.tx_power = 0; // default
|
2020-12-14 12:50:01 +00:00
|
|
|
uint8_t defaultpskIndex = 1;
|
|
|
|
channelSettings.psk.bytes[0] = defaultpskIndex;
|
|
|
|
channelSettings.psk.size = 1;
|
|
|
|
strcpy(channelSettings.name, "");
|
|
|
|
}
|
|
|
|
|
2020-12-15 05:14:36 +00:00
|
|
|
// Convert the old string "Default" to our new short representation
|
2020-12-14 12:50:01 +00:00
|
|
|
if(strcmp(channelSettings.name, "Default") == 0)
|
|
|
|
*channelSettings.name = '\0';
|
|
|
|
|
|
|
|
// Convert the short "" representation for Default into a usable string
|
|
|
|
channelName = channelSettings.name;
|
2020-12-15 05:14:36 +00:00
|
|
|
if(!*channelName) { // emptystring
|
|
|
|
// Per mesh.proto spec, if bandwidth is specified we must ignore modemConfig enum, we assume that in that case
|
|
|
|
// the app fucked up and forgot to set channelSettings.name
|
|
|
|
channelName = "Unset";
|
|
|
|
if(channelSettings.bandwidth == 0) switch(channelSettings.modem_config) {
|
|
|
|
case ChannelSettings_ModemConfig_Bw125Cr45Sf128:
|
2020-12-15 08:13:16 +00:00
|
|
|
channelName = "Medium"; break;
|
2020-12-15 05:14:36 +00:00
|
|
|
case ChannelSettings_ModemConfig_Bw500Cr45Sf128:
|
2020-12-15 08:13:16 +00:00
|
|
|
channelName = "ShortFast"; break;
|
2020-12-15 05:14:36 +00:00
|
|
|
case ChannelSettings_ModemConfig_Bw31_25Cr48Sf512:
|
|
|
|
channelName = "LongAlt"; break;
|
|
|
|
case ChannelSettings_ModemConfig_Bw125Cr48Sf4096:
|
|
|
|
channelName = "LongSlow"; break;
|
|
|
|
default:
|
|
|
|
channelName = "Invalid"; break;
|
|
|
|
}
|
|
|
|
}
|
2020-12-14 12:50:01 +00:00
|
|
|
|
|
|
|
// Convert any old usage of the defaultpsk into our new short representation.
|
|
|
|
if(channelSettings.psk.size == sizeof(defaultpsk) &&
|
|
|
|
memcmp(channelSettings.psk.bytes, defaultpsk, sizeof(defaultpsk)) == 0) {
|
|
|
|
*channelSettings.psk.bytes = 1;
|
|
|
|
channelSettings.psk.size = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Convert the short single byte variants of psk into variant that can be used more generally
|
|
|
|
memcpy(activePSK, channelSettings.psk.bytes, channelSettings.psk.size);
|
|
|
|
activePSKSize = channelSettings.psk.size;
|
|
|
|
if(activePSKSize == 1) {
|
|
|
|
uint8_t pskIndex = activePSK[0];
|
|
|
|
DEBUG_MSG("Expanding short PSK #%d\n", pskIndex);
|
|
|
|
if(pskIndex == 0)
|
|
|
|
activePSKSize = 0; // Turn off encryption
|
|
|
|
else {
|
|
|
|
memcpy(activePSK, defaultpsk, sizeof(defaultpsk));
|
|
|
|
activePSKSize = sizeof(defaultpsk);
|
|
|
|
// Bump up the last byte of PSK as needed
|
|
|
|
uint8_t *last = activePSK + sizeof(defaultpsk) - 1;
|
|
|
|
*last = *last + pskIndex - 1; // index of 1 means no change vs defaultPSK
|
|
|
|
}
|
2020-03-30 23:05:28 +00:00
|
|
|
}
|
2020-04-17 21:30:42 +00:00
|
|
|
|
2020-05-10 02:08:04 +00:00
|
|
|
// Tell our crypto engine about the psk
|
2020-12-14 12:50:01 +00:00
|
|
|
crypto->setKey(activePSKSize, activePSK);
|
2020-05-10 02:08:04 +00:00
|
|
|
|
2020-04-17 21:30:42 +00:00
|
|
|
// temp hack for quicker testing
|
2020-10-24 00:16:15 +00:00
|
|
|
// devicestate.no_save = true;
|
2020-06-24 20:10:36 +00:00
|
|
|
if (devicestate.no_save) {
|
2020-06-24 23:14:38 +00:00
|
|
|
DEBUG_MSG("***** DEVELOPMENT MODE - DO NOT RELEASE *****\n");
|
2020-06-24 20:10:36 +00:00
|
|
|
|
|
|
|
// Sleep quite frequently to stress test the BLE comms, broadcast position every 6 mins
|
2020-11-14 02:07:25 +00:00
|
|
|
radioConfig.preferences.screen_on_secs = 10;
|
|
|
|
radioConfig.preferences.wait_bluetooth_secs = 10;
|
2020-06-24 20:10:36 +00:00
|
|
|
radioConfig.preferences.position_broadcast_secs = 6 * 60;
|
|
|
|
radioConfig.preferences.ls_secs = 60;
|
2020-10-15 08:11:40 +00:00
|
|
|
radioConfig.preferences.region = RegionCode_TW;
|
2020-11-14 02:07:25 +00:00
|
|
|
|
|
|
|
// Enter super deep sleep soon and stay there not very long
|
2020-11-28 04:10:19 +00:00
|
|
|
// radioConfig.preferences.mesh_sds_timeout_secs = 10;
|
|
|
|
// radioConfig.preferences.sds_secs = 60;
|
2020-06-24 20:10:36 +00:00
|
|
|
}
|
2020-09-19 18:19:42 +00:00
|
|
|
|
2020-10-29 05:26:36 +00:00
|
|
|
// Update the global myRegion
|
|
|
|
initRegion();
|
2020-11-28 04:10:19 +00:00
|
|
|
|
2020-09-19 18:19:42 +00:00
|
|
|
return didFactoryReset;
|
2020-03-30 23:05:28 +00:00
|
|
|
}
|
2020-02-04 17:00:17 +00:00
|
|
|
|
2020-06-16 22:26:30 +00:00
|
|
|
void NodeDB::installDefaultDeviceState()
|
|
|
|
{
|
2020-09-25 19:52:08 +00:00
|
|
|
// We try to preserve the region setting because it will really bum users out if we discard it
|
|
|
|
String oldRegion = myNodeInfo.region;
|
|
|
|
RegionCode oldRegionCode = radioConfig.preferences.region;
|
|
|
|
|
2020-06-16 22:26:30 +00:00
|
|
|
memset(&devicestate, 0, sizeof(devicestate));
|
|
|
|
|
2020-09-19 18:19:42 +00:00
|
|
|
*numNodes = 0; // Forget node DB
|
|
|
|
|
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.radio.has_channel_settings = true;
|
|
|
|
devicestate.radio.has_preferences = true;
|
2020-02-07 17:36:15 +00:00
|
|
|
devicestate.node_db_count = 0;
|
2020-06-16 22:26:30 +00:00
|
|
|
devicestate.receive_queue_count = 0; // Not yet implemented FIXME
|
2020-02-07 17:36:15 +00:00
|
|
|
|
2020-03-30 23:05:28 +00:00
|
|
|
resetRadioConfig();
|
2020-02-22 22:56:19 +00:00
|
|
|
|
2020-03-25 20:36:54 +00:00
|
|
|
// default to no GPS, until one has been found by probing
|
|
|
|
myNodeInfo.has_gps = false;
|
2020-06-03 20:15:45 +00:00
|
|
|
myNodeInfo.message_timeout_msec = FLOOD_EXPIRE_TIME;
|
|
|
|
generatePacketId(); // FIXME - ugly way to init current_packet_id;
|
2020-03-25 20:36:54 +00:00
|
|
|
|
2020-02-04 17:00:17 +00:00
|
|
|
// Init our blank owner info to reasonable defaults
|
2020-04-15 03:22:27 +00:00
|
|
|
getMacAddr(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
|
|
|
|
2020-06-08 00:22:07 +00:00
|
|
|
// Set default owner name
|
|
|
|
pickNewNodeNum(); // Note: we will repick later, just in case the settings are corrupted, but we need a valid
|
|
|
|
// owner.short_name now
|
|
|
|
sprintf(owner.long_name, "Unknown %02x%02x", ourMacAddr[4], ourMacAddr[5]);
|
2020-06-16 22:26:30 +00:00
|
|
|
sprintf(owner.short_name, "?%02X", (unsigned)(myNodeInfo.my_node_num & 0xff));
|
2020-09-25 19:52:08 +00:00
|
|
|
|
|
|
|
// Restore region if possible
|
|
|
|
if (oldRegionCode != RegionCode_Unset)
|
|
|
|
radioConfig.preferences.region = oldRegionCode;
|
|
|
|
if (oldRegion.length())
|
|
|
|
strcpy(myNodeInfo.region, oldRegion.c_str());
|
2020-06-16 22:02:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void NodeDB::init()
|
|
|
|
{
|
|
|
|
installDefaultDeviceState();
|
2020-06-03 20:51:53 +00:00
|
|
|
|
2020-02-07 17:36:15 +00:00
|
|
|
// saveToDisk();
|
|
|
|
loadFromDisk();
|
2020-05-25 17:07:42 +00:00
|
|
|
// saveToDisk();
|
2020-05-13 00:57:51 +00:00
|
|
|
|
2020-06-08 05:12:06 +00:00
|
|
|
// We set node_num and packet_id _after_ loading from disk, because we always want to use the values this
|
|
|
|
// rom was compiled for, not what happens to be in the save file.
|
|
|
|
myNodeInfo.node_num_bits = sizeof(NodeNum) * 8;
|
|
|
|
myNodeInfo.packet_id_bits = sizeof(PacketId) * 8;
|
|
|
|
|
2020-12-27 03:22:08 +00:00
|
|
|
myNodeInfo.error_code = CriticalErrorCode_None; // For the error code, only show values from this boot (discard value from flash)
|
2020-12-26 05:36:21 +00:00
|
|
|
myNodeInfo.error_address = 0;
|
|
|
|
|
2020-12-10 03:32:51 +00:00
|
|
|
// likewise - we always want the app requirements to come from the running appload
|
|
|
|
myNodeInfo.min_app_version = 20120; // format is Mmmss (where M is 1+the numeric major number. i.e. 20120 means 1.1.20
|
|
|
|
|
2020-06-06 15:30:01 +00:00
|
|
|
// Note! We do this after loading saved settings, so that if somehow an invalid nodenum was stored in preferences we won't
|
|
|
|
// keep using that nodenum forever. Crummy guess at our nodenum (but we will check against the nodedb to avoid conflicts)
|
|
|
|
pickNewNodeNum();
|
|
|
|
|
|
|
|
// Include our owner in the node db under our nodenum
|
|
|
|
NodeInfo *info = getOrCreateNode(getNodeNum());
|
|
|
|
info->user = owner;
|
|
|
|
info->has_user = true;
|
|
|
|
|
2020-05-13 00:57:51 +00:00
|
|
|
// We set these _after_ loading from disk - because they come from the build and are more trusted than
|
|
|
|
// what is stored in flash
|
2020-09-25 19:52:08 +00:00
|
|
|
if (xstr(HW_VERSION)[0])
|
|
|
|
strncpy(myNodeInfo.region, optstr(HW_VERSION), sizeof(myNodeInfo.region));
|
|
|
|
else
|
|
|
|
DEBUG_MSG("This build does not specify a HW_VERSION\n"); // Eventually new builds will no longer include this build flag
|
|
|
|
|
|
|
|
// Check for the old style of region code strings, if found, convert to the new enum.
|
|
|
|
// Those strings will look like "1.0-EU433"
|
|
|
|
if (radioConfig.preferences.region == RegionCode_Unset && strncmp(myNodeInfo.region, "1.0-", 4) == 0) {
|
|
|
|
const char *regionStr = myNodeInfo.region + 4; // EU433 or whatever
|
|
|
|
for (const RegionInfo *r = regions; r->code != RegionCode_Unset; r++)
|
|
|
|
if (strcmp(r->name, regionStr) == 0) {
|
|
|
|
radioConfig.preferences.region = r->code;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-13 00:57:51 +00:00
|
|
|
strncpy(myNodeInfo.firmware_version, optstr(APP_VERSION), sizeof(myNodeInfo.firmware_version));
|
|
|
|
strncpy(myNodeInfo.hw_model, HW_VENDOR, sizeof(myNodeInfo.hw_model));
|
|
|
|
|
2020-03-30 23:05:28 +00:00
|
|
|
resetRadioConfig(); // If bogus settings got saved, then fix them
|
2020-02-07 17:36:15 +00:00
|
|
|
|
2020-09-25 19:52:08 +00:00
|
|
|
DEBUG_MSG("legacy_region=%s, region=%d, NODENUM=0x%x, dbsize=%d\n", myNodeInfo.region, radioConfig.preferences.region,
|
|
|
|
myNodeInfo.my_node_num, *numNodes);
|
2020-02-07 17:36:15 +00:00
|
|
|
}
|
|
|
|
|
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()
|
|
|
|
{
|
2020-06-06 15:30:01 +00:00
|
|
|
NodeNum r = myNodeInfo.my_node_num;
|
|
|
|
|
|
|
|
// If we don't have a nodenum at app - pick an initial nodenum based on the macaddr
|
|
|
|
if (r == 0)
|
2020-11-28 01:56:21 +00:00
|
|
|
r = (ourMacAddr[2] << 24) | (ourMacAddr[3] << 16) | (ourMacAddr[4] << 8) | ourMacAddr[5];
|
2020-06-03 20:46:31 +00:00
|
|
|
|
2020-06-03 20:51:53 +00:00
|
|
|
if (r == NODENUM_BROADCAST || r < NUM_RESERVED)
|
2020-02-08 20:42:54 +00:00
|
|
|
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-04-15 03:22:27 +00:00
|
|
|
#ifdef FS
|
2020-06-16 22:02:11 +00:00
|
|
|
// static DeviceState scratch; We no longer read into a tempbuf because this structure is 15KB of valuable RAM
|
2020-02-08 15:41:04 +00:00
|
|
|
|
2020-05-25 17:07:42 +00:00
|
|
|
auto 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-06-16 22:02:11 +00:00
|
|
|
memset(&devicestate, 0, sizeof(devicestate));
|
|
|
|
if (!pb_decode(&stream, DeviceState_fields, &devicestate)) {
|
2020-02-07 17:36:15 +00:00
|
|
|
DEBUG_MSG("Error: can't decode protobuf %s\n", PB_GET_ERROR(&stream));
|
2020-06-16 22:26:30 +00:00
|
|
|
installDefaultDeviceState(); // Our in RAM copy might now be corrupt
|
2020-02-07 17:36:15 +00:00
|
|
|
// FIXME - report failure to phone
|
2020-03-19 02:15:51 +00:00
|
|
|
} else {
|
2020-06-16 22:02:11 +00:00
|
|
|
if (devicestate.version < DEVICESTATE_MIN_VER) {
|
2020-02-08 15:41:04 +00:00
|
|
|
DEBUG_MSG("Warn: devicestate is old, discarding\n");
|
2020-06-16 22:26:30 +00:00
|
|
|
installDefaultDeviceState();
|
|
|
|
} else {
|
2020-06-16 22:02:11 +00:00
|
|
|
DEBUG_MSG("Loaded saved preferences version %d\n", devicestate.version);
|
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");
|
|
|
|
}
|
2020-06-16 22:02:11 +00:00
|
|
|
|
2020-04-15 03:22:27 +00:00
|
|
|
#else
|
|
|
|
DEBUG_MSG("ERROR: Filesystem not implemented\n");
|
|
|
|
#endif
|
2020-02-07 17:36:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void NodeDB::saveToDisk()
|
|
|
|
{
|
2020-04-15 03:22:27 +00:00
|
|
|
#ifdef FS
|
2020-06-24 20:10:36 +00:00
|
|
|
if (!devicestate.no_save) {
|
|
|
|
auto f = FS.open(preftmp, FILE_O_WRITE);
|
|
|
|
if (f) {
|
|
|
|
DEBUG_MSG("Writing preferences\n");
|
2020-02-12 21:31:09 +00:00
|
|
|
|
2020-06-24 20:10:36 +00:00
|
|
|
pb_ostream_t stream = {&writecb, &f, SIZE_MAX, 0};
|
2020-02-12 21:31:09 +00:00
|
|
|
|
2020-06-24 20:10:36 +00:00
|
|
|
// DEBUG_MSG("Presave channel name=%s\n", channelSettings.name);
|
2020-02-07 17:36:15 +00:00
|
|
|
|
2020-06-24 20:10:36 +00:00
|
|
|
devicestate.version = DEVICESTATE_CUR_VER;
|
|
|
|
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
|
2020-02-07 17:36:15 +00:00
|
|
|
|
2020-06-24 20:10:36 +00:00
|
|
|
f.close();
|
|
|
|
} else {
|
|
|
|
// Success - replace the old file
|
|
|
|
f.close();
|
|
|
|
|
|
|
|
// brief window of risk here ;-)
|
|
|
|
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-06-06 15:30:01 +00:00
|
|
|
} else {
|
2020-06-24 20:10:36 +00:00
|
|
|
DEBUG_MSG("ERROR: can't write prefs\n"); // FIXME report to app
|
2020-06-06 15:30:01 +00:00
|
|
|
}
|
2020-03-19 02:15:51 +00:00
|
|
|
} else {
|
2020-06-24 23:14:38 +00:00
|
|
|
DEBUG_MSG("***** DEVELOPMENT MODE - DO NOT RELEASE - not saving to flash *****\n");
|
2020-02-07 17:36:15 +00:00
|
|
|
}
|
2020-04-15 03:22:27 +00:00
|
|
|
#else
|
|
|
|
DEBUG_MSG("ERROR filesystem not implemented\n");
|
|
|
|
#endif
|
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-05-04 18:15:05 +00:00
|
|
|
uint32_t now = 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-11-28 04:10:19 +00:00
|
|
|
#include "MeshPlugin.h"
|
|
|
|
|
2020-12-03 08:48:44 +00:00
|
|
|
/** Update position info for this node based on received position data
|
|
|
|
*/
|
2020-12-05 02:00:46 +00:00
|
|
|
void NodeDB::updatePosition(uint32_t nodeId, const Position &p)
|
|
|
|
{
|
|
|
|
NodeInfo *info = getOrCreateNode(nodeId);
|
|
|
|
|
2020-12-09 05:42:36 +00:00
|
|
|
DEBUG_MSG("DB update position node=0x%x time=%u, latI=%d, lonI=%d\n", nodeId, p.time, p.latitude_i, p.longitude_i);
|
|
|
|
|
2020-12-05 02:00:46 +00:00
|
|
|
info->position = p;
|
|
|
|
info->has_position = true;
|
|
|
|
updateGUIforNode = info;
|
|
|
|
notifyObservers(true); // Force an update whether or not our node counts have changed
|
|
|
|
}
|
2020-12-03 08:48:44 +00:00
|
|
|
|
2020-12-05 02:00:46 +00:00
|
|
|
/** Update user info for this node based on received user data
|
|
|
|
*/
|
|
|
|
void NodeDB::updateUser(uint32_t nodeId, const User &p)
|
|
|
|
{
|
2020-12-03 08:48:44 +00:00
|
|
|
NodeInfo *info = getOrCreateNode(nodeId);
|
|
|
|
|
2020-12-05 02:00:46 +00:00
|
|
|
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,
|
|
|
|
sizeof(info->user)); // Both of these blocks start as filled with zero so I think this is okay
|
|
|
|
|
|
|
|
info->user = p;
|
|
|
|
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;
|
|
|
|
|
|
|
|
if (changed) {
|
|
|
|
updateGUIforNode = info;
|
|
|
|
powerFSM.trigger(EVENT_NODEDB_UPDATED);
|
|
|
|
notifyObservers(true); // Force an update whether or not our node counts have changed
|
|
|
|
|
|
|
|
// Not really needed - we will save anyways when we go to sleep
|
|
|
|
// We just changed something important about the user, store our DB
|
|
|
|
// saveToDisk();
|
|
|
|
}
|
2020-12-03 08:48:44 +00:00
|
|
|
}
|
|
|
|
|
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-05-10 00:51:20 +00:00
|
|
|
if (mp.which_payload == MeshPacket_decoded_tag) {
|
|
|
|
const SubPacket &p = mp.decoded;
|
2020-04-17 00:32:36 +00:00
|
|
|
DEBUG_MSG("Update DB node 0x%x, rx_time=%u\n", mp.from, mp.rx_time);
|
2020-02-03 17:13:19 +00:00
|
|
|
|
2020-02-08 20:42:54 +00:00
|
|
|
NodeInfo *info = getOrCreateNode(mp.from);
|
2020-02-03 17:13:19 +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-04-30 19:37:58 +00:00
|
|
|
info->snr = mp.rx_snr; // keep the most recent SNR we received for this node.
|
|
|
|
|
2020-05-12 20:35:22 +00:00
|
|
|
switch (p.which_payload) {
|
2020-12-05 02:00:46 +00:00
|
|
|
case SubPacket_position_tag: {
|
|
|
|
// handle a legacy position packet
|
|
|
|
DEBUG_MSG("WARNING: Processing a (deprecated) position packet from %d\n", mp.from);
|
|
|
|
updatePosition(mp.from, p.position);
|
|
|
|
break;
|
|
|
|
}
|
2020-02-03 17:13:19 +00:00
|
|
|
|
2020-12-05 02:00:46 +00:00
|
|
|
case SubPacket_data_tag: {
|
|
|
|
if (mp.to == NODENUM_BROADCAST || mp.to == nodeDB.getNodeNum())
|
|
|
|
MeshPlugin::callPlugins(mp);
|
|
|
|
break;
|
|
|
|
}
|
2020-02-13 03:58:44 +00:00
|
|
|
|
2020-12-05 02:00:46 +00:00
|
|
|
case SubPacket_user_tag: {
|
|
|
|
DEBUG_MSG("WARNING: Processing a (deprecated) user packet from %d\n", mp.from);
|
|
|
|
updateUser(mp.from, p.user);
|
|
|
|
break;
|
|
|
|
}
|
2020-07-06 00:03:12 +00:00
|
|
|
|
2020-12-05 02:00:46 +00:00
|
|
|
default: {
|
|
|
|
notifyObservers(); // If the node counts have changed, notify observers
|
|
|
|
}
|
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)
|
|
|
|
{
|
2020-03-25 20:36:54 +00:00
|
|
|
DEBUG_MSG("NOTE! Recording critical error %d, address=%x\n", code, address);
|
2020-03-24 20:33:24 +00:00
|
|
|
myNodeInfo.error_code = code;
|
|
|
|
myNodeInfo.error_address = address;
|
|
|
|
myNodeInfo.error_count++;
|
|
|
|
}
|