Compare commits

...
13 Commits
Author SHA1 Message Date
Thomas Göttgens 8b9e06b05f Merge branch 'develop' into zps-module 2026-04-12 17:19:30 +02:00
Catalin Patulea 288d7a3e7b Revert "Add include directive for mbedtls error handling in build flags" (#10073)
This reverts commit 391928ed08.

Since esp32_https_server commit e2c9f98, this is no longer necessary.

Also, the esp32-common.ini '-include mbedtls/error.h' causes a build error
under IDF v5 (https://github.com/meshtastic/firmware/pull/9122#issuecomment-4185767085):

  <command-line>: fatal error: mbedtls/error.h: No such file or directory

so this change fixes that error.
2026-04-11 06:35:42 -05:00
Catalin Patulea 4990bf51e3 Delete PointerQueue::dequeuePtrFromISR, unused since commit db766f1 (#99). (#10090) 2026-04-10 16:20:25 -05:00
Tom b2c8cbb78d Enhance traffic management by adjusting position update interval and refining hop exhaustion logic based on channel congestion (#9921) 2026-04-10 10:53:04 -05:00
Jonathan Bennett 8061f83704 Modify log output to show milliseconds (#10115)
Updated timestamp format to include milliseconds.
2026-04-10 07:21:24 -05:00
Tom a3b49b9028 Add powerlimits to reconfigured radio settings as well as init settings. (#10025)
* Add powerlimits to reconfigured radio settings as well as init settings.

* Refactor preamble length handling for wide LoRa configurations

* Moved the preamble setting to the main class and made the references static
2026-04-09 16:18:05 -05:00
Ben Meadors 17945884a5 Merge remote-tracking branch 'origin/master' into develop 2026-04-09 06:21:33 -05:00
renovate[bot] 7fdee353b5 Update meshtastic-esp32_https_server digest to 0c71f38 (#10081)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-08 21:16:56 -05:00
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 563 additions and 43 deletions
+1
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
+2 -2
View File
@@ -137,7 +137,7 @@ void RedirectablePrint::log_to_serial(const char *logLevel, const char *format,
if (color) {
::printf("\u001b[0m");
}
::printf("| %02d:%02d:%02d %u ", hour, min, sec, millis() / 1000);
::printf("| %02d:%02d:%02d %u.%03u ", hour, min, sec, millis() / 1000, millis() % 1000);
#else
printf("%s ", logLevel);
if (color) {
@@ -151,7 +151,7 @@ void RedirectablePrint::log_to_serial(const char *logLevel, const char *format,
if (color) {
::printf("\u001b[0m");
}
::printf("| ??:??:?? %u ", millis() / 1000);
::printf("| ??:??:?? %u.%03u ", millis() / 1000, millis() % 1000);
#else
printf("%s ", logLevel);
if (color) {
+3 -1
View File
@@ -163,9 +163,11 @@ void menuHandler::LoraRegionPicker(uint32_t duration)
// Guard: without a reboot, reconfigure() applies the region directly.
// Reject LORA_24 on sub-GHz-only hardware — getRadio() used to catch this post-reboot.
// TODO: change this to either use the validateLoraConfig() logic or at least check the region for wideLora
// rather than a hardcoded check for LORA_24.
if (selectedRegion == meshtastic_Config_LoRaConfig_RegionCode_LORA_24 &&
!(RadioLibInterface::instance && RadioLibInterface::instance->wideLora())) {
LOG_WARN("Radio hardware does not support 2.4 GHz; ignoring LORA_24 selection");
LOG_WARN("Radio hardware does not support 2.4 GHz; ignoring region selection");
return;
}
+2 -2
View File
@@ -32,8 +32,8 @@
#define default_map_publish_interval_secs 60 * 60
// Traffic management defaults
#define default_traffic_mgmt_position_precision_bits 24 // ~10m grid cells
#define default_traffic_mgmt_position_min_interval_secs ONE_DAY // 1 day between identical positions
#define default_traffic_mgmt_position_precision_bits 24 // ~10m grid cells
#define default_traffic_mgmt_position_min_interval_secs (ONE_DAY / 2) // 12 hours between identical positions
#ifdef USERPREFS_RINGTONE_NAG_SECS
#define default_ringtone_nag_secs USERPREFS_RINGTONE_NAG_SECS
+10 -11
View File
@@ -71,12 +71,10 @@ template <typename T> bool LR11x0Interface<T>::init()
RadioLibInterface::init();
limitPower(LR1110_MAX_POWER);
if ((power > LR1120_MAX_POWER) &&
(config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { // clamp again if wide freq range
power = LR1120_MAX_POWER;
preambleLength = 12; // 12 is the default for operation above 2GHz
if (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_LORA_24) { // clamp if wide freq range
limitPower(LR1120_MAX_POWER);
} else {
limitPower(LR1110_MAX_POWER); // default clamp for non-wide freq range
}
#ifdef LR11X0_RF_SWITCH_SUBGHZ
@@ -177,6 +175,12 @@ template <typename T> bool LR11x0Interface<T>::reconfigure()
err = lora.setSyncWord(syncWord);
assert(err == RADIOLIB_ERR_NONE);
if (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_LORA_24) { // clamp if wide freq range
limitPower(LR1120_MAX_POWER);
} else {
limitPower(LR1110_MAX_POWER); // default clamp for non-wide freq range
}
err = lora.setPreambleLength(preambleLength);
assert(err == RADIOLIB_ERR_NONE);
@@ -184,11 +188,6 @@ template <typename T> bool LR11x0Interface<T>::reconfigure()
if (err != RADIOLIB_ERR_NONE)
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
if (power > LR1110_MAX_POWER) // This chip has lower power limits than some
power = LR1110_MAX_POWER;
if ((power > LR1120_MAX_POWER) && (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) // 2.4G power limit
power = LR1120_MAX_POWER;
err = lora.setOutputPower(power);
assert(err == RADIOLIB_ERR_NONE);
-10
View File
@@ -17,14 +17,4 @@ template <class T> class PointerQueue : public TypedQueue<T *>
return this->dequeue(&p, maxWait) ? p : nullptr;
}
#ifdef HAS_FREE_RTOS
// returns a ptr or null if the queue was empty
T *dequeuePtrFromISR(BaseType_t *higherPriWoken)
{
T *p;
return this->dequeueFromISR(&p, higherPriWoken) ? p : nullptr;
}
#endif
};
+1 -2
View File
@@ -240,8 +240,7 @@ bool RF95Interface::reconfigure()
if (err != RADIOLIB_ERR_NONE)
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
if (power > RF95_MAX_POWER) // This chip has lower power limits than some
power = RF95_MAX_POWER;
limitPower(RF95_MAX_POWER);
#ifdef USE_RF95_RFO
err = lora->setOutputPower(power, true);
+7
View File
@@ -1038,6 +1038,13 @@ void RadioInterface::applyModemConfig()
saveFreq(freq + loraConfig.frequency_offset);
const char *channelName = channels.getName(channels.getPrimaryIndex());
if (newRegion->wideLora) { // clamp if wide freq range
preambleLength = wideLoraPreambleLengthDefault; // 12 is the default for operation above 2GHz
} else {
preambleLength =
preambleLengthDefault; // 8 is default, but we use longer to increase the amount of sleep time when receiving
}
slotTimeMsec = computeSlotTimeMsec();
preambleTimeMsec = preambleLength * (pow_of_2(sf) / bw);
+13 -8
View File
@@ -92,15 +92,20 @@ class RadioInterface
uint8_t sf = 9;
uint8_t cr = 5;
const uint8_t NUM_SYM_CAD = 2; // Number of symbols used for CAD, 2 is the default since RadioLib 6.3.0 as per AN1200.48
const uint8_t NUM_SYM_CAD_24GHZ = 4; // Number of symbols used for CAD in 2.4 GHz, 4 is recommended in AN1200.22 of SX1280
static constexpr uint8_t NUM_SYM_CAD =
2; // Number of symbols used for CAD, 2 is the default since RadioLib 6.3.0 as per AN1200.48
static constexpr uint8_t NUM_SYM_CAD_24GHZ =
4; // Number of symbols used for CAD in 2.4 GHz, 4 is recommended in AN1200.22 of SX1280
uint32_t slotTimeMsec = computeSlotTimeMsec();
uint16_t preambleLength = 16; // 8 is default, but we use longer to increase the amount of sleep time when receiving
uint32_t preambleTimeMsec = 165; // calculated on startup, this is the default for LongFast
const uint32_t PROCESSING_TIME_MSEC =
4500; // time to construct, process and construct a packet again (empirically determined)
const uint8_t CWmin = 3; // minimum CWsize
const uint8_t CWmax = 8; // maximum CWsize
uint16_t preambleLength = 16; // 8 is default, but we use longer to increase the amount of sleep time when receiving
static constexpr uint16_t preambleLengthDefault =
16; // 8 is default, but we use longer to increase the amount of sleep time when receiving
static constexpr uint16_t wideLoraPreambleLengthDefault = 12; // 12 is default for wide Lora
uint32_t preambleTimeMsec = 165; // calculated on startup, this is the default for LongFast
static constexpr uint32_t PROCESSING_TIME_MSEC =
4500; // time to construct, process and construct a packet again (empirically determined)
static constexpr uint8_t CWmin = 3; // minimum CWsize
static constexpr uint8_t CWmax = 8; // maximum CWsize
meshtastic_MeshPacket *sendingPacket = NULL; // The packet we are currently sending
uint32_t lastTxStart = 0L;
+4 -2
View File
@@ -245,8 +245,10 @@ template <typename T> bool SX126xInterface<T>::reconfigure()
if (err != RADIOLIB_ERR_NONE)
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
if (power > SX126X_MAX_POWER) // This chip has lower power limits than some
power = SX126X_MAX_POWER;
limitPower(SX126X_MAX_POWER);
// Make sure we reach the minimum power supported to turn the chip on (-9dBm)
if (power < -9)
power = -9;
err = lora.setOutputPower(power);
if (err != RADIOLIB_ERR_NONE)
+1 -2
View File
@@ -144,8 +144,7 @@ template <typename T> bool SX128xInterface<T>::reconfigure()
if (err != RADIOLIB_ERR_NONE)
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
if (power > SX128X_MAX_POWER) // This chip has lower power limits than some
power = SX128X_MAX_POWER;
limitPower(SX128X_MAX_POWER);
err = lora.setOutputPower(power);
if (err != RADIOLIB_ERR_NONE)
+7
View File
@@ -104,6 +104,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
@@ -174,6 +178,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
+6 -1
View File
@@ -7,6 +7,7 @@
#include "NodeDB.h"
#include "Router.h"
#include "TypeConversions.h"
#include "airtime.h"
#include "concurrency/LockGuard.h"
#include "configuration.h"
#include "mesh-pb-constants.h"
@@ -1001,7 +1002,11 @@ void TrafficManagementModule::alterReceived(meshtastic_MeshPacket &mp)
const auto &cfg = moduleConfig.traffic_management;
const bool isTelemetry = mp.decoded.portnum == meshtastic_PortNum_TELEMETRY_APP;
const bool isPosition = mp.decoded.portnum == meshtastic_PortNum_POSITION_APP;
const bool shouldExhaust = (isTelemetry && cfg.exhaust_hop_telemetry) || (isPosition && cfg.exhaust_hop_position);
// Only exhaust telemetry hops when channel is actually congested, mirroring the same
// airtime checks that gate self-generated telemetry in the telemetry modules.
const bool channelBusy = airTime && (!airTime->isTxAllowedChannelUtil(true) || !airTime->isTxAllowedAirUtil());
const bool shouldExhaust =
((channelBusy && isTelemetry && cfg.exhaust_hop_telemetry) || (isPosition && cfg.exhaust_hop_position));
if (!shouldExhaust || !isBroadcast(mp.to))
return;
+419
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
+86
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;
+1 -2
View File
@@ -39,7 +39,6 @@ build_flags =
-Wall
-Wextra
-Isrc/platform/esp32
-include mbedtls/error.h
-std=gnu++17
-DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG
-DCORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_DEBUG
@@ -68,7 +67,7 @@ lib_deps =
${environmental_extra.lib_deps}
${radiolib_base.lib_deps}
# renovate: datasource=git-refs depName=meshtastic-esp32_https_server packageName=https://github.com/meshtastic/esp32_https_server gitBranch=master
https://github.com/meshtastic/esp32_https_server/archive/b78f12c86ea65c3ca08968840ff554ff7ed69b60.zip
https://github.com/meshtastic/esp32_https_server/archive/0c71f380390ad483ff134ad938e07f6cf1226c5b.zip
# renovate: datasource=custom.pio depName=NimBLE-Arduino packageName=h2zero/library/NimBLE-Arduino
h2zero/NimBLE-Arduino@1.4.3
# renovate: datasource=git-refs depName=libpax packageName=https://github.com/dbinfrago/libpax gitBranch=master