Compare commits

..

5 Commits

Author SHA1 Message Date
Thomas Göttgens
b0060b61fe Merge branch 'develop' into zps-module 2026-02-04 14:03:29 +01:00
Thomas Göttgens
a7435b85a1 trunk fmt 2025-08-28 13:50:22 +10:00
Thomas Göttgens
729d6c576f everyone's a critic, especially copilot. 2025-08-28 13:50:22 +10:00
Thomas Göttgens
15b84fca01 Fix compile and check issues 2025-08-28 13:50:22 +10:00
Thomas Göttgens
285b30dff0 the original ZPS module from https://github.com/a-f-G-U-C/Meshtastic-ZPS - work in progress, adapted to 2.x firmware convention, disabled by default 2025-08-28 13:50:22 +10:00
16 changed files with 522 additions and 2562 deletions

View File

@@ -203,16 +203,6 @@ HostMetrics:
# UserStringCommand: cat /sys/firmware/devicetree/base/serial-number # Command to execute, to send the results as the userString
StoreAndForward:
# Enabled: true # Enable Store and Forward++, true by default
# DBPath: /var/lib/meshtasticd/ # Path to the S&F++ Sqlite DB
# Stratum0: false # Specify if this node is a Stratum 0 node, the controller node.
# InitialSync: 10 # Number of messages to
# Hops: 3 # Number of hops to use for SF++ messages
# AnnounceInterval: 5 # Interval in minutes between announcing tip of chain hash
# MaxChainLength: 1000 # Maximum number of messages to store in a chain
Config:
# DisplayMode: TWOCOLOR # uncomment to force BaseUI
# DisplayMode: COLOR # uncomment to force MUI

View File

@@ -50,6 +50,7 @@ build_flags = -Wno-missing-field-initializers
-DRADIOLIB_EXCLUDE_APRS=1
-DRADIOLIB_EXCLUDE_LORAWAN=1
-DMESHTASTIC_EXCLUDE_DROPZONE=1
-DMESHTASTIC_EXCLUDE_ZPS=1
-DMESHTASTIC_EXCLUDE_REPLYBOT=1
-DMESHTASTIC_EXCLUDE_REMOTEHARDWARE=1
-DMESHTASTIC_EXCLUDE_HEALTH_TELEMETRY=1
@@ -97,7 +98,7 @@ lib_deps =
# renovate: datasource=custom.pio depName=NonBlockingRTTTL packageName=end2endzone/library/NonBlockingRTTTL
end2endzone/NonBlockingRTTTL@1.4.0
build_flags = ${env.build_flags} -Os
build_src_filter = ${env.build_src_filter} -<platform/portduino/> -<graphics/niche/> -<modules/Native/>
build_src_filter = ${env.build_src_filter} -<platform/portduino/> -<graphics/niche/>
; Common libs for communicating over TCP/IP networks such as MQTT
[networking_base]

View File

@@ -145,18 +145,6 @@ bool NextHopRouter::perhapsRebroadcast(const meshtastic_MeshPacket *p)
tosend->hop_start -= (tosend->hop_limit - 2);
tosend->hop_limit = 2;
}
#elif ARCH_PORTDUINO
if (tosend->which_payload_variant == meshtastic_MeshPacket_decoded_tag &&
portduino_config.nohop_ports.size()) {
for (const auto &port : portduino_config.nohop_ports) {
if (port == tosend->decoded.portnum) {
LOG_DEBUG("0-hopping portnum %u", tosend->decoded.portnum);
tosend->hop_start -= tosend->hop_limit;
tosend->hop_limit = 0;
break;
}
}
}
#endif
if (p->next_hop == NO_NEXT_HOP_PREFERENCE) {

View File

@@ -648,9 +648,9 @@ uint32_t RadioInterface::getTxDelayMsecWeighted(meshtastic_MeshPacket *p)
void printPacket(const char *prefix, const meshtastic_MeshPacket *p)
{
#if defined(DEBUG_PORT) && !defined(DEBUG_MUTE)
std::string out = DEBUG_PORT.mt_sprintf(
"%s (id=0x%08x fr=0x%08x to=0x%08x, transport = %u, WantAck=%d, HopLim=%d HopStart=%d Ch=0x%x", prefix, p->id, p->from,
p->to, p->transport_mechanism, p->want_ack, p->hop_limit, p->hop_start, p->channel);
std::string out =
DEBUG_PORT.mt_sprintf("%s (id=0x%08x fr=0x%08x to=0x%08x, transport = %u, WantAck=%d, HopLim=%d Ch=0x%x", prefix, p->id,
p->from, p->to, p->transport_mechanism, p->want_ack, p->hop_limit, p->channel);
if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) {
auto &s = p->decoded;

View File

@@ -16,7 +16,6 @@
#endif
#include "Default.h"
#if ARCH_PORTDUINO
#include "modules/Native/StoreForwardPlusPlus.h"
#include "platform/portduino/PortduinoGlue.h"
#endif
#if ENABLE_JSON_LOGGING || ARCH_PORTDUINO
@@ -366,12 +365,6 @@ ErrorCode Router::send(meshtastic_MeshPacket *p)
abortSendAndNak(encodeResult, p);
return encodeResult; // FIXME - this isn't a valid ErrorCode
}
#if ARCH_PORTDUINO
if (p_decoded->decoded.portnum == meshtastic_PortNum_TEXT_MESSAGE_APP &&
(p->from == 0 || p->from == nodeDB->getNodeNum()) && storeForwardPlusPlusModule && portduino_config.sfpp_enabled) {
storeForwardPlusPlusModule->handleEncrypted(p_decoded, p);
}
#endif
#if !MESHTASTIC_EXCLUDE_MQTT
// Only publish to MQTT if we're the original transmitter of the packet
if (moduleConfig.mqtt.enabled && isFromUs(p) && mqtt) {
@@ -752,22 +745,6 @@ void Router::handleReceived(meshtastic_MeshPacket *p, RxSource src)
cancelSending(p->from, p->id);
skipHandle = true;
}
#if ARCH_PORTDUINO
if (portduino_config.whitelist_enabled) {
bool allowed = false;
for (const auto &port : portduino_config.whitelist_ports) {
if (port == p->decoded.portnum) {
allowed = true;
break;
}
}
if (!allowed) {
LOG_DEBUG("Dropping packet not on Portduino Whitelist");
cancelSending(p->from, p->id);
skipHandle = true;
}
}
#endif
} else {
printPacket("packet decoding failed or skipped (no PSK?)", p);
}

