CommonCLI.cpp 41 KB

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