Fork of the espurna firmware for `mhsw` switches
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

430 lines
12 KiB

  1. /*
  2. TERMINAL MODULE
  3. Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
  4. */
  5. #if TERMINAL_SUPPORT
  6. #include "utils.h"
  7. #include "libs/EmbedisWrap.h"
  8. #include "libs/StreamInjector.h"
  9. #include "libs/HeapStats.h"
  10. #include <vector>
  11. #include <Stream.h>
  12. StreamInjector _serial = StreamInjector(TERMINAL_BUFFER_SIZE);
  13. EmbedisWrap embedis(_serial, TERMINAL_BUFFER_SIZE);
  14. #if SERIAL_RX_ENABLED
  15. char _serial_rx_buffer[TERMINAL_BUFFER_SIZE];
  16. static unsigned char _serial_rx_pointer = 0;
  17. #endif // SERIAL_RX_ENABLED
  18. // -----------------------------------------------------------------------------
  19. // Commands
  20. // -----------------------------------------------------------------------------
  21. void _terminalHelpCommand() {
  22. // Get sorted list of commands
  23. std::vector<String> commands;
  24. unsigned char size = embedis.getCommandCount();
  25. for (unsigned int i=0; i<size; i++) {
  26. String command = embedis.getCommandName(i);
  27. bool inserted = false;
  28. for (unsigned char j=0; j<commands.size(); j++) {
  29. // Check if we have to insert it before the current element
  30. if (commands[j].compareTo(command) > 0) {
  31. commands.insert(commands.begin() + j, command);
  32. inserted = true;
  33. break;
  34. }
  35. }
  36. // If we could not insert it, just push it at the end
  37. if (!inserted) commands.push_back(command);
  38. }
  39. // Output the list
  40. DEBUG_MSG_P(PSTR("Available commands:\n"));
  41. for (unsigned char i=0; i<commands.size(); i++) {
  42. DEBUG_MSG_P(PSTR("> %s\n"), (commands[i]).c_str());
  43. }
  44. }
  45. void _terminalKeysCommand() {
  46. // Get sorted list of keys
  47. std::vector<String> keys = _settingsKeys();
  48. // Write key-values
  49. DEBUG_MSG_P(PSTR("Current settings:\n"));
  50. for (unsigned int i=0; i<keys.size(); i++) {
  51. String value = getSetting(keys[i]);
  52. DEBUG_MSG_P(PSTR("> %s => \"%s\"\n"), (keys[i]).c_str(), value.c_str());
  53. }
  54. unsigned long freeEEPROM = SPI_FLASH_SEC_SIZE - settingsSize();
  55. UNUSED(freeEEPROM);
  56. DEBUG_MSG_P(PSTR("Number of keys: %d\n"), keys.size());
  57. DEBUG_MSG_P(PSTR("Current EEPROM sector: %u\n"), EEPROMr.current());
  58. DEBUG_MSG_P(PSTR("Free EEPROM: %d bytes (%d%%)\n"), freeEEPROM, 100 * freeEEPROM / SPI_FLASH_SEC_SIZE);
  59. }
  60. #if LWIP_VERSION_MAJOR != 1
  61. // not yet CONNECTING or LISTENING
  62. extern struct tcp_pcb *tcp_bound_pcbs;
  63. // accepting or sending data
  64. extern struct tcp_pcb *tcp_active_pcbs;
  65. // // TIME-WAIT status
  66. extern struct tcp_pcb *tcp_tw_pcbs;
  67. String _terminalPcbStateToString(const unsigned char state) {
  68. switch (state) {
  69. case 0: return F("CLOSED");
  70. case 1: return F("LISTEN");
  71. case 2: return F("SYN_SENT");
  72. case 3: return F("SYN_RCVD");
  73. case 4: return F("ESTABLISHED");
  74. case 5: return F("FIN_WAIT_1");
  75. case 6: return F("FIN_WAIT_2");
  76. case 7: return F("CLOSE_WAIT");
  77. case 8: return F("CLOSING");
  78. case 9: return F("LAST_ACK");
  79. case 10: return F("TIME_WAIT");
  80. default: return String(int(state));
  81. };
  82. }
  83. void _terminalPrintTcpPcb(tcp_pcb* pcb) {
  84. char remote_ip[32] = {0};
  85. char local_ip[32] = {0};
  86. inet_ntoa_r((pcb->local_ip), local_ip, sizeof(local_ip));
  87. inet_ntoa_r((pcb->remote_ip), remote_ip, sizeof(remote_ip));
  88. DEBUG_MSG_P(PSTR("state=%s local=%s:%u remote=%s:%u snd_queuelen=%u lastack=%u send_wnd=%u rto=%u\n"),
  89. _terminalPcbStateToString(pcb->state).c_str(),
  90. local_ip, pcb->local_port,
  91. remote_ip, pcb->remote_port,
  92. pcb->snd_queuelen, pcb->lastack,
  93. pcb->snd_wnd, pcb->rto
  94. );
  95. }
  96. void _terminalPrintTcpPcbs() {
  97. tcp_pcb *pcb;
  98. //DEBUG_MSG_P(PSTR("Active PCB states:\n"));
  99. for (pcb = tcp_active_pcbs; pcb != NULL; pcb = pcb->next) {
  100. _terminalPrintTcpPcb(pcb);
  101. }
  102. //DEBUG_MSG_P(PSTR("TIME-WAIT PCB states:\n"));
  103. for (pcb = tcp_tw_pcbs; pcb != NULL; pcb = pcb->next) {
  104. _terminalPrintTcpPcb(pcb);
  105. }
  106. //DEBUG_MSG_P(PSTR("BOUND PCB states:\n"));
  107. for (pcb = tcp_bound_pcbs; pcb != NULL; pcb = pcb->next) {
  108. _terminalPrintTcpPcb(pcb);
  109. }
  110. }
  111. void _terminalPrintDnsResult(const char* name, const ip_addr_t* address) {
  112. // TODO fix asynctcp building with lwip-ipv6
  113. /*
  114. #if LWIP_IPV6
  115. if (IP_IS_V6(address)) {
  116. DEBUG_MSG_P(PSTR("[DNS] %s has IPV6 address %s\n"), name, ip6addr_ntoa(ip_2_ip6(address)));
  117. }
  118. #endif
  119. */
  120. DEBUG_MSG_P(PSTR("[DNS] %s has address %s\n"), name, ipaddr_ntoa(address));
  121. }
  122. void _terminalDnsFound(const char* name, const ip_addr_t* result, void*) {
  123. if (!result) {
  124. DEBUG_MSG_P(PSTR("[DNS] %s not found\n"), name);
  125. return;
  126. }
  127. _terminalPrintDnsResult(name, result);
  128. }
  129. #endif // LWIP_VERSION_MAJOR != 1
  130. void _terminalInitCommand() {
  131. terminalRegisterCommand(F("COMMANDS"), [](Embedis* e) {
  132. _terminalHelpCommand();
  133. terminalOK();
  134. });
  135. terminalRegisterCommand(F("ERASE.CONFIG"), [](Embedis* e) {
  136. terminalOK();
  137. customResetReason(CUSTOM_RESET_TERMINAL);
  138. eraseSDKConfig();
  139. *((int*) 0) = 0; // see https://github.com/esp8266/Arduino/issues/1494
  140. });
  141. terminalRegisterCommand(F("FACTORY.RESET"), [](Embedis* e) {
  142. resetSettings();
  143. terminalOK();
  144. });
  145. terminalRegisterCommand(F("GPIO"), [](Embedis* e) {
  146. int pin = -1;
  147. if (e->argc < 2) {
  148. DEBUG_MSG("Printing all GPIO pins:\n");
  149. } else {
  150. pin = String(e->argv[1]).toInt();
  151. if (!gpioValid(pin)) {
  152. terminalError(F("Invalid GPIO pin"));
  153. return;
  154. }
  155. if (e->argc > 2) {
  156. bool state = String(e->argv[2]).toInt() == 1;
  157. digitalWrite(pin, state);
  158. }
  159. }
  160. for (int i = 0; i <= 15; i++) {
  161. if (gpioValid(i) && (pin == -1 || pin == i)) {
  162. DEBUG_MSG_P(PSTR("GPIO %s pin %d is %s\n"), GPEP(i) ? "output" : "input", i, digitalRead(i) == HIGH ? "HIGH" : "LOW");
  163. }
  164. }
  165. terminalOK();
  166. });
  167. terminalRegisterCommand(F("HEAP"), [](Embedis* e) {
  168. infoHeapStats();
  169. terminalOK();
  170. });
  171. terminalRegisterCommand(F("STACK"), [](Embedis* e) {
  172. infoMemory("Stack", CONT_STACKSIZE, getFreeStack());
  173. terminalOK();
  174. });
  175. terminalRegisterCommand(F("HELP"), [](Embedis* e) {
  176. _terminalHelpCommand();
  177. terminalOK();
  178. });
  179. terminalRegisterCommand(F("INFO"), [](Embedis* e) {
  180. info();
  181. terminalOK();
  182. });
  183. terminalRegisterCommand(F("KEYS"), [](Embedis* e) {
  184. _terminalKeysCommand();
  185. terminalOK();
  186. });
  187. terminalRegisterCommand(F("GET"), [](Embedis* e) {
  188. if (e->argc < 2) {
  189. terminalError(F("Wrong arguments"));
  190. return;
  191. }
  192. for (unsigned char i = 1; i < e->argc; i++) {
  193. String key = String(e->argv[i]);
  194. String value;
  195. if (!Embedis::get(key, value)) {
  196. DEBUG_MSG_P(PSTR("> %s =>\n"), key.c_str());
  197. continue;
  198. }
  199. DEBUG_MSG_P(PSTR("> %s => \"%s\"\n"), key.c_str(), value.c_str());
  200. }
  201. terminalOK();
  202. });
  203. terminalRegisterCommand(F("RELOAD"), [](Embedis* e) {
  204. espurnaReload();
  205. terminalOK();
  206. });
  207. terminalRegisterCommand(F("RESET"), [](Embedis* e) {
  208. terminalOK();
  209. deferredReset(100, CUSTOM_RESET_TERMINAL);
  210. });
  211. terminalRegisterCommand(F("RESET.SAFE"), [](Embedis* e) {
  212. systemStabilityCounter(SYSTEM_CHECK_MAX);
  213. terminalOK();
  214. deferredReset(100, CUSTOM_RESET_TERMINAL);
  215. });
  216. terminalRegisterCommand(F("UPTIME"), [](Embedis* e) {
  217. DEBUG_MSG_P(PSTR("Uptime: %d seconds\n"), getUptime());
  218. terminalOK();
  219. });
  220. terminalRegisterCommand(F("CONFIG"), [](Embedis* e) {
  221. DynamicJsonBuffer jsonBuffer(1024);
  222. JsonObject& root = jsonBuffer.createObject();
  223. settingsGetJson(root);
  224. // XXX: replace with streaming
  225. String output;
  226. root.printTo(output);
  227. DEBUG_MSG(output.c_str());
  228. });
  229. #if not SETTINGS_AUTOSAVE
  230. terminalRegisterCommand(F("SAVE"), [](Embedis* e) {
  231. eepromCommit();
  232. terminalOK();
  233. });
  234. #endif
  235. #if SECURE_CLIENT == SECURE_CLIENT_BEARSSL
  236. terminalRegisterCommand(F("MFLN.PROBE"), [](Embedis* e) {
  237. if (e->argc != 3) {
  238. terminalError(F("[url] [value]"));
  239. return;
  240. }
  241. URL _url(e->argv[1]);
  242. uint16_t requested_mfln = atol(e->argv[2]);
  243. auto client = std::make_unique<BearSSL::WiFiClientSecure>();
  244. client->setInsecure();
  245. if (client->probeMaxFragmentLength(_url.host.c_str(), _url.port, requested_mfln)) {
  246. terminalOK();
  247. } else {
  248. terminalError(F("Buffer size not supported"));
  249. }
  250. });
  251. #endif
  252. #if LWIP_VERSION_MAJOR != 1
  253. terminalRegisterCommand(F("HOST"), [](Embedis* e) {
  254. if (e->argc != 2) {
  255. terminalError(F("HOST [hostname]"));
  256. return;
  257. }
  258. ip_addr_t result;
  259. auto error = dns_gethostbyname(e->argv[1], &result, _terminalDnsFound, nullptr);
  260. if (error == ERR_OK) {
  261. _terminalPrintDnsResult(e->argv[1], &result);
  262. terminalOK();
  263. return;
  264. } else if (error != ERR_INPROGRESS) {
  265. DEBUG_MSG_P(PSTR("[DNS] dns_gethostbyname error: %s\n"), lwip_strerr(error));
  266. return;
  267. }
  268. });
  269. terminalRegisterCommand(F("NETSTAT"), [](Embedis*) {
  270. _terminalPrintTcpPcbs();
  271. });
  272. #endif // LWIP_VERSION_MAJOR != 1
  273. }
  274. void _terminalLoop() {
  275. #if DEBUG_SERIAL_SUPPORT
  276. while (DEBUG_PORT.available()) {
  277. _serial.inject(DEBUG_PORT.read());
  278. }
  279. #endif
  280. embedis.process();
  281. #if SERIAL_RX_ENABLED
  282. while (SERIAL_RX_PORT.available() > 0) {
  283. char rc = SERIAL_RX_PORT.read();
  284. _serial_rx_buffer[_serial_rx_pointer++] = rc;
  285. if ((_serial_rx_pointer == TERMINAL_BUFFER_SIZE) || (rc == 10)) {
  286. terminalInject(_serial_rx_buffer, (size_t) _serial_rx_pointer);
  287. _serial_rx_pointer = 0;
  288. }
  289. }
  290. #endif // SERIAL_RX_ENABLED
  291. }
  292. // -----------------------------------------------------------------------------
  293. // Pubic API
  294. // -----------------------------------------------------------------------------
  295. void terminalInject(void *data, size_t len) {
  296. _serial.inject((char *) data, len);
  297. }
  298. void terminalInject(char ch) {
  299. _serial.inject(ch);
  300. }
  301. Stream & terminalSerial() {
  302. return (Stream &) _serial;
  303. }
  304. void terminalRegisterCommand(const String& name, void (*call)(Embedis*)) {
  305. Embedis::command(name, call);
  306. };
  307. void terminalOK() {
  308. DEBUG_MSG_P(PSTR("+OK\n"));
  309. }
  310. void terminalError(const String& error) {
  311. DEBUG_MSG_P(PSTR("-ERROR: %s\n"), error.c_str());
  312. }
  313. void terminalSetup() {
  314. _serial.callback([](uint8_t ch) {
  315. #if TELNET_SUPPORT
  316. telnetWrite(ch);
  317. #endif
  318. #if DEBUG_SERIAL_SUPPORT
  319. DEBUG_PORT.write(ch);
  320. #endif
  321. });
  322. #if WEB_SUPPORT
  323. wsRegister()
  324. .onVisible([](JsonObject& root) { root["cmdVisible"] = 1; });
  325. #endif
  326. _terminalInitCommand();
  327. #if SERIAL_RX_ENABLED
  328. SERIAL_RX_PORT.begin(SERIAL_RX_BAUDRATE);
  329. #endif // SERIAL_RX_ENABLED
  330. // Register loop
  331. espurnaRegisterLoop(_terminalLoop);
  332. }
  333. #endif // TERMINAL_SUPPORT