mirror of
https://github.com/meshtastic/firmware.git
synced 2025-02-02 10:50:40 +00:00
commit
cc127f7dad
@ -9,10 +9,11 @@ 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
|
||||
* make gpio watch work, use thread and setup
|
||||
* DONE test GPIO watch
|
||||
* DONE set --set-chan-fast, --set-chan-default
|
||||
* writeup docs on gpio
|
||||
* DONE make python ping command
|
||||
* DONE make hello world example service
|
||||
* make python ping command
|
||||
* 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
|
||||
@ -31,6 +32,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
|
||||
|
@ -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.
|
||||
|
2
proto
2
proto
@ -1 +1 @@
|
||||
Subproject commit d0868e366bc8f8d9b7ed1d1c5a80cac0de9dc956
|
||||
Subproject commit e0df97118b3dc8105c9c8dbd59e9bb8cd859b1bb
|
@ -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");
|
||||
}
|
@ -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);
|
||||
|
@ -5,6 +5,8 @@
|
||||
|
||||
std::vector<MeshPlugin *> *MeshPlugin::plugins;
|
||||
|
||||
const MeshPacket *MeshPlugin::currentRequest;
|
||||
|
||||
MeshPlugin::MeshPlugin(const char *_name) : name(_name)
|
||||
{
|
||||
// Can't trust static initalizer order, so we check each time
|
||||
@ -25,23 +27,30 @@ 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 (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);
|
||||
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
|
||||
|
@ -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
|
||||
|
@ -57,16 +57,24 @@ 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
|
||||
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.
|
||||
*
|
||||
@ -76,11 +84,19 @@ 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];
|
||||
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", channelSettings.name, 'A' + (code % 26));
|
||||
suffix = 'A' + (code % 26);
|
||||
} else {
|
||||
suffix = '0' + channelSettings.psk.bytes[0];
|
||||
}
|
||||
|
||||
snprintf(buf, sizeof(buf), "#%s-%c", channelName, suffix);
|
||||
return buf;
|
||||
}
|
||||
|
||||
@ -110,13 +126,62 @@ 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 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
|
||||
// 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 = "Medium"; break;
|
||||
case ChannelSettings_ModemConfig_Bw500Cr45Sf128:
|
||||
channelName = "ShortFast"; 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) &&
|
||||
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;
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -55,11 +55,11 @@ template <class T> 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
|
||||
}
|
||||
|
@ -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");
|
||||
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -10,9 +10,49 @@ 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)
|
||||
{
|
||||
DEBUG_MSG("Received RemoteHardware typ=%d\n", p.typ);
|
||||
|
||||
switch (p.typ) {
|
||||
case HardwareMessage_Type_WRITE_GPIOS:
|
||||
// Print notification to LCD screen
|
||||
@ -22,24 +62,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 +84,15 @@ 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
|
||||
DEBUG_MSG("Now watching GPIOs 0x%x\n", watchGpios);
|
||||
break;
|
||||
}
|
||||
|
||||
case HardwareMessage_Type_READ_GPIOS_REPLY:
|
||||
case HardwareMessage_Type_GPIOS_CHANGED:
|
||||
break; // Ignore - we might see our own replies
|
||||
@ -59,5 +101,34 @@ bool RemoteHardwarePlugin::handleReceivedProtobuf(const MeshPacket &req, const H
|
||||
DEBUG_MSG("Hardware operation %d not yet implemented! FIXME\n", p.typ);
|
||||
break;
|
||||
}
|
||||
|
||||
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;
|
||||
DEBUG_MSG("Broadcasting GPIOS 0x%x changed!\n", 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)
|
||||
}
|
@ -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<HardwareMessage>
|
||||
class RemoteHardwarePlugin : public ProtobufPlugin<HardwareMessage>, 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<HardwareMessage>
|
||||
@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;
|
@ -1,6 +1,6 @@
|
||||
#include "configuration.h"
|
||||
#include "ReplyPlugin.h"
|
||||
#include "MeshService.h"
|
||||
#include "configuration.h"
|
||||
#include "main.h"
|
||||
|
||||
#include <assert.h>
|
||||
@ -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;
|
||||
}
|
||||
|
@ -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();
|
||||
};
|
||||
|
@ -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
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user