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-03-19 02:15:51 +00:00
# include "GPS.h"
2020-04-10 19:18:48 +00:00
//#include "MeshBluetoothService.h"
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
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"
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
The algoritm 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
2020-04-17 16:48:54 +00:00
# include "Router.h"
2020-02-03 17:13:19 +00:00
2020-04-17 16:48:54 +00:00
MeshService : : MeshService ( ) : toPhoneQueue ( MAX_RX_TOPHONE )
2020-02-02 20:45:32 +00:00
{
2020-02-03 17:13:19 +00:00
// assert(MAX_RX_TOPHONE == 32); // FIXME, delete this, just checking my clever macro
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
}
2020-04-17 16:48:54 +00:00
int MeshService : : handleFromRadio ( const 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
printPacket ( " Forwarding to phone " , mp ) ;
nodeDB . updateFrom ( * mp ) ; // update our DB state based off sniffing every RX packet from the radio
2020-02-18 01:47:01 +00:00
2022-04-27 00:40:24 +00:00
sendToPhone ( ( MeshPacket * ) 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 ( )
{
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
void MeshService : : reloadOwner ( )
{
2021-10-29 13:28:48 +00:00
// DEBUG_MSG("reloadOwner()\n");
// update our local data directly
nodeDB . updateUser ( nodeDB . getNodeNum ( ) , owner ) ;
2022-02-27 10:21:02 +00:00
assert ( nodeInfoModule ) ;
2021-10-29 13:28:48 +00:00
// update everyone else
2022-02-27 10:21:02 +00:00
if ( nodeInfoModule )
nodeInfoModule - > sendOurNodeInfo ( ) ;
2022-10-04 12:32:07 +00:00
nodeDB . saveToDisk ( SEGMENT_DEVICESTATE ) ;
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
*/
void MeshService : : handleToRadio ( MeshPacket & p )
2020-02-02 20:45:32 +00:00
{
2022-10-01 10:06:59 +00:00
# ifdef ARCH_PORTDUINO
// Simulates device is receiving a packet via the LoRa chip
if ( p . decoded . portnum = = PortNum_SIMULATOR_APP ) {
// Simulator packet (=Compressed packet) is encapsulated in a MeshPacket, so need to unwrap first
Compressed scratch ;
Compressed * decoded = NULL ;
if ( p . which_payload_variant = = MeshPacket_decoded_tag ) {
memset ( & scratch , 0 , sizeof ( scratch ) ) ;
p . decoded . payload . size = pb_decode_from_bytes ( p . decoded . payload . bytes , p . decoded . payload . size , & Compressed_msg , & scratch ) ;
if ( p . decoded . payload . size ) {
decoded = & scratch ;
// Extract the original payload and replace
memcpy ( & p . decoded . payload , & decoded - > data , sizeof ( decoded - > data ) ) ;
// Switch the port from PortNum_SIMULATOR_APP back to the original PortNum
p . decoded . portnum = decoded - > portnum ;
} else
DEBUG_MSG ( " Error decoding protobuf for simulator message! \n " ) ;
}
// Let SimRadio receive as if it did via its LoRa chip
SimRadio : : instance - > startReceive ( & p ) ;
return ;
}
# endif
2021-03-05 02:19:27 +00:00
if ( p . from ! = 0 ) { // We don't let phones assign nodenums to their sent messages
DEBUG_MSG ( " Warning: phone tried to pick a nodenum, we don't allow that. \n " ) ;
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 ) ;
}
2022-04-27 00:40:24 +00:00
void MeshService : : sendToMesh ( MeshPacket * p , RxSource src , bool ccToPhone )
2020-02-02 20:45:32 +00:00
{
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
2021-10-27 13:16:51 +00:00
router - > sendLocal ( p , src ) ;
2022-04-27 00:40:24 +00:00
if ( ccToPhone ) {
sendToPhone ( p ) ;
}
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
{
NodeInfo * node = nodeDB . getNode ( nodeDB . getNodeNum ( ) ) ;
assert ( node ) ;
2022-08-07 20:08:46 +00:00
if ( node - > has_position & & ( node - > position . latitude_i ! = 0 | | node - > position . longitude_i ! = 0 ) ) {
2022-02-27 10:21:02 +00:00
if ( positionModule ) {
2021-03-06 02:36:30 +00:00
DEBUG_MSG ( " Sending position ping to 0x%x, wantReplies=%d \n " , dest , wantReplies ) ;
2022-02-27 10:21:02 +00:00
positionModule - > sendOurPosition ( dest , wantReplies ) ;
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 ) {
2021-03-06 02:36:30 +00:00
DEBUG_MSG ( " Sending nodeinfo ping to 0x%x, wantReplies=%d \n " , dest , wantReplies ) ;
2022-02-27 10:21:02 +00:00
nodeInfoModule - > sendOurNodeInfo ( dest , wantReplies ) ;
2021-03-06 02:36:30 +00:00
}
}
2020-02-12 22:07:06 +00:00
}
2022-05-07 10:31:21 +00:00
void MeshService : : sendToPhone ( MeshPacket * p )
{
2022-04-27 00:40:24 +00:00
if ( toPhoneQueue . numFree ( ) = = 0 ) {
DEBUG_MSG ( " NOTE: tophone queue is full, discarding oldest \n " ) ;
MeshPacket * d = toPhoneQueue . dequeuePtr ( 0 ) ;
if ( d )
releaseToPool ( d ) ;
}
MeshPacket * copied = packetPool . allocCopy ( * p ) ;
perhapsDecode ( copied ) ;
assert ( toPhoneQueue . enqueue ( copied , 0 ) ) ; // FIXME, instead of failing for full queue, delete the oldest mssages
fromNum + + ;
}
2021-03-05 02:19:27 +00:00
NodeInfo * MeshService : : refreshMyNodeInfo ( )
{
2021-01-04 01:59:53 +00:00
NodeInfo * node = nodeDB . getNode ( nodeDB . getNodeNum ( ) ) ;
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
2021-01-04 01:59:53 +00:00
Position & position = node - > position ;
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
2021-03-27 08:17:01 +00:00
// For the time in the position field, only set that if we have a real GPS clock
position . time = getValidTime ( RTCQualityGPS ) ;
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)
2021-01-04 01:59:53 +00:00
NodeInfo * node = refreshMyNodeInfo ( ) ;
2021-10-24 12:48:48 +00:00
Position pos = 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 {
2020-12-09 04:05:15 +00:00
// The GPS has lost lock, if we are fixed position we should just keep using
// the old position
2022-04-26 11:00:11 +00:00
# ifdef GPS_EXTRAVERBOSE
2021-10-24 12:48:48 +00:00
DEBUG_MSG ( " onGPSchanged() - lost validLocation \n " ) ;
# endif
2022-05-21 20:38:33 +00:00
if ( config . position . fixed_position ) {
2020-12-09 04:05:15 +00:00
DEBUG_MSG ( " WARNING: Using fixed position \n " ) ;
2021-10-24 12:48:48 +00:00
pos = 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
// I KNOW this is redundant with refreshMyNodeInfo() above, but these are
// 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-09-09 10:51:41 +00:00
DEBUG_MSG ( " 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
bool MeshService : : isToPhoneQueueEmpty ( )
{
return toPhoneQueue . isEmpty ( ) ;
}