Compare commits

..

74 Commits

Author SHA1 Message Date
Ben Meadors
5757b88c48 Merge branch 'master' into NextHopRouter 2025-02-17 09:33:29 -06:00
A_Ponzano
7eb77276cd Add support for new NRF52 board, MeshLink (#5736)
* Add support for MeshLink

* Updated, enabled watchdog and added button definition

* added eink variant and removed some compile errors

* Small board json file edit

* Finally got trunk working (somehow?), this is just cleanup with trunk fmt

* Various improvements and cleanup. Removed the use of PIN_3V3_En and defined a specific WD_EN pin instead for better clarity. Will do a bit more testing asap to make sure everything still works as intended :)

* Enable on-board QSPI Flash

* run trunk fmt with clang-format

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
Co-authored-by: Thomas Göttgens <tgoettgens@gmail.com>
Co-authored-by: Austin <vidplace7@gmail.com>
2025-02-16 19:49:17 -06:00
Eric Severance
7648391f91 Reject invalid configuration for the default MQTT server (#6066)
* Sanity check configuration for the default MQTT server

* Skip for MESHTASTIC_EXCLUDE_MQTT

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-02-16 07:15:30 -06:00
porkcube
4407d9e040 assigning SDA/SCL so it actually works 8| (#6065) 2025-02-16 06:39:48 -06:00
GUVWAF
27fea5fc07 Fix STM32WL TCXO setting; enable logs and modules (#6063)
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-02-15 09:06:10 -06:00
Michael Gjelsø
2f6cd02111 Typo for Bandit button LEDs (#6053)
Changed Button 2 LED index define from BUTTON1_COLOR_INDEX to correct BUTTON2_COLOR_INDEX
2025-02-15 08:06:41 -06:00
GUVWAF
8c9947b05c Allow NeighborInfo on non-default frequency slot (#6061) 2025-02-15 07:55:51 -06:00
Eric Severance
50b7d6a0f7 Establish MQTT connection only from MQTT::runOnce (#6057)
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-02-14 20:32:41 -06:00
Eric Severance
c83ffd4911 Consider the MQTT TLS remote IP when enabled. (#6058) 2025-02-14 19:19:50 -06:00
Woutvstk
9b46cb4ef0 Rak4631 remove spi1 (#6042)
* Removed non-existant SPI1 interface on rak4631

* trunk fmt
2025-02-14 12:53:22 -06:00
porkcube
01935ea35e Add XIAO nRF52840 + Wio SX1262 DIY Variant (#5976)
* added xiao nRF52840 + xiao wio sx1262 DIY variant

* fix path / make buildy buildy

* pcf cruft from personal hw

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-02-14 06:50:28 -06:00
Austin
495f69cf90 Trunk: Trailing commas begone! (#6038) 2025-02-12 08:57:23 +08:00
Ben Meadors
cea072288a Merge branch 'master' into NextHopRouter 2025-02-07 06:02:06 -06:00
Tom Fifield
b91b66bc7a Merge branch 'master' into NextHopRouter 2025-02-02 10:18:32 +08:00
GUVWAF
dc4279e7fd Merge remote-tracking branch 'origin/master' into NextHopRouter 2025-01-15 20:53:12 +01:00
GUVWAF
766bd614a4 Merge branch 'master' into NextHopRouter 2025-01-12 17:53:20 +01:00
GUVWAF
a3d9582b35 Replies (e.g. tapback emoji) are also a valid confirmation of receipt 2025-01-11 16:01:33 +01:00
GUVWAF
d5a8587deb Merge branch 'master' into NextHopRouter 2025-01-11 15:24:02 +01:00
GUVWAF
5154e29b07 Merge branch 'master' into NextHopRouter 2025-01-07 20:05:22 +01:00
GUVWAF
919660e005 Merge branch 'master' into NextHopRouter 2025-01-04 12:26:21 +01:00
GUVWAF
c7d04d7064 Merge remote-tracking branch 'origin/master' into NextHopRouter 2024-12-29 19:57:16 +01:00
GUVWAF
4915a07c2a Merge branch 'master' into NextHopRouter 2024-12-18 21:05:25 +01:00
GUVWAF
98b4a29ef9 Merge branch 'master' into NextHopRouter 2024-12-10 20:26:52 +01:00
GUVWAF
41d0a39ba6 Merge remote-tracking branch 'origin/master' into NextHopRouter 2024-11-27 19:44:29 +01:00
GUVWAF
0952c861ae Don't cancel relay if we were the assigned next-hop 2024-11-27 19:44:19 +01:00
GUVWAF
017bff8be7 Merge remote-tracking branch 'origin/master' into NextHopRouter 2024-11-23 09:36:18 +01:00
GUVWAF
e593d54743 Merge remote-tracking branch 'origin/master' into NextHopRouter 2024-11-19 20:52:51 +01:00
GUVWAF
6a29793f23 Merge remote-tracking branch 'origin/master' into NextHopRouter 2024-11-18 18:02:30 +01:00
GUVWAF
47116f65cd Guard against clients setting next_hop/relay_node 2024-11-18 18:02:15 +01:00
Tom Fifield
98719e4c62 Merge branch 'master' into NextHopRouter 2024-11-17 09:50:39 +08:00
GUVWAF
bfc6a1940d Handle repeated/fallback to flooding packets properly
First check if it's not still in the TxQueue
2024-11-16 16:32:24 +01:00
GUVWAF
360637c25d Start retransmissions in NextHopRouter if ReliableRouter didn't do it 2024-11-16 16:31:31 +01:00
GUVWAF
3ea2918f7f Merge remote-tracking branch 'origin/master' into NextHopRouter 2024-11-16 13:44:10 +01:00
GUVWAF
b229abc2b4 Update protos 2024-11-16 13:42:32 +01:00
GUVWAF
dbe520c3ab Merge remote-tracking branch 'origin/master' into NextHopRouter 2024-11-14 19:00:45 +01:00
GUVWAF
42d17b3322 Formatting and correct NUM_RETRANSMISSIONS 2024-11-11 13:01:02 +01:00
GUVWAF
3725319b4b Merge remote-tracking branch 'origin/master' into NextHopRouter 2024-11-11 10:51:20 +01:00
GUVWAF
17495e7dbf Check for hop_limit and also update next hop when hop_start == hop_limit on ACK
Also check for broadcast in `getNextHop()`
2024-11-11 10:50:36 +01:00
GUVWAF
93bcee3aab Fix another cppcheck error 2024-11-09 20:01:19 +01:00
GUVWAF
71a90b3b78 Fix 'router' not in scope error 2024-11-08 22:22:43 +01:00
GUVWAF
be73b099a7 Fix cppcheck error 2024-11-08 22:14:25 +01:00
GUVWAF
f37abe8f0f Also when want_ack is set, we should try to retransmit 2024-11-08 21:42:54 +01:00
GUVWAF
78bf1e192b Use relayID 2024-11-08 21:28:36 +01:00
GUVWAF
70aa28c53c Stop retransmission for original packet 2024-11-08 21:28:19 +01:00
GUVWAF
fbefce7e10 Merge remote-tracking branch 'origin/master' into NextHopRouter 2024-11-08 20:14:48 +01:00
GUVWAF
69f88b9fdc Fix newlines in traceroute print logs 2024-11-05 21:28:02 +01:00
GUVWAF
24ff7c0bfb Better checking for fallback to flooding 2024-11-05 21:27:10 +01:00
GUVWAF
bb64b1480b Remove relayer if we cancel a transmission 2024-11-05 19:40:00 +01:00
GUVWAF
790801f8e7 Revert perhapsRebroadcast() 2024-11-05 18:57:50 +01:00
GUVWAF
28944adf20 Merge remote-tracking branch 'origin/master' into NextHopRouter 2024-11-05 18:54:32 +01:00
GUVWAF
aab973e81b Stop retransmission even if there's not relay node 2024-11-02 19:35:01 +01:00
GUVWAF
e4c98185d2 Improve ACKs for repeated packets and responses 2024-11-02 19:34:21 +01:00
GUVWAF
01344835af Fix retransmissions 2024-11-01 19:32:49 +01:00
GUVWAF
9de8d5ae66 Remove next-hop update logic from NeighborInfoModule 2024-11-01 17:04:01 +01:00
GUVWAF
ba4220fe50 Update protos, store multiple relayers 2024-11-01 17:02:04 +01:00
GUVWAF
aae4443e25 Merge remote-tracking branch 'origin/master' into NextHopRouter 2024-11-01 09:41:43 +01:00
GUVWAF
6fe42ed4c5 Add next_hop and relayed_by in PacketHistory for setting next-hop and handle flooding fallback 2024-11-01 09:16:51 +01:00
GUVWAF
2e303a33be Add 1 retransmission for intermediate hops when using NextHopRouter 2024-11-01 09:11:34 +01:00
GUVWAF
913268b132 temp 2024-11-01 09:11:34 +01:00
Thomas Göttgens
e91dcb4ec3 Merge branch 'master' into NextHopRouter 2024-09-04 09:57:03 +02:00
Thomas Göttgens
b8e01b4044 Merge branch 'master' into NextHopRouter 2024-06-19 18:00:27 +02:00
Thomas Göttgens
d4ef0cdba5 Merge branch 'master' into NextHopRouter 2024-05-03 15:35:05 +02:00
Thomas Göttgens
b456e34c6e Merge branch 'master' into NextHopRouter 2024-04-23 13:03:09 +02:00
GUVWAF
25ec0514da Resolve conflicts (needs testing) 2024-04-20 13:35:36 +02:00
GUVWAF
3ba9ecbbfe Also update next-hop based on received DM for us 2023-11-28 19:53:40 +01:00
GUVWAF
ef2c6eed05 Set last byte to 0xFF if it ended at 0x00
As per an idea of @S5NC
2023-11-27 20:36:48 +01:00
Ben Meadors
81f57b65e1 Merge branch 'master' into NextHopRouter 2023-11-26 15:56:50 -06:00
Thomas Göttgens
42757d847f Merge branch 'master' into NextHopRouter 2023-11-16 15:11:58 +01:00
Thomas Göttgens
c7293cf6f1 Merge branch 'master' into NextHopRouter 2023-10-31 14:14:11 +01:00
GUVWAF
27a492adf8 If packet traveled 1 hop, set relay_node as next_hop for the original transmitter 2023-10-14 17:18:05 +02:00
GUVWAF
9b1dd75549 Short-circuit to FloodingRouter for broadcasts 2023-10-14 17:17:12 +02:00
GUVWAF
44dc270c8a Set original hop limit in header flags 2023-10-13 21:00:38 +02:00
Ben Meadors
3776064b80 Merge branch 'master' into NextHopRouter 2023-10-03 06:58:04 -05:00
GUVWAF
0d6729b9eb Initial version of NextHopRouter 2023-10-02 19:31:23 +02:00
56 changed files with 1570 additions and 340 deletions

View File

@@ -0,0 +1,10 @@
{
"overrides": [
{
"files": "userPrefs.jsonc",
"options": {
"trailingComma": "none"
}
}
]
}

View File

@@ -11,9 +11,15 @@ build_flags =
${arduino_base.build_flags}
-flto
-Isrc/platform/stm32wl -g
-DMESHTASTIC_MINIMIZE_BUILD
-DMESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
-DMESHTASTIC_EXCLUDE_INPUTBROKER
-DMESHTASTIC_EXCLUDE_I2C
-DMESHTASTIC_EXCLUDE_POWERMON
-DMESHTASTIC_EXCLUDE_SCREEN
-DMESHTASTIC_EXCLUDE_MQTT
-DMESHTASTIC_EXCLUDE_BLUETOOTH
-DMESHTASTIC_EXCLUDE_PKI
-DMESHTASTIC_EXCLUDE_GPS
-DDEBUG_MUTE
; -DVECT_TAB_OFFSET=0x08000000
-DconfigUSE_CMSIS_RTOS_V2=1
; -DSPI_MODE_0=SPI_MODE0

52
boards/meshlink.json Normal file
View File

@@ -0,0 +1,52 @@
{
"build": {
"arduino": {
"ldscript": "nrf52840_s140_v6.ld"
},
"core": "nRF5",
"cpu": "cortex-m4",
"extra_flags": "-DMESHLINK -DNRF52840_XXAA",
"f_cpu": "64000000L",
"hwids": [
["0x239A", "0x00B3"],
["0x239A", "0x8029"],
["0x239A", "0x0029"],
["0x239A", "0x002A"],
["0x239A", "0x802A"]
],
"usb_product": "MeshLink",
"mcu": "nrf52840",
"variant": "meshlink",
"bsp": {
"name": "adafruit"
},
"softdevice": {
"sd_flags": "-DS140",
"sd_name": "s140",
"sd_version": "6.1.1",
"sd_fwid": "0x00B6"
},
"bootloader": {
"settings_addr": "0xFF000"
}
},
"connectivity": ["bluetooth"],
"debug": {
"jlink_device": "nRF52840_xxAA",
"svd_path": "nrf52840.svd"
},
"frameworks": ["arduino"],
"name": "MeshLink",
"upload": {
"maximum_ram_size": 248832,
"maximum_size": 815104,
"speed": 115200,
"protocol": "nrfutil",
"protocols": ["nrfutil", "jlink", "nrfjprog", "stlink"],
"use_1200bps_touch": true,
"require_upload_port": true,
"wait_for_upload_port": true
},
"url": "https://www.loraitalia.it",
"vendor": "LoraItalia"
}

View File

@@ -153,7 +153,7 @@ class AmbientLightingThread : public concurrency::OSThread
pixels.fill(BUTTON1_COLOR, BUTTON1_COLOR_INDEX, 1);
#endif
#if defined(BUTTON2_COLOR) && defined(BUTTON2_COLOR_INDEX)
pixels.fill(BUTTON2_COLOR, BUTTON1_COLOR_INDEX, 1);
pixels.fill(BUTTON2_COLOR, BUTTON2_COLOR_INDEX, 1);
#endif
#endif
pixels.show();

View File

@@ -6,28 +6,28 @@
void d_writeCommand(uint8_t c)
{
SPI1.beginTransaction(SPISettings(4000000, MSBFIRST, SPI_MODE0));
SPI.beginTransaction(SPISettings(4000000, MSBFIRST, SPI_MODE0));
if (PIN_EINK_DC >= 0)
digitalWrite(PIN_EINK_DC, LOW);
if (PIN_EINK_CS >= 0)
digitalWrite(PIN_EINK_CS, LOW);
SPI1.transfer(c);
SPI.transfer(c);
if (PIN_EINK_CS >= 0)
digitalWrite(PIN_EINK_CS, HIGH);
if (PIN_EINK_DC >= 0)
digitalWrite(PIN_EINK_DC, HIGH);
SPI1.endTransaction();
SPI.endTransaction();
}
void d_writeData(uint8_t d)
{
SPI1.beginTransaction(SPISettings(4000000, MSBFIRST, SPI_MODE0));
SPI.beginTransaction(SPISettings(4000000, MSBFIRST, SPI_MODE0));
if (PIN_EINK_CS >= 0)
digitalWrite(PIN_EINK_CS, LOW);
SPI1.transfer(d);
SPI.transfer(d);
if (PIN_EINK_CS >= 0)
digitalWrite(PIN_EINK_CS, HIGH);
SPI1.endTransaction();
SPI.endTransaction();
}
unsigned long d_waitWhileBusy(uint16_t busy_time)
@@ -53,7 +53,7 @@ unsigned long d_waitWhileBusy(uint16_t busy_time)
void scanEInkDevice(void)
{
SPI1.begin();
SPI.begin();
d_writeCommand(0x22);
d_writeData(0x83);
d_writeCommand(0x20);
@@ -62,6 +62,6 @@ void scanEInkDevice(void)
LOG_DEBUG("EInk display found");
else
LOG_DEBUG("EInk display not found");
SPI1.end();
SPI.end();
}
#endif

View File

@@ -140,6 +140,15 @@ bool EInkDisplay::connect()
adafruitDisplay->setRotation(3);
adafruitDisplay->setPartialWindow(0, 0, displayWidth, displayHeight);
}
#elif defined(MESHLINK)
{
auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY, SPI1);
adafruitDisplay = new GxEPD2_BW<EINK_DISPLAY_MODEL, EINK_DISPLAY_MODEL::HEIGHT>(*lowLevel);
adafruitDisplay->init();
adafruitDisplay->setRotation(3);
adafruitDisplay->setPartialWindow(0, 0, displayWidth, displayHeight);
}
#elif defined(RAK4630) || defined(MAKERPYTHON)
{
if (eink_found) {

View File

@@ -644,9 +644,9 @@ void setup()
// but we need to do this after main cpu init (esp32setup), because we need the random seed set
nodeDB = new NodeDB;
// If we're taking on the repeater role, use flood router and turn off 3V3_S rail because peripherals are not needed
// If we're taking on the repeater role, use NextHopRouter and turn off 3V3_S rail because peripherals are not needed
if (config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER) {
router = new FloodingRouter();
router = new NextHopRouter();
#ifdef PIN_3V3_EN
digitalWrite(PIN_3V3_EN, LOW);
#endif

View File

@@ -13,7 +13,8 @@ FloodingRouter::FloodingRouter() {}
ErrorCode FloodingRouter::send(meshtastic_MeshPacket *p)
{
// Add any messages _we_ send to the seen message list (so we will ignore all retransmissions we see)
wasSeenRecently(p); // FIXME, move this to a sniffSent method
p->relay_node = nodeDB->getLastByteOfNodeNum(getNodeNum()); // First set the relayer to us
wasSeenRecently(p); // FIXME, move this to a sniffSent method
return Router::send(p);
}
@@ -23,26 +24,17 @@ bool FloodingRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
if (wasSeenRecently(p)) { // Note: this will also add a recent packet record
printPacket("Ignore dupe incoming msg", p);
rxDupe++;
if (config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER &&
config.device.role != meshtastic_Config_DeviceConfig_Role_REPEATER &&
config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER_LATE) {
// cancel rebroadcast of this message *if* there was already one, unless we're a router/repeater!
if (Router::cancelSending(p->from, p->id))
txRelayCanceled++;
}
if (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_LATE && iface) {
iface->clampToLateRebroadcastWindow(getFrom(p), p->id);
}
/* If the original transmitter is doing retransmissions (hopStart equals hopLimit) for a reliable transmission, e.g., when
the ACK got lost, we will handle the packet again to make sure it gets an ACK to its packet. */
the ACK got lost, we will handle the packet again to make sure it gets an implicit ACK. */
bool isRepeated = p->hop_start > 0 && p->hop_start == p->hop_limit;
if (isRepeated) {
LOG_DEBUG("Repeated reliable tx");
if (!perhapsRebroadcast(p) && isToUs(p) && p->want_ack) {
// FIXME - channel index should be used, but the packet is still encrypted here
sendAckNak(meshtastic_Routing_Error_NONE, getFrom(p), p->id, 0, 0);
}
// Check if it's still in the Tx queue, if not, we have to relay it again
if (!findInTxQueue(p->from, p->id))
perhapsRebroadcast(p);
} else {
perhapsCancelDupe(p);
}
return true;
@@ -51,13 +43,27 @@ bool FloodingRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
return Router::shouldFilterReceived(p);
}
void FloodingRouter::perhapsCancelDupe(const meshtastic_MeshPacket *p)
{
if (config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER &&
config.device.role != meshtastic_Config_DeviceConfig_Role_REPEATER &&
config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER_LATE) {
// cancel rebroadcast of this message *if* there was already one, unless we're a router/repeater!
if (Router::cancelSending(p->from, p->id))
txRelayCanceled++;
}
if (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_LATE && iface) {
iface->clampToLateRebroadcastWindow(getFrom(p), p->id);
}
}
bool FloodingRouter::isRebroadcaster()
{
return config.device.role != meshtastic_Config_DeviceConfig_Role_CLIENT_MUTE &&
config.device.rebroadcast_mode != meshtastic_Config_DeviceConfig_RebroadcastMode_NONE;
}
bool FloodingRouter::perhapsRebroadcast(const meshtastic_MeshPacket *p)
void FloodingRouter::perhapsRebroadcast(const meshtastic_MeshPacket *p)
{
if (!isToUs(p) && (p->hop_limit > 0) && !isFromUs(p)) {
if (p->id != 0) {
@@ -72,13 +78,12 @@ bool FloodingRouter::perhapsRebroadcast(const meshtastic_MeshPacket *p)
tosend->hop_limit = 2;
}
#endif
tosend->next_hop = NO_NEXT_HOP_PREFERENCE; // this should already be the case, but just in case
LOG_INFO("Rebroadcast received floodmsg");
// Note: we are careful to resend using the original senders node id
// We are careful not to call our hooked version of send() - because we don't want to check this again
Router::send(tosend);
return true;
} else {
LOG_DEBUG("No rebroadcast: Role = CLIENT_MUTE or Rebroadcast Mode = NONE");
}
@@ -86,13 +91,12 @@ bool FloodingRouter::perhapsRebroadcast(const meshtastic_MeshPacket *p)
LOG_DEBUG("Ignore 0 id broadcast");
}
}
return false;
}
void FloodingRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtastic_Routing *c)
{
bool isAckorReply = (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) && (p->decoded.request_id != 0);
bool isAckorReply = (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) &&
(p->decoded.request_id != 0 || p->decoded.reply_id != 0);
if (isAckorReply && !isToUs(p) && !isBroadcast(p->to)) {
// do not flood direct message that is ACKed or replied to
LOG_DEBUG("Rxd an ACK/reply not for me, cancel rebroadcast");

View File

@@ -1,6 +1,5 @@
#pragma once
#include "PacketHistory.h"
#include "Router.h"
/**
@@ -26,14 +25,11 @@
Any entries in recentBroadcasts that are older than X seconds (longer than the
max time a flood can take) will be discarded.
*/
class FloodingRouter : public Router, protected PacketHistory
class FloodingRouter : public Router
{
private:
bool isRebroadcaster();
/** Check if we should rebroadcast this packet, and do so if needed
* @return true if rebroadcasted */
bool perhapsRebroadcast(const meshtastic_MeshPacket *p);
/* Check if we should rebroadcast this packet, and do so if needed */
void perhapsRebroadcast(const meshtastic_MeshPacket *p);
public:
/**
@@ -62,4 +58,10 @@ class FloodingRouter : public Router, protected PacketHistory
* Look for broadcasts we need to rebroadcast
*/
virtual void sniffReceived(const meshtastic_MeshPacket *p, const meshtastic_Routing *c) override;
/* Call when receiving a duplicate packet to check whether we should cancel a packet in the Tx queue */
void perhapsCancelDupe(const meshtastic_MeshPacket *p);
// Return true if we are a rebroadcaster
bool isRebroadcaster();
};

View File

@@ -117,6 +117,19 @@ meshtastic_MeshPacket *MeshPacketQueue::remove(NodeNum from, PacketId id, bool t
return NULL;
}
/* Attempt to find a packet from this queue. Return true if it was found. */
bool MeshPacketQueue::find(NodeNum from, PacketId id)
{
for (auto it = queue.begin(); it != queue.end(); it++) {
auto p = (*it);
if (getFrom(p) == from && p->id == id) {
return true;
}
}
return false;
}
/**
* Attempt to find a lower-priority packet in the queue and replace it with the provided one.
* @return True if the replacement succeeded, false otherwise

View File

@@ -37,4 +37,7 @@ class MeshPacketQueue
/** Attempt to find and remove a packet from this queue. Returns the packet which was removed from the queue */
meshtastic_MeshPacket *remove(NodeNum from, PacketId id, bool tx_normal = true, bool tx_late = true);
/* Attempt to find a packet from this queue. Return true if it was found. */
bool find(NodeNum from, PacketId id);
};

View File

@@ -173,7 +173,9 @@ void MeshService::handleToRadio(meshtastic_MeshPacket &p)
return;
}
#endif
p.from = 0; // We don't let phones assign nodenums to their sent messages
p.from = 0; // We don't let clients assign nodenums to their sent messages
p.next_hop = NO_NEXT_HOP_PREFERENCE; // We don't let clients assign next_hop to their sent messages
p.relay_node = NO_RELAY_NODE; // We don't let clients assign relay_node to their sent messages
if (p.id == 0)
p.id = generatePacketId(); // If the phone didn't supply one, then pick one

View File

@@ -40,6 +40,11 @@ enum RxSource {
/// We normally just use max 3 hops for sending reliable messages
#define HOP_RELIABLE 3
// For old firmware or when falling back to flooding, there is no next-hop preference
#define NO_NEXT_HOP_PREFERENCE 0
// For old firmware there is no relay node set
#define NO_RELAY_NODE 0
typedef int ErrorCode;
/// Alloc and free packets to our global, ISR safe pool

272
src/mesh/NextHopRouter.cpp Normal file
View File

@@ -0,0 +1,272 @@
#include "NextHopRouter.h"
NextHopRouter::NextHopRouter() {}
PendingPacket::PendingPacket(meshtastic_MeshPacket *p, uint8_t numRetransmissions)
{
packet = p;
this->numRetransmissions = numRetransmissions - 1; // We subtract one, because we assume the user just did the first send
}
/**
* Send a packet
*/
ErrorCode NextHopRouter::send(meshtastic_MeshPacket *p)
{
// Add any messages _we_ send to the seen message list (so we will ignore all retransmissions we see)
p->relay_node = nodeDB->getLastByteOfNodeNum(getNodeNum()); // First set the relayer to us
wasSeenRecently(p); // FIXME, move this to a sniffSent method
p->next_hop = getNextHop(p->to, p->relay_node); // set the next hop
LOG_DEBUG("Setting next hop for packet with dest %x to %x", p->to, p->next_hop);
// If it's from us, ReliableRouter already handles retransmissions if want_ack is set. If a next hop is set and hop limit is
// not 0 or want_ack is set, start retransmissions
if ((!isFromUs(p) || !p->want_ack) && p->next_hop != NO_NEXT_HOP_PREFERENCE && (p->hop_limit > 0 || p->want_ack))
startRetransmission(packetPool.allocCopy(*p)); // start retransmission for relayed packet
return Router::send(p);
}
bool NextHopRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
{
bool wasFallback = false;
bool weWereNextHop = false;
if (wasSeenRecently(p, true, &wasFallback, &weWereNextHop)) { // Note: this will also add a recent packet record
printPacket("Ignore dupe incoming msg", p);
rxDupe++;
stopRetransmission(p->from, p->id);
// If it was a fallback to flooding, try to relay again
if (wasFallback) {
LOG_INFO("Fallback to flooding from relay_node=0x%x", p->relay_node);
// Check if it's still in the Tx queue, if not, we have to relay it again
if (!findInTxQueue(p->from, p->id))
perhapsRelay(p);
} else {
bool isRepeated = p->hop_start > 0 && p->hop_start == p->hop_limit;
// If repeated and not in Tx queue anymore, try relaying again, or if we are the destination, send the ACK again
if (isRepeated) {
if (!findInTxQueue(p->from, p->id) && !perhapsRelay(p) && isToUs(p) && p->want_ack)
sendAckNak(meshtastic_Routing_Error_NONE, getFrom(p), p->id, p->channel, 0);
} else if (!weWereNextHop) {
perhapsCancelDupe(p); // If it's a dupe, cancel relay if we were not explicitly asked to relay
}
}
return true;
}
return Router::shouldFilterReceived(p);
}
void NextHopRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtastic_Routing *c)
{
NodeNum ourNodeNum = getNodeNum();
uint8_t ourRelayID = nodeDB->getLastByteOfNodeNum(ourNodeNum);
bool isAckorReply = (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) &&
(p->decoded.request_id != 0 || p->decoded.reply_id != 0);
if (isAckorReply) {
// Update next-hop for the original transmitter of this successful transmission to the relay node, but ONLY if "from" is
// not 0 (means implicit ACK) and original packet was also relayed by this node, or we sent it directly to the destination
if (p->from != 0) {
meshtastic_NodeInfoLite *origTx = nodeDB->getMeshNode(p->from);
if (origTx) {
// Either relayer of ACK was also a relayer of the packet, or we were the relayer and the ACK came directly from
// the destination
if (wasRelayer(p->relay_node, p->decoded.request_id, p->to) ||
(wasRelayer(ourRelayID, p->decoded.request_id, p->to) && p->hop_start != 0 && p->hop_start == p->hop_limit)) {
if (origTx->next_hop != p->relay_node) { // Not already set
LOG_INFO("Update next hop of 0x%x to 0x%x based on ACK/reply", p->from, p->relay_node);
origTx->next_hop = p->relay_node;
}
}
}
}
if (!isToUs(p)) {
Router::cancelSending(p->to, p->decoded.request_id); // cancel rebroadcast for this DM
// stop retransmission for the original packet
stopRetransmission(p->to, p->decoded.request_id); // for original packet, from = to and id = request_id
}
}
perhapsRelay(p);
// handle the packet as normal
Router::sniffReceived(p, c);
}
/* Check if we should be relaying this packet if so, do so. */
bool NextHopRouter::perhapsRelay(const meshtastic_MeshPacket *p)
{
if (!isToUs(p) && !isFromUs(p) && p->hop_limit > 0) {
if (p->next_hop == NO_NEXT_HOP_PREFERENCE || p->next_hop == nodeDB->getLastByteOfNodeNum(getNodeNum())) {
if (isRebroadcaster()) {
meshtastic_MeshPacket *tosend = packetPool.allocCopy(*p); // keep a copy because we will be sending it
LOG_INFO("Relaying received message coming from %x", p->relay_node);
tosend->hop_limit--; // bump down the hop count
NextHopRouter::send(tosend);
return true;
} else {
LOG_DEBUG("Not rebroadcasting: Role = CLIENT_MUTE or Rebroadcast Mode = NONE");
}
}
}
return false;
}
/**
* Get the next hop for a destination, given the relay node
* @return the node number of the next hop, 0 if no preference (fallback to FloodingRouter)
*/
uint8_t NextHopRouter::getNextHop(NodeNum to, uint8_t relay_node)
{
// When we're a repeater router->sniffReceived will call NextHopRouter directly without checking for broadcast
if (isBroadcast(to))
return NO_NEXT_HOP_PREFERENCE;
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(to);
if (node && node->next_hop) {
// We are careful not to return the relay node as the next hop
if (node->next_hop != relay_node) {
// LOG_DEBUG("Next hop for 0x%x is 0x%x", to, node->next_hop);
return node->next_hop;
} else
LOG_WARN("Next hop for 0x%x is 0x%x, same as relayer; set no pref", to, node->next_hop);
}
return NO_NEXT_HOP_PREFERENCE;
}
PendingPacket *NextHopRouter::findPendingPacket(GlobalPacketId key)
{
auto old = pending.find(key); // If we have an old record, someone messed up because id got reused
if (old != pending.end()) {
return &old->second;
} else
return NULL;
}
/**
* Stop any retransmissions we are doing of the specified node/packet ID pair
*/
bool NextHopRouter::stopRetransmission(NodeNum from, PacketId id)
{
auto key = GlobalPacketId(from, id);
return stopRetransmission(key);
}
bool NextHopRouter::stopRetransmission(GlobalPacketId key)
{
auto old = findPendingPacket(key);
if (old) {
auto p = old->packet;
/* Only when we already transmitted a packet via LoRa, we will cancel the packet in the Tx queue
to avoid canceling a transmission if it was ACKed super fast via MQTT */
if (old->numRetransmissions < NUM_RELIABLE_RETX - 1) {
// remove the 'original' (identified by originator and packet->id) from the txqueue and free it
cancelSending(getFrom(p), p->id);
// now free the pooled copy for retransmission too
packetPool.release(p);
}
auto numErased = pending.erase(key);
assert(numErased == 1);
return true;
} else
return false;
}
/**
* Add p to the list of packets to retransmit occasionally. We will free it once we stop retransmitting.
*/
PendingPacket *NextHopRouter::startRetransmission(meshtastic_MeshPacket *p, uint8_t numReTx)
{
auto id = GlobalPacketId(p);
auto rec = PendingPacket(p, numReTx);
stopRetransmission(getFrom(p), p->id);
setNextTx(&rec);
pending[id] = rec;
return &pending[id];
}
/**
* Do any retransmissions that are scheduled (FIXME - for the time being called from loop)
*/
int32_t NextHopRouter::doRetransmissions()
{
uint32_t now = millis();
int32_t d = INT32_MAX;
// FIXME, we should use a better datastructure rather than walking through this map.
// for(auto el: pending) {
for (auto it = pending.begin(), nextIt = it; it != pending.end(); it = nextIt) {
++nextIt; // we use this odd pattern because we might be deleting it...
auto &p = it->second;
bool stillValid = true; // assume we'll keep this record around
// FIXME, handle 51 day rolloever here!!!
if (p.nextTxMsec <= now) {
if (p.numRetransmissions == 0) {
if (isFromUs(p.packet)) {
LOG_DEBUG("Reliable send failed, returning a nak for fr=0x%x,to=0x%x,id=0x%x", p.packet->from, p.packet->to,
p.packet->id);
sendAckNak(meshtastic_Routing_Error_MAX_RETRANSMIT, getFrom(p.packet), p.packet->id, p.packet->channel);
}
// Note: we don't stop retransmission here, instead the Nak packet gets processed in sniffReceived
stopRetransmission(it->first);
stillValid = false; // just deleted it
} else {
LOG_DEBUG("Sending retransmission fr=0x%x,to=0x%x,id=0x%x, tries left=%d", p.packet->from, p.packet->to,
p.packet->id, p.numRetransmissions);
if (!isBroadcast(p.packet->to)) {
if (p.numRetransmissions == 1) {
// Last retransmission, reset next_hop (fallback to FloodingRouter)
p.packet->next_hop = NO_NEXT_HOP_PREFERENCE;
// Also reset it in the nodeDB
meshtastic_NodeInfoLite *sentTo = nodeDB->getMeshNode(p.packet->to);
if (sentTo) {
LOG_INFO("Resetting next hop for packet with dest 0x%x\n", p.packet->to);
sentTo->next_hop = NO_NEXT_HOP_PREFERENCE;
}
FloodingRouter::send(packetPool.allocCopy(*p.packet));
} else {
NextHopRouter::send(packetPool.allocCopy(*p.packet));
}
} else {
// Note: we call the superclass version because we don't want to have our version of send() add a new
// retransmission record
FloodingRouter::send(packetPool.allocCopy(*p.packet));
}
// Queue again
--p.numRetransmissions;
setNextTx(&p);
}
}
if (stillValid) {
// Update our desired sleep delay
int32_t t = p.nextTxMsec - now;
d = min(t, d);
}
}
return d;
}
void NextHopRouter::setNextTx(PendingPacket *pending)
{
assert(iface);
auto d = iface->getRetransmissionMsec(pending->packet);
pending->nextTxMsec = millis() + d;
LOG_DEBUG("Setting next retransmission in %u msecs: ", d);
printPacket("", pending->packet);
setReceivedMessage(); // Run ASAP, so we can figure out our correct sleep time
}

151
src/mesh/NextHopRouter.h Normal file
View File

@@ -0,0 +1,151 @@
#pragma once
#include "FloodingRouter.h"
#include <unordered_map>
/**
* An identifier for a globally unique message - a pair of the sending nodenum and the packet id assigned
* to that message
*/
struct GlobalPacketId {
NodeNum node;
PacketId id;
bool operator==(const GlobalPacketId &p) const { return node == p.node && id == p.id; }
explicit GlobalPacketId(const meshtastic_MeshPacket *p)
{
node = getFrom(p);
id = p->id;
}
GlobalPacketId(NodeNum _from, PacketId _id)
{
node = _from;
id = _id;
}
};
/**
* A packet queued for retransmission
*/
struct PendingPacket {
meshtastic_MeshPacket *packet;
/** The next time we should try to retransmit this packet */
uint32_t nextTxMsec = 0;
/** Starts at NUM_RETRANSMISSIONS -1 and counts down. Once zero it will be removed from the list */
uint8_t numRetransmissions = 0;
PendingPacket() {}
explicit PendingPacket(meshtastic_MeshPacket *p, uint8_t numRetransmissions);
};
class GlobalPacketIdHashFunction
{
public:
size_t operator()(const GlobalPacketId &p) const { return (std::hash<NodeNum>()(p.node)) ^ (std::hash<PacketId>()(p.id)); }
};
/*
Router for direct messages, which only relays if it is the next hop for a packet. The next hop is set by the current
relayer of a packet, which bases this on information from a previous successful delivery to the destination via flooding.
Namely, in the PacketHistory, we keep track of (up to 3) relayers of a packet. When the ACK is delivered back to us via a node
that also relayed the original packet, we use that node as next hop for the destination from then on. This makes sure that only
when theres a two-way connection, we assign a next hop. Both the ReliableRouter and NextHopRouter will do retransmissions (the
NextHopRouter only 1 time). For the final retry, if no one actually relayed the packet, it will reset the next hop in order to
fall back to the FloodingRouter again. Note that thus also intermediate hops will do a single retransmission if the intended
next-hop didnt relay, in order to fix changes in the middle of the route.
*/
class NextHopRouter : public FloodingRouter
{
public:
/**
* Constructor
*
*/
NextHopRouter();
/**
* Send a packet
* @return an error code
*/
virtual ErrorCode send(meshtastic_MeshPacket *p) override;
/** Do our retransmission handling */
virtual int32_t runOnce() override
{
// Note: We must doRetransmissions FIRST, because it might queue up work for the base class runOnce implementation
doRetransmissions();
int32_t r = FloodingRouter::runOnce();
// Also after calling runOnce there might be new packets to retransmit
auto d = doRetransmissions();
return min(d, r);
}
// The number of retransmissions intermediate nodes will do (actually 1 less than this)
constexpr static uint8_t NUM_INTERMEDIATE_RETX = 2;
// The number of retransmissions the original sender will do
constexpr static uint8_t NUM_RELIABLE_RETX = 3;
protected:
/**
* Pending retransmissions
*/
std::unordered_map<GlobalPacketId, PendingPacket, GlobalPacketIdHashFunction> pending;
/**
* Should this incoming filter be dropped?
*
* Called immediately on reception, before any further processing.
* @return true to abandon the packet
*/
virtual bool shouldFilterReceived(const meshtastic_MeshPacket *p) override;
/**
* Look for packets we need to relay
*/
virtual void sniffReceived(const meshtastic_MeshPacket *p, const meshtastic_Routing *c) override;
/**
* Try to find the pending packet record for this ID (or NULL if not found)
*/
PendingPacket *findPendingPacket(NodeNum from, PacketId id) { return findPendingPacket(GlobalPacketId(from, id)); }
PendingPacket *findPendingPacket(GlobalPacketId p);
/**
* Add p to the list of packets to retransmit occasionally. We will free it once we stop retransmitting.
*/
PendingPacket *startRetransmission(meshtastic_MeshPacket *p, uint8_t numReTx = NUM_INTERMEDIATE_RETX);
/**
* Stop any retransmissions we are doing of the specified node/packet ID pair
*
* @return true if we found and removed a transmission with this ID
*/
bool stopRetransmission(NodeNum from, PacketId id);
bool stopRetransmission(GlobalPacketId p);
/**
* Do any retransmissions that are scheduled (FIXME - for the time being called from loop)
*
* @return the number of msecs until our next retransmission or MAXINT if none scheduled
*/
int32_t doRetransmissions();
void setNextTx(PendingPacket *pending);
private:
/**
* Get the next hop for a destination, given the relay node
* @return the node number of the next hop, 0 if no preference (fallback to FloodingRouter)
*/
uint8_t getNextHop(NodeNum to, uint8_t relay_node);
/** Check if we should be relaying this packet if so, do so.
* @return true if we did relay */
bool perhapsRelay(const meshtastic_MeshPacket *p);
};

View File

@@ -104,6 +104,9 @@ class NodeDB
/// @return our node number
NodeNum getNodeNum() { return myNodeInfo.my_node_num; }
// @return last byte of a NodeNum, 0xFF if it ended at 0x00
uint8_t getLastByteOfNodeNum(NodeNum num) { return (uint8_t)((num & 0xFF) ? (num & 0xFF) : 0xFF); }
/// 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);

View File

@@ -16,7 +16,7 @@ PacketHistory::PacketHistory()
/**
* Update recentBroadcasts and return true if we have already seen this packet
*/
bool PacketHistory::wasSeenRecently(const meshtastic_MeshPacket *p, bool withUpdate)
bool PacketHistory::wasSeenRecently(const meshtastic_MeshPacket *p, bool withUpdate, bool *wasFallback, bool *weWereNextHop)
{
if (p->id == 0) {
LOG_DEBUG("Ignore message with zero id");
@@ -27,6 +27,9 @@ bool PacketHistory::wasSeenRecently(const meshtastic_MeshPacket *p, bool withUpd
r.id = p->id;
r.sender = getFrom(p);
r.rxTimeMsec = millis();
r.next_hop = p->next_hop;
r.relayed_by[0] = p->relay_node;
// LOG_INFO("Add relayed_by 0x%x for id=0x%x", p->relay_node, r.id);
auto found = recentPackets.find(r);
bool seenRecently = (found != recentPackets.end()); // found not equal to .end() means packet was seen recently
@@ -40,14 +43,36 @@ bool PacketHistory::wasSeenRecently(const meshtastic_MeshPacket *p, bool withUpd
if (seenRecently) {
LOG_DEBUG("Found existing packet record for fr=0x%x,to=0x%x,id=0x%x", p->from, p->to, p->id);
uint8_t ourRelayID = nodeDB->getLastByteOfNodeNum(nodeDB->getNodeNum());
if (wasFallback) {
// If it was seen with a next-hop not set to us and now it's NO_NEXT_HOP_PREFERENCE, and the relayer relayed already
// before, it's a fallback to flooding. If we didn't already relay and the next-hop neither, we might need to handle
// it now.
if (found->sender != nodeDB->getNodeNum() && found->next_hop != NO_NEXT_HOP_PREFERENCE &&
found->next_hop != ourRelayID && p->next_hop == NO_NEXT_HOP_PREFERENCE && wasRelayer(p->relay_node, found) &&
!wasRelayer(ourRelayID, found) && !wasRelayer(found->next_hop, found)) {
*wasFallback = true;
}
}
// Check if we were the next hop for this packet
if (weWereNextHop) {
*weWereNextHop = found->next_hop == ourRelayID;
}
}
if (withUpdate) {
if (found != recentPackets.end()) { // delete existing to updated timestamp (re-insert)
recentPackets.erase(found); // as unsorted_set::iterator is const (can't update timestamp - so re-insert..)
if (found != recentPackets.end()) { // delete existing to updated timestamp and relayed_by (re-insert)
// Add the existing relayed_by to the new record
for (uint8_t i = 0; i < NUM_RELAYERS - 1; i++) {
if (found->relayed_by[i])
r.relayed_by[i + 1] = found->relayed_by[i];
}
r.next_hop = found->next_hop; // keep the original next_hop (such that we check whether we were originally asked)
recentPackets.erase(found); // as unsorted_set::iterator is const (can't update - so re-insert..)
}
recentPackets.insert(r);
printPacket("Add packet record", p);
LOG_DEBUG("Add packet record fr=0x%x, id=0x%x", p->from, p->id);
}
// Capacity is reerved, so only purge expired packets if recentPackets fills past 90% capacity
@@ -75,4 +100,59 @@ void PacketHistory::clearExpiredRecentPackets()
}
LOG_DEBUG("recentPackets size=%ld (after clearing expired packets)", recentPackets.size());
}
/* Check if a certain node was a relayer of a packet in the history given an ID and sender
* @return true if node was indeed a relayer, false if not */
bool PacketHistory::wasRelayer(const uint8_t relayer, const uint32_t id, const NodeNum sender)
{
if (relayer == 0)
return false;
PacketRecord r = {.sender = sender, .id = id, .rxTimeMsec = 0, .next_hop = 0};
auto found = recentPackets.find(r);
if (found == recentPackets.end()) {
return false;
}
return wasRelayer(relayer, found);
}
/* Check if a certain node was a relayer of a packet in the history given iterator
* @return true if node was indeed a relayer, false if not */
bool PacketHistory::wasRelayer(const uint8_t relayer, std::unordered_set<PacketRecord, PacketRecordHashFunction>::iterator r)
{
for (uint8_t i = 0; i < NUM_RELAYERS; i++) {
if (r->relayed_by[i] == relayer) {
return true;
}
}
return false;
}
// Remove a relayer from the list of relayers of a packet in the history given an ID and sender
void PacketHistory::removeRelayer(const uint8_t relayer, const uint32_t id, const NodeNum sender)
{
PacketRecord r = {.sender = sender, .id = id, .rxTimeMsec = 0, .next_hop = 0};
auto found = recentPackets.find(r);
if (found == recentPackets.end()) {
return;
}
// Make a copy of the found record
r.next_hop = found->next_hop;
r.rxTimeMsec = found->rxTimeMsec;
// Only add the relayers that are not the one we want to remove
uint8_t j = 0;
for (uint8_t i = 0; i < NUM_RELAYERS; i++) {
if (found->relayed_by[i] != relayer) {
r.relayed_by[j] = found->relayed_by[i];
j++;
}
}
recentPackets.erase(found);
recentPackets.insert(r);
}

View File

@@ -1,6 +1,6 @@
#pragma once
#include "Router.h"
#include "NodeDB.h"
#include <unordered_set>
/// We clear our old flood record 10 minutes after we see the last of it
@@ -10,13 +10,18 @@
#define FLOOD_EXPIRE_TIME (10 * 60 * 1000L)
#endif
#define NUM_RELAYERS \
3 // Number of relayer we keep track of. Use 3 to be efficient with memory alignment of PacketRecord to 16 bytes
/**
* A record of a recent message broadcast
*/
struct PacketRecord {
NodeNum sender;
PacketId id;
uint32_t rxTimeMsec; // Unix time in msecs - the time we received it
uint32_t rxTimeMsec; // Unix time in msecs - the time we received it
uint8_t next_hop; // The next hop asked for this packet
uint8_t relayed_by[NUM_RELAYERS]; // Array of nodes that relayed this packet
bool operator==(const PacketRecord &p) const { return sender == p.sender && id == p.id; }
};
@@ -44,6 +49,20 @@ class PacketHistory
* Update recentBroadcasts and return true if we have already seen this packet
*
* @param withUpdate if true and not found we add an entry to recentPackets
* @param wasFallback if not nullptr, packet will be checked for fallback to flooding and value will be set to true if so
* @param weWereNextHop if not nullptr, packet will be checked for us being the next hop and value will be set to true if so
*/
bool wasSeenRecently(const meshtastic_MeshPacket *p, bool withUpdate = true);
};
bool wasSeenRecently(const meshtastic_MeshPacket *p, bool withUpdate = true, bool *wasFallback = nullptr,
bool *weWereNextHop = nullptr);
/* Check if a certain node was a relayer of a packet in the history given an ID and sender
* @return true if node was indeed a relayer, false if not */
bool wasRelayer(const uint8_t relayer, const uint32_t id, const NodeNum sender);
/* Check if a certain node was a relayer of a packet in the history given iterator
* @return true if node was indeed a relayer, false if not */
bool wasRelayer(const uint8_t relayer, std::unordered_set<PacketRecord, PacketRecordHashFunction>::iterator r);
// Remove a relayer from the list of relayers of a packet in the history given an ID and sender
void removeRelayer(const uint8_t relayer, const uint32_t id, const NodeNum sender);
};

View File

@@ -12,6 +12,7 @@
#include "PhoneAPI.h"
#include "PowerFSM.h"
#include "RadioInterface.h"
#include "Router.h"
#include "SPILock.h"
#include "TypeConversions.h"
#include "main.h"

View File

@@ -340,6 +340,10 @@ void printPacket(const char *prefix, const meshtastic_MeshPacket *p)
out += DEBUG_PORT.mt_sprintf(" via MQTT");
if (p->hop_start != 0)
out += DEBUG_PORT.mt_sprintf(" hopStart=%d", p->hop_start);
if (p->next_hop != 0)
out += DEBUG_PORT.mt_sprintf(" nextHop=0x%x", p->next_hop);
if (p->relay_node != 0)
out += DEBUG_PORT.mt_sprintf(" relay=0x%x", p->relay_node);
if (p->priority != 0)
out += DEBUG_PORT.mt_sprintf(" priority=%d", p->priority);
@@ -620,8 +624,8 @@ size_t RadioInterface::beginSending(meshtastic_MeshPacket *p)
radioBuffer.header.to = p->to;
radioBuffer.header.id = p->id;
radioBuffer.header.channel = p->channel;
radioBuffer.header.next_hop = 0; // *** For future use ***
radioBuffer.header.relay_node = 0; // *** For future use ***
radioBuffer.header.next_hop = p->next_hop;
radioBuffer.header.relay_node = p->relay_node;
if (p->hop_limit > HOP_MAX) {
LOG_WARN("hop limit %d is too high, setting to %d", p->hop_limit, HOP_RELIABLE);
p->hop_limit = HOP_RELIABLE;

View File

@@ -38,10 +38,10 @@ typedef struct {
/** The channel hash - used as a hint for the decoder to limit which channels we consider */
uint8_t channel;
// ***For future use*** Last byte of the NodeNum of the next-hop for this packet
// Last byte of the NodeNum of the next-hop for this packet
uint8_t next_hop;
// ***For future use*** Last byte of the NodeNum of the node that will relay/relayed this packet
// Last byte of the NodeNum of the node that will relay/relayed this packet
uint8_t relay_node;
} PacketHeader;
@@ -155,6 +155,9 @@ class RadioInterface
/** Attempt to cancel a previously sent packet. Returns true if a packet was found we could cancel */
virtual bool cancelSending(NodeNum from, PacketId id) { return false; }
/** Attempt to find a packet in the TxQueue. Returns true if the packet was found. */
virtual bool findInTxQueue(NodeNum from, PacketId id) { return false; }
// methods from radiohead
/// Initialise the Driver transport hardware and software.

View File

@@ -222,6 +222,12 @@ bool RadioLibInterface::cancelSending(NodeNum from, PacketId id)
return result;
}
/** Attempt to find a packet in the TxQueue. Returns true if the packet was found. */
bool RadioLibInterface::findInTxQueue(NodeNum from, PacketId id)
{
return txQueue.find(from, id);
}
/** radio helper thread callback.
We never immediately transmit after any operation (either Rx or Tx). Instead we should wait a random multiple of
'slotTimes' (see definition in RadioInterface.h) taken from a contention window (CW) to lower the chance of collision.
@@ -445,6 +451,9 @@ void RadioLibInterface::handleReceiveInterrupt()
mp->hop_start = (radioBuffer.header.flags & PACKET_FLAGS_HOP_START_MASK) >> PACKET_FLAGS_HOP_START_SHIFT;
mp->want_ack = !!(radioBuffer.header.flags & PACKET_FLAGS_WANT_ACK_MASK);
mp->via_mqtt = !!(radioBuffer.header.flags & PACKET_FLAGS_VIA_MQTT_MASK);
// If hop_start is not set, next_hop and relay_node are invalid (firmware <2.3)
mp->next_hop = mp->hop_start == 0 ? NO_NEXT_HOP_PREFERENCE : radioBuffer.header.next_hop;
mp->relay_node = mp->hop_start == 0 ? NO_RELAY_NODE : radioBuffer.header.relay_node;
addReceiveMetadata(mp);

View File

@@ -135,6 +135,9 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified
/** Attempt to cancel a previously sent packet. Returns true if a packet was found we could cancel */
virtual bool cancelSending(NodeNum from, PacketId id) override;
/** Attempt to find a packet in the TxQueue. Returns true if the packet was found. */
virtual bool findInTxQueue(NodeNum from, PacketId id) override;
private:
/** if we have something waiting to send, start a short (random) timer so we can come check for collision before actually
* doing the transmit */

View File

@@ -23,7 +23,7 @@ ErrorCode ReliableRouter::send(meshtastic_MeshPacket *p)
}
auto copy = packetPool.allocCopy(*p);
startRetransmission(copy);
startRetransmission(copy, NUM_RELIABLE_RETX);
}
/* If we have pending retransmissions, add the airtime of this packet to it, because during that time we cannot receive an
@@ -35,7 +35,7 @@ ErrorCode ReliableRouter::send(meshtastic_MeshPacket *p)
}
}
return FloodingRouter::send(p);
return isBroadcast(p->to) ? FloodingRouter::send(p) : NextHopRouter::send(p);
}
bool ReliableRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
@@ -73,7 +73,7 @@ bool ReliableRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
i->second.nextTxMsec += iface->getPacketTime(p);
}
return FloodingRouter::shouldFilterReceived(p);
return isBroadcast(p->to) ? FloodingRouter::shouldFilterReceived(p) : NextHopRouter::shouldFilterReceived(p);
}
/**
@@ -138,126 +138,5 @@ void ReliableRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtas
}
// handle the packet as normal
FloodingRouter::sniffReceived(p, c);
}
#define NUM_RETRANSMISSIONS 3
PendingPacket::PendingPacket(meshtastic_MeshPacket *p)
{
packet = p;
numRetransmissions = NUM_RETRANSMISSIONS - 1; // We subtract one, because we assume the user just did the first send
}
PendingPacket *ReliableRouter::findPendingPacket(GlobalPacketId key)
{
auto old = pending.find(key); // If we have an old record, someone messed up because id got reused
if (old != pending.end()) {
return &old->second;
} else
return NULL;
}
/**
* Stop any retransmissions we are doing of the specified node/packet ID pair
*/
bool ReliableRouter::stopRetransmission(NodeNum from, PacketId id)
{
auto key = GlobalPacketId(from, id);
return stopRetransmission(key);
}
bool ReliableRouter::stopRetransmission(GlobalPacketId key)
{
auto old = findPendingPacket(key);
if (old) {
auto p = old->packet;
/* Only when we already transmitted a packet via LoRa, we will cancel the packet in the Tx queue
to avoid canceling a transmission if it was ACKed super fast via MQTT */
if (old->numRetransmissions < NUM_RETRANSMISSIONS - 1) {
// remove the 'original' (identified by originator and packet->id) from the txqueue and free it
cancelSending(getFrom(p), p->id);
}
// now free the pooled copy for retransmission too
packetPool.release(p);
auto numErased = pending.erase(key);
assert(numErased == 1);
return true;
} else
return false;
}
/**
* Add p to the list of packets to retransmit occasionally. We will free it once we stop retransmitting.
*/
PendingPacket *ReliableRouter::startRetransmission(meshtastic_MeshPacket *p)
{
auto id = GlobalPacketId(p);
auto rec = PendingPacket(p);
stopRetransmission(getFrom(p), p->id);
setNextTx(&rec);
pending[id] = rec;
return &pending[id];
}
/**
* Do any retransmissions that are scheduled (FIXME - for the time being called from loop)
*/
int32_t ReliableRouter::doRetransmissions()
{
uint32_t now = millis();
int32_t d = INT32_MAX;
// FIXME, we should use a better datastructure rather than walking through this map.
// for(auto el: pending) {
for (auto it = pending.begin(), nextIt = it; it != pending.end(); it = nextIt) {
++nextIt; // we use this odd pattern because we might be deleting it...
auto &p = it->second;
bool stillValid = true; // assume we'll keep this record around
// FIXME, handle 51 day rollover here!!!
if (p.nextTxMsec <= now) {
if (p.numRetransmissions == 0) {
LOG_DEBUG("Reliable send failed, return a nak for fr=0x%x,to=0x%x,id=0x%x", p.packet->from, p.packet->to,
p.packet->id);
sendAckNak(meshtastic_Routing_Error_MAX_RETRANSMIT, getFrom(p.packet), p.packet->id, p.packet->channel);
// Note: we don't stop retransmission here, instead the Nak packet gets processed in sniffReceived
stopRetransmission(it->first);
stillValid = false; // just deleted it
} else {
LOG_DEBUG("Send reliable retransmission fr=0x%x,to=0x%x,id=0x%x, tries left=%d", p.packet->from, p.packet->to,
p.packet->id, p.numRetransmissions);
// Note: we call the superclass version because we don't want to have our version of send() add a new
// retransmission record
FloodingRouter::send(packetPool.allocCopy(*p.packet));
// Queue again
--p.numRetransmissions;
setNextTx(&p);
}
}
if (stillValid) {
// Update our desired sleep delay
int32_t t = p.nextTxMsec - now;
d = min(t, d);
}
}
return d;
}
void ReliableRouter::setNextTx(PendingPacket *pending)
{
assert(iface);
auto d = iface->getRetransmissionMsec(pending->packet);
pending->nextTxMsec = millis() + d;
LOG_DEBUG("Set next retransmission in %u msecs: ", d);
printPacket("", pending->packet);
setReceivedMessage(); // Run ASAP, so we can figure out our correct sleep time
isBroadcast(p->to) ? FloodingRouter::sniffReceived(p, c) : NextHopRouter::sniffReceived(p, c);
}

View File

@@ -1,61 +1,12 @@
#pragma once
#include "FloodingRouter.h"
#include <unordered_map>
/**
* An identifier for a globally unique message - a pair of the sending nodenum and the packet id assigned
* to that message
*/
struct GlobalPacketId {
NodeNum node;
PacketId id;
bool operator==(const GlobalPacketId &p) const { return node == p.node && id == p.id; }
explicit GlobalPacketId(const meshtastic_MeshPacket *p)
{
node = getFrom(p);
id = p->id;
}
GlobalPacketId(NodeNum _from, PacketId _id)
{
node = _from;
id = _id;
}
};
/**
* A packet queued for retransmission
*/
struct PendingPacket {
meshtastic_MeshPacket *packet;
/** The next time we should try to retransmit this packet */
uint32_t nextTxMsec = 0;
/** Starts at NUM_RETRANSMISSIONS -1(normally 3) and counts down. Once zero it will be removed from the list */
uint8_t numRetransmissions = 0;
PendingPacket() {}
explicit PendingPacket(meshtastic_MeshPacket *p);
};
class GlobalPacketIdHashFunction
{
public:
size_t operator()(const GlobalPacketId &p) const { return (std::hash<NodeNum>()(p.node)) ^ (std::hash<PacketId>()(p.id)); }
};
#include "NextHopRouter.h"
/**
* This is a mixin that extends Router with the ability to do (one hop only) reliable message sends.
*/
class ReliableRouter : public FloodingRouter
class ReliableRouter : public NextHopRouter
{
private:
std::unordered_map<GlobalPacketId, PendingPacket, GlobalPacketIdHashFunction> pending;
public:
/**
* Constructor
@@ -70,54 +21,14 @@ class ReliableRouter : public FloodingRouter
*/
virtual ErrorCode send(meshtastic_MeshPacket *p) override;
/** Do our retransmission handling */
virtual int32_t runOnce() override
{
// Note: We must doRetransmissions FIRST, because it might queue up work for the base class runOnce implementation
auto d = doRetransmissions();
int32_t r = FloodingRouter::runOnce();
return min(d, r);
}
protected:
/**
* Look for acks/naks or someone retransmitting us
*/
virtual void sniffReceived(const meshtastic_MeshPacket *p, const meshtastic_Routing *c) override;
/**
* Try to find the pending packet record for this ID (or NULL if not found)
*/
PendingPacket *findPendingPacket(NodeNum from, PacketId id) { return findPendingPacket(GlobalPacketId(from, id)); }
PendingPacket *findPendingPacket(GlobalPacketId p);
/**
* We hook this method so we can see packets before FloodingRouter says they should be discarded
*/
virtual bool shouldFilterReceived(const meshtastic_MeshPacket *p) override;
/**
* Add p to the list of packets to retransmit occasionally. We will free it once we stop retransmitting.
*/
PendingPacket *startRetransmission(meshtastic_MeshPacket *p);
private:
/**
* Stop any retransmissions we are doing of the specified node/packet ID pair
*
* @return true if we found and removed a transmission with this ID
*/
bool stopRetransmission(NodeNum from, PacketId id);
bool stopRetransmission(GlobalPacketId p);
/**
* Do any retransmissions that are scheduled (FIXME - for the time being called from loop)
*
* @return the number of msecs until our next retransmission or MAXINT if none scheduled
*/
int32_t doRetransmissions();
void setNextTx(PendingPacket *pending);
};
};

View File

@@ -249,6 +249,7 @@ ErrorCode Router::send(meshtastic_MeshPacket *p)
// the lora we need to make sure we have replaced it with our local address
p->from = getFrom(p);
p->relay_node = nodeDB->getLastByteOfNodeNum(getNodeNum()); // set the relayer to us
// If we are the original transmitter, set the hop limit with which we start
if (isFromUs(p))
p->hop_start = p->hop_limit;
@@ -290,7 +291,18 @@ ErrorCode Router::send(meshtastic_MeshPacket *p)
/** Attempt to cancel a previously sent packet. Returns true if a packet was found we could cancel */
bool Router::cancelSending(NodeNum from, PacketId id)
{
return iface ? iface->cancelSending(from, id) : false;
if (iface && iface->cancelSending(from, id)) {
// We are not a relayer of this packet anymore
removeRelayer(nodeDB->getLastByteOfNodeNum(nodeDB->getNodeNum()), id, from);
return true;
}
return false;
}
/** Attempt to find a packet in the TxQueue. Returns true if the packet was found. */
bool Router::findInTxQueue(NodeNum from, PacketId id)
{
return iface->findInTxQueue(from, id);
}
/**

View File

@@ -4,6 +4,7 @@
#include "MemoryPool.h"
#include "MeshTypes.h"
#include "Observer.h"
#include "PacketHistory.h"
#include "PointerQueue.h"
#include "RadioInterface.h"
#include "concurrency/OSThread.h"
@@ -11,7 +12,7 @@
/**
* A mesh aware router that supports multiple interfaces.
*/
class Router : protected concurrency::OSThread
class Router : protected concurrency::OSThread, protected PacketHistory
{
private:
/// Packets which have just arrived from the radio, ready to be processed by this service and possibly
@@ -50,6 +51,9 @@ class Router : protected concurrency::OSThread
/** Attempt to cancel a previously sent packet. Returns true if a packet was found we could cancel */
bool cancelSending(NodeNum from, PacketId id);
/** Attempt to find a packet in the TxQueue. Returns true if the packet was found. */
bool findInTxQueue(NodeNum from, PacketId id);
/** Allocate and return a meshpacket which defaults as send to broadcast from the current node.
* The returned packet is guaranteed to have a unique packet ID already assigned
*/

View File

@@ -18,6 +18,9 @@ bool STM32WLE5JCInterface::init()
{
RadioLibInterface::init();
// https://github.com/Seeed-Studio/LoRaWan-E5-Node/blob/main/Middlewares/Third_Party/SubGHz_Phy/stm32_radio_driver/radio_driver.c
setTCXOVoltage(1.7);
lora.setRfSwitchTable(rfswitch_pins, rfswitch_table);
if (power > STM32WLx_MAX_POWER) // This chip has lower power limits than some
@@ -39,4 +42,4 @@ bool STM32WLE5JCInterface::init()
return res == RADIOLIB_ERR_NONE;
}
#endif // ARCH_STM32WL
#endif // ARCH_STM32WL

View File

@@ -16,9 +16,6 @@ class STM32WLE5JCInterface : public SX126xInterface<STM32WLx>
virtual bool init() override;
};
// https://github.com/Seeed-Studio/LoRaWan-E5-Node/blob/main/Middlewares/Third_Party/SubGHz_Phy/stm32_radio_driver/radio_driver.c
static const float tcxoVoltage = 1.7;
/* https://wiki.seeedstudio.com/LoRa-E5_STM32WLE5JC_Module/
* Wio-E5 module ONLY transmits through RFO_HP
* Receive: PA4=1, PA5=0

View File

@@ -5,9 +5,6 @@
#include "configuration.h"
#include "main.h"
#include "mesh/api/ethServerAPI.h"
#if !MESHTASTIC_EXCLUDE_MQTT
#include "mqtt/MQTT.h"
#endif
#include "target_specific.h"
#include <RAK13800_W5100S.h>
#include <SPI.h>
@@ -72,12 +69,6 @@ static int32_t reconnectETH()
ethStartupComplete = true;
}
#if !MESHTASTIC_EXCLUDE_MQTT
// FIXME this is kinda yucky, instead we should just have an observable for 'wifireconnected'
if (mqtt && !moduleConfig.mqtt.proxy_to_client_enabled && !mqtt->isConnectedDirectly()) {
mqtt->reconnect();
}
#endif
}
#ifndef DISABLE_NTP

View File

@@ -1775,4 +1775,4 @@ extern const pb_msgdesc_t meshtastic_ChunkedPayloadResponse_msg;
} /* extern "C" */
#endif
#endif
#endif

View File

@@ -7,9 +7,6 @@
#include "main.h"
#include "mesh/api/WiFiServerAPI.h"
#if !MESHTASTIC_EXCLUDE_MQTT
#include "mqtt/MQTT.h"
#endif
#include "target_specific.h"
#include <WiFi.h>
#include <WiFiUdp.h>
@@ -111,12 +108,6 @@ static void onNetworkConnected()
#endif
APStartupComplete = true;
}
// FIXME this is kinda yucky, instead we should just have an observable for 'wifireconnected'
#ifndef MESHTASTIC_EXCLUDE_MQTT
if (mqtt)
mqtt->reconnect();
#endif
}
static int32_t reconnectWiFi()

View File

@@ -162,7 +162,9 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta
case meshtastic_AdminMessage_set_module_config_tag:
LOG_INFO("Client set module config");
handleSetModuleConfig(r->set_module_config);
if (!handleSetModuleConfig(r->set_module_config)) {
myReply = allocErrorResponse(meshtastic_Routing_Error_BAD_REQUEST, &mp);
}
break;
case meshtastic_AdminMessage_set_channel_tag:
@@ -648,15 +650,23 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c)
saveChanges(changes, requiresReboot);
}
void AdminModule::handleSetModuleConfig(const meshtastic_ModuleConfig &c)
bool AdminModule::handleSetModuleConfig(const meshtastic_ModuleConfig &c)
{
if (!hasOpenEditTransaction)
disableBluetooth();
switch (c.which_payload_variant) {
case meshtastic_ModuleConfig_mqtt_tag:
#if MESHTASTIC_EXCLUDE_MQTT
LOG_WARN("Set module config: MESHTASTIC_EXCLUDE_MQTT is defined. Not setting MQTT config");
return false;
#else
LOG_INFO("Set module config: MQTT");
if (!MQTT::isValidConfig(c.payload_variant.mqtt)) {
return false;
}
moduleConfig.has_mqtt = true;
moduleConfig.mqtt = c.payload_variant.mqtt;
#endif
break;
case meshtastic_ModuleConfig_serial_tag:
LOG_INFO("Set module config: Serial");
@@ -724,6 +734,7 @@ void AdminModule::handleSetModuleConfig(const meshtastic_ModuleConfig &c)
break;
}
saveChanges(SEGMENT_MODULECONFIG);
return true;
}
void AdminModule::handleSetChannel(const meshtastic_Channel &cc)
@@ -1160,4 +1171,4 @@ void disableBluetooth()
nrf52Bluetooth->shutdown();
#endif
#endif
}
}

