2021-06-27 17:56:28 +00:00
# include "configuration.h"
2020-02-02 20:45:32 +00:00
# include <assert.h>
2020-04-15 03:22:27 +00:00
# include <string>
2020-02-02 20:45:32 +00:00
2020-07-10 02:57:55 +00:00
# include "../concurrency/Periodic.h"
# include "BluetoothCommon.h" // needed for updateBatteryLevel, FIXME, eventually when we pull mesh out into a lib we shouldn't be whacking bluetooth from here
2023-01-21 13:34:29 +00:00
# include "GPS.h"
2020-03-19 02:15:51 +00:00
# include "MeshService.h"
2020-02-03 17:13:19 +00:00
# include "NodeDB.h"
2020-03-03 21:31:44 +00:00
# include "PowerFSM.h"
2020-10-07 23:28:57 +00:00
# include "RTC.h"
2023-06-17 14:10:09 +00:00
# include "TypeConversions.h"
2020-03-19 02:15:51 +00:00
# include "main.h"
# include "mesh-pb-constants.h"
2022-02-27 08:18:35 +00:00
# include "modules/NodeInfoModule.h"
# include "modules/PositionModule.h"
2020-12-05 02:00:46 +00:00
# include "power.h"
2020-02-02 20:45:32 +00:00
2022-08-22 21:41:23 +00:00
# ifdef ARCH_ESP32
# include "nimble/NimbleBluetooth.h"
# endif
2020-02-02 20:45:32 +00:00
/*
receivedPacketQueue - this is a queue of messages we ' ve received from the mesh , which we are keeping to deliver to the phone .
2020-03-19 02:15:51 +00:00
It is implemented with a FreeRTos queue ( wrapped with a little RTQueue class ) of pointers to MeshPacket protobufs ( which were
alloced with new ) . After a packet ptr is removed from the queue and processed it should be deleted . ( eventually we should move
sent packets into a ' sentToPhone ' queue of packets we can delete just as soon as we are sure the phone has acked those packets -
when the phone writes to FromNum )
2020-02-02 20:45:32 +00:00
2020-03-19 02:15:51 +00:00
mesh - an instance of Mesh class . Which manages the interface to the mesh radio library , reception of packets from other nodes ,
arbitrating to select a node number and keeping the current nodedb .
2020-02-02 20:45:32 +00:00
*/
2020-02-08 20:42:54 +00:00
/* Broadcast when a newly powered mesh node wants to find a node num it can use
2023-07-14 21:25:20 +00:00
The algorithm is as follows :
2020-03-19 02:15:51 +00:00
* when a node starts up , it broadcasts their user and the normal flow is for all other nodes to reply with their User as well ( so
the new node can build its node db )
* If a node ever receives a User ( not just the first broadcast ) message where the sender node number equals our node number , that
indicates a collision has occurred and the following steps should happen :
2020-02-08 20:42:54 +00:00
2020-03-19 02:15:51 +00:00
If the receiving node ( that was already in the mesh ) ' s macaddr is LOWER than the new User who just tried to sign in : it gets to
keep its nodenum . We send a broadcast message of OUR User ( we use a broadcast so that the other node can receive our message ,
considering we have the same id - it also serves to let observers correct their nodedb ) - this case is rare so it should be okay .
2020-02-08 20:42:54 +00:00
2020-03-19 02:15:51 +00:00
If any node receives a User where the macaddr is GTE than their local macaddr , they have been vetoed and should pick a new random
nodenum ( filtering against whatever it knows about the nodedb ) and rebroadcast their User .
2020-02-08 20:42:54 +00:00
FIXME in the initial proof of concept we just skip the entire want / deny flow and just hand pick node numbers at first .
*/
2020-02-02 20:45:32 +00:00
2020-02-08 20:42:54 +00:00
MeshService service ;
2020-02-08 18:00:15 +00:00
2023-07-09 01:37:04 +00:00
static MemoryDynamic < meshtastic_MqttClientProxyMessage > staticMqttClientProxyMessagePool ;
2023-01-21 17:22:19 +00:00
static MemoryDynamic < meshtastic_QueueStatus > staticQueueStatusPool ;
2023-01-04 12:56:52 +00:00
2023-07-09 01:37:04 +00:00
Allocator < meshtastic_MqttClientProxyMessage > & mqttClientProxyMessagePool = staticMqttClientProxyMessagePool ;
2023-01-21 17:22:19 +00:00
Allocator < meshtastic_QueueStatus > & queueStatusPool = staticQueueStatusPool ;
2023-01-04 12:56:52 +00:00
2020-04-17 16:48:54 +00:00
# include "Router.h"
2020-02-03 17:13:19 +00:00
2023-07-09 01:37:04 +00:00
MeshService : : MeshService ( )
: toPhoneQueue ( MAX_RX_TOPHONE ) , toPhoneQueueStatusQueue ( MAX_RX_TOPHONE ) , toPhoneMqttProxyQueue ( MAX_RX_TOPHONE )
2020-02-02 20:45:32 +00:00
{
2023-01-21 13:34:29 +00:00
lastQueueStatus = { 0 , 0 , 16 , 0 } ;
2020-02-02 20:45:32 +00:00
}
void MeshService : : init ( )
{
2020-12-26 05:36:21 +00:00
// moved much earlier in boot (called from setup())
// nodeDB.init();
2020-02-06 16:49:33 +00:00
2020-09-06 21:45:43 +00:00
if ( gps )
gpsObserver . observe ( & gps - > newStatus ) ;
2020-02-06 18:58:19 +00:00
}
2023-01-21 17:22:19 +00:00
int MeshService : : handleFromRadio ( const meshtastic_MeshPacket * mp )
2020-02-18 01:47:01 +00:00
{
2021-08-03 05:07:39 +00:00
powerFSM . trigger ( EVENT_PACKET_FOR_PHONE ) ; // Possibly keep the node from sleeping
2020-03-03 21:31:44 +00:00
2021-03-05 03:44:45 +00:00
nodeDB . updateFrom ( * mp ) ; // update our DB state based off sniffing every RX packet from the radio
2023-08-01 23:24:40 +00:00
if ( mp - > which_payload_variant = = meshtastic_MeshPacket_decoded_tag & &
mp - > decoded . portnum = = meshtastic_PortNum_TELEMETRY_APP & & mp - > decoded . request_id > 0 ) {
LOG_DEBUG (
" Received telemetry response. Skip sending our NodeInfo because this potentially a Repeater which will ignore our "
" request for its NodeInfo. \n " ) ;
} else if ( mp - > which_payload_variant = = meshtastic_MeshPacket_decoded_tag & & ! nodeDB . getMeshNode ( mp - > from ) - > has_user & &
nodeInfoModule ) {
2023-03-29 11:51:22 +00:00
LOG_INFO ( " Heard a node on channel %d we don't know, sending NodeInfo and asking for a response. \n " , mp - > channel ) ;
nodeInfoModule - > sendOurNodeInfo ( mp - > from , true , mp - > channel ) ;
2023-02-11 12:57:51 +00:00
}
2020-02-18 01:47:01 +00:00
2023-02-11 12:57:51 +00:00
printPacket ( " Forwarding to phone " , mp ) ;
2023-03-14 21:50:32 +00:00
sendToPhone ( packetPool . allocCopy ( * mp ) ) ;
2021-03-05 03:44:45 +00:00
2020-04-17 16:48:54 +00:00
return 0 ;
2020-02-02 20:45:32 +00:00
}
2020-02-08 17:39:26 +00:00
/// Do idle processing (mostly processing messages which have been queued from the radio)
void MeshService : : loop ( )
{
2023-01-04 12:56:52 +00:00
if ( lastQueueStatus . free = = 0 ) { // check if there is now free space in TX queue
2023-01-21 17:22:19 +00:00
meshtastic_QueueStatus qs = router - > getQueueStatus ( ) ;
2023-01-04 12:56:52 +00:00
if ( qs . free ! = lastQueueStatus . free )
( void ) sendQueueStatusToPhone ( qs , 0 , 0 ) ;
}
2020-04-17 16:48:54 +00:00
if ( oldFromNum ! = fromNum ) { // We don't want to generate extra notifies for multiple new packets
fromNumChanged . notifyObservers ( fromNum ) ;
2020-04-17 20:05:16 +00:00
oldFromNum = fromNum ;
2020-04-17 16:48:54 +00:00
}
2020-02-08 17:39:26 +00:00
}
2020-02-11 19:56:48 +00:00
/// The radioConfig object just changed, call this to force the hw to change to the new settings
2022-10-04 12:32:07 +00:00
bool MeshService : : reloadConfig ( int saveWhat )
2020-02-11 19:56:48 +00:00
{
// If we can successfully set this radio to these settings, save them to disk
2020-10-29 05:26:36 +00:00
// This will also update the region as needed
2020-09-19 18:19:42 +00:00
bool didReset = nodeDB . resetRadioConfig ( ) ; // Don't let the phone send us fatally bad settings
2020-12-05 02:00:46 +00:00
2021-03-11 10:29:47 +00:00
configChanged . notifyObservers ( NULL ) ; // This will cause radio hardware to change freqs etc
2022-10-04 12:32:07 +00:00
nodeDB . saveToDisk ( saveWhat ) ;
2020-09-19 18:19:42 +00:00
return didReset ;
2020-02-11 19:56:48 +00:00
}
2020-09-16 16:08:35 +00:00
/// The owner User record just got updated, update our node DB and broadcast the info into the mesh
2022-11-21 01:50:45 +00:00
void MeshService : : reloadOwner ( bool shouldSave )
2020-09-16 16:08:35 +00:00
{
2022-12-30 02:41:37 +00:00
// LOG_DEBUG("reloadOwner()\n");
2021-10-29 13:28:48 +00:00
// update our local data directly
nodeDB . updateUser ( nodeDB . getNodeNum ( ) , owner ) ;
2022-02-27 10:21:02 +00:00
assert ( nodeInfoModule ) ;
2022-11-21 01:50:45 +00:00
// update everyone else and save to disk
if ( nodeInfoModule & & shouldSave ) {
2022-02-27 10:21:02 +00:00
nodeInfoModule - > sendOurNodeInfo ( ) ;
2022-11-21 01:50:45 +00:00
}
2020-09-16 16:08:35 +00:00
}
2020-04-22 21:55:36 +00:00
/**
* Given a ToRadio buffer parse it and properly handle it ( setup radio , owner or send packet into the mesh )
2020-04-25 17:59:40 +00:00
* Called by PhoneAPI . handleToRadio . Note : p is a scratch buffer , this function is allowed to write to it but it can not keep a
* reference
2020-04-22 21:55:36 +00:00
*/
2023-01-21 17:22:19 +00:00
void MeshService : : handleToRadio ( meshtastic_MeshPacket & p )
2020-02-02 20:45:32 +00:00
{
2023-02-11 09:00:19 +00:00
# if defined(ARCH_PORTDUINO) && !HAS_RADIO
2022-10-01 10:06:59 +00:00
// Simulates device is receiving a packet via the LoRa chip
2023-01-21 17:46:17 +00:00
if ( p . decoded . portnum = = meshtastic_PortNum_SIMULATOR_APP ) {
2022-10-01 10:06:59 +00:00
// Simulator packet (=Compressed packet) is encapsulated in a MeshPacket, so need to unwrap first
2023-01-21 17:46:17 +00:00
meshtastic_Compressed scratch ;
meshtastic_Compressed * decoded = NULL ;
if ( p . which_payload_variant = = meshtastic_MeshPacket_decoded_tag ) {
2022-10-01 10:06:59 +00:00
memset ( & scratch , 0 , sizeof ( scratch ) ) ;
2023-01-21 13:34:29 +00:00
p . decoded . payload . size =
2023-01-21 17:46:17 +00:00
pb_decode_from_bytes ( p . decoded . payload . bytes , p . decoded . payload . size , & meshtastic_Compressed_msg , & scratch ) ;
2022-10-01 10:06:59 +00:00
if ( p . decoded . payload . size ) {
decoded = & scratch ;
// Extract the original payload and replace
memcpy ( & p . decoded . payload , & decoded - > data , sizeof ( decoded - > data ) ) ;
2023-01-21 13:34:29 +00:00
// Switch the port from PortNum_SIMULATOR_APP back to the original PortNum
2022-10-01 10:06:59 +00:00
p . decoded . portnum = decoded - > portnum ;
} else
2022-12-30 16:27:07 +00:00
LOG_ERROR ( " Error decoding protobuf for simulator message! \n " ) ;
2022-10-01 10:06:59 +00:00
}
// Let SimRadio receive as if it did via its LoRa chip
2023-01-21 13:34:29 +00:00
SimRadio : : instance - > startReceive ( & p ) ;
return ;
2022-10-01 10:06:59 +00:00
}
2023-01-21 13:34:29 +00:00
# endif
2021-03-05 02:19:27 +00:00
if ( p . from ! = 0 ) { // We don't let phones assign nodenums to their sent messages
2022-12-30 02:41:37 +00:00
LOG_WARN ( " phone tried to pick a nodenum, we don't allow that. \n " ) ;
2021-03-05 02:19:27 +00:00
p . from = 0 ;
} else {
// p.from = nodeDB.getNodeNum();
}
2020-02-19 18:53:09 +00:00
2020-04-22 21:55:36 +00:00
if ( p . id = = 0 )
p . id = generatePacketId ( ) ; // If the phone didn't supply one, then pick one
2020-04-17 18:52:20 +00:00
2020-10-09 02:01:13 +00:00
p . rx_time = getValidTime ( RTCQualityFromNet ) ; // Record the time the packet arrived from the phone
2020-10-09 06:16:51 +00:00
// (so we update our nodedb for the local node)
2020-04-17 19:41:01 +00:00
2020-04-22 21:55:36 +00:00
// Send the packet into the mesh
2020-02-19 18:53:09 +00:00
2021-10-27 13:16:51 +00:00
sendToMesh ( packetPool . allocCopy ( p ) , RX_SRC_USER ) ;
2020-04-17 18:52:20 +00:00
2020-04-22 21:55:36 +00:00
bool loopback = false ; // if true send any packet the phone sends back itself (for testing)
if ( loopback ) {
// no need to copy anymore because handle from radio assumes it should _not_ delete
// packetPool.allocCopy(r.variant.packet);
handleFromRadio ( & p ) ;
// handleFromRadio will tell the phone a new packet arrived
2020-03-15 23:27:15 +00:00
}
2020-02-02 20:45:32 +00:00
}
2021-02-11 09:39:53 +00:00
/** Attempt to cancel a previously sent packet from this _local_ node. Returns true if a packet was found we could cancel */
2021-03-05 02:19:27 +00:00
bool MeshService : : cancelSending ( PacketId id )
{
2021-02-11 09:39:53 +00:00
return router - > cancelSending ( nodeDB . getNodeNum ( ) , id ) ;
}
2023-01-21 17:22:19 +00:00
ErrorCode MeshService : : sendQueueStatusToPhone ( const meshtastic_QueueStatus & qs , ErrorCode res , uint32_t mesh_packet_id )
2023-01-04 12:56:52 +00:00
{
2023-01-21 17:22:19 +00:00
meshtastic_QueueStatus * copied = queueStatusPool . allocCopy ( qs ) ;
2023-01-04 12:56:52 +00:00
copied - > res = res ;
copied - > mesh_packet_id = mesh_packet_id ;
if ( toPhoneQueueStatusQueue . numFree ( ) = = 0 ) {
LOG_DEBUG ( " NOTE: tophone queue status queue is full, discarding oldest \n " ) ;
2023-01-21 17:22:19 +00:00
meshtastic_QueueStatus * d = toPhoneQueueStatusQueue . dequeuePtr ( 0 ) ;
2023-01-04 12:56:52 +00:00
if ( d )
releaseQueueStatusToPool ( d ) ;
}
lastQueueStatus = * copied ;
res = toPhoneQueueStatusQueue . enqueue ( copied , 0 ) ;
fromNum + + ;
return res ? ERRNO_OK : ERRNO_UNKNOWN ;
}
2023-01-21 17:22:19 +00:00
void MeshService : : sendToMesh ( meshtastic_MeshPacket * p , RxSource src , bool ccToPhone )
2020-02-02 20:45:32 +00:00
{
2023-01-04 12:56:52 +00:00
uint32_t mesh_packet_id = p - > id ;
2020-02-17 00:03:16 +00:00
nodeDB . updateFrom ( * p ) ; // update our local DB for this packet (because phone might have sent position packets etc...)
2020-02-19 04:17:11 +00:00
2020-05-19 18:56:17 +00:00
// Note: We might return !OK if our fifo was full, at that point the only option we have is to drop it
2023-01-04 12:56:52 +00:00
ErrorCode res = router - > sendLocal ( p , src ) ;
/* NOTE(pboldin): Prepare and send QueueStatus message to the phone as a
* high - priority message . */
2023-01-21 17:22:19 +00:00
meshtastic_QueueStatus qs = router - > getQueueStatus ( ) ;
2023-01-04 12:56:52 +00:00
ErrorCode r = sendQueueStatusToPhone ( qs , res , mesh_packet_id ) ;
if ( r ! = ERRNO_OK ) {
LOG_DEBUG ( " Can't send status to phone " ) ;
}
2022-04-27 00:40:24 +00:00
if ( ccToPhone ) {
2023-03-14 21:50:32 +00:00
sendToPhone ( packetPool . allocCopy ( * p ) ) ;
2022-04-27 00:40:24 +00:00
}
2020-02-06 16:49:33 +00:00
}
2020-02-02 20:45:32 +00:00
2020-03-25 20:09:12 +00:00
void MeshService : : sendNetworkPing ( NodeNum dest , bool wantReplies )
2020-02-12 22:07:06 +00:00
{
2023-06-17 14:10:09 +00:00
meshtastic_NodeInfoLite * node = nodeDB . getMeshNode ( nodeDB . getNodeNum ( ) ) ;
2020-02-12 22:07:06 +00:00
assert ( node ) ;
2023-05-30 10:26:34 +00:00
if ( hasValidPosition ( node ) ) {
2022-02-27 10:21:02 +00:00
if ( positionModule ) {
2023-03-29 11:51:22 +00:00
LOG_INFO ( " Sending position ping to 0x%x, wantReplies=%d, channel=%d \n " , dest , wantReplies , node - > channel ) ;
positionModule - > sendOurPosition ( dest , wantReplies , node - > channel ) ;
2021-03-06 02:36:30 +00:00
}
2021-03-24 22:15:15 +00:00
} else {
2022-02-27 10:21:02 +00:00
if ( nodeInfoModule ) {
2023-03-29 11:51:22 +00:00
LOG_INFO ( " Sending nodeinfo ping to 0x%x, wantReplies=%d, channel=%d \n " , dest , wantReplies , node - > channel ) ;
nodeInfoModule - > sendOurNodeInfo ( dest , wantReplies , node - > channel ) ;
2021-03-06 02:36:30 +00:00
}
}
2020-02-12 22:07:06 +00:00
}
2023-01-21 17:22:19 +00:00
void MeshService : : sendToPhone ( meshtastic_MeshPacket * p )
2022-05-07 10:31:21 +00:00
{
2022-04-27 00:40:24 +00:00
if ( toPhoneQueue . numFree ( ) = = 0 ) {
2022-12-30 16:27:07 +00:00
LOG_WARN ( " ToPhone queue is full, discarding oldest \n " ) ;
2023-01-21 17:22:19 +00:00
meshtastic_MeshPacket * d = toPhoneQueue . dequeuePtr ( 0 ) ;
2022-04-27 00:40:24 +00:00
if ( d )
releaseToPool ( d ) ;
}
2023-03-14 21:50:32 +00:00
perhapsDecode ( p ) ;
assert ( toPhoneQueue . enqueue ( p , 0 ) ) ;
2022-04-27 00:40:24 +00:00
fromNum + + ;
}
2023-07-09 01:37:04 +00:00
void MeshService : : sendMqttMessageToClientProxy ( meshtastic_MqttClientProxyMessage * m )
{
LOG_DEBUG ( " Sending mqtt message on topic '%s' to client for proxying to server \n " , m - > topic ) ;
if ( toPhoneMqttProxyQueue . numFree ( ) = = 0 ) {
LOG_WARN ( " MqttClientProxyMessagePool queue is full, discarding oldest \n " ) ;
meshtastic_MqttClientProxyMessage * d = toPhoneMqttProxyQueue . dequeuePtr ( 0 ) ;
if ( d )
releaseMqttClientProxyMessageToPool ( d ) ;
}
assert ( toPhoneMqttProxyQueue . enqueue ( m , 0 ) ) ;
fromNum + + ;
}
2023-06-17 14:10:09 +00:00
meshtastic_NodeInfoLite * MeshService : : refreshLocalMeshNode ( )
2021-03-05 02:19:27 +00:00
{
2023-06-17 14:10:09 +00:00
meshtastic_NodeInfoLite * node = nodeDB . getMeshNode ( nodeDB . getNodeNum ( ) ) ;
2021-01-04 01:59:53 +00:00
assert ( node ) ;
// We might not have a position yet for our local node, in that case, at least try to send the time
2021-03-05 02:19:27 +00:00
if ( ! node - > has_position ) {
2021-01-04 01:59:53 +00:00
memset ( & node - > position , 0 , sizeof ( node - > position ) ) ;
node - > has_position = true ;
}
2021-03-05 02:19:27 +00:00
2023-06-17 14:10:09 +00:00
meshtastic_PositionLite & position = node - > position ;
2021-01-04 01:59:53 +00:00
2021-03-27 08:17:01 +00:00
// Update our local node info with our time (even if we don't decide to update anyone else)
2021-03-26 01:30:15 +00:00
node - > last_heard =
2021-03-05 02:19:27 +00:00
getValidTime ( RTCQualityFromNet ) ; // This nodedb timestamp might be stale, so update it if our clock is kinda valid
2021-01-04 01:59:53 +00:00
2023-01-11 13:50:07 +00:00
position . time = getValidTime ( RTCQualityFromNet ) ;
2021-03-27 08:17:01 +00:00
2022-03-20 14:55:38 +00:00
updateBatteryLevel ( powerStatus - > getBatteryChargePercent ( ) ) ;
2021-01-04 01:59:53 +00:00
return node ;
}
2021-10-23 03:21:59 +00:00
int MeshService : : onGPSChanged ( const meshtastic : : GPSStatus * newStatus )
2020-02-06 18:58:19 +00:00
{
2020-02-12 22:07:06 +00:00
// Update our local node info with our position (even if we don't decide to update anyone else)
2023-08-12 14:29:19 +00:00
const meshtastic_NodeInfoLite * node = refreshLocalMeshNode ( ) ;
2023-01-21 17:22:19 +00:00
meshtastic_Position pos = meshtastic_Position_init_default ;
2020-09-16 16:18:44 +00:00
2021-10-23 03:21:59 +00:00
if ( newStatus - > getHasLock ( ) ) {
2021-10-24 12:48:48 +00:00
// load data from GPS object, will add timestamp + battery further down
pos = gps - > p ;
2021-03-05 02:19:27 +00:00
} else {
2023-08-24 15:55:49 +00:00
// The GPS has lost lock
2022-04-26 11:00:11 +00:00
# ifdef GPS_EXTRAVERBOSE
2022-12-30 02:41:37 +00:00
LOG_DEBUG ( " onGPSchanged() - lost validLocation \n " ) ;
2021-10-24 12:48:48 +00:00
# endif
2023-08-24 15:55:49 +00:00
}
// Used fixed position if configured regalrdless of GPS lock
if ( config . position . fixed_position ) {
LOG_WARN ( " Using fixed position \n " ) ;
pos = ConvertToPosition ( node - > position ) ;
2020-12-09 04:05:15 +00:00
}
2020-02-06 16:49:33 +00:00
2021-10-24 12:48:48 +00:00
// Finally add a fresh timestamp and battery level reading
2023-06-17 14:10:09 +00:00
// I KNOW this is redundant with refreshLocalMeshNode() above, but these are
2021-10-24 12:48:48 +00:00
// inexpensive nonblocking calls and can be refactored in due course
pos . time = getValidTime ( RTCQualityGPS ) ;
// In debug logs, identify position by @timestamp:stage (stage 4 = nodeDB)
2022-12-30 02:41:37 +00:00
LOG_DEBUG ( " onGPSChanged() pos@%x, time=%u, lat=%d, lon=%d, alt=%d \n " , pos . timestamp , pos . time , pos . latitude_i ,
2022-05-07 10:31:21 +00:00
pos . longitude_i , pos . altitude ) ;
2020-08-12 22:51:57 +00:00
2020-12-05 00:46:19 +00:00
// Update our current position in the local DB
2021-10-23 01:58:56 +00:00
nodeDB . updatePosition ( nodeDB . getNodeNum ( ) , pos , RX_SRC_LOCAL ) ;
2020-12-05 00:46:19 +00:00
2020-04-10 19:40:44 +00:00
return 0 ;
2020-02-02 20:45:32 +00:00
}
2022-10-16 14:58:58 +00:00
2023-01-21 13:34:29 +00:00
bool MeshService : : isToPhoneQueueEmpty ( )
2022-10-16 14:58:58 +00:00
{
return toPhoneQueue . isEmpty ( ) ;
2023-06-17 14:10:09 +00:00
}