mirror of
https://github.com/meshtastic/firmware.git
synced 2025-07-31 02:45:41 +00:00
Compare commits
7 Commits
d65e796a34
...
acdb89d91e
Author | SHA1 | Date | |
---|---|---|---|
![]() |
acdb89d91e | ||
![]() |
b5a8e8f51b | ||
![]() |
cc5d00e211 | ||
![]() |
1a8ab2aadc | ||
![]() |
608fdc6f52 | ||
![]() |
1d8638b47d | ||
![]() |
7d3af3aa8e |
@ -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
|
||||
*/
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
242
src/modules/MeshTipsModule.cpp
Normal file
242
src/modules/MeshTipsModule.cpp
Normal 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;
|
||||
}
|
80
src/modules/MeshTipsModule.h
Normal file
80
src/modules/MeshTipsModule.h
Normal 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;
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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, "
|
||||
|
@ -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,
|
||||
|
@ -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';
|
||||
|
@ -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, "
|
||||
|
@ -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
|
||||
|
@ -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++) {
|
||||
|
@ -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
|
||||
|
@ -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
|
@ -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);
|
||||
|
@ -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);
|
||||
|
||||
|
50
test/test_meshpacket_serializer/ports/test_encrypted.cpp
Normal file
50
test/test_meshpacket_serializer/ports/test_encrypted.cpp
Normal 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;
|
||||
}
|
51
test/test_meshpacket_serializer/ports/test_nodeinfo.cpp
Normal file
51
test/test_meshpacket_serializer/ports/test_nodeinfo.cpp
Normal 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;
|
||||
}
|
57
test/test_meshpacket_serializer/ports/test_position.cpp
Normal file
57
test/test_meshpacket_serializer/ports/test_position.cpp
Normal 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;
|
||||
}
|
528
test/test_meshpacket_serializer/ports/test_telemetry.cpp
Normal file
528
test/test_meshpacket_serializer/ports/test_telemetry.cpp
Normal 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;
|
||||
}
|
42
test/test_meshpacket_serializer/ports/test_text_message.cpp
Normal file
42
test/test_meshpacket_serializer/ports/test_text_message.cpp
Normal 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;
|
||||
}
|
53
test/test_meshpacket_serializer/ports/test_waypoint.cpp
Normal file
53
test/test_meshpacket_serializer/ports/test_waypoint.cpp
Normal 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;
|
||||
}
|
44
test/test_meshpacket_serializer/test_helpers.h
Normal file
44
test/test_meshpacket_serializer/test_helpers.h
Normal 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;
|
||||
}
|
51
test/test_meshpacket_serializer/test_serializer.cpp
Normal file
51
test/test_meshpacket_serializer/test_serializer.cpp
Normal 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);
|
||||
}
|
@ -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
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user