ESP8266-based WiFi serial modem emulator ROM
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 964 lines 22 kB view raw
1/* 2 * WiFiPPP 3 * Copyright (c) 2021 joshua stein <jcs@jcs.org> 4 * 5 * Permission to use, copy, modify, and distribute this software for any 6 * purpose with or without fee is hereby granted, provided that the above 7 * copyright notice and this permission notice appear in all copies. 8 * 9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 */ 17 18/* 19 * Useful AT command sets to emulate: 20 * 21 * USRobotics Courier 56K Business Modem 22 * http://web.archive.org/web/20161116174421/http://support.usr.com/support/3453c/3453c-ug/alphabetic.html 23 * 24 * Xecom XE3314L 25 * http://web.archive.org/web/20210816224031/http://static6.arrow.com/aropdfconversion/63e466a4e0c7e004c40f79e4dbe4c1356a3dcef6/xe3314l.pdf 26 */ 27 28#include "wifippp.h" 29 30uint8_t state = STATE_AT; 31 32static char curcmd[64] = { 0 }; 33static char lastcmd[64] = { 0 }; 34static unsigned char curcmdlen = 0; 35static unsigned char lastcmdlen = 0; 36static unsigned char plusses = 0; 37static unsigned long plus_wait = 0; 38static unsigned long last_dtr = 0; 39static unsigned long last_autobaud = 0; 40static unsigned long last_pixel_color = 0; 41 42void 43loop(void) 44{ 45 int b = -1, i; 46 long now = millis(); 47 bool hangup = false; 48 49 if (last_pixel_color - now > 500) { 50 pixel_color_by_state(); 51 last_pixel_color = now; 52 } 53 54 socks_process(); 55 56 if (serial_dtr()) { 57 if (!last_dtr) { 58 if (settings->autobaud) { 59#ifdef AT_TRACE 60 syslog.logf(LOG_DEBUG, "new connection with " 61 "DTR, doing auto-baud"); 62#endif 63 serial_autobaud(); 64 now = millis(); 65 last_autobaud = now; 66 } else 67 serial_start(settings->baud); 68 } 69 last_dtr = now; 70 } else if (last_dtr && (now - last_dtr > 1750)) { 71 /* had DTR, dropped it for 1.75 secs, hangup */ 72 hangup = true; 73 last_dtr = 0; 74 syslog.log(LOG_DEBUG, "dropped DTR, hanging up"); 75 } 76 77 switch (state) { 78 case STATE_AT: 79 if (!(serial_available() && (b = serial_read()))) 80 return; 81 82 /* USR modem mode, ignore input not starting with at or a/ */ 83 if (curcmdlen == 0 && (b != 'A' && b != 'a')) { 84#ifdef AT_TRACE 85 syslog.logf(LOG_DEBUG, "bogus char: 0x%x (%c)", b, b); 86#endif 87 if (b > 127) { 88 /* 89 * Help modem auto-detection by just spitting 90 * out OK in response to hopefully ATs received 91 * during autobaud 92 */ 93 if (!settings->quiet) { 94 if (settings->verbal) 95 output("\r\nOK\r\n"); 96 else 97 output("\r0\r"); 98 } 99 } 100 return; 101 } 102 103 if (curcmdlen == 1 && b == '/') { 104 if (settings->echo) 105 output("/\r"); 106 curcmd[0] = '\0'; 107 curcmdlen = 0; 108 exec_cmd((char *)&lastcmd, lastcmdlen); 109 break; 110 } else if (curcmdlen == 1 && (b != 'T' && b != 't')) { 111 if (settings->echo) 112 output("\b \b"); 113 curcmdlen = 0; 114 return; 115 } 116 117 switch (b) { 118 case '\n': 119 case '\r': 120 if (b == '\r') { 121 /* if sender is using \r\n, ignore the \n */ 122 now = millis(); 123 while (millis() - now < 10) { 124 int b2 = serial_peek(); 125 if (b2 == -1) 126 continue; 127 else if (b2 == '\n') { 128 /* this is a \r\n, ignore \n */ 129 serial_read(); 130 break; 131 } else { 132 /* some other data */ 133 break; 134 } 135 } 136 } 137 output("\r"); 138 curcmd[curcmdlen] = '\0'; 139 exec_cmd((char *)&curcmd, curcmdlen); 140 curcmd[0] = '\0'; 141 curcmdlen = 0; 142 break; 143 case '\b': 144 case 127: 145 if (curcmdlen) { 146 if (settings->echo) 147 output("\b \b"); 148 curcmdlen--; 149 } 150 break; 151 default: 152 curcmd[curcmdlen++] = b; 153 if (settings->echo) 154 output(b); 155 } 156 break; 157 case STATE_TELNET: 158 b = -1; 159 160 if (hangup) { 161 telnet_disconnect(); 162 break; 163 } 164 165 if (serial_available()) 166 b = serial_read(); 167 168 if (b == -1 && plus_wait > 0 && (millis() - plus_wait) >= 500) { 169 /* received no input within 500ms of a plus */ 170 if (plusses >= 3) { 171 state = STATE_AT; 172 if (!settings->quiet) { 173 if (settings->verbal) 174 output("\r\nOK\r\n"); 175 else 176 output("0\r"); 177 } 178 } else { 179 /* cancel, flush any plus signs received */ 180 for (i = 0; i < plusses; i++) 181 telnet_write("+"); 182 } 183 plusses = 0; 184 plus_wait = 0; 185 } else if (b != -1) { 186 if (b == '+') { 187 plusses++; 188 plus_wait = millis(); 189 break; 190 } 191 192 if (plusses) { 193 for (i = 0; i < plusses; i++) 194 telnet_write("+"); 195 plusses = 0; 196 } 197 plus_wait = 0; 198 telnet_write(b); 199 break; 200 } 201 202 if ((b = telnet_read()) != -1) { 203 serial_write(b); 204 return; 205 } else if (!telnet_connected()) { 206 if (!settings->quiet) { 207 if (settings->verbal) 208 output("\r\nNO CARRIER\r\n"); 209 else 210 output("3\r"); 211 } 212 state = STATE_AT; 213 break; 214 } 215 break; 216 case STATE_PPP: 217 if (hangup) { 218 ppp_stop(true); 219 break; 220 } 221 222 ppp_process(); 223 break; 224 } 225} 226 227void 228exec_cmd(char *cmd, size_t len) 229{ 230 char *errstr = NULL; 231 char *lcmd, *olcmd; 232 char cmd_char; 233 uint8_t cmd_num = 0; 234 bool did_nl = false; 235 bool did_response = false; 236 237 lcmd = olcmd = (char *)malloc(len + 1); 238 if (lcmd == NULL) { 239 if (settings->verbal) 240 outputf("ERROR malloc %zu failed\r\n", len); 241 else 242 output("4\r"); 243 return; 244 } 245 246#ifdef AT_TRACE 247 syslog.logf(LOG_DEBUG, "%s: parsing \"%s\"", __func__, cmd); 248#endif 249 250 for (size_t i = 0; i < len; i++) 251 lcmd[i] = tolower(cmd[i]); 252 lcmd[len] = '\0'; 253 254 /* shouldn't be able to get here, but just in case */ 255 if (len < 2 || lcmd[0] != 'a' || lcmd[1] != 't') { 256 errstr = strdup("not an AT command"); 257 goto error; 258 } 259 260 memcpy(&lastcmd, lcmd, len + 1); 261 lastcmdlen = len; 262 263 /* strip AT */ 264 cmd += 2; 265 lcmd += 2; 266 len -= 2; 267 268parse_cmd: 269 if (lcmd[0] == '\0') 270 goto done_parsing; 271 272 /* remove command character */ 273 cmd_char = lcmd[0]; 274 len--; 275 cmd++; 276 lcmd++; 277 278 /* find optional single digit after command, defaulting to 0 */ 279 cmd_num = 0; 280 if (cmd[0] >= '0' && cmd[0] <= '9') { 281 if (cmd[1] >= '0' && cmd[1] <= '9') 282 /* nothing uses more than 1 digit */ 283 goto error; 284 cmd_num = cmd[0] - '0'; 285 len--; 286 cmd++; 287 lcmd++; 288 } 289 290#ifdef AT_TRACE 291 syslog.logf(LOG_DEBUG, "%s: parsing AT %c[%d] args \"%s\"", __func__, 292 cmd_char, cmd_num, lcmd); 293#endif 294 295 switch (cmd_char) { 296 case 'd': { 297 char *host, *ohost, *bookmark; 298 uint16_t port; 299 int chars; 300 int index; 301 302 if (len < 2) 303 goto error; 304 305 switch (lcmd[0]) { 306 case 't': 307 /* ATDT: dial a host */ 308 host = ohost = (char *)malloc(len); 309 if (host == NULL) 310 goto error; 311 host[0] = '\0'; 312 if (sscanf(lcmd, "t%[^:]:%hu%n", host, &port, 313 &chars) == 2 && chars > 0) 314 /* matched host:port */ 315 ; 316 else if (sscanf(lcmd, "t%[^:]%n", host, &chars) == 1 317 && chars > 0) 318 /* host without port */ 319 port = 23; 320 else { 321 errstr = strdup("invalid hostname"); 322 goto error; 323 } 324 break; 325 case 's': 326 /* ATDS: dial a stored host */ 327 if (sscanf(lcmd, "s%d", &index) != 1) 328 goto error; 329 330 if (index < 1 || index > NUM_BOOKMARKS) { 331 errstr = strdup("invalid index"); 332 goto error; 333 } 334 335 bookmark = settings->bookmarks[index - 1]; 336 337 host = ohost = (char *)malloc(BOOKMARK_SIZE); 338 if (host == NULL) 339 goto error; 340 341 host[0] = '\0'; 342 343 if (sscanf(bookmark, "%[^:]:%hu%n", host, &port, 344 &chars) == 2 && chars > 0) 345 /* matched host:port */ 346 ; 347 else if (sscanf(bookmark, "%[^:]%n", host, &chars) == 1 348 && chars > 0) 349 /* host without port */ 350 port = 23; 351 else { 352 errstr = strdup("invalid hostname"); 353 goto error; 354 } 355 break; 356 case 'p': 357 if (strncmp(lcmd, "ppp", 3) == 0) { 358 /* 359 * We can't easily support ATD because we allow 360 * hostnames, so if the user typed 361 * "atdtelnethost", we can't tell whether they 362 * meant "atd telnethost" or "atdt elnethost", 363 * but we can allow PPP since that is easy to 364 * check and will probably be done 365 * automatically by PPP software. 366 */ 367 host = ohost = (char *)malloc(4); 368 if (host == NULL) 369 goto error; 370 sprintf(host, "ppp"); 371 } else 372 goto error; 373 break; 374 default: 375 goto error; 376 } 377 378 /* skip leading spaces */ 379 while (host[0] == ' ') 380 host++; 381 382 if (host[0] == '\0') { 383 errstr = strdup("blank hostname"); 384 goto error; 385 } 386 387 /* no commands can follow */ 388 len = 0; 389 390 if (strcasecmp(host, "ppp") == 0) { 391 ip4_addr_t t_addr; 392 ip_addr_copy(t_addr, settings->ppp_server_ip); 393 if (!settings->quiet) { 394 if (settings->verbal) 395 outputf("\nDIALING %s:PPP\r\n", 396 ipaddr_ntoa(&t_addr)); 397 } 398 399 telnet_disconnect(); 400 if (ppp_start()) 401 /* ppp_begin outputs CONNECT line, since it has 402 * to do so before calling ppp_listen */ 403 state = STATE_PPP; 404 else if (!settings->quiet) { 405 if (settings->verbal) 406 output("NO ANSWER\r\n"); 407 else 408 output("8\r"); 409 } 410 } else { 411 if (!settings->quiet && settings->verbal) 412 outputf("\nDIALING %s:%d\r\n", host, port); 413 414 if (telnet_connect(host, port) == 0) { 415 if (!settings->quiet) { 416 if (settings->verbal) 417 outputf("CONNECT %d %s:%d\r\n", 418 Serial.baudRate(), host, 419 port); 420 else 421 output("18\r"); /* 57600 */ 422 } 423 state = STATE_TELNET; 424 } else if (!settings->quiet) { 425 if (settings->verbal) 426 output("\nNO ANSWER\r\n"); 427 else 428 output("8\r"); 429 } 430 } 431 432 if (!settings->quiet) 433 did_nl = true; 434 435 free(ohost); 436 break; 437 } 438 case 'e': 439 /* ATE/ATE0 or ATE1: disable or enable echo */ 440 switch (cmd_num) { 441 case 0: 442 settings->echo = 0; 443 break; 444 case 1: 445 settings->echo = 1; 446 break; 447 default: 448 goto error; 449 } 450 break; 451 case 'h': 452 /* ATH/ATH0: hangup */ 453 switch (cmd_num) { 454 case 0: 455 telnet_disconnect(); 456 output("\r\n"); 457 break; 458 default: 459 goto error; 460 } 461 break; 462 case 'i': 463 /* ATI/ATI#: show information pages */ 464 switch (cmd_num) { 465 case 0: 466 case 3: 467 /* ATI/ATI0/ATI3: show product name */ 468 outputf("\njcs WiFiPPP %s\r\n", WIFIPPP_VERSION); 469 did_nl = true; 470 break; 471 case 1: 472 /* ATI1: checksum of RAM (not used) */ 473 output("\n1337\r\n"); 474 did_nl = true; 475 break; 476 case 2: 477 /* ATI2: test RAM (show ram info) */ 478 outputf("\nFree heap: %d\r\n", ESP.getFreeHeap()); 479 did_nl = true; 480 break; 481 case 4: { 482 /* ATI4: show settings */ 483 ip4_addr_t t_addr; 484 485 output("\n"); 486 487 outputf("Firmware version: %s\r\n", WIFIPPP_VERSION); 488 489 outputf("Default baud rate: %d\r\n", settings->baud); 490 outputf("Current baud rate: %d\r\n", 491 Serial.baudRate()); 492 493 outputf("Default WiFi SSID: %s\r\n", 494 settings->wifi_ssid); 495 outputf("Current WiFi SSID: %s\r\n", WiFi.SSID()); 496 outputf("WiFi connected: %s\r\n", 497 WiFi.status() == WL_CONNECTED ? "yes" : "no"); 498 if (WiFi.status() == WL_CONNECTED) { 499 outputf("IP address: %s\r\n", 500 WiFi.localIP().toString().c_str()); 501 outputf("Gateway IP: %s\r\n", 502 WiFi.gatewayIP().toString().c_str()); 503 outputf("DNS server IP: %s\r\n", 504 WiFi.dnsIP().toString().c_str()); 505 } 506 507 ip_addr_copy(t_addr, settings->ppp_server_ip); 508 outputf("PPP server: %s\r\n", 509 ipaddr_ntoa(&t_addr)); 510 ip_addr_copy(t_addr, settings->ppp_client_ip); 511 outputf("PPP client: %s\r\n", 512 ipaddr_ntoa(&t_addr)); 513 514 outputf("Syslog server: %s\r\n", 515 settings->syslog_server); 516 517 for (int i = 0; i < NUM_BOOKMARKS; i++) { 518 if (settings->bookmarks[i][0] != '\0') 519 outputf("ATDS bookmark %d: %s\r\n", 520 i + 1, settings->bookmarks[i]); 521 } 522 523 did_nl = true; 524 break; 525 } 526 case 5: { 527 /* ATI5: scan for wifi networks */ 528 int n = WiFi.scanNetworks(); 529 530 output("\n"); 531 532 for (int i = 0; i < n; i++) { 533 outputf("%02d: %s (chan %d, %ddBm, ", 534 i + 1, 535 WiFi.SSID(i).c_str(), 536 WiFi.channel(i), 537 WiFi.RSSI(i)); 538 539 switch (WiFi.encryptionType(i)) { 540 case ENC_TYPE_WEP: 541 output("WEP"); 542 break; 543 case ENC_TYPE_TKIP: 544 output("WPA-PSK"); 545 break; 546 case ENC_TYPE_CCMP: 547 output("WPA2-PSK"); 548 break; 549 case ENC_TYPE_NONE: 550 output("NONE"); 551 break; 552 case ENC_TYPE_AUTO: 553 output("WPA-PSK/WPA2-PSK"); 554 break; 555 default: 556 outputf("?(%d)", 557 WiFi.encryptionType(i)); 558 } 559 560 output(")\r\n"); 561 } 562 did_nl = true; 563 break; 564 } 565 default: 566 goto error; 567 } 568 break; 569 case 'o': 570 /* ATO: go back online after a +++ */ 571 switch (cmd_num) { 572 case 0: 573 if (telnet_connected()) 574 state = STATE_TELNET; 575 else 576 goto error; 577 break; 578 default: 579 goto error; 580 } 581 break; 582 case 'q': 583 /* ATQ/ATQ0 or ATQ1: enable or disable quiet */ 584 switch (cmd_num) { 585 case 0: 586 settings->quiet = 0; 587 break; 588 case 1: 589 case 2: 590 settings->quiet = 1; 591 break; 592 default: 593 goto error; 594 } 595 break; 596 case 'v': 597 /* ATV/ATV0 or ATV1: enable or disable verbal responses */ 598 switch (cmd_num) { 599 case 0: 600 settings->verbal = 0; 601 break; 602 case 1: 603 settings->verbal = 1; 604 break; 605 default: 606 goto error; 607 } 608 break; 609 case 'x': 610 /* ATX/ATX#: ignore dialtone, certain results (not used) */ 611 break; 612 case 'z': 613 /* ATZ/ATZ0: restart */ 614 switch (cmd_num) { 615 case 0: 616 if (!settings->quiet) { 617 if (settings->verbal) 618 output("\nOK\r\n"); 619 else 620 output("0\r"); 621 } 622 ESP.restart(); 623 /* NOTREACHED */ 624 default: 625 goto error; 626 } 627 break; 628 case '$': 629 /* wifi232 commands, all consume the rest of the input string */ 630 if (strcmp(lcmd, "autobaud=0") == 0) { 631 /* AT$AUTOBAUD=0: disable autobaud setting */ 632 settings->autobaud = 0; 633 } else if (strcmp(lcmd, "autobaud=1") == 0) { 634 /* AT$AUTOBAUD=1: enable autobaud setting */ 635 settings->autobaud = 1; 636 } else if (strcmp(lcmd, "autobaud?") == 0) { 637 /* AT$AUTOBAUD?: print autobaud setting */ 638 outputf("\n%d\r\n", settings->autobaud); 639 did_nl = true; 640 } else if (strncmp(lcmd, "baud=", 5) == 0) { 641 uint32_t baud = 0; 642 int i, chars = 0; 643 644 /* AT$BAUD=...: set baud rate */ 645 if (sscanf(lcmd, "baud=%d%n", &baud, &chars) != 1 || 646 chars == 0) { 647 errstr = strdup("invalid baud rate"); 648 goto error; 649 } 650 651 for (i = 0; ; i++) { 652 if (ok_bauds[i] == 0) { 653 errstr = strdup("unsupported baud " 654 "rate"); 655 goto error; 656 } 657 if (ok_bauds[i] == baud) 658 break; 659 } 660 661 settings->baud = baud; 662 if (!settings->quiet) { 663 if (settings->verbal) 664 outputf("\nOK switching to %d\r\n", 665 settings->baud); 666 else 667 output("0\r"); 668 } 669 serial_flush(); 670 serial_start(settings->baud); 671 } else if (strcmp(lcmd, "baud?") == 0) { 672 /* AT$BAUD?: print default baud rate */ 673 outputf("\n%d\r\n", settings->baud); 674 did_nl = true; 675 } else if (strcmp(lcmd, "led?") == 0) { 676 /* AT$LED?: show pixel brightness setting */ 677 outputf("\n%d\r\n", settings->pixel_brightness); 678 did_nl = true; 679 } else if (strncmp(lcmd, "led=", 4) == 0) { 680 /* AT$LED=n: set pixel brightness */ 681 int br, chars; 682 if (sscanf(lcmd, "led=%d%n", &br, &chars) != 1 || 683 chars == 0 || br < 0 || br > 10) { 684 errstr = strdup("brightness must be between " 685 "0 and 10"); 686 goto error; 687 } 688 settings->pixel_brightness = br; 689 pixel_adjust_brightness(); 690 } else if (strncmp(lcmd, "naws=", 5) == 0) { 691 /* AT$NAWS=: set telnet NAWS */ 692 int w, h, chars; 693 if (sscanf(lcmd + 5, "%dx%d%n", &w, &h, &chars) == 2 && 694 chars > 0) { 695 if (w < 1 || w > 255) { 696 errstr = strdup("invalid width"); 697 goto error; 698 } 699 if (h < 1 || h > 255) { 700 errstr = strdup("invalid height"); 701 goto error; 702 } 703 704 settings->telnet_tts_w = w; 705 settings->telnet_tts_h = h; 706 } else { 707 errstr = strdup("must be WxH"); 708 goto error; 709 } 710 } else if (strcmp(lcmd, "naws?") == 0) { 711 /* AT$NAWS?: show telnet NAWS setting */ 712 outputf("\n%dx%d\r\n", settings->telnet_tts_w, 713 settings->telnet_tts_h); 714 did_nl = true; 715 } else if (strcmp(lcmd, "net=0") == 0) { 716 /* AT$NET=0: disable telnet setting */ 717 settings->telnet = 0; 718 } else if (strcmp(lcmd, "net=1") == 0) { 719 /* AT$NET=1: enable telnet setting */ 720 settings->telnet = 1; 721 } else if (strcmp(lcmd, "net?") == 0) { 722 /* AT$NET?: show telnet setting */ 723 outputf("\n%d\r\n", settings->telnet); 724 did_nl = true; 725 } else if (strncmp(lcmd, "pass=", 5) == 0) { 726 /* AT$PASS=...: store wep/wpa passphrase */ 727 memset(settings->wifi_pass, 0, 728 sizeof(settings->wifi_pass)); 729 strncpy(settings->wifi_pass, cmd + 5, 730 sizeof(settings->wifi_pass)); 731 732 WiFi.disconnect(); 733 if (settings->wifi_ssid[0]) 734 WiFi.begin(settings->wifi_ssid, 735 settings->wifi_pass); 736 } else if (strcmp(lcmd, "pass?") == 0) { 737 /* AT$PASS?: print wep/wpa passphrase */ 738 outputf("\n%s\r\n", settings->wifi_pass); 739 did_nl = true; 740 } else if (strncmp(lcmd, "pppc=", 5) == 0) { 741 /* AT$PPPC=...: store PPP client IP */ 742 ip4_addr_t t_addr; 743 744 if (!ipaddr_aton(cmd + 5, &t_addr)) { 745 errstr = strdup("invalid IP"); 746 goto error; 747 } 748 749 ip_addr_copy(settings->ppp_client_ip, t_addr); 750 } else if (strcmp(lcmd, "pppc?") == 0) { 751 /* AT$PPPC?: print PPP client IP */ 752 ip4_addr_t t_addr; 753 ip_addr_copy(t_addr, settings->ppp_client_ip); 754 outputf("\n%s\r\n", ipaddr_ntoa(&t_addr)); 755 did_nl = true; 756 } else if (strncmp(lcmd, "ppps=", 5) == 0) { 757 /* AT$PPPS=...: store PPP server IP */ 758 ip4_addr_t t_addr; 759 760 if (!ipaddr_aton(cmd + 5, &t_addr)) { 761 errstr = strdup("invalid IP"); 762 goto error; 763 } 764 765 ip_addr_copy(settings->ppp_server_ip, t_addr); 766 /* re-bind to the new ip */ 767 socks_setup(); 768 } else if (strcmp(lcmd, "ppps?") == 0) { 769 /* AT$PPPS?: print PPP server IP */ 770 ip4_addr_t t_addr; 771 ip_addr_copy(t_addr, settings->ppp_server_ip); 772 outputf("\n%s\r\n", ipaddr_ntoa(&t_addr)); 773 did_nl = true; 774 } else if (strncmp(lcmd, "ssid=", 5) == 0) { 775 /* AT$SSID=...: set wifi ssid */ 776 memset(settings->wifi_ssid, 0, 777 sizeof(settings->wifi_ssid)); 778 strncpy(settings->wifi_ssid, cmd + 5, 779 sizeof(settings->wifi_ssid)); 780 781 WiFi.disconnect(); 782 if (settings->wifi_ssid[0]) 783 WiFi.begin(settings->wifi_ssid, 784 settings->wifi_pass); 785 } else if (strcmp(lcmd, "ssid?") == 0) { 786 /* AT$SSID?: print wifi ssid */ 787 outputf("\n%s\r\n", settings->wifi_ssid); 788 did_nl = true; 789 } else if (strncmp(lcmd, "syslog=", 7) == 0) { 790 /* AT$SYSLOG=...: set syslog server */ 791 memset(settings->syslog_server, 0, 792 sizeof(settings->syslog_server)); 793 strncpy(settings->syslog_server, cmd + 7, 794 sizeof(settings->syslog_server)); 795 syslog_setup(); 796 syslog.logf(LOG_INFO, "syslog server changed to %s", 797 settings->syslog_server); 798 } else if (strcmp(lcmd, "syslog?") == 0) { 799 /* AT$SYSLOG?: print syslog server */ 800 outputf("\n%s\r\n", settings->syslog_server); 801 did_nl = true; 802 } else if (strncmp(lcmd, "ttype=", 6) == 0) { 803 /* AT$TTYPE=: set telnet TTYPE */ 804 memset(settings->telnet_tterm, 0, 805 sizeof(settings->telnet_tterm)); 806 strncpy(settings->telnet_tterm, cmd + 4, 807 sizeof(settings->telnet_tterm)); 808 } else if (strcmp(lcmd, "ttype?") == 0) { 809 /* AT$TTYPE?: show telnet TTYPE setting */ 810 outputf("\n%s\r\n", settings->telnet_tterm); 811 did_nl = true; 812 } else if (strncmp(lcmd, "update?", 7) == 0) { 813 /* AT$UPDATE?: show whether an OTA update is available */ 814 char *url = NULL; 815 if (strncmp(lcmd, "update? http", 12) == 0) 816 url = lcmd + 8; 817 update_process(url, false, false); 818 did_response = true; 819 } else if (strncmp(lcmd, "update!", 7) == 0) { 820 /* AT$UPDATE!: force an OTA update */ 821 char *url = NULL; 822 if (strncmp(lcmd, "update! http", 12) == 0) 823 url = lcmd + 8; 824 update_process(url, true, true); 825 did_response = true; 826 } else if (strcmp(lcmd, "update") == 0 || 827 strncmp(lcmd, "update http", 11) == 0) { 828 /* AT$UPDATE: do an OTA update */ 829 char *url = NULL; 830 if (strncmp(lcmd, "update http", 11) == 0) 831 url = lcmd + 7; 832 update_process(url, true, false); 833 did_response = true; 834 } else 835 goto error; 836 837 /* consume all chars */ 838 len = 0; 839 break; 840 case '&': 841 if (cmd[0] == '\0') 842 goto error; 843 844 cmd_char = lcmd[0]; 845 len--; 846 cmd++; 847 lcmd++; 848 849 /* find optional single digit after &command, defaulting to 0 */ 850 cmd_num = 0; 851 if (cmd[0] == '?') { 852 cmd_num = '?'; 853 len--; 854 cmd++; 855 lcmd++; 856 } else if (cmd[0] >= '0' && cmd[0] <= '9') { 857 if (cmd[1] >= '0' && cmd[1] <= '9') 858 /* nothing uses more than 1 digit */ 859 goto error; 860 cmd_num = cmd[0] - '0'; 861 len--; 862 cmd++; 863 lcmd++; 864 } 865 866 switch (cmd_char) { 867 case 'w': 868 switch (cmd_num) { 869 case 0: 870 /* AT&W: save settings */ 871 /* force setting dirty */ 872 (void)EEPROM.getDataPtr(); 873 if (!EEPROM.commit()) 874 goto error; 875 break; 876 default: 877 goto error; 878 } 879 break; 880 case 'z': { 881 /* AT&Z: manage bookmarks */ 882 uint32_t index = 0; 883 uint8_t query; 884 int chars = 0; 885 886 if (sscanf(lcmd, "%u=%n", &index, &chars) == 1 && 887 chars > 0) { 888 /* AT&Zn=...: store address */ 889 query = 0; 890 } else if (sscanf(lcmd, "%u?%n", &index, &chars) == 1 && 891 chars > 0) { 892 /* AT&Zn?: query stored address */ 893 query = 1; 894 } else { 895 errstr = strdup("invalid store command"); 896 goto error; 897 } 898 899 if (index < 1 || index > NUM_BOOKMARKS) { 900 errstr = strdup("invalid index"); 901 goto error; 902 } 903 904 if (query) { 905 outputf("\n%s\r\n", 906 settings->bookmarks[index - 1]); 907 did_nl = true; 908 } else { 909 memset(settings->bookmarks[index - 1], 0, 910 sizeof(settings->bookmarks[0])); 911 strncpy(settings->bookmarks[index - 1], 912 cmd + 2, 913 sizeof(settings->bookmarks[0]) - 1); 914 } 915 916 /* consume all chars */ 917 len = 0; 918 break; 919 } 920 default: 921 goto error; 922 } 923 break; 924 case ' ': 925 /* skip spaces between commands */ 926 break; 927 default: 928 goto error; 929 } 930 931done_parsing: 932 /* if any len left, parse as another command */ 933 if (len > 0) 934 goto parse_cmd; 935 936 if (olcmd) 937 free(olcmd); 938 939 if (!did_response && state == STATE_AT && !settings->quiet) { 940 if (settings->verbal) 941 outputf("%sOK\r\n", did_nl ? "" : "\n"); 942 else 943 output("0\r"); 944 } 945 946 return; 947 948error: 949 if (olcmd) 950 free(olcmd); 951 952 if (!did_response && !settings->quiet) { 953 if (settings->verbal) { 954 output("\nERROR"); 955 if (errstr != NULL) 956 outputf(" %s", errstr); 957 output("\r\n"); 958 } else 959 output("4\r"); 960 } 961 962 if (errstr != NULL) 963 free(errstr); 964}