From 1e5d0b25adcedc11029dfb9de383198a0152017a Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Fri, 11 Dec 2020 18:29:32 +0800 Subject: [PATCH 1/9] Add doc note about threading and use OSThread to make GPIO watching work Thanks to @mc-hamster for the idea --- docs/software/TODO.md | 2 +- docs/software/plugin-api.md | 5 ++ src/plugins/RemoteHardwarePlugin.cpp | 88 ++++++++++++++++++++++++---- src/plugins/RemoteHardwarePlugin.h | 23 +++++++- 4 files changed, 104 insertions(+), 14 deletions(-) diff --git a/docs/software/TODO.md b/docs/software/TODO.md index 2940a5fb..46f943c0 100644 --- a/docs/software/TODO.md +++ b/docs/software/TODO.md @@ -10,7 +10,7 @@ For app cleanup: * DONE write devapi user guide * DONE update android code: https://developer.android.com/topic/libraries/view-binding/migration * only do wantReplies once per packet type, if we change network settings force it again -* make gpio watch work, use thread and setup +* test GPIO watch * DONE make hello world example service * make python ping command * DONE have python tool check max packet size before sending to device diff --git a/docs/software/plugin-api.md b/docs/software/plugin-api.md index ba544928..e0699a6b 100644 --- a/docs/software/plugin-api.md +++ b/docs/software/plugin-api.md @@ -49,6 +49,11 @@ The easiest way to get started is: * Rebuild with your new messaging goodness and install on the device * Use the [meshtastic commandline tool](https://github.com/meshtastic/Meshtastic-python) to send a packet to your board "meshtastic --dest 1234 --ping" +## Threading + +It is very common that you would like your plugin to be invoked periodically. +We use a crude/basic cooperative threading system to allow this on any of our supported platforms. Simply inherit from OSThread and implement runOnce(). See the OSThread [documentation](/src/concurrency/OSThread.h) for more details. For an example consumer of this API see RemoteHardwarePlugin::runOnce. + ## Picking a port number For any new 'apps' that run on the device or via sister apps on phones/PCs they should pick and use a unique 'portnum' for their application. diff --git a/src/plugins/RemoteHardwarePlugin.cpp b/src/plugins/RemoteHardwarePlugin.cpp index 11aaad23..dee93363 100644 --- a/src/plugins/RemoteHardwarePlugin.cpp +++ b/src/plugins/RemoteHardwarePlugin.cpp @@ -10,6 +10,44 @@ RemoteHardwarePlugin remoteHardwarePlugin; #define NUM_GPIOS 64 +// Because (FIXME) we currently don't tell API clients status on sent messages +// we need to throttle our sending, so that if a gpio is bouncing up and down we +// don't generate more messages than the net can send. So we limit watch messages to +// a max of one change per 30 seconds +#define WATCH_INTERVAL_MSEC (30 * 1000) + +/// Set pin modes for every set bit in a mask +static void pinModes(uint64_t mask, uint8_t mode) { + for (uint8_t i = 0; i < NUM_GPIOS; i++) { + if (mask & (1 << i)) { + pinMode(i, mode); + } + } +} + +/// Read all the pins mentioned in a mask +static uint64_t digitalReads(uint64_t mask) { + uint64_t res = 0; + + pinModes(mask, INPUT_PULLUP); + + for (uint8_t i = 0; i < NUM_GPIOS; i++) { + uint64_t m = 1 << i; + if (mask & m) { + if (digitalRead(i)) + res |= m; + } + } + + return res; +} + + +RemoteHardwarePlugin::RemoteHardwarePlugin() + : ProtobufPlugin("remotehardware", PortNum_REMOTE_HARDWARE_APP, HardwareMessage_fields), + concurrency::OSThread("remotehardware") +{ +} bool RemoteHardwarePlugin::handleReceivedProtobuf(const MeshPacket &req, const HardwareMessage &p) { @@ -22,24 +60,17 @@ bool RemoteHardwarePlugin::handleReceivedProtobuf(const MeshPacket &req, const H uint64_t mask = 1 << i; if (p.gpio_mask & mask) { digitalWrite(i, (p.gpio_value & mask) ? 1 : 0); - pinMode(i, OUTPUT); } } + pinModes(p.gpio_mask, 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); - } - } + uint64_t res = digitalReads(p.gpio_mask); // Send the reply HardwareMessage reply = HardwareMessage_init_default; @@ -51,6 +82,14 @@ bool RemoteHardwarePlugin::handleReceivedProtobuf(const MeshPacket &req, const H break; } + case HardwareMessage_Type_WATCH_GPIOS: { + watchGpios = p.gpio_mask; + lastWatchMsec = 0; // Force a new publish soon + previousWatch = ~watchGpios; // generate a 'previous' value which is guaranteed to not match (to force an initial publish) + enabled = true; // Let our thread run at least once + break; + } + case HardwareMessage_Type_READ_GPIOS_REPLY: case HardwareMessage_Type_GPIOS_CHANGED: break; // Ignore - we might see our own replies @@ -61,3 +100,30 @@ bool RemoteHardwarePlugin::handleReceivedProtobuf(const MeshPacket &req, const H } return true; // handled } + +int32_t RemoteHardwarePlugin::runOnce() { + if(watchGpios) { + uint32_t now = millis(); + + if(now - lastWatchMsec >= WATCH_INTERVAL_MSEC) { + uint64_t curVal = digitalReads(watchGpios); + + if(curVal != previousWatch) { + previousWatch = curVal; + + // Something changed! Tell the world with a broadcast message + HardwareMessage reply = HardwareMessage_init_default; + reply.typ = HardwareMessage_Type_GPIOS_CHANGED; + reply.gpio_value = curVal; + MeshPacket *p = allocDataProtobuf(reply); + service.sendToMesh(p); + } + } + } + else { + // No longer watching anything - stop using CPU + enabled = false; + } + + return 200; // Poll our GPIOs every 200ms (FIXME, make adjustable via protobuf arg) +} \ No newline at end of file diff --git a/src/plugins/RemoteHardwarePlugin.h b/src/plugins/RemoteHardwarePlugin.h index faae4894..fe9bf960 100644 --- a/src/plugins/RemoteHardwarePlugin.h +++ b/src/plugins/RemoteHardwarePlugin.h @@ -1,17 +1,26 @@ #pragma once #include "ProtobufPlugin.h" #include "remote_hardware.pb.h" +#include "concurrency/OSThread.h" /** * A plugin that provides easy low-level remote access to device hardware. */ -class RemoteHardwarePlugin : public ProtobufPlugin +class RemoteHardwarePlugin : public ProtobufPlugin, private concurrency::OSThread { + /// The current set of GPIOs we've been asked to watch for changes + uint64_t watchGpios = 0; + + /// The previously read value of watched pins + uint64_t previousWatch = 0; + + /// The timestamp of our last watch event (we throttle watches to 1 change every 30 seconds) + uint32_t lastWatchMsec = 0; public: /** Constructor * name is for debugging output */ - RemoteHardwarePlugin() : ProtobufPlugin("remotehardware", PortNum_REMOTE_HARDWARE_APP, HardwareMessage_fields) {} + RemoteHardwarePlugin(); protected: /** Called to handle a particular incoming message @@ -19,6 +28,16 @@ class RemoteHardwarePlugin : public ProtobufPlugin @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); + + /** + * Periodically read the gpios we have been asked to WATCH, if they have changed, + * broadcast a message with the change information. + * + * The method that will be called each time our thread gets a chance to run + * + * Returns desired period for next invocation (or RUN_SAME for no change) + */ + virtual int32_t runOnce(); }; extern RemoteHardwarePlugin remoteHardwarePlugin; \ No newline at end of file From 138cebbf03fed993d0a1c48acf8d70dd0f266e59 Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Sun, 13 Dec 2020 11:53:32 +0800 Subject: [PATCH 2/9] turn nrf52 ble back on --- src/nrf52/main-nrf52.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nrf52/main-nrf52.cpp b/src/nrf52/main-nrf52.cpp index 8b7bb65b..a1a96f3b 100644 --- a/src/nrf52/main-nrf52.cpp +++ b/src/nrf52/main-nrf52.cpp @@ -51,7 +51,7 @@ void getMacAddr(uint8_t *dmac) NRF52Bluetooth *nrf52Bluetooth; static bool bleOn = false; -static const bool enableBle = false; // Set to false for easier debugging +static const bool enableBle = true; // Set to false for easier debugging void setBluetoothEnable(bool on) { From ad8bcba5ef3347141f63d6d1d78eb75e9cdc1e17 Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Sun, 13 Dec 2020 11:54:00 +0800 Subject: [PATCH 3/9] remove hack for the othernet ppr1 rev 1 board --- variants/ppr1/variant.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variants/ppr1/variant.h b/variants/ppr1/variant.h index e6bfd8a0..04a6e3c0 100644 --- a/variants/ppr1/variant.h +++ b/variants/ppr1/variant.h @@ -112,7 +112,7 @@ static const uint8_t AREF = PIN_AREF; // #define PIN_GPS_WAKE 20 // CELL_CTRL in schematic? based on their example code #define PIN_GPS_EN 7 // GPS_EN active high -#define PIN_VUSB_EN 21 +// #define PIN_VUSB_EN 21 // LCD From ee8f4de5abf33af34ae05d92c425d791392c4cae Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Sun, 13 Dec 2020 12:57:37 +0800 Subject: [PATCH 4/9] make plugin reply handling simpler --- docs/software/TODO.md | 7 ++++--- src/mesh/MeshPlugin.cpp | 9 +++++++-- src/mesh/MeshPlugin.h | 9 +++++++++ src/plugins/ReplyPlugin.cpp | 12 ++++++------ src/plugins/ReplyPlugin.h | 7 +++---- 5 files changed, 29 insertions(+), 15 deletions(-) diff --git a/docs/software/TODO.md b/docs/software/TODO.md index 46f943c0..50796ef1 100644 --- a/docs/software/TODO.md +++ b/docs/software/TODO.md @@ -11,8 +11,9 @@ For app cleanup: * DONE update android code: https://developer.android.com/topic/libraries/view-binding/migration * only do wantReplies once per packet type, if we change network settings force it again * test GPIO watch -* DONE make hello world example service +* writeup docs on gpio * make python ping command +* DONE make hello world example service * 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 @@ -20,8 +21,8 @@ For app cleanup: * 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 on android side send old or new positions as needed / user messages +* DONE 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 diff --git a/src/mesh/MeshPlugin.cpp b/src/mesh/MeshPlugin.cpp index 5320b7ae..e2a6bf28 100644 --- a/src/mesh/MeshPlugin.cpp +++ b/src/mesh/MeshPlugin.cpp @@ -5,6 +5,8 @@ std::vector *MeshPlugin::plugins; +const MeshPacket *MeshPlugin::currentRequest; + MeshPlugin::MeshPlugin(const char *_name) : name(_name) { // Can't trust static initalizer order, so we check each time @@ -27,11 +29,13 @@ void MeshPlugin::callPlugins(const MeshPacket &mp) // DEBUG_MSG("In call plugins\n"); for (auto i = plugins->begin(); i != plugins->end(); ++i) { auto &pi = **i; + + pi.currentRequest = ∓ 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()) + // Possibly send replies + if (mp.decoded.want_response) pi.sendResponse(mp); DEBUG_MSG("Plugin %s handled=%d\n", pi.name, handled); @@ -41,6 +45,7 @@ void MeshPlugin::callPlugins(const MeshPacket &mp) else { DEBUG_MSG("Plugin %s not interested\n", pi.name); } + pi.currentRequest = NULL; } } diff --git a/src/mesh/MeshPlugin.h b/src/mesh/MeshPlugin.h index f753651e..01af5811 100644 --- a/src/mesh/MeshPlugin.h +++ b/src/mesh/MeshPlugin.h @@ -31,6 +31,15 @@ class MeshPlugin protected: const char *name; + /** + * If this plugin is currently handling a request currentRequest will be preset + * to the packet with the request. This is mostly useful for reply handlers. + * + * Note: this can be static because we are guaranteed to be processing only one + * plugin at a time. + */ + static const MeshPacket *currentRequest; + /** * Initialize your plugin. This setup function is called once after all hardware and mesh protocol layers have * been initialized diff --git a/src/plugins/ReplyPlugin.cpp b/src/plugins/ReplyPlugin.cpp index d5c9e727..788edc55 100644 --- a/src/plugins/ReplyPlugin.cpp +++ b/src/plugins/ReplyPlugin.cpp @@ -1,6 +1,6 @@ -#include "configuration.h" #include "ReplyPlugin.h" #include "MeshService.h" +#include "configuration.h" #include "main.h" #include @@ -8,8 +8,10 @@ // Create an a static instance of our plugin - this registers with the plugin system ReplyPlugin replyPlugin; -bool ReplyPlugin::handleReceived(const MeshPacket &req) +MeshPacket *ReplyPlugin::allocReply() { + assert(currentRequest); // should always be !NULL + auto req = *currentRequest; auto &p = req.decoded.data; // The incoming message is in p.payload DEBUG_MSG("Received message from=0x%0x, id=%d, msg=%.*s\n", req.from, req.id, p.payload.size, p.payload.bytes); @@ -17,11 +19,9 @@ bool ReplyPlugin::handleReceived(const MeshPacket &req) screen->print("Sending reply\n"); const char *replyStr = "Message Received"; - auto reply = allocDataPacket(); // Allocate a packet for sending + auto reply = allocDataPacket(); // Allocate a packet for sending reply->decoded.data.payload.size = strlen(replyStr); // You must specify how many bytes are in the reply memcpy(reply->decoded.data.payload.bytes, replyStr, reply->decoded.data.payload.size); - setReplyTo(reply, req); // Set packet params so that this packet is marked as a reply to a previous request - service.sendToMesh(reply); // Queue the reply for sending - return true; // We handled it + return reply; } diff --git a/src/plugins/ReplyPlugin.h b/src/plugins/ReplyPlugin.h index 0d3686b5..7d0ff8b7 100644 --- a/src/plugins/ReplyPlugin.h +++ b/src/plugins/ReplyPlugin.h @@ -15,9 +15,8 @@ class ReplyPlugin : public SinglePortPlugin 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 + /** For reply plugin we do all of our processing in the (normally optional) + * want_replies handling */ - virtual bool handleReceived(const MeshPacket &mp); + virtual MeshPacket *allocReply(); }; From e80c79edbe97222966a14c2c3cb37004f9803777 Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Sun, 13 Dec 2020 15:59:26 +0800 Subject: [PATCH 5/9] clean up debug msgs --- docs/software/TODO.md | 4 ++-- src/mesh/DSRRouter.cpp | 18 +++++++++--------- src/mesh/FloodingRouter.cpp | 3 +-- src/mesh/MeshPlugin.cpp | 10 +++++++--- src/mesh/PhoneAPI.cpp | 9 ++++++--- src/mesh/ProtobufPlugin.h | 4 ++-- src/mesh/RadioInterface.cpp | 2 +- src/mesh/ReliableRouter.cpp | 2 +- src/plugins/RemoteHardwarePlugin.cpp | 5 +++++ 9 files changed, 34 insertions(+), 23 deletions(-) diff --git a/docs/software/TODO.md b/docs/software/TODO.md index 50796ef1..ac5c2d33 100644 --- a/docs/software/TODO.md +++ b/docs/software/TODO.md @@ -9,10 +9,9 @@ For app cleanup: * DONE check build guide * DONE write devapi user guide * DONE update android code: https://developer.android.com/topic/libraries/view-binding/migration -* only do wantReplies once per packet type, if we change network settings force it again * test GPIO watch * writeup docs on gpio -* make python ping command +* DONE make python ping command * DONE make hello world example service * DONE have python tool check max packet size before sending to device * DONE if request was sent reliably, send reply reliably @@ -32,6 +31,7 @@ For app cleanup: * DONE test that position, text messages and user info work properly with new android app and old device code * do UDP tunnel * fix the RTC drift bug +* only do wantReplies once per packet type, if we change network settings force it again * move python ping functionality into device, reply with rxsnr info * use channels for gpio security https://github.com/meshtastic/Meshtastic-device/issues/104 * generate autodocs diff --git a/src/mesh/DSRRouter.cpp b/src/mesh/DSRRouter.cpp index f1ec32e3..d8ca542d 100644 --- a/src/mesh/DSRRouter.cpp +++ b/src/mesh/DSRRouter.cpp @@ -170,7 +170,7 @@ bool DSRRouter::weAreInRoute(const RouteDiscovery &route) **/ void DSRRouter::updateRoutes(const RouteDiscovery &route, bool isRequest) { - DEBUG_MSG("FIXME not implemented"); + DEBUG_MSG("FIXME not implemented updateRoutes\n"); } /** @@ -178,7 +178,7 @@ void DSRRouter::updateRoutes(const RouteDiscovery &route, bool isRequest) */ void DSRRouter::sendRouteReply(const RouteDiscovery &route, NodeNum toAppend) { - DEBUG_MSG("FIXME not implemented"); + DEBUG_MSG("FIXME not implemented sendRoute\n"); } /** @@ -188,7 +188,7 @@ void DSRRouter::sendRouteReply(const RouteDiscovery &route, NodeNum toAppend) */ NodeNum DSRRouter::getNextHop(NodeNum dest) { - DEBUG_MSG("FIXME not implemented"); + DEBUG_MSG("FIXME not implemented getNextHop\n"); return 0; } @@ -198,7 +198,7 @@ NodeNum DSRRouter::getNextHop(NodeNum dest) */ void DSRRouter::resendRouteRequest(const MeshPacket *p) { - DEBUG_MSG("FIXME not implemented"); + DEBUG_MSG("FIXME not implemented resendRoute\n"); } /** @@ -208,7 +208,7 @@ void DSRRouter::resendRouteRequest(const MeshPacket *p) */ void DSRRouter::addRoute(NodeNum dest, NodeNum forwarder, uint8_t numHops) { - DEBUG_MSG("FIXME not implemented"); + DEBUG_MSG("FIXME not implemented addRoute\n"); } /** @@ -216,7 +216,7 @@ void DSRRouter::addRoute(NodeNum dest, NodeNum forwarder, uint8_t numHops) */ void DSRRouter::removeRoute(NodeNum dest) { - DEBUG_MSG("FIXME not implemented"); + DEBUG_MSG("FIXME not implemented removeRoute\n"); } /** @@ -224,7 +224,7 @@ void DSRRouter::removeRoute(NodeNum dest) */ void DSRRouter::sendNextHop(NodeNum n, const MeshPacket *p) { - DEBUG_MSG("FIXME not implemented"); + DEBUG_MSG("FIXME not implemented sendNextHop\n"); } /** @@ -232,7 +232,7 @@ void DSRRouter::sendNextHop(NodeNum n, const MeshPacket *p) */ void DSRRouter::sendRouteError(const MeshPacket *p, RouteError err) { - DEBUG_MSG("FIXME not implemented"); + DEBUG_MSG("FIXME not implemented sendRouteError\n"); } /** make a copy of p, start discovery, but only if we don't @@ -241,5 +241,5 @@ void DSRRouter::sendRouteError(const MeshPacket *p, RouteError err) */ void DSRRouter::startDiscovery(NodeNum dest) { - DEBUG_MSG("FIXME not implemented"); + DEBUG_MSG("FIXME not implemented startDiscovery\n"); } \ No newline at end of file diff --git a/src/mesh/FloodingRouter.cpp b/src/mesh/FloodingRouter.cpp index ffd652bf..db335558 100644 --- a/src/mesh/FloodingRouter.cpp +++ b/src/mesh/FloodingRouter.cpp @@ -38,8 +38,7 @@ void FloodingRouter::sniffReceived(const MeshPacket *p) tosend->hop_limit--; // bump down the hop count - DEBUG_MSG("Rebroadcasting received floodmsg to neighbors, fr=0x%x,to=0x%x,id=%d,hop_limit=%d\n", p->from, p->to, - p->id, tosend->hop_limit); + printPacket("Rebroadcasting received floodmsg to neighbors", p); // Note: we are careful to resend using the original senders node id // We are careful not to call our hooked version of send() - because we don't want to check this again Router::send(tosend); diff --git a/src/mesh/MeshPlugin.cpp b/src/mesh/MeshPlugin.cpp index e2a6bf28..ebaee49a 100644 --- a/src/mesh/MeshPlugin.cpp +++ b/src/mesh/MeshPlugin.cpp @@ -27,11 +27,14 @@ MeshPlugin::~MeshPlugin() void MeshPlugin::callPlugins(const MeshPacket &mp) { // DEBUG_MSG("In call plugins\n"); + bool pluginFound = false; for (auto i = plugins->begin(); i != plugins->end(); ++i) { auto &pi = **i; pi.currentRequest = ∓ if (pi.wantPortnum(mp.decoded.data.portnum)) { + pluginFound = true; + bool handled = pi.handleReceived(mp); // Possibly send replies @@ -42,11 +45,12 @@ void MeshPlugin::callPlugins(const MeshPacket &mp) if (handled) break; } - else { - DEBUG_MSG("Plugin %s not interested\n", pi.name); - } + pi.currentRequest = NULL; } + + if(!pluginFound) + DEBUG_MSG("No plugins interested in portnum=%d\n", mp.decoded.data.portnum); } /** Messages can be received that have the want_response bit set. If set, this callback will be invoked diff --git a/src/mesh/PhoneAPI.cpp b/src/mesh/PhoneAPI.cpp index c3ae6930..999526de 100644 --- a/src/mesh/PhoneAPI.cpp +++ b/src/mesh/PhoneAPI.cpp @@ -96,9 +96,9 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf) if (!available()) { // DEBUG_MSG("getFromRadio, !available\n"); return false; - } else { - DEBUG_MSG("getFromRadio, state=%d\n", state); - } + } + + DEBUG_MSG("getFromRadio, state=%d\n", state); // In case we send a FromRadio packet memset(&fromRadioScratch, 0, sizeof(fromRadioScratch)); @@ -162,6 +162,9 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf) case STATE_SEND_PACKETS: // Do we have a message from the mesh? if (packetForPhone) { + + printPacket("phone downloaded packet", packetForPhone); + // Encapsulate as a FromRadio packet fromRadioScratch.which_variant = FromRadio_packet_tag; fromRadioScratch.variant.packet = *packetForPhone; diff --git a/src/mesh/ProtobufPlugin.h b/src/mesh/ProtobufPlugin.h index cda4ed8b..a5c0aea1 100644 --- a/src/mesh/ProtobufPlugin.h +++ b/src/mesh/ProtobufPlugin.h @@ -55,11 +55,11 @@ template class ProtobufPlugin : private SinglePortPlugin // 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); + DEBUG_MSG("Received %s from=0x%0x, id=0x%x, 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 handleReceivedProtobuf(mp, scratch); return false; // Let others look at this message also if they want } diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp index a1bbc5c4..f52fd7c2 100644 --- a/src/mesh/RadioInterface.cpp +++ b/src/mesh/RadioInterface.cpp @@ -123,7 +123,7 @@ void printPacket(const char *prefix, const MeshPacket *p) auto &s = p->decoded; switch (s.which_payload) { case SubPacket_data_tag: - DEBUG_MSG(" Payload:Data"); + DEBUG_MSG(" Portnum=%d", s.data.portnum); break; case SubPacket_position_tag: DEBUG_MSG(" Payload:Position"); diff --git a/src/mesh/ReliableRouter.cpp b/src/mesh/ReliableRouter.cpp index bbbb5238..93402c12 100644 --- a/src/mesh/ReliableRouter.cpp +++ b/src/mesh/ReliableRouter.cpp @@ -92,7 +92,7 @@ void ReliableRouter::sendAckNak(bool isAck, NodeNum to, PacketId idFrom) auto p = allocForSending(); p->hop_limit = 0; // Assume just immediate neighbors for now p->to = to; - DEBUG_MSG("Sending an ack=0x%x,to=0x%x,idFrom=%d,id=%d\n", isAck, to, idFrom, p->id); + DEBUG_MSG("Sending an ack=0x%x,to=0x%x,idFrom=0x%x,id=0x%x\n", isAck, to, idFrom, p->id); if (isAck) { p->decoded.ack.success_id = idFrom; diff --git a/src/plugins/RemoteHardwarePlugin.cpp b/src/plugins/RemoteHardwarePlugin.cpp index dee93363..e9f25e51 100644 --- a/src/plugins/RemoteHardwarePlugin.cpp +++ b/src/plugins/RemoteHardwarePlugin.cpp @@ -51,6 +51,8 @@ RemoteHardwarePlugin::RemoteHardwarePlugin() bool RemoteHardwarePlugin::handleReceivedProtobuf(const MeshPacket &req, const HardwareMessage &p) { + DEBUG_MSG("Received RemoteHardware typ=%d\n", p.typ); + switch (p.typ) { case HardwareMessage_Type_WRITE_GPIOS: // Print notification to LCD screen @@ -87,6 +89,7 @@ bool RemoteHardwarePlugin::handleReceivedProtobuf(const MeshPacket &req, const H lastWatchMsec = 0; // Force a new publish soon previousWatch = ~watchGpios; // generate a 'previous' value which is guaranteed to not match (to force an initial publish) enabled = true; // Let our thread run at least once + DEBUG_MSG("Now watching GPIOs 0x%x\n", watchGpios); break; } @@ -98,6 +101,7 @@ bool RemoteHardwarePlugin::handleReceivedProtobuf(const MeshPacket &req, const H DEBUG_MSG("Hardware operation %d not yet implemented! FIXME\n", p.typ); break; } + return true; // handled } @@ -110,6 +114,7 @@ int32_t RemoteHardwarePlugin::runOnce() { if(curVal != previousWatch) { previousWatch = curVal; + DEBUG_MSG("Broadcasting GPIOS 0x%x changed!\n", curVal); // Something changed! Tell the world with a broadcast message HardwareMessage reply = HardwareMessage_init_default; From 0cdc1fc959ebcbda8b08b0534dc118e34e284329 Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Sun, 13 Dec 2020 16:11:38 +0800 Subject: [PATCH 6/9] make gpiowatch work correctly --- src/mesh/Router.cpp | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index 9ea3cceb..458a338f 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -93,19 +93,28 @@ MeshPacket *Router::allocForSending() p->to = NODENUM_BROADCAST; p->hop_limit = HOP_RELIABLE; p->id = generatePacketId(); - p->rx_time = getValidTime(RTCQualityFromNet); // Just in case we process the packet locally - make sure it has a valid timestamp + p->rx_time = + getValidTime(RTCQualityFromNet); // Just in case we process the packet locally - make sure it has a valid timestamp return p; } ErrorCode Router::sendLocal(MeshPacket *p) { + // No need to deliver externally if the destination is the local node if (p->to == nodeDB.getNodeNum()) { - DEBUG_MSG("Enqueuing internal message for the receive queue\n"); + printPacket("Enqueuing local", p); fromRadioQueue.enqueue(p); return ERRNO_OK; - } else - return send(p); + } + + // If we are sending a broadcast, we also treat it as if we just received it ourself + // this allows local apps (and PCs) to see broadcasts sourced locally + if (p->to == NODENUM_BROADCAST) { + handleReceived(p); + } + + return send(p); } /** From 5cdc2f5142ba4c9cc87f07dc2fb5eb231931e6c8 Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Mon, 14 Dec 2020 20:50:01 +0800 Subject: [PATCH 7/9] Make ChannelSettings SUPER short for common channels --- proto | 2 +- src/mesh/NodeDB.cpp | 55 ++++++++++++++++++++++++++++++++----- src/mesh/NodeDB.h | 1 + src/mesh/RadioInterface.cpp | 4 +-- 4 files changed, 52 insertions(+), 10 deletions(-) diff --git a/proto b/proto index d0868e36..b1e1a543 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit d0868e366bc8f8d9b7ed1d1c5a80cac0de9dc956 +Subproject commit b1e1a54330d1a7f74f097dbf27cdba9cfb739473 diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index f6ef03f5..bc38ad56 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -57,6 +57,13 @@ static uint8_t ourMacAddr[6]; */ NodeNum displayedNodeNum; +/// 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; + /** * 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 @@ -77,10 +84,10 @@ const char *getChannelName() static char buf[32]; uint8_t code = 0; - for (int i = 0; i < channelSettings.psk.size; i++) - code ^= channelSettings.psk.bytes[i]; + for (int i = 0; i < activePSKSize; i++) + code ^= activePSK[i]; - snprintf(buf, sizeof(buf), "#%s-%c", channelSettings.name, 'A' + (code % 26)); + snprintf(buf, sizeof(buf), "#%s-%c", channelName, 'A' + (code % 26)); return buf; } @@ -110,13 +117,47 @@ bool NodeDB::resetRadioConfig() channelSettings.modem_config = ChannelSettings_ModemConfig_Bw125Cr48Sf4096; // slow and long range channelSettings.tx_power = 0; // default - memcpy(&channelSettings.psk.bytes, defaultpsk, sizeof(channelSettings.psk)); - channelSettings.psk.size = sizeof(defaultpsk); - strcpy(channelSettings.name, "Default"); + uint8_t defaultpskIndex = 1; + channelSettings.psk.bytes[0] = defaultpskIndex; + channelSettings.psk.size = 1; + strcpy(channelSettings.name, ""); + } + + // Convert "Default" to our new short representation + if(strcmp(channelSettings.name, "Default") == 0) + *channelSettings.name = '\0'; + + // Convert the short "" representation for Default into a usable string + channelName = channelSettings.name; + if(!*channelName) // emptystring + channelName = "Default"; + + // 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 + } } // Tell our crypto engine about the psk - crypto->setKey(channelSettings.psk.size, channelSettings.psk.bytes); + crypto->setKey(activePSKSize, activePSK); // temp hack for quicker testing // devicestate.no_save = true; diff --git a/src/mesh/NodeDB.h b/src/mesh/NodeDB.h index d595662d..65a276ae 100644 --- a/src/mesh/NodeDB.h +++ b/src/mesh/NodeDB.h @@ -13,6 +13,7 @@ extern MyNodeInfo &myNodeInfo; extern RadioConfig &radioConfig; extern ChannelSettings &channelSettings; extern User &owner; +extern const char *channelName; /// Given a node, return how many seconds in the past (vs now) that we last heard from it uint32_t sinceLastSeen(const NodeInfo *n); diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp index f52fd7c2..b1aa807b 100644 --- a/src/mesh/RadioInterface.cpp +++ b/src/mesh/RadioInterface.cpp @@ -262,10 +262,10 @@ void RadioInterface::applyModemConfig() // If user has manually specified a channel num, then use that, otherwise generate one by hashing the name int channel_num = - (channelSettings.channel_num ? channelSettings.channel_num - 1 : hash(channelSettings.name)) % myRegion->numChannels; + (channelSettings.channel_num ? channelSettings.channel_num - 1 : hash(channelName)) % myRegion->numChannels; freq = myRegion->freq + myRegion->spacing * channel_num; - DEBUG_MSG("Set radio: name=%s, config=%u, ch=%d, power=%d\n", channelSettings.name, channelSettings.modem_config, channel_num, + DEBUG_MSG("Set radio: name=%s, config=%u, ch=%d, power=%d\n", channelName, channelSettings.modem_config, channel_num, power); DEBUG_MSG("Radio myRegion->freq: %f\n", myRegion->freq); DEBUG_MSG("Radio myRegion->spacing: %f\n", myRegion->spacing); From c9f2318e78602cd4a8887af343201bc2725f08b6 Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Tue, 15 Dec 2020 13:14:36 +0800 Subject: [PATCH 8/9] Use simpler names for standard channels --- docs/software/TODO.md | 3 ++- proto | 2 +- src/mesh/NodeDB.cpp | 44 +++++++++++++++++++++++++++++++++---------- 3 files changed, 37 insertions(+), 12 deletions(-) diff --git a/docs/software/TODO.md b/docs/software/TODO.md index ac5c2d33..e1133ee7 100644 --- a/docs/software/TODO.md +++ b/docs/software/TODO.md @@ -9,7 +9,8 @@ For app cleanup: * DONE check build guide * DONE write devapi user guide * DONE update android code: https://developer.android.com/topic/libraries/view-binding/migration -* test GPIO watch +* DONE test GPIO watch +* set --set-chan-fast, --set-chan-default * writeup docs on gpio * DONE make python ping command * DONE make hello world example service diff --git a/proto b/proto index b1e1a543..5f580041 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit b1e1a54330d1a7f74f097dbf27cdba9cfb739473 +Subproject commit 5f580041beeb593d48eabacd2b959df04558c383 diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index bc38ad56..eb8c8cc3 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -70,10 +70,11 @@ static uint8_t activePSKSize; their nodes * aren't talking to each other. * - * This string is of the form "#name-XY". + * This string is of the form "#name-X". * - * Where X is a letter from A to Z (base26), and formed by xoring all the bytes of the PSK together. - * Y is not yet used but should eventually indicate 'speed/range' of the link + * 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. * * This function will also need to be implemented in GUI apps that talk to the radio. * @@ -83,11 +84,19 @@ const char *getChannelName() { static char buf[32]; - uint8_t code = 0; - for (int i = 0; i < activePSKSize; i++) - code ^= activePSK[i]; + 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]; - snprintf(buf, sizeof(buf), "#%s-%c", channelName, 'A' + (code % 26)); + suffix = 'A' + (code % 26); + } else { + suffix = '0' + channelSettings.psk.bytes[0]; + } + + snprintf(buf, sizeof(buf), "#%s-%c", channelName, suffix); return buf; } @@ -123,14 +132,29 @@ bool NodeDB::resetRadioConfig() strcpy(channelSettings.name, ""); } - // Convert "Default" to our new short representation + // Convert the old string "Default" to our new short representation if(strcmp(channelSettings.name, "Default") == 0) *channelSettings.name = '\0'; // Convert the short "" representation for Default into a usable string channelName = channelSettings.name; - if(!*channelName) // emptystring - channelName = "Default"; + 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: + channelName = "MediumRange"; break; + case ChannelSettings_ModemConfig_Bw500Cr45Sf128: + channelName = "Fast"; break; + case ChannelSettings_ModemConfig_Bw31_25Cr48Sf512: + channelName = "LongAlt"; break; + case ChannelSettings_ModemConfig_Bw125Cr48Sf4096: + channelName = "LongSlow"; break; + default: + channelName = "Invalid"; break; + } + } // Convert any old usage of the defaultpsk into our new short representation. if(channelSettings.psk.size == sizeof(defaultpsk) && From be38a58a62aa883b9a99aa1ed88c992f36b6a524 Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Tue, 15 Dec 2020 16:13:16 +0800 Subject: [PATCH 9/9] finish channel name cleanup --- docs/software/TODO.md | 2 +- proto | 2 +- src/mesh/NodeDB.cpp | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/software/TODO.md b/docs/software/TODO.md index e1133ee7..086afc16 100644 --- a/docs/software/TODO.md +++ b/docs/software/TODO.md @@ -10,7 +10,7 @@ For app cleanup: * DONE write devapi user guide * DONE update android code: https://developer.android.com/topic/libraries/view-binding/migration * DONE test GPIO watch -* set --set-chan-fast, --set-chan-default +* DONE set --set-chan-fast, --set-chan-default * writeup docs on gpio * DONE make python ping command * DONE make hello world example service diff --git a/proto b/proto index 5f580041..e0df9711 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit 5f580041beeb593d48eabacd2b959df04558c383 +Subproject commit e0df97118b3dc8105c9c8dbd59e9bb8cd859b1bb diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index eb8c8cc3..ed6720ea 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -144,9 +144,9 @@ bool NodeDB::resetRadioConfig() channelName = "Unset"; if(channelSettings.bandwidth == 0) switch(channelSettings.modem_config) { case ChannelSettings_ModemConfig_Bw125Cr45Sf128: - channelName = "MediumRange"; break; + channelName = "Medium"; break; case ChannelSettings_ModemConfig_Bw500Cr45Sf128: - channelName = "Fast"; break; + channelName = "ShortFast"; break; case ChannelSettings_ModemConfig_Bw31_25Cr48Sf512: channelName = "LongAlt"; break; case ChannelSettings_ModemConfig_Bw125Cr48Sf4096: