CommonCLI.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368
  1. #include <Arduino.h>
  2. #include "CommonCLI.h"
  3. #include "TxtDataHelpers.h"
  4. #include <RTClib.h>
  5. // Believe it or not, this std C function is busted on some platforms!
  6. static uint32_t _atoi(const char* sp) {
  7. uint32_t n = 0;
  8. while (*sp && *sp >= '0' && *sp <= '9') {
  9. n *= 10;
  10. n += (*sp++ - '0');
  11. }
  12. return n;
  13. }
  14. void CommonCLI::loadPrefs(FILESYSTEM* fs) {
  15. if (fs->exists("/com_prefs")) {
  16. loadPrefsInt(fs, "/com_prefs"); // new filename
  17. } else if (fs->exists("/node_prefs")) {
  18. loadPrefsInt(fs, "/node_prefs");
  19. savePrefs(fs); // save to new filename
  20. fs->remove("/node_prefs"); // remove old
  21. }
  22. }
  23. void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
  24. #if defined(RP2040_PLATFORM)
  25. File file = fs->open(filename, "r");
  26. #else
  27. File file = fs->open(filename);
  28. #endif
  29. if (file) {
  30. uint8_t pad[8];
  31. file.read((uint8_t *) &_prefs->airtime_factor, sizeof(_prefs->airtime_factor)); // 0
  32. file.read((uint8_t *) &_prefs->node_name, sizeof(_prefs->node_name)); // 4
  33. file.read(pad, 4); // 36
  34. file.read((uint8_t *) &_prefs->node_lat, sizeof(_prefs->node_lat)); // 40
  35. file.read((uint8_t *) &_prefs->node_lon, sizeof(_prefs->node_lon)); // 48
  36. file.read((uint8_t *) &_prefs->password[0], sizeof(_prefs->password)); // 56
  37. file.read((uint8_t *) &_prefs->freq, sizeof(_prefs->freq)); // 72
  38. file.read((uint8_t *) &_prefs->tx_power_dbm, sizeof(_prefs->tx_power_dbm)); // 76
  39. file.read((uint8_t *) &_prefs->disable_fwd, sizeof(_prefs->disable_fwd)); // 77
  40. file.read((uint8_t *) &_prefs->advert_interval, sizeof(_prefs->advert_interval)); // 78
  41. file.read((uint8_t *) pad, 1); // 79 was 'unused'
  42. file.read((uint8_t *) &_prefs->rx_delay_base, sizeof(_prefs->rx_delay_base)); // 80
  43. file.read((uint8_t *) &_prefs->tx_delay_factor, sizeof(_prefs->tx_delay_factor)); // 84
  44. file.read((uint8_t *) &_prefs->guest_password[0], sizeof(_prefs->guest_password)); // 88
  45. file.read((uint8_t *) &_prefs->direct_tx_delay_factor, sizeof(_prefs->direct_tx_delay_factor)); // 104
  46. file.read(pad, 4); // 108
  47. file.read((uint8_t *) &_prefs->sf, sizeof(_prefs->sf)); // 112
  48. file.read((uint8_t *) &_prefs->cr, sizeof(_prefs->cr)); // 113
  49. file.read((uint8_t *) &_prefs->allow_read_only, sizeof(_prefs->allow_read_only)); // 114
  50. file.read((uint8_t *) &_prefs->reserved2, sizeof(_prefs->reserved2)); // 115
  51. file.read((uint8_t *) &_prefs->bw, sizeof(_prefs->bw)); // 116
  52. file.read((uint8_t *) &_prefs->agc_reset_interval, sizeof(_prefs->agc_reset_interval)); // 120
  53. file.read(pad, 3); // 121
  54. file.read((uint8_t *) &_prefs->flood_max, sizeof(_prefs->flood_max)); // 124
  55. file.read((uint8_t *) &_prefs->flood_advert_interval, sizeof(_prefs->flood_advert_interval)); // 125
  56. file.read((uint8_t *) &_prefs->interference_threshold, sizeof(_prefs->interference_threshold)); // 126
  57. // sanitise bad pref values
  58. _prefs->rx_delay_base = constrain(_prefs->rx_delay_base, 0, 20.0f);
  59. _prefs->tx_delay_factor = constrain(_prefs->tx_delay_factor, 0, 2.0f);
  60. _prefs->direct_tx_delay_factor = constrain(_prefs->direct_tx_delay_factor, 0, 2.0f);
  61. _prefs->airtime_factor = constrain(_prefs->airtime_factor, 0, 9.0f);
  62. _prefs->freq = constrain(_prefs->freq, 400.0f, 2500.0f);
  63. _prefs->bw = constrain(_prefs->bw, 62.5f, 500.0f);
  64. _prefs->sf = constrain(_prefs->sf, 7, 12);
  65. _prefs->cr = constrain(_prefs->cr, 5, 8);
  66. _prefs->tx_power_dbm = constrain(_prefs->tx_power_dbm, 1, 30);
  67. file.close();
  68. }
  69. }
  70. void CommonCLI::savePrefs(FILESYSTEM* fs) {
  71. #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
  72. fs->remove("/com_prefs");
  73. File file = fs->open("/com_prefs", FILE_O_WRITE);
  74. #elif defined(RP2040_PLATFORM)
  75. File file = fs->open("/com_prefs", "w");
  76. #else
  77. File file = fs->open("/com_prefs", "w", true);
  78. #endif
  79. if (file) {
  80. uint8_t pad[8];
  81. memset(pad, 0, sizeof(pad));
  82. file.write((uint8_t *) &_prefs->airtime_factor, sizeof(_prefs->airtime_factor)); // 0
  83. file.write((uint8_t *) &_prefs->node_name, sizeof(_prefs->node_name)); // 4
  84. file.write(pad, 4); // 36
  85. file.write((uint8_t *) &_prefs->node_lat, sizeof(_prefs->node_lat)); // 40
  86. file.write((uint8_t *) &_prefs->node_lon, sizeof(_prefs->node_lon)); // 48
  87. file.write((uint8_t *) &_prefs->password[0], sizeof(_prefs->password)); // 56
  88. file.write((uint8_t *) &_prefs->freq, sizeof(_prefs->freq)); // 72
  89. file.write((uint8_t *) &_prefs->tx_power_dbm, sizeof(_prefs->tx_power_dbm)); // 76
  90. file.write((uint8_t *) &_prefs->disable_fwd, sizeof(_prefs->disable_fwd)); // 77
  91. file.write((uint8_t *) &_prefs->advert_interval, sizeof(_prefs->advert_interval)); // 78
  92. file.write((uint8_t *) pad, 1); // 79 was 'unused'
  93. file.write((uint8_t *) &_prefs->rx_delay_base, sizeof(_prefs->rx_delay_base)); // 80
  94. file.write((uint8_t *) &_prefs->tx_delay_factor, sizeof(_prefs->tx_delay_factor)); // 84
  95. file.write((uint8_t *) &_prefs->guest_password[0], sizeof(_prefs->guest_password)); // 88
  96. file.write((uint8_t *) &_prefs->direct_tx_delay_factor, sizeof(_prefs->direct_tx_delay_factor)); // 104
  97. file.write(pad, 4); // 108
  98. file.write((uint8_t *) &_prefs->sf, sizeof(_prefs->sf)); // 112
  99. file.write((uint8_t *) &_prefs->cr, sizeof(_prefs->cr)); // 113
  100. file.write((uint8_t *) &_prefs->allow_read_only, sizeof(_prefs->allow_read_only)); // 114
  101. file.write((uint8_t *) &_prefs->reserved2, sizeof(_prefs->reserved2)); // 115
  102. file.write((uint8_t *) &_prefs->bw, sizeof(_prefs->bw)); // 116
  103. file.write((uint8_t *) &_prefs->agc_reset_interval, sizeof(_prefs->agc_reset_interval)); // 120
  104. file.write(pad, 3); // 121
  105. file.write((uint8_t *) &_prefs->flood_max, sizeof(_prefs->flood_max)); // 124
  106. file.write((uint8_t *) &_prefs->flood_advert_interval, sizeof(_prefs->flood_advert_interval)); // 125
  107. file.write((uint8_t *) &_prefs->interference_threshold, sizeof(_prefs->interference_threshold)); // 126
  108. file.close();
  109. }
  110. }
  111. #define MIN_LOCAL_ADVERT_INTERVAL 60
  112. void CommonCLI::savePrefs() {
  113. if (_prefs->advert_interval * 2 < MIN_LOCAL_ADVERT_INTERVAL) {
  114. _prefs->advert_interval = 0; // turn it off, now that device has been manually configured
  115. }
  116. _callbacks->savePrefs();
  117. }
  118. void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, char* reply) {
  119. if (memcmp(command, "reboot", 6) == 0) {
  120. _board->reboot(); // doesn't return
  121. } else if (memcmp(command, "advert", 6) == 0) {
  122. _callbacks->sendSelfAdvertisement(400);
  123. strcpy(reply, "OK - Advert sent");
  124. } else if (memcmp(command, "clock sync", 10) == 0) {
  125. uint32_t curr = getRTCClock()->getCurrentTime();
  126. if (sender_timestamp > curr) {
  127. getRTCClock()->setCurrentTime(sender_timestamp + 1);
  128. uint32_t now = getRTCClock()->getCurrentTime();
  129. DateTime dt = DateTime(now);
  130. sprintf(reply, "OK - clock set: %02d:%02d - %d/%d/%d UTC", dt.hour(), dt.minute(), dt.day(), dt.month(), dt.year());
  131. } else {
  132. strcpy(reply, "ERR: clock cannot go backwards");
  133. }
  134. } else if (memcmp(command, "start ota", 9) == 0) {
  135. if (!_board->startOTAUpdate(_prefs->node_name, reply)) {
  136. strcpy(reply, "Error");
  137. }
  138. } else if (memcmp(command, "clock", 5) == 0) {
  139. uint32_t now = getRTCClock()->getCurrentTime();
  140. DateTime dt = DateTime(now);
  141. sprintf(reply, "%02d:%02d - %d/%d/%d UTC", dt.hour(), dt.minute(), dt.day(), dt.month(), dt.year());
  142. } else if (memcmp(command, "time ", 5) == 0) { // set time (to epoch seconds)
  143. uint32_t secs = _atoi(&command[5]);
  144. uint32_t curr = getRTCClock()->getCurrentTime();
  145. if (secs > curr) {
  146. getRTCClock()->setCurrentTime(secs);
  147. uint32_t now = getRTCClock()->getCurrentTime();
  148. DateTime dt = DateTime(now);
  149. sprintf(reply, "OK - clock set: %02d:%02d - %d/%d/%d UTC", dt.hour(), dt.minute(), dt.day(), dt.month(), dt.year());
  150. } else {
  151. strcpy(reply, "(ERR: clock cannot go backwards)");
  152. }
  153. } else if (memcmp(command, "neighbors", 9) == 0) {
  154. _callbacks->formatNeighborsReply(reply);
  155. } else if (memcmp(command, "password ", 9) == 0) {
  156. // change admin password
  157. StrHelper::strncpy(_prefs->password, &command[9], sizeof(_prefs->password));
  158. savePrefs();
  159. sprintf(reply, "password now: %s", _prefs->password); // echo back just to let admin know for sure!!
  160. } else if (memcmp(command, "clear stats", 11) == 0) {
  161. _callbacks->clearStats();
  162. strcpy(reply, "(OK - stats reset)");
  163. } else if (memcmp(command, "get ", 4) == 0) {
  164. const char* config = &command[4];
  165. if (memcmp(config, "af", 2) == 0) {
  166. sprintf(reply, "> %s", StrHelper::ftoa(_prefs->airtime_factor));
  167. } else if (memcmp(config, "int.thresh", 10) == 0) {
  168. sprintf(reply, "> %d", (uint32_t) _prefs->interference_threshold);
  169. } else if (memcmp(config, "agc.reset.interval", 18) == 0) {
  170. sprintf(reply, "> %d", ((uint32_t) _prefs->agc_reset_interval) * 4);
  171. } else if (memcmp(config, "allow.read.only", 15) == 0) {
  172. sprintf(reply, "> %s", _prefs->allow_read_only ? "on" : "off");
  173. } else if (memcmp(config, "flood.advert.interval", 21) == 0) {
  174. sprintf(reply, "> %d", ((uint32_t) _prefs->flood_advert_interval));
  175. } else if (memcmp(config, "advert.interval", 15) == 0) {
  176. sprintf(reply, "> %d", ((uint32_t) _prefs->advert_interval) * 2);
  177. } else if (memcmp(config, "guest.password", 14) == 0) {
  178. sprintf(reply, "> %s", _prefs->guest_password);
  179. } else if (memcmp(config, "name", 4) == 0) {
  180. sprintf(reply, "> %s", _prefs->node_name);
  181. } else if (memcmp(config, "repeat", 6) == 0) {
  182. sprintf(reply, "> %s", _prefs->disable_fwd ? "off" : "on");
  183. } else if (memcmp(config, "lat", 3) == 0) {
  184. sprintf(reply, "> %s", StrHelper::ftoa(_prefs->node_lat));
  185. } else if (memcmp(config, "lon", 3) == 0) {
  186. sprintf(reply, "> %s", StrHelper::ftoa(_prefs->node_lon));
  187. } else if (memcmp(config, "radio", 5) == 0) {
  188. char freq[16], bw[16];
  189. strcpy(freq, StrHelper::ftoa(_prefs->freq));
  190. strcpy(bw, StrHelper::ftoa(_prefs->bw));
  191. sprintf(reply, "> %s,%s,%d,%d", freq, bw, (uint32_t)_prefs->sf, (uint32_t)_prefs->cr);
  192. } else if (memcmp(config, "rxdelay", 7) == 0) {
  193. sprintf(reply, "> %s", StrHelper::ftoa(_prefs->rx_delay_base));
  194. } else if (memcmp(config, "txdelay", 7) == 0) {
  195. sprintf(reply, "> %s", StrHelper::ftoa(_prefs->tx_delay_factor));
  196. } else if (memcmp(config, "flood.max", 9) == 0) {
  197. sprintf(reply, "> %d", (uint32_t)_prefs->flood_max);
  198. } else if (memcmp(config, "direct.txdelay", 14) == 0) {
  199. sprintf(reply, "> %s", StrHelper::ftoa(_prefs->direct_tx_delay_factor));
  200. } else if (memcmp(config, "tx", 2) == 0 && (config[2] == 0 || config[2] == ' ')) {
  201. sprintf(reply, "> %d", (uint32_t) _prefs->tx_power_dbm);
  202. } else if (memcmp(config, "freq", 4) == 0) {
  203. sprintf(reply, "> %s", StrHelper::ftoa(_prefs->freq));
  204. } else if (memcmp(config, "public.key", 10) == 0) {
  205. strcpy(reply, "> ");
  206. mesh::Utils::toHex(&reply[2], _callbacks->getSelfIdPubKey(), PUB_KEY_SIZE);
  207. } else if (memcmp(config, "role", 4) == 0) {
  208. sprintf(reply, "> %s", _callbacks->getRole());
  209. } else {
  210. sprintf(reply, "??: %s", config);
  211. }
  212. } else if (memcmp(command, "set ", 4) == 0) {
  213. const char* config = &command[4];
  214. if (memcmp(config, "af ", 3) == 0) {
  215. _prefs->airtime_factor = atof(&config[3]);
  216. savePrefs();
  217. strcpy(reply, "OK");
  218. } else if (memcmp(config, "int.thresh ", 11) == 0) {
  219. _prefs->interference_threshold = atoi(&config[11]);
  220. savePrefs();
  221. strcpy(reply, "OK");
  222. } else if (memcmp(config, "agc.reset.interval ", 19) == 0) {
  223. _prefs->agc_reset_interval = atoi(&config[19]) / 4;
  224. savePrefs();
  225. strcpy(reply, "OK");
  226. } else if (memcmp(config, "allow.read.only ", 16) == 0) {
  227. _prefs->allow_read_only = memcmp(&config[16], "on", 2) == 0;
  228. savePrefs();
  229. strcpy(reply, "OK");
  230. } else if (memcmp(config, "flood.advert.interval ", 22) == 0) {
  231. int hours = _atoi(&config[22]);
  232. if ((hours > 0 && hours < 3) || (hours > 48)) {
  233. strcpy(reply, "Error: interval range is 3-48 hours");
  234. } else {
  235. _prefs->flood_advert_interval = (uint8_t)(hours);
  236. _callbacks->updateFloodAdvertTimer();
  237. savePrefs();
  238. strcpy(reply, "OK");
  239. }
  240. } else if (memcmp(config, "advert.interval ", 16) == 0) {
  241. int mins = _atoi(&config[16]);
  242. if ((mins > 0 && mins < MIN_LOCAL_ADVERT_INTERVAL) || (mins > 240)) {
  243. sprintf(reply, "Error: interval range is %d-240 minutes", MIN_LOCAL_ADVERT_INTERVAL);
  244. } else {
  245. _prefs->advert_interval = (uint8_t)(mins / 2);
  246. _callbacks->updateAdvertTimer();
  247. savePrefs();
  248. strcpy(reply, "OK");
  249. }
  250. } else if (memcmp(config, "guest.password ", 15) == 0) {
  251. StrHelper::strncpy(_prefs->guest_password, &config[15], sizeof(_prefs->guest_password));
  252. savePrefs();
  253. strcpy(reply, "OK");
  254. } else if (memcmp(config, "name ", 5) == 0) {
  255. StrHelper::strncpy(_prefs->node_name, &config[5], sizeof(_prefs->node_name));
  256. savePrefs();
  257. strcpy(reply, "OK");
  258. } else if (memcmp(config, "repeat ", 7) == 0) {
  259. _prefs->disable_fwd = memcmp(&config[7], "off", 3) == 0;
  260. savePrefs();
  261. strcpy(reply, _prefs->disable_fwd ? "OK - repeat is now OFF" : "OK - repeat is now ON");
  262. } else if (memcmp(config, "radio ", 6) == 0) {
  263. strcpy(tmp, &config[6]);
  264. const char *parts[4];
  265. int num = mesh::Utils::parseTextParts(tmp, parts, 4);
  266. float freq = num > 0 ? atof(parts[0]) : 0.0f;
  267. float bw = num > 1 ? atof(parts[1]) : 0.0f;
  268. uint8_t sf = num > 2 ? atoi(parts[2]) : 0;
  269. uint8_t cr = num > 3 ? atoi(parts[3]) : 0;
  270. if (freq >= 300.0f && freq <= 2500.0f && sf >= 7 && sf <= 12 && cr >= 5 && cr <= 8 && bw >= 7.0f && bw <= 500.0f) {
  271. _prefs->sf = sf;
  272. _prefs->cr = cr;
  273. _prefs->freq = freq;
  274. _prefs->bw = bw;
  275. _callbacks->savePrefs();
  276. strcpy(reply, "OK - reboot to apply");
  277. } else {
  278. strcpy(reply, "Error, invalid radio params");
  279. }
  280. } else if (memcmp(config, "lat ", 4) == 0) {
  281. _prefs->node_lat = atof(&config[4]);
  282. savePrefs();
  283. strcpy(reply, "OK");
  284. } else if (memcmp(config, "lon ", 4) == 0) {
  285. _prefs->node_lon = atof(&config[4]);
  286. savePrefs();
  287. strcpy(reply, "OK");
  288. } else if (memcmp(config, "rxdelay ", 8) == 0) {
  289. float db = atof(&config[8]);
  290. if (db >= 0) {
  291. _prefs->rx_delay_base = db;
  292. savePrefs();
  293. strcpy(reply, "OK");
  294. } else {
  295. strcpy(reply, "Error, cannot be negative");
  296. }
  297. } else if (memcmp(config, "txdelay ", 8) == 0) {
  298. float f = atof(&config[8]);
  299. if (f >= 0) {
  300. _prefs->tx_delay_factor = f;
  301. savePrefs();
  302. strcpy(reply, "OK");
  303. } else {
  304. strcpy(reply, "Error, cannot be negative");
  305. }
  306. } else if (memcmp(config, "flood.max ", 10) == 0) {
  307. uint8_t m = atoi(&config[10]);
  308. if (m <= 64) {
  309. _prefs->flood_max = m;
  310. savePrefs();
  311. strcpy(reply, "OK");
  312. } else {
  313. strcpy(reply, "Error, max 64");
  314. }
  315. } else if (memcmp(config, "direct.txdelay ", 15) == 0) {
  316. float f = atof(&config[15]);
  317. if (f >= 0) {
  318. _prefs->direct_tx_delay_factor = f;
  319. savePrefs();
  320. strcpy(reply, "OK");
  321. } else {
  322. strcpy(reply, "Error, cannot be negative");
  323. }
  324. } else if (memcmp(config, "tx ", 3) == 0) {
  325. _prefs->tx_power_dbm = atoi(&config[3]);
  326. savePrefs();
  327. _callbacks->setTxPower(_prefs->tx_power_dbm);
  328. strcpy(reply, "OK");
  329. } else if (sender_timestamp == 0 && memcmp(config, "freq ", 5) == 0) {
  330. _prefs->freq = atof(&config[5]);
  331. savePrefs();
  332. strcpy(reply, "OK - reboot to apply");
  333. } else {
  334. sprintf(reply, "unknown config: %s", config);
  335. }
  336. } else if (sender_timestamp == 0 && strcmp(command, "erase") == 0) {
  337. bool s = _callbacks->formatFileSystem();
  338. sprintf(reply, "File system erase: %s", s ? "OK" : "Err");
  339. } else if (memcmp(command, "ver", 3) == 0) {
  340. sprintf(reply, "%s (Build: %s)", _callbacks->getFirmwareVer(), _callbacks->getBuildDate());
  341. } else if (memcmp(command, "log start", 9) == 0) {
  342. _callbacks->setLoggingOn(true);
  343. strcpy(reply, " logging on");
  344. } else if (memcmp(command, "log stop", 8) == 0) {
  345. _callbacks->setLoggingOn(false);
  346. strcpy(reply, " logging off");
  347. } else if (memcmp(command, "log erase", 9) == 0) {
  348. _callbacks->eraseLogFile();
  349. strcpy(reply, " log erased");
  350. } else if (sender_timestamp == 0 && memcmp(command, "log", 3) == 0) {
  351. _callbacks->dumpLogFile();
  352. strcpy(reply, " EOF");
  353. } else {
  354. strcpy(reply, "Unknown command");
  355. }
  356. }