diff --git a/.vscode/settings.json b/.vscode/settings.json index 45052fa77..f78fe51b8 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -49,7 +49,8 @@ "string_view": "cpp", "cassert": "cpp", "iterator": "cpp", - "shared_mutex": "cpp" + "shared_mutex": "cpp", + "iostream": "cpp" }, "cSpell.words": [ "Blox", diff --git a/bin/program-release-universal.sh b/bin/program-release-universal.sh new file mode 100755 index 000000000..554f55256 --- /dev/null +++ b/bin/program-release-universal.sh @@ -0,0 +1,6 @@ + +set -e + +source bin/version.sh + +esptool.py --baud 921600 write_flash 0x10000 release/latest/bins/universal/firmware-tbeam-$VERSION.bin diff --git a/bin/regen-protos.sh b/bin/regen-protos.sh index 0f1ae43c6..817dc5d24 100755 --- a/bin/regen-protos.sh +++ b/bin/regen-protos.sh @@ -1,6 +1,12 @@ #!/bin/bash +set -e + echo "This script requires https://jpa.kapsi.fi/nanopb/download/ version 0.4.1" # the nanopb tool seems to require that the .options file be in the current directory! cd proto -../../nanopb-0.4.1-linux-x86/generator-bin/protoc --nanopb_out=-v:../src/mesh -I=../proto mesh.proto +../../nanopb-0.4.1-linux-x86/generator-bin/protoc --nanopb_out=-v:../src/mesh -I=../proto *.proto + +echo "Regenerating protobuf documentation - if you see an error message" +echo "you can ignore it unless doing a new protobuf release to github." +bin/regen-docs.sh \ No newline at end of file diff --git a/docs/software/TODO.md b/docs/software/TODO.md index 4173296e1..822fbd4a4 100644 --- a/docs/software/TODO.md +++ b/docs/software/TODO.md @@ -2,27 +2,89 @@ You probably don't care about this section - skip to the next one. +For app cleanup: + +* do fixed position bug https://github.com/meshtastic/Meshtastic-device/issues/536 +* check build guide +* generate autodocs +* write user guide +* DONE update android code: https://developer.android.com/topic/libraries/view-binding/migration +* make gpio watch work, use thread and setup +* make hello world example service +* make python ping command +* make python gpio read a bit cleaner +* DONE have python tool check max packet size before sending to device +* DONE if request was sent reliably, send reply reliably +* DONE require a recent python api to talk to these new device loads +* DONE require a recent android app to talk to these new device loads +* DONE fix handleIncomingPosition +* DONE move want_replies handling into plugins +* DONE on android for received positions handle either old or new positions / user messages +* on android side send old or new positions as needed / user messages +* test python side handle new position/user messages +* DONE make a gpio example. --gpiowrb 4 1, --gpiord 0x444, --gpiowatch 0x3ff +* DONE fix position sending to use new plugin +* DONE Add SinglePortNumPlugin - as the new most useful baseclass +* DONE move positions into regular data packets (use new app framework) +* DONE move user info into regular data packets (use new app framework) +* test that positions, text messages and user info still work +* test that position, text messages and user info work properly with new android app and old device code +* fix the RTC drift bug +* move ping functionality into device, reply with rxsnr info +* use channels for gpio security https://github.com/meshtastic/Meshtastic-device/issues/104 +* implement GPIO watch + For high speed/lots of devices/short range tasks: - When guessing numhops for sending: if I've heard from many local (0 hop neighbors) decrease hopcount by 2 rather than 1. This should nicely help 'router' nodes do the right thing when long range, or if there are many local nodes for short range. - fix timeouts/delays to be based on packet length at current radio settings -Nimble tasks: - -- readerror.txt stress test bug -- started RPA long test, jul 22 6pm -- implement nimble software update api -- update to latest bins, test OTA again (measure times) and then checkin bins -- do alpha release - -* update protocol description per cyclomies email thread * update faq with antennas https://meshtastic.discourse.group/t/range-test-ideas-requested/738/2 * update faq on recommended android version and phones * add help link inside the app, reference a page on the wiki * turn on amazon reviews support * add a tablet layout (with map next to messages) in the android app +# Old docs to merge + +MESH RADIO PROTOCOL + +Old TODO notes on the mesh radio protocol, merge into real docs someday... + +for each named group we have a pre-shared key known by all group members and +wrapped around the device. you can only be in one group at a time (FIXME?!) To +join the group we read a qr code with the preshared key and ParamsCodeEnum. that +gets sent via bluetooth to the device. ParamsCodeEnum maps to a set of various +radio params (regulatory region, center freq, SF, bandwidth, bitrate, power +etc...) so all members of the mesh can have their radios set the same way. + +once in that group, we can talk between 254 node numbers. +to get our node number (and announce our presence in the channel) we pick a +random node number and broadcast as that node with WANT-NODENUM(my globally +unique name). If anyone on the channel has seen someone _else_ using that name +within the last 24 hrs(?) they reply with DENY-NODENUM. Note: we might receive +multiple denies. Note: this allows others to speak up for some other node that +might be saving battery right now. Any time we hear from another node (for any +message type), we add that node number to the unpickable list. To dramatically +decrease the odds a node number we request is already used by someone. If no one +denies within TBD seconds, we assume that we have that node number. As long as +we keep talking to folks at least once every 24 hrs, others should remember we +have it. + +Once we have a node number we can broadcast POSITION-UPDATE(my globally unique +name, lat, lon, alt, amt battery remaining). All receivers will use this to a) +update the mapping of who is at what node nums, b) the time of last rx, c) +position. If we haven't heard from that node in a while we reply to that node +(only) with our current POSITION_UPDATE state - so that node (presumably just +rejoined the network) can build a map of all participants. + +We will periodically broadcast POSITION-UPDATE as needed based on distance moved +or a periodic minimum heartbeat. + +If user wants to send a text they can SEND_TEXT(dest user, short text message). +Dest user is a node number, or 0xff for broadcast. + # Medium priority Items to complete before 1.0. diff --git a/proto b/proto index a0b8d8889..ebd18145c 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit a0b8d888961720d70ab7467c94d8fa0687e58020 +Subproject commit ebd18145cafb0d09528150b7a5eccd52b581902f diff --git a/src/SerialConsole.cpp b/src/SerialConsole.cpp index f246b607c..9bb539fa5 100644 --- a/src/SerialConsole.cpp +++ b/src/SerialConsole.cpp @@ -1,6 +1,7 @@ #include "SerialConsole.h" #include "PowerFSM.h" #include "configuration.h" +#include "NodeDB.h" #include #define Port Serial @@ -28,7 +29,8 @@ void SerialConsole::init() void SerialConsole::handleToRadio(const uint8_t *buf, size_t len) { // Turn off debug serial printing once the API is activated, because other threads could print and corrupt packets - setDestination(&noopPrint); + if(!radioConfig.preferences.debug_log_enabled) + setDestination(&noopPrint); canWrite = true; StreamAPI::handleToRadio(buf, len); diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index 0cd5c1bbb..320c0f1e2 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -32,6 +32,7 @@ along with this program. If not, see . #include "main.h" #include "mesh-pb-constants.h" #include "meshwifi/meshwifi.h" +#include "plugins/TextMessagePlugin.h" #include "target_specific.h" #include "utils.h" @@ -720,6 +721,7 @@ void Screen::setup() powerStatusObserver.observe(&powerStatus->onNewStatus); gpsStatusObserver.observe(&gpsStatus->onNewStatus); nodeStatusObserver.observe(&nodeStatus->onNewStatus); + textMessageObserver.observe(&textMessagePlugin); } void Screen::forceDisplay() @@ -1183,14 +1185,24 @@ int Screen::handleStatusUpdate(const meshtastic::Status *arg) // DEBUG_MSG("Screen got status update %d\n", arg->getStatusType()); switch (arg->getStatusType()) { case STATUS_TYPE_NODE: - if (showingNormalScreen && (nodeDB.updateTextMessage || nodeStatus->getLastNumTotal() != nodeStatus->getNumTotal())) { + if (showingNormalScreen && + nodeStatus->getLastNumTotal() != nodeStatus->getNumTotal()) { setFrames(); // Regen the list of screens } nodeDB.updateGUI = false; - nodeDB.updateTextMessage = false; break; } return 0; } + +int Screen::handleTextMessage(const MeshPacket *arg) +{ + if (showingNormalScreen) { + setFrames(); // Regen the list of screens (will show new text message) + } + + return 0; +} + } // namespace graphics diff --git a/src/graphics/Screen.h b/src/graphics/Screen.h index d0bf62286..90e8ad089 100644 --- a/src/graphics/Screen.h +++ b/src/graphics/Screen.h @@ -77,6 +77,8 @@ class Screen : public concurrency::OSThread CallbackObserver(this, &Screen::handleStatusUpdate); CallbackObserver nodeStatusObserver = CallbackObserver(this, &Screen::handleStatusUpdate); + CallbackObserver textMessageObserver = + CallbackObserver(this, &Screen::handleTextMessage); public: Screen(uint8_t address, int sda = -1, int scl = -1); @@ -192,6 +194,7 @@ class Screen : public concurrency::OSThread DebugInfo *debug_info() { return &debugInfo; } int handleStatusUpdate(const meshtastic::Status *arg); + int handleTextMessage(const MeshPacket *arg); /// Used to force (super slow) eink displays to draw critical frames void forceDisplay(); diff --git a/src/mesh/MeshPlugin.cpp b/src/mesh/MeshPlugin.cpp new file mode 100644 index 000000000..5320b7ae1 --- /dev/null +++ b/src/mesh/MeshPlugin.cpp @@ -0,0 +1,69 @@ +#include "MeshPlugin.h" +#include "NodeDB.h" +#include "MeshService.h" +#include + +std::vector *MeshPlugin::plugins; + +MeshPlugin::MeshPlugin(const char *_name) : name(_name) +{ + // Can't trust static initalizer order, so we check each time + if(!plugins) + plugins = new std::vector(); + + plugins->push_back(this); +} + +void MeshPlugin::setup() { +} + +MeshPlugin::~MeshPlugin() +{ + assert(0); // FIXME - remove from list of plugins once someone needs this feature +} + +void MeshPlugin::callPlugins(const MeshPacket &mp) +{ + // DEBUG_MSG("In call plugins\n"); + for (auto i = plugins->begin(); i != plugins->end(); ++i) { + auto &pi = **i; + if (pi.wantPortnum(mp.decoded.data.portnum)) { + bool handled = pi.handleReceived(mp); + + // Possibly send replies (unless we are handling a locally generated message) + if (mp.decoded.want_response && mp.from != nodeDB.getNodeNum()) + pi.sendResponse(mp); + + DEBUG_MSG("Plugin %s handled=%d\n", pi.name, handled); + if (handled) + break; + } + else { + DEBUG_MSG("Plugin %s not interested\n", pi.name); + } + } +} + +/** Messages can be received that have the want_response bit set. If set, this callback will be invoked + * so that subclasses can (optionally) send a response back to the original sender. Implementing this method + * is optional + */ +void MeshPlugin::sendResponse(const MeshPacket &req) { + auto r = allocReply(); + if(r) { + DEBUG_MSG("Sending response\n"); + setReplyTo(r, req); + service.sendToMesh(r); + } + else { + DEBUG_MSG("WARNING: Client requested response but this plugin did not provide\n"); + } +} + +/** set the destination and packet parameters of packet p intended as a reply to a particular "to" packet + * This ensures that if the request packet was sent reliably, the reply is sent that way as well. +*/ +void setReplyTo(MeshPacket *p, const MeshPacket &to) { + p->to = to.from; + p->want_ack = to.want_ack; +} \ No newline at end of file diff --git a/src/mesh/MeshPlugin.h b/src/mesh/MeshPlugin.h new file mode 100644 index 000000000..f753651ec --- /dev/null +++ b/src/mesh/MeshPlugin.h @@ -0,0 +1,67 @@ +#pragma once + +#include "mesh/MeshTypes.h" +#include +/** A baseclass for any mesh "plugin". + * + * A plugin allows you to add new features to meshtastic device code, without needing to know messaging details. + * + * A key concept for this is that your plugin should use a particular "portnum" for each message type you want to receive + * and handle. + * + * Interally we use plugins to implement the core meshtastic text messaging and gps position sharing features. You + * can use these classes as examples for how to write your own custom plugin. See here: (FIXME) + */ +class MeshPlugin +{ + static std::vector *plugins; + + public: + /** Constructor + * name is for debugging output + */ + MeshPlugin(const char *_name); + + virtual ~MeshPlugin(); + + /** For use only by MeshService + */ + static void callPlugins(const MeshPacket &mp); + + protected: + const char *name; + + /** + * Initialize your plugin. This setup function is called once after all hardware and mesh protocol layers have + * been initialized + */ + virtual void setup(); + + /** + * @return true if you want to receive the specified portnum + */ + virtual bool wantPortnum(PortNum p) = 0; + + /** Called to handle a particular incoming message + + @return true if you've guaranteed you've handled this message and no other handlers should be considered for it + */ + virtual bool handleReceived(const MeshPacket &mp) { return false; } + + /** Messages can be received that have the want_response bit set. If set, this callback will be invoked + * so that subclasses can (optionally) send a response back to the original sender. */ + virtual MeshPacket *allocReply() { return NULL; } + + private: + + /** Messages can be received that have the want_response bit set. If set, this callback will be invoked + * so that subclasses can (optionally) send a response back to the original sender. This method calls allocReply() + * to generate the reply message, and if !NULL that message will be delivered to whoever sent req + */ + void sendResponse(const MeshPacket &req); +}; + +/** set the destination and packet parameters of packet p intended as a reply to a particular "to" packet + * This ensures that if the request packet was sent reliably, the reply is sent that way as well. +*/ +void setReplyTo(MeshPacket *p, const MeshPacket &to); \ No newline at end of file diff --git a/src/mesh/MeshService.cpp b/src/mesh/MeshService.cpp index 77c0f2c92..1c06406f6 100644 --- a/src/mesh/MeshService.cpp +++ b/src/mesh/MeshService.cpp @@ -13,6 +13,8 @@ #include "RTC.h" #include "main.h" #include "mesh-pb-constants.h" +#include "plugins/PositionPlugin.h" +#include "plugins/NodeInfoPlugin.h" #include "power.h" /* @@ -51,7 +53,7 @@ MeshService service; static int32_t sendOwnerCb() { - service.sendOurOwner(); + nodeInfoPlugin.sendOurNodeInfo(); return getPref_send_owner_interval() * getPref_position_broadcast_secs() * 1000; } @@ -74,113 +76,25 @@ void MeshService::init() packetReceivedObserver.observe(&router->notifyPacketReceived); } -void MeshService::sendOurOwner(NodeNum dest, bool wantReplies) -{ - MeshPacket *p = router->allocForSending(); - p->to = dest; - p->decoded.want_response = wantReplies; - p->decoded.which_payload = SubPacket_user_tag; - User &u = p->decoded.user; - u = owner; - DEBUG_MSG("sending owner %s/%s/%s\n", u.id, u.long_name, u.short_name); - - sendToMesh(p); -} - -/// handle a user packet that just arrived on the radio, return NULL if we should not process this packet at all -const MeshPacket *MeshService::handleFromRadioUser(const MeshPacket *mp) -{ - bool wasBroadcast = mp->to == NODENUM_BROADCAST; - - // Disable this collision testing if we use 32 bit nodenums - bool isCollision = (sizeof(NodeNum) == 1) && (mp->from == myNodeInfo.my_node_num); - - if (isCollision) { - // we win if we have a lower macaddr - bool weWin = memcmp(&owner.macaddr, &mp->decoded.user.macaddr, sizeof(owner.macaddr)) < 0; - - if (weWin) { - DEBUG_MSG("NOTE! Received a nodenum collision and we are vetoing\n"); - - 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->decoded.user.long_name + "\n"; - screen->print(lcd.c_str()); - } - - return mp; -} - -void MeshService::handleIncomingPosition(const MeshPacket *mp) -{ - if (mp->which_payload == MeshPacket_decoded_tag && mp->decoded.which_payload == SubPacket_position_tag) { - DEBUG_MSG("handled incoming position time=%u\n", mp->decoded.position.time); - - if (mp->decoded.position.time) { - struct timeval tv; - uint32_t secs = mp->decoded.position.time; - - tv.tv_sec = secs; - tv.tv_usec = 0; - - perhapsSetRTC(RTCQualityFromNet, &tv); - } - } else { - DEBUG_MSG("Ignoring incoming packet - not a position\n"); - } -} int MeshService::handleFromRadio(const MeshPacket *mp) { powerFSM.trigger(EVENT_RECEIVED_PACKET); // Possibly keep the node from sleeping - // If it is a position packet, perhaps set our clock - this must be before nodeDB.updateFrom - handleIncomingPosition(mp); + printPacket("Forwarding to phone", mp); + nodeDB.updateFrom(*mp); // update our DB state based off sniffing every RX packet from the radio - if (mp->which_payload == MeshPacket_decoded_tag && mp->decoded.which_payload == SubPacket_user_tag) { - mp = handleFromRadioUser(mp); + fromNum++; + + if (toPhoneQueue.numFree() == 0) { + DEBUG_MSG("NOTE: tophone queue is full, discarding oldest\n"); + MeshPacket *d = toPhoneQueue.dequeuePtr(0); + if (d) + releaseToPool(d); } - // 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) { - printPacket("Forwarding to phone", mp); - nodeDB.updateFrom(*mp); // update our DB state based off sniffing every RX packet from the radio - - fromNum++; - - if (toPhoneQueue.numFree() == 0) { - DEBUG_MSG("NOTE: tophone queue is full, discarding oldest\n"); - MeshPacket *d = toPhoneQueue.dequeuePtr(0); - if (d) - releaseToPool(d); - } - - MeshPacket *copied = packetPool.allocCopy(*mp); - assert(toPhoneQueue.enqueue(copied, 0)); // FIXME, instead of failing for full queue, delete the oldest mssages - - if (mp->decoded.want_response) - sendNetworkPing(mp->from); - } else { - DEBUG_MSG("Not delivering vetoed User message\n"); - } + MeshPacket *copied = packetPool.allocCopy(*mp); + assert(toPhoneQueue.enqueue(copied, 0)); // FIXME, instead of failing for full queue, delete the oldest mssages return 0; } @@ -201,7 +115,7 @@ bool MeshService::reloadConfig() // This will also update the region as needed bool didReset = nodeDB.resetRadioConfig(); // Don't let the phone send us fatally bad settings - + configChanged.notifyObservers(NULL); nodeDB.saveToDisk(); @@ -211,7 +125,7 @@ bool MeshService::reloadConfig() /// The owner User record just got updated, update our node DB and broadcast the info into the mesh void MeshService::reloadOwner() { - sendOurOwner(); + nodeInfoPlugin.sendOurNodeInfo(); nodeDB.saveToDisk(); } @@ -222,8 +136,6 @@ void MeshService::reloadOwner() */ void MeshService::handleToRadio(MeshPacket &p) { - handleIncomingPosition(&p); // If it is a position packet, perhaps set our clock - if (p.from == 0) // If the phone didn't set a sending node ID, use ours p.from = nodeDB.getNodeNum(); @@ -253,7 +165,8 @@ void MeshService::sendToMesh(MeshPacket *p) // Strip out any time information before sending packets to other nodes - to keep the wire size small (and because other // nodes shouldn't trust it anyways) Note: we allow a device with a local GPS to include the time, so that gpsless // devices can get time. - if (p->which_payload == MeshPacket_decoded_tag && p->decoded.which_payload == SubPacket_position_tag) { + if (p->which_payload == MeshPacket_decoded_tag && p->decoded.which_payload == SubPacket_position_tag && + p->decoded.position.time) { if (getRTCQuality() < RTCQualityGPS) { DEBUG_MSG("Stripping time %u from position send\n", p->decoded.position.time); p->decoded.position.time = 0; @@ -272,36 +185,16 @@ void MeshService::sendNetworkPing(NodeNum dest, bool wantReplies) DEBUG_MSG("Sending network ping to 0x%x, with position=%d, wantReplies=%d\n", dest, node->has_position, wantReplies); if (node->has_position) - sendOurPosition(dest, wantReplies); + positionPlugin.sendOurPosition(dest, wantReplies); else - sendOurOwner(dest, wantReplies); -} - -void MeshService::sendOurPosition(NodeNum dest, bool wantReplies) -{ - NodeInfo *node = nodeDB.getNode(nodeDB.getNodeNum()); - assert(node); - assert(node->has_position); - - // Update our local node info with our position (even if we don't decide to update anyone else) - MeshPacket *p = router->allocForSending(); - p->to = dest; - p->decoded.which_payload = SubPacket_position_tag; - p->decoded.position = node->position; - p->decoded.want_response = wantReplies; - p->decoded.position.time = - getValidTime(RTCQualityGPS); // This nodedb timestamp might be stale, so update it if our clock is valid. - sendToMesh(p); + nodeInfoPlugin.sendOurNodeInfo(dest, wantReplies); } int MeshService::onGPSChanged(const meshtastic::GPSStatus *unused) { - // Update our local node info with our position (even if we don't decide to update anyone else) - MeshPacket *p = router->allocForSending(); - p->decoded.which_payload = SubPacket_position_tag; - Position &pos = p->decoded.position; + Position pos = Position_init_default; if (gps->hasLock()) { if (gps->altitude != 0) @@ -309,6 +202,17 @@ int MeshService::onGPSChanged(const meshtastic::GPSStatus *unused) pos.latitude_i = gps->latitude; pos.longitude_i = gps->longitude; } + else { + // The GPS has lost lock, if we are fixed position we should just keep using + // the old position + if(radioConfig.preferences.fixed_position) { + NodeInfo *node = nodeDB.getNode(nodeDB.getNodeNum()); + assert(node); + assert(node->has_position); + pos = node->position; + DEBUG_MSG("WARNING: Using fixed position\n"); + } + } pos.time = getValidTime(RTCQualityGPS); @@ -316,21 +220,18 @@ int MeshService::onGPSChanged(const meshtastic::GPSStatus *unused) pos.battery_level = powerStatus->getBatteryChargePercent(); updateBatteryLevel(pos.battery_level); - // DEBUG_MSG("got gps notify time=%u, lat=%d, bat=%d\n", pos.latitude_i, pos.time, pos.battery_level); + DEBUG_MSG("got gps notify time=%u, lat=%d, bat=%d\n", pos.latitude_i, pos.time, pos.battery_level); + + // Update our current position in the local DB + nodeDB.updatePosition(nodeDB.getNodeNum(), pos); // We limit our GPS broadcasts to a max rate static uint32_t lastGpsSend; uint32_t now = millis(); if (lastGpsSend == 0 || now - lastGpsSend > getPref_position_broadcast_secs() * 1000) { lastGpsSend = now; - DEBUG_MSG("Sending position to mesh\n"); - - sendToMesh(p); - } else { - // We don't need to send this packet to anyone else, but it still serves as a nice uniform way to update our local state - nodeDB.updateFrom(*p); - - releaseToPool(p); + DEBUG_MSG("Sending position to mesh (not requesting replies)\n"); + positionPlugin.sendOurPosition(); } return 0; diff --git a/src/mesh/MeshService.h b/src/mesh/MeshService.h index a30f3d4ec..b7cfd2432 100644 --- a/src/mesh/MeshService.h +++ b/src/mesh/MeshService.h @@ -75,18 +75,13 @@ class MeshService /// sends our owner void sendNetworkPing(NodeNum dest, bool wantReplies = false); - /// Send our owner info to a particular node - void sendOurOwner(NodeNum dest = NODENUM_BROADCAST, bool wantReplies = false); - - private: - /// Broadcasts our last known position - void sendOurPosition(NodeNum dest = NODENUM_BROADCAST, bool wantReplies = false); - /// Send a packet into the mesh - note p must have been allocated from packetPool. We will return it to that pool after /// sending. This is the ONLY function you should use for sending messages into the mesh, because it also updates the nodedb /// cache void sendToMesh(MeshPacket *p); + private: + /// Called when our gps position has changed - updates nodedb and sends Location message out into the mesh /// returns 0 to allow futher processing int onGPSChanged(const meshtastic::GPSStatus *arg); @@ -94,12 +89,6 @@ class MeshService /// Handle a packet that just arrived from the radio. This method does _not_ free the provided packet. If it needs /// to keep the packet around it makes a copy int handleFromRadio(const MeshPacket *p); - - /// handle a user packet that just arrived on the radio, return NULL if we should not process this packet at all - const MeshPacket *handleFromRadioUser(const MeshPacket *mp); - - /// look at inbound packets and if they contain a position with time, possibly set our clock - void handleIncomingPosition(const MeshPacket *mp); }; extern MeshService service; diff --git a/src/mesh/MeshTypes.h b/src/mesh/MeshTypes.h index 7c58b2e3e..38f18c3a4 100644 --- a/src/mesh/MeshTypes.h +++ b/src/mesh/MeshTypes.h @@ -9,7 +9,7 @@ typedef uint32_t NodeNum; typedef uint32_t PacketId; // A packet sequence number -#define NODENUM_BROADCAST (sizeof(NodeNum) == 4 ? UINT32_MAX : UINT8_MAX) +#define NODENUM_BROADCAST UINT32_MAX #define ERRNO_OK 0 #define ERRNO_NO_INTERFACES 33 #define ERRNO_UNKNOWN 32 // pick something that doesn't conflict with RH_ROUTER_ERROR_UNABLE_TO_DELIVER diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 517845b9b..de21cb58f 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -131,13 +131,13 @@ bool NodeDB::resetRadioConfig() radioConfig.preferences.region = RegionCode_TW; // Enter super deep sleep soon and stay there not very long - //radioConfig.preferences.mesh_sds_timeout_secs = 10; - //radioConfig.preferences.sds_secs = 60; + // radioConfig.preferences.mesh_sds_timeout_secs = 10; + // radioConfig.preferences.sds_secs = 60; } // Update the global myRegion initRegion(); - + return didFactoryReset; } @@ -165,7 +165,7 @@ void NodeDB::installDefaultDeviceState() // default to no GPS, until one has been found by probing myNodeInfo.has_gps = false; myNodeInfo.message_timeout_msec = FLOOD_EXPIRE_TIME; - myNodeInfo.min_app_version = 172; + myNodeInfo.min_app_version = 20120; // format is Mmmss (where M is 1+the numeric major number. i.e. 20120 means 1.1.20 generatePacketId(); // FIXME - ugly way to init current_packet_id; // Init our blank owner info to reasonable defaults @@ -248,8 +248,7 @@ void NodeDB::pickNewNodeNum() // If we don't have a nodenum at app - pick an initial nodenum based on the macaddr if (r == 0) - r = sizeof(NodeNum) == 1 ? ourMacAddr[5] - : ((ourMacAddr[2] << 24) | (ourMacAddr[3] << 16) | (ourMacAddr[4] << 8) | ourMacAddr[5]); + r = (ourMacAddr[2] << 24) | (ourMacAddr[3] << 16) | (ourMacAddr[4] << 8) | ourMacAddr[5]; if (r == NODENUM_BROADCAST || r < NUM_RESERVED) r = NUM_RESERVED; // don't pick a reserved node number @@ -379,6 +378,48 @@ size_t NodeDB::getNumOnlineNodes() return numseen; } +#include "MeshPlugin.h" + +/** Update position info for this node based on received position data + */ +void NodeDB::updatePosition(uint32_t nodeId, const Position &p) +{ + NodeInfo *info = getOrCreateNode(nodeId); + + DEBUG_MSG("DB update position node=0x%x time=%u, latI=%d, lonI=%d\n", nodeId, p.time, p.latitude_i, p.longitude_i); + + info->position = p; + info->has_position = true; + updateGUIforNode = info; + notifyObservers(true); // Force an update whether or not our node counts have changed +} + +/** Update user info for this node based on received user data + */ +void NodeDB::updateUser(uint32_t nodeId, const User &p) +{ + NodeInfo *info = getOrCreateNode(nodeId); + + 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(); + } +} + /// 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) @@ -398,58 +439,21 @@ void NodeDB::updateFrom(const MeshPacket &mp) switch (p.which_payload) { case SubPacket_position_tag: { - // we always trust our local timestamps more - info->position = p.position; - if (mp.rx_time) - info->position.time = mp.rx_time; - info->has_position = true; - updateGUIforNode = info; - notifyObservers(true); // Force an update whether or not our node counts have changed + // handle a legacy position packet + DEBUG_MSG("WARNING: Processing a (deprecated) position packet from %d\n", mp.from); + updatePosition(mp.from, p.position); break; } case SubPacket_data_tag: { - // Keep a copy of the most recent text message. - if (p.data.typ == Data_Type_CLEAR_TEXT) { - DEBUG_MSG("Received text msg from=0x%0x, id=%d, msg=%.*s\n", mp.from, mp.id, p.data.payload.size, - p.data.payload.bytes); - if (mp.to == NODENUM_BROADCAST || mp.to == nodeDB.getNodeNum()) { - // We only store/display messages destined for us. - devicestate.rx_text_message = mp; - devicestate.has_rx_text_message = true; - updateTextMessage = true; - powerFSM.trigger(EVENT_RECEIVED_TEXT_MSG); - notifyObservers(true); // Force an update whether or not our node counts have changed - - // This is going into the wifidev feature branch - // Only update the WebUI if WiFi is enabled - //#if WiFi_MODE != 0 - // notifyWebUI(); - //#endif - } - } + if (mp.to == NODENUM_BROADCAST || mp.to == nodeDB.getNodeNum()) + MeshPlugin::callPlugins(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.user, - sizeof(info->user)); // Both of these blocks start as filled with zero so I think this is okay - - info->user = p.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; - - 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(); - } + DEBUG_MSG("WARNING: Processing a (deprecated) user packet from %d\n", mp.from); + updateUser(mp.from, p.user); break; } diff --git a/src/mesh/NodeDB.h b/src/mesh/NodeDB.h index c98e050d8..d595662dd 100644 --- a/src/mesh/NodeDB.h +++ b/src/mesh/NodeDB.h @@ -33,7 +33,6 @@ class NodeDB public: bool updateGUI = false; // we think the gui should definitely be redrawn, screen will clear this once handled NodeInfo *updateGUIforNode = NULL; // if currently showing this node, we think you should update the GUI - bool updateTextMessage = false; // if true, the GUI should show a new text message Observable newStatus; /// don't do mesh based algoritm for node id assignment (initially) @@ -58,6 +57,14 @@ class NodeDB /// we updateGUI and updateGUIforNode if we think our this change is big enough for a redraw void updateFrom(const MeshPacket &p); + /** Update position info for this node based on received position data + */ + void updatePosition(uint32_t nodeId, const Position &p); + + /** Update user info for this node based on received user data + */ + void updateUser(uint32_t nodeId, const User &p); + /// @return our node number NodeNum getNodeNum() { return myNodeInfo.my_node_num; } diff --git a/src/mesh/ProtobufPlugin.cpp b/src/mesh/ProtobufPlugin.cpp new file mode 100644 index 000000000..36728c3cd --- /dev/null +++ b/src/mesh/ProtobufPlugin.cpp @@ -0,0 +1,3 @@ +#include "ProtobufPlugin.h" + + diff --git a/src/mesh/ProtobufPlugin.h b/src/mesh/ProtobufPlugin.h new file mode 100644 index 000000000..cda4ed8bf --- /dev/null +++ b/src/mesh/ProtobufPlugin.h @@ -0,0 +1,66 @@ +#pragma once +#include "SinglePortPlugin.h" + +/** + * A base class for mesh plugins that assume that they are sending/receiving one particular protobuf based + * payload. Using one particular app ID. + * + * If you are using protobufs to encode your packets (recommended) you can use this as a baseclass for your plugin + * and avoid a bunch of boilerplate code. + */ +template class ProtobufPlugin : private SinglePortPlugin +{ + const pb_msgdesc_t *fields; + + public: + /** Constructor + * name is for debugging output + */ + ProtobufPlugin(const char *_name, PortNum _ourPortNum, const pb_msgdesc_t *_fields) + : SinglePortPlugin(_name, _ourPortNum), fields(_fields) + { + } + + protected: + + /** + * Handle a received message, the data field in the message is already decoded and is provided + */ + virtual bool handleReceivedProtobuf(const MeshPacket &mp, const T &decoded) = 0; + + /** + * Return a mesh packet which has been preinited with a particular protobuf data payload and port number. + * You can then send this packet (after customizing any of the payload fields you might need) with + * service.sendToMesh() + */ + MeshPacket *allocDataProtobuf(const T &payload) + { + // Update our local node info with our position (even if we don't decide to update anyone else) + MeshPacket *p = allocDataPacket(); + + p->decoded.data.payload.size = + pb_encode_to_bytes(p->decoded.data.payload.bytes, sizeof(p->decoded.data.payload.bytes), fields, &payload); + // DEBUG_MSG("did encode\n"); + return p; + } + + private: + /** Called to handle a particular incoming message + + @return true if you've guaranteed you've handled this message and no other handlers should be considered for it + */ + virtual bool handleReceived(const MeshPacket &mp) + { + // FIXME - we currently update position data in the DB only if the message was a broadcast or destined to us + // it would be better to update even if the message was destined to others. + + auto &p = mp.decoded.data; + DEBUG_MSG("Received %s from=0x%0x, id=%d, payloadlen=%d\n", name, mp.from, mp.id, p.payload.size); + + T scratch; + if (pb_decode_from_bytes(p.payload.bytes, p.payload.size, fields, &scratch)) + handleReceivedProtobuf(mp, scratch); + + return false; // Let others look at this message also if they want + } +}; diff --git a/src/mesh/RadioInterface.h b/src/mesh/RadioInterface.h index 62774a37c..e5253d7a3 100644 --- a/src/mesh/RadioInterface.h +++ b/src/mesh/RadioInterface.h @@ -58,7 +58,7 @@ class RadioInterface uint8_t sf = 9; uint8_t cr = 7; - uint16_t preambleLength = 32; // 8 is default, but FIXME use longer to increase the amount of sleep time when receiving + uint16_t preambleLength = 32; // 8 is default, but we use longer to increase the amount of sleep time when receiving MeshPacket *sendingPacket = NULL; // The packet we are currently sending uint32_t lastTxStart = 0L; diff --git a/src/mesh/SinglePortPlugin.h b/src/mesh/SinglePortPlugin.h new file mode 100644 index 000000000..01ee1963a --- /dev/null +++ b/src/mesh/SinglePortPlugin.h @@ -0,0 +1,40 @@ +#pragma once +#include "MeshPlugin.h" +#include "Router.h" + +/** + * Most plugins are only interested in sending/receving one particular portnum. This baseclass simplifies that common + * case. + */ +class SinglePortPlugin : public MeshPlugin +{ + protected: + PortNum ourPortNum; + + public: + /** Constructor + * name is for debugging output + */ + SinglePortPlugin(const char *_name, PortNum _ourPortNum) : MeshPlugin(_name), ourPortNum(_ourPortNum) {} + + protected: + /** + * @return true if you want to receive the specified portnum + */ + virtual bool wantPortnum(PortNum p) { return p == ourPortNum; } + + /** + * Return a mesh packet which has been preinited as a data packet with a particular port number. + * You can then send this packet (after customizing any of the payload fields you might need) with + * service.sendToMesh() + */ + MeshPacket *allocDataPacket() + { + // Update our local node info with our position (even if we don't decide to update anyone else) + MeshPacket *p = router->allocForSending(); + p->decoded.which_payload = SubPacket_data_tag; + p->decoded.data.portnum = ourPortNum; + + return p; + } +}; diff --git a/src/mesh/mesh.pb.c b/src/mesh/mesh.pb.c index d799c33d2..42b5eb949 100644 --- a/src/mesh/mesh.pb.c +++ b/src/mesh/mesh.pb.c @@ -51,10 +51,6 @@ PB_BIND(FromRadio, FromRadio, 2) PB_BIND(ToRadio, ToRadio, 2) -PB_BIND(ManufacturingData, ManufacturingData, AUTO) - - - diff --git a/src/mesh/mesh.pb.h b/src/mesh/mesh.pb.h index 01ba5b5dd..06e4aa3b4 100644 --- a/src/mesh/mesh.pb.h +++ b/src/mesh/mesh.pb.h @@ -4,6 +4,7 @@ #ifndef PB_MESH_PB_H_INCLUDED #define PB_MESH_PB_H_INCLUDED #include +#include "portnums.pb.h" #if PB_PROTO_HEADER_VERSION != 40 #error Regenerate this file with the current version of nanopb generator. @@ -22,7 +23,8 @@ typedef enum _RouteError { } RouteError; typedef enum _Constants { - Constants_Unused = 0 + Constants_Unused = 0, + Constants_DATA_PAYLOAD_LEN = 240 } Constants; typedef enum _RegionCode { @@ -50,12 +52,6 @@ typedef enum _LocationSharing { LocationSharing_LocDisabled = 2 } LocationSharing; -typedef enum _Data_Type { - Data_Type_OPAQUE = 0, - Data_Type_CLEAR_TEXT = 1, - Data_Type_CLEAR_READACK = 2 -} Data_Type; - typedef enum _ChannelSettings_ModemConfig { ChannelSettings_ModemConfig_Bw125Cr45Sf128 = 0, ChannelSettings_ModemConfig_Bw500Cr45Sf128 = 1, @@ -78,7 +74,7 @@ typedef struct _ChannelSettings { typedef PB_BYTES_ARRAY_T(240) Data_payload_t; typedef struct _Data { - Data_Type typ; + PortNum portnum; Data_payload_t payload; } Data; @@ -86,13 +82,6 @@ typedef struct _DebugString { char message[256]; } DebugString; -typedef struct _ManufacturingData { - uint32_t fradioFreq; - pb_callback_t hw_model; - pb_callback_t hw_version; - int32_t selftest_result; -} ManufacturingData; - typedef struct _MyNodeInfo { uint32_t my_node_num; bool has_gps; @@ -140,7 +129,9 @@ typedef struct _RadioConfig_UserPreferences { uint32_t gps_attempt_time; bool is_router; bool is_low_power; + bool fixed_position; bool factory_reset; + bool debug_log_enabled; pb_size_t ignore_incoming_count; uint32_t ignore_incoming[3]; } RadioConfig_UserPreferences; @@ -260,8 +251,8 @@ typedef struct _ToRadio { #define _RouteError_ARRAYSIZE ((RouteError)(RouteError_TIMEOUT+1)) #define _Constants_MIN Constants_Unused -#define _Constants_MAX Constants_Unused -#define _Constants_ARRAYSIZE ((Constants)(Constants_Unused+1)) +#define _Constants_MAX Constants_DATA_PAYLOAD_LEN +#define _Constants_ARRAYSIZE ((Constants)(Constants_DATA_PAYLOAD_LEN+1)) #define _RegionCode_MIN RegionCode_Unset #define _RegionCode_MAX RegionCode_TW @@ -275,10 +266,6 @@ typedef struct _ToRadio { #define _LocationSharing_MAX LocationSharing_LocDisabled #define _LocationSharing_ARRAYSIZE ((LocationSharing)(LocationSharing_LocDisabled+1)) -#define _Data_Type_MIN Data_Type_OPAQUE -#define _Data_Type_MAX Data_Type_CLEAR_READACK -#define _Data_Type_ARRAYSIZE ((Data_Type)(Data_Type_CLEAR_READACK+1)) - #define _ChannelSettings_ModemConfig_MIN ChannelSettings_ModemConfig_Bw125Cr45Sf128 #define _ChannelSettings_ModemConfig_MAX ChannelSettings_ModemConfig_Bw125Cr48Sf4096 #define _ChannelSettings_ModemConfig_ARRAYSIZE ((ChannelSettings_ModemConfig)(ChannelSettings_ModemConfig_Bw125Cr48Sf4096+1)) @@ -286,37 +273,35 @@ typedef struct _ToRadio { /* Initializer values for message structs */ #define Position_init_default {0, 0, 0, 0, 0} -#define Data_init_default {_Data_Type_MIN, {0, {0}}} +#define Data_init_default {_PortNum_MIN, {0, {0}}} #define User_init_default {"", "", "", {0}} #define RouteDiscovery_init_default {0, {0, 0, 0, 0, 0, 0, 0, 0}} #define SubPacket_init_default {0, {Position_init_default}, 0, 0, 0, 0, {0}, 0} #define MeshPacket_init_default {0, 0, 0, {SubPacket_init_default}, 0, 0, 0, 0, 0} #define ChannelSettings_init_default {0, _ChannelSettings_ModemConfig_MIN, {0, {0}}, "", 0, 0, 0, 0} #define RadioConfig_init_default {false, RadioConfig_UserPreferences_init_default, false, ChannelSettings_init_default} -#define RadioConfig_UserPreferences_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "", "", 0, _RegionCode_MIN, _LocationSharing_MIN, _GpsOperation_MIN, 0, 0, 0, 0, 0, 0, {0, 0, 0}} +#define RadioConfig_UserPreferences_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "", "", 0, _RegionCode_MIN, _LocationSharing_MIN, _GpsOperation_MIN, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}} #define NodeInfo_init_default {0, false, User_init_default, false, Position_init_default, 0, 0} #define MyNodeInfo_init_default {0, 0, 0, "", "", "", 0, 0, 0, 0, 0, 0, 0, 0} #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}, false, MeshPacket_init_default, 0, 0, 0} #define DebugString_init_default {""} #define FromRadio_init_default {0, 0, {MeshPacket_init_default}} #define ToRadio_init_default {0, {MeshPacket_init_default}} -#define ManufacturingData_init_default {0, {{NULL}, NULL}, {{NULL}, NULL}, 0} #define Position_init_zero {0, 0, 0, 0, 0} -#define Data_init_zero {_Data_Type_MIN, {0, {0}}} +#define Data_init_zero {_PortNum_MIN, {0, {0}}} #define User_init_zero {"", "", "", {0}} #define RouteDiscovery_init_zero {0, {0, 0, 0, 0, 0, 0, 0, 0}} #define SubPacket_init_zero {0, {Position_init_zero}, 0, 0, 0, 0, {0}, 0} #define MeshPacket_init_zero {0, 0, 0, {SubPacket_init_zero}, 0, 0, 0, 0, 0} #define ChannelSettings_init_zero {0, _ChannelSettings_ModemConfig_MIN, {0, {0}}, "", 0, 0, 0, 0} #define RadioConfig_init_zero {false, RadioConfig_UserPreferences_init_zero, false, ChannelSettings_init_zero} -#define RadioConfig_UserPreferences_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "", "", 0, _RegionCode_MIN, _LocationSharing_MIN, _GpsOperation_MIN, 0, 0, 0, 0, 0, 0, {0, 0, 0}} +#define RadioConfig_UserPreferences_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "", "", 0, _RegionCode_MIN, _LocationSharing_MIN, _GpsOperation_MIN, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}} #define NodeInfo_init_zero {0, false, User_init_zero, false, Position_init_zero, 0, 0} #define MyNodeInfo_init_zero {0, 0, 0, "", "", "", 0, 0, 0, 0, 0, 0, 0, 0} #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}, false, MeshPacket_init_zero, 0, 0, 0} #define DebugString_init_zero {""} #define FromRadio_init_zero {0, 0, {MeshPacket_init_zero}} #define ToRadio_init_zero {0, {MeshPacket_init_zero}} -#define ManufacturingData_init_zero {0, {{NULL}, NULL}, {{NULL}, NULL}, 0} /* Field tags (for use in manual encoding/decoding) */ #define ChannelSettings_tx_power_tag 1 @@ -327,13 +312,9 @@ typedef struct _ToRadio { #define ChannelSettings_channel_num_tag 9 #define ChannelSettings_psk_tag 4 #define ChannelSettings_name_tag 5 -#define Data_typ_tag 1 +#define Data_portnum_tag 1 #define Data_payload_tag 2 #define DebugString_message_tag 1 -#define ManufacturingData_fradioFreq_tag 1 -#define ManufacturingData_hw_model_tag 2 -#define ManufacturingData_hw_version_tag 3 -#define ManufacturingData_selftest_result_tag 4 #define MyNodeInfo_my_node_num_tag 1 #define MyNodeInfo_has_gps_tag 2 #define MyNodeInfo_num_channels_tag 3 @@ -370,7 +351,9 @@ typedef struct _ToRadio { #define RadioConfig_UserPreferences_region_tag 15 #define RadioConfig_UserPreferences_is_router_tag 37 #define RadioConfig_UserPreferences_is_low_power_tag 38 +#define RadioConfig_UserPreferences_fixed_position_tag 39 #define RadioConfig_UserPreferences_factory_reset_tag 100 +#define RadioConfig_UserPreferences_debug_log_enabled_tag 101 #define RadioConfig_UserPreferences_location_share_tag 32 #define RadioConfig_UserPreferences_gps_operation_tag 33 #define RadioConfig_UserPreferences_gps_update_interval_tag 34 @@ -442,7 +425,7 @@ X(a, STATIC, SINGULAR, FIXED32, time, 9) #define Position_DEFAULT NULL #define Data_FIELDLIST(X, a) \ -X(a, STATIC, SINGULAR, UENUM, typ, 1) \ +X(a, STATIC, SINGULAR, UENUM, portnum, 1) \ X(a, STATIC, SINGULAR, BYTES, payload, 2) #define Data_CALLBACK NULL #define Data_DEFAULT NULL @@ -537,7 +520,9 @@ X(a, STATIC, SINGULAR, UINT32, gps_update_interval, 34) \ X(a, STATIC, SINGULAR, UINT32, gps_attempt_time, 36) \ X(a, STATIC, SINGULAR, BOOL, is_router, 37) \ X(a, STATIC, SINGULAR, BOOL, is_low_power, 38) \ +X(a, STATIC, SINGULAR, BOOL, fixed_position, 39) \ X(a, STATIC, SINGULAR, BOOL, factory_reset, 100) \ +X(a, STATIC, SINGULAR, BOOL, debug_log_enabled, 101) \ X(a, STATIC, REPEATED, UINT32, ignore_incoming, 103) #define RadioConfig_UserPreferences_CALLBACK NULL #define RadioConfig_UserPreferences_DEFAULT NULL @@ -623,14 +608,6 @@ X(a, STATIC, ONEOF, MESSAGE, (variant,set_owner,variant.set_owner), 102) #define ToRadio_variant_set_radio_MSGTYPE RadioConfig #define ToRadio_variant_set_owner_MSGTYPE User -#define ManufacturingData_FIELDLIST(X, a) \ -X(a, STATIC, SINGULAR, UINT32, fradioFreq, 1) \ -X(a, CALLBACK, SINGULAR, STRING, hw_model, 2) \ -X(a, CALLBACK, SINGULAR, STRING, hw_version, 3) \ -X(a, STATIC, SINGULAR, SINT32, selftest_result, 4) -#define ManufacturingData_CALLBACK pb_default_field_callback -#define ManufacturingData_DEFAULT NULL - extern const pb_msgdesc_t Position_msg; extern const pb_msgdesc_t Data_msg; extern const pb_msgdesc_t User_msg; @@ -646,7 +623,6 @@ extern const pb_msgdesc_t DeviceState_msg; extern const pb_msgdesc_t DebugString_msg; extern const pb_msgdesc_t FromRadio_msg; extern const pb_msgdesc_t ToRadio_msg; -extern const pb_msgdesc_t ManufacturingData_msg; /* Defines for backwards compatibility with code written before nanopb-0.4.0 */ #define Position_fields &Position_msg @@ -664,25 +640,23 @@ extern const pb_msgdesc_t ManufacturingData_msg; #define DebugString_fields &DebugString_msg #define FromRadio_fields &FromRadio_msg #define ToRadio_fields &ToRadio_msg -#define ManufacturingData_fields &ManufacturingData_msg /* Maximum encoded size of messages (where known) */ #define Position_size 39 -#define Data_size 245 +#define Data_size 246 #define User_size 72 #define RouteDiscovery_size 88 -#define SubPacket_size 274 -#define MeshPacket_size 313 +#define SubPacket_size 275 +#define MeshPacket_size 314 #define ChannelSettings_size 84 -#define RadioConfig_size 308 -#define RadioConfig_UserPreferences_size 219 +#define RadioConfig_size 314 +#define RadioConfig_UserPreferences_size 225 #define NodeInfo_size 132 #define MyNodeInfo_size 110 -#define DeviceState_size 5460 +#define DeviceState_size 5468 #define DebugString_size 258 -#define FromRadio_size 322 -#define ToRadio_size 316 -/* ManufacturingData_size depends on runtime parameters */ +#define FromRadio_size 323 +#define ToRadio_size 318 #ifdef __cplusplus } /* extern "C" */ diff --git a/src/mesh/portnums.pb.c b/src/mesh/portnums.pb.c new file mode 100644 index 000000000..0995ab494 --- /dev/null +++ b/src/mesh/portnums.pb.c @@ -0,0 +1,10 @@ +/* Automatically generated nanopb constant definitions */ +/* Generated by nanopb-0.4.1 */ + +#include "portnums.pb.h" +#if PB_PROTO_HEADER_VERSION != 40 +#error Regenerate this file with the current version of nanopb generator. +#endif + + + diff --git a/src/mesh/portnums.pb.h b/src/mesh/portnums.pb.h new file mode 100644 index 000000000..eab09126b --- /dev/null +++ b/src/mesh/portnums.pb.h @@ -0,0 +1,37 @@ +/* Automatically generated nanopb header */ +/* Generated by nanopb-0.4.1 */ + +#ifndef PB_PORTNUMS_PB_H_INCLUDED +#define PB_PORTNUMS_PB_H_INCLUDED +#include + +#if PB_PROTO_HEADER_VERSION != 40 +#error Regenerate this file with the current version of nanopb generator. +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* Enum definitions */ +typedef enum _PortNum { + PortNum_UNKNOWN_APP = 0, + PortNum_TEXT_MESSAGE_APP = 1, + PortNum_REMOTE_HARDWARE_APP = 2, + PortNum_POSITION_APP = 3, + PortNum_NODEINFO_APP = 4, + PortNum_PRIVATE_APP = 256, + PortNum_IP_TUNNEL_APP = 1024 +} PortNum; + +/* Helper constants for enums */ +#define _PortNum_MIN PortNum_UNKNOWN_APP +#define _PortNum_MAX PortNum_IP_TUNNEL_APP +#define _PortNum_ARRAYSIZE ((PortNum)(PortNum_IP_TUNNEL_APP+1)) + + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif diff --git a/src/mesh/remote_hardware.pb.c b/src/mesh/remote_hardware.pb.c new file mode 100644 index 000000000..a334db461 --- /dev/null +++ b/src/mesh/remote_hardware.pb.c @@ -0,0 +1,13 @@ +/* Automatically generated nanopb constant definitions */ +/* Generated by nanopb-0.4.1 */ + +#include "remote_hardware.pb.h" +#if PB_PROTO_HEADER_VERSION != 40 +#error Regenerate this file with the current version of nanopb generator. +#endif + +PB_BIND(HardwareMessage, HardwareMessage, AUTO) + + + + diff --git a/src/mesh/remote_hardware.pb.h b/src/mesh/remote_hardware.pb.h new file mode 100644 index 000000000..f85cd8ff2 --- /dev/null +++ b/src/mesh/remote_hardware.pb.h @@ -0,0 +1,69 @@ +/* Automatically generated nanopb header */ +/* Generated by nanopb-0.4.1 */ + +#ifndef PB_REMOTE_HARDWARE_PB_H_INCLUDED +#define PB_REMOTE_HARDWARE_PB_H_INCLUDED +#include + +#if PB_PROTO_HEADER_VERSION != 40 +#error Regenerate this file with the current version of nanopb generator. +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* Enum definitions */ +typedef enum _HardwareMessage_Type { + HardwareMessage_Type_UNSET = 0, + HardwareMessage_Type_WRITE_GPIOS = 1, + HardwareMessage_Type_WATCH_GPIOS = 2, + HardwareMessage_Type_GPIOS_CHANGED = 3, + HardwareMessage_Type_READ_GPIOS = 4, + HardwareMessage_Type_READ_GPIOS_REPLY = 5 +} HardwareMessage_Type; + +/* Struct definitions */ +typedef struct _HardwareMessage { + HardwareMessage_Type typ; + uint64_t gpio_mask; + uint64_t gpio_value; +} HardwareMessage; + + +/* Helper constants for enums */ +#define _HardwareMessage_Type_MIN HardwareMessage_Type_UNSET +#define _HardwareMessage_Type_MAX HardwareMessage_Type_READ_GPIOS_REPLY +#define _HardwareMessage_Type_ARRAYSIZE ((HardwareMessage_Type)(HardwareMessage_Type_READ_GPIOS_REPLY+1)) + + +/* Initializer values for message structs */ +#define HardwareMessage_init_default {_HardwareMessage_Type_MIN, 0, 0} +#define HardwareMessage_init_zero {_HardwareMessage_Type_MIN, 0, 0} + +/* Field tags (for use in manual encoding/decoding) */ +#define HardwareMessage_typ_tag 1 +#define HardwareMessage_gpio_mask_tag 2 +#define HardwareMessage_gpio_value_tag 3 + +/* Struct field encoding specification for nanopb */ +#define HardwareMessage_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, UENUM, typ, 1) \ +X(a, STATIC, SINGULAR, UINT64, gpio_mask, 2) \ +X(a, STATIC, SINGULAR, UINT64, gpio_value, 3) +#define HardwareMessage_CALLBACK NULL +#define HardwareMessage_DEFAULT NULL + +extern const pb_msgdesc_t HardwareMessage_msg; + +/* Defines for backwards compatibility with code written before nanopb-0.4.0 */ +#define HardwareMessage_fields &HardwareMessage_msg + +/* Maximum encoded size of messages (where known) */ +#define HardwareMessage_size 24 + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif diff --git a/src/plugins/NodeInfoPlugin.cpp b/src/plugins/NodeInfoPlugin.cpp new file mode 100644 index 000000000..876f5ea02 --- /dev/null +++ b/src/plugins/NodeInfoPlugin.cpp @@ -0,0 +1,44 @@ +#include "NodeInfoPlugin.h" +#include "MeshService.h" +#include "NodeDB.h" +#include "RTC.h" +#include "Router.h" +#include "configuration.h" +#include "main.h" + +NodeInfoPlugin nodeInfoPlugin; + +bool NodeInfoPlugin::handleReceivedProtobuf(const MeshPacket &mp, const User &p) +{ + // FIXME - we currently update NodeInfo data in the DB only if the message was a broadcast or destined to us + // it would be better to update even if the message was destined to others. + + nodeDB.updateUser(mp.from, p); + + bool wasBroadcast = mp.to == NODENUM_BROADCAST; + + // Show new nodes on LCD screen + if (wasBroadcast) { + String lcd = String("Joined: ") + p.long_name + "\n"; + screen->print(lcd.c_str()); + } + + return false; // Let others look at this message also if they want +} + +void NodeInfoPlugin::sendOurNodeInfo(NodeNum dest, bool wantReplies) +{ + MeshPacket *p = allocReply(); + p->to = dest; + p->decoded.want_response = wantReplies; + + service.sendToMesh(p); +} + +MeshPacket *NodeInfoPlugin::allocReply() +{ + User &u = owner; + + DEBUG_MSG("sending owner %s/%s/%s\n", u.id, u.long_name, u.short_name); + return allocDataProtobuf(u); +} diff --git a/src/plugins/NodeInfoPlugin.h b/src/plugins/NodeInfoPlugin.h new file mode 100644 index 000000000..968d5f8b7 --- /dev/null +++ b/src/plugins/NodeInfoPlugin.h @@ -0,0 +1,32 @@ +#pragma once +#include "ProtobufPlugin.h" + +/** + * NodeInfo plugin for sending/receiving NodeInfos into the mesh + */ +class NodeInfoPlugin : public ProtobufPlugin +{ + public: + /** Constructor + * name is for debugging output + */ + NodeInfoPlugin() : ProtobufPlugin("nodeinfo", PortNum_NODEINFO_APP, User_fields) {} + + /** + * Send our NodeInfo into the mesh + */ + void sendOurNodeInfo(NodeNum dest = NODENUM_BROADCAST, bool wantReplies = false); + + protected: + /** Called to handle a particular incoming message + + @return true if you've guaranteed you've handled this message and no other handlers should be considered for it + */ + virtual bool handleReceivedProtobuf(const MeshPacket &mp, const User &p); + + /** Messages can be received that have the want_response bit set. If set, this callback will be invoked + * so that subclasses can (optionally) send a response back to the original sender. */ + virtual MeshPacket *allocReply(); +}; + +extern NodeInfoPlugin nodeInfoPlugin; \ No newline at end of file diff --git a/src/plugins/PositionPlugin.cpp b/src/plugins/PositionPlugin.cpp new file mode 100644 index 000000000..b0ce8e217 --- /dev/null +++ b/src/plugins/PositionPlugin.cpp @@ -0,0 +1,51 @@ +#include "PositionPlugin.h" +#include "MeshService.h" +#include "NodeDB.h" +#include "RTC.h" +#include "Router.h" +#include "configuration.h" + +PositionPlugin positionPlugin; + +bool PositionPlugin::handleReceivedProtobuf(const MeshPacket &mp, const Position &p) +{ + // FIXME - we currently update position data in the DB only if the message was a broadcast or destined to us + // it would be better to update even if the message was destined to others. + + if (p.time) { + struct timeval tv; + uint32_t secs = p.time; + + tv.tv_sec = secs; + tv.tv_usec = 0; + + perhapsSetRTC(RTCQualityFromNet, &tv); + } + + nodeDB.updatePosition(mp.from, p); + + return false; // Let others look at this message also if they want +} + +MeshPacket *PositionPlugin::allocReply() +{ + NodeInfo *node = nodeDB.getNode(nodeDB.getNodeNum()); + assert(node); + assert(node->has_position); + + // Update our local node info with our position (even if we don't decide to update anyone else) + auto position = node->position; + position.time = getValidTime(RTCQualityGPS); // This nodedb timestamp might be stale, so update it if our clock is valid. + + return allocDataProtobuf(position); +} + +void PositionPlugin::sendOurPosition(NodeNum dest, bool wantReplies) +{ + MeshPacket *p = allocReply(); + p->to = dest; + p->decoded.want_response = wantReplies; + + service.sendToMesh(p); +} + diff --git a/src/plugins/PositionPlugin.h b/src/plugins/PositionPlugin.h new file mode 100644 index 000000000..9153a2288 --- /dev/null +++ b/src/plugins/PositionPlugin.h @@ -0,0 +1,33 @@ +#pragma once +#include "ProtobufPlugin.h" + +/** + * Position plugin for sending/receiving positions into the mesh + */ +class PositionPlugin : public ProtobufPlugin +{ + public: + /** Constructor + * name is for debugging output + */ + PositionPlugin() : ProtobufPlugin("position", PortNum_POSITION_APP, Position_fields) {} + + /** + * Send our position into the mesh + */ + void sendOurPosition(NodeNum dest = NODENUM_BROADCAST, bool wantReplies = false); + + protected: + + /** Called to handle a particular incoming message + + @return true if you've guaranteed you've handled this message and no other handlers should be considered for it + */ + virtual bool handleReceivedProtobuf(const MeshPacket &mp, const Position &p); + + /** Messages can be received that have the want_response bit set. If set, this callback will be invoked + * so that subclasses can (optionally) send a response back to the original sender. */ + virtual MeshPacket *allocReply(); +}; + +extern PositionPlugin positionPlugin; \ No newline at end of file diff --git a/src/plugins/RemoteHardwarePlugin.cpp b/src/plugins/RemoteHardwarePlugin.cpp new file mode 100644 index 000000000..11aaad234 --- /dev/null +++ b/src/plugins/RemoteHardwarePlugin.cpp @@ -0,0 +1,63 @@ +#include "RemoteHardwarePlugin.h" +#include "MeshService.h" +#include "NodeDB.h" +#include "RTC.h" +#include "Router.h" +#include "configuration.h" +#include "main.h" + +RemoteHardwarePlugin remoteHardwarePlugin; + +#define NUM_GPIOS 64 + + +bool RemoteHardwarePlugin::handleReceivedProtobuf(const MeshPacket &req, const HardwareMessage &p) +{ + switch (p.typ) { + case HardwareMessage_Type_WRITE_GPIOS: + // Print notification to LCD screen + screen->print("Write GPIOs\n"); + + for (uint8_t i = 0; i < NUM_GPIOS; i++) { + uint64_t mask = 1 << i; + if (p.gpio_mask & mask) { + digitalWrite(i, (p.gpio_value & mask) ? 1 : 0); + pinMode(i, OUTPUT); + } + } + break; + + case HardwareMessage_Type_READ_GPIOS: { + // Print notification to LCD screen + screen->print("Read GPIOs\n"); + + uint64_t res = 0; + for (uint8_t i = 0; i < NUM_GPIOS; i++) { + uint64_t mask = 1 << i; + if (p.gpio_mask & mask) { + pinMode(i, INPUT_PULLUP); + if (digitalRead(i)) + res |= (1 << i); + } + } + + // Send the reply + HardwareMessage reply = HardwareMessage_init_default; + reply.typ = HardwareMessage_Type_READ_GPIOS_REPLY; + reply.gpio_value = res; + MeshPacket *p = allocDataProtobuf(reply); + setReplyTo(p, req); + service.sendToMesh(p); + break; + } + + case HardwareMessage_Type_READ_GPIOS_REPLY: + case HardwareMessage_Type_GPIOS_CHANGED: + break; // Ignore - we might see our own replies + + default: + DEBUG_MSG("Hardware operation %d not yet implemented! FIXME\n", p.typ); + break; + } + return true; // handled +} diff --git a/src/plugins/RemoteHardwarePlugin.h b/src/plugins/RemoteHardwarePlugin.h new file mode 100644 index 000000000..faae4894d --- /dev/null +++ b/src/plugins/RemoteHardwarePlugin.h @@ -0,0 +1,24 @@ +#pragma once +#include "ProtobufPlugin.h" +#include "remote_hardware.pb.h" + +/** + * A plugin that provides easy low-level remote access to device hardware. + */ +class RemoteHardwarePlugin : public ProtobufPlugin +{ + public: + /** Constructor + * name is for debugging output + */ + RemoteHardwarePlugin() : ProtobufPlugin("remotehardware", PortNum_REMOTE_HARDWARE_APP, HardwareMessage_fields) {} + + protected: + /** Called to handle a particular incoming message + + @return true if you've guaranteed you've handled this message and no other handlers should be considered for it + */ + virtual bool handleReceivedProtobuf(const MeshPacket &mp, const HardwareMessage &p); +}; + +extern RemoteHardwarePlugin remoteHardwarePlugin; \ No newline at end of file diff --git a/src/plugins/TextMessagePlugin.cpp b/src/plugins/TextMessagePlugin.cpp new file mode 100644 index 000000000..31d98feee --- /dev/null +++ b/src/plugins/TextMessagePlugin.cpp @@ -0,0 +1,28 @@ +#include "configuration.h" +#include "TextMessagePlugin.h" +#include "NodeDB.h" +#include "PowerFSM.h" + +TextMessagePlugin textMessagePlugin; + +bool TextMessagePlugin::handleReceived(const MeshPacket &mp) +{ + auto &p = mp.decoded.data; + DEBUG_MSG("Received text msg from=0x%0x, id=%d, msg=%.*s\n", mp.from, mp.id, p.payload.size, p.payload.bytes); + + // We only store/display messages destined for us. + // Keep a copy of the most recent text message. + devicestate.rx_text_message = mp; + devicestate.has_rx_text_message = true; + + powerFSM.trigger(EVENT_RECEIVED_TEXT_MSG); + notifyObservers(&mp); + + // This is going into the wifidev feature branch + // Only update the WebUI if WiFi is enabled + //#if WiFi_MODE != 0 + // notifyWebUI(); + //#endif + + return false; // Let others look at this message also if they want +} diff --git a/src/plugins/TextMessagePlugin.h b/src/plugins/TextMessagePlugin.h new file mode 100644 index 000000000..1a6a45076 --- /dev/null +++ b/src/plugins/TextMessagePlugin.h @@ -0,0 +1,25 @@ +#pragma once +#include "SinglePortPlugin.h" +#include "Observer.h" + +/** + * Text message handling for meshtastic - draws on the OLED display the most recent received message + */ +class TextMessagePlugin : public SinglePortPlugin, public Observable +{ + public: + /** Constructor + * name is for debugging output + */ + TextMessagePlugin() : SinglePortPlugin("text", PortNum_TEXT_MESSAGE_APP) {} + + protected: + + /** Called to handle a particular incoming message + + @return true if you've guaranteed you've handled this message and no other handlers should be considered for it + */ + virtual bool handleReceived(const MeshPacket &mp); +}; + +extern TextMessagePlugin textMessagePlugin; \ No newline at end of file