peers.h (4420B)
1 #pragma once 2 #include <Arduino.h> 3 #include <string.h> 4 #include "config.h" 5 #include "pet.h" // PET_FACE_MAX — peers carry the sender's pet face 6 #include "encounters.h" // Encounter struct (emitted when a peer leaves range) 7 8 // One discovered friend, with the running "encounter" segment we're building. 9 struct Peer { 10 char handle[HANDLE_MAX_LEN + 1]; // +1 for NUL so it's printable 11 int8_t rssi; // last seen signal strength (dBm) 12 uint32_t last_seen; // millis() of last valid beacon (TTL) 13 uint32_t counter; // last beacon counter (freshness) 14 uint32_t first_epoch; // wall-clock start of this segment 15 uint32_t last_epoch; // wall-clock of last sighting 16 uint16_t sightings; // beacons heard this segment 17 int8_t rssi_max; // closest approach this segment 18 uint8_t pet; // sender's pet index (==PET_BUILTIN_N → custom) 19 char pet_face[PET_FACE_MAX + 1]; // sender's pet idle face (+1 NUL for printable) 20 }; 21 22 // Bounded, flood-resistant table of nearby badges. Not internally locked — 23 // the caller (main.cpp) guards access with a portMUX, since the ESP-NOW 24 // receive callback and the main loop both touch it. 25 class PeerTable { 26 public: 27 // Insert or refresh a peer. Evicts the stalest entry if full. 28 void upsert(const char* handle, uint8_t hlen, int8_t rssi, 29 uint32_t counter, uint32_t now_ms, uint32_t now_epoch, 30 uint8_t pet, const char* pet_face) { 31 int idx = find(handle, hlen); 32 if (idx < 0) { // new segment begins 33 idx = (_n < MAX_PEERS) ? _n++ : stalest(); 34 memset(&_peers[idx], 0, sizeof(Peer)); 35 memcpy(_peers[idx].handle, handle, hlen); 36 _peers[idx].handle[hlen] = '\0'; 37 _peers[idx].first_epoch = now_epoch; 38 _peers[idx].rssi_max = rssi; 39 } 40 Peer& p = _peers[idx]; 41 p.rssi = rssi; 42 p.last_seen = now_ms; 43 p.counter = counter; 44 p.last_epoch = now_epoch; 45 p.sightings++; 46 if (rssi > p.rssi_max) p.rssi_max = rssi; 47 p.pet = pet; // refresh pet each beacon (swaps live) 48 memcpy(p.pet_face, pet_face, PET_FACE_MAX); 49 p.pet_face[PET_FACE_MAX] = '\0'; 50 } 51 52 // Drop peers idle > PEER_TTL_MS, emitting one Encounter per departure. 53 void expire(uint32_t now_ms, Encounter* out, int& out_n, int max) { 54 out_n = 0; 55 for (int i = 0; i < _n; ) { 56 if (now_ms - _peers[i].last_seen > PEER_TTL_MS) { 57 if (out && out_n < max) to_encounter(_peers[i], out[out_n++]); 58 _peers[i] = _peers[--_n]; // swap-remove 59 } else { 60 i++; 61 } 62 } 63 } 64 65 // Finalize all in-range peers as encounters (used when entering REPORT), then 66 // start a fresh segment for each so they aren't double-counted on expiry. 67 void flush(Encounter* out, int& out_n, int max) { 68 out_n = 0; 69 for (int i = 0; i < _n; i++) { 70 if (out && out_n < max) to_encounter(_peers[i], out[out_n++]); 71 _peers[i].first_epoch = _peers[i].last_epoch; 72 _peers[i].sightings = 1; 73 _peers[i].rssi_max = _peers[i].rssi; 74 } 75 } 76 77 // Copy current peers into `out`, sorted by RSSI (closest first). 78 int snapshot(Peer* out, int max) { 79 int n = (_n < max) ? _n : max; 80 for (int i = 0; i < n; i++) out[i] = _peers[i]; 81 for (int i = 1; i < n; i++) { // insertion sort, n is tiny 82 Peer key = out[i]; 83 int j = i - 1; 84 while (j >= 0 && out[j].rssi < key.rssi) { out[j + 1] = out[j]; j--; } 85 out[j + 1] = key; 86 } 87 return n; 88 } 89 90 int count() const { return _n; } 91 92 private: 93 Peer _peers[MAX_PEERS]; 94 int _n = 0; 95 96 static void to_encounter(const Peer& p, Encounter& e) { 97 strncpy(e.handle, p.handle, HANDLE_MAX_LEN); 98 e.handle[HANDLE_MAX_LEN] = '\0'; 99 e.first_epoch = p.first_epoch; 100 e.last_epoch = p.last_epoch; 101 e.sightings = p.sightings; 102 e.rssi_max = p.rssi_max; 103 } 104 105 int find(const char* handle, uint8_t hlen) { 106 for (int i = 0; i < _n; i++) 107 if (strlen(_peers[i].handle) == hlen && 108 memcmp(_peers[i].handle, handle, hlen) == 0) 109 return i; 110 return -1; 111 } 112 113 int stalest() { 114 int idx = 0; 115 for (int i = 1; i < _n; i++) 116 if (_peers[i].last_seen < _peers[idx].last_seen) idx = i; 117 return idx; 118 } 119 };