main.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594
  1. #include <Arduino.h> // needed for PlatformIO
  2. #include <Mesh.h>
  3. #if defined(NRF52_PLATFORM)
  4. #include <InternalFileSystem.h>
  5. #elif defined(RP2040_PLATFORM)
  6. #include <LittleFS.h>
  7. #elif defined(ESP32)
  8. #include <SPIFFS.h>
  9. #endif
  10. #include <helpers/ArduinoHelpers.h>
  11. #include <helpers/StaticPoolPacketManager.h>
  12. #include <helpers/SimpleMeshTables.h>
  13. #include <helpers/IdentityStore.h>
  14. #include <RTClib.h>
  15. #include <target.h>
  16. /* ---------------------------------- CONFIGURATION ------------------------------------- */
  17. #define FIRMWARE_VER_TEXT "v2 (build: 4 Feb 2025)"
  18. #ifndef LORA_FREQ
  19. #define LORA_FREQ 915.0
  20. #endif
  21. #ifndef LORA_BW
  22. #define LORA_BW 250
  23. #endif
  24. #ifndef LORA_SF
  25. #define LORA_SF 10
  26. #endif
  27. #ifndef LORA_CR
  28. #define LORA_CR 5
  29. #endif
  30. #ifndef LORA_TX_POWER
  31. #define LORA_TX_POWER 20
  32. #endif
  33. #ifndef MAX_CONTACTS
  34. #define MAX_CONTACTS 100
  35. #endif
  36. #include <helpers/BaseChatMesh.h>
  37. #define SEND_TIMEOUT_BASE_MILLIS 500
  38. #define FLOOD_SEND_TIMEOUT_FACTOR 16.0f
  39. #define DIRECT_SEND_PERHOP_FACTOR 6.0f
  40. #define DIRECT_SEND_PERHOP_EXTRA_MILLIS 250
  41. #define PUBLIC_GROUP_PSK "izOH6cXN6mrJ5e26oRXNcg=="
  42. // Believe it or not, this std C function is busted on some platforms!
  43. static uint32_t _atoi(const char* sp) {
  44. uint32_t n = 0;
  45. while (*sp && *sp >= '0' && *sp <= '9') {
  46. n *= 10;
  47. n += (*sp++ - '0');
  48. }
  49. return n;
  50. }
  51. /* -------------------------------------------------------------------------------------- */
  52. struct NodePrefs { // persisted to file
  53. float airtime_factor;
  54. char node_name[32];
  55. double node_lat, node_lon;
  56. float freq;
  57. int8_t tx_power_dbm;
  58. uint8_t unused[3];
  59. };
  60. class MyMesh : public BaseChatMesh, ContactVisitor {
  61. FILESYSTEM* _fs;
  62. NodePrefs _prefs;
  63. uint32_t expected_ack_crc;
  64. ChannelDetails* _public;
  65. unsigned long last_msg_sent;
  66. ContactInfo* curr_recipient;
  67. char command[512+10];
  68. uint8_t tmp_buf[256];
  69. char hex_buf[512];
  70. const char* getTypeName(uint8_t type) const {
  71. if (type == ADV_TYPE_CHAT) return "Chat";
  72. if (type == ADV_TYPE_REPEATER) return "Repeater";
  73. if (type == ADV_TYPE_ROOM) return "Room";
  74. return "??"; // unknown
  75. }
  76. void loadContacts() {
  77. if (_fs->exists("/contacts")) {
  78. #if defined(RP2040_PLATFORM)
  79. File file = _fs->open("/contacts", "r");
  80. #else
  81. File file = _fs->open("/contacts");
  82. #endif
  83. if (file) {
  84. bool full = false;
  85. while (!full) {
  86. ContactInfo c;
  87. uint8_t pub_key[32];
  88. uint8_t unused;
  89. uint32_t reserved;
  90. bool success = (file.read(pub_key, 32) == 32);
  91. success = success && (file.read((uint8_t *) &c.name, 32) == 32);
  92. success = success && (file.read(&c.type, 1) == 1);
  93. success = success && (file.read(&c.flags, 1) == 1);
  94. success = success && (file.read(&unused, 1) == 1);
  95. success = success && (file.read((uint8_t *) &reserved, 4) == 4);
  96. success = success && (file.read((uint8_t *) &c.out_path_len, 1) == 1);
  97. success = success && (file.read((uint8_t *) &c.last_advert_timestamp, 4) == 4);
  98. success = success && (file.read(c.out_path, 64) == 64);
  99. c.gps_lat = c.gps_lon = 0; // not yet supported
  100. if (!success) break; // EOF
  101. c.id = mesh::Identity(pub_key);
  102. c.lastmod = 0;
  103. if (!addContact(c)) full = true;
  104. }
  105. file.close();
  106. }
  107. }
  108. }
  109. void saveContacts() {
  110. #if defined(NRF52_PLATFORM)
  111. _fs->remove("/contacts");
  112. File file = _fs->open("/contacts", FILE_O_WRITE);
  113. #elif defined(RP2040_PLATFORM)
  114. File file = _fs->open("/contacts", "w");
  115. #else
  116. File file = _fs->open("/contacts", "w", true);
  117. #endif
  118. if (file) {
  119. ContactsIterator iter;
  120. ContactInfo c;
  121. uint8_t unused = 0;
  122. uint32_t reserved = 0;
  123. while (iter.hasNext(this, c)) {
  124. bool success = (file.write(c.id.pub_key, 32) == 32);
  125. success = success && (file.write((uint8_t *) &c.name, 32) == 32);
  126. success = success && (file.write(&c.type, 1) == 1);
  127. success = success && (file.write(&c.flags, 1) == 1);
  128. success = success && (file.write(&unused, 1) == 1);
  129. success = success && (file.write((uint8_t *) &reserved, 4) == 4);
  130. success = success && (file.write((uint8_t *) &c.out_path_len, 1) == 1);
  131. success = success && (file.write((uint8_t *) &c.last_advert_timestamp, 4) == 4);
  132. success = success && (file.write(c.out_path, 64) == 64);
  133. if (!success) break; // write failed
  134. }
  135. file.close();
  136. }
  137. }
  138. void setClock(uint32_t timestamp) {
  139. uint32_t curr = getRTCClock()->getCurrentTime();
  140. if (timestamp > curr) {
  141. getRTCClock()->setCurrentTime(timestamp);
  142. Serial.println(" (OK - clock set!)");
  143. } else {
  144. Serial.println(" (ERR: clock cannot go backwards)");
  145. }
  146. }
  147. void importCard(const char* command) {
  148. while (*command == ' ') command++; // skip leading spaces
  149. if (memcmp(command, "meshcore://", 11) == 0) {
  150. command += 11; // skip the prefix
  151. char *ep = strchr(command, 0); // find end of string
  152. while (ep > command) {
  153. ep--;
  154. if (mesh::Utils::isHexChar(*ep)) break; // found tail end of card
  155. *ep = 0; // remove trailing spaces and other junk
  156. }
  157. int len = strlen(command);
  158. if (len % 2 == 0) {
  159. len >>= 1; // halve, for num bytes
  160. if (mesh::Utils::fromHex(tmp_buf, len, command)) {
  161. importContact(tmp_buf, len);
  162. return;
  163. }
  164. }
  165. }
  166. Serial.println(" error: invalid format");
  167. }
  168. protected:
  169. float getAirtimeBudgetFactor() const override {
  170. return _prefs.airtime_factor;
  171. }
  172. int calcRxDelay(float score, uint32_t air_time) const override {
  173. return 0; // disable rxdelay
  174. }
  175. bool allowPacketForward(const mesh::Packet* packet) override {
  176. return true;
  177. }
  178. void onDiscoveredContact(ContactInfo& contact, bool is_new, uint8_t path_len, const uint8_t* path) override {
  179. // TODO: if not in favs, prompt to add as fav(?)
  180. Serial.printf("ADVERT from -> %s\n", contact.name);
  181. Serial.printf(" type: %s\n", getTypeName(contact.type));
  182. Serial.print(" public key: "); mesh::Utils::printHex(Serial, contact.id.pub_key, PUB_KEY_SIZE); Serial.println();
  183. saveContacts();
  184. }
  185. void onContactPathUpdated(const ContactInfo& contact) override {
  186. Serial.printf("PATH to: %s, path_len=%d\n", contact.name, (uint32_t) contact.out_path_len);
  187. saveContacts();
  188. }
  189. ContactInfo* processAck(const uint8_t *data) override {
  190. if (memcmp(data, &expected_ack_crc, 4) == 0) { // got an ACK from recipient
  191. Serial.printf(" Got ACK! (round trip: %d millis)\n", _ms->getMillis() - last_msg_sent);
  192. // NOTE: the same ACK can be received multiple times!
  193. expected_ack_crc = 0; // reset our expected hash, now that we have received ACK
  194. return NULL; // TODO: really should return ContactInfo pointer
  195. }
  196. //uint32_t crc;
  197. //memcpy(&crc, data, 4);
  198. //MESH_DEBUG_PRINTLN("unknown ACK received: %08X (expected: %08X)", crc, expected_ack_crc);
  199. return NULL;
  200. }
  201. void onMessageRecv(const ContactInfo& from, mesh::Packet* pkt, uint32_t sender_timestamp, const char *text) override {
  202. Serial.printf("(%s) MSG -> from %s\n", pkt->isRouteDirect() ? "DIRECT" : "FLOOD", from.name);
  203. Serial.printf(" %s\n", text);
  204. if (strcmp(text, "clock sync") == 0) { // special text command
  205. setClock(sender_timestamp + 1);
  206. }
  207. }
  208. void onCommandDataRecv(const ContactInfo& from, mesh::Packet* pkt, uint32_t sender_timestamp, const char *text) override {
  209. }
  210. void onSignedMessageRecv(const ContactInfo& from, mesh::Packet* pkt, uint32_t sender_timestamp, const uint8_t *sender_prefix, const char *text) override {
  211. }
  212. void onChannelMessageRecv(const mesh::GroupChannel& channel, mesh::Packet* pkt, uint32_t timestamp, const char *text) override {
  213. if (pkt->isRouteDirect()) {
  214. Serial.printf("PUBLIC CHANNEL MSG -> (Direct!)\n");
  215. } else {
  216. Serial.printf("PUBLIC CHANNEL MSG -> (Flood) hops %d\n", pkt->path_len);
  217. }
  218. Serial.printf(" %s\n", text);
  219. }
  220. uint8_t onContactRequest(const ContactInfo& contact, uint32_t sender_timestamp, const uint8_t* data, uint8_t len, uint8_t* reply) override {
  221. return 0; // unknown
  222. }
  223. void onContactResponse(const ContactInfo& contact, const uint8_t* data, uint8_t len) override {
  224. // not supported
  225. }
  226. uint32_t calcFloodTimeoutMillisFor(uint32_t pkt_airtime_millis) const override {
  227. return SEND_TIMEOUT_BASE_MILLIS + (FLOOD_SEND_TIMEOUT_FACTOR * pkt_airtime_millis);
  228. }
  229. uint32_t calcDirectTimeoutMillisFor(uint32_t pkt_airtime_millis, uint8_t path_len) const override {
  230. uint8_t path_hash_count = path_len & 63;
  231. return SEND_TIMEOUT_BASE_MILLIS +
  232. ( (pkt_airtime_millis*DIRECT_SEND_PERHOP_FACTOR + DIRECT_SEND_PERHOP_EXTRA_MILLIS) * (path_hash_count + 1));
  233. }
  234. void onSendTimeout() override {
  235. Serial.println(" ERROR: timed out, no ACK.");
  236. }
  237. public:
  238. MyMesh(mesh::Radio& radio, StdRNG& rng, mesh::RTCClock& rtc, SimpleMeshTables& tables)
  239. : BaseChatMesh(radio, *new ArduinoMillis(), rng, rtc, *new StaticPoolPacketManager(16), tables)
  240. {
  241. // defaults
  242. memset(&_prefs, 0, sizeof(_prefs));
  243. _prefs.airtime_factor = 1.0;
  244. strcpy(_prefs.node_name, "NONAME");
  245. _prefs.freq = LORA_FREQ;
  246. _prefs.tx_power_dbm = LORA_TX_POWER;
  247. command[0] = 0;
  248. curr_recipient = NULL;
  249. }
  250. float getFreqPref() const { return _prefs.freq; }
  251. int8_t getTxPowerPref() const { return _prefs.tx_power_dbm; }
  252. void begin(FILESYSTEM& fs) {
  253. _fs = &fs;
  254. BaseChatMesh::begin();
  255. #if defined(NRF52_PLATFORM)
  256. IdentityStore store(fs, "");
  257. #elif defined(RP2040_PLATFORM)
  258. IdentityStore store(fs, "/identity");
  259. store.begin();
  260. #else
  261. IdentityStore store(fs, "/identity");
  262. #endif
  263. if (!store.load("_main", self_id, _prefs.node_name, sizeof(_prefs.node_name))) { // legacy: node_name was from identity file
  264. // Need way to get some entropy to seed RNG
  265. Serial.println("Press ENTER to generate key:");
  266. char c = 0;
  267. while (c != '\n') { // wait for ENTER to be pressed
  268. if (Serial.available()) c = Serial.read();
  269. }
  270. ((StdRNG *)getRNG())->begin(millis());
  271. self_id = mesh::LocalIdentity(getRNG()); // create new random identity
  272. int count = 0;
  273. while (count < 10 && (self_id.pub_key[0] == 0x00 || self_id.pub_key[0] == 0xFF)) { // reserved id hashes
  274. self_id = mesh::LocalIdentity(getRNG()); count++;
  275. }
  276. store.save("_main", self_id);
  277. }
  278. // load persisted prefs
  279. if (_fs->exists("/node_prefs")) {
  280. #if defined(RP2040_PLATFORM)
  281. File file = _fs->open("/node_prefs", "r");
  282. #else
  283. File file = _fs->open("/node_prefs");
  284. #endif
  285. if (file) {
  286. file.read((uint8_t *) &_prefs, sizeof(_prefs));
  287. file.close();
  288. }
  289. }
  290. loadContacts();
  291. _public = addChannel("Public", PUBLIC_GROUP_PSK); // pre-configure Andy's public channel
  292. }
  293. void savePrefs() {
  294. #if defined(NRF52_PLATFORM)
  295. _fs->remove("/node_prefs");
  296. File file = _fs->open("/node_prefs", FILE_O_WRITE);
  297. #elif defined(RP2040_PLATFORM)
  298. File file = _fs->open("/node_prefs", "w");
  299. #else
  300. File file = _fs->open("/node_prefs", "w", true);
  301. #endif
  302. if (file) {
  303. file.write((const uint8_t *)&_prefs, sizeof(_prefs));
  304. file.close();
  305. }
  306. }
  307. void showWelcome() {
  308. Serial.println("===== MeshCore Chat Terminal =====");
  309. Serial.println();
  310. Serial.printf("WELCOME %s\n", _prefs.node_name);
  311. mesh::Utils::printHex(Serial, self_id.pub_key, PUB_KEY_SIZE);
  312. Serial.println();
  313. Serial.println(" (enter 'help' for basic commands)");
  314. Serial.println();
  315. }
  316. void sendSelfAdvert(int delay_millis) {
  317. auto pkt = createSelfAdvert(_prefs.node_name, _prefs.node_lat, _prefs.node_lon);
  318. if (pkt) {
  319. sendFlood(pkt, delay_millis);
  320. }
  321. }
  322. // ContactVisitor
  323. void onContactVisit(const ContactInfo& contact) override {
  324. Serial.printf(" %s - ", contact.name);
  325. char tmp[40];
  326. int32_t secs = contact.last_advert_timestamp - getRTCClock()->getCurrentTime();
  327. AdvertTimeHelper::formatRelativeTimeDiff(tmp, secs, false);
  328. Serial.println(tmp);
  329. }
  330. void handleCommand(const char* command) {
  331. while (*command == ' ') command++; // skip leading spaces
  332. if (memcmp(command, "send ", 5) == 0) {
  333. if (curr_recipient) {
  334. const char *text = &command[5];
  335. uint32_t est_timeout;
  336. int result = sendMessage(*curr_recipient, getRTCClock()->getCurrentTime(), 0, text, expected_ack_crc, est_timeout);
  337. if (result == MSG_SEND_FAILED) {
  338. Serial.println(" ERROR: unable to send.");
  339. } else {
  340. last_msg_sent = _ms->getMillis();
  341. Serial.printf(" (message sent - %s)\n", result == MSG_SEND_SENT_FLOOD ? "FLOOD" : "DIRECT");
  342. }
  343. } else {
  344. Serial.println(" ERROR: no recipient selected (use 'to' cmd).");
  345. }
  346. } else if (memcmp(command, "public ", 7) == 0) { // send GroupChannel msg
  347. uint8_t temp[5+MAX_TEXT_LEN+32];
  348. uint32_t timestamp = getRTCClock()->getCurrentTime();
  349. memcpy(temp, &timestamp, 4); // mostly an extra blob to help make packet_hash unique
  350. temp[4] = 0; // attempt and flags
  351. sprintf((char *) &temp[5], "%s: %s", _prefs.node_name, &command[7]); // <sender>: <msg>
  352. temp[5 + MAX_TEXT_LEN] = 0; // truncate if too long
  353. int len = strlen((char *) &temp[5]);
  354. auto pkt = createGroupDatagram(PAYLOAD_TYPE_GRP_TXT, _public->channel, temp, 5 + len);
  355. if (pkt) {
  356. sendFlood(pkt);
  357. Serial.println(" Sent.");
  358. } else {
  359. Serial.println(" ERROR: unable to send");
  360. }
  361. } else if (memcmp(command, "list", 4) == 0) { // show Contact list, by most recent
  362. int n = 0;
  363. if (command[4] == ' ') { // optional param, last 'N'
  364. n = atoi(&command[5]);
  365. }
  366. scanRecentContacts(n, this);
  367. } else if (strcmp(command, "clock") == 0) { // show current time
  368. uint32_t now = getRTCClock()->getCurrentTime();
  369. DateTime dt = DateTime(now);
  370. Serial.printf( "%02d:%02d - %d/%d/%d UTC\n", dt.hour(), dt.minute(), dt.day(), dt.month(), dt.year());
  371. } else if (memcmp(command, "time ", 5) == 0) { // set time (to epoch seconds)
  372. uint32_t secs = _atoi(&command[5]);
  373. setClock(secs);
  374. } else if (memcmp(command, "to ", 3) == 0) { // set current recipient
  375. curr_recipient = searchContactsByPrefix(&command[3]);
  376. if (curr_recipient) {
  377. Serial.printf(" Recipient %s now selected.\n", curr_recipient->name);
  378. } else {
  379. Serial.println(" Error: Name prefix not found.");
  380. }
  381. } else if (strcmp(command, "to") == 0) { // show current recipient
  382. if (curr_recipient) {
  383. Serial.printf(" Current: %s\n", curr_recipient->name);
  384. } else {
  385. Serial.println(" Err: no recipient selected");
  386. }
  387. } else if (strcmp(command, "advert") == 0) {
  388. auto pkt = createSelfAdvert(_prefs.node_name, _prefs.node_lat, _prefs.node_lon);
  389. if (pkt) {
  390. sendZeroHop(pkt);
  391. Serial.println(" (advert sent, zero hop).");
  392. } else {
  393. Serial.println(" ERR: unable to send");
  394. }
  395. } else if (strcmp(command, "reset path") == 0) {
  396. if (curr_recipient) {
  397. resetPathTo(*curr_recipient);
  398. saveContacts();
  399. Serial.println(" Done.");
  400. }
  401. } else if (memcmp(command, "card", 4) == 0) {
  402. Serial.printf("Hello %s\n", _prefs.node_name);
  403. auto pkt = createSelfAdvert(_prefs.node_name, _prefs.node_lat, _prefs.node_lon);
  404. if (pkt) {
  405. uint8_t len = pkt->writeTo(tmp_buf);
  406. releasePacket(pkt); // undo the obtainNewPacket()
  407. mesh::Utils::toHex(hex_buf, tmp_buf, len);
  408. Serial.println("Your MeshCore biz card:");
  409. Serial.print("meshcore://"); Serial.println(hex_buf);
  410. Serial.println();
  411. } else {
  412. Serial.println(" Error");
  413. }
  414. } else if (memcmp(command, "import ", 7) == 0) {
  415. importCard(&command[7]);
  416. } else if (memcmp(command, "set ", 4) == 0) {
  417. const char* config = &command[4];
  418. if (memcmp(config, "af ", 3) == 0) {
  419. _prefs.airtime_factor = atof(&config[3]);
  420. savePrefs();
  421. Serial.println(" OK");
  422. } else if (memcmp(config, "name ", 5) == 0) {
  423. StrHelper::strncpy(_prefs.node_name, &config[5], sizeof(_prefs.node_name));
  424. savePrefs();
  425. Serial.println(" OK");
  426. } else if (memcmp(config, "lat ", 4) == 0) {
  427. _prefs.node_lat = atof(&config[4]);
  428. savePrefs();
  429. Serial.println(" OK");
  430. } else if (memcmp(config, "lon ", 4) == 0) {
  431. _prefs.node_lon = atof(&config[4]);
  432. savePrefs();
  433. Serial.println(" OK");
  434. } else if (memcmp(config, "tx ", 3) == 0) {
  435. _prefs.tx_power_dbm = atoi(&config[3]);
  436. savePrefs();
  437. Serial.println(" OK - reboot to apply");
  438. } else if (memcmp(config, "freq ", 5) == 0) {
  439. _prefs.freq = atof(&config[5]);
  440. savePrefs();
  441. Serial.println(" OK - reboot to apply");
  442. } else {
  443. Serial.printf(" ERROR: unknown config: %s\n", config);
  444. }
  445. } else if (memcmp(command, "ver", 3) == 0) {
  446. Serial.println(FIRMWARE_VER_TEXT);
  447. } else if (memcmp(command, "help", 4) == 0) {
  448. Serial.println("Commands:");
  449. Serial.println(" set {name|lat|lon|freq|tx|af} {value}");
  450. Serial.println(" card");
  451. Serial.println(" import {biz card}");
  452. Serial.println(" clock");
  453. Serial.println(" time <epoch-seconds>");
  454. Serial.println(" list {n}");
  455. Serial.println(" to <recipient name or prefix>");
  456. Serial.println(" to");
  457. Serial.println(" send <text>");
  458. Serial.println(" advert");
  459. Serial.println(" reset path");
  460. Serial.println(" public <text>");
  461. } else {
  462. Serial.print(" ERROR: unknown command: "); Serial.println(command);
  463. }
  464. }
  465. void loop() {
  466. BaseChatMesh::loop();
  467. int len = strlen(command);
  468. while (Serial.available() && len < sizeof(command)-1) {
  469. char c = Serial.read();
  470. if (c != '\n') {
  471. command[len++] = c;
  472. command[len] = 0;
  473. }
  474. Serial.print(c);
  475. }
  476. if (len == sizeof(command)-1) { // command buffer full
  477. command[sizeof(command)-1] = '\r';
  478. }
  479. if (len > 0 && command[len - 1] == '\r') { // received complete line
  480. command[len - 1] = 0; // replace newline with C string null terminator
  481. handleCommand(command);
  482. command[0] = 0; // reset command buffer
  483. }
  484. }
  485. };
  486. StdRNG fast_rng;
  487. SimpleMeshTables tables;
  488. MyMesh the_mesh(radio_driver, fast_rng, rtc_clock, tables);
  489. void halt() {
  490. while (1) ;
  491. }
  492. void setup() {
  493. Serial.begin(115200);
  494. board.begin();
  495. if (!radio_init()) { halt(); }
  496. fast_rng.begin(radio_driver.getRngSeed());
  497. #if defined(NRF52_PLATFORM)
  498. InternalFS.begin();
  499. the_mesh.begin(InternalFS);
  500. #elif defined(RP2040_PLATFORM)
  501. LittleFS.begin();
  502. the_mesh.begin(LittleFS);
  503. #elif defined(ESP32)
  504. SPIFFS.begin(true);
  505. the_mesh.begin(SPIFFS);
  506. #else
  507. #error "need to define filesystem"
  508. #endif
  509. radio_driver.setParams(the_mesh.getFreqPref(), LORA_BW, LORA_SF, LORA_CR);
  510. radio_driver.setTxPower(the_mesh.getTxPowerPref());
  511. the_mesh.showWelcome();
  512. // send out initial Advertisement to the mesh
  513. #if ENABLE_ADVERT_ON_BOOT == 1
  514. the_mesh.sendSelfAdvert(1200); // add slight delay
  515. #endif
  516. }
  517. void loop() {
  518. the_mesh.loop();
  519. rtc_clock.tick();
  520. }