main.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593
  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. uint8_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, (int32_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. return SEND_TIMEOUT_BASE_MILLIS +
  231. ( (pkt_airtime_millis*DIRECT_SEND_PERHOP_FACTOR + DIRECT_SEND_PERHOP_EXTRA_MILLIS) * (path_len + 1));
  232. }
  233. void onSendTimeout() override {
  234. Serial.println(" ERROR: timed out, no ACK.");
  235. }
  236. public:
  237. MyMesh(mesh::Radio& radio, StdRNG& rng, mesh::RTCClock& rtc, SimpleMeshTables& tables)
  238. : BaseChatMesh(radio, *new ArduinoMillis(), rng, rtc, *new StaticPoolPacketManager(16), tables)
  239. {
  240. // defaults
  241. memset(&_prefs, 0, sizeof(_prefs));
  242. _prefs.airtime_factor = 2.0; // one third
  243. strcpy(_prefs.node_name, "NONAME");
  244. _prefs.freq = LORA_FREQ;
  245. _prefs.tx_power_dbm = LORA_TX_POWER;
  246. command[0] = 0;
  247. curr_recipient = NULL;
  248. }
  249. float getFreqPref() const { return _prefs.freq; }
  250. uint8_t getTxPowerPref() const { return _prefs.tx_power_dbm; }
  251. void begin(FILESYSTEM& fs) {
  252. _fs = &fs;
  253. BaseChatMesh::begin();
  254. #if defined(NRF52_PLATFORM)
  255. IdentityStore store(fs, "");
  256. #elif defined(RP2040_PLATFORM)
  257. IdentityStore store(fs, "/identity");
  258. store.begin();
  259. #else
  260. IdentityStore store(fs, "/identity");
  261. #endif
  262. if (!store.load("_main", self_id, _prefs.node_name, sizeof(_prefs.node_name))) { // legacy: node_name was from identity file
  263. // Need way to get some entropy to seed RNG
  264. Serial.println("Press ENTER to generate key:");
  265. char c = 0;
  266. while (c != '\n') { // wait for ENTER to be pressed
  267. if (Serial.available()) c = Serial.read();
  268. }
  269. ((StdRNG *)getRNG())->begin(millis());
  270. self_id = mesh::LocalIdentity(getRNG()); // create new random identity
  271. int count = 0;
  272. while (count < 10 && (self_id.pub_key[0] == 0x00 || self_id.pub_key[0] == 0xFF)) { // reserved id hashes
  273. self_id = mesh::LocalIdentity(getRNG()); count++;
  274. }
  275. store.save("_main", self_id);
  276. }
  277. // load persisted prefs
  278. if (_fs->exists("/node_prefs")) {
  279. #if defined(RP2040_PLATFORM)
  280. File file = _fs->open("/node_prefs", "r");
  281. #else
  282. File file = _fs->open("/node_prefs");
  283. #endif
  284. if (file) {
  285. file.read((uint8_t *) &_prefs, sizeof(_prefs));
  286. file.close();
  287. }
  288. }
  289. loadContacts();
  290. _public = addChannel("Public", PUBLIC_GROUP_PSK); // pre-configure Andy's public channel
  291. }
  292. void savePrefs() {
  293. #if defined(NRF52_PLATFORM)
  294. _fs->remove("/node_prefs");
  295. File file = _fs->open("/node_prefs", FILE_O_WRITE);
  296. #elif defined(RP2040_PLATFORM)
  297. File file = _fs->open("/node_prefs", "w");
  298. #else
  299. File file = _fs->open("/node_prefs", "w", true);
  300. #endif
  301. if (file) {
  302. file.write((const uint8_t *)&_prefs, sizeof(_prefs));
  303. file.close();
  304. }
  305. }
  306. void showWelcome() {
  307. Serial.println("===== MeshCore Chat Terminal =====");
  308. Serial.println();
  309. Serial.printf("WELCOME %s\n", _prefs.node_name);
  310. mesh::Utils::printHex(Serial, self_id.pub_key, PUB_KEY_SIZE);
  311. Serial.println();
  312. Serial.println(" (enter 'help' for basic commands)");
  313. Serial.println();
  314. }
  315. void sendSelfAdvert(int delay_millis) {
  316. auto pkt = createSelfAdvert(_prefs.node_name, _prefs.node_lat, _prefs.node_lon);
  317. if (pkt) {
  318. sendFlood(pkt, delay_millis);
  319. }
  320. }
  321. // ContactVisitor
  322. void onContactVisit(const ContactInfo& contact) override {
  323. Serial.printf(" %s - ", contact.name);
  324. char tmp[40];
  325. int32_t secs = contact.last_advert_timestamp - getRTCClock()->getCurrentTime();
  326. AdvertTimeHelper::formatRelativeTimeDiff(tmp, secs, false);
  327. Serial.println(tmp);
  328. }
  329. void handleCommand(const char* command) {
  330. while (*command == ' ') command++; // skip leading spaces
  331. if (memcmp(command, "send ", 5) == 0) {
  332. if (curr_recipient) {
  333. const char *text = &command[5];
  334. uint32_t est_timeout;
  335. int result = sendMessage(*curr_recipient, getRTCClock()->getCurrentTime(), 0, text, expected_ack_crc, est_timeout);
  336. if (result == MSG_SEND_FAILED) {
  337. Serial.println(" ERROR: unable to send.");
  338. } else {
  339. last_msg_sent = _ms->getMillis();
  340. Serial.printf(" (message sent - %s)\n", result == MSG_SEND_SENT_FLOOD ? "FLOOD" : "DIRECT");
  341. }
  342. } else {
  343. Serial.println(" ERROR: no recipient selected (use 'to' cmd).");
  344. }
  345. } else if (memcmp(command, "public ", 7) == 0) { // send GroupChannel msg
  346. uint8_t temp[5+MAX_TEXT_LEN+32];
  347. uint32_t timestamp = getRTCClock()->getCurrentTime();
  348. memcpy(temp, &timestamp, 4); // mostly an extra blob to help make packet_hash unique
  349. temp[4] = 0; // attempt and flags
  350. sprintf((char *) &temp[5], "%s: %s", _prefs.node_name, &command[7]); // <sender>: <msg>
  351. temp[5 + MAX_TEXT_LEN] = 0; // truncate if too long
  352. int len = strlen((char *) &temp[5]);
  353. auto pkt = createGroupDatagram(PAYLOAD_TYPE_GRP_TXT, _public->channel, temp, 5 + len);
  354. if (pkt) {
  355. sendFlood(pkt);
  356. Serial.println(" Sent.");
  357. } else {
  358. Serial.println(" ERROR: unable to send");
  359. }
  360. } else if (memcmp(command, "list", 4) == 0) { // show Contact list, by most recent
  361. int n = 0;
  362. if (command[4] == ' ') { // optional param, last 'N'
  363. n = atoi(&command[5]);
  364. }
  365. scanRecentContacts(n, this);
  366. } else if (strcmp(command, "clock") == 0) { // show current time
  367. uint32_t now = getRTCClock()->getCurrentTime();
  368. DateTime dt = DateTime(now);
  369. Serial.printf( "%02d:%02d - %d/%d/%d UTC\n", dt.hour(), dt.minute(), dt.day(), dt.month(), dt.year());
  370. } else if (memcmp(command, "time ", 5) == 0) { // set time (to epoch seconds)
  371. uint32_t secs = _atoi(&command[5]);
  372. setClock(secs);
  373. } else if (memcmp(command, "to ", 3) == 0) { // set current recipient
  374. curr_recipient = searchContactsByPrefix(&command[3]);
  375. if (curr_recipient) {
  376. Serial.printf(" Recipient %s now selected.\n", curr_recipient->name);
  377. } else {
  378. Serial.println(" Error: Name prefix not found.");
  379. }
  380. } else if (strcmp(command, "to") == 0) { // show current recipient
  381. if (curr_recipient) {
  382. Serial.printf(" Current: %s\n", curr_recipient->name);
  383. } else {
  384. Serial.println(" Err: no recipient selected");
  385. }
  386. } else if (strcmp(command, "advert") == 0) {
  387. auto pkt = createSelfAdvert(_prefs.node_name, _prefs.node_lat, _prefs.node_lon);
  388. if (pkt) {
  389. sendZeroHop(pkt);
  390. Serial.println(" (advert sent, zero hop).");
  391. } else {
  392. Serial.println(" ERR: unable to send");
  393. }
  394. } else if (strcmp(command, "reset path") == 0) {
  395. if (curr_recipient) {
  396. resetPathTo(*curr_recipient);
  397. saveContacts();
  398. Serial.println(" Done.");
  399. }
  400. } else if (memcmp(command, "card", 4) == 0) {
  401. Serial.printf("Hello %s\n", _prefs.node_name);
  402. auto pkt = createSelfAdvert(_prefs.node_name, _prefs.node_lat, _prefs.node_lon);
  403. if (pkt) {
  404. uint8_t len = pkt->writeTo(tmp_buf);
  405. releasePacket(pkt); // undo the obtainNewPacket()
  406. mesh::Utils::toHex(hex_buf, tmp_buf, len);
  407. Serial.println("Your MeshCore biz card:");
  408. Serial.print("meshcore://"); Serial.println(hex_buf);
  409. Serial.println();
  410. } else {
  411. Serial.println(" Error");
  412. }
  413. } else if (memcmp(command, "import ", 7) == 0) {
  414. importCard(&command[7]);
  415. } else if (memcmp(command, "set ", 4) == 0) {
  416. const char* config = &command[4];
  417. if (memcmp(config, "af ", 3) == 0) {
  418. _prefs.airtime_factor = atof(&config[3]);
  419. savePrefs();
  420. Serial.println(" OK");
  421. } else if (memcmp(config, "name ", 5) == 0) {
  422. StrHelper::strncpy(_prefs.node_name, &config[5], sizeof(_prefs.node_name));
  423. savePrefs();
  424. Serial.println(" OK");
  425. } else if (memcmp(config, "lat ", 4) == 0) {
  426. _prefs.node_lat = atof(&config[4]);
  427. savePrefs();
  428. Serial.println(" OK");
  429. } else if (memcmp(config, "lon ", 4) == 0) {
  430. _prefs.node_lon = atof(&config[4]);
  431. savePrefs();
  432. Serial.println(" OK");
  433. } else if (memcmp(config, "tx ", 3) == 0) {
  434. _prefs.tx_power_dbm = atoi(&config[3]);
  435. savePrefs();
  436. Serial.println(" OK - reboot to apply");
  437. } else if (memcmp(config, "freq ", 5) == 0) {
  438. _prefs.freq = atof(&config[5]);
  439. savePrefs();
  440. Serial.println(" OK - reboot to apply");
  441. } else {
  442. Serial.printf(" ERROR: unknown config: %s\n", config);
  443. }
  444. } else if (memcmp(command, "ver", 3) == 0) {
  445. Serial.println(FIRMWARE_VER_TEXT);
  446. } else if (memcmp(command, "help", 4) == 0) {
  447. Serial.println("Commands:");
  448. Serial.println(" set {name|lat|lon|freq|tx|af} {value}");
  449. Serial.println(" card");
  450. Serial.println(" import {biz card}");
  451. Serial.println(" clock");
  452. Serial.println(" time <epoch-seconds>");
  453. Serial.println(" list {n}");
  454. Serial.println(" to <recipient name or prefix>");
  455. Serial.println(" to");
  456. Serial.println(" send <text>");
  457. Serial.println(" advert");
  458. Serial.println(" reset path");
  459. Serial.println(" public <text>");
  460. } else {
  461. Serial.print(" ERROR: unknown command: "); Serial.println(command);
  462. }
  463. }
  464. void loop() {
  465. BaseChatMesh::loop();
  466. int len = strlen(command);
  467. while (Serial.available() && len < sizeof(command)-1) {
  468. char c = Serial.read();
  469. if (c != '\n') {
  470. command[len++] = c;
  471. command[len] = 0;
  472. }
  473. Serial.print(c);
  474. }
  475. if (len == sizeof(command)-1) { // command buffer full
  476. command[sizeof(command)-1] = '\r';
  477. }
  478. if (len > 0 && command[len - 1] == '\r') { // received complete line
  479. command[len - 1] = 0; // replace newline with C string null terminator
  480. handleCommand(command);
  481. command[0] = 0; // reset command buffer
  482. }
  483. }
  484. };
  485. StdRNG fast_rng;
  486. SimpleMeshTables tables;
  487. MyMesh the_mesh(radio_driver, fast_rng, rtc_clock, tables);
  488. void halt() {
  489. while (1) ;
  490. }
  491. void setup() {
  492. Serial.begin(115200);
  493. board.begin();
  494. if (!radio_init()) { halt(); }
  495. fast_rng.begin(radio_get_rng_seed());
  496. #if defined(NRF52_PLATFORM)
  497. InternalFS.begin();
  498. the_mesh.begin(InternalFS);
  499. #elif defined(RP2040_PLATFORM)
  500. LittleFS.begin();
  501. the_mesh.begin(LittleFS);
  502. #elif defined(ESP32)
  503. SPIFFS.begin(true);
  504. the_mesh.begin(SPIFFS);
  505. #else
  506. #error "need to define filesystem"
  507. #endif
  508. radio_set_params(the_mesh.getFreqPref(), LORA_BW, LORA_SF, LORA_CR);
  509. radio_set_tx_power(the_mesh.getTxPowerPref());
  510. the_mesh.showWelcome();
  511. // send out initial Advertisement to the mesh
  512. #if ENABLE_ADVERT_ON_BOOT == 1
  513. the_mesh.sendSelfAdvert(1200); // add slight delay
  514. #endif
  515. }
  516. void loop() {
  517. the_mesh.loop();
  518. rtc_clock.tick();
  519. }