View File

@@ -4,9 +4,6 @@
#include "modules/StatusLEDModule.h"
#include "modules/SystemCommandsModule.h"
#endif
#if !MESHTASTIC_EXCLUDE_REPLYBOT
#include "ReplyBotModule.h"
#endif
#if !MESHTASTIC_EXCLUDE_PKI
#include "KeyVerificationModule.h"
#endif
@@ -46,9 +43,6 @@
#include "modules/WaypointModule.h"
#endif
#if ARCH_PORTDUINO
#include "input/LinuxInputImpl.h"
#include "input/SeesawRotary.h"
#include "modules/Native/StoreForwardPlusPlus.h"
#include "modules/Telemetry/HostMetrics.h"
#if !MESHTASTIC_EXCLUDE_STOREFORWARD
#include "modules/StoreForwardModule.h"
@@ -100,6 +94,10 @@
#include "modules/StatusMessageModule.h"
#endif
#if !MESHTASTIC_EXCLUDE_ZPS
#include "modules/esp32/ZPSModule.h"
#endif
#if defined(HAS_HARDWARE_WATCHDOG)
#include "watchdog/watchdogThread.h"
#endif
@@ -118,9 +116,7 @@ void setupModules()
#if defined(LED_CHARGE) || defined(LED_PAIRING)
statusLEDModule = new StatusLEDModule();
#endif
#if !MESHTASTIC_EXCLUDE_REPLYBOT
new ReplyBotModule();
#endif
#if !MESHTASTIC_EXCLUDE_ADMIN
adminModule = new AdminModule();
#endif
@@ -164,6 +160,9 @@ void setupModules()
#if !MESHTASTIC_EXCLUDE_STATUS
statusMessageModule = new StatusMessageModule();
#endif
#if !MESHTASTIC_EXCLUDE_ZPS
zpsModule = new ZPSModule();
#endif
#if !MESHTASTIC_EXCLUDE_GENERIC_THREAD_MODULE
new GenericThreadModule();
#endif
@@ -185,11 +184,6 @@ void setupModules()
#endif
#if ARCH_PORTDUINO
new HostMetricsModule();
#if SFPP_ENABLED
if (portduino_config.sfpp_enabled) {
storeForwardPlusPlusModule = new StoreForwardPlusPlusModule();
}
#endif
#endif
#if HAS_TELEMETRY
new DeviceTelemetryModule();

File diff suppressed because it is too large Load Diff

View File

