CommonCLI.cpp 47 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110
  1. #include <Arduino.h>
  2. #include "CommonCLI.h"
  3. #include "TxtDataHelpers.h"
  4. #include "AdvertDataHelpers.h"
  5. #include "TxtDataHelpers.h"
  6. #include <RTClib.h>
  7. #ifndef BRIDGE_MAX_BAUD
  8. #define BRIDGE_MAX_BAUD 115200
  9. #endif
  10. // Believe it or not, this std C function is busted on some platforms!
  11. static uint32_t _atoi(const char* sp) {
  12. uint32_t n = 0;
  13. while (*sp && *sp >= '0' && *sp <= '9') {
  14. n *= 10;
  15. n += (*sp++ - '0');
  16. }
  17. return n;
  18. }
  19. static bool isValidName(const char *n) {
  20. while (*n) {
  21. if (*n == '[' || *n == ']' || *n == '\\' || *n == ':' || *n == ',' || *n == '?' || *n == '*') return false;
  22. n++;
  23. }
  24. return true;
  25. }
  26. void CommonCLI::loadPrefs(FILESYSTEM* fs) {
  27. if (fs->exists("/com_prefs")) {
  28. loadPrefsInt(fs, "/com_prefs"); // new filename
  29. } else if (fs->exists("/node_prefs")) {
  30. loadPrefsInt(fs, "/node_prefs");
  31. savePrefs(fs); // save to new filename
  32. fs->remove("/node_prefs"); // remove old
  33. }
  34. }
  35. void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
  36. #if defined(RP2040_PLATFORM)
  37. File file = fs->open(filename, "r");
  38. #else
  39. File file = fs->open(filename);
  40. #endif
  41. if (file) {
  42. uint8_t pad[8];
  43. file.read((uint8_t *)&_prefs->airtime_factor, sizeof(_prefs->airtime_factor)); // 0
  44. file.read((uint8_t *)&_prefs->node_name, sizeof(_prefs->node_name)); // 4
  45. file.read(pad, 4); // 36
  46. file.read((uint8_t *)&_prefs->node_lat, sizeof(_prefs->node_lat)); // 40
  47. file.read((uint8_t *)&_prefs->node_lon, sizeof(_prefs->node_lon)); // 48
  48. file.read((uint8_t *)&_prefs->password[0], sizeof(_prefs->password)); // 56
  49. file.read((uint8_t *)&_prefs->freq, sizeof(_prefs->freq)); // 72
  50. file.read((uint8_t *)&_prefs->tx_power_dbm, sizeof(_prefs->tx_power_dbm)); // 76
  51. file.read((uint8_t *)&_prefs->disable_fwd, sizeof(_prefs->disable_fwd)); // 77
  52. file.read((uint8_t *)&_prefs->advert_interval, sizeof(_prefs->advert_interval)); // 78
  53. file.read(pad, 1); // 79 : 1 byte unused (was rx_boosted_gain in v1.14.1, moved to end for upgrade compat)
  54. file.read((uint8_t *)&_prefs->rx_delay_base, sizeof(_prefs->rx_delay_base)); // 80
  55. file.read((uint8_t *)&_prefs->tx_delay_factor, sizeof(_prefs->tx_delay_factor)); // 84
  56. file.read((uint8_t *)&_prefs->guest_password[0], sizeof(_prefs->guest_password)); // 88
  57. file.read((uint8_t *)&_prefs->direct_tx_delay_factor, sizeof(_prefs->direct_tx_delay_factor)); // 104
  58. file.read(pad, 4); // 108 : 4 bytes unused
  59. file.read((uint8_t *)&_prefs->sf, sizeof(_prefs->sf)); // 112
  60. file.read((uint8_t *)&_prefs->cr, sizeof(_prefs->cr)); // 113
  61. file.read((uint8_t *)&_prefs->allow_read_only, sizeof(_prefs->allow_read_only)); // 114
  62. file.read((uint8_t *)&_prefs->multi_acks, sizeof(_prefs->multi_acks)); // 115
  63. file.read((uint8_t *)&_prefs->bw, sizeof(_prefs->bw)); // 116
  64. file.read((uint8_t *)&_prefs->agc_reset_interval, sizeof(_prefs->agc_reset_interval)); // 120
  65. file.read((uint8_t *)&_prefs->path_hash_mode, sizeof(_prefs->path_hash_mode)); // 121
  66. file.read((uint8_t *)&_prefs->loop_detect, sizeof(_prefs->loop_detect)); // 122
  67. file.read(pad, 1); // 123
  68. file.read((uint8_t *)&_prefs->flood_max, sizeof(_prefs->flood_max)); // 124
  69. file.read((uint8_t *)&_prefs->flood_advert_interval, sizeof(_prefs->flood_advert_interval)); // 125
  70. file.read((uint8_t *)&_prefs->interference_threshold, sizeof(_prefs->interference_threshold)); // 126
  71. file.read((uint8_t *)&_prefs->bridge_enabled, sizeof(_prefs->bridge_enabled)); // 127
  72. file.read((uint8_t *)&_prefs->bridge_delay, sizeof(_prefs->bridge_delay)); // 128
  73. file.read((uint8_t *)&_prefs->bridge_pkt_src, sizeof(_prefs->bridge_pkt_src)); // 130
  74. file.read((uint8_t *)&_prefs->bridge_baud, sizeof(_prefs->bridge_baud)); // 131
  75. file.read((uint8_t *)&_prefs->bridge_channel, sizeof(_prefs->bridge_channel)); // 135
  76. file.read((uint8_t *)&_prefs->bridge_secret, sizeof(_prefs->bridge_secret)); // 136
  77. file.read((uint8_t *)&_prefs->powersaving_enabled, sizeof(_prefs->powersaving_enabled)); // 152
  78. file.read(pad, 3); // 153
  79. file.read((uint8_t *)&_prefs->gps_enabled, sizeof(_prefs->gps_enabled)); // 156
  80. file.read((uint8_t *)&_prefs->gps_interval, sizeof(_prefs->gps_interval)); // 157
  81. file.read((uint8_t *)&_prefs->advert_loc_policy, sizeof (_prefs->advert_loc_policy)); // 161
  82. file.read((uint8_t *)&_prefs->discovery_mod_timestamp, sizeof(_prefs->discovery_mod_timestamp)); // 162
  83. file.read((uint8_t *)&_prefs->adc_multiplier, sizeof(_prefs->adc_multiplier)); // 166
  84. file.read((uint8_t *)_prefs->owner_info, sizeof(_prefs->owner_info)); // 170
  85. file.read((uint8_t *)&_prefs->rx_boosted_gain, sizeof(_prefs->rx_boosted_gain)); // 290
  86. file.read((uint8_t *)&_prefs->flood_max_unscoped, sizeof(_prefs->flood_max_unscoped)); // 291
  87. // next: 292
  88. // sanitise bad pref values
  89. _prefs->rx_delay_base = constrain(_prefs->rx_delay_base, 0, 20.0f);
  90. _prefs->tx_delay_factor = constrain(_prefs->tx_delay_factor, 0, 2.0f);
  91. _prefs->direct_tx_delay_factor = constrain(_prefs->direct_tx_delay_factor, 0, 2.0f);
  92. _prefs->airtime_factor = constrain(_prefs->airtime_factor, 0, 9.0f);
  93. _prefs->freq = constrain(_prefs->freq, 150.0f, 2500.0f);
  94. _prefs->bw = constrain(_prefs->bw, 7.8f, 500.0f);
  95. _prefs->sf = constrain(_prefs->sf, 5, 12);
  96. _prefs->cr = constrain(_prefs->cr, 5, 8);
  97. _prefs->tx_power_dbm = constrain(_prefs->tx_power_dbm, -9, 30);
  98. _prefs->multi_acks = constrain(_prefs->multi_acks, 0, 1);
  99. _prefs->adc_multiplier = constrain(_prefs->adc_multiplier, 0.0f, 10.0f);
  100. _prefs->path_hash_mode = constrain(_prefs->path_hash_mode, 0, 2); // NOTE: mode 3 reserved for future
  101. // sanitise bad bridge pref values
  102. _prefs->bridge_enabled = constrain(_prefs->bridge_enabled, 0, 1);
  103. _prefs->bridge_delay = constrain(_prefs->bridge_delay, 0, 10000);
  104. _prefs->bridge_pkt_src = constrain(_prefs->bridge_pkt_src, 0, 1);
  105. _prefs->bridge_baud = constrain(_prefs->bridge_baud, 9600, BRIDGE_MAX_BAUD);
  106. _prefs->bridge_channel = constrain(_prefs->bridge_channel, 0, 14);
  107. _prefs->powersaving_enabled = constrain(_prefs->powersaving_enabled, 0, 1);
  108. _prefs->gps_enabled = constrain(_prefs->gps_enabled, 0, 1);
  109. _prefs->advert_loc_policy = constrain(_prefs->advert_loc_policy, 0, 2);
  110. // sanitise settings
  111. _prefs->rx_boosted_gain = constrain(_prefs->rx_boosted_gain, 0, 1); // boolean
  112. // sanitise only if user sets unscoped max
  113. if (_prefs->flood_max_unscoped != FLOOD_MAX_UNSCOPED_UNSET) {
  114. _prefs->flood_max_unscoped = constrain(_prefs->flood_max_unscoped, 0, 64);
  115. }
  116. file.close();
  117. }
  118. }
  119. void CommonCLI::savePrefs(FILESYSTEM* fs) {
  120. #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
  121. fs->remove("/com_prefs");
  122. File file = fs->open("/com_prefs", FILE_O_WRITE);
  123. #elif defined(RP2040_PLATFORM)
  124. File file = fs->open("/com_prefs", "w");
  125. #else
  126. File file = fs->open("/com_prefs", "w", true);
  127. #endif
  128. if (file) {
  129. uint8_t pad[8];
  130. memset(pad, 0, sizeof(pad));
  131. file.write((uint8_t *)&_prefs->airtime_factor, sizeof(_prefs->airtime_factor)); // 0
  132. file.write((uint8_t *)&_prefs->node_name, sizeof(_prefs->node_name)); // 4
  133. file.write(pad, 4); // 36
  134. file.write((uint8_t *)&_prefs->node_lat, sizeof(_prefs->node_lat)); // 40
  135. file.write((uint8_t *)&_prefs->node_lon, sizeof(_prefs->node_lon)); // 48
  136. file.write((uint8_t *)&_prefs->password[0], sizeof(_prefs->password)); // 56
  137. file.write((uint8_t *)&_prefs->freq, sizeof(_prefs->freq)); // 72
  138. file.write((uint8_t *)&_prefs->tx_power_dbm, sizeof(_prefs->tx_power_dbm)); // 76
  139. file.write((uint8_t *)&_prefs->disable_fwd, sizeof(_prefs->disable_fwd)); // 77
  140. file.write((uint8_t *)&_prefs->advert_interval, sizeof(_prefs->advert_interval)); // 78
  141. file.write(pad, 1); // 79 : 1 byte unused (rx_boosted_gain moved to end)
  142. file.write((uint8_t *)&_prefs->rx_delay_base, sizeof(_prefs->rx_delay_base)); // 80
  143. file.write((uint8_t *)&_prefs->tx_delay_factor, sizeof(_prefs->tx_delay_factor)); // 84
  144. file.write((uint8_t *)&_prefs->guest_password[0], sizeof(_prefs->guest_password)); // 88
  145. file.write((uint8_t *)&_prefs->direct_tx_delay_factor, sizeof(_prefs->direct_tx_delay_factor)); // 104
  146. file.write(pad, 4); // 108 : 4 byte unused
  147. file.write((uint8_t *)&_prefs->sf, sizeof(_prefs->sf)); // 112
  148. file.write((uint8_t *)&_prefs->cr, sizeof(_prefs->cr)); // 113
  149. file.write((uint8_t *)&_prefs->allow_read_only, sizeof(_prefs->allow_read_only)); // 114
  150. file.write((uint8_t *)&_prefs->multi_acks, sizeof(_prefs->multi_acks)); // 115
  151. file.write((uint8_t *)&_prefs->bw, sizeof(_prefs->bw)); // 116
  152. file.write((uint8_t *)&_prefs->agc_reset_interval, sizeof(_prefs->agc_reset_interval)); // 120
  153. file.write((uint8_t *)&_prefs->path_hash_mode, sizeof(_prefs->path_hash_mode)); // 121
  154. file.write((uint8_t *)&_prefs->loop_detect, sizeof(_prefs->loop_detect)); // 122
  155. file.write(pad, 1); // 123
  156. file.write((uint8_t *)&_prefs->flood_max, sizeof(_prefs->flood_max)); // 124
  157. file.write((uint8_t *)&_prefs->flood_advert_interval, sizeof(_prefs->flood_advert_interval)); // 125
  158. file.write((uint8_t *)&_prefs->interference_threshold, sizeof(_prefs->interference_threshold)); // 126
  159. file.write((uint8_t *)&_prefs->bridge_enabled, sizeof(_prefs->bridge_enabled)); // 127
  160. file.write((uint8_t *)&_prefs->bridge_delay, sizeof(_prefs->bridge_delay)); // 128
  161. file.write((uint8_t *)&_prefs->bridge_pkt_src, sizeof(_prefs->bridge_pkt_src)); // 130
  162. file.write((uint8_t *)&_prefs->bridge_baud, sizeof(_prefs->bridge_baud)); // 131
  163. file.write((uint8_t *)&_prefs->bridge_channel, sizeof(_prefs->bridge_channel)); // 135
  164. file.write((uint8_t *)&_prefs->bridge_secret, sizeof(_prefs->bridge_secret)); // 136
  165. file.write((uint8_t *)&_prefs->powersaving_enabled, sizeof(_prefs->powersaving_enabled)); // 152
  166. file.write(pad, 3); // 153
  167. file.write((uint8_t *)&_prefs->gps_enabled, sizeof(_prefs->gps_enabled)); // 156
  168. file.write((uint8_t *)&_prefs->gps_interval, sizeof(_prefs->gps_interval)); // 157
  169. file.write((uint8_t *)&_prefs->advert_loc_policy, sizeof(_prefs->advert_loc_policy)); // 161
  170. file.write((uint8_t *)&_prefs->discovery_mod_timestamp, sizeof(_prefs->discovery_mod_timestamp)); // 162
  171. file.write((uint8_t *)&_prefs->adc_multiplier, sizeof(_prefs->adc_multiplier)); // 166
  172. file.write((uint8_t *)_prefs->owner_info, sizeof(_prefs->owner_info)); // 170
  173. file.write((uint8_t *)&_prefs->rx_boosted_gain, sizeof(_prefs->rx_boosted_gain)); // 290
  174. file.write((uint8_t *)&_prefs->flood_max_unscoped, sizeof(_prefs->flood_max_unscoped)); // 291
  175. // next: 292
  176. file.close();
  177. }
  178. }
  179. #define MIN_LOCAL_ADVERT_INTERVAL 60
  180. void CommonCLI::savePrefs() {
  181. if (_prefs->advert_interval * 2 < MIN_LOCAL_ADVERT_INTERVAL) {
  182. _prefs->advert_interval = 0; // turn it off, now that device has been manually configured
  183. }
  184. _callbacks->savePrefs();
  185. }
  186. uint8_t CommonCLI::buildAdvertData(uint8_t node_type, uint8_t* app_data) {
  187. if (_prefs->advert_loc_policy == ADVERT_LOC_NONE) {
  188. AdvertDataBuilder builder(node_type, _prefs->node_name);
  189. return builder.encodeTo(app_data);
  190. } else if (_prefs->advert_loc_policy == ADVERT_LOC_SHARE) {
  191. AdvertDataBuilder builder(node_type, _prefs->node_name, _sensors->node_lat, _sensors->node_lon);
  192. return builder.encodeTo(app_data);
  193. } else {
  194. AdvertDataBuilder builder(node_type, _prefs->node_name, _prefs->node_lat, _prefs->node_lon);
  195. return builder.encodeTo(app_data);
  196. }
  197. }
  198. void CommonCLI::handleCommand(uint32_t sender_timestamp, char* command, char* reply) {
  199. if (memcmp(command, "poweroff", 8) == 0 || memcmp(command, "shutdown", 8) == 0) {
  200. _board->powerOff(); // doesn't return
  201. } else if (memcmp(command, "reboot", 6) == 0) {
  202. _board->reboot(); // doesn't return
  203. } else if (memcmp(command, "clkreboot", 9) == 0) {
  204. // Reset clock
  205. getRTCClock()->setCurrentTime(1715770351); // 15 May 2024, 8:50pm
  206. _board->reboot(); // doesn't return
  207. } else if (memcmp(command, "advert.zerohop", 14) == 0 && (command[14] == 0 || command[14] == ' ')) {
  208. // send zerohop advert
  209. _callbacks->sendSelfAdvertisement(1500, false); // longer delay, give CLI response time to be sent first
  210. strcpy(reply, "OK - zerohop advert sent");
  211. } else if (memcmp(command, "advert", 6) == 0) {
  212. // send flood advert
  213. _callbacks->sendSelfAdvertisement(1500, true); // longer delay, give CLI response time to be sent first
  214. strcpy(reply, "OK - Advert sent");
  215. } else if (memcmp(command, "clock sync", 10) == 0) {
  216. uint32_t curr = getRTCClock()->getCurrentTime();
  217. if (sender_timestamp > curr) {
  218. getRTCClock()->setCurrentTime(sender_timestamp + 1);
  219. uint32_t now = getRTCClock()->getCurrentTime();
  220. DateTime dt = DateTime(now);
  221. sprintf(reply, "OK - clock set: %02d:%02d - %d/%d/%d UTC", dt.hour(), dt.minute(), dt.day(), dt.month(), dt.year());
  222. } else {
  223. strcpy(reply, "ERR: clock cannot go backwards");
  224. }
  225. } else if (memcmp(command, "start ota", 9) == 0) {
  226. if (!_board->startOTAUpdate(_prefs->node_name, reply)) {
  227. strcpy(reply, "Error");
  228. }
  229. } else if (memcmp(command, "clock", 5) == 0) {
  230. uint32_t now = getRTCClock()->getCurrentTime();
  231. DateTime dt = DateTime(now);
  232. sprintf(reply, "%02d:%02d - %d/%d/%d UTC", dt.hour(), dt.minute(), dt.day(), dt.month(), dt.year());
  233. } else if (memcmp(command, "time ", 5) == 0) { // set time (to epoch seconds)
  234. uint32_t secs = _atoi(&command[5]);
  235. uint32_t curr = getRTCClock()->getCurrentTime();
  236. if (secs > curr) {
  237. getRTCClock()->setCurrentTime(secs);
  238. uint32_t now = getRTCClock()->getCurrentTime();
  239. DateTime dt = DateTime(now);
  240. sprintf(reply, "OK - clock set: %02d:%02d - %d/%d/%d UTC", dt.hour(), dt.minute(), dt.day(), dt.month(), dt.year());
  241. } else {
  242. strcpy(reply, "(ERR: clock cannot go backwards)");
  243. }
  244. } else if (memcmp(command, "neighbors", 9) == 0) {
  245. _callbacks->formatNeighborsReply(reply);
  246. } else if (memcmp(command, "neighbor.remove ", 16) == 0) {
  247. const char* hex = &command[16];
  248. uint8_t pubkey[PUB_KEY_SIZE];
  249. int hex_len = min((int)strlen(hex), PUB_KEY_SIZE*2);
  250. int pubkey_len = hex_len / 2;
  251. if (mesh::Utils::fromHex(pubkey, pubkey_len, hex)) {
  252. _callbacks->removeNeighbor(pubkey, pubkey_len);
  253. strcpy(reply, "OK");
  254. } else {
  255. strcpy(reply, "ERR: bad pubkey");
  256. }
  257. } else if (memcmp(command, "tempradio ", 10) == 0) {
  258. strcpy(tmp, &command[10]);
  259. const char *parts[5];
  260. int num = mesh::Utils::parseTextParts(tmp, parts, 5);
  261. float freq = num > 0 ? strtof(parts[0], nullptr) : 0.0f;
  262. float bw = num > 1 ? strtof(parts[1], nullptr) : 0.0f;
  263. uint8_t sf = num > 2 ? atoi(parts[2]) : 0;
  264. uint8_t cr = num > 3 ? atoi(parts[3]) : 0;
  265. int temp_timeout_mins = num > 4 ? atoi(parts[4]) : 0;
  266. if (freq >= 150.0f && freq <= 2500.0f && sf >= 5 && sf <= 12 && cr >= 5 && cr <= 8 && bw >= 7.0f && bw <= 500.0f && temp_timeout_mins > 0) {
  267. _callbacks->applyTempRadioParams(freq, bw, sf, cr, temp_timeout_mins);
  268. sprintf(reply, "OK - temp params for %d mins", temp_timeout_mins);
  269. } else {
  270. strcpy(reply, "Error, invalid params");
  271. }
  272. } else if (memcmp(command, "password ", 9) == 0) {
  273. // change admin password
  274. StrHelper::strncpy(_prefs->password, &command[9], sizeof(_prefs->password));
  275. savePrefs();
  276. sprintf(reply, "password now: ");
  277. StrHelper::strncpy(&reply[14], _prefs->password, 160-15); // echo back just to let admin know for sure!!
  278. } else if (memcmp(command, "clear stats", 11) == 0) {
  279. _callbacks->clearStats();
  280. strcpy(reply, "(OK - stats reset)");
  281. } else if (memcmp(command, "get ", 4) == 0) {
  282. handleGetCmd(sender_timestamp, command, reply);
  283. } else if (memcmp(command, "set ", 4) == 0) {
  284. handleSetCmd(sender_timestamp, command, reply);
  285. } else if (sender_timestamp == 0 && strcmp(command, "erase") == 0) {
  286. bool s = _callbacks->formatFileSystem();
  287. sprintf(reply, "File system erase: %s", s ? "OK" : "Err");
  288. } else if (memcmp(command, "ver", 3) == 0) {
  289. sprintf(reply, "%s (Build: %s)", _callbacks->getFirmwareVer(), _callbacks->getBuildDate());
  290. } else if (memcmp(command, "board", 5) == 0) {
  291. sprintf(reply, "%s", _board->getManufacturerName());
  292. } else if (memcmp(command, "sensor get ", 11) == 0) {
  293. const char* key = command + 11;
  294. const char* val = _sensors->getSettingByKey(key);
  295. if (val != NULL) {
  296. sprintf(reply, "> %s", val);
  297. } else {
  298. strcpy(reply, "null");
  299. }
  300. } else if (memcmp(command, "sensor set ", 11) == 0) {
  301. strcpy(tmp, &command[11]);
  302. const char *parts[2];
  303. int num = mesh::Utils::parseTextParts(tmp, parts, 2, ' ');
  304. const char *key = (num > 0) ? parts[0] : "";
  305. const char *value = (num > 1) ? parts[1] : "null";
  306. if (_sensors->setSettingValue(key, value)) {
  307. strcpy(reply, "ok");
  308. } else {
  309. strcpy(reply, "can't find custom var");
  310. }
  311. } else if (memcmp(command, "sensor list", 11) == 0) {
  312. char* dp = reply;
  313. int start = 0;
  314. int end = _sensors->getNumSettings();
  315. if (strlen(command) > 11) {
  316. start = _atoi(command+12);
  317. }
  318. if (start >= end) {
  319. strcpy(reply, "no custom var");
  320. } else {
  321. sprintf(dp, "%d vars\n", end);
  322. dp = strchr(dp, 0);
  323. int i;
  324. for (i = start; i < end && (dp-reply < 134); i++) {
  325. sprintf(dp, "%s=%s\n",
  326. _sensors->getSettingName(i),
  327. _sensors->getSettingValue(i));
  328. dp = strchr(dp, 0);
  329. }
  330. if (i < end) {
  331. sprintf(dp, "... next:%d", i);
  332. } else {
  333. *(dp-1) = 0; // remove last CR
  334. }
  335. }
  336. } else if (memcmp(command, "region", 6) == 0) {
  337. handleRegionCmd(command, reply);
  338. #if ENV_INCLUDE_GPS == 1
  339. } else if (memcmp(command, "gps on", 6) == 0) {
  340. if (_sensors->setSettingValue("gps", "1")) {
  341. _prefs->gps_enabled = 1;
  342. savePrefs();
  343. strcpy(reply, "ok");
  344. } else {
  345. strcpy(reply, "gps toggle not found");
  346. }
  347. } else if (memcmp(command, "gps off", 7) == 0) {
  348. if (_sensors->setSettingValue("gps", "0")) {
  349. _prefs->gps_enabled = 0;
  350. savePrefs();
  351. strcpy(reply, "ok");
  352. } else {
  353. strcpy(reply, "gps toggle not found");
  354. }
  355. } else if (memcmp(command, "gps sync", 8) == 0) {
  356. LocationProvider * l = _sensors->getLocationProvider();
  357. if (l != NULL) {
  358. l->syncTime();
  359. strcpy(reply, "ok");
  360. } else {
  361. strcpy(reply, "gps provider not found");
  362. }
  363. } else if (memcmp(command, "gps setloc", 10) == 0) {
  364. _prefs->node_lat = _sensors->node_lat;
  365. _prefs->node_lon = _sensors->node_lon;
  366. savePrefs();
  367. strcpy(reply, "ok");
  368. } else if (memcmp(command, "gps advert", 10) == 0) {
  369. if (strlen(command) == 10) {
  370. switch (_prefs->advert_loc_policy) {
  371. case ADVERT_LOC_NONE:
  372. strcpy(reply, "> none");
  373. break;
  374. case ADVERT_LOC_PREFS:
  375. strcpy(reply, "> prefs");
  376. break;
  377. case ADVERT_LOC_SHARE:
  378. strcpy(reply, "> share");
  379. break;
  380. default:
  381. strcpy(reply, "error");
  382. }
  383. } else if (memcmp(command+11, "none", 4) == 0) {
  384. _prefs->advert_loc_policy = ADVERT_LOC_NONE;
  385. savePrefs();
  386. strcpy(reply, "ok");
  387. } else if (memcmp(command+11, "share", 5) == 0) {
  388. _prefs->advert_loc_policy = ADVERT_LOC_SHARE;
  389. savePrefs();
  390. strcpy(reply, "ok");
  391. } else if (memcmp(command+11, "prefs", 5) == 0) {
  392. _prefs->advert_loc_policy = ADVERT_LOC_PREFS;
  393. savePrefs();
  394. strcpy(reply, "ok");
  395. } else {
  396. strcpy(reply, "error");
  397. }
  398. } else if (memcmp(command, "gps", 3) == 0) {
  399. LocationProvider * l = _sensors->getLocationProvider();
  400. if (l != NULL) {
  401. bool enabled = l->isEnabled(); // is EN pin on ?
  402. bool fix = l->isValid(); // has fix ?
  403. int sats = l->satellitesCount();
  404. bool active = !strcmp(_sensors->getSettingByKey("gps"), "1");
  405. if (enabled) {
  406. sprintf(reply, "on, %s, %s, %d sats",
  407. active?"active":"deactivated",
  408. fix?"fix":"no fix",
  409. sats);
  410. } else {
  411. strcpy(reply, "off");
  412. }
  413. } else {
  414. strcpy(reply, "Can't find GPS");
  415. }
  416. #endif
  417. } else if (memcmp(command, "powersaving on", 14) == 0) {
  418. _prefs->powersaving_enabled = 1;
  419. savePrefs();
  420. strcpy(reply, "ok"); // TODO: to return Not supported if required
  421. } else if (memcmp(command, "powersaving off", 15) == 0) {
  422. _prefs->powersaving_enabled = 0;
  423. savePrefs();
  424. strcpy(reply, "ok");
  425. } else if (memcmp(command, "powersaving", 11) == 0) {
  426. if (_prefs->powersaving_enabled) {
  427. strcpy(reply, "on");
  428. } else {
  429. strcpy(reply, "off");
  430. }
  431. } else if (memcmp(command, "log start", 9) == 0) {
  432. _callbacks->setLoggingOn(true);
  433. strcpy(reply, " logging on");
  434. } else if (memcmp(command, "log stop", 8) == 0) {
  435. _callbacks->setLoggingOn(false);
  436. strcpy(reply, " logging off");
  437. } else if (memcmp(command, "log erase", 9) == 0) {
  438. _callbacks->eraseLogFile();
  439. strcpy(reply, " log erased");
  440. } else if (sender_timestamp == 0 && memcmp(command, "log", 3) == 0) {
  441. _callbacks->dumpLogFile();
  442. strcpy(reply, " EOF");
  443. } else if (sender_timestamp == 0 && memcmp(command, "stats-packets", 13) == 0 && (command[13] == 0 || command[13] == ' ')) {
  444. _callbacks->formatPacketStatsReply(reply);
  445. } else if (sender_timestamp == 0 && memcmp(command, "stats-radio", 11) == 0 && (command[11] == 0 || command[11] == ' ')) {
  446. _callbacks->formatRadioStatsReply(reply);
  447. } else if (sender_timestamp == 0 && memcmp(command, "stats-core", 10) == 0 && (command[10] == 0 || command[10] == ' ')) {
  448. _callbacks->formatStatsReply(reply);
  449. } else {
  450. strcpy(reply, "Unknown command");
  451. }
  452. }
  453. void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* reply) {
  454. const char* config = &command[4];
  455. if (memcmp(config, "dutycycle ", 10) == 0) {
  456. float dc = atof(&config[10]);
  457. if (dc < 1 || dc > 100) {
  458. strcpy(reply, "ERROR: dutycycle must be 1-100");
  459. } else {
  460. _prefs->airtime_factor = (100.0f / dc) - 1.0f;
  461. savePrefs();
  462. float actual = 100.0f / (_prefs->airtime_factor + 1.0f);
  463. int a_int = (int)actual;
  464. int a_frac = (int)((actual - a_int) * 10.0f + 0.5f);
  465. sprintf(reply, "OK - %d.%d%%", a_int, a_frac);
  466. }
  467. } else if (memcmp(config, "af ", 3) == 0) {
  468. _prefs->airtime_factor = atof(&config[3]);
  469. savePrefs();
  470. strcpy(reply, "OK");
  471. } else if (memcmp(config, "int.thresh ", 11) == 0) {
  472. _prefs->interference_threshold = atoi(&config[11]);
  473. savePrefs();
  474. strcpy(reply, "OK");
  475. } else if (memcmp(config, "agc.reset.interval ", 19) == 0) {
  476. _prefs->agc_reset_interval = atoi(&config[19]) / 4;
  477. savePrefs();
  478. sprintf(reply, "OK - interval rounded to %d", ((uint32_t) _prefs->agc_reset_interval) * 4);
  479. } else if (memcmp(config, "multi.acks ", 11) == 0) {
  480. _prefs->multi_acks = atoi(&config[11]);
  481. savePrefs();
  482. strcpy(reply, "OK");
  483. } else if (memcmp(config, "allow.read.only ", 16) == 0) {
  484. _prefs->allow_read_only = memcmp(&config[16], "on", 2) == 0;
  485. savePrefs();
  486. strcpy(reply, "OK");
  487. } else if (memcmp(config, "flood.advert.interval ", 22) == 0) {
  488. int hours = _atoi(&config[22]);
  489. if ((hours > 0 && hours < 3) || (hours > 168)) {
  490. strcpy(reply, "Error: interval range is 3-168 hours");
  491. } else {
  492. _prefs->flood_advert_interval = (uint8_t)(hours);
  493. _callbacks->updateFloodAdvertTimer();
  494. savePrefs();
  495. strcpy(reply, "OK");
  496. }
  497. } else if (memcmp(config, "advert.interval ", 16) == 0) {
  498. int mins = _atoi(&config[16]);
  499. if ((mins > 0 && mins < MIN_LOCAL_ADVERT_INTERVAL) || (mins > 240)) {
  500. sprintf(reply, "Error: interval range is %d-240 minutes", MIN_LOCAL_ADVERT_INTERVAL);
  501. } else {
  502. _prefs->advert_interval = (uint8_t)(mins / 2);
  503. _callbacks->updateAdvertTimer();
  504. savePrefs();
  505. strcpy(reply, "OK");
  506. }
  507. } else if (memcmp(config, "guest.password ", 15) == 0) {
  508. StrHelper::strncpy(_prefs->guest_password, &config[15], sizeof(_prefs->guest_password));
  509. savePrefs();
  510. strcpy(reply, "OK");
  511. } else if (memcmp(config, "prv.key ", 8) == 0) {
  512. uint8_t prv_key[PRV_KEY_SIZE];
  513. bool success = mesh::Utils::fromHex(prv_key, PRV_KEY_SIZE, &config[8]);
  514. // only allow rekey if key is valid
  515. if (success && mesh::LocalIdentity::validatePrivateKey(prv_key)) {
  516. mesh::LocalIdentity new_id;
  517. new_id.readFrom(prv_key, PRV_KEY_SIZE);
  518. _callbacks->saveIdentity(new_id);
  519. strcpy(reply, "OK, reboot to apply! New pubkey: ");
  520. mesh::Utils::toHex(&reply[33], new_id.pub_key, PUB_KEY_SIZE);
  521. } else {
  522. strcpy(reply, "Error, bad key");
  523. }
  524. } else if (memcmp(config, "name ", 5) == 0) {
  525. if (isValidName(&config[5])) {
  526. StrHelper::strncpy(_prefs->node_name, &config[5], sizeof(_prefs->node_name));
  527. savePrefs();
  528. strcpy(reply, "OK");
  529. } else {
  530. strcpy(reply, "Error, bad chars");
  531. }
  532. } else if (memcmp(config, "repeat ", 7) == 0) {
  533. _prefs->disable_fwd = memcmp(&config[7], "off", 3) == 0;
  534. savePrefs();
  535. strcpy(reply, _prefs->disable_fwd ? "OK - repeat is now OFF" : "OK - repeat is now ON");
  536. #if defined(USE_SX1262) || defined(USE_SX1268) || defined(USE_LR1110)
  537. } else if (memcmp(config, "radio.rxgain ", 13) == 0) {
  538. _prefs->rx_boosted_gain = memcmp(&config[13], "on", 2) == 0;
  539. strcpy(reply, "OK");
  540. savePrefs();
  541. _callbacks->setRxBoostedGain(_prefs->rx_boosted_gain);
  542. #endif
  543. } else if (memcmp(config, "radio ", 6) == 0) {
  544. strcpy(tmp, &config[6]);
  545. const char *parts[4];
  546. int num = mesh::Utils::parseTextParts(tmp, parts, 4);
  547. float freq = num > 0 ? strtof(parts[0], nullptr) : 0.0f;
  548. float bw = num > 1 ? strtof(parts[1], nullptr) : 0.0f;
  549. uint8_t sf = num > 2 ? atoi(parts[2]) : 0;
  550. uint8_t cr = num > 3 ? atoi(parts[3]) : 0;
  551. if (freq >= 150.0f && freq <= 2500.0f && sf >= 5 && sf <= 12 && cr >= 5 && cr <= 8 && bw >= 7.0f && bw <= 500.0f) {
  552. _prefs->sf = sf;
  553. _prefs->cr = cr;
  554. _prefs->freq = freq;
  555. _prefs->bw = bw;
  556. _callbacks->savePrefs();
  557. strcpy(reply, "OK - reboot to apply");
  558. } else {
  559. strcpy(reply, "Error, invalid radio params");
  560. }
  561. } else if (memcmp(config, "lat ", 4) == 0) {
  562. _prefs->node_lat = atof(&config[4]);
  563. savePrefs();
  564. strcpy(reply, "OK");
  565. } else if (memcmp(config, "lon ", 4) == 0) {
  566. _prefs->node_lon = atof(&config[4]);
  567. savePrefs();
  568. strcpy(reply, "OK");
  569. } else if (memcmp(config, "rxdelay ", 8) == 0) {
  570. float db = atof(&config[8]);
  571. if (db >= 0 && db <= 20.0f) {
  572. _prefs->rx_delay_base = db;
  573. savePrefs();
  574. strcpy(reply, "OK");
  575. } else {
  576. strcpy(reply, "Error, must be 0-20");
  577. }
  578. } else if (memcmp(config, "txdelay ", 8) == 0) {
  579. float f = atof(&config[8]);
  580. if (f >= 0 && f <= 2.0f) {
  581. _prefs->tx_delay_factor = f;
  582. savePrefs();
  583. strcpy(reply, "OK");
  584. } else {
  585. strcpy(reply, "Error, must be 0-2");
  586. }
  587. } else if (memcmp(config, "flood.max ", 10) == 0) {
  588. uint8_t m = atoi(&config[10]);
  589. if (m <= 64) {
  590. _prefs->flood_max = m;
  591. savePrefs();
  592. strcpy(reply, "OK");
  593. } else {
  594. strcpy(reply, "Error, max 64");
  595. }
  596. } else if (memcmp(config, "flood.max.unscoped ", 19) == 0) {
  597. uint8_t m = atoi(&config[19]);
  598. if (m <= 64) {
  599. _prefs->flood_max_unscoped = m;
  600. savePrefs();
  601. strcpy(reply, "OK");
  602. } else {
  603. strcpy(reply, "Error, max 64");
  604. }
  605. } else if (memcmp(config, "direct.txdelay ", 15) == 0) {
  606. float f = atof(&config[15]);
  607. if (f >= 0 && f <= 2.0f) {
  608. _prefs->direct_tx_delay_factor = f;
  609. savePrefs();
  610. strcpy(reply, "OK");
  611. } else {
  612. strcpy(reply, "Error, must be 0-2");
  613. }
  614. } else if (memcmp(config, "owner.info ", 11) == 0) {
  615. config += 11;
  616. char *dp = _prefs->owner_info;
  617. while (*config && dp - _prefs->owner_info < sizeof(_prefs->owner_info)-1) {
  618. *dp++ = (*config == '|') ? '\n' : *config; // translate '|' to newline chars
  619. config++;
  620. }
  621. *dp = 0;
  622. savePrefs();
  623. strcpy(reply, "OK");
  624. } else if (memcmp(config, "path.hash.mode ", 15) == 0) {
  625. config += 15;
  626. uint8_t mode = atoi(config);
  627. if (mode < 3) {
  628. _prefs->path_hash_mode = mode;
  629. savePrefs();
  630. strcpy(reply, "OK");
  631. } else {
  632. strcpy(reply, "Error, must be 0,1, or 2");
  633. }
  634. } else if (memcmp(config, "loop.detect ", 12) == 0) {
  635. config += 12;
  636. uint8_t mode;
  637. if (memcmp(config, "off", 3) == 0) {
  638. mode = LOOP_DETECT_OFF;
  639. } else if (memcmp(config, "minimal", 7) == 0) {
  640. mode = LOOP_DETECT_MINIMAL;
  641. } else if (memcmp(config, "moderate", 8) == 0) {
  642. mode = LOOP_DETECT_MODERATE;
  643. } else if (memcmp(config, "strict", 6) == 0) {
  644. mode = LOOP_DETECT_STRICT;
  645. } else {
  646. mode = 0xFF;
  647. strcpy(reply, "Error, must be: off, minimal, moderate, or strict");
  648. }
  649. if (mode != 0xFF) {
  650. _prefs->loop_detect = mode;
  651. savePrefs();
  652. strcpy(reply, "OK");
  653. }
  654. } else if (memcmp(config, "tx ", 3) == 0) {
  655. _prefs->tx_power_dbm = atoi(&config[3]);
  656. savePrefs();
  657. _callbacks->setTxPower(_prefs->tx_power_dbm);
  658. strcpy(reply, "OK");
  659. } else if (sender_timestamp == 0 && memcmp(config, "freq ", 5) == 0) {
  660. _prefs->freq = atof(&config[5]);
  661. savePrefs();
  662. strcpy(reply, "OK - reboot to apply");
  663. #ifdef WITH_BRIDGE
  664. } else if (memcmp(config, "bridge.enabled ", 15) == 0) {
  665. _prefs->bridge_enabled = memcmp(&config[15], "on", 2) == 0;
  666. _callbacks->setBridgeState(_prefs->bridge_enabled);
  667. savePrefs();
  668. strcpy(reply, "OK");
  669. } else if (memcmp(config, "bridge.delay ", 13) == 0) {
  670. int delay = _atoi(&config[13]);
  671. if (delay >= 0 && delay <= 10000) {
  672. _prefs->bridge_delay = (uint16_t)delay;
  673. savePrefs();
  674. strcpy(reply, "OK");
  675. } else {
  676. strcpy(reply, "Error: delay must be between 0-10000 ms");
  677. }
  678. } else if (memcmp(config, "bridge.source ", 14) == 0) {
  679. _prefs->bridge_pkt_src = memcmp(&config[14], "rx", 2) == 0;
  680. savePrefs();
  681. strcpy(reply, "OK");
  682. #endif
  683. #ifdef WITH_RS232_BRIDGE
  684. } else if (memcmp(config, "bridge.baud ", 12) == 0) {
  685. uint32_t baud = atoi(&config[12]);
  686. if (baud >= 9600 && baud <= BRIDGE_MAX_BAUD) {
  687. _prefs->bridge_baud = (uint32_t)baud;
  688. _callbacks->restartBridge();
  689. savePrefs();
  690. strcpy(reply, "OK");
  691. } else {
  692. sprintf(reply, "Error: baud rate must be between 9600-%d",BRIDGE_MAX_BAUD);
  693. }
  694. #endif
  695. #ifdef WITH_ESPNOW_BRIDGE
  696. } else if (memcmp(config, "bridge.channel ", 15) == 0) {
  697. int ch = atoi(&config[15]);
  698. if (ch > 0 && ch < 15) {
  699. _prefs->bridge_channel = (uint8_t)ch;
  700. _callbacks->restartBridge();
  701. savePrefs();
  702. strcpy(reply, "OK");
  703. } else {
  704. strcpy(reply, "Error: channel must be between 1-14");
  705. }
  706. } else if (memcmp(config, "bridge.secret ", 14) == 0) {
  707. StrHelper::strncpy(_prefs->bridge_secret, &config[14], sizeof(_prefs->bridge_secret));
  708. _callbacks->restartBridge();
  709. savePrefs();
  710. strcpy(reply, "OK");
  711. #endif
  712. } else if (memcmp(config, "adc.multiplier ", 15) == 0) {
  713. _prefs->adc_multiplier = atof(&config[15]);
  714. if (_board->setAdcMultiplier(_prefs->adc_multiplier)) {
  715. savePrefs();
  716. if (_prefs->adc_multiplier == 0.0f) {
  717. strcpy(reply, "OK - using default board multiplier");
  718. } else {
  719. sprintf(reply, "OK - multiplier set to %.3f", _prefs->adc_multiplier);
  720. }
  721. } else {
  722. _prefs->adc_multiplier = 0.0f;
  723. strcpy(reply, "Error: unsupported by this board");
  724. };
  725. } else {
  726. strcpy(reply, "unknown config: ");
  727. StrHelper::strncpy(&reply[16], config, 160-17);
  728. }
  729. }
  730. void CommonCLI::handleGetCmd(uint32_t sender_timestamp, char* command, char* reply) {
  731. const char* config = &command[4];
  732. if (memcmp(config, "dutycycle", 9) == 0) {
  733. float dc = 100.0f / (_prefs->airtime_factor + 1.0f);
  734. int dc_int = (int)dc;
  735. int dc_frac = (int)((dc - dc_int) * 10.0f + 0.5f);
  736. sprintf(reply, "> %d.%d%%", dc_int, dc_frac);
  737. } else if (memcmp(config, "af", 2) == 0) {
  738. sprintf(reply, "> %s", StrHelper::ftoa(_prefs->airtime_factor));
  739. } else if (memcmp(config, "int.thresh", 10) == 0) {
  740. sprintf(reply, "> %d", (uint32_t) _prefs->interference_threshold);
  741. } else if (memcmp(config, "agc.reset.interval", 18) == 0) {
  742. sprintf(reply, "> %d", ((uint32_t) _prefs->agc_reset_interval) * 4);
  743. } else if (memcmp(config, "multi.acks", 10) == 0) {
  744. sprintf(reply, "> %d", (uint32_t) _prefs->multi_acks);
  745. } else if (memcmp(config, "allow.read.only", 15) == 0) {
  746. sprintf(reply, "> %s", _prefs->allow_read_only ? "on" : "off");
  747. } else if (memcmp(config, "flood.advert.interval", 21) == 0) {
  748. sprintf(reply, "> %d", ((uint32_t) _prefs->flood_advert_interval));
  749. } else if (memcmp(config, "advert.interval", 15) == 0) {
  750. sprintf(reply, "> %d", ((uint32_t) _prefs->advert_interval) * 2);
  751. } else if (memcmp(config, "guest.password", 14) == 0) {
  752. sprintf(reply, "> %s", _prefs->guest_password);
  753. } else if (sender_timestamp == 0 && memcmp(config, "prv.key", 7) == 0) { // from serial command line only
  754. uint8_t prv_key[PRV_KEY_SIZE];
  755. int len = _callbacks->getSelfId().writeTo(prv_key, PRV_KEY_SIZE);
  756. mesh::Utils::toHex(tmp, prv_key, len);
  757. sprintf(reply, "> %s", tmp);
  758. } else if (memcmp(config, "name", 4) == 0) {
  759. sprintf(reply, "> %s", _prefs->node_name);
  760. } else if (memcmp(config, "repeat", 6) == 0) {
  761. sprintf(reply, "> %s", _prefs->disable_fwd ? "off" : "on");
  762. } else if (memcmp(config, "lat", 3) == 0) {
  763. sprintf(reply, "> %s", StrHelper::ftoa(_prefs->node_lat));
  764. } else if (memcmp(config, "lon", 3) == 0) {
  765. sprintf(reply, "> %s", StrHelper::ftoa(_prefs->node_lon));
  766. #if defined(USE_SX1262) || defined(USE_SX1268) || defined(USE_LR1110)
  767. } else if (memcmp(config, "radio.rxgain", 12) == 0) {
  768. sprintf(reply, "> %s", _prefs->rx_boosted_gain ? "on" : "off");
  769. #endif
  770. } else if (memcmp(config, "radio", 5) == 0) {
  771. char freq[16], bw[16];
  772. strcpy(freq, StrHelper::ftoa(_prefs->freq));
  773. strcpy(bw, StrHelper::ftoa3(_prefs->bw));
  774. sprintf(reply, "> %s,%s,%d,%d", freq, bw, (uint32_t)_prefs->sf, (uint32_t)_prefs->cr);
  775. } else if (memcmp(config, "rxdelay", 7) == 0) {
  776. sprintf(reply, "> %s", StrHelper::ftoa(_prefs->rx_delay_base));
  777. } else if (memcmp(config, "txdelay", 7) == 0) {
  778. sprintf(reply, "> %s", StrHelper::ftoa(_prefs->tx_delay_factor));
  779. } else if (memcmp(config, "flood.max.unscoped", 18) == 0) {
  780. if (_prefs->flood_max_unscoped == FLOOD_MAX_UNSCOPED_UNSET) {
  781. sprintf(reply, "> default (tracks flood.max=%u)", (unsigned)_prefs->flood_max);
  782. } else {
  783. sprintf(reply, "> %d", (uint32_t)_prefs->flood_max_unscoped);
  784. }
  785. } else if (memcmp(config, "flood.max", 9) == 0) {
  786. sprintf(reply, "> %d", (uint32_t)_prefs->flood_max);
  787. } else if (memcmp(config, "direct.txdelay", 14) == 0) {
  788. sprintf(reply, "> %s", StrHelper::ftoa(_prefs->direct_tx_delay_factor));
  789. } else if (memcmp(config, "owner.info", 10) == 0) {
  790. auto start = reply;
  791. *reply++ = '>';
  792. *reply++ = ' ';
  793. const char* sp = _prefs->owner_info;
  794. while (*sp && reply - start < 159) {
  795. *reply++ = (*sp == '\n') ? '|' : *sp; // translate newline back to orig '|'
  796. sp++;
  797. }
  798. *reply = 0; // set null terminator
  799. } else if (memcmp(config, "path.hash.mode", 14) == 0) {
  800. sprintf(reply, "> %d", (uint32_t)_prefs->path_hash_mode);
  801. } else if (memcmp(config, "loop.detect", 11) == 0) {
  802. if (_prefs->loop_detect == LOOP_DETECT_OFF) {
  803. strcpy(reply, "> off");
  804. } else if (_prefs->loop_detect == LOOP_DETECT_MINIMAL) {
  805. strcpy(reply, "> minimal");
  806. } else if (_prefs->loop_detect == LOOP_DETECT_MODERATE) {
  807. strcpy(reply, "> moderate");
  808. } else {
  809. strcpy(reply, "> strict");
  810. }
  811. } else if (memcmp(config, "tx", 2) == 0 && (config[2] == 0 || config[2] == ' ')) {
  812. sprintf(reply, "> %d", (int32_t) _prefs->tx_power_dbm);
  813. } else if (memcmp(config, "freq", 4) == 0) {
  814. sprintf(reply, "> %s", StrHelper::ftoa(_prefs->freq));
  815. } else if (memcmp(config, "public.key", 10) == 0) {
  816. strcpy(reply, "> ");
  817. mesh::Utils::toHex(&reply[2], _callbacks->getSelfId().pub_key, PUB_KEY_SIZE);
  818. } else if (memcmp(config, "role", 4) == 0) {
  819. sprintf(reply, "> %s", _callbacks->getRole());
  820. } else if (memcmp(config, "bridge.type", 11) == 0) {
  821. sprintf(reply, "> %s",
  822. #ifdef WITH_RS232_BRIDGE
  823. "rs232"
  824. #elif WITH_ESPNOW_BRIDGE
  825. "espnow"
  826. #else
  827. "none"
  828. #endif
  829. );
  830. #ifdef WITH_BRIDGE
  831. } else if (memcmp(config, "bridge.enabled", 14) == 0) {
  832. sprintf(reply, "> %s", _prefs->bridge_enabled ? "on" : "off");
  833. } else if (memcmp(config, "bridge.delay", 12) == 0) {
  834. sprintf(reply, "> %d", (uint32_t)_prefs->bridge_delay);
  835. } else if (memcmp(config, "bridge.source", 13) == 0) {
  836. sprintf(reply, "> %s", _prefs->bridge_pkt_src ? "logRx" : "logTx");
  837. #endif
  838. #ifdef WITH_RS232_BRIDGE
  839. } else if (memcmp(config, "bridge.baud", 11) == 0) {
  840. sprintf(reply, "> %d", (uint32_t)_prefs->bridge_baud);
  841. #endif
  842. #ifdef WITH_ESPNOW_BRIDGE
  843. } else if (memcmp(config, "bridge.channel", 14) == 0) {
  844. sprintf(reply, "> %d", (uint32_t)_prefs->bridge_channel);
  845. } else if (memcmp(config, "bridge.secret", 13) == 0) {
  846. sprintf(reply, "> %s", _prefs->bridge_secret);
  847. #endif
  848. } else if (memcmp(config, "bootloader.ver", 14) == 0) {
  849. #ifdef NRF52_PLATFORM
  850. char ver[32];
  851. if (_board->getBootloaderVersion(ver, sizeof(ver))) {
  852. sprintf(reply, "> %s", ver);
  853. } else {
  854. strcpy(reply, "> unknown");
  855. }
  856. #else
  857. strcpy(reply, "ERROR: unsupported");
  858. #endif
  859. } else if (memcmp(config, "adc.multiplier", 14) == 0) {
  860. float adc_mult = _board->getAdcMultiplier();
  861. if (adc_mult == 0.0f) {
  862. strcpy(reply, "Error: unsupported by this board");
  863. } else {
  864. sprintf(reply, "> %.3f", adc_mult);
  865. }
  866. // Power management commands
  867. } else if (memcmp(config, "pwrmgt.support", 14) == 0) {
  868. #ifdef NRF52_POWER_MANAGEMENT
  869. strcpy(reply, "> supported");
  870. #else
  871. strcpy(reply, "> unsupported");
  872. #endif
  873. } else if (memcmp(config, "pwrmgt.source", 13) == 0) {
  874. #ifdef NRF52_POWER_MANAGEMENT
  875. strcpy(reply, _board->isExternalPowered() ? "> external" : "> battery");
  876. #else
  877. strcpy(reply, "ERROR: Power management not supported");
  878. #endif
  879. } else if (memcmp(config, "pwrmgt.bootreason", 17) == 0) {
  880. #ifdef NRF52_POWER_MANAGEMENT
  881. sprintf(reply, "> Reset: %s; Shutdown: %s",
  882. _board->getResetReasonString(_board->getResetReason()),
  883. _board->getShutdownReasonString(_board->getShutdownReason()));
  884. #else
  885. strcpy(reply, "ERROR: Power management not supported");
  886. #endif
  887. } else if (memcmp(config, "pwrmgt.bootmv", 13) == 0) {
  888. #ifdef NRF52_POWER_MANAGEMENT
  889. sprintf(reply, "> %u mV", _board->getBootVoltage());
  890. #else
  891. strcpy(reply, "ERROR: Power management not supported");
  892. #endif
  893. } else {
  894. sprintf(reply, "??: %s", config);
  895. }
  896. }
  897. static char* skipSpaces(char* s) {
  898. while (*s == ' ') s++;
  899. return s;
  900. }
  901. static void rtrimSpaces(char* s) {
  902. char* e = s + strlen(s);
  903. while (e > s && e[-1] == ' ') *--e = '\0';
  904. }
  905. static char* takeToken(char** cursor) {
  906. char* p = skipSpaces(*cursor);
  907. if (*p == '\0') { *cursor = p; return nullptr; }
  908. char* tok = p;
  909. while (*p && *p != ' ') p++;
  910. if (*p) *p++ = '\0';
  911. *cursor = p;
  912. return tok;
  913. }
  914. static char* splitNameJump(char* tok) {
  915. for (char* q = tok; *q; q++) {
  916. if (*q == '|' || *q == ',') {
  917. *q = '\0';
  918. char* jump = skipSpaces(q + 1);
  919. rtrimSpaces(jump);
  920. return jump;
  921. }
  922. }
  923. return nullptr;
  924. }
  925. static bool processRegionDefSegment(RegionMap* map, char* tok, RegionEntry** cursor, char* reply) {
  926. char* jump = splitNameJump(tok);
  927. char* name = skipSpaces(tok);
  928. if (*name == '\0') { snprintf(reply, 160, "Err - empty name"); return false; }
  929. if (jump && *jump == '\0') { snprintf(reply, 160, "Err - empty jump"); return false; }
  930. RegionEntry* r = map->putRegion(name, (*cursor)->id);
  931. if (r == NULL) { snprintf(reply, 160, "Err - put failed: %s", name); return false; }
  932. r->flags = 0;
  933. if (jump) {
  934. RegionEntry* j = map->findByNamePrefix(jump);
  935. if (j == NULL) { snprintf(reply, 160, "Err - unknown jump: %s", jump); return false; }
  936. *cursor = j;
  937. } else {
  938. *cursor = r;
  939. }
  940. return true;
  941. }
  942. void CommonCLI::handleRegionCmd(char* command, char* reply) {
  943. reply[0] = 0;
  944. // `region def`: must run before parseTextParts mutates the buffer
  945. char* cmd = skipSpaces(command);
  946. if (strncmp(cmd, "region def", 10) == 0 && (cmd[10] == ' ' || cmd[10] == '\0')) {
  947. char* payload = skipSpaces(cmd + 10);
  948. rtrimSpaces(payload);
  949. if (*payload == '\0') { snprintf(reply, 160, "Err - empty def"); return; }
  950. RegionEntry* cursor = &_region_map->getWildcard();
  951. for (char* tok; (tok = takeToken(&payload)) != nullptr; ) {
  952. if (!processRegionDefSegment(_region_map, tok, &cursor, reply)) return;
  953. }
  954. _region_map->exportTo(reply, 160);
  955. return;
  956. }
  957. const char* parts[4];
  958. int n = mesh::Utils::parseTextParts(command, parts, 4, ' ');
  959. if (n == 1) {
  960. _region_map->exportTo(reply, 160);
  961. } else if (n >= 2 && strcmp(parts[1], "load") == 0) {
  962. _callbacks->startRegionsLoad();
  963. } else if (n >= 2 && strcmp(parts[1], "save") == 0) {
  964. _prefs->discovery_mod_timestamp = getRTCClock()->getCurrentTime(); // this node is now 'modified' (for discovery info)
  965. savePrefs();
  966. bool success = _callbacks->saveRegions();
  967. strcpy(reply, success ? "OK" : "Err - save failed");
  968. } else if (n >= 3 && strcmp(parts[1], "allowf") == 0) {
  969. auto region = _region_map->findByNamePrefix(parts[2]);
  970. if (region) {
  971. region->flags &= ~REGION_DENY_FLOOD;
  972. strcpy(reply, "OK");
  973. } else {
  974. strcpy(reply, "Err - unknown region");
  975. }
  976. } else if (n >= 3 && strcmp(parts[1], "denyf") == 0) {
  977. auto region = _region_map->findByNamePrefix(parts[2]);
  978. if (region) {
  979. region->flags |= REGION_DENY_FLOOD;
  980. strcpy(reply, "OK");
  981. } else {
  982. strcpy(reply, "Err - unknown region");
  983. }
  984. } else if (n >= 3 && strcmp(parts[1], "get") == 0) {
  985. auto region = _region_map->findByNamePrefix(parts[2]);
  986. if (region) {
  987. auto parent = _region_map->findById(region->parent);
  988. if (parent && parent->id != 0) {
  989. sprintf(reply, " %s (%s) %s", region->name, parent->name, (region->flags & REGION_DENY_FLOOD) ? "" : "F");
  990. } else {
  991. sprintf(reply, " %s %s", region->name, (region->flags & REGION_DENY_FLOOD) ? "" : "F");
  992. }
  993. } else {
  994. strcpy(reply, "Err - unknown region");
  995. }
  996. } else if (n >= 3 && strcmp(parts[1], "home") == 0) {
  997. auto home = _region_map->findByNamePrefix(parts[2]);
  998. if (home) {
  999. _region_map->setHomeRegion(home);
  1000. sprintf(reply, " home is now %s", home->name);
  1001. } else {
  1002. strcpy(reply, "Err - unknown region");
  1003. }
  1004. } else if (n == 2 && strcmp(parts[1], "home") == 0) {
  1005. auto home = _region_map->getHomeRegion();
  1006. sprintf(reply, " home is %s", home ? home->name : "*");
  1007. } else if (n >= 3 && strcmp(parts[1], "default") == 0) {
  1008. if (strcmp(parts[2], "<null>") == 0) {
  1009. _region_map->setDefaultRegion(NULL);
  1010. _callbacks->onDefaultRegionChanged(NULL);
  1011. _callbacks->saveRegions(); // persist in one atomic step
  1012. sprintf(reply, " default scope is now <null>");
  1013. } else {
  1014. auto def = _region_map->findByNamePrefix(parts[2]);
  1015. if (def == NULL) {
  1016. def = _region_map->putRegion(parts[2], 0); // auto-create the default region
  1017. }
  1018. if (def) {
  1019. def->flags = 0; // make sure allow flood enabled
  1020. _region_map->setDefaultRegion(def);
  1021. _callbacks->onDefaultRegionChanged(def);
  1022. _callbacks->saveRegions(); // persist in one atomic step
  1023. sprintf(reply, " default scope is now %s", def->name);
  1024. } else {
  1025. strcpy(reply, "Err - region table full");
  1026. }
  1027. }
  1028. } else if (n == 2 && strcmp(parts[1], "default") == 0) {
  1029. auto def = _region_map->getDefaultRegion();
  1030. sprintf(reply, " default scope is %s", def ? def->name : "<null>");
  1031. } else if (n >= 3 && strcmp(parts[1], "put") == 0) {
  1032. auto parent = n >= 4 ? _region_map->findByNamePrefix(parts[3]) : &(_region_map->getWildcard());
  1033. if (parent == NULL) {
  1034. strcpy(reply, "Err - unknown parent");
  1035. } else {
  1036. auto region = _region_map->putRegion(parts[2], parent->id);
  1037. if (region == NULL) {
  1038. strcpy(reply, "Err - unable to put");
  1039. } else {
  1040. region->flags = 0; // New default: enable flood
  1041. strcpy(reply, "OK - (flood allowed)");
  1042. }
  1043. }
  1044. } else if (n >= 3 && strcmp(parts[1], "remove") == 0) {
  1045. auto region = _region_map->findByName(parts[2]);
  1046. if (region) {
  1047. if (_region_map->removeRegion(*region)) {
  1048. strcpy(reply, "OK");
  1049. } else {
  1050. strcpy(reply, "Err - not empty");
  1051. }
  1052. } else {
  1053. strcpy(reply, "Err - not found");
  1054. }
  1055. } else if (n >= 3 && strcmp(parts[1], "list") == 0) {
  1056. uint8_t mask = 0;
  1057. bool invert = false;
  1058. if (strcmp(parts[2], "allowed") == 0) {
  1059. mask = REGION_DENY_FLOOD;
  1060. invert = false; // list regions that DON'T have DENY flag
  1061. } else if (strcmp(parts[2], "denied") == 0) {
  1062. mask = REGION_DENY_FLOOD;
  1063. invert = true; // list regions that DO have DENY flag
  1064. } else {
  1065. strcpy(reply, "Err - use 'allowed' or 'denied'");
  1066. return;
  1067. }
  1068. int len = _region_map->exportNamesTo(reply, 160, mask, invert);
  1069. if (len == 0) {
  1070. strcpy(reply, "-none-");
  1071. }
  1072. } else {
  1073. strcpy(reply, "Err - ??");
  1074. }
  1075. }