main.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418
  1. #include <Arduino.h> // needed for PlatformIO
  2. #include <Mesh.h>
  3. #if defined(NRF52_PLATFORM)
  4. #include <InternalFileSystem.h>
  5. #elif defined(ESP32)
  6. #include <SPIFFS.h>
  7. #endif
  8. #define RADIOLIB_STATIC_ONLY 1
  9. #include <RadioLib.h>
  10. #include <helpers/RadioLibWrappers.h>
  11. #include <helpers/ArduinoHelpers.h>
  12. #include <helpers/StaticPoolPacketManager.h>
  13. #include <helpers/SimpleMeshTables.h>
  14. #include <helpers/IdentityStore.h>
  15. #include <RTClib.h>
  16. /* ---------------------------------- CONFIGURATION ------------------------------------- */
  17. #ifndef LORA_FREQ
  18. #define LORA_FREQ 915.0
  19. #endif
  20. #ifndef LORA_BW
  21. #define LORA_BW 250
  22. #endif
  23. #ifndef LORA_SF
  24. #define LORA_SF 10
  25. #endif
  26. #ifndef LORA_CR
  27. #define LORA_CR 5
  28. #endif
  29. #ifndef LORA_TX_POWER
  30. #define LORA_TX_POWER 20
  31. #endif
  32. #ifndef MAX_CONTACTS
  33. #define MAX_CONTACTS 100
  34. #endif
  35. #include <helpers/BaseChatMesh.h>
  36. #define SEND_TIMEOUT_BASE_MILLIS 300
  37. #define FLOOD_SEND_TIMEOUT_FACTOR 16.0f
  38. #define DIRECT_SEND_PERHOP_FACTOR 4.0f
  39. #define DIRECT_SEND_PERHOP_EXTRA_MILLIS 200
  40. #if defined(HELTEC_LORA_V3)
  41. #include <helpers/HeltecV3Board.h>
  42. #include <helpers/CustomSX1262Wrapper.h>
  43. static HeltecV3Board board;
  44. #elif defined(ARDUINO_XIAO_ESP32C3)
  45. #include <helpers/XiaoC3Board.h>
  46. #include <helpers/CustomSX1262Wrapper.h>
  47. #include <helpers/CustomSX1268Wrapper.h>
  48. static XiaoC3Board board;
  49. #elif defined(SEEED_XIAO_S3)
  50. #include <helpers/ESP32Board.h>
  51. #include <helpers/CustomSX1262Wrapper.h>
  52. static ESP32Board board;
  53. #elif defined(RAK_4631)
  54. #include <helpers/RAK4631Board.h>
  55. #include <helpers/CustomSX1262Wrapper.h>
  56. static RAK4631Board board;
  57. #else
  58. #error "need to provide a 'board' object"
  59. #endif
  60. /* -------------------------------------------------------------------------------------- */
  61. static int curr_contact_idx = 0;
  62. class MyMesh : public BaseChatMesh, ContactVisitor {
  63. FILESYSTEM* _fs;
  64. uint32_t expected_ack_crc;
  65. unsigned long last_msg_sent;
  66. ContactInfo* curr_recipient;
  67. char command[MAX_TEXT_LEN+1];
  68. const char* getTypeName(uint8_t type) const {
  69. if (type == ADV_TYPE_CHAT) return "Chat";
  70. if (type == ADV_TYPE_REPEATER) return "Repeater";
  71. if (type == ADV_TYPE_ROOM) return "Room";
  72. return "??"; // unknown
  73. }
  74. void loadContacts() {
  75. if (_fs->exists("/contacts")) {
  76. File file = _fs->open("/contacts");
  77. if (file) {
  78. bool full = false;
  79. while (!full) {
  80. ContactInfo c;
  81. uint8_t pub_key[32];
  82. uint8_t unused;
  83. uint32_t reserved;
  84. bool success = (file.read(pub_key, 32) == 32);
  85. success = success && (file.read((uint8_t *) &c.name, 32) == 32);
  86. success = success && (file.read(&c.type, 1) == 1);
  87. success = success && (file.read(&c.flags, 1) == 1);
  88. success = success && (file.read(&unused, 1) == 1);
  89. success = success && (file.read((uint8_t *) &reserved, 4) == 4);
  90. success = success && (file.read((uint8_t *) &c.out_path_len, 1) == 1);
  91. success = success && (file.read((uint8_t *) &c.last_advert_timestamp, 4) == 4);
  92. success = success && (file.read(c.out_path, 64) == 64);
  93. if (!success) break; // EOF
  94. c.id = mesh::Identity(pub_key);
  95. if (!addContact(c)) full = true;
  96. }
  97. file.close();
  98. }
  99. }
  100. }
  101. void saveContacts() {
  102. #if defined(NRF52_PLATFORM)
  103. File file = _fs->open("/contacts", FILE_O_WRITE);
  104. if (file) { file.seek(0); file.truncate(); }
  105. #else
  106. File file = _fs->open("/contacts", "w", true);
  107. #endif
  108. if (file) {
  109. ContactsIterator iter;
  110. ContactInfo c;
  111. uint8_t unused = 0;
  112. uint32_t reserved = 0;
  113. while (iter.hasNext(this, c)) {
  114. bool success = (file.write(c.id.pub_key, 32) == 32);
  115. success = success && (file.write((uint8_t *) &c.name, 32) == 32);
  116. success = success && (file.write(&c.type, 1) == 1);
  117. success = success && (file.write(&c.flags, 1) == 1);
  118. success = success && (file.write(&unused, 1) == 1);
  119. success = success && (file.write((uint8_t *) &reserved, 4) == 4);
  120. success = success && (file.write((uint8_t *) &c.out_path_len, 1) == 1);
  121. success = success && (file.write((uint8_t *) &c.last_advert_timestamp, 4) == 4);
  122. success = success && (file.write(c.out_path, 64) == 64);
  123. if (!success) break; // write failed
  124. }
  125. file.close();
  126. }
  127. }
  128. protected:
  129. void onDiscoveredContact(ContactInfo& contact, bool is_new) override {
  130. // TODO: if not in favs, prompt to add as fav(?)
  131. Serial.printf("ADVERT from -> %s\n", contact.name);
  132. Serial.printf(" type: %s\n", getTypeName(contact.type));
  133. Serial.print(" public key: "); mesh::Utils::printHex(Serial, contact.id.pub_key, PUB_KEY_SIZE); Serial.println();
  134. saveContacts();
  135. }
  136. void onContactPathUpdated(const ContactInfo& contact) override {
  137. Serial.printf("PATH to: %s, path_len=%d\n", contact.name, (int32_t) contact.out_path_len);
  138. saveContacts();
  139. }
  140. bool processAck(const uint8_t *data) override {
  141. if (memcmp(data, &expected_ack_crc, 4) == 0) { // got an ACK from recipient
  142. Serial.printf(" Got ACK! (round trip: %d millis)\n", _ms->getMillis() - last_msg_sent);
  143. // NOTE: the same ACK can be received multiple times!
  144. expected_ack_crc = 0; // reset our expected hash, now that we have received ACK
  145. return true;
  146. }
  147. //uint32_t crc;
  148. //memcpy(&crc, data, 4);
  149. //MESH_DEBUG_PRINTLN("unknown ACK received: %08X (expected: %08X)", crc, expected_ack_crc);
  150. return false;
  151. }
  152. void onMessageRecv(const ContactInfo& from, bool was_flood, uint32_t sender_timestamp, const char *text) override {
  153. Serial.printf("(%s) MSG -> from %s\n", was_flood ? "FLOOD" : "DIRECT", from.name);
  154. Serial.printf(" %s\n", text);
  155. if (strcmp(text, "clock sync") == 0) { // special text command
  156. uint32_t curr = getRTCClock()->getCurrentTime();
  157. if (sender_timestamp > curr) {
  158. getRTCClock()->setCurrentTime(sender_timestamp + 1);
  159. Serial.println(" (OK - clock set!)");
  160. } else {
  161. Serial.println(" (ERR: clock cannot go backwards)");
  162. }
  163. }
  164. }
  165. uint32_t calcFloodTimeoutMillisFor(uint32_t pkt_airtime_millis) const override {
  166. return SEND_TIMEOUT_BASE_MILLIS + (FLOOD_SEND_TIMEOUT_FACTOR * pkt_airtime_millis);
  167. }
  168. uint32_t calcDirectTimeoutMillisFor(uint32_t pkt_airtime_millis, uint8_t path_len) const override {
  169. return SEND_TIMEOUT_BASE_MILLIS +
  170. ( (pkt_airtime_millis*DIRECT_SEND_PERHOP_FACTOR + DIRECT_SEND_PERHOP_EXTRA_MILLIS) * (path_len + 1));
  171. }
  172. void onSendTimeout() override {
  173. Serial.println(" ERROR: timed out, no ACK.");
  174. }
  175. public:
  176. char self_name[sizeof(ContactInfo::name)];
  177. MyMesh(RadioLibWrapper& radio, mesh::RNG& rng, mesh::RTCClock& rtc, SimpleMeshTables& tables)
  178. : BaseChatMesh(radio, *new ArduinoMillis(), rng, rtc, *new StaticPoolPacketManager(16), tables)
  179. {
  180. command[0] = 0;
  181. curr_recipient = NULL;
  182. }
  183. void begin(FILESYSTEM& fs) {
  184. _fs = &fs;
  185. BaseChatMesh::begin();
  186. strcpy(self_name, "UNSET");
  187. IdentityStore store(fs, "/identity");
  188. if (!store.load("_main", self_id, self_name, sizeof(self_name))) {
  189. self_id = mesh::LocalIdentity(getRNG()); // create new random identity
  190. store.save("_main", self_id);
  191. }
  192. loadContacts();
  193. }
  194. void showWelcome() {
  195. Serial.println("===== MeshCore Chat Terminal =====");
  196. Serial.println();
  197. Serial.printf("WELCOME %s\n", self_name);
  198. Serial.println(" (enter 'help' for basic commands)");
  199. Serial.println();
  200. }
  201. // ContactVisitor
  202. void onContactVisit(const ContactInfo& contact) override {
  203. Serial.printf(" %s - ", contact.name);
  204. char tmp[40];
  205. int32_t secs = contact.last_advert_timestamp - getRTCClock()->getCurrentTime();
  206. AdvertTimeHelper::formatRelativeTimeDiff(tmp, secs, false);
  207. Serial.println(tmp);
  208. }
  209. void handleCommand(const char* command) {
  210. while (*command == ' ') command++; // skip leading spaces
  211. if (memcmp(command, "send ", 5) == 0) {
  212. if (curr_recipient) {
  213. const char *text = &command[5];
  214. int result = sendMessage(*curr_recipient, 0, text, expected_ack_crc);
  215. if (result == MSG_SEND_FAILED) {
  216. Serial.println(" ERROR: unable to send.");
  217. } else {
  218. last_msg_sent = _ms->getMillis();
  219. Serial.printf(" (message sent - %s)\n", result == MSG_SEND_SENT_FLOOD ? "FLOOD" : "DIRECT");
  220. }
  221. } else {
  222. Serial.println(" ERROR: no recipient selected (use 'to' cmd).");
  223. }
  224. } else if (memcmp(command, "list", 4) == 0) { // show Contact list, by most recent
  225. int n = 0;
  226. if (command[4] == ' ') { // optional param, last 'N'
  227. n = atoi(&command[5]);
  228. }
  229. scanRecentContacts(n, this);
  230. } else if (strcmp(command, "clock") == 0) { // show current time
  231. uint32_t now = getRTCClock()->getCurrentTime();
  232. DateTime dt = DateTime(now);
  233. Serial.printf( "%02d:%02d - %d/%d/%d UTC\n", dt.hour(), dt.minute(), dt.day(), dt.month(), dt.year());
  234. } else if (memcmp(command, "name ", 5) == 0) { // set name
  235. strncpy(self_name, &command[5], sizeof(self_name)-1);
  236. self_name[sizeof(self_name)-1] = 0;
  237. IdentityStore store(*_fs, "/identity"); // update IdentityStore
  238. store.save("_main", self_id, self_name);
  239. } else if (memcmp(command, "to ", 3) == 0) { // set current recipient
  240. curr_recipient = searchContactsByPrefix(&command[3]);
  241. if (curr_recipient) {
  242. Serial.printf(" Recipient %s now selected.\n", curr_recipient->name);
  243. } else {
  244. Serial.println(" Error: Name prefix not found.");
  245. }
  246. } else if (strcmp(command, "to") == 0) { // show current recipient
  247. if (curr_recipient) {
  248. Serial.printf(" Current: %s\n", curr_recipient->name);
  249. } else {
  250. Serial.println(" Err: no recipient selected");
  251. }
  252. } else if (strcmp(command, "advert") == 0) {
  253. auto pkt = createSelfAdvert(self_name);
  254. if (pkt) {
  255. sendZeroHop(pkt);
  256. Serial.println(" (advert sent, zero hop).");
  257. } else {
  258. Serial.println(" ERR: unable to send");
  259. }
  260. } else if (strcmp(command, "reset path") == 0) {
  261. if (curr_recipient) {
  262. resetPathTo(*curr_recipient);
  263. saveContacts();
  264. Serial.println(" Done.");
  265. }
  266. } else if (memcmp(command, "help", 4) == 0) {
  267. Serial.printf("Hello %s, Commands:\n", self_name);
  268. Serial.println(" name <your name>");
  269. Serial.println(" clock");
  270. Serial.println(" list {n}");
  271. Serial.println(" to <recipient name or prefix>");
  272. Serial.println(" to");
  273. Serial.println(" send <text>");
  274. Serial.println(" advert");
  275. Serial.println(" reset path");
  276. } else {
  277. Serial.print(" ERROR: unknown command: "); Serial.println(command);
  278. }
  279. }
  280. void loop() {
  281. BaseChatMesh::loop();
  282. int len = strlen(command);
  283. while (Serial.available() && len < sizeof(command)-1) {
  284. char c = Serial.read();
  285. if (c != '\n') {
  286. command[len++] = c;
  287. command[len] = 0;
  288. }
  289. Serial.print(c);
  290. }
  291. if (len == sizeof(command)-1) { // command buffer full
  292. command[sizeof(command)-1] = '\r';
  293. }
  294. if (len > 0 && command[len - 1] == '\r') { // received complete line
  295. command[len - 1] = 0; // replace newline with C string null terminator
  296. handleCommand(command);
  297. command[0] = 0; // reset command buffer
  298. }
  299. }
  300. };
  301. #if defined(NRF52_PLATFORM)
  302. RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI);
  303. #elif defined(P_LORA_SCLK)
  304. SPIClass spi;
  305. RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, spi);
  306. #else
  307. RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY);
  308. #endif
  309. StdRNG fast_rng;
  310. SimpleMeshTables tables;
  311. MyMesh the_mesh(*new WRAPPER_CLASS(radio, board), fast_rng, *new VolatileRTCClock(), tables);
  312. void halt() {
  313. while (1) ;
  314. }
  315. void setup() {
  316. Serial.begin(115200);
  317. board.begin();
  318. #ifdef SX126X_DIO3_TCXO_VOLTAGE
  319. float tcxo = SX126X_DIO3_TCXO_VOLTAGE;
  320. #else
  321. float tcxo = 1.6f;
  322. #endif
  323. #if defined(NRF52_PLATFORM)
  324. SPI.setPins(P_LORA_MISO, P_LORA_SCLK, P_LORA_MOSI);
  325. SPI.begin();
  326. #elif defined(P_LORA_SCLK)
  327. spi.begin(P_LORA_SCLK, P_LORA_MISO, P_LORA_MOSI);
  328. #endif
  329. int status = radio.begin(LORA_FREQ, LORA_BW, LORA_SF, LORA_CR, RADIOLIB_SX126X_SYNC_WORD_PRIVATE, LORA_TX_POWER, 8, tcxo);
  330. if (status != RADIOLIB_ERR_NONE) {
  331. Serial.print("ERROR: radio init failed: ");
  332. Serial.println(status);
  333. halt();
  334. }
  335. radio.setCRC(0);
  336. #ifdef SX126X_CURRENT_LIMIT
  337. radio.setCurrentLimit(SX126X_CURRENT_LIMIT);
  338. #endif
  339. #ifdef SX126X_DIO2_AS_RF_SWITCH
  340. radio.setDio2AsRfSwitch(SX126X_DIO2_AS_RF_SWITCH);
  341. #endif
  342. fast_rng.begin(radio.random(0x7FFFFFFF));
  343. #if defined(NRF52_PLATFORM)
  344. InternalFS.begin();
  345. the_mesh.begin(InternalFS);
  346. #elif defined(ESP32)
  347. SPIFFS.begin(true);
  348. the_mesh.begin(SPIFFS);
  349. #else
  350. #error "need to define filesystem"
  351. #endif
  352. the_mesh.showWelcome();
  353. // send out initial Advertisement to the mesh
  354. auto pkt = the_mesh.createSelfAdvert(the_mesh.self_name);
  355. if (pkt) {
  356. the_mesh.sendFlood(pkt, 1200); // add slight delay
  357. }
  358. }
  359. void loop() {
  360. the_mesh.loop();
  361. }