@@ -1,310 +0,0 @@
#pragma once
#if __has_include("sqlite3.h")
#define SFPP_ENABLED 1
#include "Channels.h"
#include "ProtobufModule.h"
#include "Router.h"
#include "SinglePortModule.h"
#include "sqlite3.h"
#define SFPP_HASH_SIZE 16
#define SFPP_SHORT_HASH_SIZE 8
/**
* Store and forward ++ module
* There's an obvious need for a store-and-forward mechanism in Meshtastic.
* This module takes heavy inspiration from Git, building a chain of messages that can be synced between nodes.
* Each message is hashed, and the chain is built by hashing the previous commit hash and the current message hash.
* Nodes can request missing messages by requesting the next message after a given commit hash.
*
* The current focus is text messages, limited to the primary channel.
*
* Each chain is identified by a root hash, which is derived from the channelHash, the local nodenum, and the timestamp when
* created.
*
* Each message is also given a message hash, derived from the encrypted payload, the to, from, id.
* Notably not the timestamp, as we want these to match across nodes, even if the timestamps differ.
*
* The authoritative node for the chain will generate a commit hash for each message when adding it to the chain.
* The first message's commit hash is derived from the root hash and the message hash.
* Subsequent messages' commit hashes are derived from the previous commit hash and the current message hash.
* This allows a node to see only the last commit hash, and confirm it hasn't missed any messages.
*
* Nodes can request the next message in the chain by sending a LINK_REQUEST message with the root hash and the last known commit
* hash. Any node that has the next message can respond with a LINK_PROVIDE message containing the next message.
*
* When a satellite node sees a new text message, it stores it in a scratch database.
* These messages are periodically offered to the authoritative node for inclusion in the chain.
*
* The LINK_PROVIDE message does double-duty, sending both on-chain and off-chain messages.
* The differentiator is whether the commit hash is set or left empty.
*
* When a satellite node receives a canonical link message, it checks if it has the message in scratch.
* And evicts it when adding it to the canonical chain.
*
* This approach allows a node to know whether it has seen a given message before, or if it is new coming via SFPP.
* If new, and the timestamp is within the rebroadcast timeout, it will process that message as if it were just received from the
* mesh, allowing it to be decrypted, shown to the user, and rebroadcast.
*/
class StoreForwardPlusPlusModule : public ProtobufModule<meshtastic_StoreForwardPlusPlus>, private concurrency::OSThread
{
struct link_object {
uint32_t to;
uint32_t from;
uint32_t id;
uint32_t rx_time = 0;
ChannelHash channel_hash;
uint8_t encrypted_bytes[256] = {0};
size_t encrypted_len;
uint8_t message_hash[SFPP_HASH_SIZE] = {0};
size_t message_hash_len = 0;
uint8_t root_hash[SFPP_HASH_SIZE] = {0};
size_t root_hash_len = 0;
uint8_t commit_hash[SFPP_HASH_SIZE] = {0};
size_t commit_hash_len = 0;
uint32_t counter = 0;
std::string payload;
bool validObject = true; // set this false when a chain calulation fails, etc.
};
public:
/** Constructor
*
*/
StoreForwardPlusPlusModule();
/*
-Override the wantPacket method.
*/
virtual bool wantPacket(const meshtastic_MeshPacket *p) override
{
if (p->decoded.portnum == meshtastic_PortNum_TEXT_MESSAGE_APP ||
p->decoded.portnum == (portduino_config.sfpp_steal_port ? meshtastic_PortNum_TEXT_MESSAGE_COMPRESSED_APP
: meshtastic_PortNum_STORE_FORWARD_PLUSPLUS_APP)) {
return true;
} else {
return false;
}
}
void handleEncrypted(const meshtastic_MeshPacket *, const meshtastic_MeshPacket *);
protected:
/** Called to handle a particular incoming message
@return ProcessMessage::STOP if you've guaranteed you've handled this message and no other handlers should be considered for
it
*/
virtual ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override;
virtual bool handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_StoreForwardPlusPlus *t) override;
virtual int32_t runOnce() override;
private:
sqlite3 *ppDb;
sqlite3_stmt *chain_insert_stmt;
sqlite3_stmt *scratch_insert_stmt;
sqlite3_stmt *checkDupMessageHash;
sqlite3_stmt *checkDupCommitHash;
sqlite3_stmt *checkScratch;
sqlite3_stmt *removeScratch;
sqlite3_stmt *updatePayloadStmt;
sqlite3_stmt *getPayloadFromScratchStmt;
sqlite3_stmt *fromScratchStmt;
sqlite3_stmt *fromScratchByHashStmt;
sqlite3_stmt *getNextHashStmt;
sqlite3_stmt *getChainEndStmt;
sqlite3_stmt *getLinkStmt;
sqlite3_stmt *getLinkFromMessageHashStmt;
sqlite3_stmt *getHashFromRootStmt;
sqlite3_stmt *addRootToMappingsStmt;
sqlite3_stmt *getRootFromChannelHashStmt;
sqlite3_stmt *getFullRootHashStmt;
sqlite3_stmt *setChainCountStmt;
sqlite3_stmt *getChainCountStmt;
sqlite3_stmt *getScratchCountStmt;
sqlite3_stmt *getRootCanonScratchCountStmt;
sqlite3_stmt *pruneScratchQueueStmt;
sqlite3_stmt *trimOldestLinkStmt;
sqlite3_stmt *maybeAddPeerStmt;
sqlite3_stmt *getPeerStmt;
sqlite3_stmt *updatePeerStmt;
sqlite3_stmt *clearChainStmt;
sqlite3_stmt *canon_scratch_insert_stmt;
sqlite3_stmt *getCanonScratchCountStmt;
sqlite3_stmt *getCanonScratchStmt;
sqlite3_stmt *removeCanonScratch;
sqlite3_stmt *clearCanonScratchStmt;
// For a given Meshtastic ChannelHash, fills the root_hash buffer with a 32-byte root hash
// returns true if the root hash was found
bool getRootFromChannelHash(ChannelHash, uint8_t *);
// For a given root hash, returns the ChannelHash
// can handle partial root hashes
ChannelHash getChannelHashFromRoot(uint8_t *_root_hash, size_t);
// given a root hash and commit hash, returns the next commit hash in the chain
// can handle partial root and commit hashes, always fills the buffer with 32 bytes
// returns true if a next hash was found
bool getNextHash(uint8_t *, size_t, uint8_t *, size_t, uint8_t *);
// For a given Meshtastic ChannelHash, fills the root_hash buffer with a 32-byte root hash
// but this function will add the root hash if it is not already present
// returns hash size or 0 if not found/added
size_t getOrAddRootFromChannelHash(ChannelHash, uint8_t *);
// adds the ChannelHash and root_hash to the mappings table
void addRootToMappings(ChannelHash, uint8_t *);
// requests the next message in the chain from the mesh network
// Sends a LINK_REQUEST message
void requestNextMessage(uint8_t *, size_t, uint8_t *, size_t);
// request the message X entries from the end.
// used to bootstrap a chain, without downloading all of the history
void requestMessageCount(uint8_t *, size_t, uint32_t);
// sends a LINK_PROVIDE message broadcasting the given link object
void broadcastLink(uint8_t *, size_t);
// sends a LINK_PROVIDE message broadcasting the given link object
void broadcastLink(link_object &, bool, bool = false);
// sends a LINK_PROVIDE message broadcasting the given link object from scratch message store
bool sendFromScratch(uint8_t *);
// Adds the given link object to the canonical chain database
bool addToChain(link_object &);
// Adds an incoming text message to the scratch database
bool addToScratch(link_object &);
// sends a CANON_ANNOUNCE message, specifying the given root and commit hashes
void canonAnnounce(link_object &);
// checks if the message hash is present in the canonical chain database
bool isInDB(uint8_t *, size_t);
// checks if the commit hash is present in the canonical chain database
bool isCommitInDB(uint8_t *, size_t);
// checks if the message hash is present in the scratch database
bool isInScratch(uint8_t *, size_t);
// retrieves a link object from the scratch database
link_object getFromScratch(uint8_t *, size_t);
// removes a link object from the scratch database
void removeFromScratch(uint8_t *, size_t);
// iterate through our scratch database, and see if we can speculate a chain up to the given commit hash
bool speculateScratchChain(uint8_t *, size_t, uint8_t *, uint8_t *);
// retrieves the next link object from scratch given a root hash
link_object getNextScratchObject(uint8_t *);
// fills the payload section with the decrypted data for the given message hash
// probably not needed for production, but useful for testing
void updatePayload(uint8_t *, size_t, std::string);
// Takes the decrypted MeshPacket and the encrypted packet copy, and builds a link_object
// Generates a message hash, but does not set the commit hash
link_object ingestTextPacket(const meshtastic_MeshPacket &, const meshtastic_MeshPacket *);
// ingests a LINK_PROVIDE message and builds a link_object
// confirms the root hash and commit hash
link_object ingestLinkMessage(meshtastic_StoreForwardPlusPlus *);
// retrieves a link object from the canonical chain database given a commit hash
link_object getLink(uint8_t *, size_t);
// retrieves a link object from the canonical chain database given a message hash
link_object getLinkFromMessageHash(uint8_t *, size_t);
// puts the encrypted payload back into the queue as if it were just received
void rebroadcastLinkObject(link_object &);
// Check if an incoming link object's commit hash matches the calculated commit hash
bool checkCommitHash(link_object &lo, uint8_t *commit_hash_bytes, size_t hash_len);
// given a partial root hash, looks up the full 32-byte root hash
// returns true if found
bool lookUpFullRootHash(uint8_t *partial_root_hash, size_t partial_root_hash_len, uint8_t *full_root_hash);
// update the mappings table to set the chain count for the given root hash
void setChainCount(uint8_t *, size_t, uint32_t);
// get the chain count for the given root hash
uint32_t getChainCount(uint8_t *, size_t);
// get the scratch count for the given root hash
uint32_t getScratchCount(uint8_t *, size_t);
// get the canon scratch count for the given root hash
uint32_t getCanonScratchCount(uint8_t *, size_t);
link_object getLinkFromPositionFromTip(uint32_t, uint8_t *, size_t);
void pruneScratchQueue();
void trimOldestLink(uint8_t *, size_t);
void clearChain(uint8_t *, size_t);
void recalculateMessageHash(link_object &);
// given a link object with a payload and other fields, recalculates the message hash
// returns true if a match
bool recalculateHash(link_object &, uint8_t *, size_t, uint8_t *, size_t);
void updatePeers(const meshtastic_MeshPacket &, meshtastic_StoreForwardPlusPlus_SFPP_message_type);
void maybeMoveFromCanonScratch(uint8_t *, size_t);
void addToCanonScratch(link_object &);
link_object getfromCanonScratch(uint8_t *, size_t);
void removeFromCanonScratch(uint8_t *, size_t);
void clearCanonScratch(uint8_t *, size_t, uint32_t);
bool isInCanonScratch(uint8_t *, size_t);
void logLinkObject(link_object &);
// Track if we have a scheduled runOnce pending
// useful to not accudentally delay a scheduled runOnce
bool pendingRun = false;
// Once we have multiple chain types, we can extend this
enum chain_types {
channel_chain = 0,
};
uint32_t rebroadcastTimeout = 3600; // Messages older than this (in seconds) will not be rebroadcast
bool doing_split_send = false;
link_object split_link_out;
bool doing_split_receive = false;
link_object split_link_in;
bool did_announce_last = false;
uint32_t texts_rebroadcast = 0;
uint32_t links_speculated = 0;
uint32_t canon_announces = 0;
uint32_t links_requested = 0;
uint32_t links_provided = 0;
uint32_t links_added = 0;
uint32_t links_from_canon_scratch = 0;
uint32_t links_from_scratch = 0;
uint32_t split_links_sent = 0;
uint32_t split_links_received = 0;
uint32_t links_pruned = 0;
uint32_t scratch_timed_out = 0;
uint32_t sent_from_scratch = 0;
uint32_t received_from_scratch = 0;
};
extern StoreForwardPlusPlusModule *storeForwardPlusPlusModule;
#endif

