From 3e87e60d43d3735f8b9bb0ad9d447282fc95b791 Mon Sep 17 00:00:00 2001 From: geeksville Date: Fri, 7 Feb 2020 09:36:15 -0800 Subject: [PATCH] save db to flash --- TODO.md | 2 +- mesh.proto | 1 + src/MeshRadio.cpp | 1 - src/MeshRadio.h | 2 +- src/MeshService.cpp | 1 + src/NodeDB.cpp | 116 +++++++++++++++++++++++++++++++++----- src/NodeDB.h | 16 ++++-- src/mesh-pb-constants.cpp | 31 ++++++++++ src/mesh-pb-constants.h | 8 ++- src/mesh.pb.h | 19 ++++--- 10 files changed, 169 insertions(+), 28 deletions(-) create mode 120000 mesh.proto diff --git a/TODO.md b/TODO.md index e4a510e9..8dfda1d5 100644 --- a/TODO.md +++ b/TODO.md @@ -4,7 +4,7 @@ * very occasionally send our position and user packet (if for nothing else so that other nodes update last_seen) * switch to my gui layout manager * make basic gui. different screens: debug, one page for each user in the user db, last received text message -* save our node db (and any rx packets waiting for phone) to flash - see DeviceState protobuf +* save our node db on entry to sleep # Medium priority diff --git a/mesh.proto b/mesh.proto new file mode 120000 index 00000000..4dafbaa0 --- /dev/null +++ b/mesh.proto @@ -0,0 +1 @@ +../MeshUtil/app/src/main/proto/mesh.proto \ No newline at end of file diff --git a/src/MeshRadio.cpp b/src/MeshRadio.cpp index 5d704dde..1d376fab 100644 --- a/src/MeshRadio.cpp +++ b/src/MeshRadio.cpp @@ -12,7 +12,6 @@ // Change to 434.0 or other frequency, must match RX's freq! FIXME, choose a better default value #define RF95_FREQ_US 902.0f -RadioConfig radioConfig = RadioConfig_init_zero; MeshRadio::MeshRadio(MemoryPool &_pool, PointerQueue &_rxDest) : rf95(NSS_GPIO, DIO0_GPIO), diff --git a/src/MeshRadio.h b/src/MeshRadio.h index cbd2c430..2b8b8194 100644 --- a/src/MeshRadio.h +++ b/src/MeshRadio.h @@ -59,4 +59,4 @@ private: void handleReceive(MeshPacket *p); }; -extern RadioConfig radioConfig; + diff --git a/src/MeshService.cpp b/src/MeshService.cpp index be8527c1..0477de1f 100644 --- a/src/MeshService.cpp +++ b/src/MeshService.cpp @@ -53,6 +53,7 @@ void MeshService::sendOurOwner(NodeNum dest) p->payload.which_variant = SubPacket_user_tag; User &u = p->payload.variant.user; u = owner; + DEBUG_MSG("sending owner %s/%s/%s\n", u.id, u.long_name, u.short_name); sendToMesh(p); } diff --git a/src/NodeDB.cpp b/src/NodeDB.cpp index 891639dc..73853c67 100644 --- a/src/NodeDB.cpp +++ b/src/NodeDB.cpp @@ -2,6 +2,9 @@ #include #include +#include "FS.h" +#include "SPIFFS.h" + #include #include #include "configuration.h" @@ -9,16 +12,22 @@ #include "NodeDB.h" #include "GPS.h" -MyNodeInfo myNodeInfo = MyNodeInfo_init_zero; NodeDB nodeDB; +// we have plenty of ram so statically alloc this tempbuf (for now) +DeviceState devicestate; +MyNodeInfo &myNodeInfo = devicestate.my_node; +RadioConfig &radioConfig = devicestate.radio; + +#define FS SPIFFS + /** * * 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). */ -User owner = {"+1650xxxyyyy", "unset name", "????", {0}}; +User &owner = devicestate.owner; static uint8_t ourMacAddr[6]; @@ -35,16 +44,22 @@ static NodeNum getDesiredNodeNum() return r; } - - -NodeDB::NodeDB() +NodeDB::NodeDB() : nodes(devicestate.node_db), numNodes(&devicestate.node_db_count) { } - -void NodeDB::init() { +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; + devicestate.has_radio = true; + devicestate.has_owner = true; + devicestate.node_db_count = 0; + devicestate.receive_queue_count = 0; + // Init our blank owner info to reasonable defaults sprintf(owner.id, "!%02x%02x%02x%02x%02x%02x", ourMacAddr[0], ourMacAddr[1], ourMacAddr[2], ourMacAddr[3], ourMacAddr[4], ourMacAddr[5]); @@ -59,11 +74,73 @@ void NodeDB::init() { info->user = owner; info->has_user = true; info->last_seen = 0; // haven't heard a real message yet + + 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); +} + +const char *preffile = "/db.proto"; +const char *preftmp = "/db.proto.tmp"; + +void NodeDB::loadFromDisk() +{ + File f = FS.open(preffile); + if (f) + { + DEBUG_MSG("Loading saved preferences\n"); + pb_istream_t stream = {&readcb, &f, DeviceState_size}; + + if (!pb_decode(&stream, DeviceState_fields, &devicestate)) + { + DEBUG_MSG("Error: can't decode protobuf %s\n", PB_GET_ERROR(&stream)); + // FIXME - report failure to phone + } + + 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"); + pb_ostream_t stream = {&writecb, &f, DeviceState_size, 0}; + + 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 ;-) + FS.remove(preffile); + FS.rename(preftmp, preffile); + } + else + { + DEBUG_MSG("ERROR: can't write prefs\n"); // FIXME report to app + } } const NodeInfo *NodeDB::readNextInfo() { - if (readPointer < numNodes) + if (readPointer < *numNodes) return &nodes[readPointer++]; else return NULL; @@ -79,10 +156,10 @@ void NodeDB::updateFrom(const MeshPacket &mp) 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; + int oldNumNodes = *numNodes; NodeInfo *info = getOrCreateNode(mp.from); - if (oldNumNodes != numNodes) + if (oldNumNodes != *numNodes) updateGUI = true; // we just created a nodeinfo info->last_seen = gps.getTime(); @@ -96,10 +173,23 @@ void NodeDB::updateFrom(const MeshPacket &mp) 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; + } default: break; // Ignore other packet types @@ -111,7 +201,7 @@ void NodeDB::updateFrom(const MeshPacket &mp) /// Find a node in our DB, return null for missing NodeInfo *NodeDB::getNode(NodeNum n) { - for (int i = 0; i < numNodes; i++) + for (int i = 0; i < *numNodes; i++) if (nodes[i].num == n) return &nodes[i]; @@ -126,8 +216,8 @@ NodeInfo *NodeDB::getOrCreateNode(NodeNum n) if (!info) { // add the node - assert(numNodes < MAX_NUM_NODES); - info = &nodes[numNodes++]; + assert(*numNodes < MAX_NUM_NODES); + info = &nodes[(*numNodes)++]; // everything is missing except the nodenum memset(info, 0, sizeof(*info)); diff --git a/src/NodeDB.h b/src/NodeDB.h index 67108658..fd810a43 100644 --- a/src/NodeDB.h +++ b/src/NodeDB.h @@ -6,8 +6,9 @@ #include "mesh-pb-constants.h" #include "MeshTypes.h" -extern MyNodeInfo myNodeInfo; -extern User owner; +extern MyNodeInfo &myNodeInfo; +extern RadioConfig &radioConfig; +extern User &owner; class NodeDB { @@ -16,8 +17,9 @@ class NodeDB // A NodeInfo for every node we've seen // Eventually use a smarter datastructure // HashMap nodes; - NodeInfo nodes[MAX_NUM_NODES]; - int numNodes = 0; + // Note: these two references just point into our static array we serialize to/from disk + NodeInfo *nodes; + pb_size_t *numNodes; bool updateGUI = false; // we think the gui should definitely be redrawn NodeInfo *updateGUIforNode = NULL; // if currently showing this node, we think you should update the GUI @@ -32,6 +34,9 @@ public: /// Called from service after app start, to do init which can only be done after OS load void init(); + /// write to flash + void saveToDisk(); + /// 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 updateFrom(const MeshPacket &p); @@ -60,6 +65,9 @@ private: /// Find a node in our DB, create an empty NodeInfo if missing NodeInfo *getOrCreateNode(NodeNum n); + + /// read our db from flash + void loadFromDisk(); }; extern NodeDB nodeDB; diff --git a/src/mesh-pb-constants.cpp b/src/mesh-pb-constants.cpp index 88c156dd..8bc5e8a4 100644 --- a/src/mesh-pb-constants.cpp +++ b/src/mesh-pb-constants.cpp @@ -4,6 +4,7 @@ #include #include #include +#include "FS.h" /// helper function for encoding a record as a protobuf, any failures to encode are fatal and we will panic /// returns the encoded packet size @@ -37,3 +38,33 @@ bool pb_decode_from_bytes(const uint8_t *srcbuf, size_t srcbufsize, const pb_msg return true; } } + + +/// Read from an Arduino File +bool readcb(pb_istream_t *stream, uint8_t *buf, size_t count) +{ + File *file = (File *)stream->state; + bool status; + + if (buf == NULL) + { + while (count-- && file->read() != EOF) + ; + return count == 0; + } + + status = (file->read(buf, count) == count); + + if (file->available() == 0) + stream->bytes_left = 0; + + return status; +} + + +/// Write to an arduino file +bool writecb(pb_ostream_t *stream, const uint8_t *buf, size_t count) +{ + File *file = (File*) stream->state; + return file->write(buf, count) == count; +} diff --git a/src/mesh-pb-constants.h b/src/mesh-pb-constants.h index 3320d9e8..ca92edf4 100644 --- a/src/mesh-pb-constants.h +++ b/src/mesh-pb-constants.h @@ -19,4 +19,10 @@ size_t pb_encode_to_bytes(uint8_t *destbuf, size_t destbufsize, const pb_msgdesc_t *fields, const void *src_struct); /// helper function for decoding a record as a protobuf, we will return false if the decoding failed -bool pb_decode_from_bytes(const uint8_t *srcbuf, size_t srcbufsize, const pb_msgdesc_t *fields, void *dest_struct); \ No newline at end of file +bool pb_decode_from_bytes(const uint8_t *srcbuf, size_t srcbufsize, const pb_msgdesc_t *fields, void *dest_struct); + +/// Read from an Arduino File +bool readcb(pb_istream_t *stream, uint8_t *buf, size_t count); + +/// Write to an arduino file +bool writecb(pb_ostream_t *stream, const uint8_t *buf, size_t count); diff --git a/src/mesh.pb.h b/src/mesh.pb.h index bb621150..b671c620 100644 --- a/src/mesh.pb.h +++ b/src/mesh.pb.h @@ -108,6 +108,8 @@ typedef struct _DeviceState { RadioConfig radio; bool has_my_node; MyNodeInfo my_node; + bool has_owner; + User owner; pb_size_t node_db_count; NodeInfo node_db[32]; pb_size_t receive_queue_count; @@ -151,7 +153,7 @@ typedef struct _ToRadio { #define RadioConfig_init_default {0, 0, 0, 0, _RadioConfig_ModemConfig_MIN, {0, {0}}, 0, 0} #define NodeInfo_init_default {0, false, User_init_default, false, Position_init_default, 0, 0} #define MyNodeInfo_init_default {0} -#define DeviceState_init_default {false, RadioConfig_init_default, false, MyNodeInfo_init_default, 0, {NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default}, 0, {MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default}} +#define DeviceState_init_default {false, RadioConfig_init_default, false, MyNodeInfo_init_default, false, User_init_default, 0, {NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default}, 0, {MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default}} #define FromRadio_init_default {0, 0, {MeshPacket_init_default}} #define ToRadio_init_default {0, {MeshPacket_init_default}} #define Position_init_zero {0, 0, 0, 0, 0} @@ -164,7 +166,7 @@ typedef struct _ToRadio { #define RadioConfig_init_zero {0, 0, 0, 0, _RadioConfig_ModemConfig_MIN, {0, {0}}, 0, 0} #define NodeInfo_init_zero {0, false, User_init_zero, false, Position_init_zero, 0, 0} #define MyNodeInfo_init_zero {0} -#define DeviceState_init_zero {false, RadioConfig_init_zero, false, MyNodeInfo_init_zero, 0, {NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero}, 0, {MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero}} +#define DeviceState_init_zero {false, RadioConfig_init_zero, false, MyNodeInfo_init_zero, false, User_init_zero, 0, {NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero}, 0, {MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero}} #define FromRadio_init_zero {0, 0, {MeshPacket_init_zero}} #define ToRadio_init_zero {0, {MeshPacket_init_zero}} @@ -208,8 +210,9 @@ typedef struct _ToRadio { #define MeshPacket_payload_tag 3 #define DeviceState_radio_tag 1 #define DeviceState_my_node_tag 2 -#define DeviceState_node_db_tag 3 -#define DeviceState_receive_queue_tag 4 +#define DeviceState_owner_tag 3 +#define DeviceState_node_db_tag 4 +#define DeviceState_receive_queue_tag 5 #define FromRadio_packet_tag 2 #define FromRadio_num_tag 1 #define ToRadio_packet_tag 1 @@ -303,12 +306,14 @@ X(a, STATIC, SINGULAR, INT32, my_node_num, 1) #define DeviceState_FIELDLIST(X, a) \ X(a, STATIC, OPTIONAL, MESSAGE, radio, 1) \ X(a, STATIC, OPTIONAL, MESSAGE, my_node, 2) \ -X(a, STATIC, REPEATED, MESSAGE, node_db, 3) \ -X(a, STATIC, REPEATED, MESSAGE, receive_queue, 4) +X(a, STATIC, OPTIONAL, MESSAGE, owner, 3) \ +X(a, STATIC, REPEATED, MESSAGE, node_db, 4) \ +X(a, STATIC, REPEATED, MESSAGE, receive_queue, 5) #define DeviceState_CALLBACK NULL #define DeviceState_DEFAULT NULL #define DeviceState_radio_MSGTYPE RadioConfig #define DeviceState_my_node_MSGTYPE MyNodeInfo +#define DeviceState_owner_MSGTYPE User #define DeviceState_node_db_MSGTYPE NodeInfo #define DeviceState_receive_queue_MSGTYPE MeshPacket @@ -365,7 +370,7 @@ extern const pb_msgdesc_t ToRadio_msg; #define RadioConfig_size 70 #define NodeInfo_size 151 #define MyNodeInfo_size 11 -#define DeviceState_size 9269 +#define DeviceState_size 9343 #define FromRadio_size 139 #define ToRadio_size 133