Merge pull request #74 from meshtastic/master

Update from head
This commit is contained in:
Jm Casler 2021-03-07 23:25:52 -08:00 committed by GitHub
commit 77bac11d82
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
43 changed files with 446 additions and 266 deletions

View File

@ -2,7 +2,7 @@
You probably don't care about this section - skip to the next one. You probably don't care about this section - skip to the next one.
1.2 cleanup & multichannel support: ## 1.2 cleanup & multichannel support:
* DONE call RouterPlugin for *all* packets - not just Router packets * DONE call RouterPlugin for *all* packets - not just Router packets
* DONE generate channel hash from the name of the channel+the psk (not just one or the other) * DONE generate channel hash from the name of the channel+the psk (not just one or the other)
@ -21,31 +21,40 @@ You probably don't care about this section - skip to the next one.
* DONE implement 'get channels' Admin plugin operation * DONE implement 'get channels' Admin plugin operation
* DONE use get-channels from python * DONE use get-channels from python
* DONE use get channels & get settings from android * DONE use get channels & get settings from android
* use set-channel from python * DONE use set-channel from python
* DONE make settings changes from python work * DONE make settings changes from python work
* DONE pthon should stop fetching channels once we've reached our first empty channel definition (hasSettings == true) * DONE pthon should stop fetching channels once we've reached our first empty channel definition (hasSettings == true)
* DONE add check for old devices with new API library * DONE add check for old devices with new API library
* DONE release python api * DONE release python api
* DONE release protobufs * DONE release protobufs
* DONE release to developers * DONE release to developers
* DONE fix setch-fast in python tool
* age out pendingrequests in the python API
* DONE stress test channel download from python, sometimes it seems like we don't get all replies, bug was due to simultaneous android connection
* DONE combine acks and responses in a single message if possible (do routing plugin LAST and drop ACK if someone else has already replied)
* DONE don't send packets we received from the phone BACK TOWARDS THE PHONE (possibly use fromnode 0 for packets the phone sends?)
* fix 1.1.50 android debug panel display * fix 1.1.50 android debug panel display
* add gui in android app for setting region * DONE test android channel setting
* stress test channel download from python, sometimes it seems like we don't get all replies * DONE release to users
* investigate @mc-hamster report of heap corruption * DONE warn in android app about unset regions
* use set-channel from android * DONE use set-channel from android
* DONE use set-user from android * DONE add gui in android app for setting region
* combine acks and responses in a single message if possible (do routing plugin LAST and drop ACK if someone else has already replied)
* don't send packets we received from the phone BACK TOWARDS THE PHONE (possibly use fromnode 0 for packets the phone sends?)
* use portuino TCP connection to debug with python API
* make python tests more exhaustive * make python tests more exhaustive
* document the relationship between want_response (indicating remote node received it) and want_ack (indicating that this message should be sent reliably - and also get acks from the first rx node and naks if it is never delivered)
* stress test multi channel
* pick default random admin key * pick default random admin key
* DONE android should stop fetching channels once we've reached our first empty channel definition (hasSettings == true) * exclude admin channels from URL?
* add channel restrictions for plugins (and restrict routing plugin to the "control" channel) * make a way to share just secondary channels via URL
* use single byte 'well known' channel names for the four default channel names (longslow etc), and for admin, gpio, etc...
* use presence of gpio channel to enable gpio ops, same for serial etc...
* restrict gpio & serial & settings operations to the admin channel (unless local to the current node) * restrict gpio & serial & settings operations to the admin channel (unless local to the current node)
* warn in python api if we are too new to talk to the device code * add channel restrictions for plugins (and restrict routing plugin to the "control" channel)
* make a post warning about 1.2, telling how to stay on old android & python clients. link to this from the android dialog message and python version warning. * stress test multi channel
* investigate @mc-hamster report of heap corruption
* DONE use set-user from android
* use portuino TCP connection to debug with python API
* document the relationship between want_response (indicating remote node received it) and want_ack (indicating that this message should be sent reliably - and also get acks from the first rx node and naks if it is never delivered)
* DONE android should stop fetching channels once we've reached our first empty channel definition (hasSettings == true)
* DONE warn in python api if we are too new to talk to the device code
* DONE make a post warning about 1.2, telling how to stay on old android & python clients. link to this from the android dialog message and python version warning.
* DONE "FIXME - move the radioconfig/user/channel READ operations into SettingsMessage as well" * DONE "FIXME - move the radioconfig/user/channel READ operations into SettingsMessage as well"
* DONE scrub protobufs to make sure they are absoloute minimum wiresize (in particular Data, ChannelSets and positions) * DONE scrub protobufs to make sure they are absoloute minimum wiresize (in particular Data, ChannelSets and positions)
* DONE change syncword (now ox2b) * DONE change syncword (now ox2b)
@ -55,6 +64,7 @@ You probably don't care about this section - skip to the next one.
* confirm we are still calling the plugins for messages inbound from the phone (or generated locally) * confirm we are still calling the plugins for messages inbound from the phone (or generated locally)
* confirm we are still multi hop routing flood broadcasts * confirm we are still multi hop routing flood broadcasts
* confirm we are still doing resends on unicast reliable packets * confirm we are still doing resends on unicast reliable packets
* add history to routed packets: https://meshtastic.discourse.group/t/packet-source-tracking/2764/2
* add support for full DSR unicast delivery * add support for full DSR unicast delivery
* DONE move acks into routing * DONE move acks into routing
* DONE make all subpackets different versions of data * DONE make all subpackets different versions of data

2
proto

@ -1 +1 @@
Subproject commit 94bd0aae44e2c16c7776289225c804100c856cd4 Subproject commit 7c025b9a4d54bb410ec17ee653122861b413f177

View File

@ -178,10 +178,11 @@ void PowerFSM_setup()
bool isLowPower = radioConfig.preferences.is_low_power || isRouter; bool isLowPower = radioConfig.preferences.is_low_power || isRouter;
/* To determine if we're externally powered, assumptions /* To determine if we're externally powered, assumptions
1) If we're powered up and there's no battery, we must be getting power externally. 1) If we're powered up and there's no battery, we must be getting power externally. (because we'd be dead otherwise)
2) If we detect USB power from the power management chip, we must be getting power externally.
2) If we detect USB power from the power management chip, we must be getting power externally.
*/ */
bool hasPower = (powerStatus && !powerStatus->getHasBattery()) || (!isLowPower && powerStatus && powerStatus->getHasUSB()); bool hasPower = !isLowPower && powerStatus && (!powerStatus->getHasBattery() || powerStatus->getHasUSB());
DEBUG_MSG("PowerFSM init, USB power=%d\n", hasPower); DEBUG_MSG("PowerFSM init, USB power=%d\n", hasPower);
powerFSM.add_timed_transition(&stateBOOT, hasPower ? &statePOWER : &stateON, 3 * 1000, NULL, "boot timeout"); powerFSM.add_timed_transition(&stateBOOT, hasPower ? &statePOWER : &stateON, 3 * 1000, NULL, "boot timeout");

View File

@ -54,7 +54,7 @@ class ESP32CryptoEngine : public CryptoEngine
static uint8_t scratch[MAX_BLOCKSIZE]; static uint8_t scratch[MAX_BLOCKSIZE];
size_t nc_off = 0; size_t nc_off = 0;
// DEBUG_MSG("ESP32 encrypt!\n"); // DEBUG_MSG("ESP32 crypt fr=%x, num=%x, numBytes=%d!\n", fromNode, (uint32_t) packetNum, numBytes);
initNonce(fromNode, packetNum); initNonce(fromNode, packetNum);
assert(numBytes <= MAX_BLOCKSIZE); assert(numBytes <= MAX_BLOCKSIZE);
memcpy(scratch, bytes, numBytes); memcpy(scratch, bytes, numBytes);

View File

@ -229,7 +229,7 @@ static void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state
displayedNodeNum = 0; // Not currently showing a node pane displayedNodeNum = 0; // Not currently showing a node pane
MeshPacket &mp = devicestate.rx_text_message; MeshPacket &mp = devicestate.rx_text_message;
NodeInfo *node = nodeDB.getNode(mp.from); NodeInfo *node = nodeDB.getNode(getFrom(&mp));
// DEBUG_MSG("drawing text message from 0x%x: %s\n", mp.from, // DEBUG_MSG("drawing text message from 0x%x: %s\n", mp.from,
// mp.decoded.variant.data.decoded.bytes); // mp.decoded.variant.data.decoded.bytes);

View File

