Release Develop to Master

This commit is contained in:
Thomas Göttgens 2022-12-28 15:31:04 +01:00
parent d0243d2cd0
commit 14be4ee9f0
49 changed files with 711 additions and 397 deletions

47
boards/tlora-t3s3-v1.json Normal file
View File

@ -0,0 +1,47 @@
{
"build": {
"arduino": {
"ldscript": "esp32s3_out.ld"
},
"core": "esp32",
"extra_flags": [
"-DLILYGO_T3S3_V1",
"-DARDUINO_USB_CDC_ON_BOOT=1",
"-DARDUINO_USB_MODE=0",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=1"
],
"f_cpu": "240000000L",
"f_flash": "80000000L",
"flash_mode": "dio",
"hwids": [
[
"0X303A",
"0x1001"
]
],
"mcu": "esp32s3",
"variant": "tlora-t3s3-v1"
},
"connectivity": [
"wifi"
],
"debug": {
"openocd_target": "esp32s3.cfg"
},
"frameworks": [
"arduino",
"espidf"
],
"name": "LilyGo TLora-T3S3-V1",
"upload": {
"flash_size": "4MB",
"maximum_ram_size": 327680,
"maximum_size": 4194304,
"wait_for_upload_port": true,
"require_upload_port": true,
"speed": 921600
},
"url": "http://www.lilygo.cn/",
"vendor": "LilyGo"
}

View File

@ -65,7 +65,8 @@ lib_deps =
https://github.com/meshtastic/ArduinoThread.git#72921ac222eed6f526ba1682023cee290d9aa1b3
nanopb/Nanopb@^0.4.6
erriez/ErriezCRC32@^1.0.1
jgromes/RadioLib@^5.5.0
; jgromes/RadioLib@^5.5.1
https://github.com/jgromes/RadioLib.git#395844922c5d88d5db0481a9c91479931172428d
; Used for the code analysis in PIO Home / Inspect
check_tool = cppcheck
@ -81,6 +82,7 @@ framework = arduino
lib_deps =
${env.lib_deps}
mprograms/QMC5883LCompass@^1.1.1
end2endzone/NonBlockingRTTTL@^1.3.0
https://github.com/meshtastic/SparkFun_ATECCX08a_Arduino_Library.git#52b5282639d08a8cbd4b748363089eed6102dc76
build_flags = ${env.build_flags} -Os -DRADIOLIB_SPI_PARANOID=0

View File

@ -327,9 +327,7 @@ void PowerFSM_setup()
powerFSM.add_timed_transition(&stateON, &stateDARK, getConfiguredOrDefaultMs(config.display.screen_on_secs, default_screen_on_secs), NULL, "Screen-on timeout");
#ifdef ARCH_ESP32
// On most boards we use light-sleep to be our main state, but on NRF52 we just stay in DARK
State *lowPowerState = &stateLS;
// We never enter light-sleep or NB states on NRF52 (because the CPU uses so little power normally)
// See: https://github.com/meshtastic/firmware/issues/1071

View File

@ -25,7 +25,7 @@ void consolePrintf(const char *format, ...)
#endif
}
SerialConsole::SerialConsole() : StreamAPI(&Port), RedirectablePrint(&Port)
SerialConsole::SerialConsole() : StreamAPI(&Port), RedirectablePrint(&Port), concurrency::OSThread("SerialConsole")
{
assert(!console);
console = this;
@ -46,6 +46,10 @@ SerialConsole::SerialConsole() : StreamAPI(&Port), RedirectablePrint(&Port)
emitRebooted();
}
int32_t SerialConsole::runOnce()
{
return runOncePart();
}
// For the serial port we can't really detect if any client is on the other side, so instead just look for recent messages
bool SerialConsole::checkIsConnected()

View File

@ -6,7 +6,7 @@
* Provides both debug printing and, if the client starts sending protobufs to us, switches to send/receive protobufs
* (and starts dropping debug printing - FIXME, eventually those prints should be encapsulated in protobufs).
*/
class SerialConsole : public StreamAPI, public RedirectablePrint
class SerialConsole : public StreamAPI, public RedirectablePrint, private concurrency::OSThread
{
public:
SerialConsole();
@ -24,6 +24,8 @@ class SerialConsole : public StreamAPI, public RedirectablePrint
return RedirectablePrint::write(c);
}
virtual int32_t runOnce() override;
protected:
/// Check the current underlying physical link to see if the client is currently connected

View File