View File

@@ -1,4 +1,3 @@
#include "configuration.h"
#if !MESHTASTIC_EXCLUDE_REPLYBOT
/*
* ReplyBotModule.cpp
@@ -13,10 +12,11 @@
* entirely. See the official firmware documentation for guidance on adding modules.
*/
#include "ReplyBotModule.h"
#include "Channels.h"
#include "MeshService.h"
#include "NodeDB.h"
#include "ReplyBotModule.h"
#include "configuration.h"
#include "mesh/MeshTypes.h"
#include <Arduino.h>

View File

@@ -1,5 +1,4 @@
#pragma once
#include "configuration.h"
#if !MESHTASTIC_EXCLUDE_REPLYBOT
#include "SinglePortModule.h"
#include "mesh/generated/meshtastic/mesh.pb.h"

View File

@@ -0,0 +1,419 @@
/*
* ZPS - Zero-GPS Positioning System for standalone Meshtastic devices
* - experimental tools for estimating own position without a GPS -
*
* Copyright 2021 all rights reserved by https://github.com/a-f-G-U-C
* Released under GPL v3 (see LICENSE file for details)
*/
#include "ZPSModule.h"
#include "Default.h"
#include "MeshService.h"
#include "NodeDB.h"
#include "NodeStatus.h"
#include "Router.h"
#include "configuration.h"
#include "gps/RTC.h"
#include <WiFi.h>
#if !defined(MESHTASTIC_EXCLUDE_BLUETOOTH)
#include "NimBLEDevice.h"
#define BLE_MAX_REC 15
#define BLE_NO_RESULTS -1 // Indicates a BLE scan is in progress
uint8_t bleCounter = 0; // used internally by the ble scanner
uint64_t bleResult[BLE_MAX_REC + 1];
int bleResSize = BLE_NO_RESULTS;
uint64_t scanStart = 0;
ZPSModule *zpsModule;
// Mini BLE scanner, NIMBLE based and modelled loosely after the Wifi scanner
static int ble_scan(uint32_t duration, bool passive = true, bool dedup = true);
// ZPSModule::ZPSModule()
// : ProtobufModule("ZPS", ZPS_PORTNUM, Position_fields), concurrency::OSThread("ZPSModule")
ZPSModule::ZPSModule() : SinglePortModule("ZPS", ZPS_PORTNUM), concurrency::OSThread("ZPSModule")
{
setIntervalFromNow(ZPS_STARTUP_DELAY); // Delay startup by 10 seconds, no need to race :)
wantBSS = true;
wantBLE = true;
WiFi.mode(WIFI_STA);
WiFi.disconnect();
WiFi.scanNetworks(true, true); // nonblock, showhidden
scanState = SCAN_BSS_RUN;
}
ProcessMessage ZPSModule::handleReceived(const meshtastic_MeshPacket &mp)
{
meshtastic_Position pos = meshtastic_Position_init_default;
auto &pd = mp.decoded;
uint8_t nRecs = pd.payload.size >> 3;
LOG_DEBUG("handleReceived %s 0x%0x->0x%0x, id=0x%x, port=%d, len=%d, rec=%d\n", name, mp.from, mp.to, mp.id, pd.portnum,
pd.payload.size, nRecs);
if (nRecs > ZPS_DATAPKT_MAXITEMS)
nRecs = ZPS_DATAPKT_MAXITEMS;
memcpy(&netData, pd.payload.bytes, nRecs << 3);
// Currently we are unable to act as a position server, so we're
// not interested in broadcasts (this will change later)
if (mp.to != nodeDB->getNodeNum()) {
// Message is not for us, won't process
return ProcessMessage::CONTINUE;
}
#ifdef ZPS_EXTRAVERBOSE
for (int i = 0; i < nRecs; i++) {
LOG_DEBUG("ZPS[%d]: %08x"
"%08x\n",
i, (uint32_t)(netData[i] >> 32), (uint32_t)netData[i]);
}
#endif
if ((netData[0] & 0x800000000000) && (nRecs >= 2)) {
// message contains a position
pos.PDOP = (netData[0] >> 40) & 0x7f;
pos.timestamp = netData[0] & 0xffffffff;
// second int64 encodes lat and lon
pos.longitude_i = (int32_t)(netData[1] & 0xffffffff);
pos.latitude_i = (int32_t)((netData[1] >> 32) & 0xffffffff);
// FIXME should be conditional, to ensure we don't overwrite a good GPS fix!
LOG_DEBUG("ZPS lat/lon/dop/pts %d/%d/%d/%d\n", pos.latitude_i, pos.longitude_i, pos.PDOP, pos.timestamp);
// Some required fields
pos.time = getTime();
pos.location_source = meshtastic_Position_LocSource_LOC_EXTERNAL;
// don't update position if my gps fix is valid
if (nodeDB->hasValidPosition(nodeDB->getMeshNode(nodeDB->getNodeNum()))) {
LOG_DEBUG("ZPSModule::handleReceived: ignoring position update, GPS is valid\n");
return ProcessMessage::CONTINUE;
}
nodeDB->updatePosition(nodeDB->getNodeNum(), pos);
} else {
// nothing we can do - for now
return ProcessMessage::CONTINUE;
}
return ProcessMessage::CONTINUE; // Let others look at this message also if they want
}
meshtastic_MeshPacket *ZPSModule::allocReply()
{
meshtastic_MeshPacket *p = allocDataPacket();
p->decoded.payload.size = (netRecs + 2) << 3; // actually can be only +1 if no GPS data
LOG_DEBUG("Allocating dataPacket for %d items, %d bytes\n", netRecs, p->decoded.payload.size);
memcpy(p->decoded.payload.bytes, &netData, p->decoded.payload.size);
return (p);
}
void ZPSModule::sendDataPacket(NodeNum dest, bool wantReplies)
{
// cancel any not yet sent (now stale) position packets
if (prevPacketId)
service->cancelSending(prevPacketId);
meshtastic_MeshPacket *p = allocReply();
p->to = dest;
p->decoded.portnum = meshtastic_PortNum_ZPS_APP;
p->decoded.want_response = wantReplies;
p->priority = meshtastic_MeshPacket_Priority_BACKGROUND;
prevPacketId = p->id;
service->sendToMesh(p, RX_SRC_LOCAL);
}
int32_t ZPSModule::runOnce()
{
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeDB->getNodeNum());
assert(node);
// LOG_DEBUG("ZPSModule::runOnce() START, scanState: %d\n", (int) scanState);
int numWifi = 0;
if (scanState == SCAN_BSS_RUN) {
// check completion status of any running Wifi scan
numWifi = WiFi.scanComplete();
if (numWifi >= 0) {
// scan is complete
LOG_DEBUG("%d BSS found\n", numWifi);
LOG_DEBUG("BSS scan done in %d millis\n", millis() - scanStart);
if (wantBSS && haveBSS) {
// old data exists, overwrite it
netRecs = 0;
haveBSS = haveBLE = false;
}
for (int i = 0; i < numWifi; i++) {
// pack each Wifi network record into a 64-bit int
uint64_t netBytes = encodeBSS(WiFi.BSSID(i), WiFi.channel(i), abs(WiFi.RSSI(i)));
if (wantBSS) {
// load into outbound array if needed
outBufAdd(netBytes);
haveBSS = true;
}
#ifdef ZPS_EXTRAVERBOSE
LOG_DEBUG("BSS[%02d]: %08x"
"%08x\n",
i, (uint32_t)(netBytes >> 32), (uint32_t)netBytes);
#endif
}
WiFi.scanDelete();
scanState = SCAN_BSS_DONE;
#ifdef ZPS_EXTRAVERBOSE
} else if (numWifi == -1) {
// LOG_DEBUG("BSS scan in-progress\n");
} else {
LOG_DEBUG("BSS scan state=%d\n", numWifi);
#endif
}
}
if ((scanState == SCAN_BLE_RUN) && (bleResSize >= 0)) {
// completion status checked above (bleResSize >= 0)
LOG_DEBUG("BLE scan done in %d millis\n", millis() - scanStart);
scanState = SCAN_BLE_DONE;
if (wantBLE && haveBLE) {
// old data exists, overwrite it
netRecs = 0;
haveBSS = haveBLE = false;
}
for (int i = 0; i < bleResSize; i++) {
// load data into output array if needed
if (wantBLE) {
outBufAdd(bleResult[i]);
haveBLE = true;
}
#ifdef ZPS_EXTRAVERBOSE
LOG_DEBUG("BLE[%d]: %08x"
"%08x\n",
i, (uint32_t)(bleResult[i] >> 32), (uint32_t)bleResult[i]);
#endif
}
// Reset the counter once we're done with the dataset
bleResSize = BLE_NO_RESULTS;
}
// Are we finished assembling that packet? Then send it out
if ((wantBSS == haveBSS) && (wantBLE == haveBLE) &&
airTime->isTxAllowedChannelUtil(config.device.role != meshtastic_Config_DeviceConfig_Role_SENSOR) &&
airTime->isTxAllowedAirUtil() &&
(lastSend == 0 || millis() - lastSend >= Default::getConfiguredOrDefaultMsScaled(config.position.position_broadcast_secs,
default_broadcast_interval_secs,
nodeStatus->getNumOnline()))) {
haveBSS = haveBLE = false;
sendDataPacket(NODENUM_BROADCAST, false); // no replies
lastSend = millis();
netRecs = 0; // reset packet
}
/*
* State machine transitions
*
* FIXME could be managed better, for example: check if we require
* each type of scan (wantBSS/wantBLE), and if not, don't start it!
*/
if (scanState == SCAN_BLE_DONE) {
// BLE done, transition to BSS scanning
scanStart = millis();
LOG_DEBUG("BSS scan start t=%d\n", scanStart);
if (WiFi.scanNetworks(true, true) == WIFI_SCAN_RUNNING) // nonblock, showhidden
scanState = SCAN_BSS_RUN;
} else if (scanState == SCAN_BSS_DONE) {
// BSS done, transition to BLE scanning
scanStart = millis();
LOG_DEBUG("BLE scan start t=%d\n", scanStart);
if (ble_scan(ZPS_BLE_SCANTIME) == 0)
scanState = SCAN_BLE_RUN;
}
// LOG_DEBUG("ZPSModule::runOnce() DONE, scanState=%d\n", scanState);
if ((scanState == SCAN_BSS_RUN) || (scanState == SCAN_BLE_RUN)) {
return 1000; // scan in progress, re-check soon
}
return 5000;
}
uint64_t encodeBSS(uint8_t *bssid, uint8_t chan, uint8_t absRSSI)
{
uint64_t netBytes = absRSSI & 0xff;
netBytes <<= 8;
netBytes |= (chan & 0xff);
for (uint8_t b = 0; b < 6; b++) {
netBytes <<= 8;
netBytes |= bssid[b];
}
return netBytes;
}
uint64_t encodeBLE(uint8_t *addr, uint8_t absRSSI)
{
uint64_t netBytes = absRSSI & 0xff;
netBytes <<= 8;
netBytes |= 0xff; // "channel" byte reserved in BLE records
for (uint8_t b = 0; b < 6; b++) {
netBytes <<= 8;
netBytes |= addr[5 - b] & 0xff;
}
return netBytes;
}
/**
* Event handler
*/
static int ble_gap_event(struct ble_gap_event *event, void *arg)
{
// Adverts matching certain patterns are useless for positioning purposes
// (ephemeral MAC etc), so try excluding them if possible
//
// TODO: Expand the list of reject patterns for BLE adverts.
// There are likely more than 10 patterns to test and reject, including most Apple devices and others.
//
// TODO: Implement full packet search for reject patterns (use memmem() or similar),
// not just at the beginning (currently uses memcmp()).
const uint8_t rejPat[] = {0x1e, 0xff, 0x06, 0x00, 0x01}; // one of many
struct ble_hs_adv_fields fields;
int rc;
int i = 0;
uint64_t netBytes = 0;
switch (event->type) {
case BLE_GAP_EVENT_DISC:
// called once for every BLE advert received
rc = ble_hs_adv_parse_fields(&fields, event->disc.data, event->disc.length_data);
if (rc != 0)
return 0;
if (bleResSize != BLE_NO_RESULTS)
// as far as we know, we're not in the middle of a BLE scan!
LOG_DEBUG("Unexpected BLE_GAP_EVENT_DISC!\n");
#ifdef ZPS_EXTRAVERBOSE
// Dump the advertisement packet
DEBUG_PORT.hexDump("DEBUG", (unsigned char *)event->disc.data, event->disc.length_data);
#endif
// Reject beacons known to be unreliable (ephemeral etc)
if (memcmp(event->disc.data, rejPat, sizeof(rejPat)) == 0) {
LOG_DEBUG("(BLE item filtered by pattern)\n");
return 0; // Processing-wise, it's still a success
}
//
// STORE THE RESULTS IN A SORTED LIST
//
// first, pack each BLE item reading into a 64-bit int
netBytes = encodeBLE(event->disc.addr.val, abs(event->disc.rssi));
// SOME DUPLICATES SURVIVE through filter_duplicates = 1, catch them here
// Duplicate filtering is now handled in the sorting loop below,
// but right now we write for clarity not optimization
for (i = 0; i < bleCounter; i++) {
if ((bleResult[i] & 0xffffffffffff) == (netBytes & 0xffffffffffff)) {
LOG_DEBUG("(BLE duplicate filtered)\n");
return 0;
}
}
#ifdef ZPS_EXTRAVERBOSE
// redundant extraverbosity, but I need it for duplicate hunting
LOG_DEBUG("BL_[%02d]: %08x"
"%08x\n",
bleCounter, (uint32_t)(netBytes >> 32), (uint32_t)netBytes);
#endif
// then insert item into a list (up to BLE_MAX_REC records), sorted by RSSI
for (i = 0; i < bleCounter; i++) {
// find first element greater than ours, that will be our insertion point
if (bleResult[i] > netBytes)
break;
}
// any other records move down one position to vacate res[i]
for (int j = bleCounter; j > i; j--)
bleResult[j] = bleResult[j - 1];
// write new element at insertion point
bleResult[i] = netBytes;
// advance tail of list, but not beyond limit
if (bleCounter < BLE_MAX_REC)
bleCounter++;
return 0; // SUCCESS
case BLE_GAP_EVENT_DISC_COMPLETE:
LOG_DEBUG("EVENT_DISC_COMPLETE in %d millis\n", (millis() - scanStart));
LOG_DEBUG("%d BLE found\n", bleCounter);
bleResSize = bleCounter;
bleCounter = 0; // reset counter
return 0; // SUCCESS
default:
return 0; // SUCCESS
}
}
/**
* Initiates the GAP general discovery procedure (non-blocking)
*/
static int ble_scan(uint32_t duration, bool passive, bool dedup)
{
uint8_t own_addr_type;
struct ble_gap_disc_params disc_params;
int rc;
// Figure out address type to use
rc = ble_hs_id_infer_auto(0, &own_addr_type);
if (rc != 0) {
LOG_DEBUG("error determining address type; rc=%d\n", rc);
return rc;
}
// Scanning parameters, these are mostly default
disc_params.itvl = 0;
disc_params.window = 0;
disc_params.filter_policy = 0;
disc_params.limited = 0;
// These two params are the more interesting ones
disc_params.filter_duplicates = dedup; // self-explanatory
disc_params.passive = passive; // passive uses less power
// Start scanning process (non-blocking) and return
rc = ble_gap_disc(own_addr_type, duration, &disc_params, ble_gap_event, NULL);
if (rc != 0) {
LOG_DEBUG("error initiating GAP discovery; rc=%d\n", rc);
}
return rc;
}
#endif // MESHTASTIC_EXCLUDE_BLUETOOTH

