class StaticPointerQueue
+{
+ static_assert(MaxElements > 0, "MaxElements must be greater than 0");
+
+ T *buffer[MaxElements];
+ int head = 0;
+ int tail = 0;
+ int count = 0;
+ concurrency::OSThread *reader = nullptr;
+
+ public:
+ StaticPointerQueue()
+ {
+ // Initialize all buffer elements to nullptr to silence warnings and ensure clean state
+ for (int i = 0; i < MaxElements; i++) {
+ buffer[i] = nullptr;
+ }
+ }
+
+ int numFree() const { return MaxElements - count; }
+
+ bool isEmpty() const { return count == 0; }
+
+ int numUsed() const { return count; }
+
+ bool enqueue(T *x, TickType_t maxWait = portMAX_DELAY)
+ {
+ if (count >= MaxElements) {
+ return false; // Queue is full
+ }
+
+ if (reader) {
+ reader->setInterval(0);
+ concurrency::mainDelay.interrupt();
+ }
+
+ buffer[tail] = x;
+ tail = (tail + 1) % MaxElements;
+ count++;
+ return true;
+ }
+
+ bool dequeue(T **p, TickType_t maxWait = portMAX_DELAY)
+ {
+ if (count == 0) {
+ return false; // Queue is empty
+ }
+
+ *p = buffer[head];
+ head = (head + 1) % MaxElements;
+ count--;
+ return true;
+ }
+
+ // returns a ptr or null if the queue was empty
+ T *dequeuePtr(TickType_t maxWait = portMAX_DELAY)
+ {
+ T *p;
+ return dequeue(&p, maxWait) ? p : nullptr;
+ }
+
+ void setReader(concurrency::OSThread *t) { reader = t; }
+
+ // For compatibility with PointerQueue interface
+ int getMaxLen() const { return MaxElements; }
+};
diff --git a/src/mesh/StreamAPI.cpp b/src/mesh/StreamAPI.cpp
index 4a42e5197..20026767e 100644
--- a/src/mesh/StreamAPI.cpp
+++ b/src/mesh/StreamAPI.cpp
@@ -16,6 +16,95 @@ int32_t StreamAPI::runOncePart()
return result;
}
+int32_t StreamAPI::runOncePart(char *buf, uint16_t bufLen)
+{
+ auto result = readStream(buf, bufLen);
+ writeStream();
+ checkConnectionTimeout();
+ return result;
+}
+
+/**
+ * Read any rx chars from the link and call handleRecStream
+ */
+int32_t StreamAPI::readStream(char *buf, uint16_t bufLen)
+{
+ if (bufLen < 1) {
+ // Nothing available this time, if the computer has talked to us recently, poll often, otherwise let CPU sleep a long time
+ bool recentRx = Throttle::isWithinTimespanMs(lastRxMsec, 2000);
+ return recentRx ? 5 : 250;
+ } else {
+ handleRecStream(buf, bufLen);
+ // we had bytes available this time, so assume we might have them next time also
+ lastRxMsec = millis();
+ return 0;
+ }
+}
+
+/**
+ * call getFromRadio() and deliver encapsulated packets to the Stream
+ */
+void StreamAPI::writeStream()
+{
+ if (canWrite) {
+ uint32_t len;
+ do {
+ // Send every packet we can
+ len = getFromRadio(txBuf + HEADER_LEN);
+ emitTxBuffer(len);
+ } while (len);
+ }
+}
+
+int32_t StreamAPI::handleRecStream(char *buf, uint16_t bufLen)
+{
+ uint16_t index = 0;
+ while (bufLen > index) { // Currently we never want to block
+ int cInt = buf[index++];
+ if (cInt < 0)
+ break; // We ran out of characters (even though available said otherwise) - this can happen on rf52 adafruit
+ // arduino
+
+ uint8_t c = (uint8_t)cInt;
+
+ // Use the read pointer for a little state machine, first look for framing, then length bytes, then payload
+ size_t ptr = rxPtr;
+
+ rxPtr++; // assume we will probably advance the rxPtr
+ rxBuf[ptr] = c; // store all bytes (including framing)
+
+ // console->printf("rxPtr %d ptr=%d c=0x%x\n", rxPtr, ptr, c);
+
+ if (ptr == 0) { // looking for START1
+ if (c != START1)
+ rxPtr = 0; // failed to find framing
+ } else if (ptr == 1) { // looking for START2
+ if (c != START2)
+ rxPtr = 0; // failed to find framing
+ } else if (ptr >= HEADER_LEN - 1) { // we have at least read our 4 byte framing
+ uint32_t len = (rxBuf[2] << 8) + rxBuf[3]; // big endian 16 bit length follows framing
+
+ // console->printf("len %d\n", len);
+
+ if (ptr == HEADER_LEN - 1) {
+ // we _just_ finished our 4 byte header, validate length now (note: a length of zero is a valid
+ // protobuf also)
+ if (len > MAX_TO_FROM_RADIO_SIZE)
+ rxPtr = 0; // length is bogus, restart search for framing
+ }
+
+ if (rxPtr != 0) // Is packet still considered 'good'?
+ if (ptr + 1 >= len + HEADER_LEN) { // have we received all of the payload?
+ rxPtr = 0; // start over again on the next packet
+
+ // If we didn't just fail the packet and we now have the right # of bytes, parse it
+ handleToRadio(rxBuf + HEADER_LEN, len);
+ }
+ }
+ }
+ return 0;
+}
+
/**
* Read any rx chars from the link and call handleToRadio
*/
@@ -76,21 +165,6 @@ int32_t StreamAPI::readStream()
}
}
-/**
- * call getFromRadio() and deliver encapsulated packets to the Stream
- */
-void StreamAPI::writeStream()
-{
- if (canWrite) {
- uint32_t len;
- do {
- // Send every packet we can
- len = getFromRadio(txBuf + HEADER_LEN);
- emitTxBuffer(len);
- } while (len);
- }
-}
-
/**
* Send the current txBuffer over our stream
*/
diff --git a/src/mesh/StreamAPI.h b/src/mesh/StreamAPI.h
index 6e0364bc1..4ca2c197f 100644
--- a/src/mesh/StreamAPI.h
+++ b/src/mesh/StreamAPI.h
@@ -50,12 +50,15 @@ class StreamAPI : public PhoneAPI
* phone.
*/
virtual int32_t runOncePart();
+ virtual int32_t runOncePart(char *buf, uint16_t bufLen);
private:
/**
* Read any rx chars from the link and call handleToRadio
*/
int32_t readStream();
+ int32_t readStream(char *buf, uint16_t bufLen);
+ int32_t handleRecStream(char *buf, uint16_t bufLen);
/**
* call getFromRadio() and deliver encapsulated packets to the Stream
diff --git a/src/mesh/api/PacketAPI.cpp b/src/mesh/api/PacketAPI.cpp
index ab380d696..f4d5de540 100644
--- a/src/mesh/api/PacketAPI.cpp
+++ b/src/mesh/api/PacketAPI.cpp
@@ -19,6 +19,7 @@ PacketAPI *PacketAPI::create(PacketServer *_server)
PacketAPI::PacketAPI(PacketServer *_server)
: concurrency::OSThread("PacketAPI"), isConnected(false), programmingMode(false), server(_server)
{
+ api_type = TYPE_PACKET;
}
int32_t PacketAPI::runOnce()
diff --git a/src/mesh/api/WiFiServerAPI.cpp b/src/mesh/api/WiFiServerAPI.cpp
index b19194f78..4d729f5c7 100644
--- a/src/mesh/api/WiFiServerAPI.cpp
+++ b/src/mesh/api/WiFiServerAPI.cpp
@@ -25,6 +25,7 @@ void deInitApiServer()
WiFiServerAPI::WiFiServerAPI(WiFiClient &_client) : ServerAPI(_client)
{
+ api_type = TYPE_WIFI;
LOG_INFO("Incoming wifi connection");
}
diff --git a/src/mesh/api/ethServerAPI.cpp b/src/mesh/api/ethServerAPI.cpp
index 0ccf92df7..10ff06df2 100644
--- a/src/mesh/api/ethServerAPI.cpp
+++ b/src/mesh/api/ethServerAPI.cpp
@@ -20,6 +20,7 @@ void initApiServer(int port)
ethServerAPI::ethServerAPI(EthernetClient &_client) : ServerAPI(_client)
{
LOG_INFO("Incoming ethernet connection");
+ api_type = TYPE_ETH;
}
ethServerPort::ethServerPort(int port) : APIServerPort(port) {}
diff --git a/src/mesh/generated/meshtastic/admin.pb.h b/src/mesh/generated/meshtastic/admin.pb.h
index bc0b780b9..a542cf29c 100644
--- a/src/mesh/generated/meshtastic/admin.pb.h
+++ b/src/mesh/generated/meshtastic/admin.pb.h
@@ -132,6 +132,8 @@ typedef struct _meshtastic_SharedContact {
meshtastic_User user;
/* Add this contact to the blocked / ignored list */
bool should_ignore;
+ /* Set the IS_KEY_MANUALLY_VERIFIED bit */
+ bool manually_verified;
} meshtastic_SharedContact;
/* This message is used by a client to initiate or complete a key verification */
@@ -270,8 +272,9 @@ typedef struct _meshtastic_AdminMessage {
int32_t shutdown_seconds;
/* Tell the node to factory reset config; all device state and configuration will be returned to factory defaults; BLE bonds will be preserved. */
int32_t factory_reset_config;
- /* Tell the node to reset the nodedb. */
- int32_t nodedb_reset;
+ /* Tell the node to reset the nodedb.
+ When true, favorites are preserved through reset. */
+ bool nodedb_reset;
};
/* The node generates this key and sends it with any get_x_response packets.
The client MUST include the same key with any set_x commands. Key expires after 300 seconds.
@@ -319,13 +322,13 @@ extern "C" {
#define meshtastic_AdminMessage_InputEvent_init_default {0, 0, 0, 0}
#define meshtastic_HamParameters_init_default {"", 0, 0, ""}
#define meshtastic_NodeRemoteHardwarePinsResponse_init_default {0, {meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default}}
-#define meshtastic_SharedContact_init_default {0, false, meshtastic_User_init_default, 0}
+#define meshtastic_SharedContact_init_default {0, false, meshtastic_User_init_default, 0, 0}
#define meshtastic_KeyVerificationAdmin_init_default {_meshtastic_KeyVerificationAdmin_MessageType_MIN, 0, 0, false, 0}
#define meshtastic_AdminMessage_init_zero {0, {0}, {0, {0}}}
#define meshtastic_AdminMessage_InputEvent_init_zero {0, 0, 0, 0}
#define meshtastic_HamParameters_init_zero {"", 0, 0, ""}
#define meshtastic_NodeRemoteHardwarePinsResponse_init_zero {0, {meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero}}
-#define meshtastic_SharedContact_init_zero {0, false, meshtastic_User_init_zero, 0}
+#define meshtastic_SharedContact_init_zero {0, false, meshtastic_User_init_zero, 0, 0}
#define meshtastic_KeyVerificationAdmin_init_zero {_meshtastic_KeyVerificationAdmin_MessageType_MIN, 0, 0, false, 0}
/* Field tags (for use in manual encoding/decoding) */
@@ -341,6 +344,7 @@ extern "C" {
#define meshtastic_SharedContact_node_num_tag 1
#define meshtastic_SharedContact_user_tag 2
#define meshtastic_SharedContact_should_ignore_tag 3
+#define meshtastic_SharedContact_manually_verified_tag 4
#define meshtastic_KeyVerificationAdmin_message_type_tag 1
#define meshtastic_KeyVerificationAdmin_remote_nodenum_tag 2
#define meshtastic_KeyVerificationAdmin_nonce_tag 3
@@ -456,7 +460,7 @@ X(a, STATIC, ONEOF, BOOL, (payload_variant,exit_simulator,exit_simulato
X(a, STATIC, ONEOF, INT32, (payload_variant,reboot_seconds,reboot_seconds), 97) \
X(a, STATIC, ONEOF, INT32, (payload_variant,shutdown_seconds,shutdown_seconds), 98) \
X(a, STATIC, ONEOF, INT32, (payload_variant,factory_reset_config,factory_reset_config), 99) \
-X(a, STATIC, ONEOF, INT32, (payload_variant,nodedb_reset,nodedb_reset), 100) \
+X(a, STATIC, ONEOF, BOOL, (payload_variant,nodedb_reset,nodedb_reset), 100) \
X(a, STATIC, SINGULAR, BYTES, session_passkey, 101)
#define meshtastic_AdminMessage_CALLBACK NULL
#define meshtastic_AdminMessage_DEFAULT NULL
@@ -504,7 +508,8 @@ X(a, STATIC, REPEATED, MESSAGE, node_remote_hardware_pins, 1)
#define meshtastic_SharedContact_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, UINT32, node_num, 1) \
X(a, STATIC, OPTIONAL, MESSAGE, user, 2) \
-X(a, STATIC, SINGULAR, BOOL, should_ignore, 3)
+X(a, STATIC, SINGULAR, BOOL, should_ignore, 3) \
+X(a, STATIC, SINGULAR, BOOL, manually_verified, 4)
#define meshtastic_SharedContact_CALLBACK NULL
#define meshtastic_SharedContact_DEFAULT NULL
#define meshtastic_SharedContact_user_MSGTYPE meshtastic_User
@@ -539,7 +544,7 @@ extern const pb_msgdesc_t meshtastic_KeyVerificationAdmin_msg;
#define meshtastic_HamParameters_size 31
#define meshtastic_KeyVerificationAdmin_size 25
#define meshtastic_NodeRemoteHardwarePinsResponse_size 496
-#define meshtastic_SharedContact_size 125
+#define meshtastic_SharedContact_size 127
#ifdef __cplusplus
} /* extern "C" */
diff --git a/src/mesh/generated/meshtastic/channel.pb.h b/src/mesh/generated/meshtastic/channel.pb.h
index ca4310bf1..9dc757ab4 100644
--- a/src/mesh/generated/meshtastic/channel.pb.h
+++ b/src/mesh/generated/meshtastic/channel.pb.h
@@ -34,9 +34,9 @@ typedef enum _meshtastic_Channel_Role {
typedef struct _meshtastic_ModuleSettings {
/* Bits of precision for the location sent in position packets. */
uint32_t position_precision;
- /* Controls whether or not the phone / clients should mute the current channel
+ /* Controls whether or not the client / device should mute the current channel
Useful for noisy public channels you don't necessarily want to disable */
- bool is_client_muted;
+ bool is_muted;
} meshtastic_ModuleSettings;
typedef PB_BYTES_ARRAY_T(32) meshtastic_ChannelSettings_psk_t;
@@ -137,7 +137,7 @@ extern "C" {
/* Field tags (for use in manual encoding/decoding) */
#define meshtastic_ModuleSettings_position_precision_tag 1
-#define meshtastic_ModuleSettings_is_client_muted_tag 2
+#define meshtastic_ModuleSettings_is_muted_tag 2
#define meshtastic_ChannelSettings_channel_num_tag 1
#define meshtastic_ChannelSettings_psk_tag 2
#define meshtastic_ChannelSettings_name_tag 3
@@ -164,7 +164,7 @@ X(a, STATIC, OPTIONAL, MESSAGE, module_settings, 7)
#define meshtastic_ModuleSettings_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, UINT32, position_precision, 1) \
-X(a, STATIC, SINGULAR, BOOL, is_client_muted, 2)
+X(a, STATIC, SINGULAR, BOOL, is_muted, 2)
#define meshtastic_ModuleSettings_CALLBACK NULL
#define meshtastic_ModuleSettings_DEFAULT NULL
diff --git a/src/mesh/generated/meshtastic/config.pb.h b/src/mesh/generated/meshtastic/config.pb.h
index 67d461611..327568316 100644
--- a/src/mesh/generated/meshtastic/config.pb.h
+++ b/src/mesh/generated/meshtastic/config.pb.h
@@ -26,7 +26,8 @@ typedef enum _meshtastic_Config_DeviceConfig_Role {
meshtastic_Config_DeviceConfig_Role_ROUTER_CLIENT = 3,
/* Description: Infrastructure node for extending network coverage by relaying messages with minimal overhead. Not visible in Nodes list.
Technical Details: Mesh packets will simply be rebroadcasted over this node. Nodes configured with this role will not originate NodeInfo, Position, Telemetry
- or any other packet type. They will simply rebroadcast any mesh packets on the same frequency, channel num, spread factor, and coding rate. */
+ or any other packet type. They will simply rebroadcast any mesh packets on the same frequency, channel num, spread factor, and coding rate.
+ Deprecated in v2.7.11 because it creates "holes" in the mesh rebroadcast chain. */
meshtastic_Config_DeviceConfig_Role_REPEATER = 4,
/* Description: Broadcasts GPS position packets as priority.
Technical Details: Position Mesh packets will be prioritized higher and sent more frequently by default.
@@ -64,7 +65,12 @@ typedef enum _meshtastic_Config_DeviceConfig_Role {
in areas not already covered by other routers, or to bridge around problematic terrain,
but should not be given priority over other routers in order to avoid unnecessaraily
consuming hops. */
- meshtastic_Config_DeviceConfig_Role_ROUTER_LATE = 11
+ meshtastic_Config_DeviceConfig_Role_ROUTER_LATE = 11,
+ /* Description: Treats packets from or to favorited nodes as ROUTER, and all other packets as CLIENT.
+ Technical Details: Used for stronger attic/roof nodes to distribute messages more widely
+ from weaker, indoor, or less-well-positioned nodes. Recommended for users with multiple nodes
+ where one CLIENT_BASE acts as a more powerful base station, such as an attic/roof node. */
+ meshtastic_Config_DeviceConfig_Role_CLIENT_BASE = 12
} meshtastic_Config_DeviceConfig_Role;
/* Defines the device's behavior for how messages are rebroadcast */
@@ -168,28 +174,10 @@ typedef enum _meshtastic_Config_NetworkConfig_ProtocolFlags {
meshtastic_Config_NetworkConfig_ProtocolFlags_UDP_BROADCAST = 1
} meshtastic_Config_NetworkConfig_ProtocolFlags;
-/* How the GPS coordinates are displayed on the OLED screen. */
-typedef enum _meshtastic_Config_DisplayConfig_GpsCoordinateFormat {
- /* GPS coordinates are displayed in the normal decimal degrees format:
- DD.DDDDDD DDD.DDDDDD */
- meshtastic_Config_DisplayConfig_GpsCoordinateFormat_DEC = 0,
- /* GPS coordinates are displayed in the degrees minutes seconds format:
- DD°MM'SS"C DDD°MM'SS"C, where C is the compass point representing the locations quadrant */
- meshtastic_Config_DisplayConfig_GpsCoordinateFormat_DMS = 1,
- /* Universal Transverse Mercator format:
- ZZB EEEEEE NNNNNNN, where Z is zone, B is band, E is easting, N is northing */
- meshtastic_Config_DisplayConfig_GpsCoordinateFormat_UTM = 2,
- /* Military Grid Reference System format:
- ZZB CD EEEEE NNNNN, where Z is zone, B is band, C is the east 100k square, D is the north 100k square,
- E is easting, N is northing */
- meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MGRS = 3,
- /* Open Location Code (aka Plus Codes). */
- meshtastic_Config_DisplayConfig_GpsCoordinateFormat_OLC = 4,
- /* Ordnance Survey Grid Reference (the National Grid System of the UK).
- Format: AB EEEEE NNNNN, where A is the east 100k square, B is the north 100k square,
- E is the easting, N is the northing */
- meshtastic_Config_DisplayConfig_GpsCoordinateFormat_OSGR = 5
-} meshtastic_Config_DisplayConfig_GpsCoordinateFormat;
+/* Deprecated in 2.7.4: Unused */
+typedef enum _meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat {
+ meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_UNUSED = 0
+} meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat;
/* Unit display preference */
typedef enum _meshtastic_Config_DisplayConfig_DisplayUnits {
@@ -486,7 +474,7 @@ typedef struct _meshtastic_Config_DisplayConfig {
uint32_t screen_on_secs;
/* Deprecated in 2.7.4: Unused
How the GPS coordinates are formatted on the OLED screen. */
- meshtastic_Config_DisplayConfig_GpsCoordinateFormat gps_format;
+ meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat gps_format;
/* Automatically toggles to the next page on the screen like a carousel, based the specified interval in seconds.
Potentially useful for devices without user buttons. */
uint32_t auto_screen_carousel_secs;
@@ -510,6 +498,9 @@ typedef struct _meshtastic_Config_DisplayConfig {
/* If false (default), the device will display the time in 24-hour format on screen.
If true, the device will display the time in 12-hour format on screen. */
bool use_12h_clock;
+ /* If false (default), the device will use short names for various display screens.
+ If true, node names will show in long format */
+ bool use_long_node_name;
} meshtastic_Config_DisplayConfig;
/* Lora Config */
@@ -646,8 +637,8 @@ extern "C" {
/* Helper constants for enums */
#define _meshtastic_Config_DeviceConfig_Role_MIN meshtastic_Config_DeviceConfig_Role_CLIENT
-#define _meshtastic_Config_DeviceConfig_Role_MAX meshtastic_Config_DeviceConfig_Role_ROUTER_LATE
-#define _meshtastic_Config_DeviceConfig_Role_ARRAYSIZE ((meshtastic_Config_DeviceConfig_Role)(meshtastic_Config_DeviceConfig_Role_ROUTER_LATE+1))
+#define _meshtastic_Config_DeviceConfig_Role_MAX meshtastic_Config_DeviceConfig_Role_CLIENT_BASE
+#define _meshtastic_Config_DeviceConfig_Role_ARRAYSIZE ((meshtastic_Config_DeviceConfig_Role)(meshtastic_Config_DeviceConfig_Role_CLIENT_BASE+1))
#define _meshtastic_Config_DeviceConfig_RebroadcastMode_MIN meshtastic_Config_DeviceConfig_RebroadcastMode_ALL
#define _meshtastic_Config_DeviceConfig_RebroadcastMode_MAX meshtastic_Config_DeviceConfig_RebroadcastMode_CORE_PORTNUMS_ONLY
@@ -673,9 +664,9 @@ extern "C" {
#define _meshtastic_Config_NetworkConfig_ProtocolFlags_MAX meshtastic_Config_NetworkConfig_ProtocolFlags_UDP_BROADCAST
#define _meshtastic_Config_NetworkConfig_ProtocolFlags_ARRAYSIZE ((meshtastic_Config_NetworkConfig_ProtocolFlags)(meshtastic_Config_NetworkConfig_ProtocolFlags_UDP_BROADCAST+1))
-#define _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MIN meshtastic_Config_DisplayConfig_GpsCoordinateFormat_DEC
-#define _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MAX meshtastic_Config_DisplayConfig_GpsCoordinateFormat_OSGR
-#define _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_ARRAYSIZE ((meshtastic_Config_DisplayConfig_GpsCoordinateFormat)(meshtastic_Config_DisplayConfig_GpsCoordinateFormat_OSGR+1))
+#define _meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_MIN meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_UNUSED
+#define _meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_MAX meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_UNUSED
+#define _meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_ARRAYSIZE ((meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat)(meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_UNUSED+1))
#define _meshtastic_Config_DisplayConfig_DisplayUnits_MIN meshtastic_Config_DisplayConfig_DisplayUnits_METRIC
#define _meshtastic_Config_DisplayConfig_DisplayUnits_MAX meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL
@@ -716,7 +707,7 @@ extern "C" {
#define meshtastic_Config_NetworkConfig_address_mode_ENUMTYPE meshtastic_Config_NetworkConfig_AddressMode
-#define meshtastic_Config_DisplayConfig_gps_format_ENUMTYPE meshtastic_Config_DisplayConfig_GpsCoordinateFormat
+#define meshtastic_Config_DisplayConfig_gps_format_ENUMTYPE meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat
#define meshtastic_Config_DisplayConfig_units_ENUMTYPE meshtastic_Config_DisplayConfig_DisplayUnits
#define meshtastic_Config_DisplayConfig_oled_ENUMTYPE meshtastic_Config_DisplayConfig_OledType
#define meshtastic_Config_DisplayConfig_displaymode_ENUMTYPE meshtastic_Config_DisplayConfig_DisplayMode
@@ -737,7 +728,7 @@ extern "C" {
#define meshtastic_Config_PowerConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0}
#define meshtastic_Config_NetworkConfig_init_default {0, "", "", "", 0, _meshtastic_Config_NetworkConfig_AddressMode_MIN, false, meshtastic_Config_NetworkConfig_IpV4Config_init_default, "", 0, 0}
#define meshtastic_Config_NetworkConfig_IpV4Config_init_default {0, 0, 0, 0}
-#define meshtastic_Config_DisplayConfig_init_default {0, _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN, 0}
+#define meshtastic_Config_DisplayConfig_init_default {0, _meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN, 0, 0}
#define meshtastic_Config_LoRaConfig_init_default {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0, 0}
#define meshtastic_Config_BluetoothConfig_init_default {0, _meshtastic_Config_BluetoothConfig_PairingMode_MIN, 0}
#define meshtastic_Config_SecurityConfig_init_default {{0, {0}}, {0, {0}}, 0, {{0, {0}}, {0, {0}}, {0, {0}}}, 0, 0, 0, 0}
@@ -748,7 +739,7 @@ extern "C" {
#define meshtastic_Config_PowerConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0}
#define meshtastic_Config_NetworkConfig_init_zero {0, "", "", "", 0, _meshtastic_Config_NetworkConfig_AddressMode_MIN, false, meshtastic_Config_NetworkConfig_IpV4Config_init_zero, "", 0, 0}
#define meshtastic_Config_NetworkConfig_IpV4Config_init_zero {0, 0, 0, 0}
-#define meshtastic_Config_DisplayConfig_init_zero {0, _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN, 0}
+#define meshtastic_Config_DisplayConfig_init_zero {0, _meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN, 0, 0}
#define meshtastic_Config_LoRaConfig_init_zero {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0, 0}
#define meshtastic_Config_BluetoothConfig_init_zero {0, _meshtastic_Config_BluetoothConfig_PairingMode_MIN, 0}
#define meshtastic_Config_SecurityConfig_init_zero {{0, {0}}, {0, {0}}, 0, {{0, {0}}, {0, {0}}, {0, {0}}}, 0, 0, 0, 0}
@@ -815,6 +806,7 @@ extern "C" {
#define meshtastic_Config_DisplayConfig_wake_on_tap_or_motion_tag 10
#define meshtastic_Config_DisplayConfig_compass_orientation_tag 11
#define meshtastic_Config_DisplayConfig_use_12h_clock_tag 12
+#define meshtastic_Config_DisplayConfig_use_long_node_name_tag 13
#define meshtastic_Config_LoRaConfig_use_preset_tag 1
#define meshtastic_Config_LoRaConfig_modem_preset_tag 2
#define meshtastic_Config_LoRaConfig_bandwidth_tag 3
@@ -960,7 +952,8 @@ X(a, STATIC, SINGULAR, UENUM, displaymode, 8) \
X(a, STATIC, SINGULAR, BOOL, heading_bold, 9) \
X(a, STATIC, SINGULAR, BOOL, wake_on_tap_or_motion, 10) \
X(a, STATIC, SINGULAR, UENUM, compass_orientation, 11) \
-X(a, STATIC, SINGULAR, BOOL, use_12h_clock, 12)
+X(a, STATIC, SINGULAR, BOOL, use_12h_clock, 12) \
+X(a, STATIC, SINGULAR, BOOL, use_long_node_name, 13)
#define meshtastic_Config_DisplayConfig_CALLBACK NULL
#define meshtastic_Config_DisplayConfig_DEFAULT NULL
@@ -1038,7 +1031,7 @@ extern const pb_msgdesc_t meshtastic_Config_SessionkeyConfig_msg;
#define MESHTASTIC_MESHTASTIC_CONFIG_PB_H_MAX_SIZE meshtastic_Config_size
#define meshtastic_Config_BluetoothConfig_size 10
#define meshtastic_Config_DeviceConfig_size 100
-#define meshtastic_Config_DisplayConfig_size 32
+#define meshtastic_Config_DisplayConfig_size 34
#define meshtastic_Config_LoRaConfig_size 85
#define meshtastic_Config_NetworkConfig_IpV4Config_size 20
#define meshtastic_Config_NetworkConfig_size 204
diff --git a/src/mesh/generated/meshtastic/device_ui.pb.cpp b/src/mesh/generated/meshtastic/device_ui.pb.cpp
index 2fc8d9461..01940265f 100644
--- a/src/mesh/generated/meshtastic/device_ui.pb.cpp
+++ b/src/mesh/generated/meshtastic/device_ui.pb.cpp
@@ -28,3 +28,5 @@ PB_BIND(meshtastic_Map, meshtastic_Map, AUTO)
+
+
diff --git a/src/mesh/generated/meshtastic/device_ui.pb.h b/src/mesh/generated/meshtastic/device_ui.pb.h
index 8313438f8..b99fb10b9 100644
--- a/src/mesh/generated/meshtastic/device_ui.pb.h
+++ b/src/mesh/generated/meshtastic/device_ui.pb.h
@@ -66,12 +66,42 @@ typedef enum _meshtastic_Language {
meshtastic_Language_UKRAINIAN = 16,
/* Bulgarian */
meshtastic_Language_BULGARIAN = 17,
+ /* Czech */
+ meshtastic_Language_CZECH = 18,
+ /* Danish */
+ meshtastic_Language_DANISH = 19,
/* Simplified Chinese (experimental) */
meshtastic_Language_SIMPLIFIED_CHINESE = 30,
/* Traditional Chinese (experimental) */
meshtastic_Language_TRADITIONAL_CHINESE = 31
} meshtastic_Language;
+/* How the GPS coordinates are displayed on the OLED screen. */
+typedef enum _meshtastic_DeviceUIConfig_GpsCoordinateFormat {
+ /* GPS coordinates are displayed in the normal decimal degrees format:
+ DD.DDDDDD DDD.DDDDDD */
+ meshtastic_DeviceUIConfig_GpsCoordinateFormat_DEC = 0,
+ /* GPS coordinates are displayed in the degrees minutes seconds format:
+ DD°MM'SS"C DDD°MM'SS"C, where C is the compass point representing the locations quadrant */
+ meshtastic_DeviceUIConfig_GpsCoordinateFormat_DMS = 1,
+ /* Universal Transverse Mercator format:
+ ZZB EEEEEE NNNNNNN, where Z is zone, B is band, E is easting, N is northing */
+ meshtastic_DeviceUIConfig_GpsCoordinateFormat_UTM = 2,
+ /* Military Grid Reference System format:
+ ZZB CD EEEEE NNNNN, where Z is zone, B is band, C is the east 100k square, D is the north 100k square,
+ E is easting, N is northing */
+ meshtastic_DeviceUIConfig_GpsCoordinateFormat_MGRS = 3,
+ /* Open Location Code (aka Plus Codes). */
+ meshtastic_DeviceUIConfig_GpsCoordinateFormat_OLC = 4,
+ /* Ordnance Survey Grid Reference (the National Grid System of the UK).
+ Format: AB EEEEE NNNNN, where A is the east 100k square, B is the north 100k square,
+ E is the easting, N is the northing */
+ meshtastic_DeviceUIConfig_GpsCoordinateFormat_OSGR = 5,
+ /* Maidenhead Locator System
+ Described here: https://en.wikipedia.org/wiki/Maidenhead_Locator_System */
+ meshtastic_DeviceUIConfig_GpsCoordinateFormat_MLS = 6
+} meshtastic_DeviceUIConfig_GpsCoordinateFormat;
+
/* Struct definitions */
typedef struct _meshtastic_NodeFilter {
/* Filter unknown nodes */
@@ -161,6 +191,8 @@ typedef struct _meshtastic_DeviceUIConfig {
/* Clockface analog style
true for analog clockface, false for digital clockface */
bool is_clockface_analog;
+ /* How the GPS coordinates are formatted on the OLED screen. */
+ meshtastic_DeviceUIConfig_GpsCoordinateFormat gps_format;
} meshtastic_DeviceUIConfig;
@@ -181,9 +213,14 @@ extern "C" {
#define _meshtastic_Language_MAX meshtastic_Language_TRADITIONAL_CHINESE
#define _meshtastic_Language_ARRAYSIZE ((meshtastic_Language)(meshtastic_Language_TRADITIONAL_CHINESE+1))
+#define _meshtastic_DeviceUIConfig_GpsCoordinateFormat_MIN meshtastic_DeviceUIConfig_GpsCoordinateFormat_DEC
+#define _meshtastic_DeviceUIConfig_GpsCoordinateFormat_MAX meshtastic_DeviceUIConfig_GpsCoordinateFormat_MLS
+#define _meshtastic_DeviceUIConfig_GpsCoordinateFormat_ARRAYSIZE ((meshtastic_DeviceUIConfig_GpsCoordinateFormat)(meshtastic_DeviceUIConfig_GpsCoordinateFormat_MLS+1))
+
#define meshtastic_DeviceUIConfig_theme_ENUMTYPE meshtastic_Theme
#define meshtastic_DeviceUIConfig_language_ENUMTYPE meshtastic_Language
#define meshtastic_DeviceUIConfig_compass_mode_ENUMTYPE meshtastic_CompassMode
+#define meshtastic_DeviceUIConfig_gps_format_ENUMTYPE meshtastic_DeviceUIConfig_GpsCoordinateFormat
@@ -191,12 +228,12 @@ extern "C" {
/* Initializer values for message structs */
-#define meshtastic_DeviceUIConfig_init_default {0, 0, 0, 0, 0, 0, _meshtastic_Theme_MIN, 0, 0, 0, _meshtastic_Language_MIN, false, meshtastic_NodeFilter_init_default, false, meshtastic_NodeHighlight_init_default, {0, {0}}, false, meshtastic_Map_init_default, _meshtastic_CompassMode_MIN, 0, 0}
+#define meshtastic_DeviceUIConfig_init_default {0, 0, 0, 0, 0, 0, _meshtastic_Theme_MIN, 0, 0, 0, _meshtastic_Language_MIN, false, meshtastic_NodeFilter_init_default, false, meshtastic_NodeHighlight_init_default, {0, {0}}, false, meshtastic_Map_init_default, _meshtastic_CompassMode_MIN, 0, 0, _meshtastic_DeviceUIConfig_GpsCoordinateFormat_MIN}
#define meshtastic_NodeFilter_init_default {0, 0, 0, 0, 0, "", 0}
#define meshtastic_NodeHighlight_init_default {0, 0, 0, 0, ""}
#define meshtastic_GeoPoint_init_default {0, 0, 0}
#define meshtastic_Map_init_default {false, meshtastic_GeoPoint_init_default, "", 0}
-#define meshtastic_DeviceUIConfig_init_zero {0, 0, 0, 0, 0, 0, _meshtastic_Theme_MIN, 0, 0, 0, _meshtastic_Language_MIN, false, meshtastic_NodeFilter_init_zero, false, meshtastic_NodeHighlight_init_zero, {0, {0}}, false, meshtastic_Map_init_zero, _meshtastic_CompassMode_MIN, 0, 0}
+#define meshtastic_DeviceUIConfig_init_zero {0, 0, 0, 0, 0, 0, _meshtastic_Theme_MIN, 0, 0, 0, _meshtastic_Language_MIN, false, meshtastic_NodeFilter_init_zero, false, meshtastic_NodeHighlight_init_zero, {0, {0}}, false, meshtastic_Map_init_zero, _meshtastic_CompassMode_MIN, 0, 0, _meshtastic_DeviceUIConfig_GpsCoordinateFormat_MIN}
#define meshtastic_NodeFilter_init_zero {0, 0, 0, 0, 0, "", 0}
#define meshtastic_NodeHighlight_init_zero {0, 0, 0, 0, ""}
#define meshtastic_GeoPoint_init_zero {0, 0, 0}
@@ -239,6 +276,7 @@ extern "C" {
#define meshtastic_DeviceUIConfig_compass_mode_tag 16
#define meshtastic_DeviceUIConfig_screen_rgb_color_tag 17
#define meshtastic_DeviceUIConfig_is_clockface_analog_tag 18
+#define meshtastic_DeviceUIConfig_gps_format_tag 19
/* Struct field encoding specification for nanopb */
#define meshtastic_DeviceUIConfig_FIELDLIST(X, a) \
@@ -259,7 +297,8 @@ X(a, STATIC, SINGULAR, BYTES, calibration_data, 14) \
X(a, STATIC, OPTIONAL, MESSAGE, map_data, 15) \
X(a, STATIC, SINGULAR, UENUM, compass_mode, 16) \
X(a, STATIC, SINGULAR, UINT32, screen_rgb_color, 17) \
-X(a, STATIC, SINGULAR, BOOL, is_clockface_analog, 18)
+X(a, STATIC, SINGULAR, BOOL, is_clockface_analog, 18) \
+X(a, STATIC, SINGULAR, UENUM, gps_format, 19)
#define meshtastic_DeviceUIConfig_CALLBACK NULL
#define meshtastic_DeviceUIConfig_DEFAULT NULL
#define meshtastic_DeviceUIConfig_node_filter_MSGTYPE meshtastic_NodeFilter
@@ -316,7 +355,7 @@ extern const pb_msgdesc_t meshtastic_Map_msg;
/* Maximum encoded size of messages (where known) */
#define MESHTASTIC_MESHTASTIC_DEVICE_UI_PB_H_MAX_SIZE meshtastic_DeviceUIConfig_size
-#define meshtastic_DeviceUIConfig_size 201
+#define meshtastic_DeviceUIConfig_size 204
#define meshtastic_GeoPoint_size 33
#define meshtastic_Map_size 58
#define meshtastic_NodeFilter_size 47
diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.h b/src/mesh/generated/meshtastic/deviceonly.pb.h
index b2bc16e36..71d5c4d0c 100644
--- a/src/mesh/generated/meshtastic/deviceonly.pb.h
+++ b/src/mesh/generated/meshtastic/deviceonly.pb.h
@@ -360,7 +360,7 @@ extern const pb_msgdesc_t meshtastic_BackupPreferences_msg;
/* Maximum encoded size of messages (where known) */
/* meshtastic_NodeDatabase_size depends on runtime parameters */
#define MESHTASTIC_MESHTASTIC_DEVICEONLY_PB_H_MAX_SIZE meshtastic_BackupPreferences_size
-#define meshtastic_BackupPreferences_size 2271
+#define meshtastic_BackupPreferences_size 2277
#define meshtastic_ChannelFile_size 718
#define meshtastic_DeviceState_size 1944
#define meshtastic_NodeInfoLite_size 196
diff --git a/src/mesh/generated/meshtastic/localonly.pb.h b/src/mesh/generated/meshtastic/localonly.pb.h
index ca8dcd5fb..3ab6f02c1 100644
--- a/src/mesh/generated/meshtastic/localonly.pb.h
+++ b/src/mesh/generated/meshtastic/localonly.pb.h
@@ -187,8 +187,8 @@ extern const pb_msgdesc_t meshtastic_LocalModuleConfig_msg;
/* Maximum encoded size of messages (where known) */
#define MESHTASTIC_MESHTASTIC_LOCALONLY_PB_H_MAX_SIZE meshtastic_LocalConfig_size
-#define meshtastic_LocalConfig_size 747
-#define meshtastic_LocalModuleConfig_size 669
+#define meshtastic_LocalConfig_size 749
+#define meshtastic_LocalModuleConfig_size 673
#ifdef __cplusplus
} /* extern "C" */
diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h
index 9cd0b47b6..38ff17946 100644
--- a/src/mesh/generated/meshtastic/mesh.pb.h
+++ b/src/mesh/generated/meshtastic/mesh.pb.h
@@ -253,14 +253,14 @@ typedef enum _meshtastic_HardwareModel {
meshtastic_HardwareModel_SEEED_WIO_TRACKER_L1 = 99,
/* Seeed Tracker L1 EINK driver */
meshtastic_HardwareModel_SEEED_WIO_TRACKER_L1_EINK = 100,
- /* Reserved ID for future and past use */
- meshtastic_HardwareModel_QWANTZ_TINY_ARMS = 101,
+ /* Muzi Works R1 Neo */
+ meshtastic_HardwareModel_MUZI_R1_NEO = 101,
/* Lilygo T-Deck Pro */
meshtastic_HardwareModel_T_DECK_PRO = 102,
/* Lilygo TLora Pager */
meshtastic_HardwareModel_T_LORA_PAGER = 103,
- /* GAT562 Mesh Trial Tracker */
- meshtastic_HardwareModel_GAT562_MESH_TRIAL_TRACKER = 104,
+ /* M5Stack Reserved */
+ meshtastic_HardwareModel_M5STACK_RESERVED = 104, /* 0x68 */
/* RAKwireless WisMesh Tag */
meshtastic_HardwareModel_WISMESH_TAG = 105,
/* RAKwireless WisBlock Core RAK3312 https://docs.rakwireless.com/product-categories/wisduo/rak3112-module/overview/ */
@@ -272,6 +272,28 @@ typedef enum _meshtastic_HardwareModel {
meshtastic_HardwareModel_HELTEC_MESH_SOLAR = 108,
/* Lilygo T-Echo Lite */
meshtastic_HardwareModel_T_ECHO_LITE = 109,
+ /* New Heltec LoRA32 with ESP32-S3 CPU */
+ meshtastic_HardwareModel_HELTEC_V4 = 110,
+ /* M5Stack C6L */
+ meshtastic_HardwareModel_M5STACK_C6L = 111,
+ /* M5Stack Cardputer Adv */
+ meshtastic_HardwareModel_M5STACK_CARDPUTER_ADV = 112,
+ /* ESP32S3 main controller with GPS and TFT screen. */
+ meshtastic_HardwareModel_HELTEC_WIRELESS_TRACKER_V2 = 113,
+ /* LilyGo T-Watch Ultra */
+ meshtastic_HardwareModel_T_WATCH_ULTRA = 114,
+ /* Elecrow ThinkNode M3 */
+ meshtastic_HardwareModel_THINKNODE_M3 = 115,
+ /* RAK WISMESH_TAP_V2 with ESP32-S3 CPU */
+ meshtastic_HardwareModel_WISMESH_TAP_V2 = 116,
+ /* RAK3401 */
+ meshtastic_HardwareModel_RAK3401 = 117,
+ /* RAK6421 Hat+ */
+ meshtastic_HardwareModel_RAK6421 = 118,
+ /* Elecrow ThinkNode M4 */
+ meshtastic_HardwareModel_THINKNODE_M4 = 119,
+ /* Elecrow ThinkNode M6 */
+ meshtastic_HardwareModel_THINKNODE_M6 = 120,
/* ------------------------------------------------------------------------------------------------------------------------------------------
Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits.
------------------------------------------------------------------------------------------------------------------------------------------ */
@@ -813,7 +835,11 @@ typedef struct _meshtastic_MeshPacket {
Note: Our crypto implementation uses this field as well.
See [crypto](/docs/overview/encryption) for details. */
uint32_t from;
- /* The (immediate) destination for this packet */
+ /* The (immediate) destination for this packet
+ If the value is 4,294,967,295 (maximum value of an unsigned 32bit integer), this indicates that the packet was
+ not destined for a specific node, but for a channel as indicated by the value of `channel` below.
+ If the value is another, this indicates that the packet was destined for a specific
+ node (i.e. a kind of "Direct Message" to this node) and not broadcast on a channel. */
uint32_t to;
/* (Usually) If set, this indicates the index in the secondary_channels table that this packet was sent/received on.
If unset, packet was on the primary channel.
diff --git a/src/mesh/generated/meshtastic/module_config.pb.h b/src/mesh/generated/meshtastic/module_config.pb.h
index b27f5f515..47d3b5baa 100644
--- a/src/mesh/generated/meshtastic/module_config.pb.h
+++ b/src/mesh/generated/meshtastic/module_config.pb.h
@@ -317,6 +317,9 @@ typedef struct _meshtastic_ModuleConfig_RangeTestConfig {
/* Bool value indicating that this node should save a RangeTest.csv file.
ESP32 Only */
bool save;
+ /* Bool indicating that the node should cleanup / destroy it's RangeTest.csv file.
+ ESP32 Only */
+ bool clear_on_reboot;
} meshtastic_ModuleConfig_RangeTestConfig;
/* Configuration for both device and environment metrics */
@@ -353,6 +356,9 @@ typedef struct _meshtastic_ModuleConfig_TelemetryConfig {
uint32_t health_update_interval;
/* Enable/Disable the health telemetry module on-device display */
bool health_screen_enabled;
+ /* Enable/Disable the device telemetry module to send metrics to the mesh
+ Note: We will still send telemtry to the connected phone / client every minute over the API */
+ bool device_telemetry_enabled;
} meshtastic_ModuleConfig_TelemetryConfig;
/* Canned Messages Module Config */
@@ -519,8 +525,8 @@ extern "C" {
#define meshtastic_ModuleConfig_SerialConfig_init_default {0, 0, 0, 0, _meshtastic_ModuleConfig_SerialConfig_Serial_Baud_MIN, 0, _meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MIN, 0}
#define meshtastic_ModuleConfig_ExternalNotificationConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
#define meshtastic_ModuleConfig_StoreForwardConfig_init_default {0, 0, 0, 0, 0, 0}
-#define meshtastic_ModuleConfig_RangeTestConfig_init_default {0, 0, 0}
-#define meshtastic_ModuleConfig_TelemetryConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
+#define meshtastic_ModuleConfig_RangeTestConfig_init_default {0, 0, 0, 0}
+#define meshtastic_ModuleConfig_TelemetryConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
#define meshtastic_ModuleConfig_CannedMessageConfig_init_default {0, 0, 0, 0, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, 0, 0, "", 0}
#define meshtastic_ModuleConfig_AmbientLightingConfig_init_default {0, 0, 0, 0, 0}
#define meshtastic_RemoteHardwarePin_init_default {0, "", _meshtastic_RemoteHardwarePinType_MIN}
@@ -535,8 +541,8 @@ extern "C" {
#define meshtastic_ModuleConfig_SerialConfig_init_zero {0, 0, 0, 0, _meshtastic_ModuleConfig_SerialConfig_Serial_Baud_MIN, 0, _meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MIN, 0}
#define meshtastic_ModuleConfig_ExternalNotificationConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
#define meshtastic_ModuleConfig_StoreForwardConfig_init_zero {0, 0, 0, 0, 0, 0}
-#define meshtastic_ModuleConfig_RangeTestConfig_init_zero {0, 0, 0}
-#define meshtastic_ModuleConfig_TelemetryConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
+#define meshtastic_ModuleConfig_RangeTestConfig_init_zero {0, 0, 0, 0}
+#define meshtastic_ModuleConfig_TelemetryConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
#define meshtastic_ModuleConfig_CannedMessageConfig_init_zero {0, 0, 0, 0, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, 0, 0, "", 0}
#define meshtastic_ModuleConfig_AmbientLightingConfig_init_zero {0, 0, 0, 0, 0}
#define meshtastic_RemoteHardwarePin_init_zero {0, "", _meshtastic_RemoteHardwarePinType_MIN}
@@ -610,6 +616,7 @@ extern "C" {
#define meshtastic_ModuleConfig_RangeTestConfig_enabled_tag 1
#define meshtastic_ModuleConfig_RangeTestConfig_sender_tag 2
#define meshtastic_ModuleConfig_RangeTestConfig_save_tag 3
+#define meshtastic_ModuleConfig_RangeTestConfig_clear_on_reboot_tag 4
#define meshtastic_ModuleConfig_TelemetryConfig_device_update_interval_tag 1
#define meshtastic_ModuleConfig_TelemetryConfig_environment_update_interval_tag 2
#define meshtastic_ModuleConfig_TelemetryConfig_environment_measurement_enabled_tag 3
@@ -623,6 +630,7 @@ extern "C" {
#define meshtastic_ModuleConfig_TelemetryConfig_health_measurement_enabled_tag 11
#define meshtastic_ModuleConfig_TelemetryConfig_health_update_interval_tag 12
#define meshtastic_ModuleConfig_TelemetryConfig_health_screen_enabled_tag 13
+#define meshtastic_ModuleConfig_TelemetryConfig_device_telemetry_enabled_tag 14
#define meshtastic_ModuleConfig_CannedMessageConfig_rotary1_enabled_tag 1
#define meshtastic_ModuleConfig_CannedMessageConfig_inputbroker_pin_a_tag 2
#define meshtastic_ModuleConfig_CannedMessageConfig_inputbroker_pin_b_tag 3
@@ -803,7 +811,8 @@ X(a, STATIC, SINGULAR, BOOL, is_server, 6)
#define meshtastic_ModuleConfig_RangeTestConfig_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, BOOL, enabled, 1) \
X(a, STATIC, SINGULAR, UINT32, sender, 2) \
-X(a, STATIC, SINGULAR, BOOL, save, 3)
+X(a, STATIC, SINGULAR, BOOL, save, 3) \
+X(a, STATIC, SINGULAR, BOOL, clear_on_reboot, 4)
#define meshtastic_ModuleConfig_RangeTestConfig_CALLBACK NULL
#define meshtastic_ModuleConfig_RangeTestConfig_DEFAULT NULL
@@ -820,7 +829,8 @@ X(a, STATIC, SINGULAR, UINT32, power_update_interval, 9) \
X(a, STATIC, SINGULAR, BOOL, power_screen_enabled, 10) \
X(a, STATIC, SINGULAR, BOOL, health_measurement_enabled, 11) \
X(a, STATIC, SINGULAR, UINT32, health_update_interval, 12) \
-X(a, STATIC, SINGULAR, BOOL, health_screen_enabled, 13)
+X(a, STATIC, SINGULAR, BOOL, health_screen_enabled, 13) \
+X(a, STATIC, SINGULAR, BOOL, device_telemetry_enabled, 14)
#define meshtastic_ModuleConfig_TelemetryConfig_CALLBACK NULL
#define meshtastic_ModuleConfig_TelemetryConfig_DEFAULT NULL
@@ -901,11 +911,11 @@ extern const pb_msgdesc_t meshtastic_RemoteHardwarePin_msg;
#define meshtastic_ModuleConfig_MapReportSettings_size 14
#define meshtastic_ModuleConfig_NeighborInfoConfig_size 10
#define meshtastic_ModuleConfig_PaxcounterConfig_size 30
-#define meshtastic_ModuleConfig_RangeTestConfig_size 10
+#define meshtastic_ModuleConfig_RangeTestConfig_size 12
#define meshtastic_ModuleConfig_RemoteHardwareConfig_size 96
#define meshtastic_ModuleConfig_SerialConfig_size 28
#define meshtastic_ModuleConfig_StoreForwardConfig_size 24
-#define meshtastic_ModuleConfig_TelemetryConfig_size 46
+#define meshtastic_ModuleConfig_TelemetryConfig_size 48
#define meshtastic_ModuleConfig_size 227
#define meshtastic_RemoteHardwarePin_size 21
diff --git a/src/mesh/generated/meshtastic/telemetry.pb.h b/src/mesh/generated/meshtastic/telemetry.pb.h
index 9af095e78..dec89ba15 100644
--- a/src/mesh/generated/meshtastic/telemetry.pb.h
+++ b/src/mesh/generated/meshtastic/telemetry.pb.h
@@ -101,7 +101,9 @@ typedef enum _meshtastic_TelemetrySensorType {
/* SEN5X PM SENSORS */
meshtastic_TelemetrySensorType_SEN5X = 43,
/* TSL2561 light sensor */
- meshtastic_TelemetrySensorType_TSL2561 = 44
+ meshtastic_TelemetrySensorType_TSL2561 = 44,
+ /* BH1750 light sensor */
+ meshtastic_TelemetrySensorType_BH1750 = 45
} meshtastic_TelemetrySensorType;
/* Struct definitions */
@@ -357,6 +359,8 @@ typedef struct _meshtastic_LocalStats {
uint32_t heap_total_bytes;
/* Number of bytes free in the heap */
uint32_t heap_free_bytes;
+ /* Number of packets that were dropped because the transmit queue was full. */
+ uint16_t num_tx_dropped;
} meshtastic_LocalStats;
/* Health telemetry metrics */
@@ -436,8 +440,8 @@ extern "C" {
/* Helper constants for enums */
#define _meshtastic_TelemetrySensorType_MIN meshtastic_TelemetrySensorType_SENSOR_UNSET
-#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_TSL2561
-#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_TSL2561+1))
+#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_BH1750
+#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_BH1750+1))
@@ -454,7 +458,7 @@ extern "C" {
#define meshtastic_EnvironmentMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
#define meshtastic_PowerMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
#define meshtastic_AirQualityMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
-#define meshtastic_LocalStats_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
+#define meshtastic_LocalStats_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
#define meshtastic_HealthMetrics_init_default {false, 0, false, 0, false, 0}
#define meshtastic_HostMetrics_init_default {0, 0, 0, false, 0, false, 0, 0, 0, 0, false, ""}
#define meshtastic_Telemetry_init_default {0, 0, {meshtastic_DeviceMetrics_init_default}}
@@ -463,7 +467,7 @@ extern "C" {
#define meshtastic_EnvironmentMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
#define meshtastic_PowerMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
#define meshtastic_AirQualityMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
-#define meshtastic_LocalStats_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
+#define meshtastic_LocalStats_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
#define meshtastic_HealthMetrics_init_zero {false, 0, false, 0, false, 0}
#define meshtastic_HostMetrics_init_zero {0, 0, 0, false, 0, false, 0, 0, 0, 0, false, ""}
#define meshtastic_Telemetry_init_zero {0, 0, {meshtastic_DeviceMetrics_init_zero}}
@@ -551,6 +555,7 @@ extern "C" {
#define meshtastic_LocalStats_num_tx_relay_canceled_tag 11
#define meshtastic_LocalStats_heap_total_bytes_tag 12
#define meshtastic_LocalStats_heap_free_bytes_tag 13
+#define meshtastic_LocalStats_num_tx_dropped_tag 14
#define meshtastic_HealthMetrics_heart_bpm_tag 1
#define meshtastic_HealthMetrics_spO2_tag 2
#define meshtastic_HealthMetrics_temperature_tag 3
@@ -672,7 +677,8 @@ X(a, STATIC, SINGULAR, UINT32, num_rx_dupe, 9) \
X(a, STATIC, SINGULAR, UINT32, num_tx_relay, 10) \
X(a, STATIC, SINGULAR, UINT32, num_tx_relay_canceled, 11) \
X(a, STATIC, SINGULAR, UINT32, heap_total_bytes, 12) \
-X(a, STATIC, SINGULAR, UINT32, heap_free_bytes, 13)
+X(a, STATIC, SINGULAR, UINT32, heap_free_bytes, 13) \
+X(a, STATIC, SINGULAR, UINT32, num_tx_dropped, 14)
#define meshtastic_LocalStats_CALLBACK NULL
#define meshtastic_LocalStats_DEFAULT NULL
@@ -749,7 +755,7 @@ extern const pb_msgdesc_t meshtastic_Nau7802Config_msg;
#define meshtastic_EnvironmentMetrics_size 113
#define meshtastic_HealthMetrics_size 11
#define meshtastic_HostMetrics_size 264
-#define meshtastic_LocalStats_size 72
+#define meshtastic_LocalStats_size 76
#define meshtastic_Nau7802Config_size 16
#define meshtastic_PowerMetrics_size 81
#define meshtastic_Telemetry_size 272
diff --git a/src/mesh/http/ContentHandler.cpp b/src/mesh/http/ContentHandler.cpp
index 42ebb8417..7b7ebb595 100644
--- a/src/mesh/http/ContentHandler.cpp
+++ b/src/mesh/http/ContentHandler.cpp
@@ -55,12 +55,12 @@ HTTPClient httpClient;
// We need to specify some content-type mapping, so the resources get delivered with the
// right content type and are displayed correctly in the browser
-char contentTypes[][2][32] = {{".txt", "text/plain"}, {".html", "text/html"},
- {".js", "text/javascript"}, {".png", "image/png"},
- {".jpg", "image/jpg"}, {".gz", "application/gzip"},
- {".gif", "image/gif"}, {".json", "application/json"},
- {".css", "text/css"}, {".ico", "image/vnd.microsoft.icon"},
- {".svg", "image/svg+xml"}, {"", ""}};
+char const *contentTypes[][2] = {{".txt", "text/plain"}, {".html", "text/html"},
+ {".js", "text/javascript"}, {".png", "image/png"},
+ {".jpg", "image/jpg"}, {".gz", "application/gzip"},
+ {".gif", "image/gif"}, {".json", "application/json"},
+ {".css", "text/css"}, {".ico", "image/vnd.microsoft.icon"},
+ {".svg", "image/svg+xml"}, {"", ""}};
// const char *certificate = NULL; // change this as needed, leave as is for no TLS check (yolo security)
@@ -148,6 +148,8 @@ void registerHandlers(HTTPServer *insecureServer, HTTPSServer *secureServer)
void handleAPIv1FromRadio(HTTPRequest *req, HTTPResponse *res)
{
+ if (webServerThread)
+ webServerThread->markActivity();
LOG_DEBUG("webAPI handleAPIv1FromRadio");
@@ -292,11 +294,14 @@ JSONArray htmlListDir(const char *dirname, uint8_t levels)
JSONObject thisFileMap;
thisFileMap["size"] = new JSONValue((int)file.size());
#ifdef ARCH_ESP32
- thisFileMap["name"] = new JSONValue(String(file.path()).substring(1).c_str());
+ String fileName = String(file.path()).substring(1);
+ thisFileMap["name"] = new JSONValue(fileName.c_str());
#else
- thisFileMap["name"] = new JSONValue(String(file.name()).substring(1).c_str());
+ String fileName = String(file.name()).substring(1);
+ thisFileMap["name"] = new JSONValue(fileName.c_str());
#endif
- if (String(file.name()).substring(1).endsWith(".gz")) {
+ String tempName = String(file.name()).substring(1);
+ if (tempName.endsWith(".gz")) {
#ifdef ARCH_ESP32
String modifiedFile = String(file.path()).substring(1);
#else
@@ -339,9 +344,15 @@ void handleFsBrowseStatic(HTTPRequest *req, HTTPResponse *res)
JSONValue *value = new JSONValue(jsonObjOuter);
- res->print(value->Stringify().c_str());
+ std::string jsonString = value->Stringify();
+ res->print(jsonString.c_str());
delete value;
+
+ // Clean up the fileList to prevent memory leak
+ for (auto *val : fileList) {
+ delete val;
+ }
}
void handleFsDeleteStatic(HTTPRequest *req, HTTPResponse *res)
@@ -362,7 +373,8 @@ void handleFsDeleteStatic(HTTPRequest *req, HTTPResponse *res)
JSONObject jsonObjOuter;
jsonObjOuter["status"] = new JSONValue("ok");
JSONValue *value = new JSONValue(jsonObjOuter);
- res->print(value->Stringify().c_str());
+ std::string jsonString = value->Stringify();
+ res->print(jsonString.c_str());
delete value;
return;
} else {
@@ -371,7 +383,8 @@ void handleFsDeleteStatic(HTTPRequest *req, HTTPResponse *res)
JSONObject jsonObjOuter;
jsonObjOuter["status"] = new JSONValue("Error");
JSONValue *value = new JSONValue(jsonObjOuter);
- res->print(value->Stringify().c_str());
+ std::string jsonString = value->Stringify();
+ res->print(jsonString.c_str());
delete value;
return;
}
@@ -380,6 +393,9 @@ void handleFsDeleteStatic(HTTPRequest *req, HTTPResponse *res)
void handleStatic(HTTPRequest *req, HTTPResponse *res)
{
+ if (webServerThread)
+ webServerThread->markActivity();
+
// Get access to the parameters
ResourceParameters *params = req->getParams();
@@ -610,33 +626,35 @@ void handleReport(HTTPRequest *req, HTTPResponse *res)
res->println("");
}
+ // Helper lambda to create JSON array and clean up memory properly
+ auto createJSONArrayFromLog = [](uint32_t *logArray, int count) -> JSONValue * {
+ JSONArray tempArray;
+ for (int i = 0; i < count; i++) {
+ tempArray.push_back(new JSONValue((int)logArray[i]));
+ }
+ JSONValue *result = new JSONValue(tempArray);
+ // Note: Don't delete tempArray elements here - JSONValue now owns them
+ return result;
+ };
+
// data->airtime->tx_log
- JSONArray txLogValues;
uint32_t *logArray;
logArray = airTime->airtimeReport(TX_LOG);
- for (int i = 0; i < airTime->getPeriodsToLog(); i++) {
- txLogValues.push_back(new JSONValue((int)logArray[i]));
- }
+ JSONValue *txLogJsonValue = createJSONArrayFromLog(logArray, airTime->getPeriodsToLog());
// data->airtime->rx_log
- JSONArray rxLogValues;
logArray = airTime->airtimeReport(RX_LOG);
- for (int i = 0; i < airTime->getPeriodsToLog(); i++) {
- rxLogValues.push_back(new JSONValue((int)logArray[i]));
- }
+ JSONValue *rxLogJsonValue = createJSONArrayFromLog(logArray, airTime->getPeriodsToLog());
// data->airtime->rx_all_log
- JSONArray rxAllLogValues;
logArray = airTime->airtimeReport(RX_ALL_LOG);
- for (int i = 0; i < airTime->getPeriodsToLog(); i++) {
- rxAllLogValues.push_back(new JSONValue((int)logArray[i]));
- }
+ JSONValue *rxAllLogJsonValue = createJSONArrayFromLog(logArray, airTime->getPeriodsToLog());
// data->airtime
JSONObject jsonObjAirtime;
- jsonObjAirtime["tx_log"] = new JSONValue(txLogValues);
- jsonObjAirtime["rx_log"] = new JSONValue(rxLogValues);
- jsonObjAirtime["rx_all_log"] = new JSONValue(rxAllLogValues);
+ jsonObjAirtime["tx_log"] = txLogJsonValue;
+ jsonObjAirtime["rx_log"] = rxLogJsonValue;
+ jsonObjAirtime["rx_all_log"] = rxAllLogJsonValue;
jsonObjAirtime["channel_utilization"] = new JSONValue(airTime->channelUtilizationPercent());
jsonObjAirtime["utilization_tx"] = new JSONValue(airTime->utilizationTXPercent());
jsonObjAirtime["seconds_since_boot"] = new JSONValue(int(airTime->getSecondsSinceBoot()));
@@ -646,7 +664,9 @@ void handleReport(HTTPRequest *req, HTTPResponse *res)
// data->wifi
JSONObject jsonObjWifi;
jsonObjWifi["rssi"] = new JSONValue(WiFi.RSSI());
- jsonObjWifi["ip"] = new JSONValue(WiFi.localIP().toString().c_str());
+ String wifiIPString = WiFi.localIP().toString();
+ std::string wifiIP = wifiIPString.c_str();
+ jsonObjWifi["ip"] = new JSONValue(wifiIP.c_str());
// data->memory
JSONObject jsonObjMemory;
@@ -692,7 +712,8 @@ void handleReport(HTTPRequest *req, HTTPResponse *res)
jsonObjOuter["status"] = new JSONValue("ok");
// serialize and write it to the stream
JSONValue *value = new JSONValue(jsonObjOuter);
- res->print(value->Stringify().c_str());
+ std::string jsonString = value->Stringify();
+ res->print(jsonString.c_str());
delete value;
}
@@ -763,8 +784,14 @@ void handleNodes(HTTPRequest *req, HTTPResponse *res)
jsonObjOuter["status"] = new JSONValue("ok");
// serialize and write it to the stream
JSONValue *value = new JSONValue(jsonObjOuter);
- res->print(value->Stringify().c_str());
+ std::string jsonString = value->Stringify();
+ res->print(jsonString.c_str());
delete value;
+
+ // Clean up the nodesArray to prevent memory leak
+ for (auto *val : nodesArray) {
+ delete val;
+ }
}
/*
@@ -911,7 +938,8 @@ void handleBlinkLED(HTTPRequest *req, HTTPResponse *res)
JSONObject jsonObjOuter;
jsonObjOuter["status"] = new JSONValue("ok");
JSONValue *value = new JSONValue(jsonObjOuter);
- res->print(value->Stringify().c_str());
+ std::string jsonString = value->Stringify();
+ res->print(jsonString.c_str());
delete value;
}
@@ -953,7 +981,13 @@ void handleScanNetworks(HTTPRequest *req, HTTPResponse *res)
// serialize and write it to the stream
JSONValue *value = new JSONValue(jsonObjOuter);
- res->print(value->Stringify().c_str());
+ std::string jsonString = value->Stringify();
+ res->print(jsonString.c_str());
delete value;
+
+ // Clean up the networkObjs to prevent memory leak
+ for (auto *val : networkObjs) {
+ delete val;
+ }
}
#endif
\ No newline at end of file
diff --git a/src/mesh/http/ContentHandler.h b/src/mesh/http/ContentHandler.h
index 2066a6d57..91cad3359 100644
--- a/src/mesh/http/ContentHandler.h
+++ b/src/mesh/http/ContentHandler.h
@@ -26,7 +26,7 @@ class HttpAPI : public PhoneAPI
{
public:
- // Nothing here yet
+ HttpAPI() { api_type = TYPE_HTTP; }
private:
// Nothing here yet
diff --git a/src/mesh/http/WebServer.cpp b/src/mesh/http/WebServer.cpp
index bf170de59..3a264fa5a 100644
--- a/src/mesh/http/WebServer.cpp
+++ b/src/mesh/http/WebServer.cpp
@@ -49,6 +49,12 @@ Preferences prefs;
using namespace httpsserver;
#include "mesh/http/ContentHandler.h"
+static const uint32_t ACTIVE_THRESHOLD_MS = 5000;
+static const uint32_t MEDIUM_THRESHOLD_MS = 30000;
+static const int32_t ACTIVE_INTERVAL_MS = 50;
+static const int32_t MEDIUM_INTERVAL_MS = 200;
+static const int32_t IDLE_INTERVAL_MS = 1000;
+
static SSLCert *cert;
static HTTPSServer *secureServer;
static HTTPServer *insecureServer;
@@ -175,6 +181,32 @@ WebServerThread::WebServerThread() : concurrency::OSThread("WebServer")
if (!config.network.wifi_enabled && !config.network.eth_enabled) {
disable();
}
+ lastActivityTime = millis();
+}
+
+void WebServerThread::markActivity()
+{
+ lastActivityTime = millis();
+}
+
+int32_t WebServerThread::getAdaptiveInterval()
+{
+ uint32_t currentTime = millis();
+ uint32_t timeSinceActivity;
+
+ if (currentTime >= lastActivityTime) {
+ timeSinceActivity = currentTime - lastActivityTime;
+ } else {
+ timeSinceActivity = (UINT32_MAX - lastActivityTime) + currentTime + 1;
+ }
+
+ if (timeSinceActivity < ACTIVE_THRESHOLD_MS) {
+ return ACTIVE_INTERVAL_MS;
+ } else if (timeSinceActivity < MEDIUM_THRESHOLD_MS) {
+ return MEDIUM_INTERVAL_MS;
+ } else {
+ return IDLE_INTERVAL_MS;
+ }
}
int32_t WebServerThread::runOnce()
@@ -189,8 +221,7 @@ int32_t WebServerThread::runOnce()
ESP.restart();
}
- // Loop every 5ms.
- return (5);
+ return getAdaptiveInterval();
}
void initWebServer()
diff --git a/src/mesh/http/WebServer.h b/src/mesh/http/WebServer.h
index 815d87432..e7a29a5a7 100644
--- a/src/mesh/http/WebServer.h
+++ b/src/mesh/http/WebServer.h
@@ -10,13 +10,17 @@ void createSSLCert();
class WebServerThread : private concurrency::OSThread
{
+ private:
+ uint32_t lastActivityTime = 0;
public:
WebServerThread();
uint32_t requestRestart = 0;
+ void markActivity();
protected:
virtual int32_t runOnce() override;
+ int32_t getAdaptiveInterval();
};
extern WebServerThread *webServerThread;
diff --git a/src/mesh/mesh-pb-constants.h b/src/mesh/mesh-pb-constants.h
index 08c03dc6b..e4f65aa28 100644
--- a/src/mesh/mesh-pb-constants.h
+++ b/src/mesh/mesh-pb-constants.h
@@ -15,8 +15,27 @@
// FIXME - max_count is actually 32 but we save/load this as one long string of preencoded MeshPacket bytes - not a big array in
// RAM #define MAX_RX_TOPHONE (member_size(DeviceState, receive_queue) / member_size(DeviceState, receive_queue[0]))
#ifndef MAX_RX_TOPHONE
+#if defined(ARCH_ESP32) && !(defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S3))
+#define MAX_RX_TOPHONE 8
+#else
#define MAX_RX_TOPHONE 32
#endif
+#endif
+
+/// max number of QueueStatus packets which can be waiting for delivery to phone
+#ifndef MAX_RX_QUEUESTATUS_TOPHONE
+#define MAX_RX_QUEUESTATUS_TOPHONE 2
+#endif
+
+/// max number of MqttClientProxyMessage packets which can be waiting for delivery to phone
+#ifndef MAX_RX_MQTTPROXY_TOPHONE
+#define MAX_RX_MQTTPROXY_TOPHONE 8
+#endif
+
+/// max number of ClientNotification packets which can be waiting for delivery to phone
+#ifndef MAX_RX_NOTIFICATION_TOPHONE
+#define MAX_RX_NOTIFICATION_TOPHONE 2
+#endif
/// Verify baseline assumption of node size. If it increases, we need to reevaluate
/// the impact of its memory footprint, notably on MAX_NUM_NODES.
diff --git a/src/mesh/raspihttp/PiWebServer.cpp b/src/mesh/raspihttp/PiWebServer.cpp
index 7d3542e83..3e9dbe8c2 100644
--- a/src/mesh/raspihttp/PiWebServer.cpp
+++ b/src/mesh/raspihttp/PiWebServer.cpp
@@ -65,8 +65,8 @@ mail: marchammermann@googlemail.com
#define DEFAULT_REALM "default_realm"
#define PREFIX ""
-#define KEY_PATH settingsStrings[websslkeypath].c_str()
-#define CERT_PATH settingsStrings[websslcertpath].c_str()
+#define KEY_PATH portduino_config.webserver_ssl_key_path.c_str()
+#define CERT_PATH portduino_config.webserver_ssl_cert_path.c_str()
struct _file_config configWeb;
@@ -458,8 +458,8 @@ PiWebServerThread::PiWebServerThread()
}
}
- if (settingsMap[webserverport] != 0) {
- webservport = settingsMap[webserverport];
+ if (portduino_config.webserverport != 0) {
+ webservport = portduino_config.webserverport;
LOG_INFO("Use webserver port from yaml config %i ", webservport);
} else {
LOG_INFO("Webserver port in yaml config set to 0, defaulting to port 9443");
@@ -490,7 +490,7 @@ PiWebServerThread::PiWebServerThread()
u_map_put(&configWeb.mime_types, ".ico", "image/x-icon");
u_map_put(&configWeb.mime_types, ".svg", "image/svg+xml");
- webrootpath = settingsStrings[webserverrootpath];
+ webrootpath = portduino_config.webserver_root_path;
configWeb.files_path = (char *)webrootpath.c_str();
configWeb.url_prefix = "";
diff --git a/src/mesh/raspihttp/PiWebServer.h b/src/mesh/raspihttp/PiWebServer.h
index b45348cf3..5a4adedaa 100644
--- a/src/mesh/raspihttp/PiWebServer.h
+++ b/src/mesh/raspihttp/PiWebServer.h
@@ -27,7 +27,7 @@ class HttpAPI : public PhoneAPI
{
public:
- // Nothing here yet
+ HttpAPI() { api_type = TYPE_HTTP; }
private:
// Nothing here yet
diff --git a/src/mesh/udp/UdpMulticastHandler.h b/src/mesh/udp/UdpMulticastHandler.h
index 9650668a8..2df8686a3 100644
--- a/src/mesh/udp/UdpMulticastHandler.h
+++ b/src/mesh/udp/UdpMulticastHandler.h
@@ -50,10 +50,10 @@ class UdpMulticastHandler final
LOG_DEBUG("UDP broadcast from: %s, len=%u", packet.remoteIP().toString().c_str(), packetLength);
#endif
meshtastic_MeshPacket mp;
- mp.transport_mechanism = meshtastic_MeshPacket_TransportMechanism_TRANSPORT_MULTICAST_UDP;
LOG_DEBUG("Decoding MeshPacket from UDP len=%u", packetLength);
bool isPacketDecoded = pb_decode_from_bytes(packet.data(), packetLength, &meshtastic_MeshPacket_msg, &mp);
if (isPacketDecoded && router && mp.which_payload_variant == meshtastic_MeshPacket_encrypted_tag) {
+ mp.transport_mechanism = meshtastic_MeshPacket_TransportMechanism_TRANSPORT_MULTICAST_UDP;
mp.pki_encrypted = false;
mp.public_key.size = 0;
memset(mp.public_key.bytes, 0, sizeof(mp.public_key.bytes));
diff --git a/src/mesh/wifi/WiFiAPClient.cpp b/src/mesh/wifi/WiFiAPClient.cpp
index 1133ad424..45944872e 100644
--- a/src/mesh/wifi/WiFiAPClient.cpp
+++ b/src/mesh/wifi/WiFiAPClient.cpp
@@ -94,11 +94,13 @@ static void onNetworkConnected()
// ESPmDNS (ESP32) and SimpleMDNS (RP2040) have slightly different APIs for adding TXT records
#ifdef ARCH_ESP32
MDNS.addServiceTxt("meshtastic", "tcp", "shortname", String(owner.short_name));
- MDNS.addServiceTxt("meshtastic", "tcp", "id", String(owner.id));
+ MDNS.addServiceTxt("meshtastic", "tcp", "id", String(nodeDB->getNodeId().c_str()));
+ MDNS.addServiceTxt("meshtastic", "tcp", "pio_env", optstr(APP_ENV));
// ESP32 prints obtained IP address in WiFiEvent
#elif defined(ARCH_RP2040)
MDNS.addServiceTxt("meshtastic", "shortname", owner.short_name);
- MDNS.addServiceTxt("meshtastic", "id", owner.id);
+ MDNS.addServiceTxt("meshtastic", "id", nodeDB->getNodeId().c_str());
+ MDNS.addServiceTxt("meshtastic", "pio_env", optstr(APP_ENV));
LOG_INFO("Obtained IP address: %s", WiFi.localIP().toString().c_str());
#endif
}
@@ -332,6 +334,23 @@ bool initWifi()
}
#ifdef ARCH_ESP32
+#if ESP_ARDUINO_VERSION <= ESP_ARDUINO_VERSION_VAL(3, 0, 0)
+// Most of the next 12 lines of code are adapted from espressif/arduino-esp32
+// Licensed under the GNU Lesser General Public License v2.1
+// https://github.com/espressif/arduino-esp32/blob/1f038677eb2eaf5e9ca6b6074486803c15468bed/libraries/WiFi/src/WiFiSTA.cpp#L755
+esp_netif_t *get_esp_interface_netif(esp_interface_t interface);
+IPv6Address GlobalIPv6()
+{
+ esp_ip6_addr_t addr;
+ if (WiFiGenericClass::getMode() == WIFI_MODE_NULL) {
+ return IPv6Address();
+ }
+ if (esp_netif_get_ip6_global(get_esp_interface_netif(ESP_IF_WIFI_STA), &addr)) {
+ return IPv6Address();
+ }
+ return IPv6Address(addr.addr);
+}
+#endif
// Called by the Espressif SDK to
static void WiFiEvent(WiFiEvent_t event)
{
@@ -353,6 +372,17 @@ static void WiFiEvent(WiFiEvent_t event)
break;
case ARDUINO_EVENT_WIFI_STA_CONNECTED:
LOG_INFO("Connected to access point");
+ if (config.network.ipv6_enabled) {
+#if ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(3, 0, 0)
+ if (!WiFi.enableIPv6()) {
+ LOG_WARN("Failed to enable IPv6");
+ }
+#else
+ if (!WiFi.enableIpV6()) {
+ LOG_WARN("Failed to enable IPv6");
+ }
+#endif
+ }
#ifdef WIFI_LED
digitalWrite(WIFI_LED, HIGH);
#endif
@@ -381,7 +411,8 @@ static void WiFiEvent(WiFiEvent_t event)
LOG_INFO("Obtained Local IP6 address: %s", WiFi.linkLocalIPv6().toString().c_str());
LOG_INFO("Obtained GlobalIP6 address: %s", WiFi.globalIPv6().toString().c_str());
#else
- LOG_INFO("Obtained IP6 address: %s", WiFi.localIPv6().toString().c_str());
+ LOG_INFO("Obtained Local IP6 address: %s", WiFi.localIPv6().toString().c_str());
+ LOG_INFO("Obtained GlobalIP6 address: %s", GlobalIPv6().toString().c_str());
#endif
break;
case ARDUINO_EVENT_WIFI_STA_LOST_IP:
@@ -512,4 +543,4 @@ uint8_t getWifiDisconnectReason()
{
return wifiDisconnectReason;
}
-#endif // HAS_WIFI
\ No newline at end of file
+#endif // HAS_WIFI
diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp
index d647b3ecd..7e978ac8c 100644
--- a/src/modules/AdminModule.cpp
+++ b/src/modules/AdminModule.cpp
@@ -104,6 +104,19 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta
(config.security.admin_key[2].size == 32 &&
memcmp(mp.public_key.bytes, config.security.admin_key[2].bytes, 32) == 0)) {
LOG_INFO("PKC admin payload with authorized sender key");
+
+ // Automatically favorite the node that is using the admin key
+ auto remoteNode = nodeDB->getMeshNode(mp.from);
+ if (remoteNode && !remoteNode->is_favorite) {
+ if (config.device.role == meshtastic_Config_DeviceConfig_Role_CLIENT_BASE) {
+ // Special case for CLIENT_BASE: is_favorite has special meaning, and we don't want to automatically set it
+ // without the user doing so deliberately.
+ LOG_INFO("PKC admin valid, but not auto-favoriting node %x because role==CLIENT_BASE", mp.from);
+ } else {
+ LOG_INFO("PKC admin valid. Auto-favoriting node %x", mp.from);
+ remoteNode->is_favorite = true;
+ }
+ }
} else {
myReply = allocErrorResponse(meshtastic_Routing_Error_ADMIN_PUBLIC_KEY_UNAUTHORIZED, &mp);
LOG_INFO("Received PKC admin payload, but the sender public key does not match the admin authorized key!");
@@ -276,7 +289,12 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta
case meshtastic_AdminMessage_nodedb_reset_tag: {
disableBluetooth();
LOG_INFO("Initiate node-db reset");
- nodeDB->resetNodes();
+ // CLIENT_BASE, ROUTER and ROUTER_LATE are able to preserve the remaining hop count when relaying a packet via a
+ // favorited node, so ensure that their favorites are kept on reset
+ bool rolePreference =
+ isOneOf(config.device.role, meshtastic_Config_DeviceConfig_Role_CLIENT_BASE,
+ meshtastic_Config_DeviceConfig_Role_ROUTER, meshtastic_Config_DeviceConfig_Role_ROUTER_LATE);
+ nodeDB->resetNodes(rolePreference ? rolePreference : r->nodedb_reset);
reboot(DEFAULT_REBOOT_SECONDS);
break;
}
@@ -505,7 +523,7 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta
if (mp.decoded.want_response && !myReply) {
myReply = allocErrorResponse(meshtastic_Routing_Error_NONE, &mp);
}
- if (mp.pki_encrypted) {
+ if (mp.pki_encrypted && myReply) {
myReply->pki_encrypted = true;
}
return handled;
@@ -550,10 +568,8 @@ void AdminModule::handleSetOwner(const meshtastic_User &o)
changed |= strcmp(owner.short_name, o.short_name);
strncpy(owner.short_name, o.short_name, sizeof(owner.short_name));
}
- if (*o.id) {
- changed |= strcmp(owner.id, o.id);
- strncpy(owner.id, o.id, sizeof(owner.id));
- }
+ snprintf(owner.id, sizeof(owner.id), "!%08x", nodeDB->getNodeNum());
+
if (owner.is_licensed != o.is_licensed) {
changed = 1;
owner.is_licensed = o.is_licensed;
@@ -607,10 +623,9 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c)
}
config.device = c.payload_variant.device;
if (config.device.rebroadcast_mode == meshtastic_Config_DeviceConfig_RebroadcastMode_NONE &&
- IS_ONE_OF(config.device.role, meshtastic_Config_DeviceConfig_Role_ROUTER,
- meshtastic_Config_DeviceConfig_Role_REPEATER)) {
+ config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER) {
config.device.rebroadcast_mode = meshtastic_Config_DeviceConfig_RebroadcastMode_ALL;
- const char *warning = "Rebroadcast mode can't be set to NONE for a router or repeater";
+ const char *warning = "Rebroadcast mode can't be set to NONE for a router";
LOG_WARN(warning);
sendWarning(warning);
}
@@ -623,8 +638,9 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c)
LOG_DEBUG("Tried to set node_info_broadcast_secs too low, setting to %d", min_node_info_broadcast_secs);
config.device.node_info_broadcast_secs = min_node_info_broadcast_secs;
}
- // Router Client is deprecated; Set it to client
- if (c.payload_variant.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_CLIENT) {
+ // Router Client and Repeater deprecated; Set it to client
+ if (IS_ONE_OF(c.payload_variant.device.role, meshtastic_Config_DeviceConfig_Role_ROUTER_CLIENT,
+ meshtastic_Config_DeviceConfig_Role_REPEATER)) {
config.device.role = meshtastic_Config_DeviceConfig_Role_CLIENT;
if (moduleConfig.store_forward.enabled && !moduleConfig.store_forward.is_server) {
moduleConfig.store_forward.is_server = true;
@@ -633,10 +649,9 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c)
}
}
#if USERPREFS_EVENT_MODE
- // If we're in event mode, nobody is a Router or Repeater
+ // If we're in event mode, nobody is a Router or Router Late
if (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER ||
- config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_LATE ||
- config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER) {
+ config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_LATE) {
config.device.role = meshtastic_Config_DeviceConfig_Role_CLIENT;
}
#endif
@@ -703,20 +718,40 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c)
#endif
config.display = c.payload_variant.display;
break;
- case meshtastic_Config_lora_tag:
+
+ case meshtastic_Config_lora_tag: {
+ // Wrap the entire case in a block to scope variables and avoid crossing initialization
+ auto oldLoraConfig = config.lora;
+ auto validatedLora = c.payload_variant.lora;
+
LOG_INFO("Set config: LoRa");
config.has_lora = true;
+
+ if (validatedLora.coding_rate < 4 || validatedLora.coding_rate > 8) {
+ LOG_WARN("Invalid coding_rate %d, setting to 5", validatedLora.coding_rate);
+ validatedLora.coding_rate = 5;
+ }
+
+ if (validatedLora.spread_factor < 7 || validatedLora.spread_factor > 12) {
+ LOG_WARN("Invalid spread_factor %d, setting to 11", validatedLora.spread_factor);
+ validatedLora.spread_factor = 11;
+ }
+
+ if (validatedLora.bandwidth == 0) {
+ int originalBandwidth = validatedLora.bandwidth;
+ validatedLora.bandwidth = myRegion->wideLora ? 800 : 250;
+ LOG_WARN("Invalid bandwidth %d, setting to default", originalBandwidth);
+ }
+
// If no lora radio parameters change, don't need to reboot
- if (config.lora.use_preset == c.payload_variant.lora.use_preset && config.lora.region == c.payload_variant.lora.region &&
- config.lora.modem_preset == c.payload_variant.lora.modem_preset &&
- config.lora.bandwidth == c.payload_variant.lora.bandwidth &&
- config.lora.spread_factor == c.payload_variant.lora.spread_factor &&
- config.lora.coding_rate == c.payload_variant.lora.coding_rate &&
- config.lora.tx_power == c.payload_variant.lora.tx_power &&
- config.lora.frequency_offset == c.payload_variant.lora.frequency_offset &&
- config.lora.override_frequency == c.payload_variant.lora.override_frequency &&
- config.lora.channel_num == c.payload_variant.lora.channel_num &&
- config.lora.sx126x_rx_boosted_gain == c.payload_variant.lora.sx126x_rx_boosted_gain) {
+ if (oldLoraConfig.use_preset == validatedLora.use_preset && oldLoraConfig.region == validatedLora.region &&
+ oldLoraConfig.modem_preset == validatedLora.modem_preset && oldLoraConfig.bandwidth == validatedLora.bandwidth &&
+ oldLoraConfig.spread_factor == validatedLora.spread_factor &&
+ oldLoraConfig.coding_rate == validatedLora.coding_rate && oldLoraConfig.tx_power == validatedLora.tx_power &&
+ oldLoraConfig.frequency_offset == validatedLora.frequency_offset &&
+ oldLoraConfig.override_frequency == validatedLora.override_frequency &&
+ oldLoraConfig.channel_num == validatedLora.channel_num &&
+ oldLoraConfig.sx126x_rx_boosted_gain == validatedLora.sx126x_rx_boosted_gain) {
requiresReboot = false;
}
@@ -735,7 +770,7 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c)
digitalWrite(RF95_FAN_EN, HIGH ^ 0);
}
#endif
- config.lora = c.payload_variant.lora;
+ config.lora = validatedLora;
// If we're setting region for the first time, init the region and regenerate the keys
if (isRegionUnset && config.lora.region > meshtastic_Config_LoRaConfig_RegionCode_UNSET) {
// Use consolidated key generation function
@@ -745,12 +780,26 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c)
if (myRegion->dutyCycle < 100) {
config.lora.ignore_mqtt = true; // Ignore MQTT by default if region has a duty cycle limit
}
+ // Compare the entire string, we are sure of the length as a topic has never been set
if (strcmp(moduleConfig.mqtt.root, default_mqtt_root) == 0) {
sprintf(moduleConfig.mqtt.root, "%s/%s", default_mqtt_root, myRegion->name);
changes = SEGMENT_CONFIG | SEGMENT_MODULECONFIG | SEGMENT_NODEDATABASE | SEGMENT_DEVICESTATE;
}
}
+ if (config.lora.region != myRegion->code) {
+ // Region has changed so check whether there is a regulatory one we should be using instead.
+ // Additionally as a side-effect, assume a new value under myRegion
+ initRegion();
+
+ if (strncmp(moduleConfig.mqtt.root, default_mqtt_root, strlen(default_mqtt_root)) == 0) {
+ // Default root is in use, so subscribe to the appropriate MQTT topic for this region
+ sprintf(moduleConfig.mqtt.root, "%s/%s", default_mqtt_root, myRegion->name);
+ }
+
+ changes = SEGMENT_CONFIG | SEGMENT_MODULECONFIG;
+ }
break;
+ }
case meshtastic_Config_bluetooth_tag:
LOG_INFO("Set config: Bluetooth");
config.has_bluetooth = true;
diff --git a/src/modules/CannedMessageModule.cpp b/src/modules/CannedMessageModule.cpp
index 76b950322..9433c0a9e 100644
--- a/src/modules/CannedMessageModule.cpp
+++ b/src/modules/CannedMessageModule.cpp
@@ -16,6 +16,7 @@
#include "graphics/draw/NotificationRenderer.h"
#include "graphics/emotes.h"
#include "graphics/images.h"
+#include "input/SerialKeyboard.h"
#include "main.h" // for cardkb_found
#include "mesh/generated/meshtastic/cannedmessages.pb.h"
#include "modules/AdminModule.h"
@@ -255,7 +256,7 @@ void CannedMessageModule::updateDestinationSelectionList()
for (size_t i = 0; i < numMeshNodes; ++i) {
meshtastic_NodeInfoLite *node = nodeDB->getMeshNodeByIndex(i);
- if (!node || node->num == myNodeNum)
+ if (!node || node->num == myNodeNum || !node->has_user || node->user.public_key.size != 32)
continue;
const String &nodeName = node->user.long_name;
@@ -404,14 +405,14 @@ bool CannedMessageModule::isUpEvent(const InputEvent *event)
return event->inputEvent == INPUT_BROKER_UP ||
((runState == CANNED_MESSAGE_RUN_STATE_ACTIVE || runState == CANNED_MESSAGE_RUN_STATE_EMOTE_PICKER ||
runState == CANNED_MESSAGE_RUN_STATE_DESTINATION_SELECTION) &&
- event->inputEvent == INPUT_BROKER_ALT_PRESS);
+ (event->inputEvent == INPUT_BROKER_LEFT || event->inputEvent == INPUT_BROKER_ALT_PRESS));
}
bool CannedMessageModule::isDownEvent(const InputEvent *event)
{
return event->inputEvent == INPUT_BROKER_DOWN ||
((runState == CANNED_MESSAGE_RUN_STATE_ACTIVE || runState == CANNED_MESSAGE_RUN_STATE_EMOTE_PICKER ||
runState == CANNED_MESSAGE_RUN_STATE_DESTINATION_SELECTION) &&
- event->inputEvent == INPUT_BROKER_USER_PRESS);
+ (event->inputEvent == INPUT_BROKER_RIGHT || event->inputEvent == INPUT_BROKER_USER_PRESS));
}
bool CannedMessageModule::isSelectEvent(const InputEvent *event)
{
@@ -692,10 +693,10 @@ bool CannedMessageModule::handleMessageSelectorInput(const InputEvent *event, bo
// Normal canned message selection
if (runState == CANNED_MESSAGE_RUN_STATE_INACTIVE || runState == CANNED_MESSAGE_RUN_STATE_DISABLED) {
} else {
+#if CANNED_MESSAGE_ADD_CONFIRMATION
// Show confirmation dialog before sending canned message
NodeNum destNode = dest;
ChannelIndex chan = channel;
-#if CANNED_MESSAGE_ADD_CONFIRMATION
graphics::menuHandler::showConfirmationBanner("Send message?", [this, destNode, chan, current]() {
this->sendText(destNode, chan, current, false);
payload = runState;
@@ -836,6 +837,7 @@ bool CannedMessageModule::handleFreeTextInput(const InputEvent *event)
if (event->inputEvent == INPUT_BROKER_BACK && this->freetext.length() > 0) {
payload = 0x08;
lastTouchMillis = millis();
+ requestFocus();
runOnce();
return true;
}
@@ -844,6 +846,7 @@ bool CannedMessageModule::handleFreeTextInput(const InputEvent *event)
if (event->inputEvent == INPUT_BROKER_LEFT) {
payload = INPUT_BROKER_LEFT;
lastTouchMillis = millis();
+ requestFocus();
runOnce();
return true;
}
@@ -851,6 +854,7 @@ bool CannedMessageModule::handleFreeTextInput(const InputEvent *event)
if (event->inputEvent == INPUT_BROKER_RIGHT) {
payload = INPUT_BROKER_RIGHT;
lastTouchMillis = millis();
+ requestFocus();
runOnce();
return true;
}
@@ -973,9 +977,17 @@ void CannedMessageModule::sendText(NodeNum dest, ChannelIndex channel, const cha
LOG_INFO("Send message id=%u, dest=%x, msg=%.*s", p->id, p->to, p->decoded.payload.size, p->decoded.payload.bytes);
if (p->to != 0xffffffff) {
- LOG_INFO("Proactively adding %x as favorite node", p->to);
- nodeDB->set_favorite(true, p->to);
+ // Only add as favorite if our role is NOT CLIENT_BASE
+ if (config.device.role != 12) {
+ LOG_INFO("Proactively adding %x as favorite node", p->to);
+ nodeDB->set_favorite(true, p->to);
+ } else {
+ LOG_DEBUG("Not favoriting node %x as we are CLIENT_BASE role", p->to);
+ }
+
screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
+ p->pki_encrypted = true;
+ p->channel = 0;
}
// Send to mesh and phone (even if no phone connected, to track ACKs)
@@ -1119,7 +1131,6 @@ int32_t CannedMessageModule::runOnce()
this->runState = CANNED_MESSAGE_RUN_STATE_INACTIVE;
}
}
- e.action = UIFrameEvent::Action::REGENERATE_FRAMESET;
this->currentMessageIndex = -1;
this->freetext = "";
this->cursor = 0;
@@ -1561,10 +1572,17 @@ void CannedMessageModule::drawDestinationSelectionScreen(OLEDDisplay *display, O
meshtastic_NodeInfoLite *node = this->filteredNodes[nodeIndex].node;
if (node) {
if (node->is_favorite) {
+#if defined(M5STACK_UNITC6L)
+ snprintf(entryText, sizeof(entryText), "* %s", node->user.short_name);
+ } else {
+ snprintf(entryText, sizeof(entryText), "%s", node->user.short_name);
+ }
+#else
snprintf(entryText, sizeof(entryText), "* %s", node->user.long_name);
} else {
snprintf(entryText, sizeof(entryText), "%s", node->user.long_name);
}
+#endif
}
}
}
@@ -1735,7 +1753,11 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st
int yOffset = y + 10;
#else
display->setFont(FONT_MEDIUM);
+#if defined(M5STACK_UNITC6L)
+ int yOffset = y;
+#else
int yOffset = y + 10;
+#endif
#endif
// --- Delivery Status Message ---
@@ -1760,13 +1782,20 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st
}
display->drawString(display->getWidth() / 2 + x, yOffset, buffer);
+#if defined(M5STACK_UNITC6L)
+ yOffset += lineCount * FONT_HEIGHT_MEDIUM - 5; // only 1 line gap, no extra padding
+#else
yOffset += lineCount * FONT_HEIGHT_MEDIUM; // only 1 line gap, no extra padding
-
+#endif
#ifndef USE_EINK
// --- SNR + RSSI Compact Line ---
if (this->ack) {
display->setFont(FONT_SMALL);
+#if defined(M5STACK_UNITC6L)
+ snprintf(buffer, sizeof(buffer), "SNR: %.1f dB \nRSSI: %d", this->lastRxSnr, this->lastRxRssi);
+#else
snprintf(buffer, sizeof(buffer), "SNR: %.1f dB RSSI: %d", this->lastRxSnr, this->lastRxRssi);
+#endif
display->drawString(display->getWidth() / 2 + x, yOffset, buffer);
}
#endif
@@ -1820,7 +1849,88 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st
display->drawString(x + display->getWidth() - display->getStringWidth(buffer), y + 0, buffer);
}
- // --- Draw Free Text input with multi-emote support and proper line wrapping ---
+#if INPUTBROKER_SERIAL_TYPE == 1
+ // Chatter Modifier key mode label (right side)
+ {
+ uint8_t mode = globalSerialKeyboard ? globalSerialKeyboard->getShift() : 0;
+ const char *label = (mode == 0) ? "a" : (mode == 1) ? "A" : "#";
+
+ display->setFont(FONT_SMALL);
+ display->setTextAlignment(TEXT_ALIGN_LEFT);
+
+ const int16_t th = FONT_HEIGHT_SMALL;
+ const int16_t tw = display->getStringWidth(label);
+ const int16_t padX = 3;
+ const int16_t padY = 2;
+ const int16_t r = 3;
+
+ const int16_t bw = tw + padX * 2;
+ const int16_t bh = th + padY * 2;
+
+ const int16_t bx = x + display->getWidth() - bw - 2;
+ const int16_t by = y + display->getHeight() - bh - 2;
+
+ display->setColor(WHITE);
+ display->fillRect(bx + r, by, bw - r * 2, bh);
+ display->fillRect(bx, by + r, r, bh - r * 2);
+ display->fillRect(bx + bw - r, by + r, r, bh - r * 2);
+ display->fillCircle(bx + r, by + r, r);
+ display->fillCircle(bx + bw - r - 1, by + r, r);
+ display->fillCircle(bx + r, by + bh - r - 1, r);
+ display->fillCircle(bx + bw - r - 1, by + bh - r - 1, r);
+
+ display->setColor(BLACK);
+ display->drawString(bx + padX, by + padY, label);
+ }
+
+ // LEFT-SIDE DESTINATION-HINT BOX (“Dest: Shift + ◄”)
+ {
+ display->setFont(FONT_SMALL);
+ display->setTextAlignment(TEXT_ALIGN_LEFT);
+
+ const char *label = "Dest: Shift + ";
+ int16_t labelW = display->getStringWidth(label);
+
+ // triangle size visually matches glyph height, not full line height
+ const int triH = FONT_HEIGHT_SMALL - 3;
+ const int triW = triH * 0.7;
+
+ const int16_t padX = 3;
+ const int16_t padY = 2;
+ const int16_t r = 3;
+
+ const int16_t bw = labelW + triW + padX * 2 + 2;
+ const int16_t bh = FONT_HEIGHT_SMALL + padY * 2;
+
+ const int16_t bx = x + 2;
+ const int16_t by = y + display->getHeight() - bh - 2;
+
+ // Rounded white box
+ display->setColor(WHITE);
+ display->fillRect(bx + r, by, bw - (r * 2), bh);
+ display->fillRect(bx, by + r, r, bh - (r * 2));
+ display->fillRect(bx + bw - r, by + r, r, bh - (r * 2));
+ display->fillCircle(bx + r, by + r, r);
+ display->fillCircle(bx + bw - r - 1, by + r, r);
+ display->fillCircle(bx + r, by + bh - r - 1, r);
+ display->fillCircle(bx + bw - r - 1, by + bh - r - 1, r);
+
+ // Draw text
+ display->setColor(BLACK);
+ display->drawString(bx + padX, by + padY, label);
+
+ // Perfectly center triangle on text baseline
+ int16_t tx = bx + padX + labelW;
+ int16_t ty = by + padY + (FONT_HEIGHT_SMALL / 2) - (triH / 2) - 1; // -1 for optical centering
+
+ // ◄ Left-pointing triangle
+ display->fillTriangle(tx + triW, ty, // top-right
+ tx, ty + triH / 2, // left center
+ tx + triW, ty + triH // bottom-right
+ );
+ }
+#endif
+ // Draw Free Text input with multi-emote support and proper line wrapping
display->setColor(WHITE);
{
int inputY = 0 + y + FONT_HEIGHT_SMALL;
diff --git a/src/modules/ExternalNotificationModule.cpp b/src/modules/ExternalNotificationModule.cpp
index 1f871f87e..91e96b8d4 100644
--- a/src/modules/ExternalNotificationModule.cpp
+++ b/src/modules/ExternalNotificationModule.cpp
@@ -69,7 +69,7 @@ bool ascending = true;
#endif
#define EXT_NOTIFICATION_MODULE_OUTPUT_MS 1000
-#define EXT_NOTIFICATION_DEFAULT_THREAD_MS 25
+#define EXT_NOTIFICATION_FAST_THREAD_MS 25
#define ASCII_BELL 0x07
@@ -88,49 +88,32 @@ int32_t ExternalNotificationModule::runOnce()
if (!moduleConfig.external_notification.enabled) {
return INT32_MAX; // we don't need this thread here...
} else {
-
- bool isPlaying = rtttl::isPlaying();
+ uint32_t delay = EXT_NOTIFICATION_MODULE_OUTPUT_MS;
+ bool isRtttlPlaying = rtttl::isPlaying();
#ifdef HAS_I2S
- isPlaying = rtttl::isPlaying() || audioThread->isPlaying();
+ // audioThread->isPlaying() also handles actually playing the RTTTL, needs to be called in loop
+ isRtttlPlaying = isRtttlPlaying || audioThread->isPlaying();
#endif
- if ((nagCycleCutoff < millis()) && !isPlaying) {
- // let the song finish if we reach timeout
+ if ((nagCycleCutoff < millis()) && !isRtttlPlaying) {
+ // Turn off external notification immediately when timeout is reached, regardless of song state
nagCycleCutoff = UINT32_MAX;
- LOG_INFO("Turning off external notification: ");
- for (int i = 0; i < 3; i++) {
- setExternalState(i, false);
- externalTurnedOn[i] = 0;
- LOG_INFO("%d ", i);
- }
- LOG_INFO("");
-#ifdef HAS_I2S
- // GPIO0 is used as mclk for I2S audio and set to OUTPUT by the sound library
- // T-Deck uses GPIO0 as trackball button, so restore the mode
-#if defined(T_DECK) || (defined(BUTTON_PIN) && BUTTON_PIN == 0)
- pinMode(0, INPUT);
-#endif
-#endif
+ ExternalNotificationModule::stopNow();
isNagging = false;
return INT32_MAX; // save cycles till we're needed again
}
// If the output is turned on, turn it back off after the given period of time.
if (isNagging) {
- if (externalTurnedOn[0] + (moduleConfig.external_notification.output_ms ? moduleConfig.external_notification.output_ms
- : EXT_NOTIFICATION_MODULE_OUTPUT_MS) <
- millis()) {
+ delay = (moduleConfig.external_notification.output_ms ? moduleConfig.external_notification.output_ms
+ : EXT_NOTIFICATION_MODULE_OUTPUT_MS);
+ if (externalTurnedOn[0] + delay < millis()) {
setExternalState(0, !getExternal(0));
}
- if (externalTurnedOn[1] + (moduleConfig.external_notification.output_ms ? moduleConfig.external_notification.output_ms
- : EXT_NOTIFICATION_MODULE_OUTPUT_MS) <
- millis()) {
+ if (externalTurnedOn[1] + delay < millis()) {
setExternalState(1, !getExternal(1));
}
// Only toggle buzzer output if not using PWM mode (to avoid conflict with RTTTL)
- if (!moduleConfig.external_notification.use_pwm &&
- externalTurnedOn[2] + (moduleConfig.external_notification.output_ms ? moduleConfig.external_notification.output_ms
- : EXT_NOTIFICATION_MODULE_OUTPUT_MS) <
- millis()) {
+ if (!moduleConfig.external_notification.use_pwm && externalTurnedOn[2] + delay < millis()) {
LOG_DEBUG("EXTERNAL 2 %d compared to %d", externalTurnedOn[2] + moduleConfig.external_notification.output_ms,
millis());
setExternalState(2, !getExternal(2));
@@ -181,6 +164,8 @@ int32_t ExternalNotificationModule::runOnce()
colorState = 1;
}
}
+ // we need fast updates for the color change
+ delay = EXT_NOTIFICATION_FAST_THREAD_MS;
#endif
#ifdef T_WATCH_S3
@@ -190,12 +175,14 @@ int32_t ExternalNotificationModule::runOnce()
// Play RTTTL over i2s audio interface if enabled as buzzer
#ifdef HAS_I2S
- if (moduleConfig.external_notification.use_i2s_as_buzzer && canBuzz()) {
+ if (moduleConfig.external_notification.use_i2s_as_buzzer) {
if (audioThread->isPlaying()) {
// Continue playing
} else if (isNagging && (nagCycleCutoff >= millis())) {
audioThread->beginRttl(rtttlConfig.ringtone, strlen_P(rtttlConfig.ringtone));
}
+ // we need fast updates to play the RTTTL
+ delay = EXT_NOTIFICATION_FAST_THREAD_MS;
}
#endif
// now let the PWM buzzer play
@@ -206,9 +193,11 @@ int32_t ExternalNotificationModule::runOnce()
// start the song again if we have time left
rtttl::begin(config.device.buzzer_gpio, rtttlConfig.ringtone);
}
+ // we need fast updates to play the RTTTL
+ delay = EXT_NOTIFICATION_FAST_THREAD_MS;
}
- return EXT_NOTIFICATION_DEFAULT_THREAD_MS;
+ return delay;
}
}
@@ -316,14 +305,16 @@ bool ExternalNotificationModule::nagging()
void ExternalNotificationModule::stopNow()
{
+ LOG_INFO("Turning off external notification: ");
+ LOG_INFO("Stop RTTTL playback");
rtttl::stop();
#ifdef HAS_I2S
+ LOG_INFO("Stop audioThread playback");
if (audioThread->isPlaying())
audioThread->stop();
#endif
- nagCycleCutoff = 1; // small value
- isNagging = false;
// Turn off all outputs
+ LOG_INFO("Turning off setExternalStates");
for (int i = 0; i < 3; i++) {
setExternalState(i, false);
externalTurnedOn[i] = 0;
@@ -331,6 +322,18 @@ void ExternalNotificationModule::stopNow()
setIntervalFromNow(0);
#ifdef T_WATCH_S3
drv.stop();
+#endif
+
+ // Prevent the state machine from immediately re-triggering outputs after a manual stop.
+ isNagging = false;
+ nagCycleCutoff = UINT32_MAX;
+
+#ifdef HAS_I2S
+ // GPIO0 is used as mclk for I2S audio and set to OUTPUT by the sound library
+ // T-Deck uses GPIO0 as trackball button, so restore the mode
+#if defined(T_DECK) || (defined(BUTTON_PIN) && BUTTON_PIN == 0)
+ pinMode(0, INPUT);
+#endif
#endif
}
@@ -364,9 +367,10 @@ ExternalNotificationModule::ExternalNotificationModule()
// moduleConfig.external_notification.alert_message_buzzer = true;
if (moduleConfig.external_notification.enabled) {
+#if !defined(MESHTASTIC_EXCLUDE_INPUTBROKER)
if (inputBroker) // put our callback in the inputObserver list
inputObserver.observe(inputBroker);
-
+#endif
if (nodeDB->loadProto(rtttlConfigFile, meshtastic_RTTTLConfig_size, sizeof(meshtastic_RTTTLConfig),
&meshtastic_RTTTLConfig_msg, &rtttlConfig) != LoadFileResult::LOAD_SUCCESS) {
memset(rtttlConfig.ringtone, 0, sizeof(rtttlConfig.ringtone));
@@ -439,7 +443,7 @@ ExternalNotificationModule::ExternalNotificationModule()
ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshPacket &mp)
{
- if (moduleConfig.external_notification.enabled && !isMuted) {
+ if (moduleConfig.external_notification.enabled && !isSilenced) {
#ifdef T_WATCH_S3
drv.setWaveform(0, 75);
drv.setWaveform(1, 56);
@@ -450,12 +454,13 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP
// Check if the message contains a bell character. Don't do this loop for every pin, just once.
auto &p = mp.decoded;
bool containsBell = false;
- for (int i = 0; i < p.payload.size; i++) {
+ for (size_t i = 0; i < p.payload.size; i++) {
if (p.payload.bytes[i] == ASCII_BELL) {
containsBell = true;
}
}
+ meshtastic_Channel ch = channels.getByIndex(mp.channel ? mp.channel : channels.getPrimaryIndex());
if (moduleConfig.external_notification.alert_bell) {
if (containsBell) {
LOG_INFO("externalNotificationModule - Notification Bell");
@@ -506,7 +511,8 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP
}
}
- if (moduleConfig.external_notification.alert_message) {
+ if (moduleConfig.external_notification.alert_message &&
+ (!ch.settings.has_module_settings || !ch.settings.module_settings.is_muted)) {
LOG_INFO("externalNotificationModule - Notification Module");
isNagging = true;
setExternalState(0, true);
@@ -517,7 +523,8 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP
}
}
- if (moduleConfig.external_notification.alert_message_vibra) {
+ if (moduleConfig.external_notification.alert_message_vibra &&
+ (!ch.settings.has_module_settings || !ch.settings.module_settings.is_muted)) {
LOG_INFO("externalNotificationModule - Notification Module (Vibra)");
isNagging = true;
setExternalState(1, true);
@@ -528,25 +535,33 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP
}
}
- if (moduleConfig.external_notification.alert_message_buzzer) {
+ if (moduleConfig.external_notification.alert_message_buzzer &&
+ (!ch.settings.has_module_settings || !ch.settings.module_settings.is_muted)) {
LOG_INFO("externalNotificationModule - Notification Module (Buzzer)");
- isNagging = true;
- if (!moduleConfig.external_notification.use_pwm && !moduleConfig.external_notification.use_i2s_as_buzzer) {
- setExternalState(2, true);
- } else {
+ if (config.device.buzzer_mode != meshtastic_Config_DeviceConfig_BuzzerMode_DIRECT_MSG_ONLY ||
+ (!isBroadcast(mp.to) && isToUs(&mp))) {
+ // Buzz if buzzer mode is not in DIRECT_MSG_ONLY or is DM to us
+ isNagging = true;
+ if (!moduleConfig.external_notification.use_pwm && !moduleConfig.external_notification.use_i2s_as_buzzer) {
+ setExternalState(2, true);
+ } else {
#ifdef HAS_I2S
- if (moduleConfig.external_notification.use_i2s_as_buzzer) {
- audioThread->beginRttl(rtttlConfig.ringtone, strlen_P(rtttlConfig.ringtone));
- } else
+ if (moduleConfig.external_notification.use_i2s_as_buzzer) {
+ audioThread->beginRttl(rtttlConfig.ringtone, strlen_P(rtttlConfig.ringtone));
+ } else
#endif
- if (moduleConfig.external_notification.use_pwm) {
- rtttl::begin(config.device.buzzer_gpio, rtttlConfig.ringtone);
+ if (moduleConfig.external_notification.use_pwm) {
+ rtttl::begin(config.device.buzzer_gpio, rtttlConfig.ringtone);
+ }
+ }
+ if (moduleConfig.external_notification.nag_timeout) {
+ nagCycleCutoff = millis() + moduleConfig.external_notification.nag_timeout * 1000;
+ } else {
+ nagCycleCutoff = millis() + moduleConfig.external_notification.output_ms;
}
- }
- if (moduleConfig.external_notification.nag_timeout) {
- nagCycleCutoff = millis() + moduleConfig.external_notification.nag_timeout * 1000;
} else {
- nagCycleCutoff = millis() + moduleConfig.external_notification.output_ms;
+ // Don't beep if buzzer mode is "direct messages only" and it is no direct message
+ LOG_INFO("Message buzzer was suppressed because buzzer mode DIRECT_MSG_ONLY");
}
}
setIntervalFromNow(0); // run once so we know if we should do something
diff --git a/src/modules/ExternalNotificationModule.h b/src/modules/ExternalNotificationModule.h
index 19cf9eb7b..f667f7be9 100644
--- a/src/modules/ExternalNotificationModule.h
+++ b/src/modules/ExternalNotificationModule.h
@@ -43,8 +43,8 @@ class ExternalNotificationModule : public SinglePortModule, private concurrency:
void setExternalState(uint8_t index = 0, bool on = false);
bool getExternal(uint8_t index = 0);
- void setMute(bool mute) { isMuted = mute; }
- bool getMute() { return isMuted; }
+ void setMute(bool mute) { isSilenced = mute; }
+ bool getMute() { return isSilenced; }
bool canBuzz();
bool nagging();
@@ -67,7 +67,7 @@ class ExternalNotificationModule : public SinglePortModule, private concurrency:
bool isNagging = false;
- bool isMuted = false;
+ bool isSilenced = false;
virtual AdminMessageHandleResult handleAdminMessageForModule(const meshtastic_MeshPacket &mp,
meshtastic_AdminMessage *request,
diff --git a/src/modules/Modules.cpp b/src/modules/Modules.cpp
index 3528f57f5..827524fc3 100644
--- a/src/modules/Modules.cpp
+++ b/src/modules/Modules.cpp
@@ -3,11 +3,18 @@
#include "buzz/BuzzerFeedbackThread.h"
#include "input/ExpressLRSFiveWay.h"
#include "input/InputBroker.h"
+#include "input/RotaryEncoderImpl.h"
#include "input/RotaryEncoderInterruptImpl1.h"
#include "input/SerialKeyboardImpl.h"
-#include "input/TrackballInterruptImpl1.h"
#include "input/UpDownInterruptImpl1.h"
+#include "input/i2cButton.h"
#include "modules/SystemCommandsModule.h"
+#if HAS_TRACKBALL
+#include "input/TrackballInterruptImpl1.h"
+#endif
+
+#include "modules/StatusLEDModule.h"
+
#if !MESHTASTIC_EXCLUDE_I2C
#include "input/cardKbI2cImpl.h"
#endif
@@ -87,7 +94,7 @@
#include "modules/StoreForwardModule.h"
#endif
#endif
-#if defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_RP2040) || defined(ARCH_PORTDUINO)
+
#if !MESHTASTIC_EXCLUDE_EXTERNALNOTIFICATION
#include "modules/ExternalNotificationModule.h"
#endif
@@ -97,7 +104,6 @@
#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !MESHTASTIC_EXCLUDE_SERIAL
#include "modules/SerialModule.h"
#endif
-#endif
#if !MESHTASTIC_EXCLUDE_DROPZONE
#include "modules/DropzoneModule.h"
@@ -108,175 +114,197 @@
*/
void setupModules()
{
- if (config.device.role != meshtastic_Config_DeviceConfig_Role_REPEATER) {
#if (HAS_BUTTON || ARCH_PORTDUINO) && !MESHTASTIC_EXCLUDE_INPUTBROKER
- if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
- inputBroker = new InputBroker();
- systemCommandsModule = new SystemCommandsModule();
- buzzerFeedbackThread = new BuzzerFeedbackThread();
- }
+ if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
+ inputBroker = new InputBroker();
+ systemCommandsModule = new SystemCommandsModule();
+ buzzerFeedbackThread = new BuzzerFeedbackThread();
+ }
#endif
+#if defined(LED_CHARGE) || defined(LED_PAIRING)
+ statusLEDModule = new StatusLEDModule();
+#endif
+
#if !MESHTASTIC_EXCLUDE_ADMIN
- adminModule = new AdminModule();
+ adminModule = new AdminModule();
#endif
#if !MESHTASTIC_EXCLUDE_NODEINFO
- nodeInfoModule = new NodeInfoModule();
+ nodeInfoModule = new NodeInfoModule();
#endif
#if !MESHTASTIC_EXCLUDE_GPS
- positionModule = new PositionModule();
+ positionModule = new PositionModule();
#endif
#if !MESHTASTIC_EXCLUDE_WAYPOINT
- waypointModule = new WaypointModule();
+ waypointModule = new WaypointModule();
#endif
#if !MESHTASTIC_EXCLUDE_TEXTMESSAGE
- textMessageModule = new TextMessageModule();
+ textMessageModule = new TextMessageModule();
#endif
#if !MESHTASTIC_EXCLUDE_TRACEROUTE
- traceRouteModule = new TraceRouteModule();
+ traceRouteModule = new TraceRouteModule();
#endif
#if !MESHTASTIC_EXCLUDE_NEIGHBORINFO
+ if (moduleConfig.has_neighbor_info && moduleConfig.neighbor_info.enabled) {
neighborInfoModule = new NeighborInfoModule();
+ }
#endif
#if !MESHTASTIC_EXCLUDE_DETECTIONSENSOR
+ if (moduleConfig.has_detection_sensor && moduleConfig.detection_sensor.enabled) {
detectionSensorModule = new DetectionSensorModule();
+ }
#endif
#if !MESHTASTIC_EXCLUDE_ATAK
+ if (config.device.role == meshtastic_Config_DeviceConfig_Role_TAK ||
+ config.device.role == meshtastic_Config_DeviceConfig_Role_TAK_TRACKER) {
atakPluginModule = new AtakPluginModule();
+ }
#endif
#if !MESHTASTIC_EXCLUDE_PKI
- keyVerificationModule = new KeyVerificationModule();
+ keyVerificationModule = new KeyVerificationModule();
#endif
#if !MESHTASTIC_EXCLUDE_DROPZONE
- dropzoneModule = new DropzoneModule();
+ dropzoneModule = new DropzoneModule();
#endif
#if !MESHTASTIC_EXCLUDE_GENERIC_THREAD_MODULE
- new GenericThreadModule();
+ new GenericThreadModule();
#endif
- // Note: if the rest of meshtastic doesn't need to explicitly use your module, you do not need to assign the instance
- // to a global variable.
+ // Note: if the rest of meshtastic doesn't need to explicitly use your module, you do not need to assign the instance
+ // to a global variable.
#if !MESHTASTIC_EXCLUDE_REMOTEHARDWARE
- new RemoteHardwareModule();
+ new RemoteHardwareModule();
#endif
#if !MESHTASTIC_EXCLUDE_POWERSTRESS
- new PowerStressModule();
+ new PowerStressModule();
#endif
- // Example: Put your module here
- // new ReplyModule();
+ // Example: Put your module here
+ // new ReplyModule();
#if (HAS_BUTTON || ARCH_PORTDUINO) && !MESHTASTIC_EXCLUDE_INPUTBROKER
- if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
- rotaryEncoderInterruptImpl1 = new RotaryEncoderInterruptImpl1();
- if (!rotaryEncoderInterruptImpl1->init()) {
- delete rotaryEncoderInterruptImpl1;
- rotaryEncoderInterruptImpl1 = nullptr;
- }
- upDownInterruptImpl1 = new UpDownInterruptImpl1();
- if (!upDownInterruptImpl1->init()) {
- delete upDownInterruptImpl1;
- upDownInterruptImpl1 = nullptr;
- }
- cardKbI2cImpl = new CardKbI2cImpl();
- cardKbI2cImpl->init();
+ if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
+#ifndef T_LORA_PAGER
+ rotaryEncoderInterruptImpl1 = new RotaryEncoderInterruptImpl1();
+ if (!rotaryEncoderInterruptImpl1->init()) {
+ delete rotaryEncoderInterruptImpl1;
+ rotaryEncoderInterruptImpl1 = nullptr;
+ }
+#elif defined(T_LORA_PAGER)
+ // use a special FSM based rotary encoder version for T-LoRa Pager
+ rotaryEncoderImpl = new RotaryEncoderImpl();
+ if (!rotaryEncoderImpl->init()) {
+ delete rotaryEncoderImpl;
+ rotaryEncoderImpl = nullptr;
+ }
+#else
+ upDownInterruptImpl1 = new UpDownInterruptImpl1();
+ if (!upDownInterruptImpl1->init()) {
+ delete upDownInterruptImpl1;
+ upDownInterruptImpl1 = nullptr;
+ }
+#endif
+ cardKbI2cImpl = new CardKbI2cImpl();
+ cardKbI2cImpl->init();
+#if defined(M5STACK_UNITC6L)
+ i2cButton = new i2cButtonThread("i2cButtonThread");
+#endif
#ifdef INPUTBROKER_MATRIX_TYPE
- kbMatrixImpl = new KbMatrixImpl();
- kbMatrixImpl->init();
+ kbMatrixImpl = new KbMatrixImpl();
+ kbMatrixImpl->init();
#endif // INPUTBROKER_MATRIX_TYPE
#ifdef INPUTBROKER_SERIAL_TYPE
- aSerialKeyboardImpl = new SerialKeyboardImpl();
- aSerialKeyboardImpl->init();
+ aSerialKeyboardImpl = new SerialKeyboardImpl();
+ aSerialKeyboardImpl->init();
#endif // INPUTBROKER_MATRIX_TYPE
- }
+ }
#endif // HAS_BUTTON
#if ARCH_PORTDUINO
- if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
- seesawRotary = new SeesawRotary("SeesawRotary");
- if (!seesawRotary->init()) {
- delete seesawRotary;
- seesawRotary = nullptr;
- }
- aLinuxInputImpl = new LinuxInputImpl();
- aLinuxInputImpl->init();
+ if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
+ seesawRotary = new SeesawRotary("SeesawRotary");
+ if (!seesawRotary->init()) {
+ delete seesawRotary;
+ seesawRotary = nullptr;
}
+ aLinuxInputImpl = new LinuxInputImpl();
+ aLinuxInputImpl->init();
+ }
#endif
-#if !MESHTASTIC_EXCLUDE_INPUTBROKER
- if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
- trackballInterruptImpl1 = new TrackballInterruptImpl1();
- trackballInterruptImpl1->init(TB_DOWN, TB_UP, TB_LEFT, TB_RIGHT, TB_PRESS);
- }
+#if !MESHTASTIC_EXCLUDE_INPUTBROKER && HAS_TRACKBALL
+ if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
+ trackballInterruptImpl1 = new TrackballInterruptImpl1();
+ trackballInterruptImpl1->init(TB_DOWN, TB_UP, TB_LEFT, TB_RIGHT, TB_PRESS);
+ }
#endif
#ifdef INPUTBROKER_EXPRESSLRSFIVEWAY_TYPE
- expressLRSFiveWayInput = new ExpressLRSFiveWay();
+ expressLRSFiveWayInput = new ExpressLRSFiveWay();
#endif
#if HAS_SCREEN && !MESHTASTIC_EXCLUDE_CANNEDMESSAGES
- if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
- cannedMessageModule = new CannedMessageModule();
- }
+ if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
+ cannedMessageModule = new CannedMessageModule();
+ }
#endif
#if ARCH_PORTDUINO
- new HostMetricsModule();
+ new HostMetricsModule();
#endif
#if HAS_TELEMETRY
- new DeviceTelemetryModule();
+ new DeviceTelemetryModule();
#endif
-// TODO: How to improve this?
-#if HAS_SENSOR && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
+#if HAS_TELEMETRY && HAS_SENSOR && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
+ if (moduleConfig.has_telemetry &&
+ (moduleConfig.telemetry.environment_measurement_enabled || moduleConfig.telemetry.environment_screen_enabled)) {
new EnvironmentTelemetryModule();
+ }
#if __has_include("Adafruit_PM25AQI.h")
- if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_PMSA003I].first > 0) {
- new AirQualityTelemetryModule();
- }
+ if (moduleConfig.has_telemetry && moduleConfig.telemetry.air_quality_enabled &&
+ nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_PMSA003I].first > 0) {
+ new AirQualityTelemetryModule();
+ }
#endif
#if !MESHTASTIC_EXCLUDE_HEALTH_TELEMETRY
- if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_MAX30102].first > 0 ||
- nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_MLX90614].first > 0) {
- new HealthTelemetryModule();
- }
+ if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_MAX30102].first > 0 ||
+ nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_MLX90614].first > 0) {
+ new HealthTelemetryModule();
+ }
#endif
#endif
#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_POWER_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
+ if (moduleConfig.has_telemetry &&
+ (moduleConfig.telemetry.power_measurement_enabled || moduleConfig.telemetry.power_screen_enabled)) {
new PowerTelemetryModule();
+ }
#endif
-#if (defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_RP2040)) && !defined(CONFIG_IDF_TARGET_ESP32S2) && \
- !defined(CONFIG_IDF_TARGET_ESP32C3)
+#if (defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_RP2040) || defined(ARCH_STM32WL)) && \
+ !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3)
#if !MESHTASTIC_EXCLUDE_SERIAL
- if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
- new SerialModule();
- }
+ if (moduleConfig.has_serial && moduleConfig.serial.enabled &&
+ config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
+ new SerialModule();
+ }
#endif
#endif
#ifdef ARCH_ESP32
- // Only run on an esp32 based device.
+ // Only run on an esp32 based device.
#if defined(USE_SX1280) && !MESHTASTIC_EXCLUDE_AUDIO
- audioModule = new AudioModule();
+ audioModule = new AudioModule();
#endif
#if !MESHTASTIC_EXCLUDE_PAXCOUNTER
+ if (moduleConfig.has_paxcounter && moduleConfig.paxcounter.enabled) {
paxcounterModule = new PaxcounterModule();
+ }
#endif
#endif
#if defined(ARCH_ESP32) || defined(ARCH_PORTDUINO)
#if !MESHTASTIC_EXCLUDE_STOREFORWARD
+ if (moduleConfig.has_store_forward && moduleConfig.store_forward.enabled) {
storeForwardModule = new StoreForwardModule();
+ }
#endif
#endif
-#if defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_RP2040) || defined(ARCH_PORTDUINO)
#if !MESHTASTIC_EXCLUDE_EXTERNALNOTIFICATION
- externalNotificationModule = new ExternalNotificationModule();
+ externalNotificationModule = new ExternalNotificationModule();
#endif
#if !MESHTASTIC_EXCLUDE_RANGETEST && !MESHTASTIC_EXCLUDE_GPS
+ if (moduleConfig.has_range_test && moduleConfig.range_test.enabled)
new RangeTestModule();
#endif
-#endif
- } else {
-#if !MESHTASTIC_EXCLUDE_ADMIN
- adminModule = new AdminModule();
-#endif
-#if HAS_TELEMETRY
- new DeviceTelemetryModule();
-#endif
-#if !MESHTASTIC_EXCLUDE_TRACEROUTE
- traceRouteModule = new TraceRouteModule();
-#endif
- }
// NOTE! This module must be added LAST because it likes to check for replies from other modules and avoid sending extra
// acks
routingModule = new RoutingModule();
diff --git a/src/modules/NeighborInfoModule.cpp b/src/modules/NeighborInfoModule.cpp
index eebf428a4..936a7b44a 100644
--- a/src/modules/NeighborInfoModule.cpp
+++ b/src/modules/NeighborInfoModule.cpp
@@ -34,7 +34,8 @@ void NeighborInfoModule::printNodeDBNeighbors()
}
}
-/* Send our initial owner announcement 35 seconds after we start (to give network time to setup) */
+/* Send our initial owner announcement 35 seconds after we start (to give
+ * network time to setup) */
NeighborInfoModule::NeighborInfoModule()
: ProtobufModule("neighborinfo", meshtastic_PortNum_NEIGHBORINFO_APP, &meshtastic_NeighborInfo_msg),
concurrency::OSThread("NeighborInfo")
@@ -53,8 +54,8 @@ NeighborInfoModule::NeighborInfoModule()
}
/*
-Collect neighbor info from the nodeDB's history, capping at a maximum number of entries and max time
-Assumes that the neighborInfo packet has been allocated
+Collect neighbor info from the nodeDB's history, capping at a maximum number of
+entries and max time Assumes that the neighborInfo packet has been allocated
@returns the number of entries collected
*/
uint32_t NeighborInfoModule::collectNeighborInfo(meshtastic_NeighborInfo *neighborInfo)
@@ -71,8 +72,8 @@ uint32_t NeighborInfoModule::collectNeighborInfo(meshtastic_NeighborInfo *neighb
if ((neighborInfo->neighbors_count < MAX_NUM_NEIGHBORS) && (nbr.node_id != my_node_id)) {
neighborInfo->neighbors[neighborInfo->neighbors_count].node_id = nbr.node_id;
neighborInfo->neighbors[neighborInfo->neighbors_count].snr = nbr.snr;
- // Note: we don't set the last_rx_time and node_broadcast_intervals_secs here, because we don't want to send this over
- // the mesh
+ // Note: we don't set the last_rx_time and node_broadcast_intervals_secs
+ // here, because we don't want to send this over the mesh
neighborInfo->neighbors_count++;
}
}
@@ -88,8 +89,9 @@ void NeighborInfoModule::cleanUpNeighbors()
uint32_t now = getTime();
NodeNum my_node_id = nodeDB->getNodeNum();
for (auto it = neighbors.rbegin(); it != neighbors.rend();) {
- // We will remove a neighbor if we haven't heard from them in twice the broadcast interval
- // cannot use isWithinTimespanMs() as it->last_rx_time is seconds since 1970
+ // We will remove a neighbor if we haven't heard from them in twice the
+ // broadcast interval cannot use isWithinTimespanMs() as it->last_rx_time is
+ // seconds since 1970
if ((now - it->last_rx_time > it->node_broadcast_interval_secs * 2) && (it->node_id != my_node_id)) {
LOG_DEBUG("Remove neighbor with node ID 0x%x", it->node_id);
it = std::vector::reverse_iterator(
@@ -105,14 +107,15 @@ void NeighborInfoModule::sendNeighborInfo(NodeNum dest, bool wantReplies)
{
meshtastic_NeighborInfo neighborInfo = meshtastic_NeighborInfo_init_zero;
collectNeighborInfo(&neighborInfo);
- meshtastic_MeshPacket *p = allocDataProtobuf(neighborInfo);
- // send regardless of whether or not we have neighbors in our DB,
- // because we want to get neighbors for the next cycle
- p->to = dest;
- p->decoded.want_response = wantReplies;
- p->priority = meshtastic_MeshPacket_Priority_BACKGROUND;
- printNeighborInfo("SENDING", &neighborInfo);
- service->sendToMesh(p, RX_SRC_LOCAL, true);
+ // only send neighbours if we have some to send
+ if (neighborInfo.neighbors_count > 0) {
+ meshtastic_MeshPacket *p = allocDataProtobuf(neighborInfo);
+ p->to = dest;
+ p->decoded.want_response = wantReplies;
+ p->priority = meshtastic_MeshPacket_Priority_BACKGROUND;
+ printNeighborInfo("SENDING", &neighborInfo);
+ service->sendToMesh(p, RX_SRC_LOCAL, true);
+ }
}
/*
@@ -131,25 +134,55 @@ int32_t NeighborInfoModule::runOnce()
return Default::getConfiguredOrDefaultMs(moduleConfig.neighbor_info.update_interval, default_neighbor_info_broadcast_secs);
}
+meshtastic_MeshPacket *NeighborInfoModule::allocReply()
+{
+ LOG_INFO("NeighborInfoRequested.");
+ if (lastSentReply && Throttle::isWithinTimespanMs(lastSentReply, 3 * 60 * 1000)) {
+ LOG_DEBUG("Skip Neighbors reply since we sent a reply <3min ago");
+ ignoreRequest = true; // Mark it as ignored for MeshModule
+ return nullptr;
+ }
+
+ meshtastic_NeighborInfo neighborInfo = meshtastic_NeighborInfo_init_zero;
+ collectNeighborInfo(&neighborInfo);
+
+ meshtastic_MeshPacket *reply = allocDataProtobuf(neighborInfo);
+
+ if (reply) {
+ lastSentReply = millis(); // Track when we sent this reply
+ }
+ return reply;
+}
+
/*
Collect a received neighbor info packet from another node
Pass it to an upper client; do not persist this data on the mesh
*/
bool NeighborInfoModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_NeighborInfo *np)
{
+ LOG_DEBUG("NeighborInfo: handleReceivedProtobuf");
if (np) {
printNeighborInfo("RECEIVED", np);
- updateNeighbors(mp, np);
+ // Ignore dummy/interceptable packets: single neighbor with nodeId 0 and snr 0
+ if (np->neighbors_count != 1 || np->neighbors[0].node_id != 0 || np->neighbors[0].snr != 0.0f) {
+ LOG_DEBUG(" Updating neighbours");
+ updateNeighbors(mp, np);
+ } else {
+ LOG_DEBUG(" Ignoring dummy neighbor info packet (single neighbor with nodeId 0, snr 0)");
+ }
} else if (mp.hop_start != 0 && mp.hop_start == mp.hop_limit) {
+ LOG_DEBUG("Get or create neighbor: %u with snr %f", mp.from, mp.rx_snr);
// If the hopLimit is the same as hopStart, then it is a neighbor
- getOrCreateNeighbor(mp.from, mp.from, 0, mp.rx_snr); // Set the broadcast interval to 0, as we don't know it
+ getOrCreateNeighbor(mp.from, mp.from, 0,
+ mp.rx_snr); // Set the broadcast interval to 0, as we don't know it
}
// Allow others to handle this packet
return false;
}
/*
-Copy the content of a current NeighborInfo packet into a new one and update the last_sent_by_id to our NodeNum
+Copy the content of a current NeighborInfo packet into a new one and update the
+last_sent_by_id to our NodeNum
*/
void NeighborInfoModule::alterReceivedProtobuf(meshtastic_MeshPacket &p, meshtastic_NeighborInfo *n)
{
@@ -167,8 +200,10 @@ void NeighborInfoModule::resetNeighbors()
void NeighborInfoModule::updateNeighbors(const meshtastic_MeshPacket &mp, const meshtastic_NeighborInfo *np)
{
- // The last sent ID will be 0 if the packet is from the phone, which we don't count as
- // an edge. So we assume that if it's zero, then this packet is from our node.
+ LOG_DEBUG("updateNeighbors");
+ // The last sent ID will be 0 if the packet is from the phone, which we don't
+ // count as an edge. So we assume that if it's zero, then this packet is from
+ // our node.
if (mp.which_payload_variant == meshtastic_MeshPacket_decoded_tag && mp.from) {
getOrCreateNeighbor(mp.from, np->last_sent_by_id, np->node_broadcast_interval_secs, mp.rx_snr);
}
@@ -187,7 +222,8 @@ meshtastic_Neighbor *NeighborInfoModule::getOrCreateNeighbor(NodeNum originalSen
// if found, update it
neighbors[i].snr = snr;
neighbors[i].last_rx_time = getTime();
- // Only if this is the original sender, the broadcast interval corresponds to it
+ // Only if this is the original sender, the broadcast interval corresponds
+ // to it
if (originalSender == n && node_broadcast_interval_secs != 0)
neighbors[i].node_broadcast_interval_secs = node_broadcast_interval_secs;
return &neighbors[i];
@@ -199,10 +235,12 @@ meshtastic_Neighbor *NeighborInfoModule::getOrCreateNeighbor(NodeNum originalSen
new_nbr.node_id = n;
new_nbr.snr = snr;
new_nbr.last_rx_time = getTime();
- // Only if this is the original sender, the broadcast interval corresponds to it
+ // Only if this is the original sender, the broadcast interval corresponds to
+ // it
if (originalSender == n && node_broadcast_interval_secs != 0)
new_nbr.node_broadcast_interval_secs = node_broadcast_interval_secs;
- else // Assume the same broadcast interval as us for the neighbor if we don't know it
+ else // Assume the same broadcast interval as us for the neighbor if we don't
+ // know it
new_nbr.node_broadcast_interval_secs = moduleConfig.neighbor_info.update_interval;
if (neighbors.size() < MAX_NUM_NEIGHBORS) {
@@ -214,4 +252,4 @@ meshtastic_Neighbor *NeighborInfoModule::getOrCreateNeighbor(NodeNum originalSen
neighbors.push_back(new_nbr);
}
return &neighbors.back();
-}
\ No newline at end of file
+}
diff --git a/src/modules/NeighborInfoModule.h b/src/modules/NeighborInfoModule.h
index aa76a2187..abb530329 100644
--- a/src/modules/NeighborInfoModule.h
+++ b/src/modules/NeighborInfoModule.h
@@ -28,6 +28,10 @@ class NeighborInfoModule : public ProtobufModule, priva
*/
virtual bool handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_NeighborInfo *nb) override;
+ /* Messages can be received that have the want_response bit set. If set, this callback will be invoked
+ * so that subclasses can (optionally) send a response back to the original sender. */
+ virtual meshtastic_MeshPacket *allocReply() override;
+
/*
* Collect neighbor info from the nodeDB's history, capping at a maximum number of entries and max time
* @return the number of entries collected
@@ -66,5 +70,8 @@ class NeighborInfoModule : public ProtobufModule, priva
/* These are for debugging only */
void printNeighborInfo(const char *header, const meshtastic_NeighborInfo *np);
void printNodeDBNeighbors();
+
+ private:
+ uint32_t lastSentReply = 0; // Last time we sent a position reply (used for reply throttling only)
};
extern NeighborInfoModule *neighborInfoModule;
\ No newline at end of file
diff --git a/src/modules/NodeInfoModule.cpp b/src/modules/NodeInfoModule.cpp
index 413168334..72f52fe1f 100644
--- a/src/modules/NodeInfoModule.cpp
+++ b/src/modules/NodeInfoModule.cpp
@@ -12,12 +12,12 @@ NodeInfoModule *nodeInfoModule;
bool NodeInfoModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_User *pptr)
{
- auto p = *pptr;
-
if (mp.from == nodeDB->getNodeNum()) {
LOG_WARN("Ignoring packet supposed to be from our own node: %08x", mp.from);
return false;
}
+
+ auto p = *pptr;
if (p.is_licensed != owner.is_licensed) {
LOG_WARN("Invalid nodeInfo detected, is_licensed mismatch!");
return true;
@@ -34,21 +34,42 @@ bool NodeInfoModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, mes
bool wasBroadcast = isBroadcast(mp.to);
+ // LOG_DEBUG("did encode");
// if user has changed while packet was not for us, inform phone
- if (hasChanged && !wasBroadcast && !isToUs(&mp))
- service->sendToPhone(packetPool.allocCopy(mp));
+ if (hasChanged && !wasBroadcast && !isToUs(&mp)) {
+ auto packetCopy = packetPool.allocCopy(mp); // Keep a copy of the packet for later analysis
+
+ // Re-encode the user protobuf, as we have stripped out the user.id
+ packetCopy->decoded.payload.size = pb_encode_to_bytes(
+ packetCopy->decoded.payload.bytes, sizeof(packetCopy->decoded.payload.bytes), &meshtastic_User_msg, &p);
+
+ service->sendToPhone(packetCopy);
+ }
// LOG_DEBUG("did handleReceived");
return false; // Let others look at this message also if they want
}
+void NodeInfoModule::alterReceivedProtobuf(meshtastic_MeshPacket &mp, meshtastic_User *p)
+{
+ // Coerce user.id to be derived from the node number
+ snprintf(p->id, sizeof(p->id), "!%08x", getFrom(&mp));
+
+ // Re-encode the altered protobuf back into the packet
+ mp.decoded.payload.size =
+ pb_encode_to_bytes(mp.decoded.payload.bytes, sizeof(mp.decoded.payload.bytes), &meshtastic_User_msg, p);
+}
+
void NodeInfoModule::sendOurNodeInfo(NodeNum dest, bool wantReplies, uint8_t channel, bool _shorterTimeout)
{
// cancel any not yet sent (now stale) position packets
if (prevPacketId) // if we wrap around to zero, we'll simply fail to cancel in that rare case (no big deal)
service->cancelSending(prevPacketId);
shorterTimeout = _shorterTimeout;
+ DEBUG_HEAP_BEFORE;
meshtastic_MeshPacket *p = allocReply();
+ DEBUG_HEAP_AFTER("NodeInfoModule::sendOurNodeInfo", p);
+
if (p) { // Check whether we didn't ignore it
p->to = dest;
p->decoded.want_response = (config.device.role != meshtastic_Config_DeviceConfig_Role_TRACKER &&
@@ -95,11 +116,12 @@ meshtastic_MeshPacket *NodeInfoModule::allocReply()
u.public_key.bytes[0] = 0;
u.public_key.size = 0;
}
- // Coerce unmessagable for Repeater role
- if (u.role == meshtastic_Config_DeviceConfig_Role_REPEATER) {
- u.has_is_unmessagable = true;
- u.is_unmessagable = true;
- }
+
+ // FIXME: Clear the user.id field since it should be derived from node number on the receiving end
+ // u.id[0] = '\0';
+
+ // Ensure our user.id is derived correctly
+ strcpy(u.id, nodeDB->getNodeId().c_str());
LOG_INFO("Send owner %s/%s/%s", u.id, u.long_name, u.short_name);
lastSentToMesh = millis();
diff --git a/src/modules/NodeInfoModule.h b/src/modules/NodeInfoModule.h
index c1fb9ccce..572b81700 100644
--- a/src/modules/NodeInfoModule.h
+++ b/src/modules/NodeInfoModule.h
@@ -30,6 +30,9 @@ class NodeInfoModule : public ProtobufModule, private concurren
*/
virtual bool handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_User *p) override;
+ /** Called to alter received User protobuf */
+ virtual void alterReceivedProtobuf(meshtastic_MeshPacket &mp, meshtastic_User *p) override;
+
/** Messages can be received that have the want_response bit set. If set, this callback will be invoked
* so that subclasses can (optionally) send a response back to the original sender. */
virtual meshtastic_MeshPacket *allocReply() override;
diff --git a/src/modules/RangeTestModule.cpp b/src/modules/RangeTestModule.cpp
index 6f3d69acf..026b3028d 100644
--- a/src/modules/RangeTestModule.cpp
+++ b/src/modules/RangeTestModule.cpp
@@ -31,7 +31,7 @@ uint32_t packetSequence = 0;
int32_t RangeTestModule::runOnce()
{
-#if defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_PORTDUINO)
+#if defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_STM32WL) || defined(ARCH_PORTDUINO)
/*
Uncomment the preferences below if you want to use the module
@@ -41,12 +41,12 @@ int32_t RangeTestModule::runOnce()
// moduleConfig.range_test.enabled = 1;
// moduleConfig.range_test.sender = 30;
// moduleConfig.range_test.save = 1;
+ // moduleConfig.range_test.clear_on_reboot = 1;
// Fixed position is useful when testing indoors.
// config.position.fixed_position = 1;
uint32_t senderHeartbeat = moduleConfig.range_test.sender * 1000;
-
if (moduleConfig.range_test.enabled) {
if (firstTime) {
@@ -54,6 +54,11 @@ int32_t RangeTestModule::runOnce()
firstTime = 0;
+ if (moduleConfig.range_test.clear_on_reboot) {
+ // User wants to delete previous range test(s)
+ LOG_INFO("Range Test Module - Clearing out previous test file");
+ rangeTestModuleRadio->removeFile();
+ }
if (moduleConfig.range_test.sender) {
LOG_INFO("Init Range Test Module -- Sender");
started = millis(); // make a note of when we started
@@ -130,7 +135,7 @@ void RangeTestModuleRadio::sendPayload(NodeNum dest, bool wantReplies)
ProcessMessage RangeTestModuleRadio::handleReceived(const meshtastic_MeshPacket &mp)
{
-#if defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_PORTDUINO)
+#if defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_STM32WL) || defined(ARCH_PORTDUINO)
if (moduleConfig.range_test.enabled) {
@@ -141,7 +146,6 @@ ProcessMessage RangeTestModuleRadio::handleReceived(const meshtastic_MeshPacket
*/
if (!isFromUs(&mp)) {
-
if (moduleConfig.range_test.save) {
appendFile(mp);
}
@@ -155,6 +159,7 @@ ProcessMessage RangeTestModuleRadio::handleReceived(const meshtastic_MeshPacket
LOG_DEBUG("---- Received Packet:");
LOG_DEBUG("mp.from %d", mp.from);
LOG_DEBUG("mp.rx_snr %f", mp.rx_snr);
+ LOG_DEBUG("mp.rx_rssi %f", mp.rx_rssi);
LOG_DEBUG("mp.hop_limit %d", mp.hop_limit);
LOG_DEBUG("---- Node Information of Received Packet (mp.from):");
LOG_DEBUG("n->user.long_name %s", n->user.long_name);
@@ -230,8 +235,8 @@ bool RangeTestModuleRadio::appendFile(const meshtastic_MeshPacket &mp)
}
// Print the CSV header
- if (fileToWrite.println(
- "time,from,sender name,sender lat,sender long,rx lat,rx long,rx elevation,rx snr,distance,hop limit,payload")) {
+ if (fileToWrite.println("time,from,sender name,sender lat,sender long,rx lat,rx long,rx elevation,rx "
+ "snr,distance,hop limit,payload,rx rssi")) {
LOG_INFO("File was written");
} else {
LOG_ERROR("File write failed");
@@ -293,9 +298,46 @@ bool RangeTestModuleRadio::appendFile(const meshtastic_MeshPacket &mp)
// TODO: If quotes are found in the payload, it has to be escaped.
fileToAppend.printf("\"%s\"\n", p.payload.bytes);
+ fileToAppend.printf("%i,", mp.rx_rssi); // RX RSSI
+
fileToAppend.flush();
fileToAppend.close();
-#endif
return 1;
+
+#else
+ LOG_ERROR("Failed to store range test results - feature only available for ESP32");
+
+ return 0;
+#endif
+}
+
+bool RangeTestModuleRadio::removeFile()
+{
+#ifdef ARCH_ESP32
+ if (!FSBegin()) {
+ LOG_DEBUG("An Error has occurred while mounting the filesystem");
+ return 0;
+ }
+
+ if (!FSCom.exists("/static/rangetest.csv")) {
+ LOG_DEBUG("No range tests found.");
+ return 0;
+ }
+
+ LOG_INFO("Deleting previous range test.");
+ bool result = FSCom.remove("/static/rangetest.csv");
+
+ if (!result) {
+ LOG_ERROR("Failed to delete range test.");
+ return 0;
+ }
+ LOG_INFO("Range test removed.");
+
+ return 1;
+#else
+ LOG_ERROR("Failed to remove range test results - feature only available for ESP32");
+
+ return 0;
+#endif
}
\ No newline at end of file
diff --git a/src/modules/RangeTestModule.h b/src/modules/RangeTestModule.h
index b632d343e..0512e70a8 100644
--- a/src/modules/RangeTestModule.h
+++ b/src/modules/RangeTestModule.h
@@ -44,6 +44,11 @@ class RangeTestModuleRadio : public SinglePortModule
*/
bool appendFile(const meshtastic_MeshPacket &mp);
+ /**
+ * Cleanup range test data from filesystem
+ */
+ bool removeFile();
+
protected:
/** Called to handle a particular incoming message
diff --git a/src/modules/RoutingModule.cpp b/src/modules/RoutingModule.cpp
index b10413cc8..05173983c 100644
--- a/src/modules/RoutingModule.cpp
+++ b/src/modules/RoutingModule.cpp
@@ -42,17 +42,19 @@ bool RoutingModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, mesh
meshtastic_MeshPacket *RoutingModule::allocReply()
{
- if (config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER)
- return NULL;
assert(currentRequest);
return NULL;
}
-void RoutingModule::sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex, uint8_t hopLimit)
+void RoutingModule::sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex, uint8_t hopLimit,
+ bool ackWantsAck)
{
auto p = allocAckNak(err, to, idFrom, chIndex, hopLimit);
+ // Allow the caller to set want_ack on this ACK packet if it's important that the ACK be delivered reliably
+ p->want_ack = ackWantsAck;
+
router->sendLocal(p); // we sometimes send directly to the local node
}
@@ -73,7 +75,7 @@ uint8_t RoutingModule::getHopLimitForResponse(uint8_t hopStart, uint8_t hopLimit
return Default::getConfiguredOrDefaultHopLimit(config.lora.hop_limit); // Use the default hop limit
}
-RoutingModule::RoutingModule() : ProtobufModule(ROUTING_MODULE, meshtastic_PortNum_ROUTING_APP, &meshtastic_Routing_msg)
+RoutingModule::RoutingModule() : ProtobufModule("routing", meshtastic_PortNum_ROUTING_APP, &meshtastic_Routing_msg)
{
isPromiscuous = true;
diff --git a/src/modules/RoutingModule.h b/src/modules/RoutingModule.h
index 7b43a6e98..a4e0679d0 100644
--- a/src/modules/RoutingModule.h
+++ b/src/modules/RoutingModule.h
@@ -2,8 +2,6 @@
#include "Channels.h"
#include "ProtobufModule.h"
-static const char *ROUTING_MODULE = "routing";
-
/**
* Routing module for router control messages
*/
@@ -15,8 +13,8 @@ class RoutingModule : public ProtobufModule
*/
RoutingModule();
- virtual void sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex,
- uint8_t hopLimit = 0);
+ virtual void sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex, uint8_t hopLimit = 0,
+ bool ackWantsAck = false);
// Given the hopStart and hopLimit upon reception of a request, return the hop limit to use for the response
uint8_t getHopLimitForResponse(uint8_t hopStart, uint8_t hopLimit);
diff --git a/src/modules/SerialModule.cpp b/src/modules/SerialModule.cpp
index 866497ecc..719e342b1 100644
--- a/src/modules/SerialModule.cpp
+++ b/src/modules/SerialModule.cpp
@@ -45,9 +45,12 @@
*/
+#ifdef HELTEC_MESH_SOLAR
+#include "meshSolarApp.h"
+#endif
-#if (defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_RP2040)) && !defined(CONFIG_IDF_TARGET_ESP32S2) && \
- !defined(CONFIG_IDF_TARGET_ESP32C3)
+#if (defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_RP2040) || defined(ARCH_STM32WL)) && \
+ !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3)
#define RX_BUFFER 256
#define TIMEOUT 250
@@ -60,15 +63,25 @@
SerialModule *serialModule;
SerialModuleRadio *serialModuleRadio;
-#if defined(TTGO_T_ECHO) || defined(T_ECHO_LITE) || defined(CANARYONE) || defined(MESHLINK) || defined(ELECROW_ThinkNode_M1) || \
- defined(ELECROW_ThinkNode_M5)
-SerialModule::SerialModule() : StreamAPI(&Serial), concurrency::OSThread("Serial") {}
+#if defined(TTGO_T_ECHO) || defined(CANARYONE) || defined(MESHLINK) || defined(ELECROW_ThinkNode_M1) || \
+ defined(ELECROW_ThinkNode_M5) || defined(HELTEC_MESH_SOLAR) || defined(T_ECHO_LITE) || defined(ELECROW_ThinkNode_M3) || \
+ defined(MUZI_BASE)
+SerialModule::SerialModule() : StreamAPI(&Serial), concurrency::OSThread("Serial")
+{
+ api_type = TYPE_SERIAL;
+}
static Print *serialPrint = &Serial;
-#elif defined(CONFIG_IDF_TARGET_ESP32C6)
-SerialModule::SerialModule() : StreamAPI(&Serial1), concurrency::OSThread("Serial") {}
+#elif defined(CONFIG_IDF_TARGET_ESP32C6) || defined(RAK3172) || defined(EBYTE_E77_MBL)
+SerialModule::SerialModule() : StreamAPI(&Serial1), concurrency::OSThread("Serial")
+{
+ api_type = TYPE_SERIAL;
+}
static Print *serialPrint = &Serial1;
#else
-SerialModule::SerialModule() : StreamAPI(&Serial2), concurrency::OSThread("Serial") {}
+SerialModule::SerialModule() : StreamAPI(&Serial2), concurrency::OSThread("Serial")
+{
+ api_type = TYPE_SERIAL;
+}
static Print *serialPrint = &Serial2;
#endif
@@ -78,7 +91,8 @@ size_t serialPayloadSize;
bool SerialModule::isValidConfig(const meshtastic_ModuleConfig_SerialConfig &config)
{
if (config.override_console_serial_port && !IS_ONE_OF(config.mode, meshtastic_ModuleConfig_SerialConfig_Serial_Mode_NMEA,
- meshtastic_ModuleConfig_SerialConfig_Serial_Mode_CALTOPO)) {
+ meshtastic_ModuleConfig_SerialConfig_Serial_Mode_CALTOPO,
+ meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MS_CONFIG)) {
const char *warning =
"Invalid Serial config: override console serial port is only supported in NMEA and CalTopo output-only modes.";
LOG_ERROR(warning);
@@ -169,7 +183,18 @@ int32_t SerialModule::runOnce()
Serial.begin(baud);
Serial.setTimeout(moduleConfig.serial.timeout > 0 ? moduleConfig.serial.timeout : TIMEOUT);
}
-
+#elif defined(ARCH_STM32WL)
+#ifndef RAK3172
+ HardwareSerial *serialInstance = &Serial2;
+#else
+ HardwareSerial *serialInstance = &Serial1;
+#endif
+ if (moduleConfig.serial.rxd && moduleConfig.serial.txd) {
+ serialInstance->setTx(moduleConfig.serial.txd);
+ serialInstance->setRx(moduleConfig.serial.rxd);
+ }
+ serialInstance->begin(baud);
+ serialInstance->setTimeout(moduleConfig.serial.timeout > 0 ? moduleConfig.serial.timeout : TIMEOUT);
#elif defined(ARCH_ESP32)
if (moduleConfig.serial.rxd && moduleConfig.serial.txd) {
@@ -180,7 +205,7 @@ int32_t SerialModule::runOnce()
Serial.setTimeout(moduleConfig.serial.timeout > 0 ? moduleConfig.serial.timeout : TIMEOUT);
}
#elif !defined(TTGO_T_ECHO) && !defined(T_ECHO_LITE) && !defined(CANARYONE) && !defined(MESHLINK) && \
- !defined(ELECROW_ThinkNode_M1) && !defined(ELECROW_ThinkNode_M5)
+ !defined(ELECROW_ThinkNode_M1) && !defined(ELECROW_ThinkNode_M3) && !defined(ELECROW_ThinkNode_M5) && !defined(MUZI_BASE)
if (moduleConfig.serial.rxd && moduleConfig.serial.txd) {
#ifdef ARCH_RP2040
Serial2.setFIFOSize(RX_BUFFER);
@@ -237,17 +262,32 @@ int32_t SerialModule::runOnce()
}
#if !defined(TTGO_T_ECHO) && !defined(T_ECHO_LITE) && !defined(CANARYONE) && !defined(MESHLINK) && \
- !defined(ELECROW_ThinkNode_M1) && !defined(ELECROW_ThinkNode_M5)
+ !defined(ELECROW_ThinkNode_M1) && !defined(ELECROW_ThinkNode_M3) && !defined(ELECROW_ThinkNode_M5) && !defined(MUZI_BASE)
else if ((moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_WS85)) {
processWXSerial();
- } else {
+ }
+#if defined(HELTEC_MESH_SOLAR)
+ else if ((moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MS_CONFIG)) {
+ serialPayloadSize = Serial.readBytes(serialBytes, sizeof(serialBytes) - 1);
+ // If the parsing fails, the following parsing will be performed.
+ if ((serialPayloadSize > 0) && (meshSolarCmdHandle(serialBytes) != 0)) {
+ return runOncePart(serialBytes, serialPayloadSize);
+ }
+ }
+#endif
+ else {
#if defined(CONFIG_IDF_TARGET_ESP32C6)
while (Serial1.available()) {
serialPayloadSize = Serial1.readBytes(serialBytes, meshtastic_Constants_DATA_PAYLOAD_LEN);
#else
- while (Serial2.available()) {
- serialPayloadSize = Serial2.readBytes(serialBytes, meshtastic_Constants_DATA_PAYLOAD_LEN);
+#ifndef RAK3172
+ HardwareSerial *serialInstance = &Serial2;
+#else
+ HardwareSerial *serialInstance = &Serial1;
+#endif
+ while (serialInstance->available()) {
+ serialPayloadSize = serialInstance->readBytes(serialBytes, meshtastic_Constants_DATA_PAYLOAD_LEN);
#endif
serialModuleRadio->sendPayload();
}
@@ -497,7 +537,8 @@ ParsedLine parseLine(const char *line)
void SerialModule::processWXSerial()
{
#if !defined(TTGO_T_ECHO) && !defined(T_ECHO_LITE) && !defined(CANARYONE) && !defined(CONFIG_IDF_TARGET_ESP32C6) && \
- !defined(MESHLINK) && !defined(ELECROW_ThinkNode_M1) && !defined(ELECROW_ThinkNode_M5)
+ !defined(MESHLINK) && !defined(ELECROW_ThinkNode_M1) && !defined(ELECROW_ThinkNode_M3) && !defined(ELECROW_ThinkNode_M5) && \
+ !defined(ARCH_STM32WL) && !defined(MUZI_BASE)
static unsigned int lastAveraged = 0;
static unsigned int averageIntervalMillis = 300000; // 5 minutes hard coded.
static double dir_sum_sin = 0;
diff --git a/src/modules/SerialModule.h b/src/modules/SerialModule.h
index 1c74c927c..dbe4f75db 100644
--- a/src/modules/SerialModule.h
+++ b/src/modules/SerialModule.h
@@ -8,8 +8,8 @@
#include
#include
-#if (defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_RP2040)) && !defined(CONFIG_IDF_TARGET_ESP32S2) && \
- !defined(CONFIG_IDF_TARGET_ESP32C3)
+#if (defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_RP2040) || defined(ARCH_STM32WL)) && \
+ !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3)
class SerialModule : public StreamAPI, private concurrency::OSThread
{
diff --git a/src/modules/StatusLEDModule.cpp b/src/modules/StatusLEDModule.cpp
new file mode 100644
index 000000000..fc9ed310e
--- /dev/null
+++ b/src/modules/StatusLEDModule.cpp
@@ -0,0 +1,94 @@
+#include "StatusLEDModule.h"
+#include "MeshService.h"
+#include "configuration.h"
+#include
+
+/*
+StatusLEDModule manages the device's status LEDs, updating their states based on power and Bluetooth status.
+It reflects charging, charged, discharging, and Bluetooth connection states using the appropriate LEDs.
+*/
+StatusLEDModule *statusLEDModule;
+
+StatusLEDModule::StatusLEDModule() : concurrency::OSThread("StatusLEDModule")
+{
+ bluetoothStatusObserver.observe(&bluetoothStatus->onNewStatus);
+ powerStatusObserver.observe(&powerStatus->onNewStatus);
+}
+
+int StatusLEDModule::handleStatusUpdate(const meshtastic::Status *arg)
+{
+ switch (arg->getStatusType()) {
+ case STATUS_TYPE_POWER: {
+ meshtastic::PowerStatus *powerStatus = (meshtastic::PowerStatus *)arg;
+ if (powerStatus->getHasUSB()) {
+ power_state = charging;
+ if (powerStatus->getBatteryChargePercent() >= 100) {
+ power_state = charged;
+ }
+ } else {
+ power_state = discharging;
+ }
+ break;
+ }
+ case STATUS_TYPE_BLUETOOTH: {
+ meshtastic::BluetoothStatus *bluetoothStatus = (meshtastic::BluetoothStatus *)arg;
+ switch (bluetoothStatus->getConnectionState()) {
+ case meshtastic::BluetoothStatus::ConnectionState::DISCONNECTED: {
+ ble_state = unpaired;
+ PAIRING_LED_starttime = millis();
+ break;
+ }
+ case meshtastic::BluetoothStatus::ConnectionState::PAIRING: {
+ ble_state = pairing;
+ PAIRING_LED_starttime = millis();
+ break;
+ }
+ case meshtastic::BluetoothStatus::ConnectionState::CONNECTED: {
+ ble_state = connected;
+ PAIRING_LED_starttime = millis();
+ break;
+ }
+ }
+
+ break;
+ }
+ }
+ return 0;
+};
+
+int32_t StatusLEDModule::runOnce()
+{
+
+ if (power_state == charging) {
+ CHARGE_LED_state = !CHARGE_LED_state;
+ } else if (power_state == charged) {
+ CHARGE_LED_state = LED_STATE_ON;
+ } else {
+ CHARGE_LED_state = LED_STATE_OFF;
+ }
+
+ if (!config.bluetooth.enabled || PAIRING_LED_starttime + 30 * 1000 < millis()) {
+ PAIRING_LED_state = LED_STATE_OFF;
+ } else if (ble_state == unpaired) {
+ if (slowTrack) {
+ PAIRING_LED_state = !PAIRING_LED_state;
+ slowTrack = false;
+ } else {
+ slowTrack = true;
+ }
+ } else if (ble_state == pairing) {
+ PAIRING_LED_state = !PAIRING_LED_state;
+ } else {
+ PAIRING_LED_state = LED_STATE_ON;
+ }
+
+#ifdef LED_CHARGE
+ digitalWrite(LED_CHARGE, CHARGE_LED_state);
+#endif
+ // digitalWrite(green_LED_PIN, LED_STATE_OFF);
+#ifdef LED_PAIRING
+ digitalWrite(LED_PAIRING, PAIRING_LED_state);
+#endif
+
+ return (my_interval);
+}
diff --git a/src/modules/StatusLEDModule.h b/src/modules/StatusLEDModule.h
new file mode 100644
index 000000000..d9e3a4f33
--- /dev/null
+++ b/src/modules/StatusLEDModule.h
@@ -0,0 +1,44 @@
+#pragma once
+
+#include "BluetoothStatus.h"
+#include "MeshModule.h"
+#include "PowerStatus.h"
+#include "concurrency/OSThread.h"
+#include "configuration.h"
+#include
+#include
+
+class StatusLEDModule : private concurrency::OSThread
+{
+ bool slowTrack = false;
+
+ public:
+ StatusLEDModule();
+
+ int handleStatusUpdate(const meshtastic::Status *);
+
+ protected:
+ unsigned int my_interval = 1000; // interval in millisconds
+ virtual int32_t runOnce() override;
+
+ CallbackObserver bluetoothStatusObserver =
+ CallbackObserver(this, &StatusLEDModule::handleStatusUpdate);
+ CallbackObserver powerStatusObserver =
+ CallbackObserver(this, &StatusLEDModule::handleStatusUpdate);
+
+ private:
+ bool CHARGE_LED_state = LED_STATE_OFF;
+ bool PAIRING_LED_state = LED_STATE_OFF;
+
+ uint32_t PAIRING_LED_starttime = 0;
+
+ enum PowerState { discharging, charging, charged };
+
+ PowerState power_state = discharging;
+
+ enum BLEState { unpaired, pairing, connected };
+
+ BLEState ble_state = unpaired;
+};
+
+extern StatusLEDModule *statusLEDModule;
diff --git a/src/modules/StoreForwardModule.cpp b/src/modules/StoreForwardModule.cpp
index 72ac99118..b8a710bf5 100644
--- a/src/modules/StoreForwardModule.cpp
+++ b/src/modules/StoreForwardModule.cpp
@@ -204,6 +204,10 @@ void StoreForwardModule::historyAdd(const meshtastic_MeshPacket &mp)
this->packetHistory[this->packetHistoryTotalCount].payload_size = p.payload.size;
this->packetHistory[this->packetHistoryTotalCount].rx_rssi = mp.rx_rssi;
this->packetHistory[this->packetHistoryTotalCount].rx_snr = mp.rx_snr;
+ this->packetHistory[this->packetHistoryTotalCount].hop_start = mp.hop_start;
+ this->packetHistory[this->packetHistoryTotalCount].hop_limit = mp.hop_limit;
+ this->packetHistory[this->packetHistoryTotalCount].via_mqtt = mp.via_mqtt;
+ this->packetHistory[this->packetHistoryTotalCount].transport_mechanism = mp.transport_mechanism;
memcpy(this->packetHistory[this->packetHistoryTotalCount].payload, p.payload.bytes, meshtastic_Constants_DATA_PAYLOAD_LEN);
this->packetHistoryTotalCount++;
@@ -256,6 +260,10 @@ meshtastic_MeshPacket *StoreForwardModule::preparePayload(NodeNum dest, uint32_t
p->decoded.emoji = (uint32_t)this->packetHistory[i].emoji;
p->rx_rssi = this->packetHistory[i].rx_rssi;
p->rx_snr = this->packetHistory[i].rx_snr;
+ p->hop_start = this->packetHistory[i].hop_start;
+ p->hop_limit = this->packetHistory[i].hop_limit;
+ p->via_mqtt = this->packetHistory[i].via_mqtt;
+ p->transport_mechanism = (meshtastic_MeshPacket_TransportMechanism)this->packetHistory[i].transport_mechanism;
// Let's assume that if the server received the S&F request that the client is in range.
// TODO: Make this configurable.
diff --git a/src/modules/StoreForwardModule.h b/src/modules/StoreForwardModule.h
index 25836eded..148568e1b 100644
--- a/src/modules/StoreForwardModule.h
+++ b/src/modules/StoreForwardModule.h
@@ -21,6 +21,10 @@ struct PacketHistoryStruct {
pb_size_t payload_size;
int32_t rx_rssi;
float rx_snr;
+ uint8_t hop_start;
+ uint8_t hop_limit;
+ bool via_mqtt;
+ uint8_t transport_mechanism;
};
class StoreForwardModule : private concurrency::OSThread, public ProtobufModule
diff --git a/src/modules/SystemCommandsModule.cpp b/src/modules/SystemCommandsModule.cpp
index 74b9678f4..7fa4485c8 100644
--- a/src/modules/SystemCommandsModule.cpp
+++ b/src/modules/SystemCommandsModule.cpp
@@ -1,4 +1,5 @@
#include "SystemCommandsModule.h"
+#include "input/InputBroker.h"
#include "meshUtils.h"
#if HAS_SCREEN
#include "graphics/Screen.h"
@@ -22,7 +23,7 @@ SystemCommandsModule::SystemCommandsModule()
int SystemCommandsModule::handleInputEvent(const InputEvent *event)
{
- LOG_INFO("Input event %u! kb %u", event->inputEvent, event->kbchar);
+ LOG_INPUT("SystemCommands Input event %u! kb %u", event->inputEvent, event->kbchar);
// System commands (all others fall through)
switch (event->kbchar) {
// Fn key symbols
@@ -85,10 +86,8 @@ int SystemCommandsModule::handleInputEvent(const InputEvent *event)
switch (event->inputEvent) {
// GPS
case INPUT_BROKER_GPS_TOGGLE:
- LOG_WARN("GPS Toggle");
#if !MESHTASTIC_EXCLUDE_GPS
if (gps) {
- LOG_WARN("GPS Toggle2");
if (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED &&
config.position.fixed_position == false) {
nodeDB->clearLocalPosition();
diff --git a/src/modules/Telemetry/DeviceTelemetry.cpp b/src/modules/Telemetry/DeviceTelemetry.cpp
index 08fd09db0..066b9361d 100644
--- a/src/modules/Telemetry/DeviceTelemetry.cpp
+++ b/src/modules/Telemetry/DeviceTelemetry.cpp
@@ -26,8 +26,8 @@ int32_t DeviceTelemetryModule::runOnce()
Default::getConfiguredOrDefaultMsScaled(moduleConfig.telemetry.device_update_interval,
default_telemetry_broadcast_interval_secs, numOnlineNodes))) &&
airTime->isTxAllowedChannelUtil(!isImpoliteRole) && airTime->isTxAllowedAirUtil() &&
- config.device.role != meshtastic_Config_DeviceConfig_Role_REPEATER &&
- config.device.role != meshtastic_Config_DeviceConfig_Role_CLIENT_HIDDEN) {
+ config.device.role != meshtastic_Config_DeviceConfig_Role_CLIENT_HIDDEN &&
+ moduleConfig.telemetry.device_telemetry_enabled) {
sendTelemetry();
lastSentToMesh = uptimeLastMs;
} else if (service->isToPhoneQueueEmpty()) {
@@ -44,10 +44,6 @@ int32_t DeviceTelemetryModule::runOnce()
bool DeviceTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Telemetry *t)
{
- // Don't worry about storing telemetry in NodeDB if we're a repeater
- if (config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER)
- return false;
-
if (t->which_variant == meshtastic_Telemetry_device_metrics_tag) {
#if defined(DEBUG_PORT) && !defined(DEBUG_MUTE)
const char *sender = getSenderShortName(mp);
@@ -126,6 +122,7 @@ meshtastic_Telemetry DeviceTelemetryModule::getLocalStatsTelemetry()
telemetry.variant.local_stats.num_packets_rx = RadioLibInterface::instance->rxGood + RadioLibInterface::instance->rxBad;
telemetry.variant.local_stats.num_packets_rx_bad = RadioLibInterface::instance->rxBad;
telemetry.variant.local_stats.num_tx_relay = RadioLibInterface::instance->txRelay;
+ telemetry.variant.local_stats.num_tx_dropped = RadioLibInterface::instance->txDrop;
}
#ifdef ARCH_PORTDUINO
if (SimRadio::instance) {
@@ -133,6 +130,7 @@ meshtastic_Telemetry DeviceTelemetryModule::getLocalStatsTelemetry()
telemetry.variant.local_stats.num_packets_rx = SimRadio::instance->rxGood + SimRadio::instance->rxBad;
telemetry.variant.local_stats.num_packets_rx_bad = SimRadio::instance->rxBad;
telemetry.variant.local_stats.num_tx_relay = SimRadio::instance->txRelay;
+ telemetry.variant.local_stats.num_tx_dropped = SimRadio::instance->txDrop;
}
#else
telemetry.variant.local_stats.heap_total_bytes = memGet.getHeapSize();
@@ -172,7 +170,10 @@ bool DeviceTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly)
telemetry.variant.device_metrics.battery_level, telemetry.variant.device_metrics.voltage,
telemetry.variant.device_metrics.uptime_seconds);
+ DEBUG_HEAP_BEFORE;
meshtastic_MeshPacket *p = allocDataProtobuf(telemetry);
+ DEBUG_HEAP_AFTER("DeviceTelemetryModule::sendTelemetry", p);
+
p->to = dest;
p->decoded.want_response = false;
p->priority = meshtastic_MeshPacket_Priority_BACKGROUND;
diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp
index 8926b171c..29e815092 100644
--- a/src/modules/Telemetry/EnvironmentTelemetry.cpp
+++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp
@@ -30,172 +30,112 @@
namespace graphics
{
-extern void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *titleStr, bool battery_only);
+extern void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *titleStr, bool force_no_invert,
+ bool show_date);
}
#if __has_include()
#include "Sensor/AHT10.h"
-AHT10Sensor aht10Sensor;
-#else
-NullSensor aht10Sensor;
#endif
+
#if __has_include()
#include "Sensor/BME280Sensor.h"
-BME280Sensor bme280Sensor;
-#else
-NullSensor bmp280Sensor;
#endif
#if __has_include()
#include "Sensor/BMP085Sensor.h"
-BMP085Sensor bmp085Sensor;
-#else
-NullSensor bmp085Sensor;
#endif
#if __has_include()
#include "Sensor/BMP280Sensor.h"
-BMP280Sensor bmp280Sensor;
-#else
-NullSensor bme280Sensor;
#endif
#if __has_include()
#include "Sensor/LTR390UVSensor.h"
-LTR390UVSensor ltr390uvSensor;
-#else
-NullSensor ltr390uvSensor;
#endif
#if __has_include(