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.

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