mirror of
https://github.com/liquidraver/ZephCore.git
synced 2026-10-29 08:23:42 +00:00
normalize source-file line endings to LF
Add .gitattributes rules so .c/.h/.cpp/.hpp are always stored as LF (prevents EOL drift from editors with autocrlf-true defaults), and renormalize the 30 source files that had drifted to CRLF in the index. Pure mechanical change — `git diff --ignore-cr-at-eol` is empty. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
1 parent
28e12c7eef
commit
5e7adfb130
31 files changed
+6788
-6783
No files matched your search
@@ -1,80 +1,80 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
* Adaptive Contention Window — EMA-based flood retransmit delay
|
||||
*
|
||||
* Counts neighbor retransmit dupes within a 10s window per packet.
|
||||
* Dupe counts feed a rolling EMA that drives an adaptive delay factor.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace mesh {
|
||||
|
||||
class Packet;
|
||||
|
||||
class ContentionTracker {
|
||||
public:
|
||||
ContentionTracker();
|
||||
|
||||
/* FNV-1a 32-bit hash for ring buffer correlation (not dedup SHA256). */
|
||||
static uint32_t computePacketHash32(const Packet *pkt);
|
||||
|
||||
void trackRetransmit(uint32_t hash32, uint32_t now_ms);
|
||||
|
||||
/* Returns true if packet matched a tracked retransmit (dupe recorded). */
|
||||
bool recordDupeIfTracked(uint32_t hash32, uint32_t now_ms);
|
||||
|
||||
/* Returns backoff_multiplier * airtime, clamped by remaining headroom.
|
||||
* Returns 0 when hard cap reached or backoff disabled. */
|
||||
uint16_t getReactiveHeadroom(uint32_t hash32, uint32_t airtime_ms) const;
|
||||
|
||||
void addReactiveExtension(uint32_t hash32, uint16_t added_ms);
|
||||
|
||||
/* Finalize expired entries into EMA. */
|
||||
void tick(uint32_t now_ms);
|
||||
|
||||
float getContentionEstimate() const;
|
||||
|
||||
/* Saturating curve: MIN + (MAX-MIN) * est/(est+HALFPOINT).
|
||||
* Returns 0.5 during warmup. */
|
||||
float getFloodDelayFactor() const;
|
||||
|
||||
bool isWarmedUp() const { return _finalized_count >= WARMUP_PACKETS; }
|
||||
|
||||
void setBackoffMultiplier(float m) { _backoff_multiplier = m; }
|
||||
float getBackoffMultiplier() const { return _backoff_multiplier; }
|
||||
|
||||
private:
|
||||
static constexpr int RING_SIZE = 24; /* max concurrent tracked retransmits */
|
||||
static constexpr uint32_t WINDOW_MS = 10000; /* dupe observation window; covers SF12 2-hop */
|
||||
static constexpr int EMA_SHIFT = 3; /* alpha = 1/8 */
|
||||
static constexpr int WARMUP_PACKETS = 4; /* min samples before EMA is trusted */
|
||||
static constexpr float MIN_FLOOD_FACTOR = 0.40f; /* sparse mesh baseline */
|
||||
static constexpr float MAX_FLOOD_FACTOR = 1.00f; /* dense mesh ceiling */
|
||||
static constexpr float FLOOD_EST_HALFPOINT = 4.0f; /* midpoint: factor=0.60 at est=4 */
|
||||
static constexpr float DEFAULT_BACKOFF_MULT = 0.2f; /* airtime*0.2 per dupe heard */
|
||||
static constexpr uint32_t REACTIVE_HARD_CAP_MS = 2000; /* max cumulative reactive extension */
|
||||
static constexpr uint32_t STALE_MS = 300000; /* 5 min: reset EMA if no traffic */
|
||||
|
||||
struct Entry {
|
||||
uint32_t hash32;
|
||||
uint32_t first_seen_ms;
|
||||
uint8_t dupe_count;
|
||||
uint16_t reactive_added_ms;
|
||||
bool active;
|
||||
};
|
||||
|
||||
Entry _ring[RING_SIZE];
|
||||
int _next_idx;
|
||||
uint32_t _ema_x256;
|
||||
int _finalized_count;
|
||||
uint32_t _last_retransmit_ms;
|
||||
float _backoff_multiplier;
|
||||
|
||||
void finalizeEntry(int idx);
|
||||
int findEntry(uint32_t hash32) const;
|
||||
};
|
||||
|
||||
} /* namespace mesh */
|
||||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
* Adaptive Contention Window — EMA-based flood retransmit delay
|
||||
*
|
||||
* Counts neighbor retransmit dupes within a 10s window per packet.
|
||||
* Dupe counts feed a rolling EMA that drives an adaptive delay factor.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace mesh {
|
||||
|
||||
class Packet;
|
||||
|
||||
class ContentionTracker {
|
||||
public:
|
||||
ContentionTracker();
|
||||
|
||||
/* FNV-1a 32-bit hash for ring buffer correlation (not dedup SHA256). */
|
||||
static uint32_t computePacketHash32(const Packet *pkt);
|
||||
|
||||
void trackRetransmit(uint32_t hash32, uint32_t now_ms);
|
||||
|
||||
/* Returns true if packet matched a tracked retransmit (dupe recorded). */
|
||||
bool recordDupeIfTracked(uint32_t hash32, uint32_t now_ms);
|
||||
|
||||
/* Returns backoff_multiplier * airtime, clamped by remaining headroom.
|
||||
* Returns 0 when hard cap reached or backoff disabled. */
|
||||
uint16_t getReactiveHeadroom(uint32_t hash32, uint32_t airtime_ms) const;
|
||||
|
||||
void addReactiveExtension(uint32_t hash32, uint16_t added_ms);
|
||||
|
||||
/* Finalize expired entries into EMA. */
|
||||
void tick(uint32_t now_ms);
|
||||
|
||||
float getContentionEstimate() const;
|
||||
|
||||
/* Saturating curve: MIN + (MAX-MIN) * est/(est+HALFPOINT).
|
||||
* Returns 0.5 during warmup. */
|
||||
float getFloodDelayFactor() const;
|
||||
|
||||
bool isWarmedUp() const { return _finalized_count >= WARMUP_PACKETS; }
|
||||
|
||||
void setBackoffMultiplier(float m) { _backoff_multiplier = m; }
|
||||
float getBackoffMultiplier() const { return _backoff_multiplier; }
|
||||
|
||||
private:
|
||||
static constexpr int RING_SIZE = 24; /* max concurrent tracked retransmits */
|
||||
static constexpr uint32_t WINDOW_MS = 10000; /* dupe observation window; covers SF12 2-hop */
|
||||
static constexpr int EMA_SHIFT = 3; /* alpha = 1/8 */
|
||||
static constexpr int WARMUP_PACKETS = 4; /* min samples before EMA is trusted */
|
||||
static constexpr float MIN_FLOOD_FACTOR = 0.40f; /* sparse mesh baseline */
|
||||
static constexpr float MAX_FLOOD_FACTOR = 1.00f; /* dense mesh ceiling */
|
||||
static constexpr float FLOOD_EST_HALFPOINT = 4.0f; /* midpoint: factor=0.60 at est=4 */
|
||||
static constexpr float DEFAULT_BACKOFF_MULT = 0.2f; /* airtime*0.2 per dupe heard */
|
||||
static constexpr uint32_t REACTIVE_HARD_CAP_MS = 2000; /* max cumulative reactive extension */
|
||||
static constexpr uint32_t STALE_MS = 300000; /* 5 min: reset EMA if no traffic */
|
||||
|
||||
struct Entry {
|
||||
uint32_t hash32;
|
||||
uint32_t first_seen_ms;
|
||||
uint8_t dupe_count;
|
||||
uint16_t reactive_added_ms;
|
||||
bool active;
|
||||
};
|
||||
|
||||
Entry _ring[RING_SIZE];
|
||||
int _next_idx;
|
||||
uint32_t _ema_x256;
|
||||
int _finalized_count;
|
||||
uint32_t _last_retransmit_ms;
|
||||
float _backoff_multiplier;
|
||||
|
||||
void finalizeEntry(int idx);
|
||||
int findEntry(uint32_t hash32) const;
|
||||
};
|
||||
|
||||
} /* namespace mesh */
|
||||
+127
-127
@@ -1,127 +1,127 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
* ZephCore Dispatcher - packet queue and radio scheduling
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <mesh/MeshCore.h>
|
||||
#include <mesh/Identity.h>
|
||||
#include <mesh/Packet.h>
|
||||
#include <mesh/Utils.h>
|
||||
#include <mesh/Radio.h>
|
||||
#include <mesh/Clock.h>
|
||||
#include <string.h>
|
||||
|
||||
namespace mesh {
|
||||
|
||||
class PacketManager {
|
||||
public:
|
||||
virtual Packet *allocNew() = 0;
|
||||
virtual void free(Packet *packet) = 0;
|
||||
virtual void queueOutbound(Packet *packet, uint8_t priority, uint32_t scheduled_for) = 0;
|
||||
virtual Packet *getNextOutbound(uint32_t now) = 0;
|
||||
virtual int getOutboundCount(uint32_t now) const = 0;
|
||||
virtual int getOutboundTotal() const = 0;
|
||||
virtual int getFreeCount() const = 0;
|
||||
virtual Packet *getOutboundByIdx(int i) = 0;
|
||||
virtual Packet *removeOutboundByIdx(int i) = 0;
|
||||
virtual uint32_t getOutboundSchedule(int i) const = 0;
|
||||
virtual bool rescheduleOutbound(int i, uint32_t new_scheduled_for) = 0;
|
||||
virtual void queueInbound(Packet *packet, uint32_t scheduled_for) = 0;
|
||||
virtual Packet *getNextInbound(uint32_t now) = 0;
|
||||
};
|
||||
|
||||
/* Notifies event loop of pending TX so it can schedule a wake. */
|
||||
typedef void (*tx_queued_callback_t)(uint32_t delay_ms, void *user_data);
|
||||
|
||||
typedef uint32_t DispatcherAction;
|
||||
|
||||
#define ACTION_RELEASE (0)
|
||||
#define ACTION_MANUAL_HOLD (1)
|
||||
#define ACTION_RETRANSMIT(pri) (((uint32_t)1 + (pri))<<24)
|
||||
#define ACTION_RETRANSMIT_DELAYED(pri, _delay) ((((uint32_t)1 + (pri))<<24) | (_delay))
|
||||
|
||||
#define ERR_EVENT_FULL (1 << 0)
|
||||
#define ERR_EVENT_CAD_TIMEOUT (1 << 1)
|
||||
#define ERR_EVENT_STARTRX_TIMEOUT (1 << 2)
|
||||
|
||||
class Dispatcher {
|
||||
Packet *outbound;
|
||||
uint32_t outbound_expiry, outbound_start, total_air_time, rx_air_time;
|
||||
uint32_t next_tx_time;
|
||||
uint32_t cad_busy_start;
|
||||
uint32_t tx_budget_ms;
|
||||
uint32_t last_budget_update;
|
||||
uint32_t duty_cycle_window_ms;
|
||||
uint32_t radio_nonrx_start;
|
||||
uint32_t next_agc_reset_time;
|
||||
bool prev_isrecv_mode;
|
||||
uint32_t n_sent_flood, n_sent_direct;
|
||||
uint32_t n_recv_flood, n_recv_direct;
|
||||
tx_queued_callback_t _tx_queued_cb;
|
||||
void *_tx_queued_user_data;
|
||||
|
||||
void processRecvPacket(Packet *pkt);
|
||||
|
||||
protected:
|
||||
Radio *_radio;
|
||||
MillisecondClock *_ms;
|
||||
PacketManager *_mgr;
|
||||
uint16_t _err_flags;
|
||||
|
||||
Dispatcher(Radio &radio, MillisecondClock &ms, PacketManager &mgr);
|
||||
void notifyTxQueued(uint32_t delay_ms) {
|
||||
if (_tx_queued_cb) _tx_queued_cb(delay_ms, _tx_queued_user_data);
|
||||
}
|
||||
virtual DispatcherAction onRecvPacket(Packet *pkt) = 0;
|
||||
virtual void logRxRaw(float snr, float rssi, const uint8_t raw[], int len) { (void)snr; (void)rssi; (void)raw; (void)len; }
|
||||
virtual void logRx(Packet *packet, int len, float score) { (void)packet; (void)len; (void)score; }
|
||||
virtual void logTx(Packet *packet, int len) { (void)packet; (void)len; }
|
||||
virtual void logTxFail(Packet *packet, int len) { (void)packet; (void)len; }
|
||||
virtual const char *getLogDateTime() { return ""; }
|
||||
virtual uint8_t getDutyCyclePercent() const;
|
||||
static bool isAdminPacket(const Packet *pkt);
|
||||
virtual int calcRxDelay(float score, uint32_t air_time) const;
|
||||
virtual uint32_t getCADFailRetryDelay() const;
|
||||
virtual uint32_t getCADFailMaxDuration() const;
|
||||
virtual int getInterferenceThreshold() const { return 0; }
|
||||
virtual int getAGCResetInterval() const { return 0; }
|
||||
virtual uint32_t getDutyCycleWindowMs() const { return 3600000UL; } /* 1h default */
|
||||
|
||||
public:
|
||||
void begin();
|
||||
void loop();
|
||||
void maintenanceLoop();
|
||||
Packet *obtainNewPacket();
|
||||
void releasePacket(Packet *packet);
|
||||
void sendPacket(Packet *packet, uint8_t priority, uint32_t delay_millis = 0);
|
||||
|
||||
uint32_t getTotalAirTime() const { return total_air_time; }
|
||||
uint32_t getReceiveAirTime() const { return rx_air_time; }
|
||||
uint32_t getNumSentFlood() const { return n_sent_flood; }
|
||||
uint32_t getNumSentDirect() const { return n_sent_direct; }
|
||||
uint32_t getNumRecvFlood() const { return n_recv_flood; }
|
||||
uint32_t getNumRecvDirect() const { return n_recv_direct; }
|
||||
uint16_t getErrFlags() const { return _err_flags; }
|
||||
void resetStats() {
|
||||
n_sent_flood = n_sent_direct = 0;
|
||||
n_recv_flood = n_recv_direct = 0;
|
||||
_err_flags = 0;
|
||||
}
|
||||
void setTxQueuedCallback(tx_queued_callback_t cb, void *user_data) {
|
||||
_tx_queued_cb = cb;
|
||||
_tx_queued_user_data = user_data;
|
||||
}
|
||||
bool millisHasNowPassed(uint32_t timestamp) const;
|
||||
uint32_t futureMillis(int millis_from_now) const;
|
||||
|
||||
private:
|
||||
void updateTxBudget();
|
||||
uint32_t getMaxTxBudgetMs() const;
|
||||
bool tryParsePacket(Packet *pkt, const uint8_t *raw, int len);
|
||||
void checkRecv();
|
||||
void checkSend();
|
||||
};
|
||||
|
||||
} /* namespace mesh */
|
||||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
* ZephCore Dispatcher - packet queue and radio scheduling
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <mesh/MeshCore.h>
|
||||
#include <mesh/Identity.h>
|
||||
#include <mesh/Packet.h>
|
||||
#include <mesh/Utils.h>
|
||||
#include <mesh/Radio.h>
|
||||
#include <mesh/Clock.h>
|
||||
#include <string.h>
|
||||
|
||||
namespace mesh {
|
||||
|
||||
class PacketManager {
|
||||
public:
|
||||
virtual Packet *allocNew() = 0;
|
||||
virtual void free(Packet *packet) = 0;
|
||||
virtual void queueOutbound(Packet *packet, uint8_t priority, uint32_t scheduled_for) = 0;
|
||||
virtual Packet *getNextOutbound(uint32_t now) = 0;
|
||||
virtual int getOutboundCount(uint32_t now) const = 0;
|
||||
virtual int getOutboundTotal() const = 0;
|
||||
virtual int getFreeCount() const = 0;
|
||||
virtual Packet *getOutboundByIdx(int i) = 0;
|
||||
virtual Packet *removeOutboundByIdx(int i) = 0;
|
||||
virtual uint32_t getOutboundSchedule(int i) const = 0;
|
||||
virtual bool rescheduleOutbound(int i, uint32_t new_scheduled_for) = 0;
|
||||
virtual void queueInbound(Packet *packet, uint32_t scheduled_for) = 0;
|
||||
virtual Packet *getNextInbound(uint32_t now) = 0;
|
||||
};
|
||||
|
||||
/* Notifies event loop of pending TX so it can schedule a wake. */
|
||||
typedef void (*tx_queued_callback_t)(uint32_t delay_ms, void *user_data);
|
||||
|
||||
typedef uint32_t DispatcherAction;
|
||||
|
||||
#define ACTION_RELEASE (0)
|
||||
#define ACTION_MANUAL_HOLD (1)
|
||||
#define ACTION_RETRANSMIT(pri) (((uint32_t)1 + (pri))<<24)
|
||||
#define ACTION_RETRANSMIT_DELAYED(pri, _delay) ((((uint32_t)1 + (pri))<<24) | (_delay))
|
||||
|
||||
#define ERR_EVENT_FULL (1 << 0)
|
||||
#define ERR_EVENT_CAD_TIMEOUT (1 << 1)
|
||||
#define ERR_EVENT_STARTRX_TIMEOUT (1 << 2)
|
||||
|
||||
class Dispatcher {
|
||||
Packet *outbound;
|
||||
uint32_t outbound_expiry, outbound_start, total_air_time, rx_air_time;
|
||||
uint32_t next_tx_time;
|
||||
uint32_t cad_busy_start;
|
||||
uint32_t tx_budget_ms;
|
||||
uint32_t last_budget_update;
|
||||
uint32_t duty_cycle_window_ms;
|
||||
uint32_t radio_nonrx_start;
|
||||
uint32_t next_agc_reset_time;
|
||||
bool prev_isrecv_mode;
|
||||
uint32_t n_sent_flood, n_sent_direct;
|
||||
uint32_t n_recv_flood, n_recv_direct;
|
||||
tx_queued_callback_t _tx_queued_cb;
|
||||
void *_tx_queued_user_data;
|
||||
|
||||
void processRecvPacket(Packet *pkt);
|
||||
|
||||
protected:
|
||||
Radio *_radio;
|
||||
MillisecondClock *_ms;
|
||||
PacketManager *_mgr;
|
||||
uint16_t _err_flags;
|
||||
|
||||
Dispatcher(Radio &radio, MillisecondClock &ms, PacketManager &mgr);
|
||||
void notifyTxQueued(uint32_t delay_ms) {
|
||||
if (_tx_queued_cb) _tx_queued_cb(delay_ms, _tx_queued_user_data);
|
||||
}
|
||||
virtual DispatcherAction onRecvPacket(Packet *pkt) = 0;
|
||||
virtual void logRxRaw(float snr, float rssi, const uint8_t raw[], int len) { (void)snr; (void)rssi; (void)raw; (void)len; }
|
||||
virtual void logRx(Packet *packet, int len, float score) { (void)packet; (void)len; (void)score; }
|
||||
virtual void logTx(Packet *packet, int len) { (void)packet; (void)len; }
|
||||
virtual void logTxFail(Packet *packet, int len) { (void)packet; (void)len; }
|
||||
virtual const char *getLogDateTime() { return ""; }
|
||||
virtual uint8_t getDutyCyclePercent() const;
|
||||
static bool isAdminPacket(const Packet *pkt);
|
||||
virtual int calcRxDelay(float score, uint32_t air_time) const;
|
||||
virtual uint32_t getCADFailRetryDelay() const;
|
||||
virtual uint32_t getCADFailMaxDuration() const;
|
||||
virtual int getInterferenceThreshold() const { return 0; }
|
||||
virtual int getAGCResetInterval() const { return 0; }
|
||||
virtual uint32_t getDutyCycleWindowMs() const { return 3600000UL; } /* 1h default */
|
||||
|
||||
public:
|
||||
void begin();
|
||||
void loop();
|
||||
void maintenanceLoop();
|
||||
Packet *obtainNewPacket();
|
||||
void releasePacket(Packet *packet);
|
||||
void sendPacket(Packet *packet, uint8_t priority, uint32_t delay_millis = 0);
|
||||
|
||||
uint32_t getTotalAirTime() const { return total_air_time; }
|
||||
uint32_t getReceiveAirTime() const { return rx_air_time; }
|
||||
uint32_t getNumSentFlood() const { return n_sent_flood; }
|
||||
uint32_t getNumSentDirect() const { return n_sent_direct; }
|
||||
uint32_t getNumRecvFlood() const { return n_recv_flood; }
|
||||
uint32_t getNumRecvDirect() const { return n_recv_direct; }
|
||||
uint16_t getErrFlags() const { return _err_flags; }
|
||||
void resetStats() {
|
||||
n_sent_flood = n_sent_direct = 0;
|
||||
n_recv_flood = n_recv_direct = 0;
|
||||
_err_flags = 0;
|
||||
}
|
||||
void setTxQueuedCallback(tx_queued_callback_t cb, void *user_data) {
|
||||
_tx_queued_cb = cb;
|
||||
_tx_queued_user_data = user_data;
|
||||
}
|
||||
bool millisHasNowPassed(uint32_t timestamp) const;
|
||||
uint32_t futureMillis(int millis_from_now) const;
|
||||
|
||||
private:
|
||||
void updateTxBudget();
|
||||
uint32_t getMaxTxBudgetMs() const;
|
||||
bool tryParsePacket(Packet *pkt, const uint8_t *raw, int len);
|
||||
void checkRecv();
|
||||
void checkSend();
|
||||
};
|
||||
|
||||
} /* namespace mesh */
|
||||
+107
-107
@@ -1,107 +1,107 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
* ZephCore Mesh - routing protocol layer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <mesh/Dispatcher.h>
|
||||
#include <mesh/ContentionTracker.h>
|
||||
#ifdef CONFIG_ZEPHCORE_APC
|
||||
#include <mesh/PowerController.h>
|
||||
#endif
|
||||
#include <mesh/RTC.h>
|
||||
|
||||
namespace mesh {
|
||||
|
||||
struct GroupChannel {
|
||||
uint8_t hash[PATH_HASH_SIZE];
|
||||
uint8_t secret[PUB_KEY_SIZE];
|
||||
};
|
||||
|
||||
class MeshTables {
|
||||
public:
|
||||
virtual bool hasSeen(const Packet *packet) = 0;
|
||||
virtual void clear(const Packet *packet) = 0;
|
||||
};
|
||||
|
||||
class Mesh : public Dispatcher {
|
||||
RNG *_rng;
|
||||
RTCClock *_rtc;
|
||||
MeshTables *_tables;
|
||||
|
||||
void removeSelfFromPath(Packet *packet);
|
||||
void routeDirectRecvAcks(Packet *packet, uint32_t delay_millis);
|
||||
DispatcherAction forwardMultipartDirect(Packet *pkt);
|
||||
|
||||
protected:
|
||||
ContentionTracker _contention;
|
||||
ContentionTracker& getContentionTracker() { return _contention; }
|
||||
const ContentionTracker& getContentionTracker() const { return _contention; }
|
||||
#ifdef CONFIG_ZEPHCORE_APC
|
||||
PowerController _power_ctrl;
|
||||
PowerController& getPowerController() { return _power_ctrl; }
|
||||
const PowerController& getPowerController() const { return _power_ctrl; }
|
||||
#endif
|
||||
void extendPendingRetransmit(uint32_t hash32);
|
||||
|
||||
DispatcherAction onRecvPacket(Packet *pkt) override;
|
||||
virtual uint32_t getCADFailRetryDelay() const override;
|
||||
virtual DispatcherAction routeRecvPacket(Packet *packet);
|
||||
virtual bool filterRecvFloodPacket(Packet *packet) { return false; }
|
||||
virtual bool allowPacketForward(const Packet *packet);
|
||||
virtual uint32_t getRetransmitDelay(const Packet *packet);
|
||||
virtual uint32_t getDirectRetransmitDelay(const Packet *packet) { return 0; }
|
||||
/* Passive contention tracking: if true, track heard floods we don't forward
|
||||
* (warms the contention EMA on nodes that don't relay, e.g. companions). */
|
||||
virtual bool passivelyTrackFloods() const { return false; }
|
||||
/* Added to caller-supplied delay on every sendFlood. Default 0 (repeater
|
||||
* behavior). Companion overrides to spread its initial TX adaptively. */
|
||||
virtual uint32_t getInitialFloodJitter(const Packet *packet) { (void)packet; return 0; }
|
||||
virtual uint8_t getExtraAckTransmitCount() const { return 0; }
|
||||
virtual int searchPeersByHash(const uint8_t *hash) { (void)hash; return 0; }
|
||||
virtual void getPeerSharedSecret(uint8_t *dest_secret, int peer_idx) { (void)dest_secret; (void)peer_idx; }
|
||||
virtual void onPeerDataRecv(Packet *packet, uint8_t type, int sender_idx, const uint8_t *secret, uint8_t *data, size_t len) { (void)packet; (void)type; (void)sender_idx; (void)secret; (void)data; (void)len; }
|
||||
virtual void onTraceRecv(Packet *packet, uint32_t tag, uint32_t auth_code, uint8_t flags, const uint8_t *path_snrs, const uint8_t *path_hashes, uint8_t path_len) { (void)packet; (void)tag; (void)auth_code; (void)flags; (void)path_snrs; (void)path_hashes; (void)path_len; }
|
||||
virtual bool onPeerPathRecv(Packet *packet, int sender_idx, const uint8_t *secret, uint8_t *path, uint8_t path_len, uint8_t extra_type, uint8_t *extra, uint8_t extra_len) { (void)packet; (void)sender_idx; (void)secret; (void)path; (void)path_len; (void)extra_type; (void)extra; (void)extra_len; return false; }
|
||||
virtual void onAdvertRecv(Packet *packet, const Identity &id, uint32_t timestamp, const uint8_t *app_data, size_t app_data_len) { (void)packet; (void)id; (void)timestamp; (void)app_data; (void)app_data_len; }
|
||||
virtual void onAnonDataRecv(Packet *packet, const uint8_t *secret, const Identity &sender, uint8_t *data, size_t len) { (void)packet; (void)secret; (void)sender; (void)data; (void)len; }
|
||||
virtual void onPathRecv(Packet *packet, Identity &sender, uint8_t *path, uint8_t path_len, uint8_t extra_type, uint8_t *extra, uint8_t extra_len) { (void)packet; (void)sender; (void)path; (void)path_len; (void)extra_type; (void)extra; (void)extra_len; }
|
||||
virtual void onControlDataRecv(Packet *packet) { (void)packet; }
|
||||
virtual void onRawDataRecv(Packet *packet) { (void)packet; }
|
||||
virtual int searchChannelsByHash(const uint8_t *hash, GroupChannel channels[], int max_matches) { (void)hash; (void)channels; (void)max_matches; return 0; }
|
||||
virtual void onGroupDataRecv(Packet *packet, uint8_t type, const GroupChannel &channel, uint8_t *data, size_t len) { (void)packet; (void)type; (void)channel; (void)data; (void)len; }
|
||||
virtual void onAckRecv(Packet *packet, uint32_t ack_crc) { (void)packet; (void)ack_crc; }
|
||||
|
||||
public:
|
||||
Mesh(Radio &radio, MillisecondClock &ms, RNG &rng, RTCClock &rtc, PacketManager &mgr, MeshTables &tables);
|
||||
void begin();
|
||||
void loop();
|
||||
void maintenanceLoop();
|
||||
|
||||
LocalIdentity self_id;
|
||||
|
||||
RNG *getRNG() const { return _rng; }
|
||||
RTCClock *getRTCClock() const { return _rtc; }
|
||||
MeshTables *getTables() const { return _tables; }
|
||||
|
||||
Packet *createAdvert(const LocalIdentity &id, const uint8_t *app_data = nullptr, size_t app_data_len = 0);
|
||||
Packet *createAck(uint32_t ack_crc);
|
||||
Packet *createMultiAck(uint32_t ack_crc, uint8_t remaining);
|
||||
Packet *createControlData(const uint8_t *data, size_t len);
|
||||
Packet *createDatagram(uint8_t type, const Identity &dest, const uint8_t *secret, const uint8_t *data, size_t len);
|
||||
Packet *createAnonDatagram(uint8_t type, const LocalIdentity &sender, const Identity &dest, const uint8_t *secret, const uint8_t *data, size_t data_len);
|
||||
Packet *createGroupDatagram(uint8_t type, const GroupChannel &channel, const uint8_t *data, size_t data_len);
|
||||
Packet *createPathReturn(const Identity &dest, const uint8_t *secret, const uint8_t *path, uint8_t path_len, uint8_t extra_type, const uint8_t *extra, size_t extra_len);
|
||||
Packet *createPathReturn(const uint8_t *dest_hash, const uint8_t *secret, const uint8_t *path, uint8_t path_len, uint8_t extra_type, const uint8_t *extra, size_t extra_len);
|
||||
Packet *createRawData(const uint8_t *data, size_t len);
|
||||
Packet *createTrace(uint32_t tag, uint32_t auth_code, uint8_t flags = 0);
|
||||
|
||||
void sendFlood(Packet *packet, uint32_t delay_millis = 0, uint8_t path_hash_size = 1);
|
||||
void sendFlood(Packet *packet, uint16_t *transport_codes, uint32_t delay_millis = 0, uint8_t path_hash_size = 1);
|
||||
void sendDirect(Packet *packet, const uint8_t *path, uint8_t path_len, uint32_t delay_millis = 0);
|
||||
void sendZeroHop(Packet *packet, uint32_t delay_millis = 0);
|
||||
void sendZeroHop(Packet *packet, uint16_t *transport_codes, uint32_t delay_millis = 0);
|
||||
};
|
||||
|
||||
} /* namespace mesh */
|
||||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
* ZephCore Mesh - routing protocol layer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <mesh/Dispatcher.h>
|
||||
#include <mesh/ContentionTracker.h>
|
||||
#ifdef CONFIG_ZEPHCORE_APC
|
||||
#include <mesh/PowerController.h>
|
||||
#endif
|
||||
#include <mesh/RTC.h>
|
||||
|
||||
namespace mesh {
|
||||
|
||||
struct GroupChannel {
|
||||
uint8_t hash[PATH_HASH_SIZE];
|
||||
uint8_t secret[PUB_KEY_SIZE];
|
||||
};
|
||||
|
||||
class MeshTables {
|
||||
public:
|
||||
virtual bool hasSeen(const Packet *packet) = 0;
|
||||
virtual void clear(const Packet *packet) = 0;
|
||||
};
|
||||
|
||||
class Mesh : public Dispatcher {
|
||||
RNG *_rng;
|
||||
RTCClock *_rtc;
|
||||
MeshTables *_tables;
|
||||
|
||||
void removeSelfFromPath(Packet *packet);
|
||||
void routeDirectRecvAcks(Packet *packet, uint32_t delay_millis);
|
||||
DispatcherAction forwardMultipartDirect(Packet *pkt);
|
||||
|
||||
protected:
|
||||
ContentionTracker _contention;
|
||||
ContentionTracker& getContentionTracker() { return _contention; }
|
||||
const ContentionTracker& getContentionTracker() const { return _contention; }
|
||||
#ifdef CONFIG_ZEPHCORE_APC
|
||||
PowerController _power_ctrl;
|
||||
PowerController& getPowerController() { return _power_ctrl; }
|
||||
const PowerController& getPowerController() const { return _power_ctrl; }
|
||||
#endif
|
||||
void extendPendingRetransmit(uint32_t hash32);
|
||||
|
||||
DispatcherAction onRecvPacket(Packet *pkt) override;
|
||||
virtual uint32_t getCADFailRetryDelay() const override;
|
||||
virtual DispatcherAction routeRecvPacket(Packet *packet);
|
||||
virtual bool filterRecvFloodPacket(Packet *packet) { return false; }
|
||||
virtual bool allowPacketForward(const Packet *packet);
|
||||
virtual uint32_t getRetransmitDelay(const Packet *packet);
|
||||
virtual uint32_t getDirectRetransmitDelay(const Packet *packet) { return 0; }
|
||||
/* Passive contention tracking: if true, track heard floods we don't forward
|
||||
* (warms the contention EMA on nodes that don't relay, e.g. companions). */
|
||||
virtual bool passivelyTrackFloods() const { return false; }
|
||||
/* Added to caller-supplied delay on every sendFlood. Default 0 (repeater
|
||||
* behavior). Companion overrides to spread its initial TX adaptively. */
|
||||
virtual uint32_t getInitialFloodJitter(const Packet *packet) { (void)packet; return 0; }
|
||||
virtual uint8_t getExtraAckTransmitCount() const { return 0; }
|
||||
virtual int searchPeersByHash(const uint8_t *hash) { (void)hash; return 0; }
|
||||
virtual void getPeerSharedSecret(uint8_t *dest_secret, int peer_idx) { (void)dest_secret; (void)peer_idx; }
|
||||
virtual void onPeerDataRecv(Packet *packet, uint8_t type, int sender_idx, const uint8_t *secret, uint8_t *data, size_t len) { (void)packet; (void)type; (void)sender_idx; (void)secret; (void)data; (void)len; }
|
||||
virtual void onTraceRecv(Packet *packet, uint32_t tag, uint32_t auth_code, uint8_t flags, const uint8_t *path_snrs, const uint8_t *path_hashes, uint8_t path_len) { (void)packet; (void)tag; (void)auth_code; (void)flags; (void)path_snrs; (void)path_hashes; (void)path_len; }
|
||||
virtual bool onPeerPathRecv(Packet *packet, int sender_idx, const uint8_t *secret, uint8_t *path, uint8_t path_len, uint8_t extra_type, uint8_t *extra, uint8_t extra_len) { (void)packet; (void)sender_idx; (void)secret; (void)path; (void)path_len; (void)extra_type; (void)extra; (void)extra_len; return false; }
|
||||
virtual void onAdvertRecv(Packet *packet, const Identity &id, uint32_t timestamp, const uint8_t *app_data, size_t app_data_len) { (void)packet; (void)id; (void)timestamp; (void)app_data; (void)app_data_len; }
|
||||
virtual void onAnonDataRecv(Packet *packet, const uint8_t *secret, const Identity &sender, uint8_t *data, size_t len) { (void)packet; (void)secret; (void)sender; (void)data; (void)len; }
|
||||
virtual void onPathRecv(Packet *packet, Identity &sender, uint8_t *path, uint8_t path_len, uint8_t extra_type, uint8_t *extra, uint8_t extra_len) { (void)packet; (void)sender; (void)path; (void)path_len; (void)extra_type; (void)extra; (void)extra_len; }
|
||||
virtual void onControlDataRecv(Packet *packet) { (void)packet; }
|
||||
virtual void onRawDataRecv(Packet *packet) { (void)packet; }
|
||||
virtual int searchChannelsByHash(const uint8_t *hash, GroupChannel channels[], int max_matches) { (void)hash; (void)channels; (void)max_matches; return 0; }
|
||||
virtual void onGroupDataRecv(Packet *packet, uint8_t type, const GroupChannel &channel, uint8_t *data, size_t len) { (void)packet; (void)type; (void)channel; (void)data; (void)len; }
|
||||
virtual void onAckRecv(Packet *packet, uint32_t ack_crc) { (void)packet; (void)ack_crc; }
|
||||
|
||||
public:
|
||||
Mesh(Radio &radio, MillisecondClock &ms, RNG &rng, RTCClock &rtc, PacketManager &mgr, MeshTables &tables);
|
||||
void begin();
|
||||
void loop();
|
||||
void maintenanceLoop();
|
||||
|
||||
LocalIdentity self_id;
|
||||
|
||||
RNG *getRNG() const { return _rng; }
|
||||
RTCClock *getRTCClock() const { return _rtc; }
|
||||
MeshTables *getTables() const { return _tables; }
|
||||
|
||||
Packet *createAdvert(const LocalIdentity &id, const uint8_t *app_data = nullptr, size_t app_data_len = 0);
|
||||
Packet *createAck(uint32_t ack_crc);
|
||||
Packet *createMultiAck(uint32_t ack_crc, uint8_t remaining);
|
||||
Packet *createControlData(const uint8_t *data, size_t len);
|
||||
Packet *createDatagram(uint8_t type, const Identity &dest, const uint8_t *secret, const uint8_t *data, size_t len);
|
||||
Packet *createAnonDatagram(uint8_t type, const LocalIdentity &sender, const Identity &dest, const uint8_t *secret, const uint8_t *data, size_t data_len);
|
||||
Packet *createGroupDatagram(uint8_t type, const GroupChannel &channel, const uint8_t *data, size_t data_len);
|
||||
Packet *createPathReturn(const Identity &dest, const uint8_t *secret, const uint8_t *path, uint8_t path_len, uint8_t extra_type, const uint8_t *extra, size_t extra_len);
|
||||
Packet *createPathReturn(const uint8_t *dest_hash, const uint8_t *secret, const uint8_t *path, uint8_t path_len, uint8_t extra_type, const uint8_t *extra, size_t extra_len);
|
||||
Packet *createRawData(const uint8_t *data, size_t len);
|
||||
Packet *createTrace(uint32_t tag, uint32_t auth_code, uint8_t flags = 0);
|
||||
|
||||
void sendFlood(Packet *packet, uint32_t delay_millis = 0, uint8_t path_hash_size = 1);
|
||||
void sendFlood(Packet *packet, uint16_t *transport_codes, uint32_t delay_millis = 0, uint8_t path_hash_size = 1);
|
||||
void sendDirect(Packet *packet, const uint8_t *path, uint8_t path_len, uint32_t delay_millis = 0);
|
||||
void sendZeroHop(Packet *packet, uint32_t delay_millis = 0);
|
||||
void sendZeroHop(Packet *packet, uint16_t *transport_codes, uint32_t delay_millis = 0);
|
||||
};
|
||||
|
||||
} /* namespace mesh */
|
||||
@@ -1,113 +1,113 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
* Adaptive Power Control (APC) — echo-based TX power reduction
|
||||
*
|
||||
* Measures link margin by tracking echo packets (flood dupes of
|
||||
* packets we sent or retransmitted, heard back from neighbors).
|
||||
* Feeds per-packet margins into a rolling EMA to produce an
|
||||
* adaptive TX power reduction in dBm.
|
||||
*
|
||||
* Rogue filtering: when 2+ distinct neighbors echo the same packet,
|
||||
* clusters their SNRs within 6 dB of the best. An isolated high
|
||||
* outlier (rogue, badly placed neighbor) is dropped.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace mesh {
|
||||
|
||||
class Packet;
|
||||
|
||||
class PowerController {
|
||||
public:
|
||||
PowerController();
|
||||
|
||||
/* Enable/disable APC. When disabled, APC tracking/math is bypassed. */
|
||||
void setEnabled(bool en);
|
||||
bool isEnabled() const { return _enabled; }
|
||||
|
||||
/* Set current spreading factor (needed for margin calculation). */
|
||||
void setSF(uint8_t sf) { _sf = sf; }
|
||||
|
||||
/* Set target link margin in dB. Higher = more conservative
|
||||
* (better for networks with poor-RX hardware). Default 16 dB. */
|
||||
void setTargetMargin(uint8_t margin_db) { _target_margin_x4 = (int)margin_db * 4; }
|
||||
uint8_t getTargetMargin() const { return (uint8_t)(_target_margin_x4 / 4); }
|
||||
|
||||
/* Called when we send or retransmit a flood packet. */
|
||||
void trackTransmit(uint32_t hash32, uint32_t now_ms);
|
||||
|
||||
/* Called for every received flood dupe. Updates per-source best
|
||||
* SNR and source diversity. Returns true if the dupe matched a
|
||||
* tracked transmit. */
|
||||
bool recordEcho(uint32_t hash32, int8_t snr_x4,
|
||||
uint8_t first_hop_hash, uint32_t now_ms);
|
||||
|
||||
/* Finalize expired entries, update EMA, adjust power, handle
|
||||
* staleness. Call from maintenanceLoop (~5 s). */
|
||||
void tick(uint32_t now_ms);
|
||||
|
||||
/* Current TX power reduction in dBm (0 to MAX_REDUCTION_DB).
|
||||
* Returns 0 when disabled. */
|
||||
int8_t getPowerReduction() const { return _enabled ? _power_reduction_db : 0; }
|
||||
|
||||
/* Current margin estimate in dB (for diagnostics). */
|
||||
float getMarginEstimate() const;
|
||||
|
||||
/* Source count from most recently finalized entry (diagnostics). */
|
||||
uint8_t getLastSourceCount() const { return _last_source_count; }
|
||||
|
||||
bool isWarmedUp() const { return _finalized_count >= WARMUP_COUNT; }
|
||||
bool isStale(uint32_t now_ms) const;
|
||||
|
||||
private:
|
||||
static constexpr int RING_SIZE = 16;
|
||||
static constexpr uint32_t ECHO_WINDOW_MS = 10000; /* 10s: covers SF12 2-hop echo */
|
||||
static constexpr uint32_t STALE_MS = 120000; /* 2 min */
|
||||
static constexpr int EMA_SHIFT = 2; /* alpha = 1/4 */
|
||||
static constexpr int WARMUP_COUNT = 3;
|
||||
static constexpr int MAX_SOURCES = 3;
|
||||
static constexpr int8_t STEP_DOWN_DB = 3;
|
||||
static constexpr int8_t STEP_UP_DB = 6;
|
||||
static constexpr int8_t MAX_REDUCTION_DB = 12;
|
||||
static constexpr int8_t MIN_TX_POWER_DBM = -9; /* SX1262 hw min */
|
||||
static constexpr int CLUSTER_WIDTH_X4 = 24; /* 6 dB in x4 */
|
||||
static constexpr int DEFAULT_TARGET_MARGIN_X4 = 64; /* 16 dB * 4 */
|
||||
static constexpr int HYSTERESIS_X4 = 4; /* 1 dB * 4 */
|
||||
|
||||
struct Source {
|
||||
uint8_t hash;
|
||||
int8_t snr_x4;
|
||||
};
|
||||
|
||||
struct EchoEntry {
|
||||
uint32_t hash32;
|
||||
uint32_t timestamp_ms;
|
||||
uint8_t source_count;
|
||||
uint8_t sf_at_track; /* SF when packet was transmitted */
|
||||
Source sources[MAX_SOURCES];
|
||||
bool active;
|
||||
};
|
||||
|
||||
EchoEntry _ring[RING_SIZE];
|
||||
int _next_idx;
|
||||
int32_t _margin_ema_x256; /* fixed-point EMA (x4 * 256) */
|
||||
int _finalized_count;
|
||||
uint32_t _last_echo_ms;
|
||||
int8_t _power_reduction_db;
|
||||
bool _enabled;
|
||||
uint8_t _sf;
|
||||
uint8_t _last_source_count;
|
||||
int _target_margin_x4;
|
||||
|
||||
void finalizeEntry(int idx);
|
||||
int findEntry(uint32_t hash32) const;
|
||||
int8_t computeRobustSNR(const EchoEntry &entry) const;
|
||||
|
||||
/* SNR threshold for a given SF (x4 fixed point). */
|
||||
static int8_t sfThresholdX4(uint8_t sf);
|
||||
};
|
||||
|
||||
} /* namespace mesh */
|
||||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
* Adaptive Power Control (APC) — echo-based TX power reduction
|
||||
*
|
||||
* Measures link margin by tracking echo packets (flood dupes of
|
||||
* packets we sent or retransmitted, heard back from neighbors).
|
||||
* Feeds per-packet margins into a rolling EMA to produce an
|
||||
* adaptive TX power reduction in dBm.
|
||||
*
|
||||
* Rogue filtering: when 2+ distinct neighbors echo the same packet,
|
||||
* clusters their SNRs within 6 dB of the best. An isolated high
|
||||
* outlier (rogue, badly placed neighbor) is dropped.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace mesh {
|
||||
|
||||
class Packet;
|
||||
|
||||
class PowerController {
|
||||
public:
|
||||
PowerController();
|
||||
|
||||
/* Enable/disable APC. When disabled, APC tracking/math is bypassed. */
|
||||
void setEnabled(bool en);
|
||||
bool isEnabled() const { return _enabled; }
|
||||
|
||||
/* Set current spreading factor (needed for margin calculation). */
|
||||
void setSF(uint8_t sf) { _sf = sf; }
|
||||
|
||||
/* Set target link margin in dB. Higher = more conservative
|
||||
* (better for networks with poor-RX hardware). Default 16 dB. */
|
||||
void setTargetMargin(uint8_t margin_db) { _target_margin_x4 = (int)margin_db * 4; }
|
||||
uint8_t getTargetMargin() const { return (uint8_t)(_target_margin_x4 / 4); }
|
||||
|
||||
/* Called when we send or retransmit a flood packet. */
|
||||
void trackTransmit(uint32_t hash32, uint32_t now_ms);
|
||||
|
||||
/* Called for every received flood dupe. Updates per-source best
|
||||
* SNR and source diversity. Returns true if the dupe matched a
|
||||
* tracked transmit. */
|
||||
bool recordEcho(uint32_t hash32, int8_t snr_x4,
|
||||
uint8_t first_hop_hash, uint32_t now_ms);
|
||||
|
||||
/* Finalize expired entries, update EMA, adjust power, handle
|
||||
* staleness. Call from maintenanceLoop (~5 s). */
|
||||
void tick(uint32_t now_ms);
|
||||
|
||||
/* Current TX power reduction in dBm (0 to MAX_REDUCTION_DB).
|
||||
* Returns 0 when disabled. */
|
||||
int8_t getPowerReduction() const { return _enabled ? _power_reduction_db : 0; }
|
||||
|
||||
/* Current margin estimate in dB (for diagnostics). */
|
||||
float getMarginEstimate() const;
|
||||
|
||||
/* Source count from most recently finalized entry (diagnostics). */
|
||||
uint8_t getLastSourceCount() const { return _last_source_count; }
|
||||
|
||||
bool isWarmedUp() const { return _finalized_count >= WARMUP_COUNT; }
|
||||
bool isStale(uint32_t now_ms) const;
|
||||
|
||||
private:
|
||||
static constexpr int RING_SIZE = 16;
|
||||
static constexpr uint32_t ECHO_WINDOW_MS = 10000; /* 10s: covers SF12 2-hop echo */
|
||||
static constexpr uint32_t STALE_MS = 120000; /* 2 min */
|
||||
static constexpr int EMA_SHIFT = 2; /* alpha = 1/4 */
|
||||
static constexpr int WARMUP_COUNT = 3;
|
||||
static constexpr int MAX_SOURCES = 3;
|
||||
static constexpr int8_t STEP_DOWN_DB = 3;
|
||||
static constexpr int8_t STEP_UP_DB = 6;
|
||||
static constexpr int8_t MAX_REDUCTION_DB = 12;
|
||||
static constexpr int8_t MIN_TX_POWER_DBM = -9; /* SX1262 hw min */
|
||||
static constexpr int CLUSTER_WIDTH_X4 = 24; /* 6 dB in x4 */
|
||||
static constexpr int DEFAULT_TARGET_MARGIN_X4 = 64; /* 16 dB * 4 */
|
||||
static constexpr int HYSTERESIS_X4 = 4; /* 1 dB * 4 */
|
||||
|
||||
struct Source {
|
||||
uint8_t hash;
|
||||
int8_t snr_x4;
|
||||
};
|
||||
|
||||
struct EchoEntry {
|
||||
uint32_t hash32;
|
||||
uint32_t timestamp_ms;
|
||||
uint8_t source_count;
|
||||
uint8_t sf_at_track; /* SF when packet was transmitted */
|
||||
Source sources[MAX_SOURCES];
|
||||
bool active;
|
||||
};
|
||||
|
||||
EchoEntry _ring[RING_SIZE];
|
||||
int _next_idx;
|
||||
int32_t _margin_ema_x256; /* fixed-point EMA (x4 * 256) */
|
||||
int _finalized_count;
|
||||
uint32_t _last_echo_ms;
|
||||
int8_t _power_reduction_db;
|
||||
bool _enabled;
|
||||
uint8_t _sf;
|
||||
uint8_t _last_source_count;
|
||||
int _target_margin_x4;
|
||||
|
||||
void finalizeEntry(int idx);
|
||||
int findEntry(uint32_t hash32) const;
|
||||
int8_t computeRobustSNR(const EchoEntry &entry) const;
|
||||
|
||||
/* SNR threshold for a given SF (x4 fixed point). */
|
||||
static int8_t sfThresholdX4(uint8_t sf);
|
||||
};
|
||||
|
||||
} /* namespace mesh */
|
||||
@@ -1,43 +1,43 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
* ZephCore Radio interface - matches Dispatcher.h
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace mesh {
|
||||
|
||||
class Radio {
|
||||
public:
|
||||
virtual void begin() {}
|
||||
|
||||
virtual int recvRaw(uint8_t *bytes, int sz) = 0;
|
||||
virtual uint32_t getEstAirtimeFor(int len_bytes) = 0;
|
||||
virtual float packetScore(float snr, int packet_len) = 0;
|
||||
virtual bool startSendRaw(const uint8_t *bytes, int len) = 0;
|
||||
virtual bool isSendComplete() = 0;
|
||||
virtual void onSendFinished() = 0;
|
||||
|
||||
virtual int getNoiseFloor() const { return 0; }
|
||||
virtual void triggerNoiseFloorCalibrate(int threshold) { (void)threshold; }
|
||||
virtual void resetAGC() {}
|
||||
|
||||
virtual bool isInRecvMode() const = 0;
|
||||
virtual bool isReceiving() { return false; }
|
||||
virtual bool isRadioReady() { return true; }
|
||||
virtual float getLastRSSI() const { return 0; }
|
||||
virtual float getLastSNR() const { return 0; }
|
||||
|
||||
/* Adaptive Power Control */
|
||||
virtual void setTxPowerReduction(int8_t reduction_db) { (void)reduction_db; }
|
||||
virtual int8_t getTxPowerReduction() const { return 0; }
|
||||
|
||||
/* Packet statistics */
|
||||
virtual uint32_t getPacketsRecv() const { return 0; }
|
||||
virtual uint32_t getPacketsSent() const { return 0; }
|
||||
virtual uint32_t getPacketsRecvErrors() const { return 0; }
|
||||
};
|
||||
|
||||
} /* namespace mesh */
|
||||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
* ZephCore Radio interface - matches Dispatcher.h
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace mesh {
|
||||
|
||||
class Radio {
|
||||
public:
|
||||
virtual void begin() {}
|
||||
|
||||
virtual int recvRaw(uint8_t *bytes, int sz) = 0;
|
||||
virtual uint32_t getEstAirtimeFor(int len_bytes) = 0;
|
||||
virtual float packetScore(float snr, int packet_len) = 0;
|
||||
virtual bool startSendRaw(const uint8_t *bytes, int len) = 0;
|
||||
virtual bool isSendComplete() = 0;
|
||||
virtual void onSendFinished() = 0;
|
||||
|
||||
virtual int getNoiseFloor() const { return 0; }
|
||||
virtual void triggerNoiseFloorCalibrate(int threshold) { (void)threshold; }
|
||||
virtual void resetAGC() {}
|
||||
|
||||
virtual bool isInRecvMode() const = 0;
|
||||
virtual bool isReceiving() { return false; }
|
||||
virtual bool isRadioReady() { return true; }
|
||||
virtual float getLastRSSI() const { return 0; }
|
||||
virtual float getLastSNR() const { return 0; }
|
||||
|
||||
/* Adaptive Power Control */
|
||||
virtual void setTxPowerReduction(int8_t reduction_db) { (void)reduction_db; }
|
||||
virtual int8_t getTxPowerReduction() const { return 0; }
|
||||
|
||||
/* Packet statistics */
|
||||
virtual uint32_t getPacketsRecv() const { return 0; }
|
||||
virtual uint32_t getPacketsSent() const { return 0; }
|
||||
virtual uint32_t getPacketsRecvErrors() const { return 0; }
|
||||
};
|
||||
|
||||
} /* namespace mesh */
|
||||
Reference in new issue
Block a user