mirror of
https://github.com/meshtastic/firmware.git
synced 2026-01-04 00:52:17 +00:00
cleanup directory structure
This commit is contained in:
126
src/mesh/FloodingRouter.cpp
Normal file
126
src/mesh/FloodingRouter.cpp
Normal file
@@ -0,0 +1,126 @@
|
||||
#include "FloodingRouter.h"
|
||||
#include "configuration.h"
|
||||
#include "mesh-pb-constants.h"
|
||||
|
||||
/// We clear our old flood record five minute after we see the last of it
|
||||
#define FLOOD_EXPIRE_TIME (5 * 60 * 1000L)
|
||||
|
||||
FloodingRouter::FloodingRouter() : toResend(MAX_NUM_NODES)
|
||||
{
|
||||
recentBroadcasts.reserve(MAX_NUM_NODES); // Prealloc the worst case # of records - to prevent heap fragmentation
|
||||
// setup our periodic task
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a packet on a suitable interface. This routine will
|
||||
* later free() the packet to pool. This routine is not allowed to stall.
|
||||
* If the txmit queue is full it might return an error
|
||||
*/
|
||||
ErrorCode FloodingRouter::send(MeshPacket *p)
|
||||
{
|
||||
// We update our table of recent broadcasts, even for messages we send
|
||||
wasSeenRecently(p);
|
||||
|
||||
return Router::send(p);
|
||||
}
|
||||
|
||||
// Return a delay in msec before sending the next packet
|
||||
uint32_t getRandomDelay()
|
||||
{
|
||||
return random(200, 10 * 1000L); // between 200ms and 10s
|
||||
}
|
||||
|
||||
/**
|
||||
* Called from loop()
|
||||
* Handle any packet that is received by an interface on this node.
|
||||
* Note: some packets may merely being passed through this node and will be forwarded elsewhere.
|
||||
*
|
||||
* Note: this method will free the provided packet
|
||||
*/
|
||||
void FloodingRouter::handleReceived(MeshPacket *p)
|
||||
{
|
||||
if (wasSeenRecently(p)) {
|
||||
DEBUG_MSG("Ignoring incoming floodmsg, because we've already seen it\n");
|
||||
packetPool.release(p);
|
||||
} else {
|
||||
if (p->to == NODENUM_BROADCAST) {
|
||||
if (p->id != 0) {
|
||||
uint32_t delay = getRandomDelay();
|
||||
|
||||
DEBUG_MSG("Rebroadcasting received floodmsg to neighbors in %u msec, fr=0x%x,to=0x%x,id=%d\n", delay, p->from,
|
||||
p->to, p->id);
|
||||
|
||||
MeshPacket *tosend = packetPool.allocCopy(*p);
|
||||
toResend.enqueue(tosend);
|
||||
setPeriod(delay); // This will work even if we were already waiting a random delay
|
||||
} else {
|
||||
DEBUG_MSG("Ignoring a simple (0 hop) broadcast\n");
|
||||
}
|
||||
}
|
||||
|
||||
// handle the packet as normal
|
||||
Router::handleReceived(p);
|
||||
}
|
||||
}
|
||||
|
||||
void FloodingRouter::doTask()
|
||||
{
|
||||
MeshPacket *p = toResend.dequeuePtr(0);
|
||||
|
||||
if (p) {
|
||||
DEBUG_MSG("Sending delayed message!\n");
|
||||
// Note: we are careful to resend using the original senders node id
|
||||
// We are careful not to call our hooked version of send() - because we don't want to check this again
|
||||
Router::send(p);
|
||||
}
|
||||
|
||||
if (toResend.isEmpty())
|
||||
disable(); // no more work right now
|
||||
else {
|
||||
setPeriod(getRandomDelay());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update recentBroadcasts and return true if we have already seen this packet
|
||||
*/
|
||||
bool FloodingRouter::wasSeenRecently(const MeshPacket *p)
|
||||
{
|
||||
if (p->to != NODENUM_BROADCAST)
|
||||
return false; // Not a broadcast, so we don't care
|
||||
|
||||
if (p->id == 0) {
|
||||
DEBUG_MSG("Ignoring message with zero id\n");
|
||||
return false; // Not a floodable message ID, so we don't care
|
||||
}
|
||||
|
||||
uint32_t now = millis();
|
||||
for (int i = 0; i < recentBroadcasts.size();) {
|
||||
BroadcastRecord &r = recentBroadcasts[i];
|
||||
|
||||
if ((now - r.rxTimeMsec) >= FLOOD_EXPIRE_TIME) {
|
||||
// DEBUG_MSG("Deleting old broadcast record %d\n", i);
|
||||
recentBroadcasts.erase(recentBroadcasts.begin() + i); // delete old record
|
||||
} else {
|
||||
if (r.id == p->id && r.sender == p->from) {
|
||||
DEBUG_MSG("Found existing broadcast record for fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id);
|
||||
|
||||
// Update the time on this record to now
|
||||
r.rxTimeMsec = now;
|
||||
return true;
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
// Didn't find an existing record, make one
|
||||
BroadcastRecord r;
|
||||
r.id = p->id;
|
||||
r.sender = p->from;
|
||||
r.rxTimeMsec = now;
|
||||
recentBroadcasts.push_back(r);
|
||||
DEBUG_MSG("Adding broadcast record for fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id);
|
||||
|
||||
return false;
|
||||
}
|
||||
83
src/mesh/FloodingRouter.h
Normal file
83
src/mesh/FloodingRouter.h
Normal file
@@ -0,0 +1,83 @@
|
||||
#pragma once
|
||||
|
||||
#include "PeriodicTask.h"
|
||||
#include "Router.h"
|
||||
#include <vector>
|
||||
|
||||
/**
|
||||
* A record of a recent message broadcast
|
||||
*/
|
||||
struct BroadcastRecord {
|
||||
NodeNum sender;
|
||||
PacketId id;
|
||||
uint32_t rxTimeMsec; // Unix time in msecs - the time we received it
|
||||
};
|
||||
|
||||
/**
|
||||
* This is a mixin that extends Router with the ability to do Naive Flooding (in the standard mesh protocol sense)
|
||||
*
|
||||
* Rules for broadcasting (listing here for now, will move elsewhere eventually):
|
||||
|
||||
If to==BROADCAST and id==0, this is a simple broadcast (0 hops). It will be
|
||||
sent only by the current node and other nodes will not attempt to rebroadcast
|
||||
it.
|
||||
|
||||
If to==BROADCAST and id!=0, this is a "naive flooding" broadcast. The initial
|
||||
node will send it on all local interfaces.
|
||||
|
||||
When other nodes receive this message, they will
|
||||
first check if their recentBroadcasts table contains the (from, id) pair that
|
||||
indicates this message. If so, we've already seen it - so we discard it. If
|
||||
not, we add it to the table and then resend this message on all interfaces.
|
||||
When resending we are careful to use the "from" ID of the original sender. Not
|
||||
our own ID. When resending we pick a random delay between 0 and 10 seconds to
|
||||
decrease the chance of collisions with transmitters we can not even hear.
|
||||
|
||||
Any entries in recentBroadcasts that are older than X seconds (longer than the
|
||||
max time a flood can take) will be discarded.
|
||||
*/
|
||||
class FloodingRouter : public Router, public PeriodicTask
|
||||
{
|
||||
private:
|
||||
/** FIXME: really should be a std::unordered_set with the key being sender,id.
|
||||
* This would make checking packets in wasSeenRecently faster.
|
||||
*/
|
||||
std::vector<BroadcastRecord> recentBroadcasts;
|
||||
|
||||
/**
|
||||
* Packets we've received that we need to resend after a short delay
|
||||
*/
|
||||
PointerQueue<MeshPacket> toResend;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
*/
|
||||
FloodingRouter();
|
||||
|
||||
/**
|
||||
* Send a packet on a suitable interface. This routine will
|
||||
* later free() the packet to pool. This routine is not allowed to stall.
|
||||
* If the txmit queue is full it might return an error
|
||||
*/
|
||||
virtual ErrorCode send(MeshPacket *p);
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Called from loop()
|
||||
* Handle any packet that is received by an interface on this node.
|
||||
* Note: some packets may merely being passed through this node and will be forwarded elsewhere.
|
||||
*
|
||||
* Note: this method will free the provided packet
|
||||
*/
|
||||
virtual void handleReceived(MeshPacket *p);
|
||||
|
||||
virtual void doTask();
|
||||
|
||||
private:
|
||||
/**
|
||||
* Update recentBroadcasts and return true if we have already seen this packet
|
||||
*/
|
||||
bool wasSeenRecently(const MeshPacket *p);
|
||||
};
|
||||
81
src/mesh/MemoryPool.h
Normal file
81
src/mesh/MemoryPool.h
Normal file
@@ -0,0 +1,81 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "PointerQueue.h"
|
||||
|
||||
/**
|
||||
* A pool based allocator
|
||||
*
|
||||
* Eventually this routine will even be safe for ISR use...
|
||||
*/
|
||||
template <class T> class MemoryPool
|
||||
{
|
||||
PointerQueue<T> dead;
|
||||
|
||||
T *buf; // our large raw block of memory
|
||||
|
||||
size_t maxElements;
|
||||
|
||||
public:
|
||||
MemoryPool(size_t _maxElements) : dead(_maxElements), maxElements(_maxElements)
|
||||
{
|
||||
buf = new T[maxElements];
|
||||
|
||||
// prefill dead
|
||||
for (size_t i = 0; i < maxElements; i++)
|
||||
release(&buf[i]);
|
||||
}
|
||||
|
||||
~MemoryPool() { delete[] buf; }
|
||||
|
||||
/// Return a queable object which has been prefilled with zeros. Panic if no buffer is available
|
||||
/// Note: this method is safe to call from regular OR ISR code
|
||||
T *allocZeroed()
|
||||
{
|
||||
T *p = allocZeroed(0);
|
||||
|
||||
assert(p); // FIXME panic instead
|
||||
return p;
|
||||
}
|
||||
|
||||
/// Return a queable object which has been prefilled with zeros - allow timeout to wait for available buffers (you probably
|
||||
/// don't want this version).
|
||||
T *allocZeroed(TickType_t maxWait)
|
||||
{
|
||||
T *p = dead.dequeuePtr(maxWait);
|
||||
|
||||
if (p)
|
||||
memset(p, 0, sizeof(T));
|
||||
return p;
|
||||
}
|
||||
|
||||
/// Return a queable object which is a copy of some other object
|
||||
T *allocCopy(const T &src, TickType_t maxWait = portMAX_DELAY)
|
||||
{
|
||||
T *p = dead.dequeuePtr(maxWait);
|
||||
|
||||
if (p)
|
||||
*p = src;
|
||||
return p;
|
||||
}
|
||||
|
||||
/// Return a buffer for use by others
|
||||
void release(T *p)
|
||||
{
|
||||
assert(dead.enqueue(p, 0));
|
||||
assert(p >= buf &&
|
||||
(size_t)(p - buf) <
|
||||
maxElements); // sanity check to make sure a programmer didn't free something that didn't come from this pool
|
||||
}
|
||||
|
||||
/// Return a buffer from an ISR, if higherPriWoken is set to true you have some work to do ;-)
|
||||
void releaseFromISR(T *p, BaseType_t *higherPriWoken)
|
||||
{
|
||||
assert(dead.enqueueFromISR(p, higherPriWoken));
|
||||
assert(p >= buf &&
|
||||
(size_t)(p - buf) <
|
||||
maxElements); // sanity check to make sure a programmer didn't free something that didn't come from this pool
|
||||
}
|
||||
};
|
||||
28
src/mesh/PointerQueue.h
Normal file
28
src/mesh/PointerQueue.h
Normal file
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include "TypedQueue.h"
|
||||
|
||||
/**
|
||||
* A wrapper for freertos queues that assumes each element is a pointer
|
||||
*/
|
||||
template <class T> class PointerQueue : public TypedQueue<T *>
|
||||
{
|
||||
public:
|
||||
PointerQueue(int maxElements) : TypedQueue<T *>(maxElements) {}
|
||||
|
||||
// returns a ptr or null if the queue was empty
|
||||
T *dequeuePtr(TickType_t maxWait = portMAX_DELAY)
|
||||
{
|
||||
T *p;
|
||||
|
||||
return this->dequeue(&p, maxWait) ? p : nullptr;
|
||||
}
|
||||
|
||||
// returns a ptr or null if the queue was empty
|
||||
T *dequeuePtrFromISR(BaseType_t *higherPriWoken)
|
||||
{
|
||||
T *p;
|
||||
|
||||
return this->dequeueFromISR(&p, higherPriWoken) ? p : nullptr;
|
||||
}
|
||||
};
|
||||
121
src/mesh/RF95Interface.cpp
Normal file
121
src/mesh/RF95Interface.cpp
Normal file
@@ -0,0 +1,121 @@
|
||||
#include "RF95Interface.h"
|
||||
#include "MeshRadio.h" // kinda yucky, but we need to know which region we are in
|
||||
#include "RadioLibRF95.h"
|
||||
#include <configuration.h>
|
||||
|
||||
RF95Interface::RF95Interface(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, SPIClass &spi)
|
||||
: RadioLibInterface(cs, irq, rst, 0, spi)
|
||||
{
|
||||
// FIXME - we assume devices never get destroyed
|
||||
}
|
||||
|
||||
/// Initialise the Driver transport hardware and software.
|
||||
/// Make sure the Driver is properly configured before calling init().
|
||||
/// \return true if initialisation succeeded.
|
||||
bool RF95Interface::init()
|
||||
{
|
||||
applyModemConfig();
|
||||
if (power > 20) // This chip has lower power limits than some
|
||||
power = 20;
|
||||
|
||||
iface = lora = new RadioLibRF95(&module);
|
||||
int res = lora->begin(freq, bw, sf, cr, syncWord, power, currentLimit, preambleLength);
|
||||
DEBUG_MSG("LORA init result %d\n", res);
|
||||
|
||||
if (res == ERR_NONE)
|
||||
res = lora->setCRC(SX126X_LORA_CRC_ON);
|
||||
|
||||
if (res == ERR_NONE)
|
||||
startReceive(); // start receiving
|
||||
|
||||
return res == ERR_NONE;
|
||||
}
|
||||
|
||||
void INTERRUPT_ATTR RF95Interface::disableInterrupt()
|
||||
{
|
||||
lora->clearDio0Action();
|
||||
}
|
||||
|
||||
bool RF95Interface::reconfigure()
|
||||
{
|
||||
applyModemConfig();
|
||||
|
||||
// set mode to standby
|
||||
setStandby();
|
||||
|
||||
// configure publicly accessible settings
|
||||
int err = lora->setSpreadingFactor(sf);
|
||||
assert(err == ERR_NONE);
|
||||
|
||||
err = lora->setBandwidth(bw);
|
||||
assert(err == ERR_NONE);
|
||||
|
||||
err = lora->setCodingRate(cr);
|
||||
assert(err == ERR_NONE);
|
||||
|
||||
err = lora->setSyncWord(syncWord);
|
||||
assert(err == ERR_NONE);
|
||||
|
||||
err = lora->setCurrentLimit(currentLimit);
|
||||
assert(err == ERR_NONE);
|
||||
|
||||
err = lora->setPreambleLength(preambleLength);
|
||||
assert(err == ERR_NONE);
|
||||
|
||||
err = lora->setFrequency(freq);
|
||||
assert(err == ERR_NONE);
|
||||
|
||||
if (power > 20) // This chip has lower power limits than some
|
||||
power = 20;
|
||||
err = lora->setOutputPower(power);
|
||||
assert(err == ERR_NONE);
|
||||
|
||||
startReceive(); // restart receiving
|
||||
|
||||
return ERR_NONE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add SNR data to received messages
|
||||
*/
|
||||
void RF95Interface::addReceiveMetadata(MeshPacket *mp)
|
||||
{
|
||||
mp->rx_snr = lora->getSNR();
|
||||
}
|
||||
|
||||
void RF95Interface::setStandby()
|
||||
{
|
||||
int err = lora->standby();
|
||||
assert(err == ERR_NONE);
|
||||
|
||||
isReceiving = false; // If we were receiving, not any more
|
||||
disableInterrupt();
|
||||
completeSending(); // If we were sending, not anymore
|
||||
}
|
||||
|
||||
void RF95Interface::startReceive()
|
||||
{
|
||||
setStandby();
|
||||
int err = lora->startReceive();
|
||||
assert(err == ERR_NONE);
|
||||
|
||||
isReceiving = true;
|
||||
|
||||
// Must be done AFTER, starting transmit, because startTransmit clears (possibly stale) interrupt pending register bits
|
||||
enableInterrupt(isrRxLevel0);
|
||||
}
|
||||
|
||||
/** Could we send right now (i.e. either not actively receving or transmitting)? */
|
||||
bool RF95Interface::isActivelyReceiving()
|
||||
{
|
||||
return lora->isReceiving();
|
||||
}
|
||||
|
||||
bool RF95Interface::sleep()
|
||||
{
|
||||
// put chipset into sleep mode
|
||||
disableInterrupt();
|
||||
lora->sleep();
|
||||
|
||||
return true;
|
||||
}
|
||||
55
src/mesh/RF95Interface.h
Normal file
55
src/mesh/RF95Interface.h
Normal file
@@ -0,0 +1,55 @@
|
||||
#pragma once
|
||||
|
||||
#include "MeshRadio.h" // kinda yucky, but we need to know which region we are in
|
||||
#include "RadioLibInterface.h"
|
||||
#include "RadioLibRF95.h"
|
||||
|
||||
/**
|
||||
* Our new not radiohead adapter for RF95 style radios
|
||||
*/
|
||||
class RF95Interface : public RadioLibInterface
|
||||
{
|
||||
RadioLibRF95 *lora; // Either a RFM95 or RFM96 depending on what was stuffed on this board
|
||||
|
||||
public:
|
||||
RF95Interface(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, SPIClass &spi);
|
||||
|
||||
/// Initialise the Driver transport hardware and software.
|
||||
/// Make sure the Driver is properly configured before calling init().
|
||||
/// \return true if initialisation succeeded.
|
||||
virtual bool init();
|
||||
|
||||
/// Apply any radio provisioning changes
|
||||
/// Make sure the Driver is properly configured before calling init().
|
||||
/// \return true if initialisation succeeded.
|
||||
virtual bool reconfigure();
|
||||
|
||||
/// Prepare hardware for sleep. Call this _only_ for deep sleep, not needed for light sleep.
|
||||
virtual bool sleep();
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Glue functions called from ISR land
|
||||
*/
|
||||
virtual void disableInterrupt();
|
||||
|
||||
/**
|
||||
* Enable a particular ISR callback glue function
|
||||
*/
|
||||
virtual void enableInterrupt(void (*callback)()) { lora->setDio0Action(callback); }
|
||||
|
||||
/** are we actively receiving a packet (only called during receiving state) */
|
||||
virtual bool isActivelyReceiving();
|
||||
|
||||
/**
|
||||
* Start waiting to receive a message
|
||||
*/
|
||||
virtual void startReceive();
|
||||
|
||||
/**
|
||||
* Add SNR data to received messages
|
||||
*/
|
||||
virtual void addReceiveMetadata(MeshPacket *mp);
|
||||
private:
|
||||
void setStandby();
|
||||
};
|
||||
57
src/mesh/RadioInterface.cpp
Normal file
57
src/mesh/RadioInterface.cpp
Normal file
@@ -0,0 +1,57 @@
|
||||
|
||||
#include "RadioInterface.h"
|
||||
#include "NodeDB.h"
|
||||
#include "assert.h"
|
||||
#include "configuration.h"
|
||||
#include <assert.h>
|
||||
#include <pb_decode.h>
|
||||
#include <pb_encode.h>
|
||||
|
||||
RadioInterface::RadioInterface() : txQueue(MAX_TX_QUEUE)
|
||||
{
|
||||
assert(sizeof(PacketHeader) == 4); // make sure the compiler did what we expected
|
||||
}
|
||||
|
||||
ErrorCode SimRadio::send(MeshPacket *p)
|
||||
{
|
||||
DEBUG_MSG("SimRadio.send\n");
|
||||
packetPool.release(p);
|
||||
return ERRNO_OK;
|
||||
}
|
||||
|
||||
void RadioInterface::deliverToReceiver(MeshPacket *p)
|
||||
{
|
||||
assert(rxDest);
|
||||
assert(rxDest->enqueue(p, 0)); // NOWAIT - fixme, if queue is full, delete older messages
|
||||
}
|
||||
|
||||
/***
|
||||
* given a packet set sendingPacket and decode the protobufs into radiobuf. Returns # of payload bytes to send
|
||||
*/
|
||||
size_t RadioInterface::beginSending(MeshPacket *p)
|
||||
{
|
||||
assert(!sendingPacket);
|
||||
|
||||
// DEBUG_MSG("sending queued packet on mesh (txGood=%d,rxGood=%d,rxBad=%d)\n", rf95.txGood(), rf95.rxGood(), rf95.rxBad());
|
||||
assert(p->has_payload);
|
||||
|
||||
lastTxStart = millis();
|
||||
|
||||
PacketHeader *h = (PacketHeader *)radiobuf;
|
||||
|
||||
h->from = p->from;
|
||||
h->to = p->to;
|
||||
h->flags = 0;
|
||||
h->id = p->id;
|
||||
|
||||
// if the sender nodenum is zero, that means uninitialized
|
||||
assert(h->from);
|
||||
|
||||
size_t numbytes = pb_encode_to_bytes(radiobuf + sizeof(PacketHeader), sizeof(radiobuf), SubPacket_fields, &p->payload) +
|
||||
sizeof(PacketHeader);
|
||||
|
||||
assert(numbytes <= MAX_RHPACKETLEN);
|
||||
|
||||
sendingPacket = p;
|
||||
return numbytes;
|
||||
}
|
||||
119
src/mesh/RadioInterface.h
Normal file
119
src/mesh/RadioInterface.h
Normal file
@@ -0,0 +1,119 @@
|
||||
#pragma once
|
||||
|
||||
#include "MemoryPool.h"
|
||||
#include "MeshTypes.h"
|
||||
#include "PointerQueue.h"
|
||||
#include "mesh.pb.h"
|
||||
|
||||
#define MAX_TX_QUEUE 16 // max number of packets which can be waiting for transmission
|
||||
|
||||
#define MAX_RHPACKETLEN 256
|
||||
|
||||
/**
|
||||
* This structure has to exactly match the wire layout when sent over the radio link. Used to keep compatibility
|
||||
* wtih the old radiohead implementation.
|
||||
*/
|
||||
typedef struct {
|
||||
uint8_t to, from, id, flags;
|
||||
} PacketHeader;
|
||||
|
||||
typedef enum {
|
||||
Bw125Cr45Sf128 = 0, ///< Bw = 125 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on. Default medium range
|
||||
Bw500Cr45Sf128, ///< Bw = 500 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on. Fast+short range
|
||||
Bw31_25Cr48Sf512, ///< Bw = 31.25 kHz, Cr = 4/8, Sf = 512chips/symbol, CRC on. Slow+long range
|
||||
Bw125Cr48Sf4096, ///< Bw = 125 kHz, Cr = 4/8, Sf = 4096chips/symbol, CRC on. Slow+long range
|
||||
} ModemConfigChoice;
|
||||
|
||||
/**
|
||||
* Basic operations all radio chipsets must implement.
|
||||
*
|
||||
* This defines the SOLE API for talking to radios (because soon we will have alternate radio implementations)
|
||||
*/
|
||||
class RadioInterface
|
||||
{
|
||||
friend class MeshRadio; // for debugging we let that class touch pool
|
||||
PointerQueue<MeshPacket> *rxDest = NULL;
|
||||
|
||||
protected:
|
||||
MeshPacket *sendingPacket = NULL; // The packet we are currently sending
|
||||
PointerQueue<MeshPacket> txQueue;
|
||||
uint32_t lastTxStart = 0L;
|
||||
|
||||
/**
|
||||
* A temporary buffer used for sending/receving packets, sized to hold the biggest buffer we might need
|
||||
* */
|
||||
uint8_t radiobuf[MAX_RHPACKETLEN];
|
||||
|
||||
/**
|
||||
* Enqueue a received packet for the registered receiver
|
||||
*/
|
||||
void deliverToReceiver(MeshPacket *p);
|
||||
|
||||
public:
|
||||
float freq = 915.0; // FIXME, init all these params from user setings
|
||||
int8_t power = 17;
|
||||
ModemConfigChoice modemConfig;
|
||||
|
||||
/** pool is the pool we will alloc our rx packets from
|
||||
* rxDest is where we will send any rx packets, it becomes receivers responsibility to return packet to the pool
|
||||
*/
|
||||
RadioInterface();
|
||||
|
||||
/**
|
||||
* Set where to deliver received packets. This method should only be used by the Router class
|
||||
*/
|
||||
void setReceiver(PointerQueue<MeshPacket> *_rxDest) { rxDest = _rxDest; }
|
||||
|
||||
virtual void loop() {} // Idle processing
|
||||
|
||||
/**
|
||||
* Return true if we think the board can go to sleep (i.e. our tx queue is empty, we are not sending or receiving)
|
||||
*
|
||||
* This method must be used before putting the CPU into deep or light sleep.
|
||||
*/
|
||||
virtual bool canSleep() { return true; }
|
||||
|
||||
/// Prepare hardware for sleep. Call this _only_ for deep sleep, not needed for light sleep.
|
||||
virtual bool sleep() { return true; }
|
||||
|
||||
/**
|
||||
* Send a packet (possibly by enquing in a private fifo). This routine will
|
||||
* later free() the packet to pool. This routine is not allowed to stall.
|
||||
* If the txmit queue is full it might return an error
|
||||
*/
|
||||
virtual ErrorCode send(MeshPacket *p) = 0;
|
||||
|
||||
// methods from radiohead
|
||||
|
||||
/// Initialise the Driver transport hardware and software.
|
||||
/// Make sure the Driver is properly configured before calling init().
|
||||
/// \return true if initialisation succeeded.
|
||||
virtual bool init() = 0;
|
||||
|
||||
/// Apply any radio provisioning changes
|
||||
/// Make sure the Driver is properly configured before calling init().
|
||||
/// \return true if initialisation succeeded.
|
||||
virtual bool reconfigure() = 0;
|
||||
|
||||
protected:
|
||||
/***
|
||||
* given a packet set sendingPacket and decode the protobufs into radiobuf. Returns # of bytes to send (including the
|
||||
* PacketHeader & payload).
|
||||
*
|
||||
* Used as the first step of
|
||||
*/
|
||||
size_t beginSending(MeshPacket *p);
|
||||
};
|
||||
|
||||
class SimRadio : public RadioInterface
|
||||
{
|
||||
public:
|
||||
virtual ErrorCode send(MeshPacket *p);
|
||||
|
||||
// methods from radiohead
|
||||
|
||||
/// Initialise the Driver transport hardware and software.
|
||||
/// Make sure the Driver is properly configured before calling init().
|
||||
/// \return true if initialisation succeeded.
|
||||
virtual bool init() { return true; }
|
||||
};
|
||||
232
src/mesh/RadioLibInterface.cpp
Normal file
232
src/mesh/RadioLibInterface.cpp
Normal file
@@ -0,0 +1,232 @@
|
||||
#include "RadioLibInterface.h"
|
||||
#include "MeshTypes.h"
|
||||
#include "mesh-pb-constants.h"
|
||||
#include <NodeDB.h> // FIXME, this class shouldn't need to look into nodedb
|
||||
#include <configuration.h>
|
||||
#include <pb_decode.h>
|
||||
#include <pb_encode.h>
|
||||
|
||||
// FIXME, we default to 4MHz SPI, SPI mode 0, check if the datasheet says it can really do that
|
||||
static SPISettings spiSettings(4000000, MSBFIRST, SPI_MODE0);
|
||||
|
||||
RadioLibInterface::RadioLibInterface(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy,
|
||||
SPIClass &spi, PhysicalLayer *_iface)
|
||||
: module(cs, irq, rst, busy, spi, spiSettings), iface(_iface)
|
||||
{
|
||||
assert(!instance); // We assume only one for now
|
||||
instance = this;
|
||||
}
|
||||
|
||||
void INTERRUPT_ATTR RadioLibInterface::isrRxLevel0()
|
||||
{
|
||||
instance->pending = ISR_RX;
|
||||
instance->disableInterrupt();
|
||||
}
|
||||
|
||||
void INTERRUPT_ATTR RadioLibInterface::isrTxLevel0()
|
||||
{
|
||||
instance->pending = ISR_TX;
|
||||
instance->disableInterrupt();
|
||||
}
|
||||
|
||||
/** Our ISR code currently needs this to find our active instance
|
||||
*/
|
||||
RadioLibInterface *RadioLibInterface::instance;
|
||||
|
||||
/**
|
||||
* Convert our modemConfig enum into wf, sf, etc...
|
||||
*/
|
||||
void RadioLibInterface::applyModemConfig()
|
||||
{
|
||||
switch (modemConfig) {
|
||||
case Bw125Cr45Sf128: ///< Bw = 125 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on. Default medium range
|
||||
bw = 125;
|
||||
cr = 5;
|
||||
sf = 7;
|
||||
break;
|
||||
case Bw500Cr45Sf128: ///< Bw = 500 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on. Fast+short range
|
||||
bw = 500;
|
||||
cr = 5;
|
||||
sf = 7;
|
||||
break;
|
||||
case Bw31_25Cr48Sf512: ///< Bw = 31.25 kHz, Cr = 4/8, Sf = 512chips/symbol, CRC on. Slow+long range
|
||||
bw = 31.25;
|
||||
cr = 8;
|
||||
sf = 9;
|
||||
break;
|
||||
case Bw125Cr48Sf4096:
|
||||
bw = 125;
|
||||
cr = 8;
|
||||
sf = 12;
|
||||
break;
|
||||
default:
|
||||
assert(0); // Unknown enum
|
||||
}
|
||||
}
|
||||
|
||||
/** Could we send right now (i.e. either not actively receving or transmitting)? */
|
||||
bool RadioLibInterface::canSendImmediately()
|
||||
{
|
||||
// We wait _if_ we are partially though receiving a packet (rather than just merely waiting for one).
|
||||
// To do otherwise would be doubly bad because not only would we drop the packet that was on the way in,
|
||||
// we almost certainly guarantee no one outside will like the packet we are sending.
|
||||
PendingISR isPending = pending;
|
||||
bool busyTx = sendingPacket != NULL;
|
||||
bool busyRx = isReceiving && isActivelyReceiving();
|
||||
|
||||
if (busyTx || busyRx || isPending)
|
||||
DEBUG_MSG("Can not send yet, busyTx=%d, busyRx=%d, intPend=%d\n", busyTx, busyRx, isPending);
|
||||
|
||||
return !busyTx && !busyRx && !isPending;
|
||||
}
|
||||
|
||||
/// Send a packet (possibly by enquing in a private fifo). This routine will
|
||||
/// later free() the packet to pool. This routine is not allowed to stall because it is called from
|
||||
/// bluetooth comms code. If the txmit queue is empty it might return an error
|
||||
ErrorCode RadioLibInterface::send(MeshPacket *p)
|
||||
{
|
||||
// We wait _if_ we are partially though receiving a packet (rather than just merely waiting for one).
|
||||
// To do otherwise would be doubly bad because not only would we drop the packet that was on the way in,
|
||||
// we almost certainly guarantee no one outside will like the packet we are sending.
|
||||
if (canSendImmediately()) {
|
||||
// if the radio is idle, we can send right away
|
||||
DEBUG_MSG("immediate send on mesh fr=0x%x,to=0x%x,id=%d\n (txGood=%d,rxGood=%d,rxBad=%d)\n", p->from, p->to, p->id,
|
||||
txGood, rxGood, rxBad);
|
||||
|
||||
startSend(p);
|
||||
return ERRNO_OK;
|
||||
} else {
|
||||
DEBUG_MSG("enqueuing packet for send from=0x%x, to=0x%x\n", p->from, p->to);
|
||||
ErrorCode res = txQueue.enqueue(p, 0) ? ERRNO_OK : ERRNO_UNKNOWN;
|
||||
|
||||
if (res != ERRNO_OK) // we weren't able to queue it, so we must drop it to prevent leaks
|
||||
packetPool.release(p);
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
bool RadioLibInterface::canSleep()
|
||||
{
|
||||
bool res = txQueue.isEmpty();
|
||||
if (!res) // only print debug messages if we are vetoing sleep
|
||||
DEBUG_MSG("radio wait to sleep, txEmpty=%d\n", txQueue.isEmpty());
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
void RadioLibInterface::loop()
|
||||
{
|
||||
PendingISR wasPending;
|
||||
while ((wasPending = pending) != 0) { // atomic read
|
||||
pending = ISR_NONE; // If the flag was set, it is _guaranteed_ the ISR won't be running, because it masked itself
|
||||
|
||||
if (wasPending == ISR_TX)
|
||||
handleTransmitInterrupt();
|
||||
else if (wasPending == ISR_RX)
|
||||
handleReceiveInterrupt();
|
||||
else
|
||||
assert(0);
|
||||
|
||||
startNextWork();
|
||||
}
|
||||
}
|
||||
|
||||
void RadioLibInterface::startNextWork()
|
||||
{
|
||||
// First send any outgoing packets we have ready
|
||||
MeshPacket *txp = txQueue.dequeuePtr(0);
|
||||
if (txp)
|
||||
startSend(txp);
|
||||
else {
|
||||
// Nothing to send, let's switch back to receive mode
|
||||
startReceive();
|
||||
}
|
||||
}
|
||||
|
||||
void RadioLibInterface::handleTransmitInterrupt()
|
||||
{
|
||||
// DEBUG_MSG("handling lora TX interrupt\n");
|
||||
assert(sendingPacket); // Were we sending? - FIXME, this was null coming out of light sleep due to RF95 ISR!
|
||||
|
||||
completeSending();
|
||||
}
|
||||
|
||||
void RadioLibInterface::completeSending()
|
||||
{
|
||||
if (sendingPacket) {
|
||||
txGood++;
|
||||
DEBUG_MSG("Completed sending to=0x%x, id=%u\n", sendingPacket->to, sendingPacket->id);
|
||||
|
||||
// We are done sending that packet, release it
|
||||
packetPool.release(sendingPacket);
|
||||
sendingPacket = NULL;
|
||||
// DEBUG_MSG("Done with send\n");
|
||||
}
|
||||
}
|
||||
|
||||
void RadioLibInterface::handleReceiveInterrupt()
|
||||
{
|
||||
assert(isReceiving);
|
||||
isReceiving = false;
|
||||
|
||||
// read the number of actually received bytes
|
||||
size_t length = iface->getPacketLength();
|
||||
|
||||
int state = iface->readData(radiobuf, length);
|
||||
if (state != ERR_NONE) {
|
||||
DEBUG_MSG("ignoring received packet due to error=%d\n", state);
|
||||
rxBad++;
|
||||
} else {
|
||||
// Skip the 4 headers that are at the beginning of the rxBuf
|
||||
int32_t payloadLen = length - sizeof(PacketHeader);
|
||||
const uint8_t *payload = radiobuf + sizeof(PacketHeader);
|
||||
|
||||
// check for short packets
|
||||
if (payloadLen < 0) {
|
||||
DEBUG_MSG("ignoring received packet too short\n");
|
||||
rxBad++;
|
||||
} else {
|
||||
const PacketHeader *h = (PacketHeader *)radiobuf;
|
||||
uint8_t ourAddr = nodeDB.getNodeNum();
|
||||
|
||||
if (h->to != 255 && h->to != ourAddr) {
|
||||
DEBUG_MSG("ignoring packet not sent to us\n");
|
||||
} else {
|
||||
MeshPacket *mp = packetPool.allocZeroed();
|
||||
|
||||
SubPacket *p = &mp->payload;
|
||||
|
||||
mp->from = h->from;
|
||||
mp->to = h->to;
|
||||
mp->id = h->id;
|
||||
addReceiveMetadata(mp);
|
||||
|
||||
if (!pb_decode_from_bytes(payload, payloadLen, SubPacket_fields, p)) {
|
||||
DEBUG_MSG("Invalid protobufs in received mesh packet, discarding.\n");
|
||||
packetPool.release(mp);
|
||||
// rxBad++; not really a hw error
|
||||
} else {
|
||||
// parsing was successful, queue for our recipient
|
||||
mp->has_payload = true;
|
||||
rxGood++;
|
||||
DEBUG_MSG("Lora RX interrupt from=0x%x, id=%u\n", mp->from, mp->id);
|
||||
|
||||
deliverToReceiver(mp);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** start an immediate transmit */
|
||||
void RadioLibInterface::startSend(MeshPacket *txp)
|
||||
{
|
||||
size_t numbytes = beginSending(txp);
|
||||
|
||||
int res = iface->startTransmit(radiobuf, numbytes);
|
||||
assert(res == ERR_NONE);
|
||||
|
||||
// Must be done AFTER, starting transmit, because startTransmit clears (possibly stale) interrupt pending register bits
|
||||
enableInterrupt(isrTxLevel0);
|
||||
}
|
||||
127
src/mesh/RadioLibInterface.h
Normal file
127
src/mesh/RadioLibInterface.h
Normal file
@@ -0,0 +1,127 @@
|
||||
#pragma once
|
||||
|
||||
#include "RadioInterface.h"
|
||||
|
||||
#include <RadioLib.h>
|
||||
|
||||
// ESP32 has special rules about ISR code
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#define INTERRUPT_ATTR IRAM_ATTR
|
||||
#else
|
||||
#define INTERRUPT_ATTR
|
||||
#endif
|
||||
|
||||
class RadioLibInterface : public RadioInterface
|
||||
{
|
||||
enum PendingISR { ISR_NONE = 0, ISR_RX, ISR_TX };
|
||||
|
||||
/**
|
||||
* What sort of interrupt do we expect our helper thread to now handle */
|
||||
volatile PendingISR pending = ISR_NONE;
|
||||
|
||||
/** Our ISR code currently needs this to find our active instance
|
||||
*/
|
||||
static RadioLibInterface *instance;
|
||||
|
||||
/**
|
||||
* Raw ISR handler that just calls our polymorphic method
|
||||
*/
|
||||
static void isrTxLevel0();
|
||||
|
||||
/**
|
||||
* Debugging counts
|
||||
*/
|
||||
uint32_t rxBad = 0, rxGood = 0, txGood = 0;
|
||||
|
||||
protected:
|
||||
float bw = 125;
|
||||
uint8_t sf = 9;
|
||||
uint8_t cr = 7;
|
||||
|
||||
/**
|
||||
* FIXME, use a meshtastic sync word, but hashed with the Channel name. Currently picking the same default
|
||||
* the RF95 used (0x14). Note: do not use 0x34 - that is reserved for lorawan
|
||||
*/
|
||||
uint8_t syncWord = SX126X_SYNC_WORD_PRIVATE;
|
||||
|
||||
float currentLimit = 100; // FIXME
|
||||
uint16_t preambleLength = 8; // 8 is default, but FIXME use longer to increase the amount of sleep time when receiving
|
||||
|
||||
Module module; // The HW interface to the radio
|
||||
|
||||
/**
|
||||
* provides lowest common denominator RadioLib API
|
||||
*/
|
||||
PhysicalLayer *iface;
|
||||
|
||||
/// are _trying_ to receive a packet currently (note - we might just be waiting for one)
|
||||
bool isReceiving;
|
||||
|
||||
/**
|
||||
* Glue functions called from ISR land
|
||||
*/
|
||||
virtual void disableInterrupt() = 0;
|
||||
|
||||
/**
|
||||
* Enable a particular ISR callback glue function
|
||||
*/
|
||||
virtual void enableInterrupt(void (*)()) = 0;
|
||||
|
||||
public:
|
||||
RadioLibInterface(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy, SPIClass &spi,
|
||||
PhysicalLayer *iface = NULL);
|
||||
|
||||
virtual ErrorCode send(MeshPacket *p);
|
||||
|
||||
// methods from radiohead
|
||||
|
||||
virtual void loop(); // Idle processing
|
||||
|
||||
/**
|
||||
* Return true if we think the board can go to sleep (i.e. our tx queue is empty, we are not sending or receiving)
|
||||
*
|
||||
* This method must be used before putting the CPU into deep or light sleep.
|
||||
*/
|
||||
virtual bool canSleep();
|
||||
|
||||
private:
|
||||
/** start an immediate transmit */
|
||||
void startSend(MeshPacket *txp);
|
||||
|
||||
/** start a queued transmit (if we have one), else start receiving */
|
||||
void startNextWork();
|
||||
|
||||
void handleTransmitInterrupt();
|
||||
void handleReceiveInterrupt();
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Convert our modemConfig enum into wf, sf, etc...
|
||||
*/
|
||||
void applyModemConfig();
|
||||
|
||||
/** Could we send right now (i.e. either not actively receiving or transmitting)? */
|
||||
bool canSendImmediately();
|
||||
|
||||
/** are we actively receiving a packet (only called during receiving state) */
|
||||
virtual bool isActivelyReceiving() = 0;
|
||||
|
||||
/**
|
||||
* Start waiting to receive a message
|
||||
*/
|
||||
virtual void startReceive() = 0;
|
||||
|
||||
/**
|
||||
* Raw ISR handler that just calls our polymorphic method
|
||||
*/
|
||||
static void isrRxLevel0();
|
||||
|
||||
/**
|
||||
* If a send was in progress finish it and return the buffer to the pool */
|
||||
void completeSending();
|
||||
|
||||
/**
|
||||
* Add SNR data to received messages
|
||||
*/
|
||||
virtual void addReceiveMetadata(MeshPacket *mp) = 0;
|
||||
};
|
||||
63
src/mesh/RadioLibRF95.cpp
Normal file
63
src/mesh/RadioLibRF95.cpp
Normal file
@@ -0,0 +1,63 @@
|
||||
#include "RadioLibRF95.h"
|
||||
|
||||
#define RF95_CHIP_VERSION 0x12
|
||||
#define RF95_ALT_VERSION 0x11 // Supposedly some versions of the chip have id 0x11
|
||||
|
||||
RadioLibRF95::RadioLibRF95(Module *mod) : SX1278(mod) {}
|
||||
|
||||
int16_t RadioLibRF95::begin(float freq, float bw, uint8_t sf, uint8_t cr, uint8_t syncWord, int8_t power, uint8_t currentLimit,
|
||||
uint16_t preambleLength, uint8_t gain)
|
||||
{
|
||||
// execute common part
|
||||
int16_t state = SX127x::begin(RF95_CHIP_VERSION, syncWord, currentLimit, preambleLength);
|
||||
if (state != ERR_NONE)
|
||||
state = SX127x::begin(RF95_ALT_VERSION, syncWord, currentLimit, preambleLength);
|
||||
RADIOLIB_ASSERT(state);
|
||||
|
||||
// configure settings not accessible by API
|
||||
state = config();
|
||||
RADIOLIB_ASSERT(state);
|
||||
|
||||
// configure publicly accessible settings
|
||||
state = setFrequency(freq);
|
||||
RADIOLIB_ASSERT(state);
|
||||
|
||||
state = setBandwidth(bw);
|
||||
RADIOLIB_ASSERT(state);
|
||||
|
||||
state = setSpreadingFactor(sf);
|
||||
RADIOLIB_ASSERT(state);
|
||||
|
||||
state = setCodingRate(cr);
|
||||
RADIOLIB_ASSERT(state);
|
||||
|
||||
state = setOutputPower(power);
|
||||
RADIOLIB_ASSERT(state);
|
||||
|
||||
state = setGain(gain);
|
||||
|
||||
return (state);
|
||||
}
|
||||
|
||||
int16_t RadioLibRF95::setFrequency(float freq)
|
||||
{
|
||||
// RADIOLIB_CHECK_RANGE(freq, 862.0, 1020.0, ERR_INVALID_FREQUENCY);
|
||||
|
||||
// set frequency
|
||||
return (SX127x::setFrequencyRaw(freq));
|
||||
}
|
||||
|
||||
#define RH_RF95_MODEM_STATUS_CLEAR 0x10
|
||||
#define RH_RF95_MODEM_STATUS_HEADER_INFO_VALID 0x08
|
||||
#define RH_RF95_MODEM_STATUS_RX_ONGOING 0x04
|
||||
#define RH_RF95_MODEM_STATUS_SIGNAL_SYNCHRONIZED 0x02
|
||||
#define RH_RF95_MODEM_STATUS_SIGNAL_DETECTED 0x01
|
||||
|
||||
bool RadioLibRF95::isReceiving()
|
||||
{
|
||||
// 0x0b == Look for header info valid, signal synchronized or signal detected
|
||||
uint8_t reg = _mod->SPIreadRegister(SX127X_REG_MODEM_STAT) & 0x1f;
|
||||
// Serial.printf("reg %x\n", reg);
|
||||
return (reg & (RH_RF95_MODEM_STATUS_SIGNAL_DETECTED | RH_RF95_MODEM_STATUS_SIGNAL_SYNCHRONIZED |
|
||||
RH_RF95_MODEM_STATUS_HEADER_INFO_VALID)) != 0;
|
||||
}
|
||||
70
src/mesh/RadioLibRF95.h
Normal file
70
src/mesh/RadioLibRF95.h
Normal file
@@ -0,0 +1,70 @@
|
||||
#pragma once
|
||||
#include <RadioLib.h>
|
||||
|
||||
/*!
|
||||
\class RFM95
|
||||
|
||||
\brief Derived class for %RFM95 modules. Overrides some methods from SX1278 due to different parameter ranges.
|
||||
*/
|
||||
class RadioLibRF95: public SX1278 {
|
||||
public:
|
||||
|
||||
// constructor
|
||||
|
||||
/*!
|
||||
\brief Default constructor. Called from Arduino sketch when creating new LoRa instance.
|
||||
|
||||
\param mod Instance of Module that will be used to communicate with the %LoRa chip.
|
||||
*/
|
||||
RadioLibRF95(Module* mod);
|
||||
|
||||
// basic methods
|
||||
|
||||
/*!
|
||||
\brief %LoRa modem initialization method. Must be called at least once from Arduino sketch to initialize the module.
|
||||
|
||||
\param freq Carrier frequency in MHz. Allowed values range from 868.0 MHz to 915.0 MHz.
|
||||
|
||||
\param bw %LoRa link bandwidth in kHz. Allowed values are 10.4, 15.6, 20.8, 31.25, 41.7, 62.5, 125, 250 and 500 kHz.
|
||||
|
||||
\param sf %LoRa link spreading factor. Allowed values range from 6 to 12.
|
||||
|
||||
\param cr %LoRa link coding rate denominator. Allowed values range from 5 to 8.
|
||||
|
||||
\param syncWord %LoRa sync word. Can be used to distinguish different networks. Note that value 0x34 is reserved for LoRaWAN networks.
|
||||
|
||||
\param power Transmission output power in dBm. Allowed values range from 2 to 17 dBm.
|
||||
|
||||
\param currentLimit Trim value for OCP (over current protection) in mA. Can be set to multiplies of 5 in range 45 to 120 mA and to multiples of 10 in range 120 to 240 mA.
|
||||
Set to 0 to disable OCP (not recommended).
|
||||
|
||||
\param preambleLength Length of %LoRa transmission preamble in symbols. The actual preamble length is 4.25 symbols longer than the set number.
|
||||
Allowed values range from 6 to 65535.
|
||||
|
||||
\param gain Gain of receiver LNA (low-noise amplifier). Can be set to any integer in range 1 to 6 where 1 is the highest gain.
|
||||
Set to 0 to enable automatic gain control (recommended).
|
||||
|
||||
\returns \ref status_codes
|
||||
*/
|
||||
int16_t begin(float freq = 915.0, float bw = 125.0, uint8_t sf = 9, uint8_t cr = 7, uint8_t syncWord = SX127X_SYNC_WORD, int8_t power = 17, uint8_t currentLimit = 100, uint16_t preambleLength = 8, uint8_t gain = 0);
|
||||
|
||||
// configuration methods
|
||||
|
||||
/*!
|
||||
\brief Sets carrier frequency. Allowed values range from 868.0 MHz to 915.0 MHz.
|
||||
|
||||
\param freq Carrier frequency to be set in MHz.
|
||||
|
||||
\returns \ref status_codes
|
||||
*/
|
||||
int16_t setFrequency(float freq);
|
||||
|
||||
// Return true if we are actively receiving a message currently
|
||||
bool isReceiving();
|
||||
|
||||
#ifndef RADIOLIB_GODMODE
|
||||
private:
|
||||
#endif
|
||||
|
||||
};
|
||||
|
||||
78
src/mesh/Router.cpp
Normal file
78
src/mesh/Router.cpp
Normal file
@@ -0,0 +1,78 @@
|
||||
#include "Router.h"
|
||||
#include "configuration.h"
|
||||
#include "mesh-pb-constants.h"
|
||||
|
||||
/**
|
||||
* Router todo
|
||||
*
|
||||
* DONE: Implement basic interface and use it elsewhere in app
|
||||
* Add naive flooding mixin (& drop duplicate rx broadcasts), add tools for sending broadcasts with incrementing sequence #s
|
||||
* Add an optional adjacent node only 'send with ack' mixin. If we timeout waiting for the ack, call handleAckTimeout(packet)
|
||||
* Add DSR mixin
|
||||
*
|
||||
**/
|
||||
|
||||
#define MAX_RX_FROMRADIO \
|
||||
4 // max number of packets destined to our queue, we dispatch packets quickly so it doesn't need to be big
|
||||
|
||||
// I think this is right, one packet for each of the three fifos + one packet being currently assembled for TX or RX
|
||||
#define MAX_PACKETS \
|
||||
(MAX_RX_TOPHONE + MAX_RX_FROMRADIO + MAX_TX_QUEUE + \
|
||||
2) // max number of packets which can be in flight (either queued from reception or queued for sending)
|
||||
|
||||
MemoryPool<MeshPacket> packetPool(MAX_PACKETS);
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* Currently we only allow one interface, that may change in the future
|
||||
*/
|
||||
Router::Router() : fromRadioQueue(MAX_RX_FROMRADIO) {}
|
||||
|
||||
/**
|
||||
* do idle processing
|
||||
* Mostly looking in our incoming rxPacket queue and calling handleReceived.
|
||||
*/
|
||||
void Router::loop()
|
||||
{
|
||||
if (iface)
|
||||
iface->loop();
|
||||
|
||||
MeshPacket *mp;
|
||||
while ((mp = fromRadioQueue.dequeuePtr(0)) != NULL) {
|
||||
handleReceived(mp);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a packet on a suitable interface. This routine will
|
||||
* later free() the packet to pool. This routine is not allowed to stall.
|
||||
* If the txmit queue is full it might return an error
|
||||
*/
|
||||
ErrorCode Router::send(MeshPacket *p)
|
||||
{
|
||||
if (iface) {
|
||||
DEBUG_MSG("Sending packet via interface fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id);
|
||||
return iface->send(p);
|
||||
} else {
|
||||
DEBUG_MSG("Dropping packet - no interfaces - fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id);
|
||||
return ERRNO_NO_INTERFACES;
|
||||
}
|
||||
}
|
||||
|
||||
#include "GPS.h"
|
||||
|
||||
/**
|
||||
* Handle any packet that is received by an interface on this node.
|
||||
* Note: some packets may merely being passed through this node and will be forwarded elsewhere.
|
||||
*/
|
||||
void Router::handleReceived(MeshPacket *p)
|
||||
{
|
||||
// FIXME, this class shouldn't EVER need to know about the GPS, move getValidTime() into a non gps dependent function
|
||||
// Also, we should set the time from the ISR and it should have msec level resolution
|
||||
p->rx_time = gps.getValidTime(); // store the arrival timestamp for the phone
|
||||
|
||||
DEBUG_MSG("Notifying observers of received packet fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id);
|
||||
notifyPacketReceived.notifyObservers(p);
|
||||
packetPool.release(p);
|
||||
}
|
||||
67
src/mesh/Router.h
Normal file
67
src/mesh/Router.h
Normal file
@@ -0,0 +1,67 @@
|
||||
#pragma once
|
||||
|
||||
#include "MemoryPool.h"
|
||||
#include "MeshTypes.h"
|
||||
#include "Observer.h"
|
||||
#include "PointerQueue.h"
|
||||
#include "RadioInterface.h"
|
||||
#include "mesh.pb.h"
|
||||
|
||||
/**
|
||||
* A mesh aware router that supports multiple interfaces.
|
||||
*/
|
||||
class Router
|
||||
{
|
||||
private:
|
||||
RadioInterface *iface;
|
||||
|
||||
/// Packets which have just arrived from the radio, ready to be processed by this service and possibly
|
||||
/// forwarded to the phone.
|
||||
PointerQueue<MeshPacket> fromRadioQueue;
|
||||
|
||||
public:
|
||||
/// Local services that want to see _every_ packet this node receives can observe this.
|
||||
/// Observers should always return 0 and _copy_ any packets they want to keep for use later (this packet will be getting
|
||||
/// freed)
|
||||
Observable<const MeshPacket *> notifyPacketReceived;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
*/
|
||||
Router();
|
||||
|
||||
/**
|
||||
* Currently we only allow one interface, that may change in the future
|
||||
*/
|
||||
void addInterface(RadioInterface *_iface)
|
||||
{
|
||||
iface = _iface;
|
||||
iface->setReceiver(&fromRadioQueue);
|
||||
}
|
||||
|
||||
/**
|
||||
* do idle processing
|
||||
* Mostly looking in our incoming rxPacket queue and calling handleReceived.
|
||||
*/
|
||||
void loop();
|
||||
|
||||
/**
|
||||
* Send a packet on a suitable interface. This routine will
|
||||
* later free() the packet to pool. This routine is not allowed to stall.
|
||||
* If the txmit queue is full it might return an error
|
||||
*/
|
||||
virtual ErrorCode send(MeshPacket *p);
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Called from loop()
|
||||
* Handle any packet that is received by an interface on this node.
|
||||
* Note: some packets may merely being passed through this node and will be forwarded elsewhere.
|
||||
*
|
||||
* Note: this method will free the provided packet
|
||||
*/
|
||||
virtual void handleReceived(MeshPacket *p);
|
||||
};
|
||||
|
||||
extern Router &router;
|
||||
115
src/mesh/SX1262Interface.cpp
Normal file
115
src/mesh/SX1262Interface.cpp
Normal file
@@ -0,0 +1,115 @@
|
||||
#include "SX1262Interface.h"
|
||||
#include <configuration.h>
|
||||
|
||||
SX1262Interface::SX1262Interface(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy,
|
||||
SPIClass &spi)
|
||||
: RadioLibInterface(cs, irq, rst, busy, spi, &lora), lora(&module)
|
||||
{
|
||||
}
|
||||
|
||||
/// Initialise the Driver transport hardware and software.
|
||||
/// Make sure the Driver is properly configured before calling init().
|
||||
/// \return true if initialisation succeeded.
|
||||
bool SX1262Interface::init()
|
||||
{
|
||||
float tcxoVoltage = 0; // None - we use an XTAL
|
||||
bool useRegulatorLDO = false; // Seems to depend on the connection to pin 9/DCC_SW - if an inductor DCDC?
|
||||
|
||||
applyModemConfig();
|
||||
if (power > 22) // This chip has lower power limits than some
|
||||
power = 22;
|
||||
int res = lora.begin(freq, bw, sf, cr, syncWord, power, currentLimit, preambleLength, tcxoVoltage, useRegulatorLDO);
|
||||
DEBUG_MSG("LORA init result %d\n", res);
|
||||
|
||||
if (res == ERR_NONE)
|
||||
res = lora.setCRC(SX126X_LORA_CRC_ON);
|
||||
|
||||
if (res == ERR_NONE)
|
||||
startReceive(); // start receiving
|
||||
|
||||
return res == ERR_NONE;
|
||||
}
|
||||
|
||||
bool SX1262Interface::reconfigure()
|
||||
{
|
||||
applyModemConfig();
|
||||
|
||||
// set mode to standby
|
||||
setStandby();
|
||||
|
||||
// configure publicly accessible settings
|
||||
int err = lora.setSpreadingFactor(sf);
|
||||
assert(err == ERR_NONE);
|
||||
|
||||
err = lora.setBandwidth(bw);
|
||||
assert(err == ERR_NONE);
|
||||
|
||||
err = lora.setCodingRate(cr);
|
||||
assert(err == ERR_NONE);
|
||||
|
||||
err = lora.setSyncWord(syncWord);
|
||||
assert(err == ERR_NONE);
|
||||
|
||||
err = lora.setCurrentLimit(currentLimit);
|
||||
assert(err == ERR_NONE);
|
||||
|
||||
err = lora.setPreambleLength(preambleLength);
|
||||
assert(err == ERR_NONE);
|
||||
|
||||
err = lora.setFrequency(freq);
|
||||
assert(err == ERR_NONE);
|
||||
|
||||
if (power > 22) // This chip has lower power limits than some
|
||||
power = 22;
|
||||
err = lora.setOutputPower(power);
|
||||
assert(err == ERR_NONE);
|
||||
|
||||
startReceive(); // restart receiving
|
||||
|
||||
return ERR_NONE;
|
||||
}
|
||||
|
||||
void SX1262Interface::setStandby()
|
||||
{
|
||||
int err = lora.standby();
|
||||
assert(err == ERR_NONE);
|
||||
|
||||
isReceiving = false; // If we were receiving, not any more
|
||||
disableInterrupt();
|
||||
completeSending(); // If we were sending, not anymore
|
||||
}
|
||||
|
||||
/**
|
||||
* Add SNR data to received messages
|
||||
*/
|
||||
void SX1262Interface::addReceiveMetadata(MeshPacket *mp)
|
||||
{
|
||||
mp->rx_snr = lora.getSNR();
|
||||
}
|
||||
|
||||
void SX1262Interface::startReceive()
|
||||
{
|
||||
setStandby();
|
||||
int err = lora.startReceive();
|
||||
assert(err == ERR_NONE);
|
||||
|
||||
isReceiving = true;
|
||||
|
||||
// Must be done AFTER, starting transmit, because startTransmit clears (possibly stale) interrupt pending register bits
|
||||
enableInterrupt(isrRxLevel0);
|
||||
}
|
||||
|
||||
/** Could we send right now (i.e. either not actively receving or transmitting)? */
|
||||
bool SX1262Interface::isActivelyReceiving()
|
||||
{
|
||||
return lora.getPacketLength() > 0;
|
||||
}
|
||||
|
||||
bool SX1262Interface::sleep()
|
||||
{
|
||||
// put chipset into sleep mode
|
||||
disableInterrupt();
|
||||
lora.sleep();
|
||||
|
||||
return true;
|
||||
}
|
||||
53
src/mesh/SX1262Interface.h
Normal file
53
src/mesh/SX1262Interface.h
Normal file
@@ -0,0 +1,53 @@
|
||||
#pragma once
|
||||
|
||||
#include "RadioLibInterface.h"
|
||||
|
||||
/**
|
||||
* Our adapter for SX1262 radios
|
||||
*/
|
||||
class SX1262Interface : public RadioLibInterface
|
||||
{
|
||||
SX1262 lora;
|
||||
|
||||
public:
|
||||
SX1262Interface(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy, SPIClass &spi);
|
||||
|
||||
/// Initialise the Driver transport hardware and software.
|
||||
/// Make sure the Driver is properly configured before calling init().
|
||||
/// \return true if initialisation succeeded.
|
||||
virtual bool init();
|
||||
|
||||
/// Apply any radio provisioning changes
|
||||
/// Make sure the Driver is properly configured before calling init().
|
||||
/// \return true if initialisation succeeded.
|
||||
virtual bool reconfigure();
|
||||
|
||||
/// Prepare hardware for sleep. Call this _only_ for deep sleep, not needed for light sleep.
|
||||
virtual bool sleep();
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Glue functions called from ISR land
|
||||
*/
|
||||
virtual void INTERRUPT_ATTR disableInterrupt() { lora.clearDio1Action(); }
|
||||
|
||||
/**
|
||||
* Enable a particular ISR callback glue function
|
||||
*/
|
||||
virtual void enableInterrupt(void (*callback)()) { lora.setDio1Action(callback); }
|
||||
|
||||
/** are we actively receiving a packet (only called during receiving state) */
|
||||
virtual bool isActivelyReceiving();
|
||||
|
||||
/**
|
||||
* Start waiting to receive a message
|
||||
*/
|
||||
virtual void startReceive();
|
||||
/**
|
||||
* Add SNR data to received messages
|
||||
*/
|
||||
virtual void addReceiveMetadata(MeshPacket *mp);
|
||||
|
||||
private:
|
||||
void setStandby();
|
||||
};
|
||||
37
src/mesh/TypedQueue.h
Normal file
37
src/mesh/TypedQueue.h
Normal file
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
#include <type_traits>
|
||||
|
||||
#include "freertosinc.h"
|
||||
|
||||
/**
|
||||
* A wrapper for freertos queues. Note: each element object should be small
|
||||
* and POD (Plain Old Data type) as elements are memcpied by value.
|
||||
*/
|
||||
template <class T> class TypedQueue
|
||||
{
|
||||
static_assert(std::is_pod<T>::value, "T must be pod");
|
||||
QueueHandle_t h;
|
||||
|
||||
public:
|
||||
TypedQueue(int maxElements)
|
||||
{
|
||||
h = xQueueCreate(maxElements, sizeof(T));
|
||||
assert(h);
|
||||
}
|
||||
|
||||
~TypedQueue() { vQueueDelete(h); }
|
||||
|
||||
int numFree() { return uxQueueSpacesAvailable(h); }
|
||||
|
||||
bool isEmpty() { return uxQueueMessagesWaiting(h) == 0; }
|
||||
|
||||
bool enqueue(T x, TickType_t maxWait = portMAX_DELAY) { return xQueueSendToBack(h, &x, maxWait) == pdTRUE; }
|
||||
|
||||
bool enqueueFromISR(T x, BaseType_t *higherPriWoken) { return xQueueSendToBackFromISR(h, &x, higherPriWoken) == pdTRUE; }
|
||||
|
||||
bool dequeue(T *p, TickType_t maxWait = portMAX_DELAY) { return xQueueReceive(h, p, maxWait) == pdTRUE; }
|
||||
|
||||
bool dequeueFromISR(T *p, BaseType_t *higherPriWoken) { return xQueueReceiveFromISR(h, p, higherPriWoken); }
|
||||
};
|
||||
Reference in New Issue
Block a user