5858
5959#define LAZY_CONTACTS_WRITE_DELAY 5000
6060
61+ #define ALERT_ACK_EXPIRY_MILLIS 6000 // wait 6 secs for ACKs to alert messages
62+
6163static File openAppend (FILESYSTEM* _fs, const char * fname) {
6264 #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
6365 return _fs->open (fname, FILE_O_WRITE);
@@ -163,6 +165,7 @@ static uint8_t getDataSize(uint8_t type) {
163165 case LPP_TEMPERATURE:
164166 case LPP_CONCENTRATION:
165167 case LPP_BAROMETRIC_PRESSURE:
168+ case LPP_RELATIVE_HUMIDITY:
166169 case LPP_ALTITUDE:
167170 case LPP_VOLTAGE:
168171 case LPP_CURRENT:
@@ -185,6 +188,7 @@ static uint32_t getMultiplier(uint8_t type) {
185188 return 100 ;
186189 case LPP_TEMPERATURE:
187190 case LPP_BAROMETRIC_PRESSURE:
191+ case LPP_RELATIVE_HUMIDITY:
188192 return 10 ;
189193 }
190194 return 1 ;
@@ -332,46 +336,54 @@ void SensorMesh::applyContactPermissions(const uint8_t* pubkey, uint16_t perms)
332336 dirty_contacts_expiry = futureMillis (LAZY_CONTACTS_WRITE_DELAY); // trigger saveContacts()
333337}
334338
335- void SensorMesh::sendAlert (AlertPriority pri, const char * text) {
336- int text_len = strlen (text);
337- uint16_t pri_mask = (pri == HIGH_PRI_ALERT) ? PERM_RECV_ALERTS_HI : PERM_RECV_ALERTS_LO;
339+ void SensorMesh::sendAlert (ContactInfo* c, Trigger* t) {
340+ int text_len = strlen (t->text );
338341
339- // send text message to all contacts with RECV_ALERT permission
340- for (int i = 0 ; i < num_contacts; i++) {
341- auto c = &contacts[i];
342- if ((c->permissions & pri_mask) == 0 ) continue ; // contact does NOT want alert
343-
344- uint8_t data[MAX_PACKET_PAYLOAD];
345- uint32_t now = getRTCClock ()->getCurrentTimeUnique (); // need different timestamp per packet
346- memcpy (data, &now, 4 );
347- data[4 ] = (TXT_TYPE_PLAIN << 2 ); // attempt and flags
348- memcpy (&data[5 ], text, text_len);
349- // calc expected ACK reply
350- // uint32_t expected_ack;
351- // mesh::Utils::sha256((uint8_t *)&expected_ack, 4, data, 5 + text_len, self_id.pub_key, PUB_KEY_SIZE);
352-
353- auto pkt = createDatagram (PAYLOAD_TYPE_TXT_MSG, c->id , c->shared_secret , data, 5 + text_len);
354- if (pkt) {
355- if (c->out_path_len >= 0 ) { // we have an out_path, so send DIRECT
356- sendDirect (pkt, c->out_path , c->out_path_len );
357- } else {
358- sendFlood (pkt);
359- }
342+ uint8_t data[MAX_PACKET_PAYLOAD];
343+ memcpy (data, &t->timestamp , 4 );
344+ data[4 ] = (TXT_TYPE_PLAIN << 2 ) | t->attempt ; // attempt and flags
345+ memcpy (&data[5 ], t->text , text_len);
346+
347+ // calc expected ACK reply
348+ mesh::Utils::sha256 ((uint8_t *)&t->expected_acks [t->attempt ], 4 , data, 5 + text_len, self_id.pub_key , PUB_KEY_SIZE);
349+ t->attempt ++;
350+
351+ auto pkt = createDatagram (PAYLOAD_TYPE_TXT_MSG, c->id , c->shared_secret , data, 5 + text_len);
352+ if (pkt) {
353+ if (c->out_path_len >= 0 ) { // we have an out_path, so send DIRECT
354+ sendDirect (pkt, c->out_path , c->out_path_len );
355+ } else {
356+ sendFlood (pkt);
360357 }
361358 }
359+ t->send_expiry = futureMillis (ALERT_ACK_EXPIRY_MILLIS);
362360}
363361
364362void SensorMesh::alertIf (bool condition, Trigger& t, AlertPriority pri, const char * text) {
365363 if (condition) {
366- if (!t.triggered ) {
367- t.triggered = true ;
368- t.time = getRTCClock ()->getCurrentTime ();
369- sendAlert (pri, text);
364+ if (!t.isTriggered () && num_alert_tasks < MAX_CONCURRENT_ALERTS) {
365+ StrHelper::strncpy (t.text , text, sizeof (t.text ));
366+ t.pri = pri;
367+ t.send_expiry = 0 ; // signal that initial send is needed
368+ t.attempt = 4 ;
369+ t.curr_contact_idx = -1 ; // start iterating thru contacts[]
370+
371+ alert_tasks[num_alert_tasks++] = &t; // add to queue
370372 }
371373 } else {
372- if (t.triggered ) {
373- t.triggered = false ;
374- // TODO: apply debounce logic
374+ if (t.isTriggered ()) {
375+ t.text [0 ] = 0 ;
376+ // remove 't' from alert queue
377+ int i = 0 ;
378+ while (i < num_alert_tasks && alert_tasks[i] != &t) i++;
379+
380+ if (i < num_alert_tasks) { // found, now delete from array
381+ num_alert_tasks--;
382+ while (i < num_alert_tasks) {
383+ alert_tasks[i] = alert_tasks[i + 1 ];
384+ i++;
385+ }
386+ }
375387 }
376388 }
377389}
@@ -629,6 +641,20 @@ bool SensorMesh::onPeerPathRecv(mesh::Packet* packet, int sender_idx, const uint
629641 return false ;
630642}
631643
644+ void SensorMesh::onAckRecv (mesh::Packet* packet, uint32_t ack_crc) {
645+ if (num_alert_tasks > 0 ) {
646+ auto t = alert_tasks[0 ]; // check current alert task
647+ for (int i = 0 ; i < t->attempt ; i++) {
648+ if (ack_crc == t->expected_acks [i]) { // matching ACK!
649+ t->attempt = 4 ; // signal to move to next contact
650+ t->send_expiry = 0 ;
651+ packet->markDoNotRetransmit (); // ACK was for this node, so don't retransmit
652+ return ;
653+ }
654+ }
655+ }
656+ }
657+
632658SensorMesh::SensorMesh (mesh::MainBoard& board, mesh::Radio& radio, mesh::MillisecondClock& ms, mesh::RNG& rng, mesh::RTCClock& rtc, mesh::MeshTables& tables)
633659 : mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(32 ), tables),
634660 _cli(board, rtc, &_prefs, this ), telemetry(MAX_PACKET_PAYLOAD - 4 )
@@ -637,6 +663,7 @@ SensorMesh::SensorMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::Millise
637663 next_local_advert = next_flood_advert = 0 ;
638664 dirty_contacts_expiry = 0 ;
639665 last_read_time = 0 ;
666+ num_alert_tasks = 0 ;
640667
641668 // defaults
642669 memset (&_prefs, 0 , sizeof (_prefs));
@@ -736,7 +763,14 @@ float SensorMesh::getTelemValue(uint8_t channel, uint8_t type) {
736763}
737764
738765bool SensorMesh::getGPS (uint8_t channel, float & lat, float & lon, float & alt) {
739- return false ; // TODO
766+ if (channel == TELEM_CHANNEL_SELF) {
767+ lat = sensors.node_lat ;
768+ lon = sensors.node_lon ;
769+ alt = sensors.node_altitude ;
770+ return true ;
771+ }
772+ // REVISIT: custom GPS channels??
773+ return false ;
740774}
741775
742776void SensorMesh::loop () {
@@ -767,6 +801,42 @@ void SensorMesh::loop() {
767801 last_read_time = curr;
768802 }
769803
804+ // check the alert send queue
805+ if (num_alert_tasks > 0 ) {
806+ auto t = alert_tasks[0 ]; // process head of queue
807+
808+ if (millisHasNowPassed (t->send_expiry )) { // next send needed?
809+ if (t->attempt >= 4 ) { // max attempts reached, try next contact
810+ t->curr_contact_idx ++;
811+ if (t->curr_contact_idx >= num_contacts) { // no more contacts to try?
812+ num_alert_tasks--; // remove t from queue
813+ for (int i = 0 ; i < num_alert_tasks; i++) {
814+ alert_tasks[i] = alert_tasks[i + 1 ];
815+ }
816+ } else {
817+ auto c = &contacts[t->curr_contact_idx ];
818+ uint16_t pri_mask = (t->pri == HIGH_PRI_ALERT) ? PERM_RECV_ALERTS_HI : PERM_RECV_ALERTS_LO;
819+
820+ if (c->permissions & pri_mask) { // contact wants alert
821+ // reset attempts
822+ t->attempt = (t->pri == LOW_PRI_ALERT) ? 3 : 0 ; // Low pri alerts, start at attempt #3 (ie. only make ONE attempt)
823+ t->timestamp = getRTCClock ()->getCurrentTimeUnique (); // need unique timestamp per contact
824+
825+ sendAlert (c, t); // NOTE: modifies attempt, expected_acks[] and send_expiry
826+ } else {
827+ // next contact tested in next ::loop()
828+ }
829+ }
830+ } else if (t->curr_contact_idx < num_contacts) {
831+ auto c = &contacts[t->curr_contact_idx ]; // send next attempt
832+ sendAlert (c, t); // NOTE: modifies attempt, expected_acks[] and send_expiry
833+ } else {
834+ // contact list has likely been modified while waiting for alert ACK, cancel this task
835+ t->attempt = 4 ; // next ::loop() will remove t from queue
836+ }
837+ }
838+ }
839+
770840 // is there are pending dirty contacts write needed?
771841 if (dirty_contacts_expiry && millisHasNowPassed (dirty_contacts_expiry)) {
772842 saveContacts ();
0 commit comments