From 90060e84c004822a439296cac0f987a8a68c2892 Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Mon, 7 Dec 2020 10:18:11 +0800 Subject: [PATCH] WIP on GPIO example --- docs/software/TODO.md | 2 +- proto | 2 +- src/mesh/MeshPlugin.cpp | 27 ++++++++++++++- src/mesh/MeshPlugin.h | 19 ++++++++--- src/mesh/ProtobufPlugin.h | 7 ++-- src/mesh/SinglePortPlugin.h | 16 +++++++++ src/mesh/remote_hardware.pb.h | 28 +++++++-------- src/plugins/NodeInfoPlugin.cpp | 19 ++++------- src/plugins/NodeInfoPlugin.h | 6 ++-- src/plugins/PositionPlugin.cpp | 17 ++++------ src/plugins/PositionPlugin.h | 6 ++-- src/plugins/RemoteHardwarePlugin.cpp | 51 +++++++++++++++++++++++++--- 12 files changed, 140 insertions(+), 60 deletions(-) diff --git a/docs/software/TODO.md b/docs/software/TODO.md index 5a26c8e10..4901ad40c 100644 --- a/docs/software/TODO.md +++ b/docs/software/TODO.md @@ -13,7 +13,7 @@ For app cleanup: * 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 -* make a gpio example +* make a gpio example. --gpiowrb 5, --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) diff --git a/proto b/proto index 6e8d220ad..f38b8e304 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit 6e8d220ad0d9f7ae6ce37db94c2b3f55a70f4f45 +Subproject commit f38b8e3040528aaf1f1b4b9024ff8df2e14ba961 diff --git a/src/mesh/MeshPlugin.cpp b/src/mesh/MeshPlugin.cpp index 77aa1b0a5..5320b7ae1 100644 --- a/src/mesh/MeshPlugin.cpp +++ b/src/mesh/MeshPlugin.cpp @@ -1,5 +1,6 @@ #include "MeshPlugin.h" #include "NodeDB.h" +#include "MeshService.h" #include std::vector *MeshPlugin::plugins; @@ -31,7 +32,7 @@ void MeshPlugin::callPlugins(const MeshPacket &mp) // Possibly send replies (unless we are handling a locally generated message) if (mp.decoded.want_response && mp.from != nodeDB.getNodeNum()) - pi.sendResponse(mp.from); + pi.sendResponse(mp); DEBUG_MSG("Plugin %s handled=%d\n", pi.name, handled); if (handled) @@ -41,4 +42,28 @@ void MeshPlugin::callPlugins(const MeshPacket &mp) 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 index 926157af9..f753651ec 100644 --- a/src/mesh/MeshPlugin.h +++ b/src/mesh/MeshPlugin.h @@ -49,8 +49,19 @@ class MeshPlugin 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. Implementing this method - * is optional + * 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 */ - virtual void sendResponse(NodeNum to) {} -}; \ No newline at end of file + 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/ProtobufPlugin.h b/src/mesh/ProtobufPlugin.h index f6bac2345..cda4ed8bf 100644 --- a/src/mesh/ProtobufPlugin.h +++ b/src/mesh/ProtobufPlugin.h @@ -1,6 +1,5 @@ #pragma once #include "SinglePortPlugin.h" -#include "Router.h" /** * A base class for mesh plugins that assume that they are sending/receiving one particular protobuf based @@ -34,12 +33,10 @@ template class ProtobufPlugin : private SinglePortPlugin * You can then send this packet (after customizing any of the payload fields you might need) with * service.sendToMesh() */ - MeshPacket *allocForSending(const T &payload) + 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 = router->allocForSending(); - p->decoded.which_payload = SubPacket_data_tag; - p->decoded.data.portnum = ourPortNum; + 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); diff --git a/src/mesh/SinglePortPlugin.h b/src/mesh/SinglePortPlugin.h index 9d7e41030..01ee1963a 100644 --- a/src/mesh/SinglePortPlugin.h +++ b/src/mesh/SinglePortPlugin.h @@ -1,5 +1,6 @@ #pragma once #include "MeshPlugin.h" +#include "Router.h" /** * Most plugins are only interested in sending/receving one particular portnum. This baseclass simplifies that common @@ -21,4 +22,19 @@ class SinglePortPlugin : public MeshPlugin * @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/remote_hardware.pb.h b/src/mesh/remote_hardware.pb.h index 036d82fb8..f85cd8ff2 100644 --- a/src/mesh/remote_hardware.pb.h +++ b/src/mesh/remote_hardware.pb.h @@ -14,32 +14,32 @@ extern "C" { #endif /* Enum definitions */ -typedef enum _HardwareMessage_MessageType { - HardwareMessage_MessageType_UNSET = 0, - HardwareMessage_MessageType_WRITE_GPIOS = 1, - HardwareMessage_MessageType_WATCH_GPIOS = 2, - HardwareMessage_MessageType_GPIOS_CHANGED = 3, - HardwareMessage_MessageType_READ_GPIOS = 4, - HardwareMessage_MessageType_READ_GPIOS_REPLY = 5 -} HardwareMessage_MessageType; +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_MessageType typ; + HardwareMessage_Type typ; uint64_t gpio_mask; uint64_t gpio_value; } HardwareMessage; /* Helper constants for enums */ -#define _HardwareMessage_MessageType_MIN HardwareMessage_MessageType_UNSET -#define _HardwareMessage_MessageType_MAX HardwareMessage_MessageType_READ_GPIOS_REPLY -#define _HardwareMessage_MessageType_ARRAYSIZE ((HardwareMessage_MessageType)(HardwareMessage_MessageType_READ_GPIOS_REPLY+1)) +#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_MessageType_MIN, 0, 0} -#define HardwareMessage_init_zero {_HardwareMessage_MessageType_MIN, 0, 0} +#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 diff --git a/src/plugins/NodeInfoPlugin.cpp b/src/plugins/NodeInfoPlugin.cpp index c6d8e6a70..876f5ea02 100644 --- a/src/plugins/NodeInfoPlugin.cpp +++ b/src/plugins/NodeInfoPlugin.cpp @@ -28,22 +28,17 @@ bool NodeInfoPlugin::handleReceivedProtobuf(const MeshPacket &mp, const User &p) void NodeInfoPlugin::sendOurNodeInfo(NodeNum dest, bool wantReplies) { - User &u = owner; - - DEBUG_MSG("sending owner %s/%s/%s\n", u.id, u.long_name, u.short_name); - MeshPacket *p = allocForSending(u); + MeshPacket *p = allocReply(); p->to = dest; p->decoded.want_response = wantReplies; service.sendToMesh(p); } +MeshPacket *NodeInfoPlugin::allocReply() +{ + User &u = owner; -/** 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 NodeInfoPlugin::sendResponse(NodeNum to) { - DEBUG_MSG("Sending user reply\n"); - sendOurNodeInfo(to, false); -} \ No newline at end of file + 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 index 98a620e25..968d5f8b7 100644 --- a/src/plugins/NodeInfoPlugin.h +++ b/src/plugins/NodeInfoPlugin.h @@ -25,10 +25,8 @@ class NodeInfoPlugin : public ProtobufPlugin 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. Implementing this method - * is optional - */ - virtual void sendResponse(NodeNum to); + * 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 index c1f813a4b..4b06f0eed 100644 --- a/src/plugins/PositionPlugin.cpp +++ b/src/plugins/PositionPlugin.cpp @@ -28,7 +28,7 @@ bool PositionPlugin::handleReceivedProtobuf(const MeshPacket &mp, const Position return false; // Let others look at this message also if they want } -void PositionPlugin::sendOurPosition(NodeNum dest, bool wantReplies) +MeshPacket *PositionPlugin::allocReply() { NodeInfo *node = nodeDB.getNode(nodeDB.getNodeNum()); assert(node); @@ -38,18 +38,15 @@ void PositionPlugin::sendOurPosition(NodeNum dest, bool wantReplies) auto position = node->position; position.time = getValidTime(RTCQualityGPS); // This nodedb timestamp might be stale, so update it if our clock is valid. - MeshPacket *p = allocForSending(position); + return allocDataProtobuf(position); +} + +void PositionPlugin::sendOurPosition(NodeNum dest, bool wantReplies) +{ + MeshPacket *p = allocReply(); p->to = dest; p->decoded.want_response = wantReplies; service.sendToMesh(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. Implementing this method - * is optional - */ -void PositionPlugin::sendResponse(NodeNum to) { - DEBUG_MSG("Sending posistion reply\n"); - sendOurPosition(to, false); -} \ No newline at end of file diff --git a/src/plugins/PositionPlugin.h b/src/plugins/PositionPlugin.h index 4ca146dd4..9153a2288 100644 --- a/src/plugins/PositionPlugin.h +++ b/src/plugins/PositionPlugin.h @@ -26,10 +26,8 @@ class PositionPlugin : public ProtobufPlugin 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. Implementing this method - * is optional - */ - virtual void sendResponse(NodeNum to); + * 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 index 70c7232af..085d7d693 100644 --- a/src/plugins/RemoteHardwarePlugin.cpp +++ b/src/plugins/RemoteHardwarePlugin.cpp @@ -8,10 +8,53 @@ RemoteHardwarePlugin remoteHardwarePlugin; -bool RemoteHardwarePlugin::handleReceivedProtobuf(const MeshPacket &mp, const HardwareMessage &p) +#define NUM_GPIOS 64 + +// A macro for clearing a struct, FIXME, move elsewhere +#define CLEAR_STRUCT(r) memset(&r, 0, sizeof(r)) + +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"); - return false; // Let others look at this message also if they want + 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"); + + 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; + CLEAR_STRUCT(reply); + reply.typ = HardwareMessage_Type_READ_GPIOS_REPLY; + reply.gpio_value = res; + MeshPacket *p = allocDataProtobuf(reply); + setReplyTo(p, req); + service.sendToMesh(p); + break; + } + default: + DEBUG_MSG("Hardware operation %d not yet implemented! FIXME\n", p.typ); + break; + } + return true; // handled } - -