@ -275,10 +275,11 @@ const char *Channels::getPrimaryName()
bool Channels::decryptForHash(ChannelIndex chIndex, ChannelHash channelHash) bool Channels::decryptForHash(ChannelIndex chIndex, ChannelHash channelHash)
{ {
if(chIndex > getNumChannels() || getHash(chIndex) != channelHash) { if(chIndex > getNumChannels() || getHash(chIndex) != channelHash) {
DEBUG_MSG("Skipping channel %d due to invalid hash/index\n", chIndex); // DEBUG_MSG("Skipping channel %d (hash %x) due to invalid hash/index, want=%x\n", chIndex, getHash(chIndex), channelHash);
return false; return false;
} }
else { else {
DEBUG_MSG("Using channel %d (hash 0x%x)\n", chIndex, channelHash);
setCrypto(chIndex); setCrypto(chIndex);
return true; return true;
} }

View File

@ -4,6 +4,10 @@
void CryptoEngine::setKey(const CryptoKey &k) void CryptoEngine::setKey(const CryptoKey &k)
{ {
DEBUG_MSG("Installing AES%d key!\n", k.length * 8); DEBUG_MSG("Installing AES%d key!\n", k.length * 8);
/* for(uint8_t i = 0; i < k.length; i++)
DEBUG_MSG("%02x ", k.bytes[i]);
DEBUG_MSG("\n"); */
key = k; key = k;
} }

View File

@ -69,7 +69,7 @@ void DSRRouter::sniffReceived(const MeshPacket *p, const Routing *c)
// ignore rebroadcasts. // ignore rebroadcasts.
// this will also add records for any ACKs we receive for our messages // this will also add records for any ACKs we receive for our messages
if (p->to != NODENUM_BROADCAST || p->hop_limit != HOP_RELIABLE) { if (p->to != NODENUM_BROADCAST || p->hop_limit != HOP_RELIABLE) {
addRoute(p->from, p->from, 0); // We are adjacent with zero hops addRoute(getFrom(p), getFrom(p), 0); // We are adjacent with zero hops
} }
if (c) if (c)

View File

@ -31,7 +31,7 @@ void FloodingRouter::sniffReceived(const MeshPacket *p, const Routing *c)
{ {
// If a broadcast, possibly _also_ send copies out into the mesh. // If a broadcast, possibly _also_ send copies out into the mesh.
// (FIXME, do something smarter than naive flooding here) // (FIXME, do something smarter than naive flooding here)
if (p->to == NODENUM_BROADCAST && p->hop_limit > 0 && p->from != getNodeNum()) { if (p->to == NODENUM_BROADCAST && p->hop_limit > 0 && getFrom(p) != getNodeNum()) {
if (p->id != 0) { if (p->id != 0) {
MeshPacket *tosend = packetPool.allocCopy(*p); // keep a copy because we will be sending it MeshPacket *tosend = packetPool.allocCopy(*p); // keep a copy because we will be sending it

View File

@ -71,7 +71,7 @@ static PacketId findId;
static bool isMyPacket(MeshPacket *p) static bool isMyPacket(MeshPacket *p)
{ {
return p->id == findId && p->from == findFrom; return p->id == findId && getFrom(p) == findFrom;
} }
/** Attempt to find and remove a packet from this queue. Returns true the packet which was removed from the queue */ /** Attempt to find and remove a packet from this queue. Returns true the packet which was removed from the queue */

View File

@ -1,23 +1,28 @@
#include "MeshPlugin.h" #include "MeshPlugin.h"
#include "NodeDB.h"
#include "MeshService.h" #include "MeshService.h"
#include "NodeDB.h"
#include <assert.h> #include <assert.h>
std::vector<MeshPlugin *> *MeshPlugin::plugins; std::vector<MeshPlugin *> *MeshPlugin::plugins;
const MeshPacket *MeshPlugin::currentRequest; const MeshPacket *MeshPlugin::currentRequest;
/**
* If any of the current chain of plugins has already sent a reply, it will be here. This is useful to allow
* the RoutingPlugin to avoid sending redundant acks
*/
MeshPacket *MeshPlugin::currentReply;
MeshPlugin::MeshPlugin(const char *_name) : name(_name) MeshPlugin::MeshPlugin(const char *_name) : name(_name)
{ {
// Can't trust static initalizer order, so we check each time // Can't trust static initalizer order, so we check each time
if(!plugins) if (!plugins)
plugins = new std::vector<MeshPlugin *>(); plugins = new std::vector<MeshPlugin *>();
plugins->push_back(this); plugins->push_back(this);
} }
void MeshPlugin::setup() { void MeshPlugin::setup() {}
}
MeshPlugin::~MeshPlugin() MeshPlugin::~MeshPlugin()
{ {
@ -31,6 +36,8 @@ void MeshPlugin::callPlugins(const MeshPacket &mp)
assert(mp.which_payloadVariant == MeshPacket_decoded_tag); // I think we are guarnteed the packet is decoded by this point? assert(mp.which_payloadVariant == MeshPacket_decoded_tag); // I think we are guarnteed the packet is decoded by this point?
currentReply = NULL; // No reply yet
// Was this message directed to us specifically? Will be false if we are sniffing someone elses packets // Was this message directed to us specifically? Will be false if we are sniffing someone elses packets
auto ourNodeNum = nodeDB.getNodeNum(); auto ourNodeNum = nodeDB.getNodeNum();
bool toUs = mp.to == NODENUM_BROADCAST || mp.to == ourNodeNum; bool toUs = mp.to == NODENUM_BROADCAST || mp.to == ourNodeNum;
@ -48,15 +55,16 @@ void MeshPlugin::callPlugins(const MeshPacket &mp)
bool handled = pi.handleReceived(mp); bool handled = pi.handleReceived(mp);
// Possibly send replies (but only if the message was directed to us specifically, i.e. not for promiscious sniffing) // Possibly send replies (but only if the message was directed to us specifically, i.e. not for promiscious sniffing)
// also: we only let the one plugin send a reply, once that happens, remaining plugins are not considered
// NOTE: we send a reply *even if the (non broadcast) request was from us* which is unfortunate but necessary because currently when the phone // NOTE: we send a reply *even if the (non broadcast) request was from us* which is unfortunate but necessary because
// sends things, it sends things using the local node ID as the from address. A better solution (FIXME) would be to let phones // currently when the phone sends things, it sends things using the local node ID as the from address. A better
// have their own distinct addresses and we 'route' to them like any other node. // solution (FIXME) would be to let phones have their own distinct addresses and we 'route' to them like any other
if (mp.decoded.want_response && toUs && (mp.from != ourNodeNum || mp.to == ourNodeNum)) { // node.
if (mp.decoded.want_response && toUs && (getFrom(&mp) != ourNodeNum || mp.to == ourNodeNum) && !currentReply) {
pi.sendResponse(mp); pi.sendResponse(mp);
DEBUG_MSG("Plugin %s sent a response\n", pi.name); DEBUG_MSG("Plugin %s sent a response\n", pi.name);
} } else {
else {
DEBUG_MSG("Plugin %s considered\n", pi.name); DEBUG_MSG("Plugin %s considered\n", pi.name);
} }
if (handled) { if (handled) {
@ -64,11 +72,17 @@ void MeshPlugin::callPlugins(const MeshPacket &mp)
break; break;
} }
} }
pi.currentRequest = NULL; pi.currentRequest = NULL;
} }
if(!pluginFound) if(currentReply) {
DEBUG_MSG("Sending response\n");
service.sendToMesh(currentReply);
currentReply = NULL;
}
if (!pluginFound)
DEBUG_MSG("No plugins interested in portnum=%d\n", mp.decoded.portnum); DEBUG_MSG("No plugins interested in portnum=%d\n", mp.decoded.portnum);
} }
@ -76,39 +90,43 @@ void MeshPlugin::callPlugins(const MeshPacket &mp)
* so that subclasses can (optionally) send a response back to the original sender. Implementing this method * so that subclasses can (optionally) send a response back to the original sender. Implementing this method
* is optional * is optional
*/ */
void MeshPlugin::sendResponse(const MeshPacket &req) { void MeshPlugin::sendResponse(const MeshPacket &req)
{
auto r = allocReply(); auto r = allocReply();
if(r) { if (r) {
DEBUG_MSG("Sending response\n");
setReplyTo(r, req); setReplyTo(r, req);
service.sendToMesh(r); currentReply = r;
} } else {
else {
// Ignore - this is now expected behavior for routing plugin (because it ignores some replies) // Ignore - this is now expected behavior for routing plugin (because it ignores some replies)
// DEBUG_MSG("WARNING: Client requested response but this plugin did not provide\n"); // 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 /** 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. * 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) { void setReplyTo(MeshPacket *p, const MeshPacket &to)
{
assert(p->which_payloadVariant == MeshPacket_decoded_tag); // Should already be set by now assert(p->which_payloadVariant == MeshPacket_decoded_tag); // Should already be set by now
p->to = to.from; p->to = getFrom(&to);
p->want_ack = to.want_ack;
// No need for an ack if we are just delivering locally (it just generates an ignored ack)
p->want_ack = (to.from != 0) ? to.want_ack : false;
if(p->priority == MeshPacket_Priority_UNSET)
p->priority = MeshPacket_Priority_RELIABLE;
p->decoded.request_id = to.id; p->decoded.request_id = to.id;
} }
std::vector<MeshPlugin *> MeshPlugin::GetMeshPluginsWithUIFrames() { std::vector<MeshPlugin *> MeshPlugin::GetMeshPluginsWithUIFrames()
{
std::vector<MeshPlugin *> pluginsWithUIFrames; std::vector<MeshPlugin *> pluginsWithUIFrames;
for (auto i = plugins->begin(); i != plugins->end(); ++i) { for (auto i = plugins->begin(); i != plugins->end(); ++i) {
auto &pi = **i; auto &pi = **i;
if ( pi.wantUIFrame()) { if (pi.wantUIFrame()) {
DEBUG_MSG("Plugin wants a UI Frame\n"); DEBUG_MSG("Plugin wants a UI Frame\n");
pluginsWithUIFrames.push_back(&pi); pluginsWithUIFrames.push_back(&pi);
} }
} }
return pluginsWithUIFrames; return pluginsWithUIFrames;
} }

View File

@ -82,6 +82,13 @@ class MeshPlugin
private: private:
/**
* If any of the current chain of plugins has already sent a reply, it will be here. This is useful to allow
* the RoutingPlugin to avoid sending redundant acks
*/
static MeshPacket *currentReply;
friend class ReliableRouter;
/** Messages can be received that have the want_response bit set. If set, this callback will be invoked /** 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() * 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 * to generate the reply message, and if !NULL that message will be delivered to whoever sent req

View File

@ -13,8 +13,8 @@
#include "RTC.h" #include "RTC.h"
#include "main.h" #include "main.h"
#include "mesh-pb-constants.h" #include "mesh-pb-constants.h"
#include "plugins/PositionPlugin.h"
#include "plugins/NodeInfoPlugin.h" #include "plugins/NodeInfoPlugin.h"
#include "plugins/PositionPlugin.h"
#include "power.h" #include "power.h"
/* /*
@ -51,8 +51,6 @@ MeshService service;
#include "Router.h" #include "Router.h"
MeshService::MeshService() : toPhoneQueue(MAX_RX_TOPHONE) MeshService::MeshService() : toPhoneQueue(MAX_RX_TOPHONE)
{ {
// assert(MAX_RX_TOPHONE == 32); // FIXME, delete this, just checking my clever macro // assert(MAX_RX_TOPHONE == 32); // FIXME, delete this, just checking my clever macro
@ -67,7 +65,6 @@ void MeshService::init()
gpsObserver.observe(&gps->newStatus); gpsObserver.observe(&gps->newStatus);
} }
int MeshService::handleFromRadio(const MeshPacket *mp) int MeshService::handleFromRadio(const MeshPacket *mp)
{ {
powerFSM.trigger(EVENT_RECEIVED_PACKET); // Possibly keep the node from sleeping powerFSM.trigger(EVENT_RECEIVED_PACKET); // Possibly keep the node from sleeping
@ -117,7 +114,8 @@ bool MeshService::reloadConfig()
void MeshService::reloadOwner() void MeshService::reloadOwner()
{ {
assert(nodeInfoPlugin); assert(nodeInfoPlugin);
nodeInfoPlugin->sendOurNodeInfo(); if(nodeInfoPlugin)
nodeInfoPlugin->sendOurNodeInfo();
nodeDB.saveToDisk(); nodeDB.saveToDisk();
} }
@ -128,8 +126,12 @@ void MeshService::reloadOwner()
*/ */
void MeshService::handleToRadio(MeshPacket &p) void MeshService::handleToRadio(MeshPacket &p)
{ {
if (p.from == 0) // If the phone didn't set a sending node ID, use ours if (p.from != 0) { // We don't let phones assign nodenums to their sent messages
p.from = nodeDB.getNodeNum(); DEBUG_MSG("Warning: phone tried to pick a nodenum, we don't allow that.\n");
p.from = 0;
} else {
// p.from = nodeDB.getNodeNum();
}
if (p.id == 0) if (p.id == 0)
p.id = generatePacketId(); // If the phone didn't supply one, then pick one p.id = generatePacketId(); // If the phone didn't supply one, then pick one
@ -151,7 +153,8 @@ void MeshService::handleToRadio(MeshPacket &p)
} }
/** Attempt to cancel a previously sent packet from this _local_ node. Returns true if a packet was found we could cancel */ /** Attempt to cancel a previously sent packet from this _local_ node. Returns true if a packet was found we could cancel */
bool MeshService::cancelSending(PacketId id) { bool MeshService::cancelSending(PacketId id)
{
return router->cancelSending(nodeDB.getNodeNum(), id); return router->cancelSending(nodeDB.getNodeNum(), id);
} }
@ -168,29 +171,36 @@ void MeshService::sendNetworkPing(NodeNum dest, bool wantReplies)
NodeInfo *node = nodeDB.getNode(nodeDB.getNodeNum()); NodeInfo *node = nodeDB.getNode(nodeDB.getNodeNum());
assert(node); assert(node);
DEBUG_MSG("Sending network ping to 0x%x, with position=%d, wantReplies=%d\n", dest, node->has_position, wantReplies); if (node->has_position) {
assert(positionPlugin && nodeInfoPlugin); if(positionPlugin) {
if (node->has_position) DEBUG_MSG("Sending position ping to 0x%x, wantReplies=%d\n", dest, wantReplies);
positionPlugin->sendOurPosition(dest, wantReplies); positionPlugin->sendOurPosition(dest, wantReplies);
else }
nodeInfoPlugin->sendOurNodeInfo(dest, wantReplies); }
else {
if(nodeInfoPlugin) {
DEBUG_MSG("Sending nodeinfo ping to 0x%x, wantReplies=%d\n", dest, wantReplies);
nodeInfoPlugin->sendOurNodeInfo(dest, wantReplies);
}
}
} }
NodeInfo *MeshService::refreshMyNodeInfo()
NodeInfo *MeshService::refreshMyNodeInfo() { {
NodeInfo *node = nodeDB.getNode(nodeDB.getNodeNum()); NodeInfo *node = nodeDB.getNode(nodeDB.getNodeNum());
assert(node); assert(node);
// We might not have a position yet for our local node, in that case, at least try to send the time // We might not have a position yet for our local node, in that case, at least try to send the time
if(!node->has_position) { if (!node->has_position) {
memset(&node->position, 0, sizeof(node->position)); memset(&node->position, 0, sizeof(node->position));
node->has_position = true; node->has_position = true;
} }
Position &position = node->position; Position &position = node->position;
// Update our local node info with our position (even if we don't decide to update anyone else) // Update our local node info with our position (even if we don't decide to update anyone else)
position.time = getValidTime(RTCQualityFromNet); // This nodedb timestamp might be stale, so update it if our clock is kinda valid position.time =
getValidTime(RTCQualityFromNet); // This nodedb timestamp might be stale, so update it if our clock is kinda valid
position.battery_level = powerStatus->getBatteryChargePercent(); position.battery_level = powerStatus->getBatteryChargePercent();
updateBatteryLevel(position.battery_level); updateBatteryLevel(position.battery_level);
@ -209,11 +219,10 @@ int MeshService::onGPSChanged(const meshtastic::GPSStatus *unused)
pos.altitude = gps->altitude; pos.altitude = gps->altitude;
pos.latitude_i = gps->latitude; pos.latitude_i = gps->latitude;
pos.longitude_i = gps->longitude; pos.longitude_i = gps->longitude;
} } else {
else {
// The GPS has lost lock, if we are fixed position we should just keep using // The GPS has lost lock, if we are fixed position we should just keep using
// the old position // the old position
if(radioConfig.preferences.fixed_position) { if (radioConfig.preferences.fixed_position) {
DEBUG_MSG("WARNING: Using fixed position\n"); DEBUG_MSG("WARNING: Using fixed position\n");
} else { } else {
// throw away old position // throw away old position

View File

@ -32,4 +32,10 @@ typedef uint32_t PacketId; // A packet sequence number
typedef int ErrorCode; typedef int ErrorCode;
/// Alloc and free packets to our global, ISR safe pool /// Alloc and free packets to our global, ISR safe pool
extern Allocator<MeshPacket> &packetPool; extern Allocator<MeshPacket> &packetPool;
/**
* Most (but not always) of the time we want to treat packets 'from' the local phone (where from == 0), as if they originated on the local node.
* If from is zero this function returns our node number instead
*/
NodeNum getFrom(const MeshPacket *p);

View File

@ -66,6 +66,14 @@ NodeNum displayedNodeNum;
NodeDB::NodeDB() : nodes(devicestate.node_db), numNodes(&devicestate.node_db_count) {} NodeDB::NodeDB() : nodes(devicestate.node_db), numNodes(&devicestate.node_db_count) {}
/**
* Most (but not always) of the time we want to treat packets 'from' the local phone (where from == 0), as if they originated on the local node.
* If from is zero this function returns our node number instead
*/
NodeNum getFrom(const MeshPacket *p) {
return (p->from == 0) ? nodeDB.getNodeNum() : p->from;
}
bool NodeDB::resetRadioConfig() bool NodeDB::resetRadioConfig()
{ {
bool didFactoryReset = false; bool didFactoryReset = false;
@ -406,7 +414,7 @@ void NodeDB::updateFrom(const MeshPacket &mp)
if (mp.which_payloadVariant == MeshPacket_decoded_tag) { if (mp.which_payloadVariant == MeshPacket_decoded_tag) {
DEBUG_MSG("Update DB node 0x%x, rx_time=%u\n", mp.from, mp.rx_time); DEBUG_MSG("Update DB node 0x%x, rx_time=%u\n", mp.from, mp.rx_time);
NodeInfo *info = getOrCreateNode(mp.from); NodeInfo *info = getOrCreateNode(getFrom(&mp));
if (mp.rx_time) { // if the packet has a valid timestamp use it to update our last_seen if (mp.rx_time) { // if the packet has a valid timestamp use it to update our last_seen
info->has_position = true; // at least the time is valid info->has_position = true; // at least the time is valid

View File

@ -154,7 +154,7 @@ extern NodeDB nodeDB;
*/ */
// Our delay functions check for this for times that should never expire // Our delay functions check for this for times that should never expire
#define DELAY_FOREVER 0xffffffff #define NODE_DELAY_FOREVER 0xffffffff
#define IF_ROUTER(routerVal, normalVal) (radioConfig.preferences.is_router ? (routerVal) : (normalVal)) #define IF_ROUTER(routerVal, normalVal) (radioConfig.preferences.is_router ? (routerVal) : (normalVal))
@ -168,8 +168,8 @@ PREF_GET(position_broadcast_secs, IF_ROUTER(12 * 60 * 60, 15 * 60))
PREF_GET(wait_bluetooth_secs, IF_ROUTER(1, 60)) PREF_GET(wait_bluetooth_secs, IF_ROUTER(1, 60))
PREF_GET(screen_on_secs, 60) PREF_GET(screen_on_secs, 60)
PREF_GET(mesh_sds_timeout_secs, IF_ROUTER(DELAY_FOREVER, 2 * 60 * 60)) PREF_GET(mesh_sds_timeout_secs, IF_ROUTER(NODE_DELAY_FOREVER, 2 * 60 * 60))
PREF_GET(phone_sds_timeout_sec, IF_ROUTER(DELAY_FOREVER, 2 * 60 * 60)) PREF_GET(phone_sds_timeout_sec, IF_ROUTER(NODE_DELAY_FOREVER, 2 * 60 * 60))
PREF_GET(sds_secs, 365 * 24 * 60 * 60) PREF_GET(sds_secs, 365 * 24 * 60 * 60)
// We default to sleeping (with bluetooth off for 5 minutes at a time). This seems to be a good tradeoff between // We default to sleeping (with bluetooth off for 5 minutes at a time). This seems to be a good tradeoff between
@ -183,3 +183,4 @@ PREF_GET(min_wake_secs, 10)
* might have changed is incremented. Allows others to detect they might now be on a new channel. * might have changed is incremented. Allows others to detect they might now be on a new channel.
*/ */
extern uint32_t radioGeneration; extern uint32_t radioGeneration;

View File

@ -26,7 +26,7 @@ bool PacketHistory::wasSeenRecently(const MeshPacket *p, bool withUpdate)
// DEBUG_MSG("Deleting old broadcast record %d\n", i); // DEBUG_MSG("Deleting old broadcast record %d\n", i);
recentPackets.erase(recentPackets.begin() + i); // delete old record recentPackets.erase(recentPackets.begin() + i); // delete old record
} else { } else {
if (r.id == p->id && r.sender == p->from) { if (r.id == p->id && r.sender == getFrom(p)) {
DEBUG_MSG("Found existing packet record for fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); DEBUG_MSG("Found existing packet record for fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id);
// Update the time on this record to now // Update the time on this record to now
@ -43,7 +43,7 @@ bool PacketHistory::wasSeenRecently(const MeshPacket *p, bool withUpdate)
if (withUpdate) { if (withUpdate) {
PacketRecord r; PacketRecord r;
r.id = p->id; r.id = p->id;
r.sender = p->from; r.sender = getFrom(p);
r.rxTimeMsec = now; r.rxTimeMsec = now;
recentPackets.push_back(r); recentPackets.push_back(r);
printPacket("Adding packet record", p); printPacket("Adding packet record", p);

View File

@ -59,6 +59,7 @@ void PhoneAPI::handleToRadio(const uint8_t *buf, size_t bufLength)
} }
// return (lastContactMsec != 0) && // return (lastContactMsec != 0) &&
memset(&toRadioScratch, 0, sizeof(toRadioScratch));
if (pb_decode_from_bytes(buf, bufLength, ToRadio_fields, &toRadioScratch)) { if (pb_decode_from_bytes(buf, bufLength, ToRadio_fields, &toRadioScratch)) {
switch (toRadioScratch.which_payloadVariant) { switch (toRadioScratch.which_payloadVariant) {
case ToRadio_packet_tag: { case ToRadio_packet_tag: {
@ -178,9 +179,8 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
// Do we have a message from the mesh? // Do we have a message from the mesh?
if (fromRadioScratch.which_payloadVariant != 0) { if (fromRadioScratch.which_payloadVariant != 0) {
// Encapsulate as a FromRadio packet // Encapsulate as a FromRadio packet
DEBUG_MSG("encoding toPhone packet to phone variant=%d", fromRadioScratch.which_payloadVariant);
size_t numbytes = pb_encode_to_bytes(buf, FromRadio_size, FromRadio_fields, &fromRadioScratch); size_t numbytes = pb_encode_to_bytes(buf, FromRadio_size, FromRadio_fields, &fromRadioScratch);
DEBUG_MSG(", %d bytes\n", numbytes); // DEBUG_MSG("encoding toPhone packet to phone variant=%d, %d bytes\n", fromRadioScratch.which_payloadVariant, numbytes);
return numbytes; return numbytes;
} }

View File

@ -58,11 +58,12 @@ template <class T> class ProtobufPlugin : protected SinglePortPlugin
// it would be better to update even if the message was destined to others. // it would be better to update even if the message was destined to others.
auto &p = mp.decoded; auto &p = mp.decoded;
DEBUG_MSG("Received %s from=0x%0x, id=0x%x, payloadlen=%d\n", name, mp.from, mp.id, p.payload.size); DEBUG_MSG("Received %s from=0x%0x, id=0x%x, portnum=%d, payloadlen=%d\n", name, mp.from, mp.id, p.portnum, p.payload.size);
T scratch; T scratch;
T *decoded = NULL; T *decoded = NULL;
if(mp.decoded.portnum == ourPortNum) { if(mp.decoded.portnum == ourPortNum) {
memset(&scratch, 0, sizeof(scratch));
if (pb_decode_from_bytes(p.payload.bytes, p.payload.size, fields, &scratch)) if (pb_decode_from_bytes(p.payload.bytes, p.payload.size, fields, &scratch))
decoded = &scratch; decoded = &scratch;
else else

View File

@ -1,16 +1,16 @@
#include "configuration.h"
#include "RadioInterface.h" #include "RadioInterface.h"
#include "Channels.h" #include "Channels.h"
#include "MeshRadio.h" #include "MeshRadio.h"
#include "MeshService.h" #include "MeshService.h"
#include "NodeDB.h" #include "NodeDB.h"
#include "assert.h" #include "assert.h"
#include "configuration.h" #include "Router.h"
#include "sleep.h" #include "sleep.h"
#include <assert.h> #include <assert.h>
#include <pb_decode.h> #include <pb_decode.h>
#include <pb_encode.h> #include <pb_encode.h>
#include "Channels.h"
#define RDEF(name, freq, spacing, num_ch, power_limit) \ #define RDEF(name, freq, spacing, num_ch, power_limit) \
{ \ { \
@ -25,9 +25,27 @@ const RegionInfo regions[] = {
RDEF(KR, 921.9f, 0.2f, 8, 0), // KR channel settings (KR920-923) Start from TTN download channel RDEF(KR, 921.9f, 0.2f, 8, 0), // KR channel settings (KR920-923) Start from TTN download channel
// freq. (921.9f is for download, others are for uplink) // freq. (921.9f is for download, others are for uplink)
RDEF(TW, 923.0f, 0.2f, 10, 0), // TW channel settings (AS2 bandplan 923-925MHz) RDEF(TW, 923.0f, 0.2f, 10, 0), // TW channel settings (AS2 bandplan 923-925MHz)
RDEF(RU, 868.9f, 0.2f, 2, 20), // See notes below
RDEF(Unset, 903.08f, 2.16f, 13, 0) // Assume US freqs if unset, Must be last RDEF(Unset, 903.08f, 2.16f, 13, 0) // Assume US freqs if unset, Must be last
}; };
/* Notes about the RU bandplan (from @denis-d in https://meshtastic.discourse.group/t/russian-band-plan-proposal/2786/2):
According to Annex 12 to GKRCh (National Radio Frequency Commission) decision 18-46-03-1 (September 11th 2018) https://digital.gov.ru/uploaded/files/prilozhenie-12-k-reshenyu-gkrch-18-46-03-1.pdf 1
We have 3 options for 868 MHz:
864,0 - 865,0 MHz ERP 25mW, Duty Cycle 0.1% (3.6 sec in hour) or LBT (Listen Before Talk), prohibited in airports.
866,0 - 868,0 MHz ERP 25mW, Duty Cycle 1% or LBT, PSD (Power Spectrum Density) 1000mW/MHz, prohibited in airports
868,7 - 869,2 MHz ERP 100mW, Duty Cycle 10% or LBT, no resctrictions
and according to RP2-1.0.2 LoRaWAN® Regional Parameters RP2-1.0.2 LoRaWAN® Regional Parameters - LoRa Alliance®
I propose to use following meshtastic bandplan:
1 channel - central frequency 868.9MHz 125kHz band
Protective band - 75kHz
2 channel - central frequency 869.1MHz 125kHz band
RDEF(RU, 868.9f, 0.2f, 2, 20)
*/
const RegionInfo *myRegion; const RegionInfo *myRegion;
void initRegion() void initRegion()
@ -119,11 +137,11 @@ uint32_t RadioInterface::getTxDelayMsec()
void printPacket(const char *prefix, const MeshPacket *p) void printPacket(const char *prefix, const MeshPacket *p)
{ {
DEBUG_MSG("%s (id=0x%08x Fr0x%02x To0x%02x, WantAck%d, HopLim%d Ch0x%x", prefix, p->id, p->from & 0xff, p->to & 0xff, p->want_ack, DEBUG_MSG("%s (id=0x%08x Fr0x%02x To0x%02x, WantAck%d, HopLim%d Ch0x%x", prefix, p->id, p->from & 0xff, p->to & 0xff,
p->hop_limit, p->channel); p->want_ack, p->hop_limit, p->channel);
if (p->which_payloadVariant == MeshPacket_decoded_tag) { if (p->which_payloadVariant == MeshPacket_decoded_tag) {
auto &s = p->decoded; auto &s = p->decoded;
DEBUG_MSG(" Portnum=%d", s.portnum); DEBUG_MSG(" Portnum=%d", s.portnum);
if (s.want_response) if (s.want_response)
@ -332,6 +350,10 @@ void RadioInterface::deliverToReceiver(MeshPacket *p)
{ {
assert(rxDest); assert(rxDest);
assert(rxDest->enqueue(p, 0)); // NOWAIT - fixme, if queue is full, delete older messages assert(rxDest->enqueue(p, 0)); // NOWAIT - fixme, if queue is full, delete older messages
// Nasty hack because our threading is primitive. interfaces shouldn't need to know about routers FIXME
if (router)
router->setReceivedMessage();
} }
/*** /***

View File

@ -1,4 +1,5 @@
#include "ReliableRouter.h" #include "ReliableRouter.h"
#include "MeshPlugin.h"
#include "MeshTypes.h" #include "MeshTypes.h"
#include "configuration.h" #include "configuration.h"
#include "mesh-pb-constants.h" #include "mesh-pb-constants.h"
@ -26,15 +27,18 @@ ErrorCode ReliableRouter::send(MeshPacket *p)
bool ReliableRouter::shouldFilterReceived(const MeshPacket *p) bool ReliableRouter::shouldFilterReceived(const MeshPacket *p)
{ {
// Note: do not use getFrom() here, because we want to ignore messages sent from phone
if (p->to == NODENUM_BROADCAST && p->from == getNodeNum()) { if (p->to == NODENUM_BROADCAST && p->from == getNodeNum()) {
printPacket("Rx someone rebroadcasting for us", p); printPacket("Rx someone rebroadcasting for us", p);
// We are seeing someone rebroadcast one of our broadcast attempts. // We are seeing someone rebroadcast one of our broadcast attempts.
// If this is the first time we saw this, cancel any retransmissions we have queued up and generate an internal ack for // If this is the first time we saw this, cancel any retransmissions we have queued up and generate an internal ack for
// the original sending process. // the original sending process.
if (stopRetransmission(p->from, p->id)) { if (stopRetransmission(getFrom(p), p->id)) {
DEBUG_MSG("Someone is retransmitting for us, generate implicit ack\n"); DEBUG_MSG("generating implicit ack\n");
sendAckNak(Routing_Error_NONE, p->from, p->id); // NOTE: we do NOT check p->wantAck here because p is the INCOMING rebroadcast and that packet is not expected to be
// marked as wantAck
sendAckNak(Routing_Error_NONE, getFrom(p), p->id);
} }
} }
@ -60,23 +64,26 @@ void ReliableRouter::sniffReceived(const MeshPacket *p, const Routing *c)
if (p->to == ourNode) { // ignore ack/nak/want_ack packets that are not address to us (we only handle 0 hop reliability if (p->to == ourNode) { // ignore ack/nak/want_ack packets that are not address to us (we only handle 0 hop reliability
// - not DSR routing) // - not DSR routing)
if (p->want_ack) { if (p->want_ack) {
sendAckNak(Routing_Error_NONE, p->from, p->id); if (MeshPlugin::currentReply)
DEBUG_MSG("Someone else has replied to this message, no need for a 2nd ack");
else
sendAckNak(Routing_Error_NONE, getFrom(p), p->id);
} }
// If the payload is valid, look for ack/nak // We consider an ack to be either a !routing packet with a request ID or a routing packet with !error
if (c) { PacketId ackId = ((c && c->error_reason == Routing_Error_NONE) || !c) ? p->decoded.request_id : 0;
PacketId ackId = c->error_reason == Routing_Error_NONE ? p->decoded.request_id : 0;
PacketId nakId = c->error_reason != Routing_Error_NONE ? p->decoded.request_id : 0;
// We intentionally don't check wasSeenRecently, because it is harmless to delete non existent retransmission records // A nak is a routing packt that has an error code
if (ackId || nakId) { PacketId nakId = (c && c->error_reason != Routing_Error_NONE) ? p->decoded.request_id : 0;
if (ackId) {
DEBUG_MSG("Received a ack=%d, stopping retransmissions\n", ackId); // We intentionally don't check wasSeenRecently, because it is harmless to delete non existent retransmission records
stopRetransmission(p->to, ackId); if (ackId || nakId) {
} else { if (ackId) {
DEBUG_MSG("Received a nak=%d, stopping retransmissions\n", nakId); DEBUG_MSG("Received a ack for 0x%x, stopping retransmissions\n", ackId);
stopRetransmission(p->to, nakId); stopRetransmission(p->to, ackId);
} } else {
DEBUG_MSG("Received a nak for 0x%x, stopping retransmissions\n", nakId);
stopRetransmission(p->to, nakId);
} }
} }
} }
@ -130,8 +137,9 @@ PendingPacket *ReliableRouter::startRetransmission(MeshPacket *p)
auto id = GlobalPacketId(p); auto id = GlobalPacketId(p);
auto rec = PendingPacket(p); auto rec = PendingPacket(p);
stopRetransmission(getFrom(p), p->id);
setNextTx(&rec); setNextTx(&rec);
stopRetransmission(p->from, p->id);
pending[id] = rec; pending[id] = rec;
return &pending[id]; return &pending[id];
@ -151,18 +159,21 @@ int32_t ReliableRouter::doRetransmissions()
++nextIt; // we use this odd pattern because we might be deleting it... ++nextIt; // we use this odd pattern because we might be deleting it...
auto &p = it->second; auto &p = it->second;
bool stillValid = true; // assume we'll keep this record around
// FIXME, handle 51 day rolloever here!!! // FIXME, handle 51 day rolloever here!!!
if (p.nextTxMsec <= now) { if (p.nextTxMsec <= now) {
if (p.numRetransmissions == 0) { if (p.numRetransmissions == 0) {
DEBUG_MSG("Reliable send failed, returning a nak fr=0x%x,to=0x%x,id=%d\n", p.packet->from, p.packet->to, DEBUG_MSG("Reliable send failed, returning a nak for fr=0x%x,to=0x%x,id=0x%x\n", p.packet->from, p.packet->to,
p.packet->id); p.packet->id);
sendAckNak(Routing_Error_MAX_RETRANSMIT, p.packet->from, p.packet->id); sendAckNak(Routing_Error_MAX_RETRANSMIT, getFrom(p.packet), p.packet->id);
// Note: we don't stop retransmission here, instead the Nak packet gets processed in sniffReceived - which // Note: we don't stop retransmission here, instead the Nak packet gets processed in sniffReceived - which
// allows the DSR version to still be able to look at the PendingPacket // allows the DSR version to still be able to look at the PendingPacket
stopRetransmission(it->first); stopRetransmission(it->first);
stillValid = false; // just deleted it
} else { } else {
DEBUG_MSG("Sending reliable retransmission fr=0x%x,to=0x%x,id=%d, tries left=%d\n", p.packet->from, p.packet->to, DEBUG_MSG("Sending reliable retransmission fr=0x%x,to=0x%x,id=0x%x, tries left=%d\n", p.packet->from,
p.packet->id, p.numRetransmissions); p.packet->to, p.packet->id, p.numRetransmissions);
// Note: we call the superclass version because we don't want to have our version of send() add a new // Note: we call the superclass version because we don't want to have our version of send() add a new
// retransmission record // retransmission record
@ -172,8 +183,10 @@ int32_t ReliableRouter::doRetransmissions()
--p.numRetransmissions; --p.numRetransmissions;
setNextTx(&p); setNextTx(&p);
} }
} else { }
// Not yet time
if (stillValid) {
// Update our desired sleep delay
int32_t t = p.nextTxMsec - now; int32_t t = p.nextTxMsec - now;
d = min(t, d); d = min(t, d);
@ -181,4 +194,14 @@ int32_t ReliableRouter::doRetransmissions()
} }
return d; return d;
}
void ReliableRouter::setNextTx(PendingPacket *pending)
{
assert(iface);
auto d = iface->getRetransmissionMsec(pending->packet);
pending->nextTxMsec = millis() + d;
DEBUG_MSG("Setting next retransmission in %u msecs: ", d);
printPacket("", pending->packet);
setReceivedMessage(); // Run ASAP, so we can figure out our correct sleep time
} }

View File

@ -15,7 +15,7 @@ struct GlobalPacketId {
GlobalPacketId(const MeshPacket *p) GlobalPacketId(const MeshPacket *p)
{ {
node = p->from; node = getFrom(p);
id = p->id; id = p->id;
} }
@ -125,7 +125,5 @@ class ReliableRouter : public FloodingRouter
*/ */
int32_t doRetransmissions(); int32_t doRetransmissions();
void setNextTx(PendingPacket *pending) { void setNextTx(PendingPacket *pending);
assert(iface);
pending->nextTxMsec = millis() + iface->getRetransmissionMsec(pending->packet); }
}; };

View File

@ -111,16 +111,21 @@ void Router::sendAckNak(Routing_Error err, NodeNum to, PacketId idFrom)
void Router::abortSendAndNak(Routing_Error err, MeshPacket *p) void Router::abortSendAndNak(Routing_Error err, MeshPacket *p)
{ {
DEBUG_MSG("Error=%d, returning NAK and dropping packet.\n", err); DEBUG_MSG("Error=%d, returning NAK and dropping packet.\n", err);
sendAckNak(Routing_Error_NO_INTERFACE, p->from, p->id); sendAckNak(Routing_Error_NO_INTERFACE, getFrom(p), p->id);
packetPool.release(p); packetPool.release(p);
} }
void Router::setReceivedMessage() {
setInterval(0); // Run ASAP, so we can figure out our correct sleep time
}
ErrorCode Router::sendLocal(MeshPacket *p) ErrorCode Router::sendLocal(MeshPacket *p)
{ {
// No need to deliver externally if the destination is the local node // No need to deliver externally if the destination is the local node
if (p->to == nodeDB.getNodeNum()) { if (p->to == nodeDB.getNodeNum()) {
printPacket("Enqueuing local", p); printPacket("Enqueuing local", p);
fromRadioQueue.enqueue(p); fromRadioQueue.enqueue(p);
setReceivedMessage();
return ERRNO_OK; return ERRNO_OK;
} else if (!iface) { } else if (!iface) {
// We must be sending to remote nodes also, fail if no interface found // We must be sending to remote nodes also, fail if no interface found
@ -138,6 +143,13 @@ ErrorCode Router::sendLocal(MeshPacket *p)
} }
} }
void printBytes(const char *label, const uint8_t *p, size_t numbytes) {
DEBUG_MSG("%s: ", label);
for(size_t i = 0; i < numbytes; i++)
DEBUG_MSG("%02x ", p[i]);
DEBUG_MSG("\n");
}
/** /**
* Send a packet on a suitable interface. This routine will * Send a packet on a suitable interface. This routine will
* later free() the packet to pool. This routine is not allowed to stall. * later free() the packet to pool. This routine is not allowed to stall.
@ -155,6 +167,10 @@ ErrorCode Router::send(MeshPacket *p)
if (p->to == NODENUM_BROADCAST) if (p->to == NODENUM_BROADCAST)
p->want_ack = false; p->want_ack = false;
// Up until this point we might have been using 0 for the from address (if it started with the phone), but when we send over
// the lora we need to make sure we have replaced it with our local address
p->from = getFrom(p);
// If the packet hasn't yet been encrypted, do so now (it might already be encrypted if we are just forwarding it) // If the packet hasn't yet been encrypted, do so now (it might already be encrypted if we are just forwarding it)
assert(p->which_payloadVariant == MeshPacket_encrypted_tag || assert(p->which_payloadVariant == MeshPacket_encrypted_tag ||
@ -164,6 +180,8 @@ ErrorCode Router::send(MeshPacket *p)
if (p->which_payloadVariant == MeshPacket_decoded_tag) { if (p->which_payloadVariant == MeshPacket_decoded_tag) {
static uint8_t bytes[MAX_RHPACKETLEN]; // we have to use a scratch buffer because a union static uint8_t bytes[MAX_RHPACKETLEN]; // we have to use a scratch buffer because a union
// printPacket("pre encrypt", p); // portnum valid here
size_t numbytes = pb_encode_to_bytes(bytes, sizeof(bytes), Data_fields, &p->decoded); size_t numbytes = pb_encode_to_bytes(bytes, sizeof(bytes), Data_fields, &p->decoded);
if (numbytes > MAX_RHPACKETLEN) { if (numbytes > MAX_RHPACKETLEN) {
@ -171,6 +189,8 @@ ErrorCode Router::send(MeshPacket *p)
return ERRNO_TOO_LARGE; return ERRNO_TOO_LARGE;
} }
//printBytes("plaintext", bytes, numbytes);
auto hash = channels.setActiveByIndex(p->channel); auto hash = channels.setActiveByIndex(p->channel);
if (hash < 0) { if (hash < 0) {
// No suitable channel could be found for sending // No suitable channel could be found for sending
@ -180,7 +200,7 @@ ErrorCode Router::send(MeshPacket *p)
// Now that we are encrypting the packet channel should be the hash (no longer the index) // Now that we are encrypting the packet channel should be the hash (no longer the index)
p->channel = hash; p->channel = hash;
crypto->encrypt(p->from, p->id, numbytes, bytes); crypto->encrypt(getFrom(p), p->id, numbytes, bytes);
// Copy back into the packet and set the variant type // Copy back into the packet and set the variant type
memcpy(p->encrypted.bytes, bytes, numbytes); memcpy(p->encrypted.bytes, bytes, numbytes);
@ -221,19 +241,26 @@ bool Router::perhapsDecode(MeshPacket *p)
if (channels.decryptForHash(chIndex, p->channel)) { if (channels.decryptForHash(chIndex, p->channel)) {
// Try to decrypt the packet if we can // Try to decrypt the packet if we can
static uint8_t bytes[MAX_RHPACKETLEN]; static uint8_t bytes[MAX_RHPACKETLEN];
size_t rawSize = p->encrypted.size;
assert(rawSize <= sizeof(bytes));
memcpy(bytes, p->encrypted.bytes, memcpy(bytes, p->encrypted.bytes,
p->encrypted rawSize); // we have to copy into a scratch buffer, because these bytes are a union with the decoded protobuf
.size); // we have to copy into a scratch buffer, because these bytes are a union with the decoded protobuf crypto->decrypt(p->from, p->id, rawSize, bytes);
crypto->decrypt(p->from, p->id, p->encrypted.size, bytes);
//printBytes("plaintext", bytes, p->encrypted.size);
// Take those raw bytes and convert them back into a well structured protobuf we can understand // Take those raw bytes and convert them back into a well structured protobuf we can understand
if (!pb_decode_from_bytes(bytes, p->encrypted.size, Data_fields, &p->decoded)) { memset(&p->decoded, 0, sizeof(p->decoded));
DEBUG_MSG("Invalid protobufs in received mesh packet (bad psk?!\n"); if (!pb_decode_from_bytes(bytes, rawSize, Data_fields, &p->decoded)) {
} else { DEBUG_MSG("Invalid protobufs in received mesh packet (bad psk?)!\n");
} else if(p->decoded.portnum == PortNum_UNKNOWN_APP) {
DEBUG_MSG("Invalid portnum (bad psk?)!\n");
}
else {
// parsing was successful // parsing was successful
p->which_payloadVariant = MeshPacket_decoded_tag; // change type to decoded
p->channel = chIndex; // change to store the index instead of the hash p->channel = chIndex; // change to store the index instead of the hash
// printPacket("decoded message", p); printPacket("decoded message", p);
p->which_payloadVariant = MeshPacket_decoded_tag;
return true; return true;
} }
} }
@ -258,11 +285,10 @@ void Router::handleReceived(MeshPacket *p)
p->rx_time = getValidTime(RTCQualityFromNet); // store the arrival timestamp for the phone p->rx_time = getValidTime(RTCQualityFromNet); // store the arrival timestamp for the phone
// Take those raw bytes and convert them back into a well structured protobuf we can understand // Take those raw bytes and convert them back into a well structured protobuf we can understand
bool decoded = perhapsDecode(p); bool decoded = perhapsDecode(p);
printPacket("handleReceived", p);
DEBUG_MSG("decoded=%d\n", decoded);
if (decoded) { if (decoded) {
// parsing was successful, queue for our recipient // parsing was successful, queue for our recipient
printPacket("handleReceived", p);
// call any promiscious plugins here, make a (non promisiocous) plugin for forwarding messages to phone api // call any promiscious plugins here, make a (non promisiocous) plugin for forwarding messages to phone api
// sniffReceived(p); // sniffReceived(p);
@ -273,7 +299,7 @@ void Router::handleReceived(MeshPacket *p)
void Router::perhapsHandleReceived(MeshPacket *p) void Router::perhapsHandleReceived(MeshPacket *p)
{ {
assert(radioConfig.has_preferences); assert(radioConfig.has_preferences);
bool ignore = is_in_repeated(radioConfig.preferences.ignore_incoming, p->from); bool ignore = is_in_repeated(radioConfig.preferences.ignore_incoming, getFrom(p));
if (ignore) if (ignore)
DEBUG_MSG("Ignoring incoming message, 0x%x is in our ignore list\n", p->from); DEBUG_MSG("Ignoring incoming message, 0x%x is in our ignore list\n", p->from);

View File

@ -63,6 +63,11 @@ class Router : protected concurrency::OSThread
* @return our local nodenum */ * @return our local nodenum */
NodeNum getNodeNum(); NodeNum getNodeNum();
/** Wake up the router thread ASAP, because we just queued a message for it.
* FIXME, this is kinda a hack because we don't have a nice way yet to say 'wake us because we are 'blocked on this queue'
*/
void setReceivedMessage();
protected: protected:
friend class RoutingPlugin; friend class RoutingPlugin;

View File

@ -67,7 +67,7 @@ extern const pb_msgdesc_t AdminMessage_msg;
#define AdminMessage_fields &AdminMessage_msg #define AdminMessage_fields &AdminMessage_msg
/* Maximum encoded size of messages (where known) */ /* Maximum encoded size of messages (where known) */
#define AdminMessage_size 338 #define AdminMessage_size 351
#ifdef __cplusplus #ifdef __cplusplus
} /* extern "C" */ } /* extern "C" */

View File

@ -82,7 +82,7 @@ extern const pb_msgdesc_t DeviceState_msg;
#define DeviceState_fields &DeviceState_msg #define DeviceState_fields &DeviceState_msg
/* Maximum encoded size of messages (where known) */ /* Maximum encoded size of messages (where known) */
#define DeviceState_size 6156 #define DeviceState_size 6169
#ifdef __cplusplus #ifdef __cplusplus
} /* extern "C" */ } /* extern "C" */

View File

@ -20,10 +20,10 @@ typedef enum _PortNum {
PortNum_ADMIN_APP = 6, PortNum_ADMIN_APP = 6,
PortNum_REPLY_APP = 32, PortNum_REPLY_APP = 32,
PortNum_IP_TUNNEL_APP = 33, PortNum_IP_TUNNEL_APP = 33,
PortNum_ENVIRONMENTAL_MEASUREMENT_APP = 34,
PortNum_SERIAL_APP = 64, PortNum_SERIAL_APP = 64,
PortNum_STORE_FORWARD_APP = 65, PortNum_STORE_FORWARD_APP = 65,
PortNum_RANGE_TEST_APP = 66, PortNum_RANGE_TEST_APP = 66,
PortNum_ENVIRONMENTAL_MEASUREMENT_APP = 67,
PortNum_PRIVATE_APP = 256, PortNum_PRIVATE_APP = 256,
PortNum_ATAK_FORWARDER = 257, PortNum_ATAK_FORWARDER = 257,
PortNum_MAX = 511 PortNum_MAX = 511

View File

@ -17,3 +17,4 @@ PB_BIND(RadioConfig_UserPreferences, RadioConfig_UserPreferences, 2)

View File

@ -19,7 +19,8 @@ typedef enum _RegionCode {
RegionCode_JP = 5, RegionCode_JP = 5,
RegionCode_ANZ = 6, RegionCode_ANZ = 6,
RegionCode_KR = 7, RegionCode_KR = 7,
RegionCode_TW = 8 RegionCode_TW = 8,
RegionCode_RU = 9
} RegionCode; } RegionCode;
typedef enum _ChargeCurrent { typedef enum _ChargeCurrent {
@ -56,6 +57,10 @@ typedef enum _LocationSharing {
LocationSharing_LocDisabled = 2 LocationSharing_LocDisabled = 2
} LocationSharing; } LocationSharing;
typedef enum _RadioConfig_UserPreferences_EnvironmentalMeasurementSensorType {
RadioConfig_UserPreferences_EnvironmentalMeasurementSensorType_DHT11 = 0
} RadioConfig_UserPreferences_EnvironmentalMeasurementSensorType;
/* Struct definitions */ /* Struct definitions */
typedef struct _RadioConfig_UserPreferences { typedef struct _RadioConfig_UserPreferences {
uint32_t position_broadcast_secs; uint32_t position_broadcast_secs;
@ -106,6 +111,9 @@ typedef struct _RadioConfig_UserPreferences {
uint32_t environmental_measurement_plugin_read_error_count_threshold; uint32_t environmental_measurement_plugin_read_error_count_threshold;
uint32_t environmental_measurement_plugin_update_interval; uint32_t environmental_measurement_plugin_update_interval;
uint32_t environmental_measurement_plugin_recovery_interval; uint32_t environmental_measurement_plugin_recovery_interval;
bool environmental_measurement_plugin_display_farenheit;
RadioConfig_UserPreferences_EnvironmentalMeasurementSensorType environmental_measurement_plugin_sensor_type;
uint32_t environmental_measurement_plugin_sensor_pin;
} RadioConfig_UserPreferences; } RadioConfig_UserPreferences;
typedef struct _RadioConfig { typedef struct _RadioConfig {
@ -116,8 +124,8 @@ typedef struct _RadioConfig {
/* Helper constants for enums */ /* Helper constants for enums */
#define _RegionCode_MIN RegionCode_Unset #define _RegionCode_MIN RegionCode_Unset
#define _RegionCode_MAX RegionCode_TW #define _RegionCode_MAX RegionCode_RU
#define _RegionCode_ARRAYSIZE ((RegionCode)(RegionCode_TW+1)) #define _RegionCode_ARRAYSIZE ((RegionCode)(RegionCode_RU+1))
#define _ChargeCurrent_MIN ChargeCurrent_MAUnset #define _ChargeCurrent_MIN ChargeCurrent_MAUnset
#define _ChargeCurrent_MAX ChargeCurrent_MA1320 #define _ChargeCurrent_MAX ChargeCurrent_MA1320
@ -131,6 +139,10 @@ typedef struct _RadioConfig {
#define _LocationSharing_MAX LocationSharing_LocDisabled #define _LocationSharing_MAX LocationSharing_LocDisabled
#define _LocationSharing_ARRAYSIZE ((LocationSharing)(LocationSharing_LocDisabled+1)) #define _LocationSharing_ARRAYSIZE ((LocationSharing)(LocationSharing_LocDisabled+1))
#define _RadioConfig_UserPreferences_EnvironmentalMeasurementSensorType_MIN RadioConfig_UserPreferences_EnvironmentalMeasurementSensorType_DHT11
#define _RadioConfig_UserPreferences_EnvironmentalMeasurementSensorType_MAX RadioConfig_UserPreferences_EnvironmentalMeasurementSensorType_DHT11
#define _RadioConfig_UserPreferences_EnvironmentalMeasurementSensorType_ARRAYSIZE ((RadioConfig_UserPreferences_EnvironmentalMeasurementSensorType)(RadioConfig_UserPreferences_EnvironmentalMeasurementSensorType_DHT11+1))
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
@ -138,9 +150,9 @@ extern "C" {
/* Initializer values for message structs */ /* Initializer values for message structs */
#define RadioConfig_init_default {false, RadioConfig_UserPreferences_init_default} #define RadioConfig_init_default {false, RadioConfig_UserPreferences_init_default}
#define RadioConfig_UserPreferences_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "", "", 0, _RegionCode_MIN, _ChargeCurrent_MIN, _LocationSharing_MIN, _GpsOperation_MIN, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, _RegionCode_MIN, _ChargeCurrent_MIN, _LocationSharing_MIN, _GpsOperation_MIN, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, _RadioConfig_UserPreferences_EnvironmentalMeasurementSensorType_MIN, 0}
#define RadioConfig_init_zero {false, RadioConfig_UserPreferences_init_zero} #define RadioConfig_init_zero {false, RadioConfig_UserPreferences_init_zero}
#define RadioConfig_UserPreferences_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "", "", 0, _RegionCode_MIN, _ChargeCurrent_MIN, _LocationSharing_MIN, _GpsOperation_MIN, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, _RegionCode_MIN, _ChargeCurrent_MIN, _LocationSharing_MIN, _GpsOperation_MIN, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, _RadioConfig_UserPreferences_EnvironmentalMeasurementSensorType_MIN, 0}
/* Field tags (for use in manual encoding/decoding) */ /* Field tags (for use in manual encoding/decoding) */
#define RadioConfig_UserPreferences_position_broadcast_secs_tag 1 #define RadioConfig_UserPreferences_position_broadcast_secs_tag 1
@ -190,6 +202,9 @@ extern "C" {
#define RadioConfig_UserPreferences_environmental_measurement_plugin_read_error_count_threshold_tag 142 #define RadioConfig_UserPreferences_environmental_measurement_plugin_read_error_count_threshold_tag 142
#define RadioConfig_UserPreferences_environmental_measurement_plugin_update_interval_tag 143 #define RadioConfig_UserPreferences_environmental_measurement_plugin_update_interval_tag 143
#define RadioConfig_UserPreferences_environmental_measurement_plugin_recovery_interval_tag 144 #define RadioConfig_UserPreferences_environmental_measurement_plugin_recovery_interval_tag 144
#define RadioConfig_UserPreferences_environmental_measurement_plugin_display_farenheit_tag 145
#define RadioConfig_UserPreferences_environmental_measurement_plugin_sensor_type_tag 146
#define RadioConfig_UserPreferences_environmental_measurement_plugin_sensor_pin_tag 147
#define RadioConfig_preferences_tag 1 #define RadioConfig_preferences_tag 1
/* Struct field encoding specification for nanopb */ /* Struct field encoding specification for nanopb */
@ -246,7 +261,10 @@ X(a, STATIC, SINGULAR, BOOL, environmental_measurement_plugin_measurement_
X(a, STATIC, SINGULAR, BOOL, environmental_measurement_plugin_screen_enabled, 141) \ X(a, STATIC, SINGULAR, BOOL, environmental_measurement_plugin_screen_enabled, 141) \
X(a, STATIC, SINGULAR, UINT32, environmental_measurement_plugin_read_error_count_threshold, 142) \ X(a, STATIC, SINGULAR, UINT32, environmental_measurement_plugin_read_error_count_threshold, 142) \
X(a, STATIC, SINGULAR, UINT32, environmental_measurement_plugin_update_interval, 143) \ X(a, STATIC, SINGULAR, UINT32, environmental_measurement_plugin_update_interval, 143) \
X(a, STATIC, SINGULAR, UINT32, environmental_measurement_plugin_recovery_interval, 144) X(a, STATIC, SINGULAR, UINT32, environmental_measurement_plugin_recovery_interval, 144) \
X(a, STATIC, SINGULAR, BOOL, environmental_measurement_plugin_display_farenheit, 145) \
X(a, STATIC, SINGULAR, UENUM, environmental_measurement_plugin_sensor_type, 146) \
X(a, STATIC, SINGULAR, UINT32, environmental_measurement_plugin_sensor_pin, 147)
#define RadioConfig_UserPreferences_CALLBACK NULL #define RadioConfig_UserPreferences_CALLBACK NULL
#define RadioConfig_UserPreferences_DEFAULT NULL #define RadioConfig_UserPreferences_DEFAULT NULL
@ -258,8 +276,8 @@ extern const pb_msgdesc_t RadioConfig_UserPreferences_msg;
#define RadioConfig_UserPreferences_fields &RadioConfig_UserPreferences_msg #define RadioConfig_UserPreferences_fields &RadioConfig_UserPreferences_msg
/* Maximum encoded size of messages (where known) */ /* Maximum encoded size of messages (where known) */
#define RadioConfig_size 335 #define RadioConfig_size 348
#define RadioConfig_UserPreferences_size 332 #define RadioConfig_UserPreferences_size 345
#ifdef __cplusplus #ifdef __cplusplus
} /* extern "C" */ } /* extern "C" */

View File

@ -18,8 +18,8 @@ size_t pb_encode_to_bytes(uint8_t *destbuf, size_t destbufsize, const pb_msgdesc
pb_ostream_t stream = pb_ostream_from_buffer(destbuf, destbufsize); pb_ostream_t stream = pb_ostream_from_buffer(destbuf, destbufsize);
if (!pb_encode(&stream, fields, src_struct)) { if (!pb_encode(&stream, fields, src_struct)) {
DEBUG_MSG("Error: can't encode protobuf %s\n", PB_GET_ERROR(&stream)); DEBUG_MSG("Panic: can't encode protobuf %s, did you make a field too large?\n", PB_GET_ERROR(&stream));
assert(0); // FIXME - panic assert(0); // If this asser fails it probably means you made a field too large for the max limits specified in mesh.options
} else { } else {
return stream.bytes_written; return stream.bytes_written;
} }

View File

@ -24,6 +24,12 @@ void AdminPlugin::handleGetRadio(const MeshPacket &req)
// We create the reply here // We create the reply here
AdminMessage r = AdminMessage_init_default; AdminMessage r = AdminMessage_init_default;
r.get_radio_response = devicestate.radio; r.get_radio_response = devicestate.radio;
// NOTE: The phone app needs to know the ls_secs value so it can properly expect sleep behavior.
// So even if we internally use 0 to represent 'use default' we still need to send the value we are
// using to the app (so that even old phone apps work with new device loads).
r.get_radio_response.preferences.ls_secs = getPref_ls_secs();
r.which_variant = AdminMessage_get_radio_response_tag; r.which_variant = AdminMessage_get_radio_response_tag;
reply = allocDataProtobuf(r); reply = allocDataProtobuf(r);
} }

View File

@ -147,7 +147,7 @@ bool ExternalNotificationPluginRadio::handleReceived(const MeshPacket &mp)
auto &p = mp.decoded; auto &p = mp.decoded;
if (mp.from != nodeDB.getNodeNum()) { if (getFrom(&mp) != nodeDB.getNodeNum()) {
// TODO: This may be a problem if messages are sent in unicide, but I'm not sure if it will. // TODO: This may be a problem if messages are sent in unicide, but I'm not sure if it will.
// Need to know if and how this could be a problem. // Need to know if and how this could be a problem.

View File

@ -12,7 +12,7 @@ bool NodeInfoPlugin::handleReceivedProtobuf(const MeshPacket &mp, const User *pp
{ {
auto p = *pptr; auto p = *pptr;
nodeDB.updateUser(mp.from, p); nodeDB.updateUser(getFrom(&mp), p);
bool wasBroadcast = mp.to == NODENUM_BROADCAST; bool wasBroadcast = mp.to == NODENUM_BROADCAST;
@ -65,8 +65,7 @@ int32_t NodeInfoPlugin::runOnce()
currentGeneration = radioGeneration; currentGeneration = radioGeneration;
DEBUG_MSG("Sending our nodeinfo to mesh (wantReplies=%d)\n", requestReplies); DEBUG_MSG("Sending our nodeinfo to mesh (wantReplies=%d)\n", requestReplies);
assert(nodeInfoPlugin); sendOurNodeInfo(NODENUM_BROADCAST, requestReplies); // Send our info (don't request replies)
nodeInfoPlugin->sendOurNodeInfo(NODENUM_BROADCAST, requestReplies); // Send our info (don't request replies)
return getPref_position_broadcast_secs() * 1000; return getPref_position_broadcast_secs() * 1000;
} }

View File

@ -20,7 +20,6 @@
*/ */
void setupPlugins() void setupPlugins()
{ {
routingPlugin = new RoutingPlugin();
adminPlugin = new AdminPlugin(); adminPlugin = new AdminPlugin();
nodeInfoPlugin = new NodeInfoPlugin(); nodeInfoPlugin = new NodeInfoPlugin();
positionPlugin = new PositionPlugin(); positionPlugin = new PositionPlugin();
@ -48,4 +47,7 @@ void setupPlugins()
// new StoreForwardPlugin(); // new StoreForwardPlugin();
new EnvironmentalMeasurementPlugin(); new EnvironmentalMeasurementPlugin();
#endif #endif
// NOTE! This plugin must be added LAST because it likes to check for replies from other plugins and avoid sending extra acks
routingPlugin = new RoutingPlugin();
} }

View File

@ -30,7 +30,7 @@ bool PositionPlugin::handleReceivedProtobuf(const MeshPacket &mp, const Position
perhapsSetRTC(RTCQualityFromNet, &tv); perhapsSetRTC(RTCQualityFromNet, &tv);
} }
nodeDB.updatePosition(mp.from, p); nodeDB.updatePosition(getFrom(&mp), p);
return false; // Let others look at this message also if they want return false; // Let others look at this message also if they want
} }

View File

@ -9,11 +9,12 @@ RoutingPlugin *routingPlugin;
bool RoutingPlugin::handleReceivedProtobuf(const MeshPacket &mp, const Routing *r) bool RoutingPlugin::handleReceivedProtobuf(const MeshPacket &mp, const Routing *r)
{ {
DEBUG_MSG("Routing sniffing", &mp); printPacket("Routing sniffing", &mp);
router->sniffReceived(&mp, r); router->sniffReceived(&mp, r);
// FIXME - move this to a non promsicious PhoneAPI plugin? // FIXME - move this to a non promsicious PhoneAPI plugin?
if (mp.to == NODENUM_BROADCAST || mp.to == nodeDB.getNodeNum()) { // Note: we are careful not to send back packets that started with the phone back to the phone
if ((mp.to == NODENUM_BROADCAST || mp.to == nodeDB.getNodeNum()) && (mp.from != 0)) {
printPacket("Delivering rx packet", &mp); printPacket("Delivering rx packet", &mp);
service.handleFromRadio(&mp); service.handleFromRadio(&mp);
} }

View File

@ -160,7 +160,7 @@ bool SerialPluginRadio::handleReceived(const MeshPacket &mp)
// DEBUG_MSG("Received text msg self=0x%0x, from=0x%0x, to=0x%0x, id=%d, msg=%.*s\n", // DEBUG_MSG("Received text msg self=0x%0x, from=0x%0x, to=0x%0x, id=%d, msg=%.*s\n",
// nodeDB.getNodeNum(), mp.from, mp.to, mp.id, p.payload.size, p.payload.bytes); // nodeDB.getNodeNum(), mp.from, mp.to, mp.id, p.payload.size, p.payload.bytes);
if (mp.from == nodeDB.getNodeNum()) { if (getFrom(&mp) == nodeDB.getNodeNum()) {
/* /*
* If radioConfig.preferences.serialplugin_echo is true, then echo the packets that are sent out back to the TX * If radioConfig.preferences.serialplugin_echo is true, then echo the packets that are sent out back to the TX

View File

@ -8,7 +8,7 @@ TextMessagePlugin *textMessagePlugin;
bool TextMessagePlugin::handleReceived(const MeshPacket &mp) bool TextMessagePlugin::handleReceived(const MeshPacket &mp)
{ {
auto &p = mp.decoded; auto &p = mp.decoded;
DEBUG_MSG("Received text msg from=0x%0x, id=%d, msg=%.*s\n", mp.from, mp.id, p.payload.size, p.payload.bytes); DEBUG_MSG("Received text msg from=0x%0x, id=0x%x, msg=%.*s\n", mp.from, mp.id, p.payload.size, p.payload.bytes);
// We only store/display messages destined for us. // We only store/display messages destined for us.
// Keep a copy of the most recent text message. // Keep a copy of the most recent text message.

View File

@ -10,20 +10,10 @@
#include <OLEDDisplay.h> #include <OLEDDisplay.h>
#include <OLEDDisplayUi.h> #include <OLEDDisplayUi.h>
EnvironmentalMeasurementPlugin *environmentalMeasurementPlugin;
EnvironmentalMeasurementPluginRadio *environmentalMeasurementPluginRadio;
EnvironmentalMeasurementPlugin::EnvironmentalMeasurementPlugin() : concurrency::OSThread("EnvironmentalMeasurementPlugin") {}
uint32_t sensor_read_error_count = 0;
#define DHT_11_GPIO_PIN 13
#define DHT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS 1000 // Some sensors (the DHT11) have a minimum required duration between read attempts #define DHT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS 1000 // Some sensors (the DHT11) have a minimum required duration between read attempts
#define FAILED_STATE_SENSOR_READ_MULTIPLIER 10 #define FAILED_STATE_SENSOR_READ_MULTIPLIER 10
#define DISPLAY_RECEIVEID_MEASUREMENTS_ON_SCREEN true #define DISPLAY_RECEIVEID_MEASUREMENTS_ON_SCREEN true
DHT dht(DHT_11_GPIO_PIN,DHT11);
#ifdef HAS_EINK #ifdef HAS_EINK
// The screen is bigger so use bigger fonts // The screen is bigger so use bigger fonts
@ -49,11 +39,15 @@ int32_t EnvironmentalMeasurementPlugin::runOnce() {
Uncomment the preferences below if you want to use the plugin Uncomment the preferences below if you want to use the plugin
without having to configure it from the PythonAPI or WebUI. without having to configure it from the PythonAPI or WebUI.
*/ */
/*radioConfig.preferences.environmental_measurement_plugin_measurement_enabled = 1; /*radioConfig.preferences.environmental_measurement_plugin_measurement_enabled = 1;
radioConfig.preferences.environmental_measurement_plugin_screen_enabled = 1; radioConfig.preferences.environmental_measurement_plugin_screen_enabled = 1;
radioConfig.preferences.environmental_measurement_plugin_read_error_count_threshold = 5; radioConfig.preferences.environmental_measurement_plugin_read_error_count_threshold = 5;
radioConfig.preferences.environmental_measurement_plugin_update_interval = 30; radioConfig.preferences.environmental_measurement_plugin_update_interval = 30;
radioConfig.preferences.environmental_measurement_plugin_recovery_interval = 600;*/ radioConfig.preferences.environmental_measurement_plugin_recovery_interval = 60;
radioConfig.preferences.environmental_measurement_plugin_display_farenheit = true;
radioConfig.preferences.environmental_measurement_plugin_sensor_pin = 13;
radioConfig.preferences.environmental_measurement_plugin_sensor_type = RadioConfig_UserPreferences_EnvironmentalMeasurementSensorType::RadioConfig_UserPreferences_EnvironmentalMeasurementSensorType_DHT11;*/
if (! (radioConfig.preferences.environmental_measurement_plugin_measurement_enabled || radioConfig.preferences.environmental_measurement_plugin_screen_enabled)){ if (! (radioConfig.preferences.environmental_measurement_plugin_measurement_enabled || radioConfig.preferences.environmental_measurement_plugin_screen_enabled)){
// If this plugin is not enabled, and the user doesn't want the display screen don't waste any OSThread time on it // If this plugin is not enabled, and the user doesn't want the display screen don't waste any OSThread time on it
@ -62,20 +56,32 @@ int32_t EnvironmentalMeasurementPlugin::runOnce() {
if (firstTime) { if (firstTime) {
// This is the first time the OSThread library has called this function, so do some setup // This is the first time the OSThread library has called this function, so do some setup
DEBUG_MSG("EnvironmentalMeasurement: Initializing\n");
environmentalMeasurementPluginRadio = new EnvironmentalMeasurementPluginRadio();
firstTime = 0; firstTime = 0;
// begin reading measurements from the sensor
// DHT have a max read-rate of 1HZ, so we should wait at least 1 second
// after initializing the sensor before we try to read from it.
// returning the interval here means that the next time OSThread
// calls our plugin, we'll run the other branch of this if statement
// and actually do a "sendOurEnvironmentalMeasurement()"
if (radioConfig.preferences.environmental_measurement_plugin_measurement_enabled) if (radioConfig.preferences.environmental_measurement_plugin_measurement_enabled)
{ {
DEBUG_MSG("EnvironmentalMeasurement: Initializing\n");
// it's possible to have this plugin enabled, only for displaying values on the screen. // it's possible to have this plugin enabled, only for displaying values on the screen.
// therefore, we should only enable the sensor loop if measurement is also enabled // therefore, we should only enable the sensor loop if measurement is also enabled
dht.begin(); switch(radioConfig.preferences.environmental_measurement_plugin_sensor_type) {
case RadioConfig_UserPreferences_EnvironmentalMeasurementSensorType_DHT11:
dht = new DHT(radioConfig.preferences.environmental_measurement_plugin_sensor_pin,DHT11);
this->dht->begin();
this->dht->read();
DEBUG_MSG("EnvironmentalMeasurement: Opened DHT11 on pin: %d\n",radioConfig.preferences.environmental_measurement_plugin_sensor_pin);
break;
default:
DEBUG_MSG("EnvironmentalMeasurement: Invalid sensor type selected; Disabling plugin");
return (INT32_MAX);
break;
}
// begin reading measurements from the sensor
// DHT have a max read-rate of 1HZ, so we should wait at least 1 second
// after initializing the sensor before we try to read from it.
// returning the interval here means that the next time OSThread
// calls our plugin, we'll run the other branch of this if statement
// and actually do a "sendOurEnvironmentalMeasurement()"
return(DHT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS); return(DHT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS);
} }
return (INT32_MAX); return (INT32_MAX);
@ -96,6 +102,7 @@ int32_t EnvironmentalMeasurementPlugin::runOnce() {
"EnvironmentalMeasurement: TEMPORARILY DISABLED; The environmental_measurement_plugin_read_error_count_threshold has been exceed: %d. Will retry reads in %d seconds\n", "EnvironmentalMeasurement: TEMPORARILY DISABLED; The environmental_measurement_plugin_read_error_count_threshold has been exceed: %d. Will retry reads in %d seconds\n",
radioConfig.preferences.environmental_measurement_plugin_read_error_count_threshold, radioConfig.preferences.environmental_measurement_plugin_read_error_count_threshold,
radioConfig.preferences.environmental_measurement_plugin_recovery_interval); radioConfig.preferences.environmental_measurement_plugin_recovery_interval);
sensor_read_error_count = 0;
return(radioConfig.preferences.environmental_measurement_plugin_recovery_interval*1000); return(radioConfig.preferences.environmental_measurement_plugin_recovery_interval*1000);
} }
DEBUG_MSG( DEBUG_MSG(
@ -110,7 +117,7 @@ int32_t EnvironmentalMeasurementPlugin::runOnce() {
sensor_read_error_count, sensor_read_error_count,
radioConfig.preferences.environmental_measurement_plugin_read_error_count_threshold-sensor_read_error_count); radioConfig.preferences.environmental_measurement_plugin_read_error_count_threshold-sensor_read_error_count);
} }
if (! environmentalMeasurementPluginRadio->sendOurEnvironmentalMeasurement() ){ if (!sendOurEnvironmentalMeasurement() ){
// if we failed to read the sensor, then try again // if we failed to read the sensor, then try again
// as soon as we can according to the maximum polling frequency // as soon as we can according to the maximum polling frequency
return(DHT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS); return(DHT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS);
@ -123,25 +130,16 @@ int32_t EnvironmentalMeasurementPlugin::runOnce() {
#endif #endif
} }
bool EnvironmentalMeasurementPluginRadio::wantUIFrame() { bool EnvironmentalMeasurementPlugin::wantUIFrame() {
return radioConfig.preferences.environmental_measurement_plugin_screen_enabled; return radioConfig.preferences.environmental_measurement_plugin_screen_enabled;
} }
void EnvironmentalMeasurementPluginRadio::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
display->setTextAlignment(TEXT_ALIGN_LEFT);
display->setFont(FONT_MEDIUM);
display->drawString(x, y, "Environment");
display->setFont(FONT_SMALL);
display->drawString(x, y += fontHeight(FONT_MEDIUM), lastSender+": T:"+ String(lastMeasurement.temperature,2) + " H:" + String(lastMeasurement.relative_humidity,2));
}
String GetSenderName(const MeshPacket &mp) { String GetSenderName(const MeshPacket &mp) {
String sender; String sender;
if (nodeDB.getNode(mp.from)){ auto node = nodeDB.getNode(getFrom(&mp));
sender = nodeDB.getNode(mp.from)->user.short_name; if (node){
sender = node->user.short_name;
} }
else { else {
sender = "UNK"; sender = "UNK";
@ -149,55 +147,99 @@ String GetSenderName(const MeshPacket &mp) {
return sender; return sender;
} }
bool EnvironmentalMeasurementPluginRadio::handleReceivedProtobuf(const MeshPacket &mp, const EnvironmentalMeasurement *pptr) uint32_t GetTimeSinceMeshPacket(const MeshPacket *mp) {
uint32_t now = getTime();
uint32_t last_seen = mp->rx_time;
int delta = (int)(now - last_seen);
if (delta < 0) // our clock must be slightly off still - not set from GPS yet
delta = 0;
return delta;
}
float EnvironmentalMeasurementPlugin::CelsiusToFarenheit(float c) {
return (c*9)/5 + 32;
}
void EnvironmentalMeasurementPlugin::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{ {
const EnvironmentalMeasurement &p = *pptr; display->setTextAlignment(TEXT_ALIGN_LEFT);
display->setFont(FONT_MEDIUM);
display->drawString(x, y, "Environment");
if (lastMeasurementPacket == nullptr) {
display->setFont(FONT_SMALL);
display->drawString(x, y += fontHeight(FONT_MEDIUM), "No measurement");
//DEBUG_MSG("EnvironmentalMeasurement: No previous measurement; not drawing frame\n");
return;
}
EnvironmentalMeasurement lastMeasurement;
uint32_t agoSecs = GetTimeSinceMeshPacket(lastMeasurementPacket);
String lastSender = GetSenderName(*lastMeasurementPacket);
auto &p = lastMeasurementPacket->decoded;
if (!pb_decode_from_bytes(p.payload.bytes,
p.payload.size,
EnvironmentalMeasurement_fields,
&lastMeasurement)) {
display->setFont(FONT_SMALL);
display->drawString(x, y += fontHeight(FONT_MEDIUM), "Measurement Error");
DEBUG_MSG("EnvironmentalMeasurement: unable to decode last packet");
return;
}
display->setFont(FONT_SMALL);
String last_temp = String(lastMeasurement.temperature,0) +"°C";
if (radioConfig.preferences.environmental_measurement_plugin_display_farenheit){
last_temp = String(CelsiusToFarenheit(lastMeasurement.temperature),0) +"°F";;
}
display->drawString(x, y += fontHeight(FONT_MEDIUM), lastSender+": "+last_temp +"/"+ String(lastMeasurement.relative_humidity,0) + "%("+String(agoSecs)+"s)");
}
bool EnvironmentalMeasurementPlugin::handleReceivedProtobuf(const MeshPacket &mp, const EnvironmentalMeasurement *p)
{
if (!(radioConfig.preferences.environmental_measurement_plugin_measurement_enabled || radioConfig.preferences.environmental_measurement_plugin_screen_enabled)){ if (!(radioConfig.preferences.environmental_measurement_plugin_measurement_enabled || radioConfig.preferences.environmental_measurement_plugin_screen_enabled)){
// If this plugin is not enabled in any capacity, don't handle the packet, and allow other plugins to consume // If this plugin is not enabled in any capacity, don't handle the packet, and allow other plugins to consume
return false; return false;
} }
bool wasBroadcast = mp.to == NODENUM_BROADCAST;
String sender = GetSenderName(mp); String sender = GetSenderName(mp);
// Show new nodes on LCD screen
if (DISPLAY_RECEIVEID_MEASUREMENTS_ON_SCREEN && wasBroadcast) {
String lcd = String("Env Measured: ") +sender + "\n" +
"T: " + p.temperature + "\n" +
"H: " + p.relative_humidity + "\n";
screen->print(lcd.c_str());
}
DEBUG_MSG("-----------------------------------------\n");
DEBUG_MSG("EnvironmentalMeasurement: Received data from %s\n", sender); DEBUG_MSG("EnvironmentalMeasurement: Received data from %s\n", sender);
DEBUG_MSG("EnvironmentalMeasurement->relative_humidity: %f\n", p.relative_humidity); DEBUG_MSG("EnvironmentalMeasurement->relative_humidity: %f\n", p->relative_humidity);
DEBUG_MSG("EnvironmentalMeasurement->temperature: %f\n", p.temperature); DEBUG_MSG("EnvironmentalMeasurement->temperature: %f\n", p->temperature);
lastMeasurementPacket = packetPool.allocCopy(mp);
lastMeasurement = p;
lastSender = sender;
return false; // Let others look at this message also if they want return false; // Let others look at this message also if they want
} }
bool EnvironmentalMeasurementPluginRadio::sendOurEnvironmentalMeasurement(NodeNum dest, bool wantReplies) bool EnvironmentalMeasurementPlugin::sendOurEnvironmentalMeasurement(NodeNum dest, bool wantReplies)
{ {
EnvironmentalMeasurement m; EnvironmentalMeasurement m;
m.barometric_pressure = 0; // TODO: Add support for barometric sensors m.barometric_pressure = 0; // TODO: Add support for barometric sensors
m.relative_humidity = dht.readHumidity();
m.temperature = dht.readTemperature();;
DEBUG_MSG("-----------------------------------------\n"); DEBUG_MSG("-----------------------------------------\n");
DEBUG_MSG("EnvironmentalMeasurement: Read data\n"); DEBUG_MSG("EnvironmentalMeasurement: Read data\n");
DEBUG_MSG("EnvironmentalMeasurement->relative_humidity: %f\n", m.relative_humidity); if (!this->dht->read(true)){
DEBUG_MSG("EnvironmentalMeasurement->temperature: %f\n", m.temperature);
if (isnan(m.relative_humidity) || isnan(m.temperature) ){
sensor_read_error_count++; sensor_read_error_count++;
DEBUG_MSG("EnvironmentalMeasurement: FAILED TO READ DATA\n"); DEBUG_MSG("EnvironmentalMeasurement: FAILED TO READ DATA\n");
return false; return false;
} }
m.relative_humidity = this->dht->readHumidity();
m.temperature = this->dht->readTemperature();
DEBUG_MSG("EnvironmentalMeasurement->relative_humidity: %f\n", m.relative_humidity);
DEBUG_MSG("EnvironmentalMeasurement->temperature: %f\n", m.temperature);
sensor_read_error_count = 0; sensor_read_error_count = 0;

View File

@ -3,61 +3,32 @@
#include "../mesh/generated/environmental_measurement.pb.h" #include "../mesh/generated/environmental_measurement.pb.h"
#include <OLEDDisplay.h> #include <OLEDDisplay.h>
#include <OLEDDisplayUi.h> #include <OLEDDisplayUi.h>
#include <DHT.h>
class EnvironmentalMeasurementPlugin : private concurrency::OSThread, public ProtobufPlugin<EnvironmentalMeasurement>
class EnvironmentalMeasurementPlugin : private concurrency::OSThread
{ {
bool firstTime = 1;
public: public:
EnvironmentalMeasurementPlugin(); EnvironmentalMeasurementPlugin(): concurrency::OSThread("EnvironmentalMeasurementPlugin"), ProtobufPlugin("EnvironmentalMeasurement", PortNum_ENVIRONMENTAL_MEASUREMENT_APP, &EnvironmentalMeasurement_msg) {
lastMeasurementPacket = nullptr;
}
virtual bool wantUIFrame();
virtual void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
protected: 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 EnvironmentalMeasurement *p);
virtual int32_t runOnce(); virtual int32_t runOnce();
};
extern EnvironmentalMeasurementPlugin *environmentalMeasurementPlugin;
/**
* EnvironmentalMeasurementPluginRadio plugin for sending/receiving environmental measurements to/from the mesh
*/
class EnvironmentalMeasurementPluginRadio : public ProtobufPlugin<EnvironmentalMeasurement>
{
public:
/** Constructor
* name is for debugging output
*/
EnvironmentalMeasurementPluginRadio() : ProtobufPlugin("EnvironmentalMeasurement", PortNum_ENVIRONMENTAL_MEASUREMENT_APP, &EnvironmentalMeasurement_msg) {
lastMeasurement.barometric_pressure = nanf("");
lastMeasurement.relative_humidity = nanf("");
lastMeasurement.temperature = nanf("");
lastSender = "N/A";
}
/** /**
* Send our EnvironmentalMeasurement into the mesh * Send our EnvironmentalMeasurement into the mesh
*/ */
bool sendOurEnvironmentalMeasurement(NodeNum dest = NODENUM_BROADCAST, bool wantReplies = false); bool sendOurEnvironmentalMeasurement(NodeNum dest = NODENUM_BROADCAST, bool wantReplies = false);
virtual void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
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 EnvironmentalMeasurement *p);
virtual bool wantUIFrame();
private: private:
float CelsiusToFarenheit(float c);
EnvironmentalMeasurement lastMeasurement; bool firstTime = 1;
DHT* dht;
String lastSender; const MeshPacket *lastMeasurementPacket;
uint32_t sensor_read_error_count = 0;
}; };
extern EnvironmentalMeasurementPluginRadio *environmentalMeasurementPluginRadio;

View File

@ -133,7 +133,7 @@ bool RangeTestPluginRadio::handleReceived(const MeshPacket &mp)
// DEBUG_MSG("Received text msg self=0x%0x, from=0x%0x, to=0x%0x, id=%d, msg=%.*s\n", // DEBUG_MSG("Received text msg self=0x%0x, from=0x%0x, to=0x%0x, id=%d, msg=%.*s\n",
// nodeDB.getNodeNum(), mp.from, mp.to, mp.id, p.payload.size, p.payload.bytes); // nodeDB.getNodeNum(), mp.from, mp.to, mp.id, p.payload.size, p.payload.bytes);
if (mp.from != nodeDB.getNodeNum()) { if (getFrom(&mp) != nodeDB.getNodeNum()) {
// DEBUG_MSG("* * Message came from the mesh\n"); // DEBUG_MSG("* * Message came from the mesh\n");
// Serial2.println("* * Message came from the mesh"); // Serial2.println("* * Message came from the mesh");
@ -144,7 +144,7 @@ bool RangeTestPluginRadio::handleReceived(const MeshPacket &mp)
*/ */
NodeInfo *n = nodeDB.getNode(mp.from); NodeInfo *n = nodeDB.getNode(getFrom(&mp));
if (radioConfig.preferences.range_test_plugin_save) { if (radioConfig.preferences.range_test_plugin_save) {
appendFile(mp); appendFile(mp);
@ -209,7 +209,7 @@ bool RangeTestPluginRadio::appendFile(const MeshPacket &mp)
{ {
auto &p = mp.decoded; auto &p = mp.decoded;
NodeInfo *n = nodeDB.getNode(mp.from); NodeInfo *n = nodeDB.getNode(getFrom(&mp));
/* /*
DEBUG_MSG("-----------------------------------------\n"); DEBUG_MSG("-----------------------------------------\n");
DEBUG_MSG("p.payload.bytes \"%s\"\n", p.payload.bytes); DEBUG_MSG("p.payload.bytes \"%s\"\n", p.payload.bytes);
@ -290,7 +290,7 @@ bool RangeTestPluginRadio::appendFile(const MeshPacket &mp)
fileToAppend.printf("??:??:??,"); // Time fileToAppend.printf("??:??:??,"); // Time
} }
fileToAppend.printf("%d,", mp.from); // From fileToAppend.printf("%d,", getFrom(&mp)); // From
fileToAppend.printf("%s,", n->user.long_name); // Long Name fileToAppend.printf("%s,", n->user.long_name); // Long Name
fileToAppend.printf("%f,", n->position.latitude_i * 1e-7); // Sender Lat fileToAppend.printf("%f,", n->position.latitude_i * 1e-7); // Sender Lat
fileToAppend.printf("%f,", n->position.longitude_i * 1e-7); // Sender Long fileToAppend.printf("%f,", n->position.longitude_i * 1e-7); // Sender Long

View File

@ -263,12 +263,12 @@ bool StoreForwardPluginRadio::handleReceived(const MeshPacket &mp)
#ifndef NO_ESP32 #ifndef NO_ESP32
if (radioConfig.preferences.store_forward_plugin_enabled) { if (radioConfig.preferences.store_forward_plugin_enabled) {
if (mp.from != nodeDB.getNodeNum()) { if (getFrom(&mp) != nodeDB.getNodeNum()) {
// DEBUG_MSG("Store & Forward Plugin -- Print Start ---------- ---------- ---------- ---------- ----------\n\n\n"); // DEBUG_MSG("Store & Forward Plugin -- Print Start ---------- ---------- ---------- ---------- ----------\n\n\n");
// DEBUG_MSG("%s (id=0x%08x Fr0x%02x To0x%02x, WantAck%d, HopLim%d", prefix, p->id, p->from & 0xff, p->to & 0xff, // DEBUG_MSG("%s (id=0x%08x Fr0x%02x To0x%02x, WantAck%d, HopLim%d", prefix, p->id, p->from & 0xff, p->to & 0xff,
// p->want_ack, p->hop_limit); // p->want_ack, p->hop_limit);
printPacket("----- PACKET FROM RADIO -----", &mp); printPacket("----- PACKET FROM RADIO -----", &mp);
uint32_t sawTime = storeForwardPlugin->sawNode(mp.from & 0xffffffff); uint32_t sawTime = storeForwardPlugin->sawNode(getFrom(&mp) & 0xffffffff);
DEBUG_MSG("We last saw this node (%u), %u sec ago\n", mp.from & 0xffffffff, (millis() - sawTime) / 1000); DEBUG_MSG("We last saw this node (%u), %u sec ago\n", mp.from & 0xffffffff, (millis() - sawTime) / 1000);
if (mp.decoded.portnum == PortNum_UNKNOWN_APP) { if (mp.decoded.portnum == PortNum_UNKNOWN_APP) {

View File

@ -1,4 +1,4 @@
[VERSION] [VERSION]
major = 1 major = 1
minor = 2 minor = 2
build = 0 build = 6