View File

@@ -0,0 +1,86 @@
#pragma once
#include "SinglePortModule.h"
#include "concurrency/OSThread.h"
#include "gps/RTC.h"
#define ZPS_PORTNUM meshtastic_PortNum_ZPS_APP
#define ZPS_DATAPKT_MAXITEMS 20 // max number of records to pack in an outbound packet (~10)
#define ZPS_STARTUP_DELAY 10000 // Module startup delay in millis
// Duration of a BLE scan in millis.
// We want this number to be SLIGHTLY UNDER an integer number of seconds,
// to be able to catch the result as fresh as possible on a 1-second polling loop
#define ZPS_BLE_SCANTIME 2900 // millis
enum SCANSTATE { SCAN_NONE, SCAN_BSS_RUN, SCAN_BSS_DONE, SCAN_BLE_RUN, SCAN_BLE_DONE };
/*
* Data packing "compression" functions
* Ingest a WiFi BSSID, channel and RSSI (or BLE address and RSSI)
* and encode them into a packed uint64
*/
uint64_t encodeBSS(uint8_t *bssid, uint8_t chan, uint8_t absRSSI);
uint64_t encodeBLE(uint8_t *addr, uint8_t absRSSI);
class ZPSModule : public SinglePortModule, private concurrency::OSThread
{
/// The id of the last packet we sent, to allow us to cancel it if we make something fresher
PacketId prevPacketId = 0;
/// We limit our broadcasts to a max rate
uint32_t lastSend = 0;
bool wantBSS = true;
bool haveBSS = false;
bool wantBLE = true;
bool haveBLE = false;
public:
/** Constructor
* name is for debugging output
*/
ZPSModule();
/**
* Send our radio environment data into the mesh
*/
void sendDataPacket(NodeNum dest = NODENUM_BROADCAST, bool wantReplies = false);
protected:
/** Called to handle a particular incoming message
@return true if you've guaranteed you've handled this message and no other handlers should be considered for it
*/
virtual ProcessMessage handleReceived(const meshtastic_MeshPacket &mp);
/** Messages can be received that have the want_response bit set. If set, this callback will be invoked
* so that subclasses can (optionally) send a response back to the original sender. */
virtual meshtastic_MeshPacket *allocReply();
/** Does our periodic broadcast */
virtual int32_t runOnce();
private:
// outbound data packet staging buffer and record counter
uint64_t netData[ZPS_DATAPKT_MAXITEMS + 2] = {0};
uint8_t netRecs = 0;
// mini state machine to alternate between BSS(Wifi) and BLE scanning
SCANSTATE scanState = SCAN_NONE;
inline void outBufAdd(uint64_t netBytes)
{
// If this is the first record, initialize the header with the current time and reset the record count.
if (!netRecs) {
netData[0] = getTime();
netData[1] = 0;
}
// push to buffer and update counter
if (netRecs < ZPS_DATAPKT_MAXITEMS)
netData[2 + (netRecs++)] = netBytes;
}
};
extern ZPSModule *zpsModule;

