CommonCLI.cpp 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  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. #define MIN_LOCAL_ADVERT_INTERVAL 60
  15. void CommonCLI::checkAdvertInterval() {
  16. if (_prefs->advert_interval * 2 < MIN_LOCAL_ADVERT_INTERVAL) {
  17. _prefs->advert_interval = 0; // turn it off, now that device has been manually configured
  18. }
  19. }
  20. void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, char* reply) {
  21. while (*command == ' ') command++; // skip leading spaces
  22. if (strlen(command) > 4 && command[2] == '|') { // optional prefix (for companion radio CLI)
  23. memcpy(reply, command, 3); // reflect the prefix back
  24. reply += 3;
  25. command += 3;
  26. }
  27. if (memcmp(command, "reboot", 6) == 0) {
  28. _board->reboot(); // doesn't return
  29. } else if (memcmp(command, "advert", 6) == 0) {
  30. _callbacks->sendSelfAdvertisement(400);
  31. strcpy(reply, "OK - Advert sent");
  32. } else if (memcmp(command, "clock sync", 10) == 0) {
  33. uint32_t curr = getRTCClock()->getCurrentTime();
  34. if (sender_timestamp > curr) {
  35. getRTCClock()->setCurrentTime(sender_timestamp + 1);
  36. strcpy(reply, "OK - clock set");
  37. } else {
  38. strcpy(reply, "ERR: clock cannot go backwards");
  39. }
  40. } else if (memcmp(command, "start ota", 9) == 0) {
  41. if (_board->startOTAUpdate()) {
  42. strcpy(reply, "OK");
  43. } else {
  44. strcpy(reply, "Error");
  45. }
  46. } else if (memcmp(command, "clock", 5) == 0) {
  47. uint32_t now = getRTCClock()->getCurrentTime();
  48. DateTime dt = DateTime(now);
  49. sprintf(reply, "%02d:%02d - %d/%d/%d UTC", dt.hour(), dt.minute(), dt.day(), dt.month(), dt.year());
  50. } else if (memcmp(command, "time ", 5) == 0) { // set time (to epoch seconds)
  51. uint32_t secs = _atoi(&command[5]);
  52. uint32_t curr = getRTCClock()->getCurrentTime();
  53. if (secs > curr) {
  54. getRTCClock()->setCurrentTime(secs);
  55. strcpy(reply, "(OK - clock set!)");
  56. } else {
  57. strcpy(reply, "(ERR: clock cannot go backwards)");
  58. }
  59. } else if (memcmp(command, "password ", 9) == 0) {
  60. // change admin password
  61. StrHelper::strncpy(_prefs->password, &command[9], sizeof(_prefs->password));
  62. checkAdvertInterval();
  63. savePrefs();
  64. sprintf(reply, "password now: %s", _prefs->password); // echo back just to let admin know for sure!!
  65. } else if (memcmp(command, "get ", 4) == 0) {
  66. const char* config = &command[4];
  67. if (memcmp(config, "af", 2) == 0) {
  68. sprintf(reply, "> %s", StrHelper::ftoa(_prefs->airtime_factor));
  69. } else if (memcmp(config, "advert.interval", 15) == 0) {
  70. sprintf(reply, "> %d", ((uint32_t) _prefs->advert_interval) * 2);
  71. } else if (memcmp(config, "guest.password", 14) == 0) {
  72. sprintf(reply, "> %s", _prefs->guest_password);
  73. } else if (memcmp(config, "name", 4) == 0) {
  74. sprintf(reply, "> %s", _prefs->node_name);
  75. } else if (memcmp(config, "repeat", 6) == 0) {
  76. sprintf(reply, "> %s", _prefs->disable_fwd ? "off" : "on");
  77. } else if (memcmp(config, "lat", 3) == 0) {
  78. sprintf(reply, "> %s", StrHelper::ftoa(_prefs->node_lat));
  79. } else if (memcmp(config, "lon", 3) == 0) {
  80. sprintf(reply, "> %s", StrHelper::ftoa(_prefs->node_lon));
  81. } else if (memcmp(config, "radio", 5) == 0) {
  82. char freq[16], bw[16];
  83. strcpy(freq, StrHelper::ftoa(_prefs->freq));
  84. strcpy(bw, StrHelper::ftoa(_prefs->bw));
  85. sprintf(reply, "> %s,%s,%d,%d", freq, bw, (uint32_t)_prefs->sf, (uint32_t)_prefs->cr);
  86. } else if (memcmp(config, "rxdelay", 7) == 0) {
  87. sprintf(reply, "> %s", StrHelper::ftoa(_prefs->rx_delay_base));
  88. } else if (memcmp(config, "txdelay", 7) == 0) {
  89. sprintf(reply, "> %s", StrHelper::ftoa(_prefs->tx_delay_factor));
  90. } else if (memcmp(config, "direct.txdelay", 14) == 0) {
  91. sprintf(reply, "> %s", StrHelper::ftoa(_prefs->direct_tx_delay_factor));
  92. } else if (memcmp(config, "tx", 2) == 0 && (config[2] == 0 || config[2] == ' ')) {
  93. sprintf(reply, "> %d", (uint32_t) _prefs->tx_power_dbm);
  94. } else if (memcmp(config, "freq", 4) == 0) {
  95. sprintf(reply, "> %s", StrHelper::ftoa(_prefs->freq));
  96. } else {
  97. sprintf(reply, "??: %s", config);
  98. }
  99. } else if (memcmp(command, "set ", 4) == 0) {
  100. const char* config = &command[4];
  101. if (memcmp(config, "af ", 3) == 0) {
  102. _prefs->airtime_factor = atof(&config[3]);
  103. savePrefs();
  104. strcpy(reply, "OK");
  105. } else if (memcmp(config, "advert.interval ", 16) == 0) {
  106. int mins = _atoi(&config[16]);
  107. if (mins > 0 && mins < MIN_LOCAL_ADVERT_INTERVAL) {
  108. sprintf(reply, "Error: min is %d mins", MIN_LOCAL_ADVERT_INTERVAL);
  109. } else if (mins > 240) {
  110. strcpy(reply, "Error: max is 240 mins");
  111. } else {
  112. _prefs->advert_interval = (uint8_t)(mins / 2);
  113. _callbacks->updateAdvertTimer();
  114. savePrefs();
  115. strcpy(reply, "OK");
  116. }
  117. } else if (memcmp(config, "guest.password ", 15) == 0) {
  118. StrHelper::strncpy(_prefs->guest_password, &config[15], sizeof(_prefs->guest_password));
  119. savePrefs();
  120. strcpy(reply, "OK");
  121. } else if (memcmp(config, "name ", 5) == 0) {
  122. StrHelper::strncpy(_prefs->node_name, &config[5], sizeof(_prefs->node_name));
  123. checkAdvertInterval();
  124. savePrefs();
  125. strcpy(reply, "OK");
  126. } else if (memcmp(config, "repeat ", 7) == 0) {
  127. _prefs->disable_fwd = memcmp(&config[7], "off", 3) == 0;
  128. savePrefs();
  129. strcpy(reply, _prefs->disable_fwd ? "OK - repeat is now OFF" : "OK - repeat is now ON");
  130. } else if (memcmp(config, "radio ", 6) == 0) {
  131. strcpy(tmp, &config[6]);
  132. const char *parts[4];
  133. int num = mesh::Utils::parseTextParts(tmp, parts, 4);
  134. float freq = num > 0 ? atof(parts[0]) : 0.0f;
  135. float bw = num > 1 ? atof(parts[1]) : 0.0f;
  136. uint8_t sf = num > 2 ? atoi(parts[2]) : 0;
  137. uint8_t cr = num > 3 ? atoi(parts[3]) : 0;
  138. if (freq >= 300.0f && freq <= 2500.0f && sf >= 7 && sf <= 12 && cr >= 5 && cr <= 8 && bw >= 7.0f && bw <= 500.0f) {
  139. _prefs->sf = sf;
  140. _prefs->cr = cr;
  141. _prefs->freq = freq;
  142. _prefs->bw = bw;
  143. _callbacks->savePrefs();
  144. strcpy(reply, "OK - reboot to apply");
  145. } else {
  146. strcpy(reply, "Error, invalid radio params");
  147. }
  148. } else if (memcmp(config, "lat ", 4) == 0) {
  149. _prefs->node_lat = atof(&config[4]);
  150. checkAdvertInterval();
  151. savePrefs();
  152. strcpy(reply, "OK");
  153. } else if (memcmp(config, "lon ", 4) == 0) {
  154. _prefs->node_lon = atof(&config[4]);
  155. checkAdvertInterval();
  156. savePrefs();
  157. strcpy(reply, "OK");
  158. } else if (memcmp(config, "rxdelay ", 8) == 0) {
  159. float db = atof(&config[8]);
  160. if (db >= 0) {
  161. _prefs->rx_delay_base = db;
  162. savePrefs();
  163. strcpy(reply, "OK");
  164. } else {
  165. strcpy(reply, "Error, cannot be negative");
  166. }
  167. } else if (memcmp(config, "txdelay ", 8) == 0) {
  168. float f = atof(&config[8]);
  169. if (f >= 0) {
  170. _prefs->tx_delay_factor = f;
  171. savePrefs();
  172. strcpy(reply, "OK");
  173. } else {
  174. strcpy(reply, "Error, cannot be negative");
  175. }
  176. } else if (memcmp(config, "direct.txdelay ", 15) == 0) {
  177. float f = atof(&config[15]);
  178. if (f >= 0) {
  179. _prefs->direct_tx_delay_factor = f;
  180. savePrefs();
  181. strcpy(reply, "OK");
  182. } else {
  183. strcpy(reply, "Error, cannot be negative");
  184. }
  185. } else if (memcmp(config, "tx ", 3) == 0) {
  186. _prefs->tx_power_dbm = atoi(&config[3]);
  187. savePrefs();
  188. _callbacks->setTxPower(_prefs->tx_power_dbm);
  189. strcpy(reply, "OK");
  190. } else if (sender_timestamp == 0 && memcmp(config, "freq ", 5) == 0) {
  191. _prefs->freq = atof(&config[5]);
  192. savePrefs();
  193. strcpy(reply, "OK - reboot to apply");
  194. } else {
  195. sprintf(reply, "unknown config: %s", config);
  196. }
  197. } else if (sender_timestamp == 0 && strcmp(command, "erase") == 0) {
  198. bool s = _callbacks->formatFileSystem();
  199. sprintf(reply, "File system erase: %s", s ? "OK" : "Err");
  200. } else if (memcmp(command, "ver", 3) == 0) {
  201. sprintf(reply, "%s (Build: %s)", _callbacks->getFirmwareVer(), _callbacks->getBuildDate());
  202. } else if (memcmp(command, "log start", 9) == 0) {
  203. _callbacks->setLoggingOn(true);
  204. strcpy(reply, " logging on");
  205. } else if (memcmp(command, "log stop", 8) == 0) {
  206. _callbacks->setLoggingOn(false);
  207. strcpy(reply, " logging off");
  208. } else if (memcmp(command, "log erase", 9) == 0) {
  209. _callbacks->eraseLogFile();
  210. strcpy(reply, " log erased");
  211. } else if (sender_timestamp == 0 && memcmp(command, "log", 3) == 0) {
  212. _callbacks->dumpLogFile();
  213. strcpy(reply, " EOF");
  214. } else {
  215. sprintf(reply, "Unknown: %s", command);
  216. }
  217. }