Compare commits

...

7 Commits

Author SHA1 Message Date
Erayd
acdb89d91e
Merge 7d3af3aa8e into b5a8e8f51b 2025-07-29 12:40:20 +12:00
Chloe Bethel
b5a8e8f51b
DEBUG_MUTE correctness (#7492)
Some checks failed
CI / docker-alp-amd64-tft (push) Has been cancelled
CI / docker-deb-arm64 (push) Has been cancelled
CI / docker-deb-armv7 (push) Has been cancelled
CI / check (push) Has been cancelled
CI / build-esp32 (push) Has been cancelled
CI / build-esp32s3 (push) Has been cancelled
CI / build-esp32c3 (push) Has been cancelled
CI / build-esp32c6 (push) Has been cancelled
CI / build-nrf52840 (push) Has been cancelled
CI / build-rp2040 (push) Has been cancelled
CI / build-rp2350 (push) Has been cancelled
CI / build-stm32 (push) Has been cancelled
CI / gather-artifacts (esp32) (push) Has been cancelled
CI / gather-artifacts (esp32c3) (push) Has been cancelled
CI / gather-artifacts (esp32c6) (push) Has been cancelled
CI / gather-artifacts (esp32s3) (push) Has been cancelled
CI / gather-artifacts (nrf52840) (push) Has been cancelled
CI / gather-artifacts (rp2040) (push) Has been cancelled
CI / gather-artifacts (rp2350) (push) Has been cancelled
CI / gather-artifacts (stm32) (push) Has been cancelled
CI / release-artifacts (push) Has been cancelled
CI / release-firmware (esp32) (push) Has been cancelled
CI / release-firmware (esp32c3) (push) Has been cancelled
CI / release-firmware (esp32c6) (push) Has been cancelled
CI / release-firmware (esp32s3) (push) Has been cancelled
CI / release-firmware (nrf52840) (push) Has been cancelled
CI / release-firmware (rp2040) (push) Has been cancelled
CI / release-firmware (rp2350) (push) Has been cancelled
CI / release-firmware (stm32) (push) Has been cancelled
CI / publish-firmware (push) Has been cancelled
* treewide: make 'ifdef DEBUG_PORT' guards also take into account DEBUG_MUTE

* stm32wl: Add a guard against having debug prints turned on without PIO_FRAMEWORK_ARDUINO_NANOLIB_FLOAT_PRINTF defined

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-07-29 08:51:38 +10:00
Ben Meadors
cc5d00e211
Core portnums rebroadcast mode whitelist instead of blacklist (#7487)
Some checks are pending
CI / build-rp2040 (push) Blocked by required conditions
CI / build-rp2350 (push) Blocked by required conditions
CI / build-stm32 (push) Blocked by required conditions
CI / build-debian-src (push) Waiting to run
CI / package-pio-deps-native-tft (push) Waiting to run
CI / test-native (push) Waiting to run
CI / docker-deb-amd64 (push) Waiting to run
CI / docker-deb-amd64-tft (push) Waiting to run
CI / docker-alp-amd64 (push) Waiting to run
CI / docker-alp-amd64-tft (push) Waiting to run
CI / docker-deb-arm64 (push) Waiting to run
CI / docker-deb-armv7 (push) Waiting to run
CI / gather-artifacts (esp32) (push) Blocked by required conditions
CI / gather-artifacts (esp32c3) (push) Blocked by required conditions
CI / gather-artifacts (esp32c6) (push) Blocked by required conditions
CI / gather-artifacts (esp32s3) (push) Blocked by required conditions
CI / gather-artifacts (nrf52840) (push) Blocked by required conditions
CI / gather-artifacts (rp2040) (push) Blocked by required conditions
CI / gather-artifacts (rp2350) (push) Blocked by required conditions
CI / gather-artifacts (stm32) (push) Blocked by required conditions
CI / release-artifacts (push) Blocked by required conditions
CI / release-firmware (esp32) (push) Blocked by required conditions
CI / release-firmware (esp32c3) (push) Blocked by required conditions
CI / release-firmware (esp32c6) (push) Blocked by required conditions
CI / release-firmware (esp32s3) (push) Blocked by required conditions
CI / release-firmware (nrf52840) (push) Blocked by required conditions
CI / release-firmware (rp2040) (push) Blocked by required conditions
CI / release-firmware (rp2350) (push) Blocked by required conditions
CI / release-firmware (stm32) (push) Blocked by required conditions
CI / publish-firmware (push) Blocked by required conditions
2025-07-28 12:37:37 -05:00
Ben Meadors
1a8ab2aadc
NodeDB count on MyNodeInfo for client progress reporting (#7489) 2025-07-28 12:23:59 -05:00
Thomas Göttgens
608fdc6f52
Santa may be checking his list twice, but we only need this in the platformio.ini (#7490) 2025-07-28 09:47:46 -05:00
rradillen
1d8638b47d
[7353] Add all telemetry fields to json output (#7363)
* Serializer bugfix

* Remove duplicate test

* fix tests

* fix float precision issues

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-07-28 09:23:04 -05:00
Steve Gilberd
7d3af3aa8e
Tips robot virtual node / relayer to different LoRa modes & channels
Note that this commit has details hardcoded for the Wellington (NZ)
mesh, and also requires the following patch to the protobufs:

-----
diff --git a/meshtastic/mesh.proto b/meshtastic/mesh.proto
index 03162d8..ec54c99 100644
--- a/meshtastic/mesh.proto
+++ b/meshtastic/mesh.proto
@@ -1393,6 +1393,21 @@ message MeshPacket {
    * Set by the firmware internally, clients are not supposed to set this.
    */
   uint32 tx_after = 20;
+
+  /*
+   * The modem preset to use fo rthis packet
+   */
+  uint32 modem_preset = 21;
+
+  /*
+   * The frequency slot to use for this packet
+   */
+  uint32 frequency_slot = 22;
+
+  /*
+   * Whether the packet has a nonstandard radio config
+   */
+  bool nonstandard_radio_config = 23;
 }

 /*
-----
2025-07-01 11:42:27 +12:00
29 changed files with 1276 additions and 28 deletions

View File

@ -94,6 +94,13 @@ class Channels
bool ensureLicensedOperation();
/**
* Validate a channel, fixing any errors as needed
*/
meshtastic_Channel &fixupChannel(ChannelIndex chIndex);
int16_t getHash(ChannelIndex i) { return hashes[i]; }
private:
/** Given a channel index, change to use the crypto key specified by that index
*
@ -111,13 +118,6 @@ class Channels
*/
int16_t generateHash(ChannelIndex channelNum);
int16_t getHash(ChannelIndex i) { return hashes[i]; }
/**
* Validate a channel, fixing any errors as needed
*/
meshtastic_Channel &fixupChannel(ChannelIndex chIndex);
/**
* Writes the default lora config
*/

View File

@ -205,6 +205,7 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
// app not to send locations on our behalf.
fromRadioScratch.which_payload_variant = meshtastic_FromRadio_my_info_tag;
strncpy(myNodeInfo.pio_env, optstr(APP_ENV), sizeof(myNodeInfo.pio_env));
myNodeInfo.nodedb_count = static_cast<uint16_t>(nodeDB->getNumMeshNodes());
fromRadioScratch.my_info = myNodeInfo;
state = STATE_SEND_UIDATA;

View File

@ -8,6 +8,9 @@
#include "error.h"
#include "main.h"
#include "mesh-pb-constants.h"
#if !MESHTASTIC_EXCLUDE_TIPS
#include "modules/MeshTipsModule.h"
#endif
#include <pb_decode.h>
#include <pb_encode.h>
@ -248,6 +251,9 @@ void RadioLibInterface::onNotify(uint32_t notification)
switch (notification) {
case ISR_TX:
handleTransmitInterrupt();
#if !MESHTASTIC_EXCLUDE_TIPS
MeshTipsModule::configureRadioForPacket(this, txQueue.getFront());
#endif
startReceive();
setTransmitDelay();
break;
@ -270,9 +276,16 @@ void RadioLibInterface::onNotify(uint32_t notification)
if (delay_remaining > 0) {
// There's still some delay pending on this packet, so resume waiting for it to elapse
notifyLater(delay_remaining, TRANSMIT_DELAY_COMPLETED, false);
#if !MESHTASTIC_EXCLUDE_TIPS
} else if (MeshTipsModule::configureRadioForPacket(this, txp)) {
// We just switched radio config, so wait to ensure the new channel is available
setTransmitDelay();
#endif
} else {
if (isChannelActive()) { // check if there is currently a LoRa packet on the channel
startReceive(); // try receiving this packet, afterwards we'll be trying to transmit again
if (!txp->nonstandard_radio_config) {
startReceive(); // try receiving this packet, afterwards we'll be trying to transmit again
}
setTransmitDelay();
} else {
// Send any outgoing packets we have ready as fast as possible to keep the time between channel scan and

View File

@ -224,9 +224,10 @@ ErrorCode Router::send(meshtastic_MeshPacket *p)
if (!config.lora.override_duty_cycle && myRegion->dutyCycle < 100) {
float hourlyTxPercent = airTime->utilizationTXPercent();
if (hourlyTxPercent > myRegion->dutyCycle) {
#ifdef DEBUG_PORT
uint8_t silentMinutes = airTime->getSilentMinutes(hourlyTxPercent, myRegion->dutyCycle);
LOG_WARN("Duty cycle limit exceeded. Aborting send for now, you can send again in %d mins", silentMinutes);
meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed();
cn->has_reply_id = true;
cn->reply_id = p->id;
@ -234,7 +235,7 @@ ErrorCode Router::send(meshtastic_MeshPacket *p)
cn->time = getValidTime(RTCQualityFromNet);
sprintf(cn->message, "Duty cycle limit exceeded. You can send again in %d mins", silentMinutes);
service->sendClientNotification(cn);
#endif
meshtastic_Routing_Error err = meshtastic_Routing_Error_DUTY_CYCLE_LIMIT;
if (isFromUs(p)) { // only send NAK to API, not to the mesh
abortSendAndNak(err, p);
@ -651,11 +652,12 @@ void Router::handleReceived(meshtastic_MeshPacket *p, RxSource src)
shouldIgnoreNonstandardPorts = true;
#endif
if (shouldIgnoreNonstandardPorts && p->which_payload_variant == meshtastic_MeshPacket_decoded_tag &&
IS_ONE_OF(p->decoded.portnum, meshtastic_PortNum_ATAK_FORWARDER, meshtastic_PortNum_ATAK_PLUGIN,
meshtastic_PortNum_PAXCOUNTER_APP, meshtastic_PortNum_IP_TUNNEL_APP, meshtastic_PortNum_AUDIO_APP,
meshtastic_PortNum_PRIVATE_APP, meshtastic_PortNum_DETECTION_SENSOR_APP, meshtastic_PortNum_RANGE_TEST_APP,
meshtastic_PortNum_REMOTE_HARDWARE_APP)) {
LOG_DEBUG("Ignore packet on blacklisted portnum for CORE_PORTNUMS_ONLY");
!IS_ONE_OF(p->decoded.portnum, meshtastic_PortNum_TEXT_MESSAGE_APP, meshtastic_PortNum_TEXT_MESSAGE_COMPRESSED_APP,
meshtastic_PortNum_POSITION_APP, meshtastic_PortNum_NODEINFO_APP, meshtastic_PortNum_ROUTING_APP,
meshtastic_PortNum_TELEMETRY_APP, meshtastic_PortNum_ADMIN_APP, meshtastic_PortNum_ALERT_APP,
meshtastic_PortNum_KEY_VERIFICATION_APP, meshtastic_PortNum_WAYPOINT_APP,
meshtastic_PortNum_STORE_FORWARD_APP, meshtastic_PortNum_TRACEROUTE_APP)) {
LOG_DEBUG("Ignore packet on non-standard portnum for CORE_PORTNUMS_ONLY");
cancelSending(p->from, p->id);
skipHandle = true;
}

View File

@ -0,0 +1,242 @@
#include "MeshTipsModule.h"
#include "Default.h"
#include "MeshService.h"
#include "RTC.h"
#include "RadioInterface.h"
#include "configuration.h"
#include "main.h"
#include <Throttle.h>
#define NODENUM_TIPS 0x00000004
static meshtastic_Config_LoRaConfig_ModemPreset originalModemPreset; // original modem preset
static uint16_t originalLoraChannel; // original frequency slot
char originalChannelName[sizeof(MeshTipsModule_TXSettings)]; // original channel name
MeshTipsModule::MeshTipsModule()
{
originalModemPreset = config.lora.modem_preset;
originalLoraChannel = config.lora.channel_num;
strncpy(originalChannelName, channels.getPrimary().name, sizeof(originalChannelName));
}
bool MeshTipsModule::configureRadioForPacket(RadioInterface *iface, meshtastic_MeshPacket *p)
{
meshtastic_ChannelSettings *c = (meshtastic_ChannelSettings *)&channels.getPrimary();
if (p && p->from == NODENUM_TIPS && p->nonstandard_radio_config &&
(p->modem_preset != config.lora.modem_preset || p->frequency_slot != config.lora.channel_num)) {
LOG_INFO("Reconfiguring for TX of packet %#08lx (from=%#08lx size=%lu)", p->id, p->from, p->decoded.payload.size);
config.lora.modem_preset = (meshtastic_Config_LoRaConfig_ModemPreset)p->modem_preset;
config.lora.channel_num = p->frequency_slot;
memset(c->name, 0, sizeof(c->name));
switch (p->modem_preset) {
case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO:
strncpy(c->name, "ShortTurbo", sizeof(c->name));
break;
case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_FAST:
strncpy(c->name, "ShortFast", sizeof(c->name));
break;
case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_SLOW:
strncpy(c->name, "ShortSlow", sizeof(c->name));
break;
case meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_FAST:
strncpy(c->name, "MediumFast", sizeof(c->name));
break;
case meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_SLOW:
strncpy(c->name, "MediumSlow", sizeof(c->name));
break;
case meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST:
strncpy(c->name, "LongFast", sizeof(c->name));
break;
case meshtastic_Config_LoRaConfig_ModemPreset_LONG_MODERATE:
strncpy(c->name, "LongMod", sizeof(c->name));
break;
case meshtastic_Config_LoRaConfig_ModemPreset_LONG_SLOW:
strncpy(c->name, "LongSlow", sizeof(c->name));
break;
}
channels.fixupChannel(channels.getPrimaryIndex());
p->channel = channels.getHash(channels.getPrimaryIndex());
iface->reconfigure();
return true;
} else if ((!p || !p->nonstandard_radio_config) &&
(config.lora.modem_preset != originalModemPreset || config.lora.channel_num != originalLoraChannel)) {
LOG_INFO("Reconfiguring for TX of packet %#08lx (from=%#08lx size=%lu)", p->id, p->from, p->decoded.payload.size);
config.lora.modem_preset = originalModemPreset;
config.lora.channel_num = originalLoraChannel;
memset(c->name, 0, sizeof(c->name));
strncpy(c->name, originalChannelName, sizeof(c->name));
channels.fixupChannel(channels.getPrimaryIndex());
iface->reconfigure();
return true;
}
return false;
}
MeshTipsModule_TXSettings MeshTipsModule::stripTargetRadioSettings(meshtastic_MeshPacket *p)
{
MeshTipsModule_TXSettings s = {
.preset = originalModemPreset,
.slot = originalLoraChannel,
};
// clamp final byte of payload, just in case, because I don't know if this is taken care of elsewhere
p->decoded.payload.bytes[p->decoded.payload.size] = 0;
if (!p || strlen((char *)p->decoded.payload.bytes) < 4 || p->decoded.payload.bytes[0] != '#')
return s;
char *msg = strchr((char *)p->decoded.payload.bytes, ' ');
if (!*msg)
return s;
*msg++ = 0;
const char presetString[3] = {p->decoded.payload.bytes[1], p->decoded.payload.bytes[2], 0};
char *slotString = (char *)&p->decoded.payload.bytes[3];
if (!*slotString)
return s;
for (char *c = slotString; *c; c++) {
if (!strchr("1234567890", *c))
return s;
}
uint8_t preset = 0;
if (!strncmp(presetString, "ST", 2))
s.preset = meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO;
else if (!strncmp(presetString, "SF", 2))
s.preset = meshtastic_Config_LoRaConfig_ModemPreset_SHORT_FAST;
else if (!strncmp(presetString, "SS", 2))
s.preset = meshtastic_Config_LoRaConfig_ModemPreset_SHORT_SLOW;
else if (!strncmp(presetString, "MF", 2))
s.preset = meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_FAST;
else if (!strncmp(presetString, "MS", 2))
s.preset = meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_SLOW;
else if (!strncmp(presetString, "LF", 2))
s.preset = meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST;
else if (!strncmp(presetString, "LM", 2))
s.preset = meshtastic_Config_LoRaConfig_ModemPreset_LONG_MODERATE;
else if (!strncmp(presetString, "LS", 2))
s.preset = meshtastic_Config_LoRaConfig_ModemPreset_LONG_SLOW;
else
return s;
s.slot = std::stoi(slotString);
p->decoded.payload.size = 1;
// don't use strcpy, because strcpy has undefined behaviour if src & dst overlap
for (char *a = msg, *b = (char *)p->decoded.payload.bytes; (*b++ = *a++);)
p->decoded.payload.size++;
return s;
}
MeshTipsNodeInfoModule *meshTipsNodeInfoModule;
bool MeshTipsNodeInfoModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_User *pptr)
{
// do nothing if we receive nodeinfo, because we only care about sending our own
return true;
}
void MeshTipsNodeInfoModule::sendTipsNodeInfo()
{
LOG_INFO("Send NodeInfo for mesh tips");
static meshtastic_User u = {
.hw_model = meshtastic_HardwareModel_PRIVATE_HW,
.is_licensed = false,
.role = meshtastic_Config_DeviceConfig_Role_CLIENT_MUTE,
.public_key =
{
.size = 32,
.bytes = {0x39, 0x37, 0x58, 0xe4, 0x05, 0x34, 0x7d, 0xe0, 0x49, 0x73, 0xec, 0xaf, 0xbc, 0x8e, 0x07, 0xe8,
0x66, 0x57, 0xe4, 0xa1, 0x2d, 0x53, 0x0e, 0x26, 0x51, 0x1f, 0x1a, 0x6c, 0xbf, 0xe8, 0x5e, 0x04},
},
.has_is_unmessagable = true,
.is_unmessagable = true,
};
strncpy(u.id, "!mesh_tips", sizeof(u.id));
strncpy(u.long_name, "WLG Mesh Tips Robot", sizeof(u.long_name));
strncpy(u.short_name, "TIPS", sizeof(u.short_name));
meshtastic_MeshPacket *p = allocDataProtobuf(u);
p->to = NODENUM_BROADCAST;
p->from = NODENUM_TIPS;
p->hop_limit = 0;
p->decoded.want_response = false;
p->priority = meshtastic_MeshPacket_Priority_BACKGROUND;
p->modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST;
meshtastic_MeshPacket *p_LF20 = packetPool.allocCopy(*p);
service->sendToMesh(p, RX_SRC_LOCAL, false);
p_LF20->frequency_slot = 20;
p_LF20->nonstandard_radio_config = true;
service->sendToMesh(p_LF20, RX_SRC_LOCAL, false);
}
MeshTipsNodeInfoModule::MeshTipsNodeInfoModule()
: ProtobufModule("nodeinfo_tips", meshtastic_PortNum_NODEINFO_APP, &meshtastic_User_msg),
concurrency::OSThread("MeshTipsNodeInfo")
{
MeshTipsModule();
setIntervalFromNow(setStartDelay()); // Send our initial owner announcement 30 seconds
// after we start (to give network time to setup)
}
int32_t MeshTipsNodeInfoModule::runOnce()
{
if (airTime->isTxAllowedAirUtil() && config.device.role != meshtastic_Config_DeviceConfig_Role_CLIENT_HIDDEN) {
sendTipsNodeInfo();
}
return Default::getConfiguredOrDefaultMs(config.device.node_info_broadcast_secs, default_node_info_broadcast_secs);
}
MeshTipsMessageModule *meshTipsMessageModule;
ProcessMessage MeshTipsMessageModule::handleReceived(const meshtastic_MeshPacket &mp)
{
#ifdef DEBUG_PORT
auto &d = mp.decoded;
#endif
meshtastic_MeshPacket *p = packetPool.allocCopy(mp);
MeshTipsModule_TXSettings s = stripTargetRadioSettings(p);
if (!p->decoded.payload.size || p->decoded.payload.bytes[0] == '#')
return ProcessMessage::STOP;
p->to = NODENUM_BROADCAST;
p->decoded.source = p->from;
p->from = NODENUM_TIPS;
p->channel = channels.getPrimaryIndex();
p->hop_limit = 0;
p->hop_start = 0;
p->rx_rssi = 0;
p->rx_snr = 0;
p->priority = meshtastic_MeshPacket_Priority_HIGH;
p->want_ack = false;
p->modem_preset = s.preset;
p->frequency_slot = s.slot;
if (s.preset != originalModemPreset || s.slot != originalLoraChannel) {
p->nonstandard_radio_config = true;
}
p->rx_time = getValidTime(RTCQualityFromNet);
service->sendToMesh(p, RX_SRC_LOCAL, false);
powerFSM.trigger(EVENT_RECEIVED_MSG);
notifyObservers(&mp);
return ProcessMessage::CONTINUE; // No other module should be caring about this message
}
bool MeshTipsMessageModule::wantPacket(const meshtastic_MeshPacket *p)
{
meshtastic_Channel *c = &channels.getByIndex(p->channel);
return c->role == meshtastic_Channel_Role_SECONDARY && strlen(c->settings.name) == strlen("Tips") &&
!strcmp(c->settings.name, "Tips") && p->decoded.portnum == meshtastic_PortNum_TEXT_MESSAGE_APP;
}

View File

@ -0,0 +1,80 @@
#pragma once
#include "Observer.h"
#include "ProtobufModule.h"
#include "RadioInterface.h"
#include "SinglePortModule.h"
typedef struct {
meshtastic_Config_LoRaConfig_ModemPreset preset;
uint16_t slot;
} MeshTipsModule_TXSettings;
/**
* Base class for the tips robot
*/
class MeshTipsModule
{
public:
/**
* Constructor
*/
MeshTipsModule();
/**
* Configure the radio to send the target packet, or return to default config if p is NULL
*/
static bool configureRadioForPacket(RadioInterface *iface, meshtastic_MeshPacket *p);
/**
* Get target modem settings
*/
MeshTipsModule_TXSettings stripTargetRadioSettings(meshtastic_MeshPacket *p);
};
/**
* Tips module for sending tips into the mesh
*/
class MeshTipsNodeInfoModule : private MeshTipsModule, public ProtobufModule<meshtastic_User>, private concurrency::OSThread
{
public:
/**
* Constructor
*/
MeshTipsNodeInfoModule();
protected:
virtual bool handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_User *p) override;
/**
* Send NodeInfo to the mesh
*/
void sendTipsNodeInfo();
/** Does our periodic broadcast */
virtual int32_t runOnce() override;
};
extern MeshTipsNodeInfoModule *meshTipsNodeInfoModule;
/**
* Text message handling for the tips robot
*/
class MeshTipsMessageModule : private MeshTipsModule, public SinglePortModule, public Observable<const meshtastic_MeshPacket *>
{
public:
/**
* Constructor
*/
MeshTipsMessageModule() : MeshTipsModule(), SinglePortModule("tips", meshtastic_PortNum_TEXT_MESSAGE_APP) {}
protected:
/**
* Called to handle a particular incoming message
*/
virtual ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override;
/**
* Indicate whether this module wants to process the packet
*/
virtual bool wantPacket(const meshtastic_MeshPacket *p) override;
};
extern MeshTipsMessageModule *meshTipsMessageModule;

View File

@ -34,6 +34,9 @@
#if !MESHTASTIC_EXCLUDE_NODEINFO
#include "modules/NodeInfoModule.h"
#endif
#if !MESHTASTIC_EXCLUDE_TIPS
#include "modules/MeshTipsModule.h"
#endif
#if !MESHTASTIC_EXCLUDE_GPS
#include "modules/PositionModule.h"
#endif
@ -122,6 +125,10 @@ void setupModules()
#if !MESHTASTIC_EXCLUDE_NODEINFO
nodeInfoModule = new NodeInfoModule();
#endif
#if !MESHTASTIC_EXCLUDE_TIPS
meshTipsNodeInfoModule = new MeshTipsNodeInfoModule();
meshTipsMessageModule = new MeshTipsMessageModule();
#endif
#if !MESHTASTIC_EXCLUDE_GPS
positionModule = new PositionModule();
#endif

View File

@ -8,7 +8,7 @@
meshtastic_MeshPacket *ReplyModule::allocReply()
{
assert(currentRequest); // should always be !NULL
#ifdef DEBUG_PORT
#if defined(DEBUG_PORT) && !defined(DEBUG_MUTE)
auto req = *currentRequest;
auto &p = req.decoded;
// The incoming message is in p.payload

View File

@ -121,7 +121,7 @@ int32_t AirQualityTelemetryModule::runOnce()
bool AirQualityTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Telemetry *t)
{
if (t->which_variant == meshtastic_Telemetry_air_quality_metrics_tag) {
#ifdef DEBUG_PORT
#if defined(DEBUG_PORT) && !defined(DEBUG_MUTE)
const char *sender = getSenderShortName(mp);
LOG_INFO("(Received from %s): pm10_standard=%i, pm25_standard=%i, pm100_standard=%i", sender,

View File

@ -49,7 +49,7 @@ bool DeviceTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPacket &
return false;
if (t->which_variant == meshtastic_Telemetry_device_metrics_tag) {
#ifdef DEBUG_PORT
#if defined(DEBUG_PORT) && !defined(DEBUG_MUTE)
const char *sender = getSenderShortName(mp);
LOG_INFO("(Received from %s): air_util_tx=%f, channel_utilization=%f, battery_level=%i, voltage=%f", sender,

View File

@ -502,7 +502,7 @@ void EnvironmentTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiSt
bool EnvironmentTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Telemetry *t)
{
if (t->which_variant == meshtastic_Telemetry_environment_metrics_tag) {
#ifdef DEBUG_PORT
#if defined(DEBUG_PORT) && !defined(DEBUG_MUTE)
const char *sender = getSenderShortName(mp);
LOG_INFO("(Received from %s): barometric_pressure=%f, current=%f, gas_resistance=%f, relative_humidity=%f, "

View File

@ -149,7 +149,7 @@ void HealthTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *
bool HealthTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Telemetry *t)
{
if (t->which_variant == meshtastic_Telemetry_health_metrics_tag) {
#ifdef DEBUG_PORT
#if defined(DEBUG_PORT) && !defined(DEBUG_MUTE)
const char *sender = getSenderShortName(mp);
LOG_INFO("(Received from %s): temperature=%f, heart_bpm=%d, spO2=%d,", sender, t->variant.health_metrics.temperature,

View File

@ -27,7 +27,7 @@ bool HostMetricsModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp,
return false;
if (t->which_variant == meshtastic_Telemetry_host_metrics_tag) {
#ifdef DEBUG_PORT
#if defined(DEBUG_PORT) && !defined(DEBUG_MUTE)
const char *sender = getSenderShortName(mp);
if (t->variant.host_metrics.has_user_string)
t->variant.host_metrics.user_string[sizeof(t->variant.host_metrics.user_string) - 1] = '\0';

View File

@ -168,7 +168,7 @@ void PowerTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *s
bool PowerTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Telemetry *t)
{
if (t->which_variant == meshtastic_Telemetry_power_metrics_tag) {
#ifdef DEBUG_PORT
#if defined(DEBUG_PORT) && !defined(DEBUG_MUTE)
const char *sender = getSenderShortName(mp);
LOG_INFO("(Received from %s): ch1_voltage=%.1f, ch1_current=%.1f, ch2_voltage=%.1f, ch2_current=%.1f, "

View File

@ -9,7 +9,7 @@ TextMessageModule *textMessageModule;
ProcessMessage TextMessageModule::handleReceived(const meshtastic_MeshPacket &mp)
{
#ifdef DEBUG_PORT
#if defined(DEBUG_PORT) && !defined(DEBUG_MUTE)
auto &p = mp.decoded;
LOG_INFO("Received text msg from=0x%0x, id=0x%x, msg=%.*s", mp.from, mp.id, p.payload.size, p.payload.bytes);
#endif

View File

@ -232,7 +232,7 @@ void TraceRouteModule::appendMyIDandSNR(meshtastic_RouteDiscovery *updated, floa
void TraceRouteModule::printRoute(meshtastic_RouteDiscovery *r, uint32_t origin, uint32_t dest, bool isTowardsDestination)
{
#ifdef DEBUG_PORT
#if defined(DEBUG_PORT) && !defined(DEBUG_MUTE)
std::string route = "Route traced:\n";
route += vformat("0x%x --> ", origin);
for (uint8_t i = 0; i < r->route_count; i++) {

View File

@ -16,7 +16,7 @@ WaypointModule *waypointModule;
ProcessMessage WaypointModule::handleReceived(const meshtastic_MeshPacket &mp)
{
#ifdef DEBUG_PORT
#if defined(DEBUG_PORT) && !defined(DEBUG_MUTE)
auto &p = mp.decoded;
LOG_INFO("Received waypoint msg from=0x%0x, id=0x%x, msg=%.*s", mp.from, mp.id, p.payload.size, p.payload.bytes);
#endif

View File

@ -32,3 +32,8 @@
#define SX126X_DIO1 1001
#define SX126X_RESET 1003
#define SX126X_BUSY 1004
#if !defined(DEBUG_MUTE) && !defined(PIO_FRAMEWORK_ARDUINO_NANOLIB_FLOAT_PRINTF)
#error \
"You MUST enable PIO_FRAMEWORK_ARDUINO_NANOLIB_FLOAT_PRINTF if debug prints are enabled. printf will print uninitialized garbage instead of floats."
#endif

View File

@ -100,6 +100,9 @@ std::string MeshPacketSerializer::JsonSerialize(const meshtastic_MeshPacket *mp,
if (decoded->variant.environment_metrics.has_iaq) {
msgPayload["iaq"] = new JSONValue((uint)decoded->variant.environment_metrics.iaq);
}
if (decoded->variant.environment_metrics.has_distance) {
msgPayload["distance"] = new JSONValue(decoded->variant.environment_metrics.distance);
}
if (decoded->variant.environment_metrics.has_wind_speed) {
msgPayload["wind_speed"] = new JSONValue(decoded->variant.environment_metrics.wind_speed);
}
@ -115,6 +118,27 @@ std::string MeshPacketSerializer::JsonSerialize(const meshtastic_MeshPacket *mp,
if (decoded->variant.environment_metrics.has_radiation) {
msgPayload["radiation"] = new JSONValue(decoded->variant.environment_metrics.radiation);
}
if (decoded->variant.environment_metrics.has_ir_lux) {
msgPayload["ir_lux"] = new JSONValue(decoded->variant.environment_metrics.ir_lux);
}
if (decoded->variant.environment_metrics.has_uv_lux) {
msgPayload["uv_lux"] = new JSONValue(decoded->variant.environment_metrics.uv_lux);
}
if (decoded->variant.environment_metrics.has_weight) {
msgPayload["weight"] = new JSONValue(decoded->variant.environment_metrics.weight);
}
if (decoded->variant.environment_metrics.has_rainfall_1h) {
msgPayload["rainfall_1h"] = new JSONValue(decoded->variant.environment_metrics.rainfall_1h);
}
if (decoded->variant.environment_metrics.has_rainfall_24h) {
msgPayload["rainfall_24h"] = new JSONValue(decoded->variant.environment_metrics.rainfall_24h);
}
if (decoded->variant.environment_metrics.has_soil_moisture) {
msgPayload["soil_moisture"] = new JSONValue((uint)decoded->variant.environment_metrics.soil_moisture);
}
if (decoded->variant.environment_metrics.has_soil_temperature) {
msgPayload["soil_temperature"] = new JSONValue(decoded->variant.environment_metrics.soil_temperature);
}
} else if (decoded->which_variant == meshtastic_Telemetry_air_quality_metrics_tag) {
if (decoded->variant.air_quality_metrics.has_pm10_standard) {
msgPayload["pm10"] = new JSONValue((unsigned int)decoded->variant.air_quality_metrics.pm10_standard);

View File

@ -131,7 +131,7 @@ void initDeepSleep()
support busted boards, assume button one was pressed wakeButtons = ((uint64_t)1) << buttons.gpios[0];
*/
#ifdef DEBUG_PORT
#if defined(DEBUG_PORT) && !defined(DEBUG_MUTE)
// If we booted because our timer ran out or the user pressed reset, send those as fake events
RESET_REASON hwReason = rtc_get_reset_reason(0);

View File

@ -0,0 +1,50 @@
#include "../test_helpers.h"
// Test encrypted packet serialization
void test_encrypted_packet_serialization()
{
meshtastic_MeshPacket packet = meshtastic_MeshPacket_init_zero;
packet.from = 0x11223344;
packet.to = 0x55667788;
packet.id = 0x9999;
packet.which_payload_variant = meshtastic_MeshPacket_encrypted_tag;
// Add some dummy encrypted data
const char *encrypted_data = "encrypted_payload_data";
packet.encrypted.size = strlen(encrypted_data);
memcpy(packet.encrypted.bytes, encrypted_data, packet.encrypted.size);
std::string json = MeshPacketSerializer::JsonSerializeEncrypted(&packet);
TEST_ASSERT_TRUE(json.length() > 0);
JSONValue *root = JSON::Parse(json.c_str());
TEST_ASSERT_NOT_NULL(root);
TEST_ASSERT_TRUE(root->IsObject());
JSONObject jsonObj = root->AsObject();
// Check basic packet fields
TEST_ASSERT_TRUE(jsonObj.find("from") != jsonObj.end());
TEST_ASSERT_EQUAL(0x11223344, (uint32_t)jsonObj["from"]->AsNumber());
TEST_ASSERT_TRUE(jsonObj.find("to") != jsonObj.end());
TEST_ASSERT_EQUAL(0x55667788, (uint32_t)jsonObj["to"]->AsNumber());
TEST_ASSERT_TRUE(jsonObj.find("id") != jsonObj.end());
TEST_ASSERT_EQUAL(0x9999, (uint32_t)jsonObj["id"]->AsNumber());
// Check that it has encrypted data fields (not "payload" but "bytes" and "size")
TEST_ASSERT_TRUE(jsonObj.find("bytes") != jsonObj.end());
TEST_ASSERT_TRUE(jsonObj["bytes"]->IsString());
TEST_ASSERT_TRUE(jsonObj.find("size") != jsonObj.end());
TEST_ASSERT_EQUAL(22, (int)jsonObj["size"]->AsNumber()); // strlen("encrypted_payload_data") = 22
// The encrypted data should be hex-encoded
std::string encrypted_hex = jsonObj["bytes"]->AsString();
TEST_ASSERT_TRUE(encrypted_hex.length() > 0);
// Should be twice the size of the original data (hex encoding)
TEST_ASSERT_EQUAL(44, encrypted_hex.length()); // 22 * 2 = 44
delete root;
}

View File

@ -0,0 +1,51 @@
#include "../test_helpers.h"
static size_t encode_user_info(uint8_t *buffer, size_t buffer_size)
{
meshtastic_User user = meshtastic_User_init_zero;
strcpy(user.short_name, "TEST");
strcpy(user.long_name, "Test User");
strcpy(user.id, "!12345678");
user.hw_model = meshtastic_HardwareModel_HELTEC_V3;
pb_ostream_t stream = pb_ostream_from_buffer(buffer, buffer_size);
pb_encode(&stream, &meshtastic_User_msg, &user);
return stream.bytes_written;
}
// Test NODEINFO_APP port
void test_nodeinfo_serialization()
{
uint8_t buffer[256];
size_t payload_size = encode_user_info(buffer, sizeof(buffer));
meshtastic_MeshPacket packet = create_test_packet(meshtastic_PortNum_NODEINFO_APP, buffer, payload_size);
std::string json = MeshPacketSerializer::JsonSerialize(&packet, false);
TEST_ASSERT_TRUE(json.length() > 0);
JSONValue *root = JSON::Parse(json.c_str());
TEST_ASSERT_NOT_NULL(root);
TEST_ASSERT_TRUE(root->IsObject());
JSONObject jsonObj = root->AsObject();
// Check message type
TEST_ASSERT_TRUE(jsonObj.find("type") != jsonObj.end());
TEST_ASSERT_EQUAL_STRING("nodeinfo", jsonObj["type"]->AsString().c_str());
// Check payload
TEST_ASSERT_TRUE(jsonObj.find("payload") != jsonObj.end());
TEST_ASSERT_TRUE(jsonObj["payload"]->IsObject());
JSONObject payload = jsonObj["payload"]->AsObject();
// Verify user data
TEST_ASSERT_TRUE(payload.find("shortname") != payload.end());
TEST_ASSERT_EQUAL_STRING("TEST", payload["shortname"]->AsString().c_str());
TEST_ASSERT_TRUE(payload.find("longname") != payload.end());
TEST_ASSERT_EQUAL_STRING("Test User", payload["longname"]->AsString().c_str());
delete root;
}

View File

@ -0,0 +1,57 @@
#include "../test_helpers.h"
static size_t encode_position(uint8_t *buffer, size_t buffer_size)
{
meshtastic_Position position = meshtastic_Position_init_zero;
position.latitude_i = 374208000; // 37.4208 degrees * 1e7
position.longitude_i = -1221981000; // -122.1981 degrees * 1e7
position.altitude = 123;
position.time = 1609459200;
position.has_altitude = true;
position.has_latitude_i = true;
position.has_longitude_i = true;
pb_ostream_t stream = pb_ostream_from_buffer(buffer, buffer_size);
pb_encode(&stream, &meshtastic_Position_msg, &position);
return stream.bytes_written;
}
// Test POSITION_APP port
void test_position_serialization()
{
uint8_t buffer[256];
size_t payload_size = encode_position(buffer, sizeof(buffer));
meshtastic_MeshPacket packet = create_test_packet(meshtastic_PortNum_POSITION_APP, buffer, payload_size);
std::string json = MeshPacketSerializer::JsonSerialize(&packet, false);
TEST_ASSERT_TRUE(json.length() > 0);
JSONValue *root = JSON::Parse(json.c_str());
TEST_ASSERT_NOT_NULL(root);
TEST_ASSERT_TRUE(root->IsObject());
JSONObject jsonObj = root->AsObject();
// Check message type
TEST_ASSERT_TRUE(jsonObj.find("type") != jsonObj.end());
TEST_ASSERT_EQUAL_STRING("position", jsonObj["type"]->AsString().c_str());
// Check payload
TEST_ASSERT_TRUE(jsonObj.find("payload") != jsonObj.end());
TEST_ASSERT_TRUE(jsonObj["payload"]->IsObject());
JSONObject payload = jsonObj["payload"]->AsObject();
// Verify position data
TEST_ASSERT_TRUE(payload.find("latitude_i") != payload.end());
TEST_ASSERT_EQUAL(374208000, (int)payload["latitude_i"]->AsNumber());
TEST_ASSERT_TRUE(payload.find("longitude_i") != payload.end());
TEST_ASSERT_EQUAL(-1221981000, (int)payload["longitude_i"]->AsNumber());
TEST_ASSERT_TRUE(payload.find("altitude") != payload.end());
TEST_ASSERT_EQUAL(123, (int)payload["altitude"]->AsNumber());
delete root;
}

View File

@ -0,0 +1,528 @@
#include "../test_helpers.h"
// Helper function to create and encode device metrics
static size_t encode_telemetry_device_metrics(uint8_t *buffer, size_t buffer_size)
{
meshtastic_Telemetry telemetry = meshtastic_Telemetry_init_zero;
telemetry.time = 1609459200;
telemetry.which_variant = meshtastic_Telemetry_device_metrics_tag;
telemetry.variant.device_metrics.battery_level = 85;
telemetry.variant.device_metrics.has_battery_level = true;
telemetry.variant.device_metrics.voltage = 3.72f;
telemetry.variant.device_metrics.has_voltage = true;
telemetry.variant.device_metrics.channel_utilization = 15.56f;
telemetry.variant.device_metrics.has_channel_utilization = true;
telemetry.variant.device_metrics.air_util_tx = 8.23f;
telemetry.variant.device_metrics.has_air_util_tx = true;
telemetry.variant.device_metrics.uptime_seconds = 12345;
telemetry.variant.device_metrics.has_uptime_seconds = true;
pb_ostream_t stream = pb_ostream_from_buffer(buffer, buffer_size);
pb_encode(&stream, &meshtastic_Telemetry_msg, &telemetry);
return stream.bytes_written;
}
// Helper function to create and encode empty environment metrics (no fields set)
static size_t encode_telemetry_environment_metrics_empty(uint8_t *buffer, size_t buffer_size)
{
meshtastic_Telemetry telemetry = meshtastic_Telemetry_init_zero;
telemetry.time = 1609459200;
telemetry.which_variant = meshtastic_Telemetry_environment_metrics_tag;
// NO fields are set - all has_* flags remain false
// This tests that empty environment metrics don't produce any JSON fields
pb_ostream_t stream = pb_ostream_from_buffer(buffer, buffer_size);
pb_encode(&stream, &meshtastic_Telemetry_msg, &telemetry);
return stream.bytes_written;
}
// Helper function to create environment metrics with ALL possible fields set
// This function should be updated whenever new fields are added to the protobuf
static size_t encode_telemetry_environment_metrics_all_fields(uint8_t *buffer, size_t buffer_size)
{
meshtastic_Telemetry telemetry = meshtastic_Telemetry_init_zero;
telemetry.time = 1609459200;
telemetry.which_variant = meshtastic_Telemetry_environment_metrics_tag;
// Basic environment metrics
telemetry.variant.environment_metrics.temperature = 23.56f;
telemetry.variant.environment_metrics.has_temperature = true;
telemetry.variant.environment_metrics.relative_humidity = 65.43f;
telemetry.variant.environment_metrics.has_relative_humidity = true;
telemetry.variant.environment_metrics.barometric_pressure = 1013.27f;
telemetry.variant.environment_metrics.has_barometric_pressure = true;
// Gas and air quality
telemetry.variant.environment_metrics.gas_resistance = 50.58f;
telemetry.variant.environment_metrics.has_gas_resistance = true;
telemetry.variant.environment_metrics.iaq = 120;
telemetry.variant.environment_metrics.has_iaq = true;
// Power measurements
telemetry.variant.environment_metrics.voltage = 3.34f;
telemetry.variant.environment_metrics.has_voltage = true;
telemetry.variant.environment_metrics.current = 0.53f;
telemetry.variant.environment_metrics.has_current = true;
// Light measurements (ALL 4 types)
telemetry.variant.environment_metrics.lux = 450.12f;
telemetry.variant.environment_metrics.has_lux = true;
telemetry.variant.environment_metrics.white_lux = 380.95f;
telemetry.variant.environment_metrics.has_white_lux = true;
telemetry.variant.environment_metrics.ir_lux = 25.37f;
telemetry.variant.environment_metrics.has_ir_lux = true;
telemetry.variant.environment_metrics.uv_lux = 15.68f;
telemetry.variant.environment_metrics.has_uv_lux = true;
// Distance measurement
telemetry.variant.environment_metrics.distance = 150.29f;
telemetry.variant.environment_metrics.has_distance = true;
// Wind measurements (ALL 4 types)
telemetry.variant.environment_metrics.wind_direction = 180;
telemetry.variant.environment_metrics.has_wind_direction = true;
telemetry.variant.environment_metrics.wind_speed = 5.52f;
telemetry.variant.environment_metrics.has_wind_speed = true;
telemetry.variant.environment_metrics.wind_gust = 8.24f;
telemetry.variant.environment_metrics.has_wind_gust = true;
telemetry.variant.environment_metrics.wind_lull = 2.13f;
telemetry.variant.environment_metrics.has_wind_lull = true;
// Weight measurement
telemetry.variant.environment_metrics.weight = 75.56f;
telemetry.variant.environment_metrics.has_weight = true;
// Radiation measurement
telemetry.variant.environment_metrics.radiation = 0.13f;
telemetry.variant.environment_metrics.has_radiation = true;
// Rainfall measurements (BOTH types)
telemetry.variant.environment_metrics.rainfall_1h = 2.57f;
telemetry.variant.environment_metrics.has_rainfall_1h = true;
telemetry.variant.environment_metrics.rainfall_24h = 15.89f;
telemetry.variant.environment_metrics.has_rainfall_24h = true;
// Soil measurements (BOTH types)
telemetry.variant.environment_metrics.soil_moisture = 85;
telemetry.variant.environment_metrics.has_soil_moisture = true;
telemetry.variant.environment_metrics.soil_temperature = 18.54f;
telemetry.variant.environment_metrics.has_soil_temperature = true;
// IMPORTANT: When new environment fields are added to the protobuf,
// they MUST be added here too, or the coverage test will fail!
pb_ostream_t stream = pb_ostream_from_buffer(buffer, buffer_size);
pb_encode(&stream, &meshtastic_Telemetry_msg, &telemetry);
return stream.bytes_written;
}
// Helper function to create and encode environment metrics with all current fields
static size_t encode_telemetry_environment_metrics(uint8_t *buffer, size_t buffer_size)
{
meshtastic_Telemetry telemetry = meshtastic_Telemetry_init_zero;
telemetry.time = 1609459200;
telemetry.which_variant = meshtastic_Telemetry_environment_metrics_tag;
// Basic environment metrics
telemetry.variant.environment_metrics.temperature = 23.56f;
telemetry.variant.environment_metrics.has_temperature = true;
telemetry.variant.environment_metrics.relative_humidity = 65.43f;
telemetry.variant.environment_metrics.has_relative_humidity = true;
telemetry.variant.environment_metrics.barometric_pressure = 1013.27f;
telemetry.variant.environment_metrics.has_barometric_pressure = true;
// Gas and air quality
telemetry.variant.environment_metrics.gas_resistance = 50.58f;
telemetry.variant.environment_metrics.has_gas_resistance = true;
telemetry.variant.environment_metrics.iaq = 120;
telemetry.variant.environment_metrics.has_iaq = true;
// Power measurements
telemetry.variant.environment_metrics.voltage = 3.34f;
telemetry.variant.environment_metrics.has_voltage = true;
telemetry.variant.environment_metrics.current = 0.53f;
telemetry.variant.environment_metrics.has_current = true;
// Light measurements
telemetry.variant.environment_metrics.lux = 450.12f;
telemetry.variant.environment_metrics.has_lux = true;
telemetry.variant.environment_metrics.white_lux = 380.95f;
telemetry.variant.environment_metrics.has_white_lux = true;
telemetry.variant.environment_metrics.ir_lux = 25.37f;
telemetry.variant.environment_metrics.has_ir_lux = true;
telemetry.variant.environment_metrics.uv_lux = 15.68f;
telemetry.variant.environment_metrics.has_uv_lux = true;
// Distance measurement
telemetry.variant.environment_metrics.distance = 150.29f;
telemetry.variant.environment_metrics.has_distance = true;
// Wind measurements
telemetry.variant.environment_metrics.wind_direction = 180;
telemetry.variant.environment_metrics.has_wind_direction = true;
telemetry.variant.environment_metrics.wind_speed = 5.52f;
telemetry.variant.environment_metrics.has_wind_speed = true;
telemetry.variant.environment_metrics.wind_gust = 8.24f;
telemetry.variant.environment_metrics.has_wind_gust = true;
telemetry.variant.environment_metrics.wind_lull = 2.13f;
telemetry.variant.environment_metrics.has_wind_lull = true;
// Weight measurement
telemetry.variant.environment_metrics.weight = 75.56f;
telemetry.variant.environment_metrics.has_weight = true;
// Radiation measurement
telemetry.variant.environment_metrics.radiation = 0.13f;
telemetry.variant.environment_metrics.has_radiation = true;
// Rainfall measurements
telemetry.variant.environment_metrics.rainfall_1h = 2.57f;
telemetry.variant.environment_metrics.has_rainfall_1h = true;
telemetry.variant.environment_metrics.rainfall_24h = 15.89f;
telemetry.variant.environment_metrics.has_rainfall_24h = true;
// Soil measurements
telemetry.variant.environment_metrics.soil_moisture = 85;
telemetry.variant.environment_metrics.has_soil_moisture = true;
telemetry.variant.environment_metrics.soil_temperature = 18.54f;
telemetry.variant.environment_metrics.has_soil_temperature = true;
pb_ostream_t stream = pb_ostream_from_buffer(buffer, buffer_size);
pb_encode(&stream, &meshtastic_Telemetry_msg, &telemetry);
return stream.bytes_written;
}
// Test TELEMETRY_APP port with device metrics
void test_telemetry_device_metrics_serialization()
{
uint8_t buffer[256];
size_t payload_size = encode_telemetry_device_metrics(buffer, sizeof(buffer));
meshtastic_MeshPacket packet = create_test_packet(meshtastic_PortNum_TELEMETRY_APP, buffer, payload_size);
std::string json = MeshPacketSerializer::JsonSerialize(&packet, false);
TEST_ASSERT_TRUE(json.length() > 0);
JSONValue *root = JSON::Parse(json.c_str());
TEST_ASSERT_NOT_NULL(root);
TEST_ASSERT_TRUE(root->IsObject());
JSONObject jsonObj = root->AsObject();
// Check message type
TEST_ASSERT_TRUE(jsonObj.find("type") != jsonObj.end());
TEST_ASSERT_EQUAL_STRING("telemetry", jsonObj["type"]->AsString().c_str());
// Check payload
TEST_ASSERT_TRUE(jsonObj.find("payload") != jsonObj.end());
TEST_ASSERT_TRUE(jsonObj["payload"]->IsObject());
JSONObject payload = jsonObj["payload"]->AsObject();
// Verify telemetry data
TEST_ASSERT_TRUE(payload.find("battery_level") != payload.end());
TEST_ASSERT_EQUAL(85, (int)payload["battery_level"]->AsNumber());
TEST_ASSERT_TRUE(payload.find("voltage") != payload.end());
TEST_ASSERT_FLOAT_WITHIN(0.01f, 3.72f, payload["voltage"]->AsNumber());
TEST_ASSERT_TRUE(payload.find("channel_utilization") != payload.end());
TEST_ASSERT_FLOAT_WITHIN(0.01f, 15.56f, payload["channel_utilization"]->AsNumber());
TEST_ASSERT_TRUE(payload.find("uptime_seconds") != payload.end());
TEST_ASSERT_EQUAL(12345, (int)payload["uptime_seconds"]->AsNumber());
// Note: JSON serialization may not preserve exact 2-decimal formatting due to float precision
// We verify the numeric values are correct within tolerance
delete root;
}
// Test that telemetry environment metrics are properly serialized
void test_telemetry_environment_metrics_serialization()
{
uint8_t buffer[256];
size_t payload_size = encode_telemetry_environment_metrics(buffer, sizeof(buffer));
meshtastic_MeshPacket packet = create_test_packet(meshtastic_PortNum_TELEMETRY_APP, buffer, payload_size);
std::string json = MeshPacketSerializer::JsonSerialize(&packet, false);
TEST_ASSERT_TRUE(json.length() > 0);
JSONValue *root = JSON::Parse(json.c_str());
TEST_ASSERT_NOT_NULL(root);
TEST_ASSERT_TRUE(root->IsObject());
JSONObject jsonObj = root->AsObject();
// Check payload exists
TEST_ASSERT_TRUE(jsonObj.find("payload") != jsonObj.end());
TEST_ASSERT_TRUE(jsonObj["payload"]->IsObject());
JSONObject payload = jsonObj["payload"]->AsObject();
// Test key fields that should be present in the serializer
TEST_ASSERT_TRUE(payload.find("temperature") != payload.end());
TEST_ASSERT_FLOAT_WITHIN(0.01f, 23.56f, payload["temperature"]->AsNumber());
TEST_ASSERT_TRUE(payload.find("relative_humidity") != payload.end());
TEST_ASSERT_FLOAT_WITHIN(0.01f, 65.43f, payload["relative_humidity"]->AsNumber());
TEST_ASSERT_TRUE(payload.find("distance") != payload.end());
TEST_ASSERT_FLOAT_WITHIN(0.01f, 150.29f, payload["distance"]->AsNumber());
// Note: JSON serialization may have float precision limitations
// We focus on verifying numeric accuracy rather than exact string formatting
delete root;
}
// Test comprehensive environment metrics coverage
void test_telemetry_environment_metrics_comprehensive()
{
uint8_t buffer[256];
size_t payload_size = encode_telemetry_environment_metrics(buffer, sizeof(buffer));
meshtastic_MeshPacket packet = create_test_packet(meshtastic_PortNum_TELEMETRY_APP, buffer, payload_size);
std::string json = MeshPacketSerializer::JsonSerialize(&packet, false);
TEST_ASSERT_TRUE(json.length() > 0);
JSONValue *root = JSON::Parse(json.c_str());
TEST_ASSERT_NOT_NULL(root);
TEST_ASSERT_TRUE(root->IsObject());
JSONObject jsonObj = root->AsObject();
// Check payload exists
TEST_ASSERT_TRUE(jsonObj.find("payload") != jsonObj.end());
TEST_ASSERT_TRUE(jsonObj["payload"]->IsObject());
JSONObject payload = jsonObj["payload"]->AsObject();
// Check all 15 originally supported fields
TEST_ASSERT_TRUE(payload.find("temperature") != payload.end());
TEST_ASSERT_TRUE(payload.find("relative_humidity") != payload.end());
TEST_ASSERT_TRUE(payload.find("barometric_pressure") != payload.end());
TEST_ASSERT_TRUE(payload.find("gas_resistance") != payload.end());
TEST_ASSERT_TRUE(payload.find("voltage") != payload.end());
TEST_ASSERT_TRUE(payload.find("current") != payload.end());
TEST_ASSERT_TRUE(payload.find("iaq") != payload.end());
TEST_ASSERT_TRUE(payload.find("distance") != payload.end());
TEST_ASSERT_TRUE(payload.find("lux") != payload.end());
TEST_ASSERT_TRUE(payload.find("white_lux") != payload.end());
TEST_ASSERT_TRUE(payload.find("wind_direction") != payload.end());
TEST_ASSERT_TRUE(payload.find("wind_speed") != payload.end());
TEST_ASSERT_TRUE(payload.find("wind_gust") != payload.end());
TEST_ASSERT_TRUE(payload.find("wind_lull") != payload.end());
TEST_ASSERT_TRUE(payload.find("radiation") != payload.end());
delete root;
}
// Test for the 7 environment fields that were added to complete coverage
void test_telemetry_environment_metrics_missing_fields()
{
uint8_t buffer[256];
size_t payload_size = encode_telemetry_environment_metrics(buffer, sizeof(buffer));
meshtastic_MeshPacket packet = create_test_packet(meshtastic_PortNum_TELEMETRY_APP, buffer, payload_size);
std::string json = MeshPacketSerializer::JsonSerialize(&packet, false);
TEST_ASSERT_TRUE(json.length() > 0);
JSONValue *root = JSON::Parse(json.c_str());
TEST_ASSERT_NOT_NULL(root);
TEST_ASSERT_TRUE(root->IsObject());
JSONObject jsonObj = root->AsObject();
// Check payload exists
TEST_ASSERT_TRUE(jsonObj.find("payload") != jsonObj.end());
TEST_ASSERT_TRUE(jsonObj["payload"]->IsObject());
JSONObject payload = jsonObj["payload"]->AsObject();
// Check the 7 fields that were previously missing
TEST_ASSERT_TRUE(payload.find("ir_lux") != payload.end());
TEST_ASSERT_FLOAT_WITHIN(0.01f, 25.37f, payload["ir_lux"]->AsNumber());
TEST_ASSERT_TRUE(payload.find("uv_lux") != payload.end());
TEST_ASSERT_FLOAT_WITHIN(0.01f, 15.68f, payload["uv_lux"]->AsNumber());
TEST_ASSERT_TRUE(payload.find("weight") != payload.end());
TEST_ASSERT_FLOAT_WITHIN(0.01f, 75.56f, payload["weight"]->AsNumber());
TEST_ASSERT_TRUE(payload.find("rainfall_1h") != payload.end());
TEST_ASSERT_FLOAT_WITHIN(0.01f, 2.57f, payload["rainfall_1h"]->AsNumber());
TEST_ASSERT_TRUE(payload.find("rainfall_24h") != payload.end());
TEST_ASSERT_FLOAT_WITHIN(0.01f, 15.89f, payload["rainfall_24h"]->AsNumber());
TEST_ASSERT_TRUE(payload.find("soil_moisture") != payload.end());
TEST_ASSERT_EQUAL(85, (int)payload["soil_moisture"]->AsNumber());
TEST_ASSERT_TRUE(payload.find("soil_temperature") != payload.end());
TEST_ASSERT_FLOAT_WITHIN(0.01f, 18.54f, payload["soil_temperature"]->AsNumber());
// Note: JSON float serialization may not preserve exact decimal formatting
// We verify the values are numerically correct within tolerance
delete root;
}
// Test that ALL environment fields are serialized (canary test for forgotten fields)
// This test will FAIL if a new environment field is added to the protobuf but not to the serializer
void test_telemetry_environment_metrics_complete_coverage()
{
uint8_t buffer[256];
size_t payload_size = encode_telemetry_environment_metrics_all_fields(buffer, sizeof(buffer));
meshtastic_MeshPacket packet = create_test_packet(meshtastic_PortNum_TELEMETRY_APP, buffer, payload_size);
std::string json = MeshPacketSerializer::JsonSerialize(&packet, false);
TEST_ASSERT_TRUE(json.length() > 0);
JSONValue *root = JSON::Parse(json.c_str());
TEST_ASSERT_NOT_NULL(root);
TEST_ASSERT_TRUE(root->IsObject());
JSONObject jsonObj = root->AsObject();
// Check payload exists
TEST_ASSERT_TRUE(jsonObj.find("payload") != jsonObj.end());
TEST_ASSERT_TRUE(jsonObj["payload"]->IsObject());
JSONObject payload = jsonObj["payload"]->AsObject();
// ✅ ALL 22 environment fields MUST be present and correct
// If this test fails, it means either:
// 1. A new field was added to the protobuf but not to the serializer
// 2. The encode_telemetry_environment_metrics_all_fields() function wasn't updated
// Basic environment (3 fields)
TEST_ASSERT_TRUE(payload.find("temperature") != payload.end());
TEST_ASSERT_FLOAT_WITHIN(0.01f, 23.56f, payload["temperature"]->AsNumber());
TEST_ASSERT_TRUE(payload.find("relative_humidity") != payload.end());
TEST_ASSERT_FLOAT_WITHIN(0.01f, 65.43f, payload["relative_humidity"]->AsNumber());
TEST_ASSERT_TRUE(payload.find("barometric_pressure") != payload.end());
TEST_ASSERT_FLOAT_WITHIN(0.01f, 1013.27f, payload["barometric_pressure"]->AsNumber());
// Gas and air quality (2 fields)
TEST_ASSERT_TRUE(payload.find("gas_resistance") != payload.end());
TEST_ASSERT_FLOAT_WITHIN(0.01f, 50.58f, payload["gas_resistance"]->AsNumber());
TEST_ASSERT_TRUE(payload.find("iaq") != payload.end());
TEST_ASSERT_EQUAL(120, (int)payload["iaq"]->AsNumber());
// Power measurements (2 fields)
TEST_ASSERT_TRUE(payload.find("voltage") != payload.end());
TEST_ASSERT_FLOAT_WITHIN(0.01f, 3.34f, payload["voltage"]->AsNumber());
TEST_ASSERT_TRUE(payload.find("current") != payload.end());
TEST_ASSERT_FLOAT_WITHIN(0.01f, 0.53f, payload["current"]->AsNumber());
// Light measurements (4 fields)
TEST_ASSERT_TRUE(payload.find("lux") != payload.end());
TEST_ASSERT_FLOAT_WITHIN(0.01f, 450.12f, payload["lux"]->AsNumber());
TEST_ASSERT_TRUE(payload.find("white_lux") != payload.end());
TEST_ASSERT_FLOAT_WITHIN(0.01f, 380.95f, payload["white_lux"]->AsNumber());
TEST_ASSERT_TRUE(payload.find("ir_lux") != payload.end());
TEST_ASSERT_FLOAT_WITHIN(0.01f, 25.37f, payload["ir_lux"]->AsNumber());
TEST_ASSERT_TRUE(payload.find("uv_lux") != payload.end());
TEST_ASSERT_FLOAT_WITHIN(0.01f, 15.68f, payload["uv_lux"]->AsNumber());
// Distance measurement (1 field)
TEST_ASSERT_TRUE(payload.find("distance") != payload.end());
TEST_ASSERT_FLOAT_WITHIN(0.01f, 150.29f, payload["distance"]->AsNumber());
// Wind measurements (4 fields)
TEST_ASSERT_TRUE(payload.find("wind_direction") != payload.end());
TEST_ASSERT_EQUAL(180, (int)payload["wind_direction"]->AsNumber());
TEST_ASSERT_TRUE(payload.find("wind_speed") != payload.end());
TEST_ASSERT_FLOAT_WITHIN(0.01f, 5.52f, payload["wind_speed"]->AsNumber());
TEST_ASSERT_TRUE(payload.find("wind_gust") != payload.end());
TEST_ASSERT_FLOAT_WITHIN(0.01f, 8.24f, payload["wind_gust"]->AsNumber());
TEST_ASSERT_TRUE(payload.find("wind_lull") != payload.end());
TEST_ASSERT_FLOAT_WITHIN(0.01f, 2.13f, payload["wind_lull"]->AsNumber());
// Weight measurement (1 field)
TEST_ASSERT_TRUE(payload.find("weight") != payload.end());
TEST_ASSERT_FLOAT_WITHIN(0.01f, 75.56f, payload["weight"]->AsNumber());
// Radiation measurement (1 field)
TEST_ASSERT_TRUE(payload.find("radiation") != payload.end());
TEST_ASSERT_FLOAT_WITHIN(0.01f, 0.13f, payload["radiation"]->AsNumber());
// Rainfall measurements (2 fields)
TEST_ASSERT_TRUE(payload.find("rainfall_1h") != payload.end());
TEST_ASSERT_FLOAT_WITHIN(0.01f, 2.57f, payload["rainfall_1h"]->AsNumber());
TEST_ASSERT_TRUE(payload.find("rainfall_24h") != payload.end());
TEST_ASSERT_FLOAT_WITHIN(0.01f, 15.89f, payload["rainfall_24h"]->AsNumber());
// Soil measurements (2 fields)
TEST_ASSERT_TRUE(payload.find("soil_moisture") != payload.end());
TEST_ASSERT_EQUAL(85, (int)payload["soil_moisture"]->AsNumber());
TEST_ASSERT_TRUE(payload.find("soil_temperature") != payload.end());
TEST_ASSERT_FLOAT_WITHIN(0.01f, 18.54f, payload["soil_temperature"]->AsNumber());
// Total: 22 environment fields
// This test ensures 100% coverage of environment metrics
// Note: JSON float serialization precision may vary due to the underlying library
// The important aspect is that all values are numerically accurate within tolerance
delete root;
}
// Test that unset environment fields are not present in JSON
void test_telemetry_environment_metrics_unset_fields()
{
uint8_t buffer[256];
size_t payload_size = encode_telemetry_environment_metrics_empty(buffer, sizeof(buffer));
meshtastic_MeshPacket packet = create_test_packet(meshtastic_PortNum_TELEMETRY_APP, buffer, payload_size);
std::string json = MeshPacketSerializer::JsonSerialize(&packet, false);
TEST_ASSERT_TRUE(json.length() > 0);
JSONValue *root = JSON::Parse(json.c_str());
TEST_ASSERT_NOT_NULL(root);
TEST_ASSERT_TRUE(root->IsObject());
JSONObject jsonObj = root->AsObject();
// Check payload exists
TEST_ASSERT_TRUE(jsonObj.find("payload") != jsonObj.end());
TEST_ASSERT_TRUE(jsonObj["payload"]->IsObject());
JSONObject payload = jsonObj["payload"]->AsObject();
// With completely empty environment metrics, NO fields should be present
// Only basic telemetry fields like "time" might be present
// All 22 environment fields should be absent (none were set)
TEST_ASSERT_TRUE(payload.find("temperature") == payload.end());
TEST_ASSERT_TRUE(payload.find("relative_humidity") == payload.end());
TEST_ASSERT_TRUE(payload.find("barometric_pressure") == payload.end());
TEST_ASSERT_TRUE(payload.find("gas_resistance") == payload.end());
TEST_ASSERT_TRUE(payload.find("iaq") == payload.end());
TEST_ASSERT_TRUE(payload.find("voltage") == payload.end());
TEST_ASSERT_TRUE(payload.find("current") == payload.end());
TEST_ASSERT_TRUE(payload.find("lux") == payload.end());
TEST_ASSERT_TRUE(payload.find("white_lux") == payload.end());
TEST_ASSERT_TRUE(payload.find("ir_lux") == payload.end());
TEST_ASSERT_TRUE(payload.find("uv_lux") == payload.end());
TEST_ASSERT_TRUE(payload.find("distance") == payload.end());
TEST_ASSERT_TRUE(payload.find("wind_direction") == payload.end());
TEST_ASSERT_TRUE(payload.find("wind_speed") == payload.end());
TEST_ASSERT_TRUE(payload.find("wind_gust") == payload.end());
TEST_ASSERT_TRUE(payload.find("wind_lull") == payload.end());
TEST_ASSERT_TRUE(payload.find("weight") == payload.end());
TEST_ASSERT_TRUE(payload.find("radiation") == payload.end());
TEST_ASSERT_TRUE(payload.find("rainfall_1h") == payload.end());
TEST_ASSERT_TRUE(payload.find("rainfall_24h") == payload.end());
TEST_ASSERT_TRUE(payload.find("soil_moisture") == payload.end());
TEST_ASSERT_TRUE(payload.find("soil_temperature") == payload.end());
delete root;
}

View File

@ -0,0 +1,42 @@
#include "../test_helpers.h"
// Test TEXT_MESSAGE_APP port
void test_text_message_serialization()
{
const char *test_text = "Hello Meshtastic!";
meshtastic_MeshPacket packet =
create_test_packet(meshtastic_PortNum_TEXT_MESSAGE_APP, (const uint8_t *)test_text, strlen(test_text));
std::string json = MeshPacketSerializer::JsonSerialize(&packet, false);
TEST_ASSERT_TRUE(json.length() > 0);
JSONValue *root = JSON::Parse(json.c_str());
TEST_ASSERT_NOT_NULL(root);
TEST_ASSERT_TRUE(root->IsObject());
JSONObject jsonObj = root->AsObject();
// Check basic packet fields
TEST_ASSERT_TRUE(jsonObj.find("from") != jsonObj.end());
TEST_ASSERT_EQUAL(0x11223344, (uint32_t)jsonObj["from"]->AsNumber());
TEST_ASSERT_TRUE(jsonObj.find("to") != jsonObj.end());
TEST_ASSERT_EQUAL(0x55667788, (uint32_t)jsonObj["to"]->AsNumber());
TEST_ASSERT_TRUE(jsonObj.find("id") != jsonObj.end());
TEST_ASSERT_EQUAL(0x9999, (uint32_t)jsonObj["id"]->AsNumber());
// Check message type
TEST_ASSERT_TRUE(jsonObj.find("type") != jsonObj.end());
TEST_ASSERT_EQUAL_STRING("text", jsonObj["type"]->AsString().c_str());
// Check payload
TEST_ASSERT_TRUE(jsonObj.find("payload") != jsonObj.end());
TEST_ASSERT_TRUE(jsonObj["payload"]->IsObject());
JSONObject payload = jsonObj["payload"]->AsObject();
TEST_ASSERT_TRUE(payload.find("text") != payload.end());
TEST_ASSERT_EQUAL_STRING("Hello Meshtastic!", payload["text"]->AsString().c_str());
delete root;
}

View File

@ -0,0 +1,53 @@
#include "../test_helpers.h"
static size_t encode_waypoint(uint8_t *buffer, size_t buffer_size)
{
meshtastic_Waypoint waypoint = meshtastic_Waypoint_init_zero;
waypoint.id = 12345;
waypoint.latitude_i = 374208000;
waypoint.longitude_i = -1221981000;
waypoint.expire = 1609459200 + 3600; // 1 hour from now
strcpy(waypoint.name, "Test Point");
strcpy(waypoint.description, "Test waypoint description");
pb_ostream_t stream = pb_ostream_from_buffer(buffer, buffer_size);
pb_encode(&stream, &meshtastic_Waypoint_msg, &waypoint);
return stream.bytes_written;
}
// Test WAYPOINT_APP port
void test_waypoint_serialization()
{
uint8_t buffer[256];
size_t payload_size = encode_waypoint(buffer, sizeof(buffer));
meshtastic_MeshPacket packet = create_test_packet(meshtastic_PortNum_WAYPOINT_APP, buffer, payload_size);
std::string json = MeshPacketSerializer::JsonSerialize(&packet, false);
TEST_ASSERT_TRUE(json.length() > 0);
JSONValue *root = JSON::Parse(json.c_str());
TEST_ASSERT_NOT_NULL(root);
TEST_ASSERT_TRUE(root->IsObject());
JSONObject jsonObj = root->AsObject();
// Check message type
TEST_ASSERT_TRUE(jsonObj.find("type") != jsonObj.end());
TEST_ASSERT_EQUAL_STRING("waypoint", jsonObj["type"]->AsString().c_str());
// Check payload
TEST_ASSERT_TRUE(jsonObj.find("payload") != jsonObj.end());
TEST_ASSERT_TRUE(jsonObj["payload"]->IsObject());
JSONObject payload = jsonObj["payload"]->AsObject();
// Verify waypoint data
TEST_ASSERT_TRUE(payload.find("id") != payload.end());
TEST_ASSERT_EQUAL(12345, (int)payload["id"]->AsNumber());
TEST_ASSERT_TRUE(payload.find("name") != payload.end());
TEST_ASSERT_EQUAL_STRING("Test Point", payload["name"]->AsString().c_str());
delete root;
}

View File

@ -0,0 +1,44 @@
#pragma once
#include "serialization/JSON.h"
#include "serialization/MeshPacketSerializer.h"
#include <Arduino.h>
#include <meshtastic/mesh.pb.h>
#include <meshtastic/mqtt.pb.h>
#include <meshtastic/telemetry.pb.h>
#include <pb_decode.h>
#include <pb_encode.h>
#include <unity.h>
// Helper function to create a test packet with the given port and payload
static meshtastic_MeshPacket create_test_packet(meshtastic_PortNum port, const uint8_t *payload, size_t payload_size)
{
meshtastic_MeshPacket packet = meshtastic_MeshPacket_init_zero;
packet.id = 0x9999;
packet.from = 0x11223344;
packet.to = 0x55667788;
packet.channel = 0;
packet.hop_limit = 3;
packet.want_ack = false;
packet.priority = meshtastic_MeshPacket_Priority_UNSET;
packet.rx_time = 1609459200;
packet.rx_snr = 10.5f;
packet.hop_start = 3;
packet.rx_rssi = -85;
packet.delayed = meshtastic_MeshPacket_Delayed_NO_DELAY;
// Set decoded variant
packet.which_payload_variant = meshtastic_MeshPacket_decoded_tag;
packet.decoded.portnum = port;
memcpy(packet.decoded.payload.bytes, payload, payload_size);
packet.decoded.payload.size = payload_size;
packet.decoded.want_response = false;
packet.decoded.dest = 0x55667788;
packet.decoded.source = 0x11223344;
packet.decoded.request_id = 0;
packet.decoded.reply_id = 0;
packet.decoded.emoji = 0;
return packet;
}

View File

@ -0,0 +1,51 @@
#include "test_helpers.h"
#include <Arduino.h>
#include <unity.h>
// Forward declarations for test functions
void test_text_message_serialization();
void test_position_serialization();
void test_nodeinfo_serialization();
void test_waypoint_serialization();
void test_telemetry_device_metrics_serialization();
void test_telemetry_environment_metrics_serialization();
void test_telemetry_environment_metrics_comprehensive();
void test_telemetry_environment_metrics_missing_fields();
void test_telemetry_environment_metrics_complete_coverage();
void test_telemetry_environment_metrics_unset_fields();
void test_encrypted_packet_serialization();
void setup()
{
UNITY_BEGIN();
// Text message tests
RUN_TEST(test_text_message_serialization);
// Position tests
RUN_TEST(test_position_serialization);
// Nodeinfo tests
RUN_TEST(test_nodeinfo_serialization);
// Waypoint tests
RUN_TEST(test_waypoint_serialization);
// Telemetry tests
RUN_TEST(test_telemetry_device_metrics_serialization);
RUN_TEST(test_telemetry_environment_metrics_serialization);
RUN_TEST(test_telemetry_environment_metrics_comprehensive);
RUN_TEST(test_telemetry_environment_metrics_missing_fields);
RUN_TEST(test_telemetry_environment_metrics_complete_coverage);
RUN_TEST(test_telemetry_environment_metrics_unset_fields);
// Encrypted packet test
RUN_TEST(test_encrypted_packet_serialization);
UNITY_END();
}
void loop()
{
delay(1000);
}

View File

@ -19,8 +19,6 @@
#ifndef _VARIANT_GAT562_MESH_TRIAL_TRACKER_
#define _VARIANT_GAT562_MESH_TRIAL_TRACKER_
#define GAT562_MESH_TRIAL_TRACKER
// led pin 2 (blue), see https://github.com/meshtastic/firmware/blob/master/src/mesh/NodeDB.cpp#L723
#define RAK4630