View File

@@ -50,7 +50,7 @@ class AdminModule : public ProtobufModule<meshtastic_AdminMessage>, public Obser
void handleSetOwner(const meshtastic_User &o);
void handleSetChannel(const meshtastic_Channel &cc);
void handleSetConfig(const meshtastic_Config &c);
void handleSetModuleConfig(const meshtastic_ModuleConfig &c);
bool handleSetModuleConfig(const meshtastic_ModuleConfig &c);
void handleSetChannel();
void handleSetHamMode(const meshtastic_HamParameters &req);
void handleStoreDeviceUIConfig(const meshtastic_DeviceUIConfig &uicfg);

View File

@@ -121,7 +121,8 @@ Will be used for broadcast.
*/
int32_t NeighborInfoModule::runOnce()
{
if (moduleConfig.neighbor_info.transmit_over_lora && !channels.isDefaultChannel(channels.getPrimaryIndex()) &&
if (moduleConfig.neighbor_info.transmit_over_lora &&
(!channels.isDefaultChannel(channels.getPrimaryIndex()) || !RadioInterface::uses_default_frequency_slot) &&
airTime->isTxAllowedChannelUtil(true) && airTime->isTxAllowedAirUtil()) {
sendNeighborInfo(NODENUM_BROADCAST, false);
} else {

View File

@@ -60,7 +60,7 @@
SerialModule *serialModule;
SerialModuleRadio *serialModuleRadio;
#if defined(TTGO_T_ECHO) || defined(CANARYONE)
#if defined(TTGO_T_ECHO) || defined(CANARYONE) || defined(MESHLINK)
SerialModule::SerialModule() : StreamAPI(&Serial), concurrency::OSThread("Serial") {}
static Print *serialPrint = &Serial;
#elif defined(CONFIG_IDF_TARGET_ESP32C6)
@@ -158,7 +158,7 @@ int32_t SerialModule::runOnce()
Serial.begin(baud);
Serial.setTimeout(moduleConfig.serial.timeout > 0 ? moduleConfig.serial.timeout : TIMEOUT);
}
#elif !defined(TTGO_T_ECHO) && !defined(CANARYONE)
#elif !defined(TTGO_T_ECHO) && !defined(CANARYONE) && !defined(MESHLINK)
if (moduleConfig.serial.rxd && moduleConfig.serial.txd) {
#ifdef ARCH_RP2040
Serial2.setFIFOSize(RX_BUFFER);
@@ -214,7 +214,7 @@ int32_t SerialModule::runOnce()
}
}
#if !defined(TTGO_T_ECHO) && !defined(CANARYONE)
#if !defined(TTGO_T_ECHO) && !defined(CANARYONE) && !defined(MESHLINK)
else if ((moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_WS85)) {
processWXSerial();
@@ -416,7 +416,7 @@ uint32_t SerialModule::getBaudRate()
*/
void SerialModule::processWXSerial()
{
#if !defined(TTGO_T_ECHO) && !defined(CANARYONE) && !defined(CONFIG_IDF_TARGET_ESP32C6)
#if !defined(TTGO_T_ECHO) && !defined(CANARYONE) && !defined(CONFIG_IDF_TARGET_ESP32C6) && !defined(MESHLINK)
static unsigned int lastAveraged = 0;
static unsigned int averageIntervalMillis = 300000; // 5 minutes hard coded.
static double dir_sum_sin = 0;

View File

@@ -109,7 +109,7 @@ void TraceRouteModule::appendMyIDandSNR(meshtastic_RouteDiscovery *updated, floa
void TraceRouteModule::printRoute(meshtastic_RouteDiscovery *r, uint32_t origin, uint32_t dest, bool isTowardsDestination)
{
#ifdef DEBUG_PORT
std::string route = "Route traced:";
std::string route = "Route traced:\n";
route += vformat("0x%x --> ", origin);
for (uint8_t i = 0; i < r->route_count; i++) {
if (i < r->snr_towards_count && r->snr_towards[i] != INT8_MIN)
@@ -129,6 +129,7 @@ void TraceRouteModule::printRoute(meshtastic_RouteDiscovery *r, uint32_t origin,
// If there's a route back (or we are the destination as then the route is complete), print it
if (r->route_back_count > 0 || origin == nodeDB->getNodeNum()) {
route += "\n";
if (r->snr_towards_count > 0 && origin == nodeDB->getNodeNum())
route += vformat("(%.2fdB) 0x%x <-- ", (float)r->snr_back[r->snr_back_count - 1] / 4, origin);
else

View File

@@ -41,6 +41,7 @@ MQTT *mqtt;
namespace
{
constexpr int reconnectMax = 5;
constexpr uint16_t mqttPort = 1883;
// FIXME - this size calculation is super sloppy, but it will go away once we dynamically alloc meshpackets
static uint8_t bytes[meshtastic_MqttClientProxyMessage_size + 30]; // 12 for channel name and 16 for nodeid
@@ -245,6 +246,11 @@ std::pair<String, uint16_t> parseHostAndPort(String server, uint16_t port = 0)
}
return std::make_pair(std::move(server), port);
}
bool isDefaultServer(const String &host)
{
return host.length() == 0 || host == default_mqtt_address;
}
} // namespace
void MQTT::mqttCallback(char *topic, byte *payload, unsigned int length)
@@ -324,7 +330,7 @@ MQTT::MQTT() : concurrency::OSThread("mqtt"), mqttQueue(MAX_MQTT_QUEUE)
}
String host = parseHostAndPort(moduleConfig.mqtt.address).first;
isConfiguredForDefaultServer = host.length() == 0 || host == default_mqtt_address;
isConfiguredForDefaultServer = isDefaultServer(host);
IPAddress ip;
isMqttServerAddressPrivate = ip.fromString(host.c_str()) && isPrivateIpAddress(ip);
@@ -408,40 +414,32 @@ void MQTT::reconnect()
}
#if HAS_NETWORKING
// Defaults
int serverPort = 1883;
int serverPort = mqttPort;
const char *serverAddr = default_mqtt_address;
const char *mqttUsername = default_mqtt_username;
const char *mqttPassword = default_mqtt_password;
MQTTClient *clientConnection = mqttClient.get();
if (*moduleConfig.mqtt.address) {
serverAddr = moduleConfig.mqtt.address;
mqttUsername = moduleConfig.mqtt.username;
mqttPassword = moduleConfig.mqtt.password;
}
#if HAS_WIFI && !defined(ARCH_PORTDUINO)
#if !defined(CONFIG_IDF_TARGET_ESP32C6)
#if HAS_WIFI && !defined(ARCH_PORTDUINO) && !defined(CONFIG_IDF_TARGET_ESP32C6)
if (moduleConfig.mqtt.tls_enabled) {
// change default for encrypted to 8883
try {
serverPort = 8883;
wifiSecureClient.setInsecure();
pubSub.setClient(wifiSecureClient);
LOG_INFO("Use TLS-encrypted session");
clientConnection = &wifiSecureClient;
} catch (const std::exception &e) {
LOG_ERROR("MQTT ERROR: %s", e.what());
}
} else {
LOG_INFO("Use non-TLS-encrypted session");
pubSub.setClient(*mqttClient);
}
#else
pubSub.setClient(*mqttClient);
#endif
#elif HAS_NETWORKING
pubSub.setClient(*mqttClient);
#endif
std::pair<String, uint16_t> hostAndPort = parseHostAndPort(serverAddr, serverPort);
serverAddr = hostAndPort.first.c_str();
serverPort = hostAndPort.second;
@@ -451,13 +449,14 @@ void MQTT::reconnect()
LOG_INFO("Connect directly to MQTT server %s, port: %d, username: %s, password: %s", serverAddr, serverPort, mqttUsername,
mqttPassword);
pubSub.setClient(*clientConnection);
bool connected = pubSub.connect(owner.id, mqttUsername, mqttPassword);
if (connected) {
LOG_INFO("MQTT connected");
enabled = true; // Start running background process again
runASAP = true;
reconnectCount = 0;
isMqttServerAddressPrivate = isPrivateIpAddress(mqttClient->remoteIP());
isMqttServerAddressPrivate = isPrivateIpAddress(clientConnection->remoteIP());
publishNodeInfo();
sendSubscriptions();
@@ -568,6 +567,23 @@ int32_t MQTT::runOnce()
return 30000;
}
bool MQTT::isValidConfig(const meshtastic_ModuleConfig_MQTTConfig &config)
{
String host;
uint16_t port;
std::tie(host, port) = parseHostAndPort(config.address, mqttPort);
const bool defaultServer = isDefaultServer(host);
if (defaultServer && config.tls_enabled) {
LOG_ERROR("Invalid MQTT config: TLS was enabled, but the default server does not support TLS");
return false;
}
if (defaultServer && port != mqttPort) {
LOG_ERROR("Invalid MQTT config: Unsupported port '%d' for the default MQTT server", port);
return false;
}
return true;
}
void MQTT::publishNodeInfo()
{
// TODO: NodeInfo broadcast over MQTT only (NODENUM_BROADCAST_NO_LORA)

View File

@@ -47,10 +47,6 @@ class MQTT : private concurrency::OSThread
*/
void onSend(const meshtastic_MeshPacket &mp_encrypted, const meshtastic_MeshPacket &mp_decoded, ChannelIndex chIndex);
/** Attempt to connect to server if necessary
*/
void reconnect();
bool isConnectedDirectly();
bool publish(const char *topic, const char *payload, bool retained);
@@ -65,6 +61,8 @@ class MQTT : private concurrency::OSThread
bool isUsingDefaultServer() { return isConfiguredForDefaultServer; }
static bool isValidConfig(const meshtastic_ModuleConfig_MQTTConfig &config);
protected:
struct QueueEntry {
std::string topic;
@@ -115,6 +113,10 @@ class MQTT : private concurrency::OSThread
*/
bool wantsLink() const;
/** Attempt to connect to server if necessary
*/
void reconnect();
/** Tell the server what subscriptions we want (based on channels.downlink_enabled)
*/
void sendSubscriptions();

View File

@@ -127,4 +127,4 @@
#if !defined(PIN_SERIAL_RX) && !defined(NRF52840_XXAA)
// No serial ports on this board - ONLY use segger in memory console
#define USE_SEGGER
#endif
#endif

View File

@@ -304,6 +304,11 @@ void cpuDeepSleep(uint32_t msecToWake)
nrf_gpio_cfg_default(WB_I2C1_SDA);
#endif
#endif
#ifdef MESHLINK
#ifdef PIN_WD_EN
digitalWrite(PIN_WD_EN, LOW);
#endif
#endif
#ifdef HELTEC_MESH_NODE_T114
nrf_gpio_cfg_default(PIN_GPS_PPS);

View File

@@ -139,6 +139,12 @@ bool SimRadio::cancelSending(NodeNum from, PacketId id)
return result;
}
/** Attempt to find a packet in the TxQueue. Returns true if the packet was found. */
bool SimRadio::findInTxQueue(NodeNum from, PacketId id)
{
return txQueue.find(from, id);
}
void SimRadio::onNotify(uint32_t notification)
{
switch (notification) {

View File

@@ -33,6 +33,9 @@ class SimRadio : public RadioInterface, protected concurrency::NotifiedWorkerThr
/** Attempt to cancel a previously sent packet. Returns true if a packet was found we could cancel */
virtual bool cancelSending(NodeNum from, PacketId id) override;
/** Attempt to find a packet in the TxQueue. Returns true if the packet was found. */
virtual bool findInTxQueue(NodeNum from, PacketId id) override;
/**
* Start waiting to receive a message
*

View File

@@ -245,6 +245,9 @@ void doDeepSleep(uint32_t msecToWake, bool skipPreflight = false, bool skipSaveN
#ifdef PIN_3V3_EN
digitalWrite(PIN_3V3_EN, LOW);
#endif
#ifdef PIN_WD_EN
digitalWrite(PIN_WD_EN, LOW);
#endif
#endif
ledBlink.set(false);
@@ -530,4 +533,4 @@ void enableLoraInterrupt()
}
#endif
}
#endif
#endif

View File

@@ -242,6 +242,7 @@ class MQTTUnitTest : public MQTT
mqttClient.release();
delete pubsub;
}
using MQTT::reconnect;
int queueSize() { return mqttQueue.numUsed(); }
void reportToMap(std::optional<uint32_t> precision = std::nullopt)
{
@@ -488,7 +489,7 @@ void test_reconnectProxyDoesNotReconnectMqtt(void)
moduleConfig.mqtt.proxy_to_client_enabled = true;
MQTTUnitTest::restart();
mqtt->reconnect();
unitTest->reconnect();
TEST_ASSERT_FALSE(pubsub->connected_);
}
@@ -799,6 +800,38 @@ void test_customMqttRoot(void)
[] { return pubsub->subscriptions_.count("custom/2/e/test/+") && pubsub->subscriptions_.count("custom/2/e/PKI/+"); }));
}
// Empty configuration is valid.
void test_configurationEmptyIsValid(void)
{
meshtastic_ModuleConfig_MQTTConfig config;
TEST_ASSERT_TRUE(MQTT::isValidConfig(config));
}
// Configuration with the default server is valid.
void test_configWithDefaultServer(void)
{
meshtastic_ModuleConfig_MQTTConfig config = {.address = default_mqtt_address};
TEST_ASSERT_TRUE(MQTT::isValidConfig(config));
}
// Configuration with the default server and port 8888 is invalid.
void test_configWithDefaultServerAndInvalidPort(void)
{
meshtastic_ModuleConfig_MQTTConfig config = {.address = default_mqtt_address ":8888"};
TEST_ASSERT_FALSE(MQTT::isValidConfig(config));
}
// Configuration with the default server and tls_enabled = true is invalid.
void test_configWithDefaultServerAndInvalidTLSEnabled(void)
{
meshtastic_ModuleConfig_MQTTConfig config = {.tls_enabled = true};
TEST_ASSERT_FALSE(MQTT::isValidConfig(config));
}
void setup()
{
initializeTestEnvironment();
@@ -842,6 +875,10 @@ void setup()
RUN_TEST(test_enabled);
RUN_TEST(test_disabled);
RUN_TEST(test_customMqttRoot);
RUN_TEST(test_configurationEmptyIsValid);
RUN_TEST(test_configWithDefaultServer);
RUN_TEST(test_configWithDefaultServerAndInvalidPort);
RUN_TEST(test_configWithDefaultServerAndInvalidTLSEnabled);
exit(UNITY_END());
}
#else

View File

@@ -70,6 +70,20 @@ lib_deps =
${nrf52840_base.lib_deps}
debug_tool = jlink
; Seeed XIAO nRF52840 + XIAO Wio SX1262 DIY
[env:seeed-xiao-nrf52840-wio-sx1262]
board = xiao_ble_sense
extends = nrf52840_base
board_level = extra
build_flags = ${nrf52840_base.build_flags} -Ivariants/diy/seeed-xiao-nrf52840-wio-sx1262 -D PRIVATE_HW
-Isrc/platform/nrf52/softdevice -Isrc/platform/nrf52/softdevice/nrf52
-L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m4/fpv4-sp-d16-hard"
board_build.ldscript = src/platform/nrf52/nrf52840_s140_v7.ld
build_src_filter = ${nrf52_base.build_src_filter} +<../variants/diy/seeed-xiao-nrf52840-wio-sx1262>
lib_deps =
${nrf52840_base.lib_deps}
debug_tool = jlink
; NanoVHF T-Energy-S3 + E22(0)-xxxM - DIY
[env:t-energy-s3_e22]
extends = esp32s3_base
@@ -86,4 +100,4 @@ build_flags =
-D BOARD_HAS_PSRAM
-D ARDUINO_USB_MODE=0
-D ARDUINO_USB_CDC_ON_BOOT=1
-I variants/diy/t-energy-s3_e22
-I variants/diy/t-energy-s3_e22

View File

@@ -0,0 +1,43 @@
# XIAO nRF52840 + XIAO Wio SX1262
For a mere doubling in price you too can swap out the XIAO ESP32C3 for a XIAO nRF52840, stack the Wio SX1262 radio board either above or underneath the nRF52840, solder the pins, and achieve a massive improvement in battery life!
I'm not really sure why else you would want to as the ESP32C3 is perfectly cromulent, easily connects to the Wio SX1262 via the B2B connector and has an onboard IPEX connector for the included Bluetooth antenna. So you'll also lose BT range, but you will also have working ADC for the battery in Meshtastic and also have an ESP32C3 to use for something else!
If you're still reading you are clearly gonna do it anyway, so...mount the Wio SX1262 either on top or underneath depending on your preference. The `variant.h` will work with either configuration though it does map the Wio SX1262's button to nRF52840 Pin `D5` as it can still be used as a user button and it's nice to be able to gracefully shutdown a node by holding it down for 5 seconds.
If you do decide to wire up the button, orient it so looking straight-down at the Wio SX1262 the radio chip is at the bottom, button in the middle and the hole is at the top - the **left** side of the button should be soldered to `GND` (e.g. the 2nd pin down the top on the **right** row of pins) and the **right** side of the button should be soldered to `D5` (e.g. the 2nd pin up from the button on the **left** row of pins.). This mirrors the original wiring and wiring it in reverse could end up connecting GND to voltage and that's no beuno.
Serial Pins remain available on `D6` (TX) and `D7` (RX) should you want to use them, The same pins could be repurposed for `i2c` if you would like to have that instead of serial, in `variant.h` you would just need to change:
```c++
// RX and TX pins
#define PIN_SERIAL1_RX (6)
#define PIN_SERIAL1_TX (7)
```
to
```c++
// RX and TX pins
#define PIN_SERIAL1_RX (-1)
#define PIN_SERIAL1_TX (-1)
```
and
```c++
#define PIN_WIRE_SDA (-1)
#define PIN_WIRE_SCL (-1)
// #define PIN_WIRE_SDA (6)
// #define PIN_WIRE_SCL (7)
```
to
```c++
#define PIN_WIRE_SDA (6)
#define PIN_WIRE_SCL (7)
```
If you wanted both serial and i2c you could even go so far as to use the pads for the PDM mic which is missing on the non-sense board (`P1.00` / `P0.16`)... or move up to the nRF52840 Plus which has even more pins available but hasn't been checked/confirmed if it follows the same pin mapping as the non-plus.

View File

@@ -0,0 +1,55 @@
#include "variant.h"
#include "nrf.h"
#include "wiring_constants.h"
#include "wiring_digital.h"
const uint32_t g_ADigitalPinMap[] = {
// D0 .. D13
2, // D0 is P0.02 (A0)
3, // D1 is P0.03 (A1)
28, // D2 is P0.28 (A2)
29, // D3 is P0.29 (A3)
4, // D4 is P0.04 (A4,SDA)
5, // D5 is P0.05 (A5,SCL)
43, // D6 is P1.11 (TX)
44, // D7 is P1.12 (RX)
45, // D8 is P1.13 (SCK)
46, // D9 is P1.14 (MISO)
47, // D10 is P1.15 (MOSI)
// LEDs
26, // D11 is P0.26 (LED RED)
6, // D12 is P0.06 (LED BLUE)
30, // D13 is P0.30 (LED GREEN)
14, // D14 is P0.14 (READ_BAT)
// LSM6DS3TR
40, // D15 is P1.08 (6D_PWR)
27, // D16 is P0.27 (6D_I2C_SCL)
7, // D17 is P0.07 (6D_I2C_SDA)
11, // D18 is P0.11 (6D_INT1)
// MIC
42, // 17,//42, // D19 is P1.10 (MIC_PWR)
32, // 26,//32, // D20 is P1.00 (PDM_CLK)
16, // 25,//16, // D21 is P0.16 (PDM_DATA)
// BQ25100
13, // D22 is P0.13 (HICHG)
17, // D23 is P0.17 (~CHG)
//
21, // D24 is P0.21 (QSPI_SCK)
25, // D25 is P0.25 (QSPI_CSN)
20, // D26 is P0.20 (QSPI_SIO_0 DI)
24, // D27 is P0.24 (QSPI_SIO_1 DO)
22, // D28 is P0.22 (QSPI_SIO_2 WP)
23, // D29 is P0.23 (QSPI_SIO_3 HOLD)
// NFC
9, // D30 is P0.09 (NFC1)
10, // D31 is P0.10 (NFC2)
// VBAT
31, // D32 is P0.10 (VBAT)
};

View File

@@ -0,0 +1,186 @@
// basically xiao_ble with pins remapped for:
// Seeed XIAO nRF52840 : https://www.seeedstudio.com/Seeed-XIAO-BLE-nRF52840-p-5201.html
// Seeed Wio SX1626 : https://www.seeedstudio.com/Wio-SX1262-with-XIAO-ESP32S3-p-5982.html
#ifndef _SEEED_XIAO_NRF52840_SENSE_H_
#define _SEEED_XIAO_NRF52840_SENSE_H_
/** Master clock frequency */
#define VARIANT_MCK (64000000ul)
#define USE_LFXO // Board uses 32khz crystal for LF
/*----------------------------------------------------------------------------
* Headers
*----------------------------------------------------------------------------*/
#include "WVariant.h"
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
#define PINS_COUNT (33)
#define NUM_DIGITAL_PINS (33)
#define NUM_ANALOG_INPUTS (8) // A6 is used for battery, A7 is analog reference
#define NUM_ANALOG_OUTPUTS (0)
// LEDs
// ----
#define LED_RED 11
#define LED_BLUE 12
#define LED_GREEN 13
#define PIN_LED1 LED_GREEN
#define PIN_LED2 LED_BLUE
#define PIN_LED3 LED_RED
#define PIN_LED PIN_LED1
#define LED_PWR (PINS_COUNT)
#define LED_BUILTIN PIN_LED
#define LED_STATE_ON 1 // State when LED is lit
// XIAO Wio-SX1262 Shield User button
#define PIN_BUTTON1 5
#define BUTTON_NEED_PULLUP
// Digital Pins
// ------------
#define D0 (0ul)
#define D1 (1ul)
#define D2 (2ul)
#define D3 (3ul)
#define D4 (4ul)
#define D5 (5ul)
#define D6 (6ul)
#define D7 (7ul)
#define D8 (8ul)
#define D9 (9ul)
#define D10 (10ul)
// Analog Pins
// -----------
#define PIN_A0 (0)
#define PIN_A1 (1)
#define PIN_A2 (2)
#define PIN_A3 (3)
#define PIN_A4 (4)
#define PIN_A5 (5)
#define PIN_VBAT (32)
#define VBAT_ENABLE (14)
static const uint8_t A0 = PIN_A0;
static const uint8_t A1 = PIN_A1;
static const uint8_t A2 = PIN_A2;
static const uint8_t A3 = PIN_A3;
static const uint8_t A4 = PIN_A4;
static const uint8_t A5 = PIN_A5;
#define ADC_RESOLUTION 12
// Other Pins
// ----------
#define PIN_NFC1 (30)
#define PIN_NFC2 (31)
// RX and TX pins
#define PIN_SERIAL1_RX (-1)
#define PIN_SERIAL1_TX (-1)
// complains if not defined
#define PIN_SERIAL2_RX (-1)
#define PIN_SERIAL2_TX (-1)
// 4 is used as RF_SW and 5 for USR button so...
#define PIN_WIRE_SDA (6)
#define PIN_WIRE_SCL (7)
static const uint8_t SDA = PIN_WIRE_SDA;
static const uint8_t SCL = PIN_WIRE_SCL;
// SPI SX1262
// ----------
#define SPI_SX1262
#ifdef SPI_SX1262
#define SPI_INTERFACES_COUNT 1
#define PIN_SPI_MISO (9)
#define PIN_SPI_MOSI (10)
#define PIN_SPI_SCK (8)
static const uint8_t SS = D3;
static const uint8_t MOSI = PIN_SPI_MOSI;
static const uint8_t MISO = PIN_SPI_MISO;
static const uint8_t SCK = PIN_SPI_SCK;
// supported modules list
#define USE_SX1262
// common pinouts for SX126X modules
#define SX126X_CS D3
#define SX126X_DIO1 D0
#define SX126X_BUSY D1
#define SX126X_RESET D2
// DIO2 controlls an antenna switch and the TCXO voltage is controlled by DIO3
#define SX126X_DIO2_AS_RF_SWITCH
#define SX126X_RXEN 38
#define SX126X_TXEN RADIOLIB_NC
#define SX126X_DIO3_TCXO_VOLTAGE 1.8
#define SX126X_DIO3_TCXO_VOLTAGE 1.8
#endif
// Wire Interfaces
// -------------------
#define WIRE_INTERFACES_COUNT 1 // 2
// Sense version has IMU and PDM Mic
// #define XIAO_SENSE
#ifndef XIAO_SENSE
// 6 DoF IMU
#define PIN_LSM6DS3TR_C_POWER (15)
#define PIN_LSM6DS3TR_C_INT1 (18)
// PDM Interfaces
// ---------------
#define PIN_PDM_PWR (19)
#define PIN_PDM_CLK (20)
#define PIN_PDM_DIN (21)
#endif
// QSPI Pins
// ---------
#define PIN_QSPI_SCK (24)
#define PIN_QSPI_CS (25)
#define PIN_QSPI_IO0 (26)
#define PIN_QSPI_IO1 (27)
#define PIN_QSPI_IO2 (28)
#define PIN_QSPI_IO3 (29)
// On-board QSPI Flash
// -------------------
#define EXTERNAL_FLASH_DEVICES P25Q16H
#define EXTERNAL_FLASH_USE_QSPI
// Battery
// -------
// P0_14 = 14 Reads battery voltage from divider on signal board.
// PIN_VBAT is reading voltage divider on XIAO and is program pin 32 / or P0.31
#define BAT_READ 14
#define BATTERY_SENSE_RESOLUTION_BITS 10
#define CHARGE_LED 23 // P0_17 = 17 D23 YELLOW CHARGE LED
#define HICHG 22 // P0_13 = 13 D22 Charge-select pin for Lipo for 100 mA instead of default 50mA charge
// The battery sense is hooked to pin A0 (5)
#define BATTERY_PIN PIN_VBAT // PIN_A0
// ratio of voltage divider = 3.0 (R17=1M, R18=510k)
#define ADC_MULTIPLIER 3 // 3.0 + a bit for being optimistic
#ifdef __cplusplus
}
#endif
/*----------------------------------------------------------------------------
* Arduino objects - C++ only
*----------------------------------------------------------------------------*/
#endif

View File

@@ -0,0 +1,30 @@
; MeshLink board developed by LoraItalia. NRF52840, eByte E22900M22S (Will also come with other frequencies), 25w MPPT solar charger (5v,12v,18v selectable), support for gps, buzzer, oled or e-ink display, 10 gpios, hardware watchdog
; https://www.loraitalia.it
; firmware for boards with or without oled display
[env:meshlink]
extends = nrf52840_base
board = meshlink
;board_check = true
build_flags = ${nrf52840_base.build_flags} -I variants/meshlink -D MESHLINK
-L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m4/fpv4-sp-d16-hard"
-D GPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely.
-D EINK_DISPLAY_MODEL=GxEPD2_213_B74
-D EINK_WIDTH=250
-D EINK_HEIGHT=122
-D USE_EINK_DYNAMICDISPLAY ; Enable Dynamic EInk
-D EINK_LIMIT_FASTREFRESH=5 ; How many consecutive fast-refreshes are permitted
-D EINK_LIMIT_RATE_BACKGROUND_SEC=30 ; Minimum interval between BACKGROUND updates
-D EINK_LIMIT_RATE_RESPONSIVE_SEC=1 ; Minimum interval between RESPONSIVE updates
-D EINK_LIMIT_GHOSTING_PX=2000 ; (Optional) How much image ghosting is tolerated
-D EINK_BACKGROUND_USES_FAST ; (Optional) Use FAST refresh for both BACKGROUND and RESPONSIVE, until a limit is reached.
-D EINK_HASQUIRK_VICIOUSFASTREFRESH ; Identify that pixels drawn by fast-refresh are harder to clear
build_src_filter = ${nrf52_base.build_src_filter} +<../variants/meshlink>
lib_deps =
${nrf52840_base.lib_deps}
https://github.com/meshtastic/GxEPD2#55f618961db45a23eff0233546430f1e5a80f63a
debug_tool = jlink
; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm)
; Note: as of 6/2013 the serial/bootloader based programming takes approximately 30 seconds
;upload_protocol = jlink

View File

@@ -0,0 +1,23 @@
#include "variant.h"
#include "nrf.h"
#include "wiring_constants.h"
#include "wiring_digital.h"
const uint32_t g_ADigitalPinMap[] = {
// P0
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
// P1
32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47};
void initVariant()
{
pinMode(PIN_LED1, OUTPUT);
digitalWrite(PIN_LED1, HIGH); // turn off the white led while booting
// otherwise it will stay lit for several seconds (could be annoying)
#ifdef PIN_WD_EN
pinMode(PIN_WD_EN, OUTPUT);
digitalWrite(PIN_WD_EN, HIGH); // Enable the Watchdog at boot
#endif
}

153
variants/meshlink/variant.h Normal file
View File

@@ -0,0 +1,153 @@
#ifndef _VARIANT_MESHLINK_
#define _VARIANT_MESHLINK_
#ifndef MESHLINK
#define MESHLINK
#endif
/** Master clock frequency */
#define VARIANT_MCK (64000000ul)
// #define USE_LFXO // Board uses 32khz crystal for LF
#define USE_LFRC // Board uses RC for LF
/*----------------------------------------------------------------------------
* Headers
*----------------------------------------------------------------------------*/
#include "WVariant.h"
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
// Number of pins defined in PinDescription array
#define PINS_COUNT (48)
#define NUM_DIGITAL_PINS (48)
#define NUM_ANALOG_INPUTS (2)
#define NUM_ANALOG_OUTPUTS (0)
#define BUTTON_PIN (-1) // If defined, this will be used for user button presses,
#define BUTTON_NEED_PULLUP
// LEDs
#define PIN_LED1 (24) // Built in white led for status
#define LED_BLUE PIN_LED1
#define LED_BUILTIN PIN_LED1
#define LED_STATE_ON 0 // State when LED is litted
#define LED_INVERTED 1
// Testing USB detection
// #define NRF_APM
/*
* Analog pins
*/
#define PIN_A1 (3) // P0.03/AIN1
#define ADC_RESOLUTION 14
// Other pins
// #define PIN_AREF (2)
// static const uint8_t AREF = PIN_AREF;
/*
* Serial interfaces
*/
#define PIN_SERIAL1_RX (32 + 8)
#define PIN_SERIAL1_TX (7)
/*
* SPI Interfaces
*/
#define SPI_INTERFACES_COUNT 2
#define PIN_SPI_MISO (8)
#define PIN_SPI_MOSI (32 + 9)
#define PIN_SPI_SCK (11)
#define PIN_SPI1_MISO (23)
#define PIN_SPI1_MOSI (21)
#define PIN_SPI1_SCK (19)
static const uint8_t SS = 12;
static const uint8_t MOSI = PIN_SPI_MOSI;
static const uint8_t MISO = PIN_SPI_MISO;
static const uint8_t SCK = PIN_SPI_SCK;
/*
* eink display pins
*/
// #define USE_EINK
#define PIN_EINK_CS (15)
#define PIN_EINK_BUSY (16)
#define PIN_EINK_DC (14)
#define PIN_EINK_RES (17)
#define PIN_EINK_SCLK (19)
#define PIN_EINK_MOSI (21) // also called SDI
/*
* Wire Interfaces
*/
#define WIRE_INTERFACES_COUNT 1
#define PIN_WIRE_SDA (1)
#define PIN_WIRE_SCL (27)
// QSPI Pins
#define PIN_QSPI_SCK 19
#define PIN_QSPI_CS 22
#define PIN_QSPI_IO0 21
#define PIN_QSPI_IO1 23
#define PIN_QSPI_IO2 32
#define PIN_QSPI_IO3 20
// On-board QSPI Flash
#define EXTERNAL_FLASH_DEVICES W25Q16JVUXIQ
#define EXTERNAL_FLASH_USE_QSPI
#define USE_SX1262
#define SX126X_CS (12)
#define SX126X_DIO1 (32 + 1)
#define SX126X_BUSY (32 + 3)
#define SX126X_RESET (6)
// #define SX126X_RXEN (13)
// DIO2 controlls an antenna switch and the TCXO voltage is controlled by DIO3
#define SX126X_DIO2_AS_RF_SWITCH
#define SX126X_DIO3_TCXO_VOLTAGE 1.8
// pin 25 is used to enable or disable the watchdog. This pin has to be disabled when cpu is put to sleep
// otherwise the timer will expire and wd will reboot the cpu
#define PIN_WD_EN (25)
#define PIN_GPS_PPS (26) // Pulse per second input from the GPS
#define GPS_TX_PIN PIN_SERIAL1_RX // This is for bits going TOWARDS the CPU
#define GPS_RX_PIN PIN_SERIAL1_TX // This is for bits going TOWARDS the GPS
// #define GPS_THREAD_INTERVAL 50
// Define pin to enable GPS toggle (set GPIO to LOW) via user button triple press
#define PIN_GPS_EN (0)
#define GPS_EN_ACTIVE LOW
#define PIN_BUZZER (31) // P0.31/AIN7
// Battery
// The battery sense is hooked to pin A0 (2)
#define BATTERY_PIN (2)
// and has 12 bit resolution
#define BATTERY_SENSE_RESOLUTION_BITS 12
#define BATTERY_SENSE_RESOLUTION 4096.0
#undef AREF_VOLTAGE
#define AREF_VOLTAGE 3.0
#define VBAT_AR_INTERNAL AR_INTERNAL_3_0
#define ADC_MULTIPLIER 1.42 // fine tuning of voltage
#ifdef __cplusplus
}
#endif
/*----------------------------------------------------------------------------
* Arduino objects - C++ only
*----------------------------------------------------------------------------*/
#endif

View File

@@ -0,0 +1,30 @@
; MeshLink board developed by LoraItalia. NRF52840, eByte E22900M22S (Will also come with other frequencies), 25w MPPT solar charger (5v,12v,18v selectable), support for gps, buzzer, oled or e-ink display, 10 gpios, hardware watchdog
; https://www.loraitalia.it
; firmware for boards with a 250x122 e-ink display
[env:meshlink_eink]
extends = nrf52840_base
board = meshlink
;board_check = true
build_flags = ${nrf52840_base.build_flags} -I variants/meshlink_eink -D MESHLINK
-L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m4/fpv4-sp-d16-hard"
-D GPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely.
-D EINK_DISPLAY_MODEL=GxEPD2_213_B74
-D EINK_WIDTH=250
-D EINK_HEIGHT=122
-D USE_EINK_DYNAMICDISPLAY ; Enable Dynamic EInk
-D EINK_LIMIT_FASTREFRESH=5 ; How many consecutive fast-refreshes are permitted
-D EINK_LIMIT_RATE_BACKGROUND_SEC=30 ; Minimum interval between BACKGROUND updates
-D EINK_LIMIT_RATE_RESPONSIVE_SEC=1 ; Minimum interval between RESPONSIVE updates
-D EINK_LIMIT_GHOSTING_PX=2000 ; (Optional) How much image ghosting is tolerated
-D EINK_BACKGROUND_USES_FAST ; (Optional) Use FAST refresh for both BACKGROUND and RESPONSIVE, until a limit is reached.
-D EINK_HASQUIRK_VICIOUSFASTREFRESH ; Identify that pixels drawn by fast-refresh are harder to clear
build_src_filter = ${nrf52_base.build_src_filter} +<../variants/meshlink_eink>
lib_deps =
${nrf52840_base.lib_deps}
https://github.com/meshtastic/GxEPD2#55f618961db45a23eff0233546430f1e5a80f63a
debug_tool = jlink
; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm)
; Note: as of 6/2013 the serial/bootloader based programming takes approximately 30 seconds
;upload_protocol = jlink

View File

@@ -0,0 +1,23 @@
#include "variant.h"
#include "nrf.h"
#include "wiring_constants.h"
#include "wiring_digital.h"
const uint32_t g_ADigitalPinMap[] = {
// P0
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
// P1
32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47};
void initVariant()
{
pinMode(PIN_LED1, OUTPUT);
digitalWrite(PIN_LED1, HIGH); // turn off the white led while booting
// otherwise it will stay lit for several seconds (could be annoying)
#ifdef PIN_WD_EN
pinMode(PIN_WD_EN, OUTPUT);
digitalWrite(PIN_WD_EN, HIGH); // Enable the Watchdog at boot
#endif
}

View File

@@ -0,0 +1,153 @@
#ifndef _VARIANT_MESHLINK_
#define _VARIANT_MESHLINK_
#ifndef MESHLINK
#define MESHLINK
#endif
/** Master clock frequency */
#define VARIANT_MCK (64000000ul)
// #define USE_LFXO // Board uses 32khz crystal for LF
#define USE_LFRC // Board uses RC for LF
/*----------------------------------------------------------------------------
* Headers
*----------------------------------------------------------------------------*/
#include "WVariant.h"
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
// Number of pins defined in PinDescription array
#define PINS_COUNT (48)
#define NUM_DIGITAL_PINS (48)
#define NUM_ANALOG_INPUTS (2)
#define NUM_ANALOG_OUTPUTS (0)
#define BUTTON_PIN (-1) // If defined, this will be used for user button presses,
#define BUTTON_NEED_PULLUP
// LEDs
#define PIN_LED1 (24) // Built in white led for status
#define LED_BLUE PIN_LED1
#define LED_BUILTIN PIN_LED1
#define LED_STATE_ON 0 // State when LED is litted
#define LED_INVERTED 1
// Testing USB detection
// #define NRF_APM
/*
* Analog pins
*/
#define PIN_A1 (3) // P0.03/AIN1
#define ADC_RESOLUTION 14
// Other pins
// #define PIN_AREF (2)
// static const uint8_t AREF = PIN_AREF;
/*
* Serial interfaces
*/
#define PIN_SERIAL1_RX (32 + 8)
#define PIN_SERIAL1_TX (7)
/*
* SPI Interfaces
*/
#define SPI_INTERFACES_COUNT 2
#define PIN_SPI_MISO (8)
#define PIN_SPI_MOSI (32 + 9)
#define PIN_SPI_SCK (11)
#define PIN_SPI1_MISO (23)
#define PIN_SPI1_MOSI (21)
#define PIN_SPI1_SCK (19)
static const uint8_t SS = 12;
static const uint8_t MOSI = PIN_SPI_MOSI;
static const uint8_t MISO = PIN_SPI_MISO;
static const uint8_t SCK = PIN_SPI_SCK;
/*
* eink display pins
*/
#define USE_EINK
#define PIN_EINK_CS (15)
#define PIN_EINK_BUSY (16)
#define PIN_EINK_DC (14)
#define PIN_EINK_RES (17)
#define PIN_EINK_SCLK (19)
#define PIN_EINK_MOSI (21) // also called SDI
/*
* Wire Interfaces
*/
#define WIRE_INTERFACES_COUNT 1
#define PIN_WIRE_SDA (1)
#define PIN_WIRE_SCL (27)
// QSPI Pins
#define PIN_QSPI_SCK 19
#define PIN_QSPI_CS 22
#define PIN_QSPI_IO0 21
#define PIN_QSPI_IO1 23
#define PIN_QSPI_IO2 32
#define PIN_QSPI_IO3 20
// On-board QSPI Flash
#define EXTERNAL_FLASH_DEVICES W25Q16JVUXIQ
#define EXTERNAL_FLASH_USE_QSPI
#define USE_SX1262
#define SX126X_CS (12)
#define SX126X_DIO1 (32 + 1)
#define SX126X_BUSY (32 + 3)
#define SX126X_RESET (6)
// #define SX126X_RXEN (13)
// DIO2 controlls an antenna switch and the TCXO voltage is controlled by DIO3
#define SX126X_DIO2_AS_RF_SWITCH
#define SX126X_DIO3_TCXO_VOLTAGE 1.8
// pin 25 is used to enable or disable the watchdog. This pin has to be disabled when cpu is put to sleep
// otherwise the timer will expire and wd will reboot the cpu
#define PIN_WD_EN (25)
#define PIN_GPS_PPS (26) // Pulse per second input from the GPS
#define GPS_TX_PIN PIN_SERIAL1_RX // This is for bits going TOWARDS the CPU
#define GPS_RX_PIN PIN_SERIAL1_TX // This is for bits going TOWARDS the GPS
// #define GPS_THREAD_INTERVAL 50
// Define pin to enable GPS toggle (set GPIO to LOW) via user button triple press
#define PIN_GPS_EN (0)
#define GPS_EN_ACTIVE LOW
#define PIN_BUZZER (31) // P0.31/AIN7
// Battery
// The battery sense is hooked to pin A0 (2)
#define BATTERY_PIN (2)
// and has 12 bit resolution
#define BATTERY_SENSE_RESOLUTION_BITS 12
#define BATTERY_SENSE_RESOLUTION 4096.0
#undef AREF_VOLTAGE
#define AREF_VOLTAGE 3.0
#define VBAT_AR_INTERNAL AR_INTERNAL_3_0
#define ADC_MULTIPLIER 1.42 // fine tuning of voltage
#ifdef __cplusplus
}
#endif
/*----------------------------------------------------------------------------
* Arduino objects - C++ only
*----------------------------------------------------------------------------*/
#endif

View File

@@ -107,15 +107,11 @@ static const uint8_t AREF = PIN_AREF;
/*
* SPI Interfaces
*/
#define SPI_INTERFACES_COUNT 2
#define SPI_INTERFACES_COUNT 1
#define PIN_SPI_MISO (45)
#define PIN_SPI_MOSI (44)
#define PIN_SPI_SCK (43)
#define PIN_SPI1_MISO (29) // (0 + 29)
#define PIN_SPI1_MOSI (30) // (0 + 30)
#define PIN_SPI1_SCK (3) // (0 + 3)
#define PIN_SPI_MISO (29)
#define PIN_SPI_MOSI (30)
#define PIN_SPI_SCK (3)
static const uint8_t SS = 42;
static const uint8_t MOSI = PIN_SPI_MOSI;
@@ -130,8 +126,8 @@ static const uint8_t SCK = PIN_SPI_SCK;
#define PIN_EINK_BUSY (0 + 4)
#define PIN_EINK_DC (0 + 17)
#define PIN_EINK_RES (-1)
#define PIN_EINK_SCLK (0 + 3)
#define PIN_EINK_MOSI (0 + 30) // also called SDI
#define PIN_EINK_SCLK PIN_SPI_SCK
#define PIN_EINK_MOSI PIN_SPI_MOSI // also called SDI
// #define USE_EINK
@@ -259,7 +255,7 @@ SO GPIO 39/TXEN MAY NOT BE DEFINED FOR SUCCESSFUL OPERATION OF THE SX1262 - TG
#define PIN_ETHERNET_RESET 21
#define PIN_ETHERNET_SS PIN_EINK_CS
#define ETH_SPI_PORT SPI1
#define ETH_SPI_PORT SPI
#define AQ_SET_PIN 10
#ifdef __cplusplus