@ -35,7 +35,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "mesh/Channels.h"
#include "mesh/generated/deviceonly.pb.h"
#include "modules/TextMessageModule.h"
#include "modules/ExternalNotificationModule.h"
#include "sleep.h"
#include "target_specific.h"
#include "utils.h"
@ -1071,7 +1071,13 @@ int32_t Screen::runOnce()
handleSetOn(false);
break;
case Cmd::ON_PRESS:
handleOnPress();
// If a nag notification is running, stop it
if (externalNotificationModule->nagCycleCutoff != UINT32_MAX) {
externalNotificationModule->stopNow();
} else {
// Don't advance the screen if we just wanted to switch off the nag notification
handleOnPress();
}
break;
case Cmd::START_BLUETOOTH_PIN_SCREEN:
handleStartBluetoothPinScreen(cmd.bluetooth_pin);
@ -1400,7 +1406,7 @@ void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
display->drawString(x, y + FONT_HEIGHT_SMALL, channelStr);
// Draw our hardware ID to assist with bluetooth pairing. Either prefix with Info or S&F Logo
if (moduleConfig.store_forward.enabled) {
#if 0
#ifdef ARCH_ESP32
if (millis() - storeForwardModule->lastHeartbeat > (storeForwardModule->heartbeatInterval * 1200)) { //no heartbeat, overlap a bit
#if defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS)
display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(ourId), y + 3 + FONT_HEIGHT_SMALL, 12, 8, imgQuestionL1);

View File

@ -242,7 +242,6 @@ void setup()
digitalWrite(PIN_3V3_EN, 1);
#endif
// Currently only the tbeam has a PMU
// PMU initialization needs to be placed before scanI2Cdevice
power = new Power();
@ -462,17 +461,6 @@ if((config.lora.region == Config_LoRaConfig_RegionCode_LORA_24) && (!rIf->wideLo
}
}
if((config.lora.region != Config_LoRaConfig_RegionCode_LORA_24) && (rIf->wideLora())){
DEBUG_MSG("Warning: Radio chip only supports 2.4GHz LoRa. Adjusting Region.\n");
config.lora.region = Config_LoRaConfig_RegionCode_LORA_24;
nodeDB.saveToDisk(SEGMENT_CONFIG);
if(!rIf->reconfigure()) {
DEBUG_MSG("Reconfigure failed, rebooting\n");
screen->startRebootScreen();
rebootAtMsec = millis() + 5000;
}
}
#if HAS_WIFI || HAS_ETHERNET
mqttInit();
#endif

View File

@ -17,7 +17,7 @@ ErrorCode FloodingRouter::send(MeshPacket *p)
return Router::send(p);
}
bool FloodingRouter::shouldFilterReceived(MeshPacket *p)
bool FloodingRouter::shouldFilterReceived(const MeshPacket *p)
{
if (wasSeenRecently(p)) { // Note: this will also add a recent packet record
printPacket("Ignoring incoming msg, because we've already seen it", p);
@ -34,7 +34,8 @@ void FloodingRouter::sniffReceived(const MeshPacket *p, const Routing *c)
// do not flood direct message that is ACKed
DEBUG_MSG("Receiving an ACK not for me, but don't need to rebroadcast this direct message anymore.\n");
Router::cancelSending(p->to, p->decoded.request_id); // cancel rebroadcast for this DM
} else if ((p->to != getNodeNum()) && (p->hop_limit > 0) && (getFrom(p) != getNodeNum())) {
}
if ((p->to != getNodeNum()) && (p->hop_limit > 0) && (getFrom(p) != getNodeNum())) {
if (p->id != 0) {
if (config.device.role != Config_DeviceConfig_Role_CLIENT_MUTE) {
MeshPacket *tosend = packetPool.allocCopy(*p); // keep a copy because we will be sending it

View File

@ -51,7 +51,7 @@ class FloodingRouter : public Router, protected PacketHistory
* Called immedately on receiption, before any further processing.
* @return true to abandon the packet
*/
virtual bool shouldFilterReceived(MeshPacket *p) override;
virtual bool shouldFilterReceived(const MeshPacket *p) override;
/**
* Look for broadcasts we need to rebroadcast

View File

@ -44,11 +44,11 @@ MeshPacket *MeshModule::allocAckNak(Routing_Error err, NodeNum to, PacketId idFr
// auto p = allocDataProtobuf(c);
MeshPacket *p = router->allocForSending();
p->decoded.portnum = PortNum_ROUTING_APP;
p->decoded.payload.size = pb_encode_to_bytes(p->decoded.payload.bytes, sizeof(p->decoded.payload.bytes), Routing_fields, &c);
p->decoded.payload.size = pb_encode_to_bytes(p->decoded.payload.bytes, sizeof(p->decoded.payload.bytes), &Routing_msg, &c);
p->priority = MeshPacket_Priority_ACK;
p->hop_limit = 0; // Assume just immediate neighbors for now
p->hop_limit = config.lora.hop_limit; // Flood ACK back to original sender
p->to = to;
p->decoded.request_id = idFrom;
p->channel = chIndex;

View File

@ -401,7 +401,7 @@ bool loadProto(const char *filename, size_t protoSize, size_t objSize, const pb_
void NodeDB::loadFromDisk()
{
// static DeviceState scratch; We no longer read into a tempbuf because this structure is 15KB of valuable RAM
if (!loadProto(prefFileName, DeviceState_size, sizeof(devicestate), DeviceState_fields, &devicestate)) {
if (!loadProto(prefFileName, DeviceState_size, sizeof(DeviceState), &DeviceState_msg, &devicestate)) {
installDefaultDeviceState(); // Our in RAM copy might now be corrupt
} else {
if (devicestate.version < DEVICESTATE_MIN_VER) {
@ -412,7 +412,7 @@ void NodeDB::loadFromDisk()
}
}
if (!loadProto(configFileName, LocalConfig_size, sizeof(LocalConfig), LocalConfig_fields, &config)) {
if (!loadProto(configFileName, LocalConfig_size, sizeof(LocalConfig), &LocalConfig_msg, &config)) {
installDefaultConfig(); // Our in RAM copy might now be corrupt
} else {
if (config.version < DEVICESTATE_MIN_VER) {
@ -423,7 +423,7 @@ void NodeDB::loadFromDisk()
}
}
if (!loadProto(moduleConfigFileName, LocalModuleConfig_size, sizeof(LocalModuleConfig), LocalModuleConfig_fields, &moduleConfig)) {
if (!loadProto(moduleConfigFileName, LocalModuleConfig_size, sizeof(LocalModuleConfig), &LocalModuleConfig_msg, &moduleConfig)) {
installDefaultModuleConfig(); // Our in RAM copy might now be corrupt
} else {
if (moduleConfig.version < DEVICESTATE_MIN_VER) {
@ -434,7 +434,7 @@ void NodeDB::loadFromDisk()
}
}
if (!loadProto(channelFileName, ChannelFile_size, sizeof(ChannelFile), ChannelFile_fields, &channelFile)) {
if (!loadProto(channelFileName, ChannelFile_size, sizeof(ChannelFile), &ChannelFile_msg, &channelFile)) {
installDefaultChannels(); // Our in RAM copy might now be corrupt
} else {
if (channelFile.version < DEVICESTATE_MIN_VER) {
@ -445,12 +445,12 @@ void NodeDB::loadFromDisk()
}
}
if (loadProto(oemConfigFile, OEMStore_size, sizeof(OEMStore), OEMStore_fields, &oemStore))
if (loadProto(oemConfigFile, OEMStore_size, sizeof(OEMStore), &OEMStore_msg, &oemStore))
DEBUG_MSG("Loaded OEMStore\n");
}
/** Save a protobuf from a file, return true for success */
bool saveProto(const char *filename, size_t protoSize, size_t objSize, const pb_msgdesc_t *fields, const void *dest_struct)
bool saveProto(const char *filename, size_t protoSize, const pb_msgdesc_t *fields, const void *dest_struct)
{
bool okay = false;
#ifdef FSCom
@ -498,7 +498,7 @@ void NodeDB::saveChannelsToDisk()
#ifdef FSCom
FSCom.mkdir("/prefs");
#endif
saveProto(channelFileName, ChannelFile_size, sizeof(channelFile), ChannelFile_fields, &channelFile);
saveProto(channelFileName, ChannelFile_size, &ChannelFile_msg, &channelFile);
}
}
@ -508,7 +508,7 @@ void NodeDB::saveDeviceStateToDisk()
#ifdef FSCom
FSCom.mkdir("/prefs");
#endif
saveProto(prefFileName, DeviceState_size, sizeof(devicestate), DeviceState_fields, &devicestate);
saveProto(prefFileName, DeviceState_size, &DeviceState_msg, &devicestate);
}
}
@ -530,7 +530,7 @@ void NodeDB::saveToDisk(int saveWhat)
config.has_power = true;
config.has_network = true;
config.has_bluetooth = true;
saveProto(configFileName, LocalConfig_size, sizeof(config), LocalConfig_fields, &config);
saveProto(configFileName, LocalConfig_size, &LocalConfig_msg, &config);
}
if (saveWhat & SEGMENT_MODULECONFIG) {
@ -541,7 +541,7 @@ void NodeDB::saveToDisk(int saveWhat)
moduleConfig.has_serial = true;
moduleConfig.has_store_forward = true;
moduleConfig.has_telemetry = true;
saveProto(moduleConfigFileName, LocalModuleConfig_size, sizeof(moduleConfig), LocalModuleConfig_fields, &moduleConfig);
saveProto(moduleConfigFileName, LocalModuleConfig_size, &LocalModuleConfig_msg, &moduleConfig);
}
if (saveWhat & SEGMENT_CHANNELS) {

View File

@ -77,7 +77,7 @@ bool PhoneAPI::handleToRadio(const uint8_t *buf, size_t bufLength)
// return (lastContactMsec != 0) &&
memset(&toRadioScratch, 0, sizeof(toRadioScratch));
if (pb_decode_from_bytes(buf, bufLength, ToRadio_fields, &toRadioScratch)) {
if (pb_decode_from_bytes(buf, bufLength, &ToRadio_msg, &toRadioScratch)) {
switch (toRadioScratch.which_payload_variant) {
case ToRadio_packet_tag:
return handleToRadioPacket(toRadioScratch.packet);
@ -291,7 +291,7 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
// Do we have a message from the mesh?
if (fromRadioScratch.which_payload_variant != 0) {
// Encapsulate as a FromRadio packet
size_t numbytes = pb_encode_to_bytes(buf, FromRadio_size, FromRadio_fields, &fromRadioScratch);
size_t numbytes = pb_encode_to_bytes(buf, FromRadio_size, &FromRadio_msg, &fromRadioScratch);
DEBUG_MSG("encoding toPhone packet to phone variant=%d, %d bytes\n", fromRadioScratch.which_payload_variant, numbytes);
return numbytes;

View File

@ -175,7 +175,7 @@ uint32_t RadioInterface::getRetransmissionMsec(const MeshPacket *p)
{
assert(slotTimeMsec); // Better be non zero
static uint8_t bytes[MAX_RHPACKETLEN];
size_t numbytes = pb_encode_to_bytes(bytes, sizeof(bytes), Data_fields, &p->decoded);
size_t numbytes = pb_encode_to_bytes(bytes, sizeof(bytes), &Data_msg, &p->decoded);
uint32_t packetAirtime = getPacketTime(numbytes + sizeof(PacketHeader));
// Make sure enough time has elapsed for this packet to be sent and an ACK is received.
// DEBUG_MSG("Waiting for flooding message with airtime %d and slotTime is %d\n", packetAirtime, slotTimeMsec);

View File

@ -66,10 +66,5 @@ class RadioLibRF95: public SX1278 {
// since default current limit for SX126x/127x in updated RadioLib is 60mA
// use the previous value
float currentLimit = 100;
#ifndef RADIOLIB_GODMODE
private:
#endif
};

View File

@ -27,7 +27,7 @@ ErrorCode ReliableRouter::send(MeshPacket *p)
return FloodingRouter::send(p);
}
bool ReliableRouter::shouldFilterReceived(MeshPacket *p)
bool ReliableRouter::shouldFilterReceived(const MeshPacket *p)
{
// Note: do not use getFrom() here, because we want to ignore messages sent from phone
if (p->from == getNodeNum()) {
@ -37,9 +37,8 @@ bool ReliableRouter::shouldFilterReceived(MeshPacket *p)
// If this is the first time we saw this, cancel any retransmissions we have queued up and generate an internal ack for
// the original sending process.
// FIXME - we might want to turn off this "optimization", it does save lots of airtime but it assumes that once we've
// heard one one adjacent node hear our packet that a) probably other adjacent nodes heard it and b) we can trust those
// nodes to reach our destination. Both of which might be incorrect.
// This "optimization", does save lots of airtime. For DMs, you also get a real ACK back
// from the intended recipient.
auto key = GlobalPacketId(getFrom(p), p->id);
auto old = findPendingPacket(key);
if (old) {
@ -54,16 +53,11 @@ bool ReliableRouter::shouldFilterReceived(MeshPacket *p)
}
}
/* send acks for repeated packets that want acks and are destined for us
* this way if an ACK is dropped and a packet is resent we'll ACK the resent packet
* make sure wasSeenRecently _doesn't_ update
* finding the channel requires decoding the packet. */
if (p->want_ack && (p->to == getNodeNum()) && wasSeenRecently(p, false) && !MeshModule::currentReply) {
if (perhapsDecode(p)) {
sendAckNak(Routing_Error_NONE, getFrom(p), p->id, p->channel);
DEBUG_MSG("acking a repeated want_ack packet\n");
}
} else if (wasSeenRecently(p, false) && p->hop_limit == HOP_RELIABLE && !MeshModule::currentReply && p->to != nodeDB.getNodeNum()) {
/* Resend implicit ACKs for repeated packets (assuming the original packet was sent with HOP_RELIABLE)
* this way if an implicit ACK is dropped and a packet is resent we'll rebroadcast again.
* Resending real ACKs is omitted, as you might receive a packet multiple times due to flooding and
* flooding this ACK back to the original sender already adds redundancy. */
if (wasSeenRecently(p, false) && p->hop_limit == HOP_RELIABLE && !MeshModule::currentReply && p->to != nodeDB.getNodeNum()) {
// retransmission on broadcast has hop_limit still equal to HOP_RELIABLE
DEBUG_MSG("Resending implicit ack for a repeated floodmsg\n");
MeshPacket *tosend = packetPool.allocCopy(*p);

View File

@ -96,7 +96,7 @@ class ReliableRouter : public FloodingRouter
/**
* We hook this method so we can see packets before FloodingRouter says they should be discarded
*/
virtual bool shouldFilterReceived(MeshPacket *p) override;
virtual bool shouldFilterReceived(const MeshPacket *p) override;
/**
* Add p to the list of packets to retransmit occasionally. We will free it once we stop retransmitting.

View File

@ -306,7 +306,7 @@ bool perhapsDecode(MeshPacket *p)
// Take those raw bytes and convert them back into a well structured protobuf we can understand
memset(&p->decoded, 0, sizeof(p->decoded));
if (!pb_decode_from_bytes(bytes, rawSize, Data_fields, &p->decoded)) {
if (!pb_decode_from_bytes(bytes, rawSize, &Data_msg, &p->decoded)) {
DEBUG_MSG("Invalid protobufs in received mesh packet (bad psk?)!\n");
} else if (p->decoded.portnum == PortNum_UNKNOWN_APP) {
DEBUG_MSG("Invalid portnum (bad psk?)!\n");
@ -360,7 +360,7 @@ Routing_Error perhapsEncode(MeshPacket *p)
if (p->which_payload_variant == MeshPacket_decoded_tag) {
static uint8_t bytes[MAX_RHPACKETLEN]; // we have to use a scratch buffer because a union
size_t numbytes = pb_encode_to_bytes(bytes, sizeof(bytes), Data_fields, &p->decoded);
size_t numbytes = pb_encode_to_bytes(bytes, sizeof(bytes), &Data_msg, &p->decoded);
// Only allow encryption on the text message app.
// TODO: Allow modules to opt into compression.

View File

@ -90,7 +90,7 @@ class Router : protected concurrency::OSThread
* Called immedately on receiption, before any further processing.
* @return true to abandon the packet
*/
virtual bool shouldFilterReceived(MeshPacket *p) { return false; }
virtual bool shouldFilterReceived(const MeshPacket *p) { return false; }
/**
* Every (non duplicate) packet this node receives will be passed through this method. This allows subclasses to

View File

@ -1,5 +1,6 @@
#include "configuration.h"
#include "SX128xInterface.h"
#include "mesh/NodeDB.h"
#include "error.h"
// Particular boards might define a different max power based on what their hardware can do
@ -50,6 +51,20 @@ bool SX128xInterface<T>::init()
// \todo Display actual typename of the adapter, not just `SX128x`
DEBUG_MSG("SX128x init result %d\n", res);
if((config.lora.region != Config_LoRaConfig_RegionCode_LORA_24) && (res == RADIOLIB_ERR_INVALID_FREQUENCY)) {
DEBUG_MSG("Warning: Radio chip only supports 2.4GHz LoRa. Adjusting Region and rebooting.\n");
config.lora.region = Config_LoRaConfig_RegionCode_LORA_24;
nodeDB.saveToDisk(SEGMENT_CONFIG);
delay(2000);
#if defined(ARCH_ESP32)
ESP.restart();
#elif defined(ARCH_NRF52)
NVIC_SystemReset();
#else
DEBUG_MSG("FIXME implement reboot for this platform. Skipping for now.\n");
#endif
}
DEBUG_MSG("Frequency set to %f\n", getFreq());
DEBUG_MSG("Bandwidth set to %f\n", bw);
DEBUG_MSG("Power output set to %d\n", power);
@ -223,13 +238,9 @@ bool SX128xInterface<T>::isChannelActive()
template<typename T>
bool SX128xInterface<T>::isActivelyReceiving()
{
#ifdef RADIOLIB_GODMODE
uint16_t irq = lora.getIrqStatus();
bool hasPreamble = (irq & RADIOLIB_SX128X_IRQ_HEADER_VALID);
return hasPreamble;
#else
return isChannelActive();
#endif
}
template<typename T>

View File

@ -27,9 +27,7 @@ class SX128xInterface : public RadioLibInterface
/// Prepare hardware for sleep. Call this _only_ for deep sleep, not needed for light sleep.
virtual bool sleep() override;
#ifdef RADIOLIB_GODMODE
bool isIRQPending() override { return lora.getIrqStatus() != 0; }
#endif
protected:

View File

@ -6,7 +6,7 @@
#define START2 0xc3
#define HEADER_LEN 4
int32_t StreamAPI::runOnce()
int32_t StreamAPI::runOncePart()
{
auto result = readStream();
writeStream();
@ -115,7 +115,7 @@ void StreamAPI::emitRebooted()
fromRadioScratch.rebooted = true;
// DEBUG_MSG("Emitting reboot packet for serial shell\n");
emitTxBuffer(pb_encode_to_bytes(txBuf + HEADER_LEN, FromRadio_size, FromRadio_fields, &fromRadioScratch));
emitTxBuffer(pb_encode_to_bytes(txBuf + HEADER_LEN, FromRadio_size, &FromRadio_msg, &fromRadioScratch));
}
/// Hookable to find out when connection changes

View File

@ -28,7 +28,7 @@ valid utf8 encoding. This makes it a bit easier to start a device outputting reg
after it has received a valid packet from the PC, turn off unencoded debug printing and switch to this packet encoding.
*/
class StreamAPI : public PhoneAPI, protected concurrency::OSThread
class StreamAPI : public PhoneAPI
{
/**
* The stream we read/write from
@ -42,13 +42,13 @@ class StreamAPI : public PhoneAPI, protected concurrency::OSThread
uint32_t lastRxMsec = 0;
public:
StreamAPI(Stream *_stream) : concurrency::OSThread("StreamAPI"), stream(_stream) {}
StreamAPI(Stream *_stream) : stream(_stream) {}
/**
* Currently we require frequent invocation from loop() to check for arrived serial packets and to send new packets to the
* phone.
*/
virtual int32_t runOnce() override;
virtual int32_t runOncePart();
private:
/**

View File

@ -14,7 +14,7 @@ void initApiServer(int port)
}
}
ethServerAPI::ethServerAPI(EthernetClient &_client) : StreamAPI(&client), client(_client)
ethServerAPI::ethServerAPI(EthernetClient &_client) : StreamAPI(&client), concurrency::OSThread("ethServerAPI"), client(_client)
{
DEBUG_MSG("Incoming ethernet connection\n");
}
@ -42,7 +42,7 @@ bool ethServerAPI::checkIsConnected()
int32_t ethServerAPI::runOnce()
{
if (client.connected()) {
return StreamAPI::runOnce();
return StreamAPI::runOncePart();
} else {
DEBUG_MSG("Client dropped connection, suspending API service\n");
enabled = false; // we no longer need to run

View File

@ -7,7 +7,7 @@
* Provides both debug printing and, if the client starts sending protobufs to us, switches to send/receive protobufs
* (and starts dropping debug printing - FIXME, eventually those prints should be encapsulated in protobufs).
*/
class ethServerAPI : public StreamAPI
class ethServerAPI : public StreamAPI, private concurrency::OSThread
{
private:
EthernetClient client;

View File

@ -54,6 +54,8 @@ typedef enum _HardwareModel {
HardwareModel_NANO_G1 = 14,
/* TODO: REPLACE */
HardwareModel_TLORA_V2_1_1P8 = 15,
/* TODO: REPLACE */
HardwareModel_TLORA_T3_S3 = 16,
/* B&Q Consulting Station Edition G1: https://uniteng.com/wiki/doku.php?id=meshtastic:station */
HardwareModel_STATION_G1 = 25,
/* Less common/prototype boards listed here (needs one more byte over the air) */

View File

@ -10,8 +10,8 @@
#endif
/* Enum definitions */
/* 1 - 99 = From Router
101 - 199 = From Client */
/* 001 - 063 = From Router
064 - 127 = From Client */
typedef enum _StoreAndForward_RequestResponse {
/* Unset/unused */
StoreAndForward_RequestResponse_UNSET = 0,
@ -28,17 +28,19 @@ typedef enum _StoreAndForward_RequestResponse {
StoreAndForward_RequestResponse_ROUTER_BUSY = 5,
/* Router is responding to a request for history. */
StoreAndForward_RequestResponse_ROUTER_HISTORY = 6,
/* Router is responding to a request for stats. */
StoreAndForward_RequestResponse_ROUTER_STATS = 7,
/* Client is an in error state. */
StoreAndForward_RequestResponse_CLIENT_ERROR = 101,
StoreAndForward_RequestResponse_CLIENT_ERROR = 64,
/* Client has requested a replay from the router. */
StoreAndForward_RequestResponse_CLIENT_HISTORY = 102,
StoreAndForward_RequestResponse_CLIENT_HISTORY = 65,
/* Client has requested stats from the router. */
StoreAndForward_RequestResponse_CLIENT_STATS = 103,
StoreAndForward_RequestResponse_CLIENT_STATS = 66,
/* Client has requested the router respond. This can work as a
"are you there" message. */
StoreAndForward_RequestResponse_CLIENT_PING = 104,
StoreAndForward_RequestResponse_CLIENT_PING = 67,
/* The response to a "Ping" */
StoreAndForward_RequestResponse_CLIENT_PONG = 105,
StoreAndForward_RequestResponse_CLIENT_PONG = 68,
/* Client has requested that the router abort processing the client's request */
StoreAndForward_RequestResponse_CLIENT_ABORT = 106
} StoreAndForward_RequestResponse;
@ -88,15 +90,17 @@ typedef struct _StoreAndForward_Heartbeat {
typedef struct _StoreAndForward {
/* TODO: REPLACE */
StoreAndForward_RequestResponse rr;
/* TODO: REPLACE */
bool has_stats;
StoreAndForward_Statistics stats;
/* TODO: REPLACE */
bool has_history;
StoreAndForward_History history;
/* TODO: REPLACE */
bool has_heartbeat;
StoreAndForward_Heartbeat heartbeat;
pb_size_t which_variant;
union {
/* TODO: REPLACE */
StoreAndForward_Statistics stats;
/* TODO: REPLACE */
StoreAndForward_History history;
/* TODO: REPLACE */
StoreAndForward_Heartbeat heartbeat;
/* Empty Payload */
bool empty;
} variant;
} StoreAndForward;
@ -116,11 +120,11 @@ extern "C" {
/* Initializer values for message structs */
#define StoreAndForward_init_default {_StoreAndForward_RequestResponse_MIN, false, StoreAndForward_Statistics_init_default, false, StoreAndForward_History_init_default, false, StoreAndForward_Heartbeat_init_default}
#define StoreAndForward_init_default {_StoreAndForward_RequestResponse_MIN, 0, {StoreAndForward_Statistics_init_default}}
#define StoreAndForward_Statistics_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0}
#define StoreAndForward_History_init_default {0, 0, 0}
#define StoreAndForward_Heartbeat_init_default {0, 0}
#define StoreAndForward_init_zero {_StoreAndForward_RequestResponse_MIN, false, StoreAndForward_Statistics_init_zero, false, StoreAndForward_History_init_zero, false, StoreAndForward_Heartbeat_init_zero}
#define StoreAndForward_init_zero {_StoreAndForward_RequestResponse_MIN, 0, {StoreAndForward_Statistics_init_zero}}
#define StoreAndForward_Statistics_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0}
#define StoreAndForward_History_init_zero {0, 0, 0}
#define StoreAndForward_Heartbeat_init_zero {0, 0}
@ -144,18 +148,20 @@ extern "C" {
#define StoreAndForward_stats_tag 2
#define StoreAndForward_history_tag 3
#define StoreAndForward_heartbeat_tag 4
#define StoreAndForward_empty_tag 5
/* Struct field encoding specification for nanopb */
#define StoreAndForward_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, UENUM, rr, 1) \
X(a, STATIC, OPTIONAL, MESSAGE, stats, 2) \
X(a, STATIC, OPTIONAL, MESSAGE, history, 3) \
X(a, STATIC, OPTIONAL, MESSAGE, heartbeat, 4)
X(a, STATIC, ONEOF, MESSAGE, (variant,stats,variant.stats), 2) \
X(a, STATIC, ONEOF, MESSAGE, (variant,history,variant.history), 3) \
X(a, STATIC, ONEOF, MESSAGE, (variant,heartbeat,variant.heartbeat), 4) \
X(a, STATIC, ONEOF, BOOL, (variant,empty,variant.empty), 5)
#define StoreAndForward_CALLBACK NULL
#define StoreAndForward_DEFAULT NULL
#define StoreAndForward_stats_MSGTYPE StoreAndForward_Statistics
#define StoreAndForward_history_MSGTYPE StoreAndForward_History
#define StoreAndForward_heartbeat_MSGTYPE StoreAndForward_Heartbeat
#define StoreAndForward_variant_stats_MSGTYPE StoreAndForward_Statistics
#define StoreAndForward_variant_history_MSGTYPE StoreAndForward_History
#define StoreAndForward_variant_heartbeat_MSGTYPE StoreAndForward_Heartbeat
#define StoreAndForward_Statistics_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, UINT32, messages_total, 1) \
@ -198,7 +204,7 @@ extern const pb_msgdesc_t StoreAndForward_Heartbeat_msg;
#define StoreAndForward_Heartbeat_size 12
#define StoreAndForward_History_size 18
#define StoreAndForward_Statistics_size 50
#define StoreAndForward_size 88
#define StoreAndForward_size 54
#ifdef __cplusplus
} /* extern "C" */

View File

@ -14,7 +14,7 @@ void initApiServer(int port)
}
}
WiFiServerAPI::WiFiServerAPI(WiFiClient &_client) : StreamAPI(&client), client(_client)
WiFiServerAPI::WiFiServerAPI(WiFiClient &_client) : StreamAPI(&client), concurrency::OSThread("WiFiServerAPI"), client(_client)
{
DEBUG_MSG("Incoming wifi connection\n");
}
@ -42,7 +42,7 @@ bool WiFiServerAPI::checkIsConnected()
int32_t WiFiServerAPI::runOnce()
{
if (client.connected()) {
return StreamAPI::runOnce();
return StreamAPI::runOncePart();
} else {
DEBUG_MSG("Client dropped connection, suspending API service\n");
enabled = false; // we no longer need to run

View File

@ -7,7 +7,7 @@
* Provides both debug printing and, if the client starts sending protobufs to us, switches to send/receive protobufs
* (and starts dropping debug printing - FIXME, eventually those prints should be encapsulated in protobufs).
*/
class WiFiServerAPI : public StreamAPI
class WiFiServerAPI : public StreamAPI, private concurrency::OSThread
{
private:
WiFiClient client;

View File

@ -515,7 +515,7 @@ void AdminModule::saveChanges(int saveWhat, bool shouldReboot)
}
}
AdminModule::AdminModule() : ProtobufModule("Admin", PortNum_ADMIN_APP, AdminMessage_fields)
AdminModule::AdminModule() : ProtobufModule("Admin", PortNum_ADMIN_APP, &AdminMessage_msg)
{
// restrict to the admin channel for rx
boundChannel = Channels::adminChannel;

View File

@ -45,8 +45,7 @@ CannedMessageModule *cannedMessageModule;
// TODO: move it into NodeDB.h!
extern bool loadProto(const char *filename, size_t protoSize, size_t objSize, const pb_msgdesc_t *fields, void *dest_struct);
extern bool saveProto(const char *filename, size_t protoSize, size_t objSize, const pb_msgdesc_t *fields,
const void *dest_struct);
extern bool saveProto(const char *filename, size_t protoSize, const pb_msgdesc_t *fields, const void *dest_struct);
CannedMessageModule::CannedMessageModule()
: SinglePortModule("canned", PortNum_TEXT_MESSAGE_APP), concurrency::OSThread("CannedMessageModule")
@ -480,7 +479,7 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st
void CannedMessageModule::loadProtoForModule()
{
if (!loadProto(cannedMessagesConfigFile, CannedMessageModuleConfig_size, sizeof(cannedMessagesConfigFile),
CannedMessageModuleConfig_fields, &cannedMessageModuleConfig)) {
&CannedMessageModuleConfig_msg, &cannedMessageModuleConfig)) {
installDefaultCannedMessageModuleConfig();
}
}
@ -499,8 +498,8 @@ bool CannedMessageModule::saveProtoForModule()
FS.mkdir("/prefs");
#endif
okay &= saveProto(cannedMessagesConfigFile, CannedMessageModuleConfig_size, sizeof(cannedMessageModuleConfig),
CannedMessageModuleConfig_fields, &cannedMessageModuleConfig);
okay &= saveProto(cannedMessagesConfigFile, CannedMessageModuleConfig_size,
&CannedMessageModuleConfig_msg, &cannedMessageModuleConfig);
return okay;
}

View File

@ -11,41 +11,9 @@
#define PIN_BUZZER false
#endif
//#include <assert.h>
/*
Documentation:
https://github.com/meshtastic/firmware/blob/master/docs/software/modules/ExternalNotificationModule.md
This module supports:
https://github.com/meshtastic/firmware/issues/654
Quick reference:
moduleConfig.external_notification.enabled
0 = Disabled (Default)
1 = Enabled
moduleConfig.external_notification.active
0 = Active Low (Default)
1 = Active High
moduleConfig.external_notification.alert_message
0 = Disabled (Default)
1 = Alert when a text message comes
moduleConfig.external_notification.alert_bell
0 = Disabled (Default)
1 = Alert when the bell character is received
moduleConfig.external_notification.output
GPIO of the output. (Default = 13)
moduleConfig.external_notification.output_ms
Amount of time in ms for the alert. Default is 1000.
https://meshtastic.org/docs/settings/moduleconfig/external-notification
*/
// Default configurations
@ -58,59 +26,113 @@
#define ASCII_BELL 0x07
bool externalCurrentState = 0;
uint32_t externalTurnedOn = 0;
ExternalNotificationModule *externalNotificationModule;
bool externalCurrentState[3] = {};
uint32_t externalTurnedOn[3] = {};
int32_t ExternalNotificationModule::runOnce()
{
/*
Uncomment the preferences below if you want to use the module
without having to configure it from the PythonAPI or WebUI.
*/
// moduleConfig.external_notification.enabled = 1;
// moduleConfig.external_notification.alert_message = 1;
// moduleConfig.external_notification.active = 1;
// moduleConfig.external_notification.alert_bell = 1;
// moduleConfig.external_notification.output_ms = 1000;
// moduleConfig.external_notification.output = 13;
if (externalCurrentState && !moduleConfig.external_notification.use_pwm) {
if (!moduleConfig.external_notification.enabled) {
return INT32_MAX; // we don't need this thread here...
} else {
if ((nagCycleCutoff < millis()) && !rtttl::isPlaying()) {
nagCycleCutoff = UINT32_MAX;
DEBUG_MSG("Turning off external notification: ");
for (int i = 0; i < 2; i++) {
if (getExternal(i)) {
setExternalOff(i);
externalTurnedOn[i] = 0;
DEBUG_MSG("%d ", i);
}
}
DEBUG_MSG("\n");
return INT32_MAX; // save cycles till we're needed again
}
// If the output is turned on, turn it back off after the given period of time.
if (externalTurnedOn + (moduleConfig.external_notification.output_ms
if (nagCycleCutoff != UINT32_MAX) {
if (externalTurnedOn[0] + (moduleConfig.external_notification.output_ms
? moduleConfig.external_notification.output_ms
: EXT_NOTIFICATION_MODULE_OUTPUT_MS) <
millis()) {
DEBUG_MSG("Turning off external notification\n");
setExternalOff();
: EXT_NOTIFICATION_MODULE_OUTPUT_MS) < millis()) {
getExternal(0) ? setExternalOff(0) : setExternalOn(0);
}
if (externalTurnedOn[1] + (moduleConfig.external_notification.output_ms
? moduleConfig.external_notification.output_ms
: EXT_NOTIFICATION_MODULE_OUTPUT_MS) < millis()) {
getExternal(1) ? setExternalOff(1) : setExternalOn(1);
}
if (externalTurnedOn[2] + (moduleConfig.external_notification.output_ms
? moduleConfig.external_notification.output_ms
: EXT_NOTIFICATION_MODULE_OUTPUT_MS) < millis()) {
getExternal(2) ? setExternalOff(2) : setExternalOn(2);
}
}
// now let the PWM buzzer play
if (moduleConfig.external_notification.use_pwm) {
if (rtttl::isPlaying()) {
rtttl::play();
} else if (nagCycleCutoff >= millis()) {
// start the song again if we have time left
rtttl::begin(config.device.buzzer_gpio, pwmRingtone);
}
}
}
if (moduleConfig.external_notification.use_pwm)
return INT32_MAX; // we don't need this thread here...
else
return 25;
}
}
void ExternalNotificationModule::setExternalOn()
void ExternalNotificationModule::setExternalOn(uint8_t index)
{
externalCurrentState = 1;
externalTurnedOn = millis();
externalCurrentState[index] = 1;
externalTurnedOn[index] = millis();
digitalWrite(output,
(moduleConfig.external_notification.active ? true : false));
switch(index) {
case 1:
if(moduleConfig.external_notification.output_vibra)
digitalWrite(moduleConfig.external_notification.output_vibra, true);
break;
case 2:
if(moduleConfig.external_notification.output_buzzer)
digitalWrite(moduleConfig.external_notification.output_buzzer, true);
break;
default:
digitalWrite(output, (moduleConfig.external_notification.active ? true : false));
break;
}
}
void ExternalNotificationModule::setExternalOff()
void ExternalNotificationModule::setExternalOff(uint8_t index)
{
externalCurrentState = 0;
externalCurrentState[index] = 0;
externalTurnedOn[index] = millis();
digitalWrite(output,
(moduleConfig.external_notification.active ? false : true));
switch(index) {
case 1:
if(moduleConfig.external_notification.output_vibra)
digitalWrite(moduleConfig.external_notification.output_vibra, false);
break;
case 2:
if(moduleConfig.external_notification.output_buzzer)
digitalWrite(moduleConfig.external_notification.output_buzzer, false);
break;
default:
digitalWrite(output, (moduleConfig.external_notification.active ? false : true));
break;
}
}
// --------
bool ExternalNotificationModule::getExternal(uint8_t index)
{
return externalCurrentState[index];
}
void ExternalNotificationModule::stopNow() {
rtttl::stop();
nagCycleCutoff = 1; // small value
setIntervalFromNow(0);
}
ExternalNotificationModule::ExternalNotificationModule()
: SinglePortModule("ExternalNotificationModule", PortNum_TEXT_MESSAGE_APP), concurrency::OSThread(
@ -121,13 +143,18 @@ ExternalNotificationModule::ExternalNotificationModule()
without having to configure it from the PythonAPI or WebUI.
*/
// moduleConfig.external_notification.enabled = 1;
// moduleConfig.external_notification.alert_message = 1;
// moduleConfig.external_notification.enabled = true;
// moduleConfig.external_notification.alert_message = true;
// moduleConfig.external_notification.alert_message_buzzer = true;
// moduleConfig.external_notification.alert_message_vibra = true;
// moduleConfig.external_notification.active = 1;
// moduleConfig.external_notification.active = true;
// moduleConfig.external_notification.alert_bell = 1;
// moduleConfig.external_notification.output_ms = 1000;
// moduleConfig.external_notification.output = 13;
// moduleConfig.external_notification.output = 4; // RAK4631 IO4
// moduleConfig.external_notification.output_buzzer = 10; // RAK4631 IO6
// moduleConfig.external_notification.output_vibra = 28; // RAK4631 IO7
// moduleConfig.external_notification.nag_timeout = 300;
if (moduleConfig.external_notification.enabled) {
@ -137,19 +164,30 @@ ExternalNotificationModule::ExternalNotificationModule()
? moduleConfig.external_notification.output
: EXT_NOTIFICATION_MODULE_OUTPUT;
if (!moduleConfig.external_notification.use_pwm) {
// Set the direction of a pin
DEBUG_MSG("Using Pin %i in digital mode\n", output);
pinMode(output, OUTPUT);
// Turn off the pin
setExternalOff();
} else {
config.device.buzzer_gpio = config.device.buzzer_gpio
? config.device.buzzer_gpio
: PIN_BUZZER;
// in PWM Mode we force the buzzer pin if it is set
DEBUG_MSG("Using Pin %i in PWM mode\n", config.device.buzzer_gpio);
// Set the direction of a pin
DEBUG_MSG("Using Pin %i in digital mode\n", output);
pinMode(output, OUTPUT);
setExternalOff(0);
externalTurnedOn[0] = 0;
if(moduleConfig.external_notification.output_vibra) {
DEBUG_MSG("Using Pin %i for vibra motor\n", moduleConfig.external_notification.output_vibra);
pinMode(moduleConfig.external_notification.output_vibra, OUTPUT);
setExternalOff(1);
externalTurnedOn[1] = 0;
}
if(moduleConfig.external_notification.output_buzzer) {
if (!moduleConfig.external_notification.use_pwm) {
DEBUG_MSG("Using Pin %i for buzzer\n", moduleConfig.external_notification.output_buzzer);
pinMode(moduleConfig.external_notification.output_buzzer, OUTPUT);
setExternalOff(2);
externalTurnedOn[2] = 0;
} else {
config.device.buzzer_gpio = config.device.buzzer_gpio
? config.device.buzzer_gpio
: PIN_BUZZER;
// in PWM Mode we force the buzzer pin if it is set
DEBUG_MSG("Using Pin %i in PWM mode\n", config.device.buzzer_gpio);
}
}
} else {
DEBUG_MSG("External Notification Module Disabled\n");
@ -163,30 +201,91 @@ ProcessMessage ExternalNotificationModule::handleReceived(const MeshPacket &mp)
if (getFrom(&mp) != nodeDB.getNodeNum()) {
// TODO: This may be a problem if messages are sent in unicide, but I'm not sure if it will.
// Need to know if and how this could be a problem.
// Check if the message contains a bell character. Don't do this loop for every pin, just once.
auto &p = mp.decoded;
bool containsBell = false;
for (int i = 0; i < p.payload.size; i++) {
if (p.payload.bytes[i] == ASCII_BELL) {
containsBell = true;
}
}
if (moduleConfig.external_notification.alert_bell) {
auto &p = mp.decoded;
DEBUG_MSG("externalNotificationModule - Notification Bell\n");
for (int i = 0; i < p.payload.size; i++) {
if (p.payload.bytes[i] == ASCII_BELL) {
if (!moduleConfig.external_notification.use_pwm) {
setExternalOn();
} else {
playBeep();
}
if (containsBell) {
DEBUG_MSG("externalNotificationModule - Notification Bell\n");
setExternalOn(0);
if (moduleConfig.external_notification.nag_timeout) {
nagCycleCutoff = millis() + moduleConfig.external_notification.nag_timeout * 1000;
} else {
nagCycleCutoff = millis() + moduleConfig.external_notification.output_ms;
}
}
}
if (moduleConfig.external_notification.alert_bell_vibra) {
if (containsBell) {
DEBUG_MSG("externalNotificationModule - Notification Bell (Vibra)\n");
setExternalOn(1);
if (moduleConfig.external_notification.nag_timeout) {
nagCycleCutoff = millis() + moduleConfig.external_notification.nag_timeout * 1000;
} else {
nagCycleCutoff = millis() + moduleConfig.external_notification.output_ms;
}
}
}
if (moduleConfig.external_notification.alert_bell_buzzer) {
if (containsBell) {
DEBUG_MSG("externalNotificationModule - Notification Bell (Buzzer)\n");
if (!moduleConfig.external_notification.use_pwm) {
setExternalOn(2);
} else {
rtttl::begin(config.device.buzzer_gpio, pwmRingtone);
}
if (moduleConfig.external_notification.nag_timeout) {
nagCycleCutoff = millis() + moduleConfig.external_notification.nag_timeout * 1000;
} else {
nagCycleCutoff = millis() + moduleConfig.external_notification.output_ms;
}
}
}
if (moduleConfig.external_notification.alert_message) {
DEBUG_MSG("externalNotificationModule - Notification Module\n");
if (!moduleConfig.external_notification.use_pwm) {
setExternalOn();
setExternalOn(0);
if (moduleConfig.external_notification.nag_timeout) {
nagCycleCutoff = millis() + moduleConfig.external_notification.nag_timeout * 1000;
} else {
playBeep();
nagCycleCutoff = millis() + moduleConfig.external_notification.output_ms;
}
}
if (!moduleConfig.external_notification.use_pwm) {
if (moduleConfig.external_notification.alert_message_vibra) {
DEBUG_MSG("externalNotificationModule - Notification Module (Vibra)\n");
setExternalOn(1);
if (moduleConfig.external_notification.nag_timeout) {
nagCycleCutoff = millis() + moduleConfig.external_notification.nag_timeout * 1000;
} else {
nagCycleCutoff = millis() + moduleConfig.external_notification.output_ms;
}
}
if (moduleConfig.external_notification.alert_message_buzzer) {
DEBUG_MSG("externalNotificationModule - Notification Module (Buzzer)\n");
if (!moduleConfig.external_notification.use_pwm) {
setExternalOn(2);
} else {
rtttl::begin(config.device.buzzer_gpio, pwmRingtone);
}
if (moduleConfig.external_notification.nag_timeout) {
nagCycleCutoff = millis() + moduleConfig.external_notification.nag_timeout * 1000;
} else {
nagCycleCutoff = millis() + moduleConfig.external_notification.output_ms;
}
}
}
setIntervalFromNow(0); // run once so we know if we should do something
}
} else {

View File

@ -3,6 +3,7 @@
#include "SinglePortModule.h"
#include "concurrency/OSThread.h"
#include "configuration.h"
#include <NonBlockingRtttl.h>
#include <Arduino.h>
#include <functional>
@ -17,18 +18,23 @@ class ExternalNotificationModule : public SinglePortModule, private concurrency:
public:
ExternalNotificationModule();
void setExternalOn();
void setExternalOff();
void getExternal();
uint32_t nagCycleCutoff = UINT32_MAX;
void setExternalOn(uint8_t index = 0);
void setExternalOff(uint8_t index = 0);
bool getExternal(uint8_t index = 0);
void stopNow();
char pwmRingtone[Constants_DATA_PAYLOAD_LEN] = "a:d=8,o=5,b=125:4d#6,a#,2d#6,16p,g#,4a#,4d#.,p,16g,16a#,d#6,a#,f6,2d#6,16p,c#.6,16c6,16a#,g#.,2a#";
protected:
// virtual MeshPacket *allocReply();
/** Called to handle a particular incoming message
@return ProcessMessage::STOP if you've guaranteed you've handled this message and no other handlers should be considered for it
*/
virtual ProcessMessage handleReceived(const MeshPacket &mp) override;
virtual int32_t runOnce() override;
};
extern ExternalNotificationModule *externalNotificationModule;

View File

@ -24,7 +24,7 @@
#endif
#if defined(ARCH_ESP32) || defined(ARCH_NRF52)
#include "modules/ExternalNotificationModule.h"
#if !defined(TTGO_T_ECHO)
#if (defined(ARCH_ESP32) || defined(ARCH_NRF52)) && !defined(TTGO_T_ECHO) && !defined(CONFIG_IDF_TARGET_ESP32S2)
#include "modules/SerialModule.h"
#endif
#endif
@ -63,13 +63,13 @@ void setupModules()
new DeviceTelemetryModule();
new EnvironmentTelemetryModule();
#endif
#if (defined(ARCH_ESP32) || defined(ARCH_NRF52)) && !defined(TTGO_T_ECHO)
#if (defined(ARCH_ESP32) || defined(ARCH_NRF52)) && !defined(TTGO_T_ECHO) && !defined(CONFIG_IDF_TARGET_ESP32S2)
new SerialModule();
#endif
#ifdef ARCH_ESP32
// Only run on an esp32 based device.
audioModule = new AudioModule();
new ExternalNotificationModule();
externalNotificationModule = new ExternalNotificationModule();
storeForwardModule = new StoreForwardModule();

View File

@ -51,7 +51,7 @@ MeshPacket *NodeInfoModule::allocReply()
}
NodeInfoModule::NodeInfoModule()
: ProtobufModule("nodeinfo", PortNum_NODEINFO_APP, User_fields), concurrency::OSThread("NodeInfoModule")
: ProtobufModule("nodeinfo", PortNum_NODEINFO_APP, &User_msg), concurrency::OSThread("NodeInfoModule")
{
isPromiscuous = true; // We always want to update our nodedb, even if we are sniffing on others
setIntervalFromNow(30 * 1000); // Send our initial owner announcement 30 seconds after we start (to give network time to setup)

View File

@ -10,7 +10,7 @@
PositionModule *positionModule;
PositionModule::PositionModule()
: ProtobufModule("position", PortNum_POSITION_APP, Position_fields), concurrency::OSThread("PositionModule")
: ProtobufModule("position", PortNum_POSITION_APP, &Position_msg), concurrency::OSThread("PositionModule")
{
isPromiscuous = true; // We always want to update our nodedb, even if we are sniffing on others
setIntervalFromNow(60 * 1000); // Send our initial position 60 seconds after we start (to give GPS time to setup)

View File

@ -47,7 +47,7 @@ static uint64_t digitalReads(uint64_t mask)
}
RemoteHardwareModule::RemoteHardwareModule()
: ProtobufModule("remotehardware", PortNum_REMOTE_HARDWARE_APP, HardwareMessage_fields), concurrency::OSThread(
: ProtobufModule("remotehardware", PortNum_REMOTE_HARDWARE_APP, &HardwareMessage_msg), concurrency::OSThread(
"remotehardware")
{
}

View File

@ -41,7 +41,7 @@ void RoutingModule::sendAckNak(Routing_Error err, NodeNum to, PacketId idFrom, C
router->sendLocal(p); // we sometimes send directly to the local node
}
RoutingModule::RoutingModule() : ProtobufModule("routing", PortNum_ROUTING_APP, Routing_fields)
RoutingModule::RoutingModule() : ProtobufModule("routing", PortNum_ROUTING_APP, &Routing_msg)
{
isPromiscuous = true;
encryptedOk = true;

View File

@ -46,6 +46,8 @@
*/
#if (defined(ARCH_ESP32) || defined(ARCH_NRF52)) && !defined(TTGO_T_ECHO) && !defined(CONFIG_IDF_TARGET_ESP32S2)
#define RXD2 16
#define TXD2 17
#define RX_BUFFER 128
@ -53,13 +55,15 @@
#define BAUD 38400
#define ACK 1
// API: Defaulting to the formerly removed phone_timeout_secs value of 15 minutes
#define SERIAL_CONNECTION_TIMEOUT (15 * 60) * 1000UL
SerialModule *serialModule;
SerialModuleRadio *serialModuleRadio;
SerialModule::SerialModule() : concurrency::OSThread("SerialModule") {}
SerialModule::SerialModule() : StreamAPI(&Serial2), concurrency::OSThread("SerialModule") {}
char serialBytes[Constants_DATA_PAYLOAD_LEN];
size_t serialPayloadSize;
char serialStringChar[Constants_DATA_PAYLOAD_LEN];
SerialModuleRadio::SerialModuleRadio() : MeshModule("SerialModuleRadio")
{
@ -80,9 +84,15 @@ SerialModuleRadio::SerialModuleRadio() : MeshModule("SerialModuleRadio")
}
}
// For the serial2 port we can't really detect if any client is on the other side, so instead just look for recent messages
bool SerialModule::checkIsConnected()
{
uint32_t now = millis();
return (now - lastContactMsec) < SERIAL_CONNECTION_TIMEOUT;
}
int32_t SerialModule::runOnce()
{
#if (defined(ARCH_ESP32) || defined(ARCH_NRF52)) && !defined(TTGO_T_ECHO) && !defined(CONFIG_IDF_TARGET_ESP32S2)
/*
Uncomment the preferences below if you want to use the module
without having to configure it from the PythonAPI or WebUI.
@ -178,19 +188,32 @@ int32_t SerialModule::runOnce()
firstTime = 0;
// in API mode send rebooted sequence
if (moduleConfig.serial.mode == ModuleConfig_SerialConfig_Serial_Mode_PROTO) {
emitRebooted();
}
} else {
// in NMEA mode send out GGA every 2 seconds, Don't read from Port
if (moduleConfig.serial.mode == ModuleConfig_SerialConfig_Serial_Mode_NMEA) {
if (moduleConfig.serial.mode == ModuleConfig_SerialConfig_Serial_Mode_PROTO) {
return runOncePart();
} else if (moduleConfig.serial.mode == ModuleConfig_SerialConfig_Serial_Mode_NMEA) {
// in NMEA mode send out GGA every 2 seconds, Don't read from Port
if (millis() - lastNmeaTime > 2000) {
lastNmeaTime = millis();
printGGA(outbuf, nodeDB.getNode(myNodeInfo.my_node_num)->position);
Serial2.printf("%s", outbuf);
}
} else {
String serialString;
while (Serial2.available()) {
serialPayloadSize = Serial2.readBytes(serialBytes, Constants_DATA_PAYLOAD_LEN);
serialString = Serial2.readString();
serialString.toCharArray(serialStringChar, Constants_DATA_PAYLOAD_LEN);
serialModuleRadio->sendPayload();
DEBUG_MSG("Received: %s\n", serialStringChar);
}
}
}
@ -201,9 +224,6 @@ int32_t SerialModule::runOnce()
return INT32_MAX;
}
#else
return INT32_MAX;
#endif
}
MeshPacket *SerialModuleRadio::allocReply()
@ -215,26 +235,25 @@ MeshPacket *SerialModuleRadio::allocReply()
void SerialModuleRadio::sendPayload(NodeNum dest, bool wantReplies)
{
Channel *ch = (boundChannel != NULL) ? &channels.getByName(boundChannel) : NULL;
MeshPacket *p = allocReply();
p->to = dest;
if (ch != NULL) {
p->channel = ch->index;
}
p->decoded.want_response = wantReplies;
p->want_ack = ACK;
p->decoded.payload.size = serialPayloadSize; // You must specify how many bytes are in the reply
memcpy(p->decoded.payload.bytes, serialBytes, p->decoded.payload.size);
p->decoded.payload.size = strlen(serialStringChar); // You must specify how many bytes are in the reply
memcpy(p->decoded.payload.bytes, serialStringChar, p->decoded.payload.size);
service.sendToMesh(p);
}
ProcessMessage SerialModuleRadio::handleReceived(const MeshPacket &mp)
{
#if (defined(ARCH_ESP32) || defined(ARCH_NRF52)) && !defined(TTGO_T_ECHO) && !defined(CONFIG_IDF_TARGET_ESP32S2)
if (moduleConfig.serial.enabled) {
if (moduleConfig.serial.mode == ModuleConfig_SerialConfig_Serial_Mode_PROTO) {
// in API mode we don't care about stuff from radio.
return ProcessMessage::CONTINUE;
}
auto &p = mp.decoded;
// DEBUG_MSG("Received text msg self=0x%0x, from=0x%0x, to=0x%0x, id=%d, msg=%.*s\n",
@ -262,22 +281,20 @@ ProcessMessage SerialModuleRadio::handleReceived(const MeshPacket &mp)
if (moduleConfig.serial.mode == ModuleConfig_SerialConfig_Serial_Mode_DEFAULT ||
moduleConfig.serial.mode == ModuleConfig_SerialConfig_Serial_Mode_SIMPLE) {
Serial2.write(p.payload.bytes, p.payload.size);
Serial2.printf("%s", p.payload.bytes);
} else if (moduleConfig.serial.mode == ModuleConfig_SerialConfig_Serial_Mode_TEXTMSG) {
NodeInfo *node = nodeDB.getNode(getFrom(&mp));
String sender = (node && node->has_user) ? node->user.short_name : "???";
Serial2.println();
Serial2.printf("%s: %s", sender, p.payload.bytes);
Serial2.println();
} else if (moduleConfig.serial.mode == ModuleConfig_SerialConfig_Serial_Mode_PROTO) {
// TODO this needs to be implemented
} else if (moduleConfig.serial.mode == ModuleConfig_SerialConfig_Serial_Mode_NMEA) {
// Decode the Payload some more
Position scratch;
Position *decoded = NULL;
if (mp.which_payload_variant == MeshPacket_decoded_tag && mp.decoded.portnum == ourPortNum) {
memset(&scratch, 0, sizeof(scratch));
if (pb_decode_from_bytes(p.payload.bytes, p.payload.size, Position_fields, &scratch)) {
if (pb_decode_from_bytes(p.payload.bytes, p.payload.size, &Position_msg, &scratch)) {
decoded = &scratch;
}
// send position packet as WPL to the serial port
@ -290,8 +307,6 @@ ProcessMessage SerialModuleRadio::handleReceived(const MeshPacket &mp)
} else {
DEBUG_MSG("Serial Module Disabled\n");
}
#endif
return ProcessMessage::CONTINUE; // Let others look at this message also if they want
}
#endif

View File

@ -8,7 +8,9 @@
#include "Router.h"
#include <functional>
class SerialModule : private concurrency::OSThread
#if (defined(ARCH_ESP32) || defined(ARCH_NRF52)) && !defined(TTGO_T_ECHO) && !defined(CONFIG_IDF_TARGET_ESP32S2)
class SerialModule : public StreamAPI, private concurrency::OSThread
{
bool firstTime = 1;
unsigned long lastNmeaTime = millis();
@ -19,6 +21,9 @@ class SerialModule : private concurrency::OSThread
protected:
virtual int32_t runOnce() override;
/// Check the current underlying physical link to see if the client is currently connected
virtual bool checkIsConnected() override;
};
extern SerialModule *serialModule;
@ -65,3 +70,5 @@ class SerialModuleRadio : public MeshModule
};
extern SerialModuleRadio *serialModuleRadio;
#endif

View File

@ -159,7 +159,7 @@ void EnvironmentTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiSt
const char *lastSender = getSenderShortName(*lastMeasurementPacket);
auto &p = lastMeasurementPacket->decoded;
if (!pb_decode_from_bytes(p.payload.bytes, p.payload.size, Telemetry_fields, &lastMeasurement)) {
if (!pb_decode_from_bytes(p.payload.bytes, p.payload.size, &Telemetry_msg, &lastMeasurement)) {
display->setFont(FONT_SMALL);
display->drawString(x, y += fontHeight(FONT_MEDIUM), "Measurement Error");
DEBUG_MSG("Unable to decode last packet");

View File

@ -24,14 +24,14 @@ void TraceRouteModule::updateRoute(MeshPacket* p)
RouteDiscovery scratch;
RouteDiscovery *updated = NULL;
memset(&scratch, 0, sizeof(scratch));
pb_decode_from_bytes(incoming.payload.bytes, incoming.payload.size, RouteDiscovery_fields, &scratch);
pb_decode_from_bytes(incoming.payload.bytes, incoming.payload.size, &RouteDiscovery_msg, &scratch);
updated = &scratch;
appendMyID(updated);
printRoute(updated, p->from, NODENUM_BROADCAST);
// Set updated route to the payload of the to be flooded packet
p->decoded.payload.size = pb_encode_to_bytes(p->decoded.payload.bytes, sizeof(p->decoded.payload.bytes), RouteDiscovery_fields, updated);
p->decoded.payload.size = pb_encode_to_bytes(p->decoded.payload.bytes, sizeof(p->decoded.payload.bytes), &RouteDiscovery_msg, updated);
}
}
@ -69,7 +69,7 @@ MeshPacket* TraceRouteModule::allocReply()
RouteDiscovery scratch;
RouteDiscovery *updated = NULL;
memset(&scratch, 0, sizeof(scratch));
pb_decode_from_bytes(p.payload.bytes, p.payload.size, RouteDiscovery_fields, &scratch);
pb_decode_from_bytes(p.payload.bytes, p.payload.size, &RouteDiscovery_msg, &scratch);
updated = &scratch;
printRoute(updated, req.from, req.to);
@ -81,6 +81,6 @@ MeshPacket* TraceRouteModule::allocReply()
}
TraceRouteModule::TraceRouteModule() : ProtobufModule("traceroute", PortNum_TRACEROUTE_APP, RouteDiscovery_fields) {
TraceRouteModule::TraceRouteModule() : ProtobufModule("traceroute", PortNum_TRACEROUTE_APP, &RouteDiscovery_msg) {
ourPortNum = PortNum_TRACEROUTE_APP;
}

View File

@ -22,41 +22,32 @@ int32_t StoreForwardModule::runOnce()
if (this->busy) {
// Only send packets if the channel is less than 25% utilized.
if (airTime->channelUtilizationPercent() < polite_channel_util_percent) {
// DEBUG_MSG("--- --- --- In busy loop 1 %d\n", this->packetHistoryTXQueue_index);
storeForwardModule->sendPayload(this->busyTo, this->packetHistoryTXQueue_index);
if (this->packetHistoryTXQueue_index == packetHistoryTXQueue_size) {
strcpy(this->routerMessage, "** S&F - Done");
storeForwardModule->sendMessage(this->busyTo, this->routerMessage);
// DEBUG_MSG("--- --- --- In busy loop - Done \n");
// Tell the client we're done sending
StoreAndForward sf = StoreAndForward_init_zero;
sf.rr = StoreAndForward_RequestResponse_ROUTER_PING;
storeForwardModule->sendMessage(this->busyTo, sf);
DEBUG_MSG("*** S&F - Done. (ROUTER_PING)\n");
this->packetHistoryTXQueue_index = 0;
this->busy = false;
} else {
this->packetHistoryTXQueue_index++;
}
} else {
DEBUG_MSG("Channel utilization is too high. Retrying later.\n");
DEBUG_MSG("*** Channel utilization is too high. Retrying later.\n");
}
DEBUG_MSG("SF bitrate = %f bytes / sec\n", myNodeInfo.bitrate);
DEBUG_MSG("*** SF bitrate = %f bytes / sec\n", myNodeInfo.bitrate);
} else if (millis() - lastHeartbeat > 300000) {
} else if ((millis() - lastHeartbeat > (heartbeatInterval * 1000)) && (airTime->channelUtilizationPercent() < polite_channel_util_percent)) {
lastHeartbeat = millis();
DEBUG_MSG("Sending heartbeat\n");
StoreAndForward sf;
DEBUG_MSG("*** Sending heartbeat\n");
StoreAndForward sf = StoreAndForward_init_zero;
sf.rr = StoreAndForward_RequestResponse_ROUTER_HEARTBEAT;
sf.has_heartbeat = true;
sf.heartbeat.period = 300;
sf.heartbeat.secondary = 0; // TODO we always have one primary router for now
MeshPacket *p = allocDataProtobuf(sf);
p->to = NODENUM_BROADCAST;
p->decoded.want_response = false;
p->priority = MeshPacket_Priority_MIN;
service.sendToMesh(p);
sf.which_variant = StoreAndForward_heartbeat_tag;
sf.variant.heartbeat.period = 300;
sf.variant.heartbeat.secondary = 0; // TODO we always have one primary router for now
storeForwardModule->sendMessage(NODENUM_BROADCAST, sf);
}
return (this->packetTimeMax);
}
@ -74,7 +65,7 @@ void StoreForwardModule::populatePSRAM()
https://learn.upesy.com/en/programmation/psram.html#psram-tab
*/
DEBUG_MSG("Before PSRAM initilization: heap %d/%d PSRAM %d/%d\n", ESP.getFreeHeap(), ESP.getHeapSize(), ESP.getFreePsram(), ESP.getPsramSize());
DEBUG_MSG("*** Before PSRAM initilization: heap %d/%d PSRAM %d/%d\n", ESP.getFreeHeap(), ESP.getHeapSize(), ESP.getFreePsram(), ESP.getPsramSize());
this->packetHistoryTXQueue =
static_cast<PacketHistoryStruct *>(ps_calloc(this->historyReturnMax, sizeof(PacketHistoryStruct)));
@ -83,46 +74,36 @@ void StoreForwardModule::populatePSRAM()
Note: This needs to be done after every thing that would use PSRAM
*/
uint32_t numberOfPackets = (this->records ? this->records : (((ESP.getFreePsram() / 3) * 2) / sizeof(PacketHistoryStruct)));
this->records = numberOfPackets;
this->packetHistory = static_cast<PacketHistoryStruct *>(ps_calloc(numberOfPackets, sizeof(PacketHistoryStruct)));
DEBUG_MSG("After PSRAM initilization: heap %d/%d PSRAM %d/%d\n", ESP.getFreeHeap(), ESP.getHeapSize(), ESP.getFreePsram(), ESP.getPsramSize());
DEBUG_MSG("numberOfPackets for packetHistory - %u\n", numberOfPackets);
DEBUG_MSG("*** After PSRAM initilization: heap %d/%d PSRAM %d/%d\n", ESP.getFreeHeap(), ESP.getHeapSize(), ESP.getFreePsram(), ESP.getPsramSize());
DEBUG_MSG("*** numberOfPackets for packetHistory - %u\n", numberOfPackets);
}
void StoreForwardModule::historyReport()
{
DEBUG_MSG("Message history contains %u records\n", this->packetHistoryCurrent);
}
/*
*
*/
void StoreForwardModule::historySend(uint32_t msAgo, uint32_t to)
{
// uint32_t packetsSent = 0;
uint32_t queueSize = storeForwardModule->historyQueueCreate(msAgo, to);
if (queueSize) {
snprintf(this->routerMessage, 80, "** S&F - Sending %u message(s)", queueSize);
storeForwardModule->sendMessage(to, this->routerMessage);
DEBUG_MSG ("*** S&F - Sending %u message(s)\n", queueSize);
this->busy = true; // runOnce() will pickup the next steps once busy = true.
this->busyTo = to;
} else {
strcpy(this->routerMessage, "** S&F - No history to send");
storeForwardModule->sendMessage(to, this->routerMessage);
DEBUG_MSG ("*** S&F - No history to send\n");
}
StoreAndForward sf = StoreAndForward_init_zero;
sf.rr = StoreAndForward_RequestResponse_ROUTER_HISTORY;
sf.which_variant = StoreAndForward_history_tag;
sf.variant.history.history_messages = queueSize;
sf.variant.history.window = msAgo;
storeForwardModule->sendMessage(to, sf);
}
uint32_t StoreForwardModule::historyQueueCreate(uint32_t msAgo, uint32_t to)
{
// uint32_t packetHistoryTXQueueIndex = 0;
this->packetHistoryTXQueue_size = 0;
for (int i = 0; i < this->packetHistoryCurrent; i++) {
@ -133,7 +114,7 @@ uint32_t StoreForwardModule::historyQueueCreate(uint32_t msAgo, uint32_t to)
DEBUG_MSG("SF historyQueueCreate - math %d\n", (millis() - msAgo));
*/
if (this->packetHistory[i].time && (this->packetHistory[i].time < (millis() - msAgo))) {
DEBUG_MSG("SF historyQueueCreate - Time matches - ok\n");
DEBUG_MSG("*** SF historyQueueCreate - Time matches - ok\n");
/*
Copy the messages that were received by the router in the last msAgo
to the packetHistoryTXQueue structure.
@ -144,7 +125,6 @@ uint32_t StoreForwardModule::historyQueueCreate(uint32_t msAgo, uint32_t to)
if ((this->packetHistory[i].to & NODENUM_BROADCAST) == NODENUM_BROADCAST ||
((this->packetHistory[i].to & NODENUM_BROADCAST) == to)) {
this->packetHistoryTXQueue[this->packetHistoryTXQueue_size].time = this->packetHistory[i].time;
this->packetHistoryTXQueue[this->packetHistoryTXQueue_size].time = this->packetHistory[i].time;
this->packetHistoryTXQueue[this->packetHistoryTXQueue_size].to = this->packetHistory[i].to;
this->packetHistoryTXQueue[this->packetHistoryTXQueue_size].from = this->packetHistory[i].from;
this->packetHistoryTXQueue[this->packetHistoryTXQueue_size].channel = this->packetHistory[i].channel;
@ -153,9 +133,8 @@ uint32_t StoreForwardModule::historyQueueCreate(uint32_t msAgo, uint32_t to)
Constants_DATA_PAYLOAD_LEN);
this->packetHistoryTXQueue_size++;
DEBUG_MSG("PacketHistoryStruct time=%d\n", this->packetHistory[i].time);
DEBUG_MSG("PacketHistoryStruct msg=%.*s\n", this->packetHistory[i].payload);
// DEBUG_MSG("PacketHistoryStruct msg=%.*s\n", this->packetHistoryTXQueue[packetHistoryTXQueueIndex].payload);
DEBUG_MSG("*** PacketHistoryStruct time=%d\n", this->packetHistory[i].time);
DEBUG_MSG("*** PacketHistoryStruct msg=%s\n", this->packetHistory[i].payload);
}
}
}
@ -174,6 +153,7 @@ void StoreForwardModule::historyAdd(const MeshPacket &mp)
memcpy(this->packetHistory[this->packetHistoryCurrent].payload, p.payload.bytes, Constants_DATA_PAYLOAD_LEN);
this->packetHistoryCurrent++;
this->packetHistoryMax++;
}
MeshPacket *StoreForwardModule::allocReply()
@ -184,7 +164,7 @@ MeshPacket *StoreForwardModule::allocReply()
void StoreForwardModule::sendPayload(NodeNum dest, uint32_t packetHistory_index)
{
DEBUG_MSG("Sending S&F Payload\n");
DEBUG_MSG("*** Sending S&F Payload\n");
MeshPacket *p = allocReply();
p->to = dest;
@ -203,12 +183,14 @@ void StoreForwardModule::sendPayload(NodeNum dest, uint32_t packetHistory_index)
service.sendToMesh(p);
}
void StoreForwardModule::sendMessage(NodeNum dest, char *str)
void StoreForwardModule::sendMessage(NodeNum dest, StoreAndForward &payload)
{
MeshPacket *p = allocReply();
MeshPacket *p = allocDataProtobuf(payload);
p->to = dest;
p->priority = MeshPacket_Priority_MIN;
// FIXME - Determine if the delayed packet is broadcast or delayed. For now, assume
// everything is broadcast.
p->delayed = MeshPacket_Delayed_DELAYED_BROADCAST;
@ -216,13 +198,37 @@ void StoreForwardModule::sendMessage(NodeNum dest, char *str)
// Let's assume that if the router received the S&F request that the client is in range.
// TODO: Make this configurable.
p->want_ack = false;
p->decoded.payload.size = strlen(str); // You must specify how many bytes are in the reply
memcpy(p->decoded.payload.bytes, str, strlen(str));
p->decoded.want_response = false;
service.sendToMesh(p);
}
// HardwareMessage_init_default
void StoreForwardModule::sendMessage(NodeNum dest, StoreAndForward_RequestResponse rr)
{
// Craft an empty response, save some bytes in flash
StoreAndForward sf = StoreAndForward_init_zero;
sf.rr = rr;
storeForwardModule->sendMessage(dest, sf);
}
void StoreForwardModule::statsSend(uint32_t to)
{
StoreAndForward sf = StoreAndForward_init_zero;
sf.rr = StoreAndForward_RequestResponse_ROUTER_STATS;
sf.which_variant = StoreAndForward_stats_tag;
sf.variant.stats.messages_total = this->packetHistoryMax;
sf.variant.stats.messages_saved = this->packetHistoryCurrent;
sf.variant.stats.messages_max = this->records;
sf.variant.stats.up_time = millis() / 1000;
sf.variant.stats.requests = this->requests;
sf.variant.stats.requests_history = this->requests_history;
sf.variant.stats.heartbeat = this->heartbeat;
sf.variant.stats.return_max = this->historyReturnMax;
sf.variant.stats.return_window = this->historyReturnWindow;
DEBUG_MSG("*** Sending S&F Stats\n");
storeForwardModule->sendMessage(to, sf);
}
ProcessMessage StoreForwardModule::handleReceived(const MeshPacket &mp)
@ -230,46 +236,28 @@ ProcessMessage StoreForwardModule::handleReceived(const MeshPacket &mp)
#ifdef ARCH_ESP32
if (moduleConfig.store_forward.enabled) {
DEBUG_MSG("--- S&F Received something\n");
// The router node should not be sending messages as a client. Unless he is a ROUTER_CLIENT
if ((getFrom(&mp) != nodeDB.getNodeNum()) || (config.device.role == Config_DeviceConfig_Role_ROUTER_CLIENT)) {
if (mp.decoded.portnum == PortNum_TEXT_MESSAGE_APP) {
DEBUG_MSG("Packet came from - PortNum_TEXT_MESSAGE_APP\n");
auto &p = mp.decoded;
if ((p.payload.bytes[0] == 'S') && (p.payload.bytes[1] == 'F') && (p.payload.bytes[2] == 0x00)) {
DEBUG_MSG("--- --- --- Request to send\n");
// Send the last 60 minutes of messages.
if (this->busy) {
strcpy(this->routerMessage, "** S&F - Busy. Try again shortly.");
storeForwardModule->sendMessage(getFrom(&mp), this->routerMessage);
} else {
storeForwardModule->historySend(1000 * 60, getFrom(&mp));
}
} else if ((p.payload.bytes[0] == 'S') && (p.payload.bytes[1] == 'F') && (p.payload.bytes[2] == 'm') &&
(p.payload.bytes[3] == 0x00)) {
strlcpy(this->routerMessage,
"01234567890123456789012345678901234567890123456789012345678901234567890123456789"
"01234567890123456789012345678901234567890123456789012345678901234567890123456789"
"01234567890123456789012345678901234567890123456789012345678901234567890123456",
sizeof(this->routerMessage));
storeForwardModule->sendMessage(getFrom(&mp), this->routerMessage);
} else {
storeForwardModule->historyAdd(mp);
}
storeForwardModule->historyAdd(mp);
DEBUG_MSG("*** S&F stored. Message history contains %u records now.\n", this->packetHistoryCurrent);
} else if (mp.decoded.portnum == PortNum_STORE_FORWARD_APP) {
DEBUG_MSG("Packet came from an PortNum_STORE_FORWARD_APP port %u\n", mp.decoded.portnum);
} else {
DEBUG_MSG("Packet came from an unknown port %u\n", mp.decoded.portnum);
}
auto &p = mp.decoded;
StoreAndForward scratch;
StoreAndForward *decoded = NULL;
if (mp.which_payload_variant == MeshPacket_decoded_tag) {
if (pb_decode_from_bytes(p.payload.bytes, p.payload.size, &StoreAndForward_msg, &scratch)) {
decoded = &scratch;
} else {
DEBUG_MSG("Error decoding protobuf module!\n");
// if we can't decode it, nobody can process it!
return ProcessMessage::STOP;
}
return handleReceivedProtobuf(mp, decoded) ? ProcessMessage::STOP : ProcessMessage::CONTINUE;
}
} // all others are irrelevant
}
}
@ -285,95 +273,127 @@ bool StoreForwardModule::handleReceivedProtobuf(const MeshPacket &mp, StoreAndFo
return false;
}
if (mp.decoded.portnum != PortNum_STORE_FORWARD_APP) {
DEBUG_MSG("Packet came from port %u\n", mp.decoded.portnum);
return false;
} else {
DEBUG_MSG("Packet came from PortNum_STORE_FORWARD_APP port %u\n", mp.decoded.portnum);
requests++;
switch (p->rr) {
case StoreAndForward_RequestResponse_CLIENT_ERROR:
case StoreAndForward_RequestResponse_CLIENT_ABORT:
if(is_server) {
// Do nothing
DEBUG_MSG("StoreAndForward_RequestResponse_CLIENT_ERROR\n");
// stop sending stuff, the client wants to abort or has another error
if ((this->busy) && (this->busyTo == getFrom(&mp))) {
DEBUG_MSG("*** Client in ERROR or ABORT requested\n");
this->packetHistoryTXQueue_index = 0;
this->busy = false;
}
}
break;
case StoreAndForward_RequestResponse_CLIENT_HISTORY:
if(is_server) {
DEBUG_MSG("StoreAndForward_RequestResponse_CLIENT_HISTORY\n");
requests_history++;
DEBUG_MSG("*** Client Request to send HISTORY\n");
// Send the last 60 minutes of messages.
if (this->busy) {
strcpy(this->routerMessage, "** S&F - Busy. Try again shortly.");
storeForwardModule->sendMessage(getFrom(&mp), this->routerMessage);
storeForwardModule->sendMessage(getFrom(&mp), StoreAndForward_RequestResponse_ROUTER_BUSY);
DEBUG_MSG("*** S&F - Busy. Try again shortly.\n");
} else {
storeForwardModule->historySend(1000 * 60, getFrom(&mp));
if ((p->which_variant == StoreAndForward_history_tag) && (p->variant.history.window > 0)){
storeForwardModule->historySend(p->variant.history.window * 60000, getFrom(&mp)); // window is in minutes
} else {
storeForwardModule->historySend(historyReturnWindow * 60000, getFrom(&mp)); // defaults to 4 hours
}
}
}
break;
case StoreAndForward_RequestResponse_CLIENT_PING:
if(is_server) {
// Do nothing
DEBUG_MSG("StoreAndForward_RequestResponse_CLIENT_PING\n");
DEBUG_MSG("*** StoreAndForward_RequestResponse_CLIENT_PING\n");
// respond with a ROUTER PONG
storeForwardModule->sendMessage(getFrom(&mp), StoreAndForward_RequestResponse_ROUTER_PONG);
}
break;
case StoreAndForward_RequestResponse_CLIENT_PONG:
if(is_server) {
// Do nothing
DEBUG_MSG("StoreAndForward_RequestResponse_CLIENT_PONG\n");
DEBUG_MSG("*** StoreAndForward_RequestResponse_CLIENT_PONG\n");
// The Client is alive, update NodeDB
nodeDB.updateFrom(mp);
}
break;
case StoreAndForward_RequestResponse_CLIENT_STATS:
if(is_server) {
// Do nothing
DEBUG_MSG("StoreAndForward_RequestResponse_CLIENT_STATS\n");
}
break;
case StoreAndForward_RequestResponse_ROUTER_BUSY:
if(is_client) {
// Do nothing
DEBUG_MSG("StoreAndForward_RequestResponse_ROUTER_BUSY\n");
DEBUG_MSG("*** Client Request to send STATS\n");
if (this->busy) {
storeForwardModule->sendMessage(getFrom(&mp), StoreAndForward_RequestResponse_ROUTER_BUSY);
DEBUG_MSG("*** S&F - Busy. Try again shortly.\n");
} else {
storeForwardModule->statsSend(getFrom(&mp));
}
}
break;
case StoreAndForward_RequestResponse_ROUTER_ERROR:
case StoreAndForward_RequestResponse_ROUTER_BUSY:
if(is_client) {
// Do nothing
DEBUG_MSG("StoreAndForward_RequestResponse_ROUTER_ERROR\n");
DEBUG_MSG("*** StoreAndForward_RequestResponse_ROUTER_BUSY\n");
// retry in messages_saved * packetTimeMax ms
retry_delay = millis() + packetHistoryCurrent * packetTimeMax * (StoreAndForward_RequestResponse_ROUTER_ERROR ? 2 : 1);
}
break;
case StoreAndForward_RequestResponse_ROUTER_PONG:
// A router responded, this is equal to receiving a heartbeat
case StoreAndForward_RequestResponse_ROUTER_HEARTBEAT:
if(is_client) {
// Do nothing
DEBUG_MSG("StoreAndForward_RequestResponse_ROUTER_HEARTBEAT\n");
// register heartbeat and interval
if (p->which_variant == StoreAndForward_heartbeat_tag) {
heartbeatInterval = p->variant.heartbeat.period;
}
lastHeartbeat = millis();
DEBUG_MSG("*** StoreAndForward Heartbeat received\n");
}
break;
case StoreAndForward_RequestResponse_ROUTER_PING:
if(is_client) {
// Do nothing
DEBUG_MSG("StoreAndForward_RequestResponse_ROUTER_PING\n");
DEBUG_MSG("*** StoreAndForward_RequestResponse_ROUTER_PING\n");
// respond with a CLIENT PONG
storeForwardModule->sendMessage(getFrom(&mp), StoreAndForward_RequestResponse_CLIENT_PONG);
}
break;
case StoreAndForward_RequestResponse_ROUTER_PONG:
case StoreAndForward_RequestResponse_ROUTER_STATS:
if(is_client) {
// Do nothing
DEBUG_MSG("StoreAndForward_RequestResponse_ROUTER_PONG\n");
DEBUG_MSG("*** Router Response STATS\n");
// These fields only have informational purpose on a client. Fill them to consume later.
if (p->which_variant == StoreAndForward_stats_tag) {
this->packetHistoryMax = p->variant.stats.messages_total;
this->packetHistoryCurrent = p->variant.stats.messages_saved;
this->records = p->variant.stats.messages_max;
this->requests = p->variant.stats.requests;
this->requests_history = p->variant.stats.requests_history;
this->heartbeat = p->variant.stats.heartbeat;
this->historyReturnMax = p->variant.stats.return_max;
this->historyReturnWindow = p->variant.stats.return_window;
}
}
break;
case StoreAndForward_RequestResponse_ROUTER_HISTORY:
if(is_client) {
// These fields only have informational purpose on a client. Fill them to consume later.
if (p->which_variant == StoreAndForward_history_tag) {
this->historyReturnWindow = p->variant.history.window / 60000;
DEBUG_MSG("*** Router Response HISTORY - Sending %d messages from last %d minutes\n", p->variant.history.history_messages, this->historyReturnWindow);
}
}
break;
default:
assert(0); // unexpected state - FIXME, make an error code and reboot
}
}
return true; // There's no need for others to look at this message.
}
@ -398,7 +418,7 @@ StoreForwardModule::StoreForwardModule()
// Router
if ((config.device.role == Config_DeviceConfig_Role_ROUTER) || (config.device.role == Config_DeviceConfig_Role_ROUTER_CLIENT)) {
DEBUG_MSG("Initializing Store & Forward Module in Router mode\n");
DEBUG_MSG("*** Initializing Store & Forward Module in Router mode\n");
if (ESP.getPsramSize() > 0) {
if (ESP.getFreePsram() >= 1024 * 1024) {
@ -424,19 +444,19 @@ StoreForwardModule::StoreForwardModule()
this->populatePSRAM();
is_server = true;
} else {
DEBUG_MSG("Device has less than 1M of PSRAM free.\n");
DEBUG_MSG("Store & Forward Module - disabling server.\n");
DEBUG_MSG("*** Device has less than 1M of PSRAM free.\n");
DEBUG_MSG("*** Store & Forward Module - disabling server.\n");
}
} else {
DEBUG_MSG("Device doesn't have PSRAM.\n");
DEBUG_MSG("Store & Forward Module - disabling server.\n");
DEBUG_MSG("*** Device doesn't have PSRAM.\n");
DEBUG_MSG("*** Store & Forward Module - disabling server.\n");
}
// Client
}
if ((config.device.role == Config_DeviceConfig_Role_CLIENT) || (config.device.role == Config_DeviceConfig_Role_ROUTER_CLIENT)) {
is_client = true;
DEBUG_MSG("Initializing Store & Forward Module in Client mode\n");
DEBUG_MSG("*** Initializing Store & Forward Module in Client mode\n");
}
}
#endif

View File

@ -24,10 +24,9 @@ class StoreForwardModule : private concurrency::OSThread, public ProtobufModule<
uint32_t busyTo = 0;
char routerMessage[Constants_DATA_PAYLOAD_LEN] = {0};
uint32_t receivedRecord[50][2] = {{0}};
PacketHistoryStruct *packetHistory = 0;
uint32_t packetHistoryCurrent = 0;
uint32_t packetHistoryMax = 0;
PacketHistoryStruct *packetHistoryTXQueue = 0;
uint32_t packetHistoryTXQueue_size = 0;
@ -35,20 +34,21 @@ class StoreForwardModule : private concurrency::OSThread, public ProtobufModule<
uint32_t packetTimeMax = 5000;
unsigned long lastHeartbeat = 0;
bool is_client = false;
bool is_server = false;
public:
StoreForwardModule();
unsigned long lastHeartbeat = 0;
uint32_t heartbeatInterval = 300;
/**
Update our local reference of when we last saw that node.
@return 0 if we have never seen that node before otherwise return the last time we saw the node.
*/
void historyAdd(const MeshPacket &mp);
void historyReport();
void statsSend(uint32_t to);
void historySend(uint32_t msAgo, uint32_t to);
uint32_t historyQueueCreate(uint32_t msAgo, uint32_t to);
@ -57,12 +57,23 @@ class StoreForwardModule : private concurrency::OSThread, public ProtobufModule<
* Send our payload into the mesh
*/
void sendPayload(NodeNum dest = NODENUM_BROADCAST, uint32_t packetHistory_index = 0);
void sendMessage(NodeNum dest, char *str);
void sendMessage(NodeNum dest, StoreAndForward &payload);
void sendMessage(NodeNum dest, StoreAndForward_RequestResponse rr);
virtual MeshPacket *allocReply() override;
/*
Override the wantPortnum method.
*/
virtual bool wantPortnum(PortNum p) { return true; };
-Override the wantPacket method.
*/
virtual bool wantPacket(const MeshPacket *p) override
{
switch(p->decoded.portnum) {
case PortNum_TEXT_MESSAGE_APP:
case PortNum_STORE_FORWARD_APP:
return true;
default:
return false;
}
}
private:
void populatePSRAM();
@ -73,6 +84,12 @@ class StoreForwardModule : private concurrency::OSThread, public ProtobufModule<
uint32_t records = 0; // Calculated
bool heartbeat = false; // No heartbeat.
// stats
uint32_t requests = 0;
uint32_t requests_history = 0;
uint32_t retry_delay = 0;
protected:
virtual int32_t runOnce() override;

View File

@ -88,6 +88,8 @@
#define HW_VENDOR HardwareModel_HELTEC_V3
#elif defined(HELTEC_WSL_V3)
#define HW_VENDOR HardwareModel_HELTEC_WSL_V3
#elif defined(TLORA_T3S3_V1)
#define HW_VENDOR HardwareModel_TLORA_T3_S3
#endif
//

View File

@ -201,7 +201,7 @@ void SimRadio::startSend(MeshPacket * txp)
} else {
DEBUG_MSG("Payload size is larger than compressed message allows! Sending empty payload.\n");
}
p->decoded.payload.size = pb_encode_to_bytes(p->decoded.payload.bytes, sizeof(p->decoded.payload.bytes), Compressed_fields, &c);
p->decoded.payload.size = pb_encode_to_bytes(p->decoded.payload.bytes, sizeof(p->decoded.payload.bytes), &Compressed_msg, &c);
p->decoded.portnum = PortNum_SIMULATOR_APP;
service.sendToPhone(p); // Sending back to simulator
}

View File

@ -0,0 +1,34 @@
#ifndef Pins_Arduino_h
#define Pins_Arduino_h
#include <stdint.h>
#define USB_VID 0x303a
#define USB_PID 0x1001
#define EXTERNAL_NUM_INTERRUPTS 46
#define NUM_DIGITAL_PINS 48
#define NUM_ANALOG_INPUTS 20
#define analogInputToDigitalPin(p) (((p)<20)?(analogChannelToDigitalPin(p)):-1)
#define digitalPinToInterrupt(p) (((p)<48)?(p):-1)
#define digitalPinHasPWM(p) (p < 46)
// The default Wire will be mapped to PMU and RTC
static const uint8_t SDA = 18;
static const uint8_t SCL = 17;
// Default SPI will be mapped to Radio
static const uint8_t SS = 7;
static const uint8_t MOSI = 6;
static const uint8_t MISO = 3;
static const uint8_t SCK = 5;
#define SPI_MOSI (11)
#define SPI_SCK (14)
#define SPI_MISO (2)
#define SPI_CS (13)
#define SDCARD_CS SPI_CS
#endif /* Pins_Arduino_h */

View File

@ -0,0 +1,9 @@
[env:tlora-t3s3-v1]
extends = esp32s3_base
board = tlora-t3s3-v1
lib_deps =
${esp32_base.lib_deps}
caveman99/ESP32 Codec2@^1.0.1
build_flags =
${esp32_base.build_flags} -D TLORA_T3S3_V1 -I variants/tlora_t3s3_v1

View File

@ -0,0 +1,54 @@
#undef GPS_RX_PIN
#undef GPS_TX_PIN
#define HAS_SDCARD
#define SDCARD_USE_SPI1
#define USE_SSD1306
#define BATTERY_PIN 1 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage
// ratio of voltage divider = 2.0 (R42=100k, R43=100k)
#define ADC_MULTIPLIER 2.11 // 2.0 + 10% for correction of display undervoltage.
#define I2C_SDA 18 // I2C pins for this board
#define I2C_SCL 17
#define LED_PIN 37 // If defined we will blink this LED
#define BUTTON_PIN 0 // If defined, this will be used for user button presses,
#define BUTTON_NEED_PULLUP
// TTGO uses a common pinout for their SX1262 vs RF95 modules - both can be enabled and we will probe at runtime for RF95 and if
// not found then probe for SX1262
#define USE_RF95 // RFM95/SX127x
#define USE_SX1262
#define USE_SX1280
#define RF95_SCK 5
#define RF95_MISO 3
#define RF95_MOSI 6
#define RF95_NSS 7
#define LORA_RESET 8
#define LORA_DIO0 9
#define LORA_DIO1 9
#define LORA_DIO2 33 // SX1262 BUSY
#define LORA_DIO3 34 // Not connected on PCB, but internally on the TTGO SX1262, if DIO3 is high the TXCO is enabled
#ifdef USE_SX1262
#define SX126X_CS RF95_NSS // FIXME - we really should define LORA_CS instead
#define SX126X_DIO1 LORA_DIO1
#define SX126X_BUSY 36
#define SX126X_RESET LORA_RESET
#define SX126X_RXEN 21
#define SX126X_TXEN 10
#endif
#ifdef USE_SX1280
#define SX128X_CS RF95_NSS
#define SX128X_DIO1 LORA_DIO1
#define SX128X_BUSY 36
#define SX128X_RESET LORA_RESET
#define SX128X_RXEN 21
#define SX128X_TXEN 10
#endif

View File

@ -5,7 +5,5 @@ lib_deps =
${esp32_base.lib_deps}
caveman99/ESP32 Codec2@^1.0.1
; the RADIOLIB_GODMODE flag can be removed once the next version of RadioLib is released. (>5.5.0)
build_flags =
${esp32_base.build_flags} -D TLORA_V2_1_18 -I variants/tlora_v2_1_18 -D RADIOLIB_GODMODE
${esp32_base.build_flags} -D TLORA_V2_1_18 -I variants/tlora_v2_1_18