CommonCLI.cpp 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813
  1. #include <Arduino.h>
  2. #include "CommonCLI.h"
  3. #include "TxtDataHelpers.h"
  4. #include "AdvertDataHelpers.h"
  5. #include <RTClib.h>
  6. // Believe it or not, this std C function is busted on some platforms!
  7. static uint32_t _atoi(const char* sp) {
  8. uint32_t n = 0;
  9. while (*sp && *sp >= '0' && *sp <= '9') {
  10. n *= 10;
  11. n += (*sp++ - '0');
  12. }
  13. return n;
  14. }
  15. static bool isValidName(const char *n) {
  16. while (*n) {
  17. if (*n == '[' || *n == ']' || *n == '\\' || *n == ':' || *n == ',' || *n == '?' || *n == '*') return false;
  18. n++;
  19. }
  20. return true;
  21. }
  22. void CommonCLI::loadPrefs(FILESYSTEM* fs) {
  23. if (fs->exists("/com_prefs")) {
  24. loadPrefsInt(fs, "/com_prefs"); // new filename
  25. } else if (fs->exists("/node_prefs")) {
  26. loadPrefsInt(fs, "/node_prefs");
  27. savePrefs(fs); // save to new filename
  28. fs->remove("/node_prefs"); // remove old
  29. }
  30. }
  31. void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
  32. #if defined(RP2040_PLATFORM)
  33. File file = fs->open(filename, "r");
  34. #else
  35. File file = fs->open(filename);
  36. #endif
  37. if (file) {
  38. uint8_t pad[8];
  39. file.read((uint8_t *)&_prefs->airtime_factor, sizeof(_prefs->airtime_factor)); // 0
  40. file.read((uint8_t *)&_prefs->node_name, sizeof(_prefs->node_name)); // 4
  41. file.read(pad, 4); // 36
  42. file.read((uint8_t *)&_prefs->node_lat, sizeof(_prefs->node_lat)); // 40
  43. file.read((uint8_t *)&_prefs->node_lon, sizeof(_prefs->node_lon)); // 48
  44. file.read((uint8_t *)&_prefs->password[0], sizeof(_prefs->password)); // 56
  45. file.read((uint8_t *)&_prefs->freq, sizeof(_prefs->freq)); // 72
  46. file.read((uint8_t *)&_prefs->tx_power_dbm, sizeof(_prefs->tx_power_dbm)); // 76
  47. file.read((uint8_t *)&_prefs->disable_fwd, sizeof(_prefs->disable_fwd)); // 77
  48. file.read((uint8_t *)&_prefs->advert_interval, sizeof(_prefs->advert_interval)); // 78
  49. file.read((uint8_t *)pad, 1); // 79 was 'unused'
  50. file.read((uint8_t *)&_prefs->rx_delay_base, sizeof(_prefs->rx_delay_base)); // 80
  51. file.read((uint8_t *)&_prefs->tx_delay_factor, sizeof(_prefs->tx_delay_factor)); // 84
  52. file.read((uint8_t *)&_prefs->guest_password[0], sizeof(_prefs->guest_password)); // 88
  53. file.read((uint8_t *)&_prefs->direct_tx_delay_factor, sizeof(_prefs->direct_tx_delay_factor)); // 104
  54. file.read(pad, 4); // 108
  55. file.read((uint8_t *)&_prefs->sf, sizeof(_prefs->sf)); // 112
  56. file.read((uint8_t *)&_prefs->cr, sizeof(_prefs->cr)); // 113
  57. file.read((uint8_t *)&_prefs->allow_read_only, sizeof(_prefs->allow_read_only)); // 114
  58. file.read((uint8_t *)&_prefs->multi_acks, sizeof(_prefs->multi_acks)); // 115
  59. file.read((uint8_t *)&_prefs->bw, sizeof(_prefs->bw)); // 116
  60. file.read((uint8_t *)&_prefs->agc_reset_interval, sizeof(_prefs->agc_reset_interval)); // 120
  61. file.read((uint8_t *)&_prefs->path_hash_mode, sizeof(_prefs->path_hash_mode)); // 121
  62. file.read(pad, 2); // 122
  63. file.read((uint8_t *)&_prefs->flood_max, sizeof(_prefs->flood_max)); // 124
  64. file.read((uint8_t *)&_prefs->flood_advert_interval, sizeof(_prefs->flood_advert_interval)); // 125
  65. file.read((uint8_t *)&_prefs->interference_threshold, sizeof(_prefs->interference_threshold)); // 126
  66. file.read((uint8_t *)&_prefs->bridge_enabled, sizeof(_prefs->bridge_enabled)); // 127
  67. file.read((uint8_t *)&_prefs->bridge_delay, sizeof(_prefs->bridge_delay)); // 128
  68. file.read((uint8_t *)&_prefs->bridge_pkt_src, sizeof(_prefs->bridge_pkt_src)); // 130
  69. file.read((uint8_t *)&_prefs->bridge_baud, sizeof(_prefs->bridge_baud)); // 131
  70. file.read((uint8_t *)&_prefs->bridge_channel, sizeof(_prefs->bridge_channel)); // 135
  71. file.read((uint8_t *)&_prefs->bridge_secret, sizeof(_prefs->bridge_secret)); // 136
  72. file.read((uint8_t *)&_prefs->powersaving_enabled, sizeof(_prefs->powersaving_enabled)); // 152
  73. file.read(pad, 3); // 153
  74. file.read((uint8_t *)&_prefs->gps_enabled, sizeof(_prefs->gps_enabled)); // 156
  75. file.read((uint8_t *)&_prefs->gps_interval, sizeof(_prefs->gps_interval)); // 157
  76. file.read((uint8_t *)&_prefs->advert_loc_policy, sizeof (_prefs->advert_loc_policy)); // 161
  77. file.read((uint8_t *)&_prefs->discovery_mod_timestamp, sizeof(_prefs->discovery_mod_timestamp)); // 162
  78. file.read((uint8_t *)&_prefs->adc_multiplier, sizeof(_prefs->adc_multiplier)); // 166
  79. file.read((uint8_t *)_prefs->owner_info, sizeof(_prefs->owner_info)); // 170
  80. // 290
  81. // sanitise bad pref values
  82. _prefs->rx_delay_base = constrain(_prefs->rx_delay_base, 0, 20.0f);
  83. _prefs->tx_delay_factor = constrain(_prefs->tx_delay_factor, 0, 2.0f);
  84. _prefs->direct_tx_delay_factor = constrain(_prefs->direct_tx_delay_factor, 0, 2.0f);
  85. _prefs->airtime_factor = constrain(_prefs->airtime_factor, 0, 9.0f);
  86. _prefs->freq = constrain(_prefs->freq, 400.0f, 2500.0f);
  87. _prefs->bw = constrain(_prefs->bw, 7.8f, 500.0f);
  88. _prefs->sf = constrain(_prefs->sf, 5, 12);
  89. _prefs->cr = constrain(_prefs->cr, 5, 8);
  90. _prefs->tx_power_dbm = constrain(_prefs->tx_power_dbm, -9, 30);
  91. _prefs->multi_acks = constrain(_prefs->multi_acks, 0, 1);
  92. _prefs->adc_multiplier = constrain(_prefs->adc_multiplier, 0.0f, 10.0f);
  93. _prefs->path_hash_mode = constrain(_prefs->path_hash_mode, 0, 2); // NOTE: mode 3 reserved for future
  94. // sanitise bad bridge pref values
  95. _prefs->bridge_enabled = constrain(_prefs->bridge_enabled, 0, 1);
  96. _prefs->bridge_delay = constrain(_prefs->bridge_delay, 0, 10000);
  97. _prefs->bridge_pkt_src = constrain(_prefs->bridge_pkt_src, 0, 1);
  98. _prefs->bridge_baud = constrain(_prefs->bridge_baud, 9600, 115200);
  99. _prefs->bridge_channel = constrain(_prefs->bridge_channel, 0, 14);
  100. _prefs->powersaving_enabled = constrain(_prefs->powersaving_enabled, 0, 1);
  101. _prefs->gps_enabled = constrain(_prefs->gps_enabled, 0, 1);
  102. _prefs->advert_loc_policy = constrain(_prefs->advert_loc_policy, 0, 2);
  103. file.close();
  104. }
  105. }
  106. void CommonCLI::savePrefs(FILESYSTEM* fs) {
  107. #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
  108. fs->remove("/com_prefs");
  109. File file = fs->open("/com_prefs", FILE_O_WRITE);
  110. #elif defined(RP2040_PLATFORM)
  111. File file = fs->open("/com_prefs", "w");
  112. #else
  113. File file = fs->open("/com_prefs", "w", true);
  114. #endif
  115. if (file) {
  116. uint8_t pad[8];
  117. memset(pad, 0, sizeof(pad));
  118. file.write((uint8_t *)&_prefs->airtime_factor, sizeof(_prefs->airtime_factor)); // 0
  119. file.write((uint8_t *)&_prefs->node_name, sizeof(_prefs->node_name)); // 4
  120. file.write(pad, 4); // 36
  121. file.write((uint8_t *)&_prefs->node_lat, sizeof(_prefs->node_lat)); // 40
  122. file.write((uint8_t *)&_prefs->node_lon, sizeof(_prefs->node_lon)); // 48
  123. file.write((uint8_t *)&_prefs->password[0], sizeof(_prefs->password)); // 56
  124. file.write((uint8_t *)&_prefs->freq, sizeof(_prefs->freq)); // 72
  125. file.write((uint8_t *)&_prefs->tx_power_dbm, sizeof(_prefs->tx_power_dbm)); // 76
  126. file.write((uint8_t *)&_prefs->disable_fwd, sizeof(_prefs->disable_fwd)); // 77
  127. file.write((uint8_t *)&_prefs->advert_interval, sizeof(_prefs->advert_interval)); // 78
  128. file.write((uint8_t *)pad, 1); // 79 was 'unused'
  129. file.write((uint8_t *)&_prefs->rx_delay_base, sizeof(_prefs->rx_delay_base)); // 80
  130. file.write((uint8_t *)&_prefs->tx_delay_factor, sizeof(_prefs->tx_delay_factor)); // 84
  131. file.write((uint8_t *)&_prefs->guest_password[0], sizeof(_prefs->guest_password)); // 88
  132. file.write((uint8_t *)&_prefs->direct_tx_delay_factor, sizeof(_prefs->direct_tx_delay_factor)); // 104
  133. file.write(pad, 4); // 108
  134. file.write((uint8_t *)&_prefs->sf, sizeof(_prefs->sf)); // 112
  135. file.write((uint8_t *)&_prefs->cr, sizeof(_prefs->cr)); // 113
  136. file.write((uint8_t *)&_prefs->allow_read_only, sizeof(_prefs->allow_read_only)); // 114
  137. file.write((uint8_t *)&_prefs->multi_acks, sizeof(_prefs->multi_acks)); // 115
  138. file.write((uint8_t *)&_prefs->bw, sizeof(_prefs->bw)); // 116
  139. file.write((uint8_t *)&_prefs->agc_reset_interval, sizeof(_prefs->agc_reset_interval)); // 120
  140. file.write((uint8_t *)&_prefs->path_hash_mode, sizeof(_prefs->path_hash_mode)); // 121
  141. file.write(pad, 2); // 122
  142. file.write((uint8_t *)&_prefs->flood_max, sizeof(_prefs->flood_max)); // 124
  143. file.write((uint8_t *)&_prefs->flood_advert_interval, sizeof(_prefs->flood_advert_interval)); // 125
  144. file.write((uint8_t *)&_prefs->interference_threshold, sizeof(_prefs->interference_threshold)); // 126
  145. file.write((uint8_t *)&_prefs->bridge_enabled, sizeof(_prefs->bridge_enabled)); // 127
  146. file.write((uint8_t *)&_prefs->bridge_delay, sizeof(_prefs->bridge_delay)); // 128
  147. file.write((uint8_t *)&_prefs->bridge_pkt_src, sizeof(_prefs->bridge_pkt_src)); // 130
  148. file.write((uint8_t *)&_prefs->bridge_baud, sizeof(_prefs->bridge_baud)); // 131
  149. file.write((uint8_t *)&_prefs->bridge_channel, sizeof(_prefs->bridge_channel)); // 135
  150. file.write((uint8_t *)&_prefs->bridge_secret, sizeof(_prefs->bridge_secret)); // 136
  151. file.write((uint8_t *)&_prefs->powersaving_enabled, sizeof(_prefs->powersaving_enabled)); // 152
  152. file.write(pad, 3); // 153
  153. file.write((uint8_t *)&_prefs->gps_enabled, sizeof(_prefs->gps_enabled)); // 156
  154. file.write((uint8_t *)&_prefs->gps_interval, sizeof(_prefs->gps_interval)); // 157
  155. file.write((uint8_t *)&_prefs->advert_loc_policy, sizeof(_prefs->advert_loc_policy)); // 161
  156. file.write((uint8_t *)&_prefs->discovery_mod_timestamp, sizeof(_prefs->discovery_mod_timestamp)); // 162
  157. file.write((uint8_t *)&_prefs->adc_multiplier, sizeof(_prefs->adc_multiplier)); // 166
  158. file.write((uint8_t *)_prefs->owner_info, sizeof(_prefs->owner_info)); // 170
  159. // 290
  160. file.close();
  161. }
  162. }
  163. #define MIN_LOCAL_ADVERT_INTERVAL 60
  164. void CommonCLI::savePrefs() {
  165. if (_prefs->advert_interval * 2 < MIN_LOCAL_ADVERT_INTERVAL) {
  166. _prefs->advert_interval = 0; // turn it off, now that device has been manually configured
  167. }
  168. _callbacks->savePrefs();
  169. }
  170. uint8_t CommonCLI::buildAdvertData(uint8_t node_type, uint8_t* app_data) {
  171. if (_prefs->advert_loc_policy == ADVERT_LOC_NONE) {
  172. AdvertDataBuilder builder(node_type, _prefs->node_name);
  173. return builder.encodeTo(app_data);
  174. } else if (_prefs->advert_loc_policy == ADVERT_LOC_SHARE) {
  175. AdvertDataBuilder builder(node_type, _prefs->node_name, _sensors->node_lat, _sensors->node_lon);
  176. return builder.encodeTo(app_data);
  177. } else {
  178. AdvertDataBuilder builder(node_type, _prefs->node_name, _prefs->node_lat, _prefs->node_lon);
  179. return builder.encodeTo(app_data);
  180. }
  181. }
  182. void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, char* reply) {
  183. if (memcmp(command, "reboot", 6) == 0) {
  184. _board->reboot(); // doesn't return
  185. } else if (memcmp(command, "clkreboot", 9) == 0) {
  186. // Reset clock
  187. getRTCClock()->setCurrentTime(1715770351); // 15 May 2024, 8:50pm
  188. _board->reboot(); // doesn't return
  189. } else if (memcmp(command, "advert", 6) == 0) {
  190. // send flood advert
  191. _callbacks->sendSelfAdvertisement(1500, true); // longer delay, give CLI response time to be sent first
  192. strcpy(reply, "OK - Advert sent");
  193. } else if (memcmp(command, "clock sync", 10) == 0) {
  194. uint32_t curr = getRTCClock()->getCurrentTime();
  195. if (sender_timestamp > curr) {
  196. getRTCClock()->setCurrentTime(sender_timestamp + 1);
  197. uint32_t now = getRTCClock()->getCurrentTime();
  198. DateTime dt = DateTime(now);
  199. sprintf(reply, "OK - clock set: %02d:%02d - %d/%d/%d UTC", dt.hour(), dt.minute(), dt.day(), dt.month(), dt.year());
  200. } else {
  201. strcpy(reply, "ERR: clock cannot go backwards");
  202. }
  203. } else if (memcmp(command, "start ota", 9) == 0) {
  204. if (!_board->startOTAUpdate(_prefs->node_name, reply)) {
  205. strcpy(reply, "Error");
  206. }
  207. } else if (memcmp(command, "clock", 5) == 0) {
  208. uint32_t now = getRTCClock()->getCurrentTime();
  209. DateTime dt = DateTime(now);
  210. sprintf(reply, "%02d:%02d - %d/%d/%d UTC", dt.hour(), dt.minute(), dt.day(), dt.month(), dt.year());
  211. } else if (memcmp(command, "time ", 5) == 0) { // set time (to epoch seconds)
  212. uint32_t secs = _atoi(&command[5]);
  213. uint32_t curr = getRTCClock()->getCurrentTime();
  214. if (secs > curr) {
  215. getRTCClock()->setCurrentTime(secs);
  216. uint32_t now = getRTCClock()->getCurrentTime();
  217. DateTime dt = DateTime(now);
  218. sprintf(reply, "OK - clock set: %02d:%02d - %d/%d/%d UTC", dt.hour(), dt.minute(), dt.day(), dt.month(), dt.year());
  219. } else {
  220. strcpy(reply, "(ERR: clock cannot go backwards)");
  221. }
  222. } else if (memcmp(command, "neighbors", 9) == 0) {
  223. _callbacks->formatNeighborsReply(reply);
  224. } else if (memcmp(command, "neighbor.remove ", 16) == 0) {
  225. const char* hex = &command[16];
  226. uint8_t pubkey[PUB_KEY_SIZE];
  227. int hex_len = min((int)strlen(hex), PUB_KEY_SIZE*2);
  228. int pubkey_len = hex_len / 2;
  229. if (mesh::Utils::fromHex(pubkey, pubkey_len, hex)) {
  230. _callbacks->removeNeighbor(pubkey, pubkey_len);
  231. strcpy(reply, "OK");
  232. } else {
  233. strcpy(reply, "ERR: bad pubkey");
  234. }
  235. } else if (memcmp(command, "tempradio ", 10) == 0) {
  236. strcpy(tmp, &command[10]);
  237. const char *parts[5];
  238. int num = mesh::Utils::parseTextParts(tmp, parts, 5);
  239. float freq = num > 0 ? strtof(parts[0], nullptr) : 0.0f;
  240. float bw = num > 1 ? strtof(parts[1], nullptr) : 0.0f;
  241. uint8_t sf = num > 2 ? atoi(parts[2]) : 0;
  242. uint8_t cr = num > 3 ? atoi(parts[3]) : 0;
  243. int temp_timeout_mins = num > 4 ? atoi(parts[4]) : 0;
  244. if (freq >= 300.0f && freq <= 2500.0f && sf >= 5 && sf <= 12 && cr >= 5 && cr <= 8 && bw >= 7.0f && bw <= 500.0f && temp_timeout_mins > 0) {
  245. _callbacks->applyTempRadioParams(freq, bw, sf, cr, temp_timeout_mins);
  246. sprintf(reply, "OK - temp params for %d mins", temp_timeout_mins);
  247. } else {
  248. strcpy(reply, "Error, invalid params");
  249. }
  250. } else if (memcmp(command, "password ", 9) == 0) {
  251. // change admin password
  252. StrHelper::strncpy(_prefs->password, &command[9], sizeof(_prefs->password));
  253. savePrefs();
  254. sprintf(reply, "password now: %s", _prefs->password); // echo back just to let admin know for sure!!
  255. } else if (memcmp(command, "clear stats", 11) == 0) {
  256. _callbacks->clearStats();
  257. strcpy(reply, "(OK - stats reset)");
  258. /*
  259. * GET commands
  260. */
  261. } else if (memcmp(command, "get ", 4) == 0) {
  262. const char* config = &command[4];
  263. if (memcmp(config, "af", 2) == 0) {
  264. sprintf(reply, "> %s", StrHelper::ftoa(_prefs->airtime_factor));
  265. } else if (memcmp(config, "int.thresh", 10) == 0) {
  266. sprintf(reply, "> %d", (uint32_t) _prefs->interference_threshold);
  267. } else if (memcmp(config, "agc.reset.interval", 18) == 0) {
  268. sprintf(reply, "> %d", ((uint32_t) _prefs->agc_reset_interval) * 4);
  269. } else if (memcmp(config, "multi.acks", 10) == 0) {
  270. sprintf(reply, "> %d", (uint32_t) _prefs->multi_acks);
  271. } else if (memcmp(config, "allow.read.only", 15) == 0) {
  272. sprintf(reply, "> %s", _prefs->allow_read_only ? "on" : "off");
  273. } else if (memcmp(config, "flood.advert.interval", 21) == 0) {
  274. sprintf(reply, "> %d", ((uint32_t) _prefs->flood_advert_interval));
  275. } else if (memcmp(config, "advert.interval", 15) == 0) {
  276. sprintf(reply, "> %d", ((uint32_t) _prefs->advert_interval) * 2);
  277. } else if (memcmp(config, "guest.password", 14) == 0) {
  278. sprintf(reply, "> %s", _prefs->guest_password);
  279. } else if (sender_timestamp == 0 && memcmp(config, "prv.key", 7) == 0) { // from serial command line only
  280. uint8_t prv_key[PRV_KEY_SIZE];
  281. int len = _callbacks->getSelfId().writeTo(prv_key, PRV_KEY_SIZE);
  282. mesh::Utils::toHex(tmp, prv_key, len);
  283. sprintf(reply, "> %s", tmp);
  284. } else if (memcmp(config, "name", 4) == 0) {
  285. sprintf(reply, "> %s", _prefs->node_name);
  286. } else if (memcmp(config, "repeat", 6) == 0) {
  287. sprintf(reply, "> %s", _prefs->disable_fwd ? "off" : "on");
  288. } else if (memcmp(config, "lat", 3) == 0) {
  289. sprintf(reply, "> %s", StrHelper::ftoa(_prefs->node_lat));
  290. } else if (memcmp(config, "lon", 3) == 0) {
  291. sprintf(reply, "> %s", StrHelper::ftoa(_prefs->node_lon));
  292. } else if (memcmp(config, "radio", 5) == 0) {
  293. char freq[16], bw[16];
  294. strcpy(freq, StrHelper::ftoa(_prefs->freq));
  295. strcpy(bw, StrHelper::ftoa3(_prefs->bw));
  296. sprintf(reply, "> %s,%s,%d,%d", freq, bw, (uint32_t)_prefs->sf, (uint32_t)_prefs->cr);
  297. } else if (memcmp(config, "rxdelay", 7) == 0) {
  298. sprintf(reply, "> %s", StrHelper::ftoa(_prefs->rx_delay_base));
  299. } else if (memcmp(config, "txdelay", 7) == 0) {
  300. sprintf(reply, "> %s", StrHelper::ftoa(_prefs->tx_delay_factor));
  301. } else if (memcmp(config, "flood.max", 9) == 0) {
  302. sprintf(reply, "> %d", (uint32_t)_prefs->flood_max);
  303. } else if (memcmp(config, "direct.txdelay", 14) == 0) {
  304. sprintf(reply, "> %s", StrHelper::ftoa(_prefs->direct_tx_delay_factor));
  305. } else if (memcmp(config, "owner.info", 10) == 0) {
  306. *reply++ = '>';
  307. *reply++ = ' ';
  308. const char* sp = _prefs->owner_info;
  309. while (*sp) {
  310. *reply++ = (*sp == '\n') ? '|' : *sp; // translate newline back to orig '|'
  311. sp++;
  312. }
  313. *reply = 0; // set null terminator
  314. } else if (memcmp(config, "path.hash.mode", 14) == 0) {
  315. sprintf(reply, "> %d", (uint32_t)_prefs->path_hash_mode);
  316. } else if (memcmp(config, "tx", 2) == 0 && (config[2] == 0 || config[2] == ' ')) {
  317. sprintf(reply, "> %d", (int32_t) _prefs->tx_power_dbm);
  318. } else if (memcmp(config, "freq", 4) == 0) {
  319. sprintf(reply, "> %s", StrHelper::ftoa(_prefs->freq));
  320. } else if (memcmp(config, "public.key", 10) == 0) {
  321. strcpy(reply, "> ");
  322. mesh::Utils::toHex(&reply[2], _callbacks->getSelfId().pub_key, PUB_KEY_SIZE);
  323. } else if (memcmp(config, "role", 4) == 0) {
  324. sprintf(reply, "> %s", _callbacks->getRole());
  325. } else if (memcmp(config, "bridge.type", 11) == 0) {
  326. sprintf(reply, "> %s",
  327. #ifdef WITH_RS232_BRIDGE
  328. "rs232"
  329. #elif WITH_ESPNOW_BRIDGE
  330. "espnow"
  331. #else
  332. "none"
  333. #endif
  334. );
  335. #ifdef WITH_BRIDGE
  336. } else if (memcmp(config, "bridge.enabled", 14) == 0) {
  337. sprintf(reply, "> %s", _prefs->bridge_enabled ? "on" : "off");
  338. } else if (memcmp(config, "bridge.delay", 12) == 0) {
  339. sprintf(reply, "> %d", (uint32_t)_prefs->bridge_delay);
  340. } else if (memcmp(config, "bridge.source", 13) == 0) {
  341. sprintf(reply, "> %s", _prefs->bridge_pkt_src ? "logRx" : "logTx");
  342. #endif
  343. #ifdef WITH_RS232_BRIDGE
  344. } else if (memcmp(config, "bridge.baud", 11) == 0) {
  345. sprintf(reply, "> %d", (uint32_t)_prefs->bridge_baud);
  346. #endif
  347. #ifdef WITH_ESPNOW_BRIDGE
  348. } else if (memcmp(config, "bridge.channel", 14) == 0) {
  349. sprintf(reply, "> %d", (uint32_t)_prefs->bridge_channel);
  350. } else if (memcmp(config, "bridge.secret", 13) == 0) {
  351. sprintf(reply, "> %s", _prefs->bridge_secret);
  352. #endif
  353. } else if (memcmp(config, "bootloader.ver", 14) == 0) {
  354. #ifdef NRF52_PLATFORM
  355. char ver[32];
  356. if (_board->getBootloaderVersion(ver, sizeof(ver))) {
  357. sprintf(reply, "> %s", ver);
  358. } else {
  359. strcpy(reply, "> unknown");
  360. }
  361. #else
  362. strcpy(reply, "ERROR: unsupported");
  363. #endif
  364. } else if (memcmp(config, "adc.multiplier", 14) == 0) {
  365. float adc_mult = _board->getAdcMultiplier();
  366. if (adc_mult == 0.0f) {
  367. strcpy(reply, "Error: unsupported by this board");
  368. } else {
  369. sprintf(reply, "> %.3f", adc_mult);
  370. }
  371. // Power management commands
  372. } else if (memcmp(config, "pwrmgt.support", 14) == 0) {
  373. #ifdef NRF52_POWER_MANAGEMENT
  374. strcpy(reply, "> supported");
  375. #else
  376. strcpy(reply, "> unsupported");
  377. #endif
  378. } else if (memcmp(config, "pwrmgt.source", 13) == 0) {
  379. #ifdef NRF52_POWER_MANAGEMENT
  380. strcpy(reply, _board->isExternalPowered() ? "> external" : "> battery");
  381. #else
  382. strcpy(reply, "ERROR: Power management not supported");
  383. #endif
  384. } else if (memcmp(config, "pwrmgt.bootreason", 17) == 0) {
  385. #ifdef NRF52_POWER_MANAGEMENT
  386. sprintf(reply, "> Reset: %s; Shutdown: %s",
  387. _board->getResetReasonString(_board->getResetReason()),
  388. _board->getShutdownReasonString(_board->getShutdownReason()));
  389. #else
  390. strcpy(reply, "ERROR: Power management not supported");
  391. #endif
  392. } else if (memcmp(config, "pwrmgt.bootmv", 13) == 0) {
  393. #ifdef NRF52_POWER_MANAGEMENT
  394. sprintf(reply, "> %u mV", _board->getBootVoltage());
  395. #else
  396. strcpy(reply, "ERROR: Power management not supported");
  397. #endif
  398. } else {
  399. sprintf(reply, "??: %s", config);
  400. }
  401. /*
  402. * SET commands
  403. */
  404. } else if (memcmp(command, "set ", 4) == 0) {
  405. const char* config = &command[4];
  406. if (memcmp(config, "af ", 3) == 0) {
  407. _prefs->airtime_factor = atof(&config[3]);
  408. savePrefs();
  409. strcpy(reply, "OK");
  410. } else if (memcmp(config, "int.thresh ", 11) == 0) {
  411. _prefs->interference_threshold = atoi(&config[11]);
  412. savePrefs();
  413. strcpy(reply, "OK");
  414. } else if (memcmp(config, "agc.reset.interval ", 19) == 0) {
  415. _prefs->agc_reset_interval = atoi(&config[19]) / 4;
  416. savePrefs();
  417. sprintf(reply, "OK - interval rounded to %d", ((uint32_t) _prefs->agc_reset_interval) * 4);
  418. } else if (memcmp(config, "multi.acks ", 11) == 0) {
  419. _prefs->multi_acks = atoi(&config[11]);
  420. savePrefs();
  421. strcpy(reply, "OK");
  422. } else if (memcmp(config, "allow.read.only ", 16) == 0) {
  423. _prefs->allow_read_only = memcmp(&config[16], "on", 2) == 0;
  424. savePrefs();
  425. strcpy(reply, "OK");
  426. } else if (memcmp(config, "flood.advert.interval ", 22) == 0) {
  427. int hours = _atoi(&config[22]);
  428. if ((hours > 0 && hours < 3) || (hours > 168)) {
  429. strcpy(reply, "Error: interval range is 3-168 hours");
  430. } else {
  431. _prefs->flood_advert_interval = (uint8_t)(hours);
  432. _callbacks->updateFloodAdvertTimer();
  433. savePrefs();
  434. strcpy(reply, "OK");
  435. }
  436. } else if (memcmp(config, "advert.interval ", 16) == 0) {
  437. int mins = _atoi(&config[16]);
  438. if ((mins > 0 && mins < MIN_LOCAL_ADVERT_INTERVAL) || (mins > 240)) {
  439. sprintf(reply, "Error: interval range is %d-240 minutes", MIN_LOCAL_ADVERT_INTERVAL);
  440. } else {
  441. _prefs->advert_interval = (uint8_t)(mins / 2);
  442. _callbacks->updateAdvertTimer();
  443. savePrefs();
  444. strcpy(reply, "OK");
  445. }
  446. } else if (memcmp(config, "guest.password ", 15) == 0) {
  447. StrHelper::strncpy(_prefs->guest_password, &config[15], sizeof(_prefs->guest_password));
  448. savePrefs();
  449. strcpy(reply, "OK");
  450. } else if (memcmp(config, "prv.key ", 8) == 0) {
  451. uint8_t prv_key[PRV_KEY_SIZE];
  452. bool success = mesh::Utils::fromHex(prv_key, PRV_KEY_SIZE, &config[8]);
  453. // only allow rekey if key is valid
  454. if (success && mesh::LocalIdentity::validatePrivateKey(prv_key)) {
  455. mesh::LocalIdentity new_id;
  456. new_id.readFrom(prv_key, PRV_KEY_SIZE);
  457. _callbacks->saveIdentity(new_id);
  458. strcpy(reply, "OK, reboot to apply! New pubkey: ");
  459. mesh::Utils::toHex(&reply[33], new_id.pub_key, PUB_KEY_SIZE);
  460. } else {
  461. strcpy(reply, "Error, bad key");
  462. }
  463. } else if (memcmp(config, "name ", 5) == 0) {
  464. if (isValidName(&config[5])) {
  465. StrHelper::strncpy(_prefs->node_name, &config[5], sizeof(_prefs->node_name));
  466. savePrefs();
  467. strcpy(reply, "OK");
  468. } else {
  469. strcpy(reply, "Error, bad chars");
  470. }
  471. } else if (memcmp(config, "repeat ", 7) == 0) {
  472. _prefs->disable_fwd = memcmp(&config[7], "off", 3) == 0;
  473. savePrefs();
  474. strcpy(reply, _prefs->disable_fwd ? "OK - repeat is now OFF" : "OK - repeat is now ON");
  475. } else if (memcmp(config, "radio ", 6) == 0) {
  476. strcpy(tmp, &config[6]);
  477. const char *parts[4];
  478. int num = mesh::Utils::parseTextParts(tmp, parts, 4);
  479. float freq = num > 0 ? strtof(parts[0], nullptr) : 0.0f;
  480. float bw = num > 1 ? strtof(parts[1], nullptr) : 0.0f;
  481. uint8_t sf = num > 2 ? atoi(parts[2]) : 0;
  482. uint8_t cr = num > 3 ? atoi(parts[3]) : 0;
  483. if (freq >= 300.0f && freq <= 2500.0f && sf >= 5 && sf <= 12 && cr >= 5 && cr <= 8 && bw >= 7.0f && bw <= 500.0f) {
  484. _prefs->sf = sf;
  485. _prefs->cr = cr;
  486. _prefs->freq = freq;
  487. _prefs->bw = bw;
  488. _callbacks->savePrefs();
  489. strcpy(reply, "OK - reboot to apply");
  490. } else {
  491. strcpy(reply, "Error, invalid radio params");
  492. }
  493. } else if (memcmp(config, "lat ", 4) == 0) {
  494. _prefs->node_lat = atof(&config[4]);
  495. savePrefs();
  496. strcpy(reply, "OK");
  497. } else if (memcmp(config, "lon ", 4) == 0) {
  498. _prefs->node_lon = atof(&config[4]);
  499. savePrefs();
  500. strcpy(reply, "OK");
  501. } else if (memcmp(config, "rxdelay ", 8) == 0) {
  502. float db = atof(&config[8]);
  503. if (db >= 0) {
  504. _prefs->rx_delay_base = db;
  505. savePrefs();
  506. strcpy(reply, "OK");
  507. } else {
  508. strcpy(reply, "Error, cannot be negative");
  509. }
  510. } else if (memcmp(config, "txdelay ", 8) == 0) {
  511. float f = atof(&config[8]);
  512. if (f >= 0) {
  513. _prefs->tx_delay_factor = f;
  514. savePrefs();
  515. strcpy(reply, "OK");
  516. } else {
  517. strcpy(reply, "Error, cannot be negative");
  518. }
  519. } else if (memcmp(config, "flood.max ", 10) == 0) {
  520. uint8_t m = atoi(&config[10]);
  521. if (m <= 64) {
  522. _prefs->flood_max = m;
  523. savePrefs();
  524. strcpy(reply, "OK");
  525. } else {
  526. strcpy(reply, "Error, max 64");
  527. }
  528. } else if (memcmp(config, "direct.txdelay ", 15) == 0) {
  529. float f = atof(&config[15]);
  530. if (f >= 0) {
  531. _prefs->direct_tx_delay_factor = f;
  532. savePrefs();
  533. strcpy(reply, "OK");
  534. } else {
  535. strcpy(reply, "Error, cannot be negative");
  536. }
  537. } else if (memcmp(config, "owner.info ", 11) == 0) {
  538. config += 11;
  539. char *dp = _prefs->owner_info;
  540. while (*config && dp - _prefs->owner_info < sizeof(_prefs->owner_info)-1) {
  541. *dp++ = (*config == '|') ? '\n' : *config; // translate '|' to newline chars
  542. config++;
  543. }
  544. *dp = 0;
  545. savePrefs();
  546. strcpy(reply, "OK");
  547. } else if (memcmp(config, "path.hash.mode ", 15) == 0) {
  548. config += 15;
  549. uint8_t mode = atoi(config);
  550. if (mode < 3) {
  551. _prefs->path_hash_mode = mode;
  552. savePrefs();
  553. strcpy(reply, "OK");
  554. } else {
  555. strcpy(reply, "Error, must be 0,1, or 2");
  556. }
  557. } else if (memcmp(config, "tx ", 3) == 0) {
  558. _prefs->tx_power_dbm = atoi(&config[3]);
  559. savePrefs();
  560. _callbacks->setTxPower(_prefs->tx_power_dbm);
  561. strcpy(reply, "OK");
  562. } else if (sender_timestamp == 0 && memcmp(config, "freq ", 5) == 0) {
  563. _prefs->freq = atof(&config[5]);
  564. savePrefs();
  565. strcpy(reply, "OK - reboot to apply");
  566. #ifdef WITH_BRIDGE
  567. } else if (memcmp(config, "bridge.enabled ", 15) == 0) {
  568. _prefs->bridge_enabled = memcmp(&config[15], "on", 2) == 0;
  569. _callbacks->setBridgeState(_prefs->bridge_enabled);
  570. savePrefs();
  571. strcpy(reply, "OK");
  572. } else if (memcmp(config, "bridge.delay ", 13) == 0) {
  573. int delay = _atoi(&config[13]);
  574. if (delay >= 0 && delay <= 10000) {
  575. _prefs->bridge_delay = (uint16_t)delay;
  576. savePrefs();
  577. strcpy(reply, "OK");
  578. } else {
  579. strcpy(reply, "Error: delay must be between 0-10000 ms");
  580. }
  581. } else if (memcmp(config, "bridge.source ", 14) == 0) {
  582. _prefs->bridge_pkt_src = memcmp(&config[14], "rx", 2) == 0;
  583. savePrefs();
  584. strcpy(reply, "OK");
  585. #endif
  586. #ifdef WITH_RS232_BRIDGE
  587. } else if (memcmp(config, "bridge.baud ", 12) == 0) {
  588. uint32_t baud = atoi(&config[12]);
  589. if (baud >= 9600 && baud <= 115200) {
  590. _prefs->bridge_baud = (uint32_t)baud;
  591. _callbacks->restartBridge();
  592. savePrefs();
  593. strcpy(reply, "OK");
  594. } else {
  595. strcpy(reply, "Error: baud rate must be between 9600-115200");
  596. }
  597. #endif
  598. #ifdef WITH_ESPNOW_BRIDGE
  599. } else if (memcmp(config, "bridge.channel ", 15) == 0) {
  600. int ch = atoi(&config[15]);
  601. if (ch > 0 && ch < 15) {
  602. _prefs->bridge_channel = (uint8_t)ch;
  603. _callbacks->restartBridge();
  604. savePrefs();
  605. strcpy(reply, "OK");
  606. } else {
  607. strcpy(reply, "Error: channel must be between 1-14");
  608. }
  609. } else if (memcmp(config, "bridge.secret ", 14) == 0) {
  610. StrHelper::strncpy(_prefs->bridge_secret, &config[14], sizeof(_prefs->bridge_secret));
  611. _callbacks->restartBridge();
  612. savePrefs();
  613. strcpy(reply, "OK");
  614. #endif
  615. } else if (memcmp(config, "adc.multiplier ", 15) == 0) {
  616. _prefs->adc_multiplier = atof(&config[15]);
  617. if (_board->setAdcMultiplier(_prefs->adc_multiplier)) {
  618. savePrefs();
  619. if (_prefs->adc_multiplier == 0.0f) {
  620. strcpy(reply, "OK - using default board multiplier");
  621. } else {
  622. sprintf(reply, "OK - multiplier set to %.3f", _prefs->adc_multiplier);
  623. }
  624. } else {
  625. _prefs->adc_multiplier = 0.0f;
  626. strcpy(reply, "Error: unsupported by this board");
  627. };
  628. } else {
  629. sprintf(reply, "unknown config: %s", config);
  630. }
  631. } else if (sender_timestamp == 0 && strcmp(command, "erase") == 0) {
  632. bool s = _callbacks->formatFileSystem();
  633. sprintf(reply, "File system erase: %s", s ? "OK" : "Err");
  634. } else if (memcmp(command, "ver", 3) == 0) {
  635. sprintf(reply, "%s (Build: %s)", _callbacks->getFirmwareVer(), _callbacks->getBuildDate());
  636. } else if (memcmp(command, "board", 5) == 0) {
  637. sprintf(reply, "%s", _board->getManufacturerName());
  638. } else if (memcmp(command, "sensor get ", 11) == 0) {
  639. const char* key = command + 11;
  640. const char* val = _sensors->getSettingByKey(key);
  641. if (val != NULL) {
  642. sprintf(reply, "> %s", val);
  643. } else {
  644. strcpy(reply, "null");
  645. }
  646. } else if (memcmp(command, "sensor set ", 11) == 0) {
  647. strcpy(tmp, &command[11]);
  648. const char *parts[2];
  649. int num = mesh::Utils::parseTextParts(tmp, parts, 2, ' ');
  650. const char *key = (num > 0) ? parts[0] : "";
  651. const char *value = (num > 1) ? parts[1] : "null";
  652. if (_sensors->setSettingValue(key, value)) {
  653. strcpy(reply, "ok");
  654. } else {
  655. strcpy(reply, "can't find custom var");
  656. }
  657. } else if (memcmp(command, "sensor list", 11) == 0) {
  658. char* dp = reply;
  659. int start = 0;
  660. int end = _sensors->getNumSettings();
  661. if (strlen(command) > 11) {
  662. start = _atoi(command+12);
  663. }
  664. if (start >= end) {
  665. strcpy(reply, "no custom var");
  666. } else {
  667. sprintf(dp, "%d vars\n", end);
  668. dp = strchr(dp, 0);
  669. int i;
  670. for (i = start; i < end && (dp-reply < 134); i++) {
  671. sprintf(dp, "%s=%s\n",
  672. _sensors->getSettingName(i),
  673. _sensors->getSettingValue(i));
  674. dp = strchr(dp, 0);
  675. }
  676. if (i < end) {
  677. sprintf(dp, "... next:%d", i);
  678. } else {
  679. *(dp-1) = 0; // remove last CR
  680. }
  681. }
  682. #if ENV_INCLUDE_GPS == 1
  683. } else if (memcmp(command, "gps on", 6) == 0) {
  684. if (_sensors->setSettingValue("gps", "1")) {
  685. _prefs->gps_enabled = 1;
  686. savePrefs();
  687. strcpy(reply, "ok");
  688. } else {
  689. strcpy(reply, "gps toggle not found");
  690. }
  691. } else if (memcmp(command, "gps off", 7) == 0) {
  692. if (_sensors->setSettingValue("gps", "0")) {
  693. _prefs->gps_enabled = 0;
  694. savePrefs();
  695. strcpy(reply, "ok");
  696. } else {
  697. strcpy(reply, "gps toggle not found");
  698. }
  699. } else if (memcmp(command, "gps sync", 8) == 0) {
  700. LocationProvider * l = _sensors->getLocationProvider();
  701. if (l != NULL) {
  702. l->syncTime();
  703. strcpy(reply, "ok");
  704. } else {
  705. strcpy(reply, "gps provider not found");
  706. }
  707. } else if (memcmp(command, "gps setloc", 10) == 0) {
  708. _prefs->node_lat = _sensors->node_lat;
  709. _prefs->node_lon = _sensors->node_lon;
  710. savePrefs();
  711. strcpy(reply, "ok");
  712. } else if (memcmp(command, "gps advert", 10) == 0) {
  713. if (strlen(command) == 10) {
  714. switch (_prefs->advert_loc_policy) {
  715. case ADVERT_LOC_NONE:
  716. strcpy(reply, "> none");
  717. break;
  718. case ADVERT_LOC_PREFS:
  719. strcpy(reply, "> prefs");
  720. break;
  721. case ADVERT_LOC_SHARE:
  722. strcpy(reply, "> share");
  723. break;
  724. default:
  725. strcpy(reply, "error");
  726. }
  727. } else if (memcmp(command+11, "none", 4) == 0) {
  728. _prefs->advert_loc_policy = ADVERT_LOC_NONE;
  729. savePrefs();
  730. strcpy(reply, "ok");
  731. } else if (memcmp(command+11, "share", 5) == 0) {
  732. _prefs->advert_loc_policy = ADVERT_LOC_SHARE;
  733. savePrefs();
  734. strcpy(reply, "ok");
  735. } else if (memcmp(command+11, "prefs", 5) == 0) {
  736. _prefs->advert_loc_policy = ADVERT_LOC_PREFS;
  737. savePrefs();
  738. strcpy(reply, "ok");
  739. } else {
  740. strcpy(reply, "error");
  741. }
  742. } else if (memcmp(command, "gps", 3) == 0) {
  743. LocationProvider * l = _sensors->getLocationProvider();
  744. if (l != NULL) {
  745. bool enabled = l->isEnabled(); // is EN pin on ?
  746. bool fix = l->isValid(); // has fix ?
  747. int sats = l->satellitesCount();
  748. bool active = !strcmp(_sensors->getSettingByKey("gps"), "1");
  749. if (enabled) {
  750. sprintf(reply, "on, %s, %s, %d sats",
  751. active?"active":"deactivated",
  752. fix?"fix":"no fix",
  753. sats);
  754. } else {
  755. strcpy(reply, "off");
  756. }
  757. } else {
  758. strcpy(reply, "Can't find GPS");
  759. }
  760. #endif
  761. } else if (memcmp(command, "powersaving on", 14) == 0) {
  762. _prefs->powersaving_enabled = 1;
  763. savePrefs();
  764. strcpy(reply, "ok"); // TODO: to return Not supported if required
  765. } else if (memcmp(command, "powersaving off", 15) == 0) {
  766. _prefs->powersaving_enabled = 0;
  767. savePrefs();
  768. strcpy(reply, "ok");
  769. } else if (memcmp(command, "powersaving", 11) == 0) {
  770. if (_prefs->powersaving_enabled) {
  771. strcpy(reply, "on");
  772. } else {
  773. strcpy(reply, "off");
  774. }
  775. } else if (memcmp(command, "log start", 9) == 0) {
  776. _callbacks->setLoggingOn(true);
  777. strcpy(reply, " logging on");
  778. } else if (memcmp(command, "log stop", 8) == 0) {
  779. _callbacks->setLoggingOn(false);
  780. strcpy(reply, " logging off");
  781. } else if (memcmp(command, "log erase", 9) == 0) {
  782. _callbacks->eraseLogFile();
  783. strcpy(reply, " log erased");
  784. } else if (sender_timestamp == 0 && memcmp(command, "log", 3) == 0) {
  785. _callbacks->dumpLogFile();
  786. strcpy(reply, " EOF");
  787. } else if (sender_timestamp == 0 && memcmp(command, "stats-packets", 13) == 0 && (command[13] == 0 || command[13] == ' ')) {
  788. _callbacks->formatPacketStatsReply(reply);
  789. } else if (sender_timestamp == 0 && memcmp(command, "stats-radio", 11) == 0 && (command[11] == 0 || command[11] == ' ')) {
  790. _callbacks->formatRadioStatsReply(reply);
  791. } else if (sender_timestamp == 0 && memcmp(command, "stats-core", 10) == 0 && (command[10] == 0 || command[10] == ' ')) {
  792. _callbacks->formatStatsReply(reply);
  793. } else {
  794. strcpy(reply, "Unknown command");
  795. }
  796. }