mirror of
https://github.com/meshtastic/firmware.git
synced 2025-06-08 14:12:05 +00:00
mesh sending and receving now much more real
This commit is contained in:
parent
4051bf8465
commit
9aed5056ad
3
TODO.md
3
TODO.md
@ -5,6 +5,7 @@
|
|||||||
* solder debug headers to board
|
* solder debug headers to board
|
||||||
* make message send from android go to service, then to mesh radio
|
* make message send from android go to service, then to mesh radio
|
||||||
* make message receive from radio go through to android
|
* make message receive from radio go through to android
|
||||||
|
* test loopback tx/rx path code without using radio
|
||||||
* have MeshService keep a node DB by sniffing user messages
|
* have MeshService keep a node DB by sniffing user messages
|
||||||
* have meshservice send location data on mesh (if device has a GPS)
|
* have meshservice send location data on mesh (if device has a GPS)
|
||||||
|
|
||||||
@ -20,6 +21,8 @@
|
|||||||
* sendToMesh can currently block for a long time, instead have it just queue a packet for a radio freertos thread
|
* sendToMesh can currently block for a long time, instead have it just queue a packet for a radio freertos thread
|
||||||
* see section 7.3 of https://cdn.sparkfun.com/assets/learn_tutorials/8/0/4/RFM95_96_97_98W.pdf and have hope radio wake only when a valid packet is received. Possibly even wake the ESP32 from deep sleep via GPIO.
|
* see section 7.3 of https://cdn.sparkfun.com/assets/learn_tutorials/8/0/4/RFM95_96_97_98W.pdf and have hope radio wake only when a valid packet is received. Possibly even wake the ESP32 from deep sleep via GPIO.
|
||||||
* fix the logo
|
* fix the logo
|
||||||
|
* do debug logging to android over bluetooth
|
||||||
|
* break out my bluetooth OTA software as a seperate library so others can use it
|
||||||
|
|
||||||
# Pre-beta priority
|
# Pre-beta priority
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
|
||||||
|
#include "PointerQueue.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A pool based allocator
|
* A pool based allocator
|
||||||
@ -10,12 +11,13 @@
|
|||||||
* Eventually this routine will even be safe for ISR use...
|
* Eventually this routine will even be safe for ISR use...
|
||||||
*/
|
*/
|
||||||
template <class T> class MemoryPool {
|
template <class T> class MemoryPool {
|
||||||
TypedQueue<T *> dead;
|
PointerQueue<T> dead;
|
||||||
|
|
||||||
T *buf; // our large raw block of memory
|
T *buf; // our large raw block of memory
|
||||||
|
|
||||||
|
size_t maxElements;
|
||||||
public:
|
public:
|
||||||
MemoryPool(int maxElements): queued(maxElements), dead(maxElements) {
|
MemoryPool(size_t _maxElements): dead(_maxElements), maxElements(_maxElements) {
|
||||||
buf = new T[maxElements];
|
buf = new T[maxElements];
|
||||||
|
|
||||||
// prefill dead
|
// prefill dead
|
||||||
@ -29,19 +31,28 @@ public:
|
|||||||
|
|
||||||
/// Return a queable object which has been prefilled with zeros
|
/// Return a queable object which has been prefilled with zeros
|
||||||
T *allocZeroed(TickType_t maxWait = portMAX_DELAY) {
|
T *allocZeroed(TickType_t maxWait = portMAX_DELAY) {
|
||||||
T *p;
|
T *p = dead.dequeuePtr(maxWait);
|
||||||
|
|
||||||
if(dead.dequeue(&p, maxWait) != pdTRUE)
|
if(p)
|
||||||
return NULL;
|
memset(p, 0, sizeof(T));
|
||||||
|
|
||||||
memset(p, 0, sizeof(T));
|
|
||||||
return p;
|
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)
|
||||||
|
memcpy(p, &src, sizeof(T));
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Return a buffer for use by others
|
/// Return a buffer for use by others
|
||||||
void free(T *p) {
|
void release(T *p) {
|
||||||
int res = dead.enqueue(p, 0);
|
int res = dead.enqueue(p, 0);
|
||||||
assert(res == pdTRUE);
|
assert(res == pdTRUE);
|
||||||
|
assert(p >= buf && (p - buf) < maxElements); // sanity check to make sure a programmer didn't free something that didn't come from this pool
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -8,148 +8,8 @@
|
|||||||
#include <pb_encode.h>
|
#include <pb_encode.h>
|
||||||
#include <pb_decode.h>
|
#include <pb_decode.h>
|
||||||
#include "mesh.pb.h"
|
#include "mesh.pb.h"
|
||||||
#include "MeshRadio.h"
|
#include "MeshService.h"
|
||||||
#include "TypedQueue.h"
|
|
||||||
#include "MemoryPool.h"
|
|
||||||
|
|
||||||
/*
|
|
||||||
receivedPacketQueue - this is a queue of messages we've received from the mesh, which we are keeping to deliver to the phone.
|
|
||||||
It is implemented with a FreeRTos queue (wrapped with a little RTQueue class) of pointers to MeshPacket protobufs (which were alloced with new).
|
|
||||||
After a packet ptr is removed from the queue and processed it should be deleted. (eventually we should move sent packets into a 'sentToPhone' queue
|
|
||||||
of packets we can delete just as soon as we are sure the phone has acked those packets - when the phone writes to FromNum)
|
|
||||||
|
|
||||||
mesh - an instance of Mesh class. Which manages the interface to the mesh radio library, reception of packets from other nodes, arbitrating to select
|
|
||||||
a node number and keeping the current nodedb.
|
|
||||||
|
|
||||||
|
|
||||||
typedef in32_t NodeNum;
|
|
||||||
|
|
||||||
class NodeInfo {
|
|
||||||
position;
|
|
||||||
last_seen
|
|
||||||
user
|
|
||||||
};
|
|
||||||
|
|
||||||
class NodeDB {
|
|
||||||
NodeNum provisionalNodeNum; // if we are trying to find a node num this is our current attempt
|
|
||||||
|
|
||||||
NodeNum ourNodeNum; // -1 if not yet found
|
|
||||||
|
|
||||||
HashMap<NodeNum, NodeInfo> nodes;
|
|
||||||
public:
|
|
||||||
/// don't do mesh based algoritm for node id assignment (initially) - instead just store in flash - possibly even in the initial alpha release do this hack
|
|
||||||
|
|
||||||
/// if returns false, that means our node should send a DenyNodeNum response. If true, we think the number is okay for use
|
|
||||||
// bool handleWantNodeNum(NodeNum n);
|
|
||||||
|
|
||||||
void handleDenyNodeNum(NodeNum FIXME read mesh proto docs, perhaps picking a random node num is not a great idea
|
|
||||||
and instead we should use a special 'im unconfigured node number' and include our desired node number in the wantnum message. the
|
|
||||||
unconfigured node num would only be used while initially joining the mesh so low odds of conflicting (especially if we randomly select
|
|
||||||
from a small number of nodenums which can be used temporarily for this operation). figure out what the lower level
|
|
||||||
mesh sw does if it does conflict? would it be better for people who are replying with denynode num to just broadcast their denial?)
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
#define MAX_PACKETS 32 // max number of packets which can be in flight (either queued from reception or queued for sending)
|
|
||||||
#define MAX_RX_TOPHONE 16 // max number of packets which can be waiting for delivery to android
|
|
||||||
|
|
||||||
/// A temporary buffer used for sending packets, sized to hold the biggest buffer we might need
|
|
||||||
static uint8_t outbuf[MeshPacket_size];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Top level app for this service. keeps the mesh, the radio config and the queue of received packets.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
class MeshService
|
|
||||||
{
|
|
||||||
MemoryPool<MeshPacket> packetPool;
|
|
||||||
|
|
||||||
/// received packets waiting for the phone to process them
|
|
||||||
/// FIXME, change to a DropOldestQueue and keep a count of the number of dropped packets to ensure
|
|
||||||
/// we never hang because android hasn't been there in a while
|
|
||||||
PointerQueue<MeshPacket> toPhoneQueue;
|
|
||||||
|
|
||||||
/// Packets which have just arrived from the radio, ready to be processed by this service and possibly
|
|
||||||
/// forwarded to the phone. Note: not using yet - seeing if I can just handle everything asap in handleFromRadio
|
|
||||||
// PointerQueue<MeshPacket> fromRadioQueue;
|
|
||||||
|
|
||||||
public:
|
|
||||||
MeshService() : packetPool(MAX_PACKETS), toPhoneQueue(MAX_RX_TOPHONE) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Do idle processing (mostly processing messages which have been queued from the radio)
|
|
||||||
// void loop() { }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* handle an incoming MeshPacket from the radio, update DB state and queue it for the phone
|
|
||||||
*/
|
|
||||||
void handleFromRadio(NodeNum from, NodeNum to, const uint8_t *buf, size_t len) {
|
|
||||||
MeshPacket *p = packetPool.allocZeroed();
|
|
||||||
assert(p);
|
|
||||||
|
|
||||||
pb_istream_t stream = pb_istream_from_buffer(buf, len);
|
|
||||||
if (!pb_decode(&stream, MeshPacket_fields, p) || !p->has_payload)
|
|
||||||
{
|
|
||||||
Serial.printf("Error: can't decode MeshPacket %s\n", PB_GET_ERROR(&stream));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// FIXME - update DB state based on payload and show recevied texts
|
|
||||||
|
|
||||||
toPhoneQueue.enqueue(p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Given a ToRadio buffer parse it and properly handle it (setup radio, owner or send packet into the mesh)
|
|
||||||
void handleToRadio(std::string s)
|
|
||||||
{
|
|
||||||
static ToRadio r; // new ToRadio(); FIXME dynamically allocate
|
|
||||||
|
|
||||||
pb_istream_t stream = pb_istream_from_buffer((const uint8_t *)s.c_str(), s.length());
|
|
||||||
if (!pb_decode(&stream, ToRadio_fields, &r))
|
|
||||||
{
|
|
||||||
Serial.printf("Error: can't decode ToRadio %s\n", PB_GET_ERROR(&stream));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
switch (r.which_variant)
|
|
||||||
{
|
|
||||||
case ToRadio_packet_tag:
|
|
||||||
sendToMesh(r.variant.packet);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
Serial.println("Error: unexpected ToRadio variant");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
/// Send a packet into the mesh
|
|
||||||
void sendToMesh(const MeshPacket &p)
|
|
||||||
{
|
|
||||||
assert(p.has_payload);
|
|
||||||
|
|
||||||
pb_ostream_t stream = pb_ostream_from_buffer(outbuf, sizeof(outbuf));
|
|
||||||
if (!pb_encode(&stream, MeshPacket_fields, &p))
|
|
||||||
{
|
|
||||||
Serial.printf("Error: can't encode MeshPacket %s\n", PB_GET_ERROR(&stream));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
assert(radio.sendTo(p.to, outbuf, stream.bytes_written) == ERRNO_OK);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
MeshService service;
|
|
||||||
|
|
||||||
static BLECharacteristic meshFromRadioCharacteristic("8ba2bcc2-ee02-4a55-a531-c525c5e454d5", BLECharacteristic::PROPERTY_READ);
|
static BLECharacteristic meshFromRadioCharacteristic("8ba2bcc2-ee02-4a55-a531-c525c5e454d5", BLECharacteristic::PROPERTY_READ);
|
||||||
static BLECharacteristic meshToRadioCharacteristic("f75c76d2-129e-4dad-a1dd-7866124401e7", BLECharacteristic::PROPERTY_WRITE);
|
static BLECharacteristic meshToRadioCharacteristic("f75c76d2-129e-4dad-a1dd-7866124401e7", BLECharacteristic::PROPERTY_WRITE);
|
||||||
|
@ -3,103 +3,127 @@
|
|||||||
#include <RHMesh.h>
|
#include <RHMesh.h>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
|
||||||
|
#include <pb_encode.h>
|
||||||
|
#include <pb_decode.h>
|
||||||
#include "MeshRadio.h"
|
#include "MeshRadio.h"
|
||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Change to 434.0 or other frequency, must match RX's freq!
|
// Change to 434.0 or other frequency, must match RX's freq!
|
||||||
#define RF95_FREQ 915.0
|
#define RF95_FREQ 915.0
|
||||||
|
|
||||||
MeshRadio radio;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* get our starting (provisional) nodenum from flash. But check first if anyone else is using it, by trying to send a message to it (arping)
|
* get our starting (provisional) nodenum from flash. But check first if anyone else is using it, by trying to send a message to it (arping)
|
||||||
*/
|
*/
|
||||||
NodeNum getDesiredNodeNum() {
|
NodeNum getDesiredNodeNum()
|
||||||
uint8_t dmac[6];
|
|
||||||
esp_efuse_mac_get_default(dmac);
|
|
||||||
|
|
||||||
// FIXME not the right way to guess node numes
|
|
||||||
uint8_t r = dmac[5];
|
|
||||||
assert(r != 0xff); // It better not be the broadcast address
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
MeshRadio::MeshRadio() : rf95(NSS_GPIO, DIO0_GPIO), manager(rf95, getDesiredNodeNum()) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
bool MeshRadio::init() {
|
|
||||||
pinMode(RESET_GPIO, OUTPUT); // Deassert reset
|
|
||||||
digitalWrite(RESET_GPIO, HIGH);
|
|
||||||
|
|
||||||
// pulse reset
|
|
||||||
digitalWrite(RESET_GPIO, LOW);
|
|
||||||
delay(10);
|
|
||||||
digitalWrite(RESET_GPIO, HIGH);
|
|
||||||
delay(10);
|
|
||||||
|
|
||||||
if (!manager.init()) {
|
|
||||||
Serial.println("LoRa radio init failed");
|
|
||||||
Serial.println("Uncomment '#define SERIAL_DEBUG' in RH_RF95.cpp for detailed debug info");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Serial.println("LoRa radio init OK!");
|
|
||||||
|
|
||||||
// Defaults after init are 434.0MHz, modulation GFSK_Rb250Fd250, +13dbM
|
|
||||||
if (!rf95.setFrequency(RF95_FREQ)) {
|
|
||||||
Serial.println("setFrequency failed");
|
|
||||||
while (1);
|
|
||||||
}
|
|
||||||
Serial.print("Set Freq to: "); Serial.println(RF95_FREQ);
|
|
||||||
|
|
||||||
// Defaults after init are 434.0MHz, 13dBm, Bw = 125 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on
|
|
||||||
|
|
||||||
// The default transmitter power is 13dBm, using PA_BOOST.
|
|
||||||
// If you are using RFM95/96/97/98 modules which uses the PA_BOOST transmitter pin, then
|
|
||||||
// you can set transmitter powers from 5 to 23 dBm:
|
|
||||||
// FIXME - can we do this? It seems to be in the Heltec board.
|
|
||||||
rf95.setTxPower(23, false);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
ErrorCode MeshRadio::sendTo(NodeNum dest, const uint8_t *buf, size_t len) {
|
|
||||||
Serial.printf("mesh sendTo %d bytes to %d\n", len, dest);
|
|
||||||
// FIXME - for now we do all packets as broadcast
|
|
||||||
dest = NODENUM_BROADCAST;
|
|
||||||
|
|
||||||
// Note: we don't use sendToWait here because we don't want to wait and for the time being don't require
|
|
||||||
// reliable delivery
|
|
||||||
// return manager.sendtoWait((uint8_t *) buf, len, dest);
|
|
||||||
return manager.sendto((uint8_t *) buf, len, dest) ? ERRNO_OK : ERRNO_UNKNOWN;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MeshRadio::loop() {
|
|
||||||
// FIXME read from radio with recvfromAckTimeout
|
|
||||||
}
|
|
||||||
|
|
||||||
void mesh_init() {
|
|
||||||
while (!radio.init()) {
|
|
||||||
Serial.println("radio init failed");
|
|
||||||
while (1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int16_t packetnum = 0; // packet counter, we increment per xmission
|
|
||||||
|
|
||||||
void mesh_loop()
|
|
||||||
{
|
{
|
||||||
radio.loop();
|
uint8_t dmac[6];
|
||||||
|
esp_efuse_mac_get_default(dmac);
|
||||||
|
|
||||||
delay(1000); // Wait 1 second between transmits, could also 'sleep' here!
|
// FIXME not the right way to guess node numes
|
||||||
|
uint8_t r = dmac[5];
|
||||||
|
assert(r != 0xff); // It better not be the broadcast address
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
MeshRadio::MeshRadio(MemoryPool<MeshPacket> &_pool, PointerQueue<MeshPacket> &_rxDest)
|
||||||
|
: rf95(NSS_GPIO, DIO0_GPIO),
|
||||||
|
manager(rf95, getDesiredNodeNum()),
|
||||||
|
pool(_pool),
|
||||||
|
rxDest(_rxDest),
|
||||||
|
txQueue(MAX_TX_QUEUE)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MeshRadio::init()
|
||||||
|
{
|
||||||
|
pinMode(RESET_GPIO, OUTPUT); // Deassert reset
|
||||||
|
digitalWrite(RESET_GPIO, HIGH);
|
||||||
|
|
||||||
|
// pulse reset
|
||||||
|
digitalWrite(RESET_GPIO, LOW);
|
||||||
|
delay(10);
|
||||||
|
digitalWrite(RESET_GPIO, HIGH);
|
||||||
|
delay(10);
|
||||||
|
|
||||||
|
if (!manager.init())
|
||||||
|
{
|
||||||
|
Serial.println("LoRa radio init failed");
|
||||||
|
Serial.println("Uncomment '#define SERIAL_DEBUG' in RH_RF95.cpp for detailed debug info");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial.println("LoRa radio init OK!");
|
||||||
|
|
||||||
|
// Defaults after init are 434.0MHz, modulation GFSK_Rb250Fd250, +13dbM
|
||||||
|
if (!rf95.setFrequency(RF95_FREQ))
|
||||||
|
{
|
||||||
|
Serial.println("setFrequency failed");
|
||||||
|
while (1)
|
||||||
|
;
|
||||||
|
}
|
||||||
|
Serial.print("Set Freq to: ");
|
||||||
|
Serial.println(RF95_FREQ);
|
||||||
|
|
||||||
|
// Defaults after init are 434.0MHz, 13dBm, Bw = 125 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on
|
||||||
|
|
||||||
|
// The default transmitter power is 13dBm, using PA_BOOST.
|
||||||
|
// If you are using RFM95/96/97/98 modules which uses the PA_BOOST transmitter pin, then
|
||||||
|
// you can set transmitter powers from 5 to 23 dBm:
|
||||||
|
// FIXME - can we do this? It seems to be in the Heltec board.
|
||||||
|
rf95.setTxPower(23, false);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ErrorCode MeshRadio::send(MeshPacket *p)
|
||||||
|
{
|
||||||
|
int res = ERRNO_UNKNOWN;
|
||||||
|
|
||||||
|
/// A temporary buffer used for sending packets, sized to hold the biggest buffer we might need
|
||||||
|
static uint8_t outbuf[SubPacket_size];
|
||||||
|
|
||||||
|
assert(p->has_payload);
|
||||||
|
|
||||||
|
pb_ostream_t stream = pb_ostream_from_buffer(outbuf, sizeof(outbuf));
|
||||||
|
if (!pb_encode(&stream, SubPacket_fields, &p->payload))
|
||||||
|
{
|
||||||
|
Serial.printf("Error: can't encode SubPacket %s\n", PB_GET_ERROR(&stream));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
res = sendTo(p->to, outbuf, stream.bytes_written);
|
||||||
|
}
|
||||||
|
|
||||||
|
pool.release(p);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
ErrorCode MeshRadio::sendTo(NodeNum dest, const uint8_t *buf, size_t len)
|
||||||
|
{
|
||||||
|
Serial.printf("mesh sendTo %d bytes to %d\n", len, dest);
|
||||||
|
// FIXME - for now we do all packets as broadcast
|
||||||
|
dest = NODENUM_BROADCAST;
|
||||||
|
|
||||||
|
// Note: we don't use sendToWait here because we don't want to wait and for the time being don't require
|
||||||
|
// reliable delivery
|
||||||
|
// return manager.sendtoWait((uint8_t *) buf, len, dest);
|
||||||
|
return manager.sendto((uint8_t *)buf, len, dest) ? ERRNO_OK : ERRNO_UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MeshRadio::loop()
|
||||||
|
{
|
||||||
|
// FIXME read from radio with recvfromAckTimeout
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
static int16_t packetnum = 0; // packet counter, we increment per xmission
|
||||||
|
|
||||||
char radiopacket[20] = "Hello World # ";
|
char radiopacket[20] = "Hello World # ";
|
||||||
sprintf(radiopacket, "hello %d", packetnum++);
|
sprintf(radiopacket, "hello %d", packetnum++);
|
||||||
|
|
||||||
assert(radio.sendTo(NODENUM_BROADCAST, (uint8_t *)radiopacket, sizeof(radiopacket)) == ERRNO_OK);
|
assert(sendTo(NODENUM_BROADCAST, (uint8_t *)radiopacket, sizeof(radiopacket)) == ERRNO_OK);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// manager.recvfromAckTimeout()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,6 +2,9 @@
|
|||||||
|
|
||||||
#include <RH_RF95.h>
|
#include <RH_RF95.h>
|
||||||
#include <RHMesh.h>
|
#include <RHMesh.h>
|
||||||
|
#include "MemoryPool.h"
|
||||||
|
#include "mesh.pb.h"
|
||||||
|
#include "PointerQueue.h"
|
||||||
|
|
||||||
#define NODENUM_BROADCAST 255
|
#define NODENUM_BROADCAST 255
|
||||||
#define ERRNO_OK 0
|
#define ERRNO_OK 0
|
||||||
@ -10,37 +13,42 @@
|
|||||||
typedef int ErrorCode;
|
typedef int ErrorCode;
|
||||||
typedef uint8_t NodeNum;
|
typedef uint8_t NodeNum;
|
||||||
|
|
||||||
/// Callback for a receive packet, the callee must copy/queue the payload elsewhere before returning
|
#define MAX_TX_QUEUE 4 // max number of packets which can be waiting for transmission
|
||||||
typedef void (*MeshRXHandler)(NodeNum from, NodeNum to, const uint8_t *buf, size_t len);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A raw low level interface to our mesh. Only understands nodenums and bytes (not protobufs or node ids)
|
* A raw low level interface to our mesh. Only understands nodenums and bytes (not protobufs or node ids)
|
||||||
*/
|
*/
|
||||||
class MeshRadio {
|
class MeshRadio {
|
||||||
public:
|
public:
|
||||||
MeshRadio();
|
/** 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
|
||||||
|
*/
|
||||||
|
MeshRadio(MemoryPool<MeshPacket> &pool, PointerQueue<MeshPacket> &rxDest);
|
||||||
|
|
||||||
bool init();
|
bool init();
|
||||||
|
|
||||||
/// Prepare the radio to enter sleep mode, where it should draw only 0.2 uA
|
/// Prepare the radio to enter sleep mode, where it should draw only 0.2 uA
|
||||||
void sleep() { rf95.sleep(); }
|
void sleep() { rf95.sleep(); }
|
||||||
|
|
||||||
/// Send a packet - the current implementation blocks for a while possibly (FIXME)
|
/// Send a packet (possibly by enquing in a private fifo). This routine will
|
||||||
ErrorCode sendTo(NodeNum dest, const uint8_t *buf, size_t len);
|
/// later free() the packet to pool.
|
||||||
|
ErrorCode send(MeshPacket *p);
|
||||||
|
|
||||||
/// Do loop callback operations (we currently FIXME poll the receive mailbox here)
|
/// Do loop callback operations (we currently FIXME poll the receive mailbox here)
|
||||||
/// for received packets it will call the rx handler
|
/// for received packets it will call the rx handler
|
||||||
void loop();
|
void loop();
|
||||||
|
|
||||||
void setRXHandler(MeshRXHandler h) { rxHandler = h; }
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
RH_RF95 rf95; // the raw radio interface
|
RH_RF95 rf95; // the raw radio interface
|
||||||
RHMesh manager;
|
RHMesh manager;
|
||||||
MeshRXHandler rxHandler;
|
// MeshRXHandler rxHandler;
|
||||||
|
|
||||||
|
MemoryPool<MeshPacket> &pool;
|
||||||
|
PointerQueue<MeshPacket> &rxDest;
|
||||||
|
PointerQueue<MeshPacket> txQueue;
|
||||||
|
|
||||||
|
/// low level send, might block for mutiple seconds
|
||||||
|
ErrorCode sendTo(NodeNum dest, const uint8_t *buf, size_t len);
|
||||||
};
|
};
|
||||||
|
|
||||||
extern MeshRadio radio;
|
|
||||||
|
|
||||||
void mesh_init();
|
|
||||||
void mesh_loop();
|
|
103
src/MeshService.cpp
Normal file
103
src/MeshService.cpp
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
#include <pb_encode.h>
|
||||||
|
#include <pb_decode.h>
|
||||||
|
#include "mesh.pb.h"
|
||||||
|
#include "MeshService.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
receivedPacketQueue - this is a queue of messages we've received from the mesh, which we are keeping to deliver to the phone.
|
||||||
|
It is implemented with a FreeRTos queue (wrapped with a little RTQueue class) of pointers to MeshPacket protobufs (which were alloced with new).
|
||||||
|
After a packet ptr is removed from the queue and processed it should be deleted. (eventually we should move sent packets into a 'sentToPhone' queue
|
||||||
|
of packets we can delete just as soon as we are sure the phone has acked those packets - when the phone writes to FromNum)
|
||||||
|
|
||||||
|
mesh - an instance of Mesh class. Which manages the interface to the mesh radio library, reception of packets from other nodes, arbitrating to select
|
||||||
|
a node number and keeping the current nodedb.
|
||||||
|
|
||||||
|
|
||||||
|
typedef in32_t NodeNum;
|
||||||
|
|
||||||
|
class NodeInfo {
|
||||||
|
position;
|
||||||
|
last_seen
|
||||||
|
user
|
||||||
|
};
|
||||||
|
|
||||||
|
class NodeDB {
|
||||||
|
NodeNum provisionalNodeNum; // if we are trying to find a node num this is our current attempt
|
||||||
|
|
||||||
|
NodeNum ourNodeNum; // -1 if not yet found
|
||||||
|
|
||||||
|
HashMap<NodeNum, NodeInfo> nodes;
|
||||||
|
public:
|
||||||
|
/// don't do mesh based algoritm for node id assignment (initially) - instead just store in flash - possibly even in the initial alpha release do this hack
|
||||||
|
|
||||||
|
/// if returns false, that means our node should send a DenyNodeNum response. If true, we think the number is okay for use
|
||||||
|
// bool handleWantNodeNum(NodeNum n);
|
||||||
|
|
||||||
|
void handleDenyNodeNum(NodeNum FIXME read mesh proto docs, perhaps picking a random node num is not a great idea
|
||||||
|
and instead we should use a special 'im unconfigured node number' and include our desired node number in the wantnum message. the
|
||||||
|
unconfigured node num would only be used while initially joining the mesh so low odds of conflicting (especially if we randomly select
|
||||||
|
from a small number of nodenums which can be used temporarily for this operation). figure out what the lower level
|
||||||
|
mesh sw does if it does conflict? would it be better for people who are replying with denynode num to just broadcast their denial?)
|
||||||
|
};
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
MeshService service;
|
||||||
|
|
||||||
|
#define MAX_PACKETS 32 // max number of packets which can be in flight (either queued from reception or queued for sending)
|
||||||
|
#define MAX_RX_TOPHONE 16 // max number of packets which can be waiting for delivery to android
|
||||||
|
|
||||||
|
MeshService::MeshService() : packetPool(MAX_PACKETS), toPhoneQueue(MAX_RX_TOPHONE), radio(packetPool, toPhoneQueue)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void MeshService::init()
|
||||||
|
{
|
||||||
|
if (!radio.init())
|
||||||
|
Serial.println("radio init failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Do idle processing (mostly processing messages which have been queued from the radio)
|
||||||
|
void MeshService::loop()
|
||||||
|
{
|
||||||
|
radio.loop(); // FIXME, possibly move radio interaction to own thread
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Given a ToRadio buffer parse it and properly handle it (setup radio, owner or send packet into the mesh)
|
||||||
|
void MeshService::handleToRadio(std::string s)
|
||||||
|
{
|
||||||
|
static ToRadio r; // this is a static scratch object, any data must be copied elsewhere before returning
|
||||||
|
|
||||||
|
pb_istream_t stream = pb_istream_from_buffer((const uint8_t *)s.c_str(), s.length());
|
||||||
|
if (!pb_decode(&stream, ToRadio_fields, &r))
|
||||||
|
{
|
||||||
|
Serial.printf("Error: can't decode ToRadio %s\n", PB_GET_ERROR(&stream));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
switch (r.which_variant)
|
||||||
|
{
|
||||||
|
case ToRadio_packet_tag:
|
||||||
|
sendToMesh(r.variant.packet);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
Serial.println("Error: unexpected ToRadio variant");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Send a packet into the mesh - note p is read only and should be copied into a pool based MeshPacket before
|
||||||
|
/// sending.
|
||||||
|
void MeshService::sendToMesh(const MeshPacket &pIn)
|
||||||
|
{
|
||||||
|
MeshPacket *pOut = packetPool.allocCopy(pIn);
|
||||||
|
assert(pOut); // FIXME
|
||||||
|
|
||||||
|
assert(radio.send(pOut) == pdTRUE);
|
||||||
|
}
|
73
src/MeshService.h
Normal file
73
src/MeshService.h
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
#include "mesh.pb.h"
|
||||||
|
#include "MeshRadio.h"
|
||||||
|
#include "PointerQueue.h"
|
||||||
|
#include "MemoryPool.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Top level app for this service. keeps the mesh, the radio config and the queue of received packets.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class MeshService
|
||||||
|
{
|
||||||
|
MemoryPool<MeshPacket> packetPool;
|
||||||
|
|
||||||
|
/// received packets waiting for the phone to process them
|
||||||
|
/// FIXME, change to a DropOldestQueue and keep a count of the number of dropped packets to ensure
|
||||||
|
/// we never hang because android hasn't been there in a while
|
||||||
|
PointerQueue<MeshPacket> toPhoneQueue;
|
||||||
|
|
||||||
|
/// Packets which have just arrived from the radio, ready to be processed by this service and possibly
|
||||||
|
/// forwarded to the phone. Note: not using yet - seeing if I can just handle everything asap in handleFromRadio
|
||||||
|
// PointerQueue<MeshPacket> fromRadioQueue;
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
MeshRadio radio;
|
||||||
|
|
||||||
|
MeshService();
|
||||||
|
|
||||||
|
void init();
|
||||||
|
|
||||||
|
/// Do idle processing (mostly processing messages which have been queued from the radio)
|
||||||
|
void loop();
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
/**
|
||||||
|
* handle an incoming MeshPacket from the radio, update DB state and queue it for the phone
|
||||||
|
*/
|
||||||
|
void handleFromRadio(NodeNum from, NodeNum to, const uint8_t *buf, size_t len) {
|
||||||
|
MeshPacket *p = packetPool.allocZeroed();
|
||||||
|
assert(p);
|
||||||
|
|
||||||
|
pb_istream_t stream = pb_istream_from_buffer(buf, len);
|
||||||
|
if (!pb_decode(&stream, MeshPacket_fields, p) || !p->has_payload)
|
||||||
|
{
|
||||||
|
Serial.printf("Error: can't decode MeshPacket %s\n", PB_GET_ERROR(&stream));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// FIXME - update DB state based on payload and show recevied texts
|
||||||
|
|
||||||
|
toPhoneQueue.enqueue(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/// Given a ToRadio buffer (from bluetooth) parse it and properly handle it (setup radio, owner or send packet into the mesh)
|
||||||
|
void handleToRadio(std::string s);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
/// Send a packet into the mesh - note p is read only and should be copied into a pool based MeshPacket before
|
||||||
|
/// sending.
|
||||||
|
void sendToMesh(const MeshPacket &p);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
extern MeshService service;
|
||||||
|
|
21
src/PointerQueue.h
Normal file
21
src/PointerQueue.h
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
#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) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// preturns a ptr or null if the queue was empty
|
||||||
|
T *dequeuePtr(TickType_t maxWait = portMAX_DELAY) {
|
||||||
|
T *p;
|
||||||
|
|
||||||
|
return this->dequeue(&p, maxWait) == pdTRUE ? p : NULL;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -29,22 +29,3 @@ public:
|
|||||||
return xQueueReceive(h, p, maxWait);
|
return xQueueReceive(h, p, maxWait);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A wrapper for freertos queues that assumes each element is a pointer
|
|
||||||
*/
|
|
||||||
template <class T> class PointerQueue: public TypedQueue<T *> {
|
|
||||||
TypedQueue h;
|
|
||||||
public:
|
|
||||||
PointerQueue(int maxElements) : TypedQueue(maxElements) {
|
|
||||||
}
|
|
||||||
|
|
||||||
// preturns a ptr or null if the queue was empty
|
|
||||||
T *dequeuePtr(TickType_t maxWait = portMAX_DELAY) {
|
|
||||||
T *p;
|
|
||||||
|
|
||||||
return dequeue(&p, maxWait) == pdTRUE ? p : NULL;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@
|
|||||||
#include <Wire.h>
|
#include <Wire.h>
|
||||||
#include "BluetoothUtil.h"
|
#include "BluetoothUtil.h"
|
||||||
#include "MeshBluetoothService.h"
|
#include "MeshBluetoothService.h"
|
||||||
#include "MeshRadio.h"
|
#include "MeshService.h"
|
||||||
|
|
||||||
#ifdef T_BEAM_V10
|
#ifdef T_BEAM_V10
|
||||||
#include "axp20x.h"
|
#include "axp20x.h"
|
||||||
@ -62,7 +62,7 @@ void doDeepSleep(uint64_t msecToWake)
|
|||||||
screen_off(); // datasheet says this will draw only 10ua
|
screen_off(); // datasheet says this will draw only 10ua
|
||||||
|
|
||||||
// Put radio in sleep mode (will still draw power but only 0.2uA)
|
// Put radio in sleep mode (will still draw power but only 0.2uA)
|
||||||
radio.sleep();
|
service.radio.sleep();
|
||||||
|
|
||||||
#ifdef RESET_OLED
|
#ifdef RESET_OLED
|
||||||
digitalWrite(RESET_OLED, 1); // put the display in reset before killing its power
|
digitalWrite(RESET_OLED, 1); // put the display in reset before killing its power
|
||||||
@ -345,7 +345,7 @@ void setup()
|
|||||||
delay(LOGO_DELAY);
|
delay(LOGO_DELAY);
|
||||||
//}
|
//}
|
||||||
|
|
||||||
mesh_init();
|
service.init();
|
||||||
BLEServer *serve = initBLE("KHBT Test"); // FIXME, use a real name based on the macaddr
|
BLEServer *serve = initBLE("KHBT Test"); // FIXME, use a real name based on the macaddr
|
||||||
BLEService *bts = createMeshBluetoothService(serve);
|
BLEService *bts = createMeshBluetoothService(serve);
|
||||||
bts->start();
|
bts->start();
|
||||||
@ -356,7 +356,7 @@ void loop()
|
|||||||
{
|
{
|
||||||
gps_loop();
|
gps_loop();
|
||||||
screen_loop();
|
screen_loop();
|
||||||
mesh_loop();
|
service.loop();
|
||||||
loopBLE();
|
loopBLE();
|
||||||
|
|
||||||
#ifdef LED_PIN
|
#ifdef LED_PIN
|
||||||
|
Loading…
Reference in New Issue
Block a user