View File

@@ -182,7 +182,6 @@ void portduinoSetup()
if (portduino_config.force_simradio == true) {
portduino_config.lora_module = use_simradio;
portduino_config.sfpp_enabled = false;
} else if (configPath != nullptr) {
if (loadConfig(configPath)) {
if (!yamlOnly)
@@ -895,28 +894,6 @@ bool loadConfig(const char *configPath)
}
}
if (yamlConfig["StoreAndForward"]) {
portduino_config.sfpp_stratum0 = (yamlConfig["StoreAndForward"]["Stratum0"]).as<bool>(false);
portduino_config.sfpp_enabled = (yamlConfig["StoreAndForward"]["Enabled"]).as<bool>(true);
portduino_config.sfpp_db_path = (yamlConfig["StoreAndForward"]["DBPath"]).as<std::string>("/var/lib/meshtasticd/");
portduino_config.sfpp_initial_sync = (yamlConfig["StoreAndForward"]["InitialSync"]).as<int>(10);
portduino_config.sfpp_hops = (yamlConfig["StoreAndForward"]["Hops"]).as<int>(3);
portduino_config.sfpp_announce_interval = (yamlConfig["StoreAndForward"]["AnnounceInterval"]).as<int>(5);
portduino_config.sfpp_max_chain = (yamlConfig["StoreAndForward"]["MaxChainLength"]).as<uint32_t>(1000);
portduino_config.sfpp_backlog_limit = (yamlConfig["StoreAndForward"]["BacklogLimit"]).as<uint32_t>(100);
portduino_config.sfpp_steal_port = (yamlConfig["StoreAndForward"]["StealPort"]).as<bool>(false);
}
if (yamlConfig["Routing"]) {
if (yamlConfig["Routing"]["WhitelistPorts"]) {
portduino_config.whitelist_ports = (yamlConfig["Routing"]["WhitelistPorts"]).as<std::vector<int>>();
if (portduino_config.whitelist_ports.size() > 0) {
portduino_config.whitelist_enabled = true;
}
}
if (yamlConfig["Routing"]["NoHopPorts"]) {
portduino_config.nohop_ports = (yamlConfig["Routing"]["NoHopPorts"]).as<std::vector<int>>();
}
}
if (yamlConfig["General"]) {
portduino_config.MaxNodes = (yamlConfig["General"]["MaxNodes"]).as<int>(200);
portduino_config.maxtophone = (yamlConfig["General"]["MaxMessageQueue"]).as<int>(100);

View File

@@ -184,26 +184,6 @@ extern struct portduino_config_struct {
bool has_statusMessage = false;
bool enable_UDP = false;
// Store and Forward++
std::string sfpp_db_path = "/var/lib/meshtasticd/";
bool sfpp_stratum0 = false;
bool sfpp_enabled = true;
bool sfpp_steal_port = false;
int sfpp_initial_sync = 10;
int sfpp_hops = 3;
int sfpp_announce_interval = 5; // minutes
uint32_t sfpp_max_chain = 1000;
uint32_t sfpp_backlog_limit = 100;
// allowed root hashes
// upstream node
// Are we allowing unknown channel hashes? Does this even make sense?
// Allow DMs
// Routing
bool whitelist_enabled = false;
std::vector<int> whitelist_ports = {};
std::vector<int> nohop_ports = {};
// General
std::string mac_address = "";
bool mac_address_explicit = false;
@@ -558,29 +538,6 @@ extern struct portduino_config_struct {
out << YAML::EndMap; // Config
}
// StoreAndForward
if (sfpp_enabled) {
out << YAML::Key << "StoreAndForward" << YAML::Value << YAML::BeginMap;
out << YAML::Key << "Enabled" << YAML::Value << sfpp_enabled;
out << YAML::Key << "DBPath" << YAML::Value << sfpp_db_path;
out << YAML::Key << "Stratum0" << YAML::Value << sfpp_stratum0;
out << YAML::Key << "InitialSync" << YAML::Value << sfpp_initial_sync;
out << YAML::Key << "Hops" << YAML::Value << sfpp_hops;
out << YAML::Key << "AnnounceInterval" << YAML::Value << sfpp_announce_interval;
out << YAML::Key << "BacklogLimit" << YAML::Value << sfpp_backlog_limit;
out << YAML::Key << "MaxChainLength" << YAML::Value << sfpp_max_chain;
out << YAML::Key << "StealPort" << YAML::Value << sfpp_steal_port;
out << YAML::EndMap; // StoreAndForward
}
// Routing
if (whitelist_enabled || nohop_ports.size() > 0) {
out << YAML::Key << "Routing" << YAML::Value << YAML::BeginMap;
out << YAML::Key << "WhitelistPorts" << YAML::Value << whitelist_ports;
out << YAML::Key << "NoHopPorts" << YAML::Value << nohop_ports;
out << YAML::EndMap; // Routing
}
// General
out << YAML::Key << "General" << YAML::Value << YAML::BeginMap;
if (config_directory != "")

View File

@@ -7,5 +7,4 @@ build_flags =
-D TLORA_V2_1_16
-I variants/esp32/tlora_v2_1_16
-D LORA_TCXO_GPIO=33
-ULED_BUILTIN
upload_speed = 115200
upload_speed = 115200

View File

@@ -9,7 +9,7 @@ lib_deps =
# renovate: datasource=custom.pio depName=Melopero RV3028 packageName=melopero/library/Melopero RV3028
melopero/Melopero RV3028@1.2.0
build_src_filter = ${portduino_base.build_src_filter} +<modules/Native/>
build_src_filter = ${portduino_base.build_src_filter}
[env:native]
extends = native_base
@@ -20,7 +20,6 @@ build_flags = ${native_base.build_flags}
!pkg-config --libs openssl --silence-errors || :
!pkg-config --cflags --libs sdl2 --silence-errors || :
!pkg-config --cflags --libs libbsd-overlay --silence-errors || :
!pkg-config --cflags --libs sqlite3 --silence-errors || :
[env:native-tft]
extends = native_base
@@ -47,7 +46,6 @@ build_flags = ${native_base.build_flags} -Os -lX11 -linput -lxkbcommon -ffunctio
!pkg-config --libs openssl --silence-errors || :
!pkg-config --cflags --libs sdl2 --silence-errors || :
!pkg-config --cflags --libs libbsd-overlay --silence-errors || :
!pkg-config --cflags --libs sqlite3 --silence-errors || :
build_src_filter =
${native_base.build_src_filter}
@@ -77,7 +75,6 @@ build_flags = ${native_base.build_flags} -Os -ffunction-sections -fdata-sections
!pkg-config --libs libulfius --silence-errors || :
!pkg-config --libs openssl --silence-errors || :
!pkg-config --cflags --libs libbsd-overlay --silence-errors || :
!pkg-config --cflags --libs sqlite3 --silence-errors || :
build_src_filter =
${native_base.build_src_filter}
@@ -111,7 +108,6 @@ build_flags = ${native_base.build_flags} -O0 -fsanitize=address -lX11 -linput -l
!pkg-config --libs libulfius --silence-errors || :
!pkg-config --libs openssl --silence-errors || :
!pkg-config --cflags --libs libbsd-overlay --silence-errors || :
!pkg-config --cflags --libs sqlite3 --silence-errors || :
build_src_filter = ${env:native-tft.build_src_filter}
[env:coverage]