Permalink
Cannot retrieve contributors at this time
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
esp8266_modem/esp_modem/esp_modem.ino
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
executable file
503 lines (465 sloc)
13.7 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* | |
| * ESP8266 based virtual modem | |
| * Copyright (C) 2016 Jussi Salin <salinjus@gmail.com> | |
| * | |
| * This program is free software: you can redistribute it and/or modify | |
| * it under the terms of the GNU General Public License as published by | |
| * the Free Software Foundation, either version 3 of the License, or | |
| * (at your option) any later version. | |
| * | |
| * This program is distributed in the hope that it will be useful, | |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| * GNU General Public License for more details. | |
| * | |
| * You should have received a copy of the GNU General Public License | |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
| */ | |
| #include <ESP8266WiFi.h> | |
| #include <algorithm> | |
| // Global variables | |
| String cmd = ""; // Gather a new AT command to this string from serial | |
| bool cmdMode = true; // Are we in AT command mode or connected mode | |
| bool telnet = true; // Is telnet control code handling enabled | |
| #define SWITCH_PIN 0 // GPIO0 (programmind mode pin) | |
| #define DEFAULT_BPS 115200 // 2400 safe for all old computers including C64 | |
| //#define USE_SWITCH 1 // Use a software reset switch | |
| //#define DEBUG 1 // Print additional debug information to serial channel | |
| #undef DEBUG | |
| #undef USE_SWITCH | |
| #define LISTEN_PORT 23 // Listen to this if not connected. Set to zero to disable. | |
| #define RING_INTERVAL 3000 // How often to print RING when having a new incoming connection (ms) | |
| WiFiClient tcpClient; | |
| WiFiServer tcpServer(LISTEN_PORT); | |
| unsigned long lastRingMs=0;// Time of last "RING" message (millis()) | |
| long myBps; // What is the current BPS setting | |
| #define MAX_CMD_LENGTH 256 // Maximum length for AT command | |
| char plusCount = 0; // Go to AT mode at "+++" sequence, that has to be counted | |
| unsigned long plusTime = 0;// When did we last receive a "+++" sequence | |
| #define LED_PIN 2 // Status LED | |
| #define LED_TIME 1 // How many ms to keep LED on at activity | |
| unsigned long ledTime = 0; | |
| #define TX_BUF_SIZE 256 // Buffer where to read from serial before writing to TCP | |
| // (that direction is very blocking by the ESP TCP stack, | |
| // so we can't do one byte a time.) | |
| uint8_t txBuf[TX_BUF_SIZE]; | |
| // Telnet codes | |
| #define DO 0xfd | |
| #define WONT 0xfc | |
| #define WILL 0xfb | |
| #define DONT 0xfe | |
| /** | |
| * Arduino main init function | |
| */ | |
| void setup() | |
| { | |
| Serial.begin(DEFAULT_BPS); | |
| myBps = DEFAULT_BPS; | |
| #ifdef USE_SWITCH | |
| pinMode(SWITCH_PIN, INPUT); | |
| digitalWrite(SWITCH_PIN, HIGH); | |
| #endif | |
| Serial.println("Virtual modem"); | |
| Serial.println("============="); | |
| Serial.println(); | |
| Serial.println("Connect to WIFI: ATWIFI<ssid>,<key>"); | |
| Serial.println("Change terminal baud rate: AT<baud>"); | |
| Serial.println("Connect by TCP: ATDT<host>:<port>"); | |
| Serial.println("See my IP address: ATIP"); | |
| Serial.println("Disable telnet command handling: ATNET0"); | |
| Serial.println("HTTP GET: ATGET<URL>"); | |
| Serial.println(); | |
| if (LISTEN_PORT > 0) | |
| { | |
| Serial.print("Listening to connections at port "); | |
| Serial.print(LISTEN_PORT); | |
| Serial.println(", which result in RING and you can answer with ATA."); | |
| tcpServer.begin(); | |
| } | |
| else | |
| { | |
| Serial.println("Incoming connections are disabled."); | |
| } | |
| Serial.println(""); | |
| Serial.println("OK"); | |
| pinMode(LED_PIN, OUTPUT); | |
| digitalWrite(LED_PIN, HIGH); | |
| } | |
| /** | |
| * Turn on the LED and store the time, so the LED will be shortly after turned off | |
| */ | |
| void led_on(void) | |
| { | |
| digitalWrite(LED_PIN, LOW); | |
| ledTime = millis(); | |
| } | |
| /** | |
| * Perform a command given in command mode | |
| */ | |
| void command() | |
| { | |
| cmd.trim(); | |
| if (cmd == "") return; | |
| Serial.println(); | |
| String upCmd = cmd; | |
| upCmd.toUpperCase(); | |
| long newBps = 0; | |
| /**** Just AT ****/ | |
| if (upCmd == "AT") Serial.println("OK"); | |
| /**** Dial to host ****/ | |
| else if ((upCmd.indexOf("ATDT") == 0) || (upCmd.indexOf("ATDP") == 0) || (upCmd.indexOf("ATDI") == 0)) | |
| { | |
| int portIndex = cmd.indexOf(":"); | |
| String host, port; | |
| if (portIndex != -1) | |
| { | |
| host = cmd.substring(4, portIndex); | |
| port = cmd.substring(portIndex + 1, cmd.length()); | |
| } | |
| else | |
| { | |
| host = cmd.substring(4, cmd.length()); | |
| port = "23"; // Telnet default | |
| } | |
| Serial.print("Connecting to "); | |
| Serial.print(host); | |
| Serial.print(":"); | |
| Serial.println(port); | |
| char *hostChr = new char[host.length() + 1]; | |
| host.toCharArray(hostChr, host.length() + 1); | |
| int portInt = port.toInt(); | |
| tcpClient.setNoDelay(true); // Try to disable naggle | |
| if (tcpClient.connect(hostChr, portInt)) | |
| { | |
| tcpClient.setNoDelay(true); // Try to disable naggle | |
| Serial.print("CONNECT "); | |
| Serial.println(myBps); | |
| cmdMode = false; | |
| Serial.flush(); | |
| if (LISTEN_PORT > 0) tcpServer.stop(); | |
| } | |
| else | |
| { | |
| Serial.println("NO CARRIER"); | |
| } | |
| delete hostChr; | |
| } | |
| /**** Connect to WIFI ****/ | |
| else if (upCmd.indexOf("ATWIFI") == 0) | |
| { | |
| int keyIndex = cmd.indexOf(","); | |
| String ssid, key; | |
| if (keyIndex != -1) | |
| { | |
| ssid = cmd.substring(6, keyIndex); | |
| key = cmd.substring(keyIndex + 1, cmd.length()); | |
| } | |
| else | |
| { | |
| ssid = cmd.substring(6, cmd.length()); | |
| key = ""; | |
| } | |
| char *ssidChr = new char[ssid.length() + 1]; | |
| ssid.toCharArray(ssidChr, ssid.length() + 1); | |
| char *keyChr = new char[key.length() + 1]; | |
| key.toCharArray(keyChr, key.length() + 1); | |
| Serial.print("Connecting to "); | |
| Serial.print(ssid); | |
| Serial.print("/"); | |
| Serial.println(key); | |
| WiFi.begin(ssidChr, keyChr); | |
| for (int i=0; i<100; i++) | |
| { | |
| delay(100); | |
| if (WiFi.status() == WL_CONNECTED) | |
| { | |
| Serial.println("OK"); | |
| break; | |
| } | |
| } | |
| if (WiFi.status() != WL_CONNECTED) | |
| { | |
| Serial.println("ERROR"); | |
| } | |
| delete ssidChr; | |
| delete keyChr; | |
| } | |
| /**** Change baud rate from default ****/ | |
| else if (upCmd == "AT300") newBps = 300; | |
| else if (upCmd == "AT1200") newBps = 1200; | |
| else if (upCmd == "AT2400") newBps = 2400; | |
| else if (upCmd == "AT9600") newBps = 9600; | |
| else if (upCmd == "AT19200") newBps = 19200; | |
| else if (upCmd == "AT38400") newBps = 38400; | |
| else if (upCmd == "AT57600") newBps = 57600; | |
| else if (upCmd == "AT115200") newBps = 115200; | |
| /**** Change telnet mode ****/ | |
| else if (upCmd == "ATNET0") | |
| { | |
| telnet = false; | |
| Serial.println("OK"); | |
| } | |
| else if (upCmd == "ATNET1") | |
| { | |
| telnet = true; | |
| Serial.println("OK"); | |
| } | |
| /**** Answer to incoming connection ****/ | |
| else if ((upCmd == "ATA") && tcpServer.hasClient()) | |
| { | |
| tcpClient = tcpServer.available(); | |
| tcpClient.setNoDelay(true); // try to disable naggle | |
| tcpServer.stop(); | |
| Serial.print("CONNECT "); | |
| Serial.println(myBps); | |
| cmdMode = false; | |
| Serial.flush(); | |
| } | |
| /**** See my IP address ****/ | |
| else if (upCmd == "ATIP") | |
| { | |
| Serial.println(WiFi.localIP()); | |
| Serial.println("OK"); | |
| } | |
| /**** HTTP GET request ****/ | |
| else if (upCmd.indexOf("ATGET") == 0) | |
| { | |
| // From the URL, aquire required variables | |
| // (12 = "ATGEThttp://") | |
| int portIndex = cmd.indexOf(":", 12); // Index where port number might begin | |
| int pathIndex = cmd.indexOf("/", 12); // Index first host name and possible port ends and path begins | |
| int port; | |
| String path, host; | |
| if (pathIndex < 0) | |
| { | |
| pathIndex = cmd.length(); | |
| } | |
| if (portIndex < 0) | |
| { | |
| port = 80; | |
| portIndex = pathIndex; | |
| } | |
| else | |
| { | |
| port = cmd.substring(portIndex+1, pathIndex).toInt(); | |
| } | |
| host = cmd.substring(12, portIndex); | |
| path = cmd.substring(pathIndex, cmd.length()); | |
| if (path == "") path = "/"; | |
| char *hostChr = new char[host.length() + 1]; | |
| host.toCharArray(hostChr, host.length() + 1); | |
| // Debug | |
| Serial.print("Getting path "); | |
| Serial.print(path); | |
| Serial.print(" from port "); | |
| Serial.print(port); | |
| Serial.print(" of host "); | |
| Serial.print(host); | |
| Serial.println("..."); | |
| // Establish connection | |
| if (!tcpClient.connect(hostChr, port)) | |
| { | |
| Serial.println("NO CARRIER"); | |
| } | |
| else | |
| { | |
| Serial.print("CONNECT "); | |
| Serial.println(myBps); | |
| cmdMode = false; | |
| // Send a HTTP request before continuing the connection as usual | |
| String request = "GET "; | |
| request += path; | |
| request += " HTTP/1.1\r\nHost: "; | |
| request += host; | |
| request += "\r\nConnection: close\r\n\r\n"; | |
| tcpClient.print(request); | |
| } | |
| delete hostChr; | |
| } | |
| /**** Unknown command ****/ | |
| else Serial.println("ERROR"); | |
| /**** Tasks to do after command has been parsed ****/ | |
| if (newBps) | |
| { | |
| Serial.println("OK"); | |
| delay(150); // Sleep enough for 4 bytes at any previous baud rate to finish ("\nOK\n") | |
| Serial.begin(newBps); | |
| myBps = newBps; | |
| } | |
| cmd = ""; | |
| } | |
| /** | |
| * Arduino main loop function | |
| */ | |
| void loop() | |
| { | |
| /**** AT command mode ****/ | |
| if (cmdMode == true) | |
| { | |
| // In command mode but new unanswered incoming connection on server listen socket | |
| if ((LISTEN_PORT > 0) && (tcpServer.hasClient())) | |
| { | |
| // Print RING every now and then while the new incoming connection exists | |
| if ((millis() - lastRingMs) > RING_INTERVAL) | |
| { | |
| Serial.println("RING"); | |
| lastRingMs = millis(); | |
| } | |
| } | |
| // In command mode - don't exchange with TCP but gather characters to a string | |
| if (Serial.available()) | |
| { | |
| char chr = Serial.read(); | |
| // Return, enter, new line, carriage return.. anything goes to end the command | |
| if ((chr == '\n') || (chr == '\r')) | |
| { | |
| command(); | |
| } | |
| // Backspace or delete deletes previous character | |
| else if ((chr == 8) || (chr == 127)) | |
| { | |
| cmd.remove(cmd.length() - 1); | |
| // We don't assume that backspace is destructive | |
| // Clear with a space | |
| Serial.write(8); | |
| Serial.write(' '); | |
| Serial.write(8); | |
| } | |
| else | |
| { | |
| if (cmd.length() < MAX_CMD_LENGTH) cmd.concat(chr); | |
| Serial.print(chr); | |
| } | |
| } | |
| } | |
| /**** Connected mode ****/ | |
| else | |
| { | |
| // Transmit from terminal to TCP | |
| if (Serial.available()) | |
| { | |
| led_on(); | |
| // In telnet in worst case we have to escape every byte | |
| // so leave half of the buffer always free | |
| int max_buf_size; | |
| if (telnet == true) | |
| max_buf_size = TX_BUF_SIZE / 2; | |
| else | |
| max_buf_size = TX_BUF_SIZE; | |
| // Read from serial, the amount available up to | |
| // maximum size of the buffer | |
| size_t len = std::min(Serial.available(), max_buf_size); | |
| Serial.readBytes(&txBuf[0], len); | |
| // Disconnect if going to AT mode with "+++" sequence | |
| for (int i=0; i<(int)len; i++) | |
| { | |
| if (txBuf[i] == '+') plusCount++; else plusCount = 0; | |
| if (plusCount >= 3) | |
| { | |
| plusTime = millis(); | |
| } | |
| if (txBuf[i] != '+') | |
| { | |
| plusCount = 0; | |
| } | |
| } | |
| // Double (escape) every 0xff for telnet, shifting the following bytes | |
| // towards the end of the buffer from that point | |
| if (telnet == true) | |
| { | |
| for (int i = len - 1; i >= 0; i--) | |
| { | |
| if (txBuf[i] == 0xff) | |
| { | |
| for (int j = TX_BUF_SIZE - 1; j > i; j--) | |
| { | |
| txBuf[j] = txBuf[j - 1]; | |
| } | |
| len++; | |
| } | |
| } | |
| } | |
| // Write the buffer to TCP finally | |
| tcpClient.write(&txBuf[0], len); | |
| yield(); | |
| } | |
| // Transmit from TCP to terminal | |
| while (tcpClient.available()) | |
| { | |
| led_on(); | |
| uint8_t rxByte = tcpClient.read(); | |
| // Is a telnet control code starting? | |
| if ((telnet == true) && (rxByte == 0xff)) | |
| { | |
| #ifdef DEBUG | |
| Serial.print("<t>"); | |
| #endif | |
| rxByte = tcpClient.read(); | |
| if (rxByte == 0xff) | |
| { | |
| // 2 times 0xff is just an escaped real 0xff | |
| Serial.write(0xff); Serial.flush(); | |
| } | |
| else | |
| { | |
| // rxByte has now the first byte of the actual non-escaped control code | |
| #ifdef DEBUG | |
| Serial.print(rxByte); | |
| Serial.print(","); | |
| #endif | |
| uint8_t cmdByte1 = rxByte; | |
| rxByte = tcpClient.read(); | |
| uint8_t cmdByte2 = rxByte; | |
| // rxByte has now the second byte of the actual non-escaped control code | |
| #ifdef DEBUG | |
| Serial.print(rxByte); Serial.flush(); | |
| #endif | |
| // We are asked to do some option, respond we won't | |
| if (cmdByte1 == DO) | |
| { | |
| tcpClient.write((uint8_t)255); tcpClient.write((uint8_t)WONT); tcpClient.write(cmdByte2); | |
| } | |
| // Server wants to do any option, allow it | |
| else if (cmdByte1 == WILL) | |
| { | |
| tcpClient.write((uint8_t)255); tcpClient.write((uint8_t)DO); tcpClient.write(cmdByte2); | |
| } | |
| } | |
| #ifdef DEBUG | |
| Serial.print("</t>"); | |
| #endif | |
| } | |
| else | |
| { | |
| // Non-control codes pass through freely | |
| Serial.write(rxByte); Serial.flush(); | |
| } | |
| } | |
| } | |
| // Disconnect if programming mode PIN (GPIO0) is switched to GND | |
| #ifdef USE_SWITCH | |
| if ((tcpClient.connected()) && (digitalRead(SWITCH_PIN) == LOW)) | |
| { | |
| tcpClient.stop(); | |
| } | |
| #endif | |
| // If we have received "+++" as last bytes from serial port and there | |
| // has been over a second without any more bytes, disconnect | |
| if (plusCount >= 3) | |
| { | |
| if (millis() - plusTime > 1000) | |
| { | |
| tcpClient.stop(); | |
| plusCount = 0; | |
| } | |
| } | |
| // Go to command mode if TCP disconnected and not in command mode | |
| if ((!tcpClient.connected()) && (cmdMode == false)) | |
| { | |
| cmdMode = true; | |
| Serial.println("NO CARRIER"); | |
| if (LISTEN_PORT > 0) tcpServer.begin(); | |
| } | |
| // Turn off tx/rx led if it has been lit long enough to be visible | |
| if (millis() - ledTime > LED_TIME) digitalWrite(LED_PIN, HIGH); | |
| } |