/* WiFi SIXFOUR - A virtual WiFi modem based on the ESP 8266 chipset Copyright (C) 2016 Paul Rickards Added EEPROM read/write, status/help pages, busy answering of incoming calls uses the readily available Sparkfun ESP8266 WiFi Shield which has 5v level shifters and 3.3v voltage regulation present-- easily connect to a C64 https://www.sparkfun.com/products/13287 based on ESP8266 based virtual modem Copyright (C) 2016 Jussi Salin https://github.com/jsalin/esp8266_modem 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 . */ #include #include #include #include // Project headers #include // 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 callConnected = false;// Are we currently in a call bool telnet = false; // Is telnet control code handling enabled bool verboseResults = false; //#define DEBUG 1 // Print additional debug information to serial channel #undef DEBUG #define LISTEN_PORT 6400 // Listen to this if not connected. Set to zero to disable. int tcpServerPort = LISTEN_PORT; #define RING_INTERVAL 3000 // How often to print RING when having a new incoming connection (ms) 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_TIME 15 // 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]; const int speedDialAddresses[] = { DIAL0_ADDRESS, DIAL1_ADDRESS, DIAL2_ADDRESS, DIAL3_ADDRESS, DIAL4_ADDRESS, DIAL5_ADDRESS, DIAL6_ADDRESS, DIAL7_ADDRESS, DIAL8_ADDRESS, DIAL9_ADDRESS }; String speedDials[10]; const int bauds[] = { 300, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200 }; byte serialspeed; bool echo = true; bool autoAnswer = false; String ssid, password, busyMsg; byte ringCount = 0; String resultCodes[] = { "OK", "CONNECT", "RING", "NO CARRIER", "ERROR", "", "NO DIALTONE", "BUSY", "NO ANSWER" }; enum resultCodes_t { R_OK, R_CONNECT, R_RING, R_NOCARRIER, R_ERROR, R_NONE, R_NODIALTONE, R_BUSY, R_NOANSWER }; unsigned long connectTime = 0; bool petTranslate = false; // Fix PET MCTerm 1.26C Pet->ASCII encoding to actual ASCII bool hex = false; enum flowControl_t { F_NONE, F_HARDWARE, F_SOFTWARE }; byte flowControl = F_NONE; // Use flow control bool txPaused = false; // Has flow control asked us to pause? enum pinPolarity_t { P_INVERTED, P_NORMAL }; // Is LOW (0) or HIGH (1) active? byte pinPolarity = P_INVERTED; // Telnet codes #define DO 0xfd #define WONT 0xfc #define WILL 0xfb #define DONT 0xfe WiFiClient tcpClient; WiFiServer tcpServer(tcpServerPort); ESP8266WebServer webServer(80); MDNSResponder mdns; extern void waitForSpace(); extern void displayHelp(); extern void welcome(); String connectTimeString() { unsigned long now = millis(); int secs = (now - connectTime) / 1000; int mins = secs / 60; int hours = mins / 60; String out = ""; if (hours < 10) out.concat("0"); out.concat(String(hours)); out.concat(":"); if (mins % 60 < 10) out.concat("0"); out.concat(String(mins % 60)); out.concat(":"); if (secs % 60 < 10) out.concat("0"); out.concat(String(secs % 60)); return out; } void writeSettings() { setEEPROM(ssid, SSID_ADDRESS, SSID_LEN); setEEPROM(password, PASS_ADDRESS, PASS_LEN); setEEPROM(busyMsg, BUSY_MSG_ADDRESS, BUSY_MSG_LEN); EEPROM.write(BAUD_ADDRESS, serialspeed); EEPROM.write(ECHO_ADDRESS, byte(echo)); EEPROM.write(AUTO_ANSWER_ADDRESS, byte(autoAnswer)); EEPROM.write(SERVER_PORT_ADDRESS, highByte(tcpServerPort)); EEPROM.write(SERVER_PORT_ADDRESS + 1, lowByte(tcpServerPort)); EEPROM.write(TELNET_ADDRESS, byte(telnet)); EEPROM.write(VERBOSE_ADDRESS, byte(verboseResults)); EEPROM.write(PET_TRANSLATE_ADDRESS, byte(petTranslate)); EEPROM.write(FLOW_CONTROL_ADDRESS, byte(flowControl)); EEPROM.write(PIN_POLARITY_ADDRESS, byte(pinPolarity)); for (int i = 0; i < 10; i++) { setEEPROM(speedDials[i], speedDialAddresses[i], 50); } EEPROM.commit(); } void readSettings() { echo = EEPROM.read(ECHO_ADDRESS); autoAnswer = EEPROM.read(AUTO_ANSWER_ADDRESS); // serialspeed = EEPROM.read(BAUD_ADDRESS); ssid = getEEPROM(SSID_ADDRESS, SSID_LEN); password = getEEPROM(PASS_ADDRESS, PASS_LEN); busyMsg = getEEPROM(BUSY_MSG_ADDRESS, BUSY_MSG_LEN); tcpServerPort = word(EEPROM.read(SERVER_PORT_ADDRESS), EEPROM.read(SERVER_PORT_ADDRESS + 1)); telnet = EEPROM.read(TELNET_ADDRESS); verboseResults = EEPROM.read(VERBOSE_ADDRESS); petTranslate = EEPROM.read(PET_TRANSLATE_ADDRESS); flowControl = EEPROM.read(FLOW_CONTROL_ADDRESS); pinPolarity = EEPROM.read(PIN_POLARITY_ADDRESS); for (int i = 0; i < 10; i++) { speedDials[i] = getEEPROM(speedDialAddresses[i], 50); } } void defaultEEPROM() { EEPROM.write(VERSION_ADDRESS, VERSIONA); EEPROM.write(VERSION_ADDRESS + 1, VERSIONB); setEEPROM("", SSID_ADDRESS, SSID_LEN); setEEPROM("", PASS_ADDRESS, PASS_LEN); setEEPROM("d", IP_TYPE_ADDRESS, 1); EEPROM.write(SERVER_PORT_ADDRESS, highByte(LISTEN_PORT)); EEPROM.write(SERVER_PORT_ADDRESS + 1, lowByte(LISTEN_PORT)); EEPROM.write(BAUD_ADDRESS, 0x00); EEPROM.write(ECHO_ADDRESS, 0x01); EEPROM.write(AUTO_ANSWER_ADDRESS, 0x01); EEPROM.write(TELNET_ADDRESS, 0x00); EEPROM.write(VERBOSE_ADDRESS, 0x01); EEPROM.write(PET_TRANSLATE_ADDRESS, 0x00); EEPROM.write(FLOW_CONTROL_ADDRESS, 0x00); EEPROM.write(PIN_POLARITY_ADDRESS, 0x01); setEEPROM(SPEEDDIAL0, speedDialAddresses[0], 50); setEEPROM(SPEEDDIAL1, speedDialAddresses[1], 50); setEEPROM(SPEEDDIAL2, speedDialAddresses[2], 50); setEEPROM(SPEEDDIAL3, speedDialAddresses[3], 50); setEEPROM(SPEEDDIAL4, speedDialAddresses[4], 50); setEEPROM(SPEEDDIAL5, speedDialAddresses[5], 50); setEEPROM(SPEEDDIAL6, speedDialAddresses[6], 50); setEEPROM(SPEEDDIAL7, speedDialAddresses[7], 50); setEEPROM(SPEEDDIAL8, speedDialAddresses[8], 50); setEEPROM(SPEEDDIAL9, speedDialAddresses[9], 50); for (int i = 5; i < 10; i++) { setEEPROM("", speedDialAddresses[i], 50); } setEEPROM("SORRY, SYSTEM IS CURRENTLY BUSY. PLEASE TRY AGAIN LATER.", BUSY_MSG_ADDRESS, BUSY_MSG_LEN); EEPROM.commit(); } String getEEPROM(int startAddress, int len) { String myString; for (int i = startAddress; i < startAddress + len; i++) { if (EEPROM.read(i) == 0x00) { break; } myString += char(EEPROM.read(i)); //Serial.print(char(EEPROM.read(i))); } //Serial.println(); return myString; } void setEEPROM(String inString, int startAddress, int maxLen) { for (unsigned int i = startAddress; i < inString.length() + startAddress; i++) { EEPROM.write(i, inString[i - startAddress]); //Serial.print(i, DEC); Serial.print(": "); Serial.println(inString[i - startAddress]); //if (EEPROM.read(i) != inString[i - startAddress]) { Serial.print(" (!)"); } //Serial.println(); } // null pad the remainder of the memory space for (int i = inString.length() + startAddress; i < maxLen + startAddress; i++) { EEPROM.write(i, 0x00); //Serial.print(i, DEC); Serial.println(": 0x00"); } } void sendResult(int resultCode) { Serial.print("\r\n"); if (verboseResults == 0) { Serial.println(resultCode); return; } if (resultCode == R_CONNECT) { Serial.print(String(resultCodes[R_CONNECT]) + " " + String(bauds[serialspeed])); } else if (resultCode == R_NOCARRIER) { Serial.print(String(resultCodes[R_NOCARRIER]) + " (" + connectTimeString() + ")"); } else { Serial.print(String(resultCodes[resultCode])); } Serial.print("\r\n"); } void sendString(String msg) { Serial.print("\r\n"); Serial.print(msg); Serial.print("\r\n"); } // Hold for 5 seconds to switch to 300 baud // Slow flash: keep holding // Fast flash: let go int checkButton() { long time = millis(); while (digitalRead(SWITCH_PIN) == LOW && millis() - time < 5000) { delay(250); digitalWrite(LED1, !digitalRead(LED1)); yield(); } if (millis() - time > 5000) { Serial.flush(); Serial.end(); serialspeed = 2; delay(100); Serial.begin(bauds[serialspeed]); sendResult(R_OK); while (digitalRead(SWITCH_PIN) == LOW) { delay(50); digitalWrite(LED1, !digitalRead(LED1)); yield(); } return 1; } else { return 0; } } void connectWiFi() { if (ssid == "" || password == "") { Serial.println("CONFIGURE SSID AND PASSWORD. TYPE AT? FOR HELP."); return; } WiFi.begin(ssid.c_str(), password.c_str()); Serial.print("\nCONNECTING TO SSID "); Serial.print(ssid); uint8_t i = 0; while (WiFi.status() != WL_CONNECTED && i++ < 20) { digitalWrite(LED2, LOW); delay(250); digitalWrite(LED2, HIGH); delay(250); Serial.print("."); } Serial.println(); if (i == 30) { Serial.print("COULD NOT CONNECT TO "); Serial.println(ssid); WiFi.disconnect(); updateLed(); } else { Serial.print("CONNECTED TO "); Serial.println(WiFi.SSID()); Serial.print("IP ADDRESS: "); Serial.println(WiFi.localIP()); updateLed(); } } void updateLed() { if (WiFi.status() == WL_CONNECTED) { digitalWrite(LED2, LOW); // on } else { digitalWrite(LED2, HIGH); //off } } void disconnectWiFi() { WiFi.disconnect(); updateLed(); } void setBaudRate(int inSpeed) { if (inSpeed == 0) { sendResult(R_ERROR); return; } int foundBaud = -1; for (unsigned int i = 0; i < sizeof(bauds); i++) { if (inSpeed == bauds[i]) { foundBaud = i; break; } } // requested baud rate not found, return error if (foundBaud == -1) { sendResult(R_ERROR); return; } if (foundBaud == serialspeed) { sendResult(R_OK); return; } Serial.print("SWITCHING SERIAL PORT TO "); Serial.print(inSpeed); Serial.println(" IN 5 SECONDS"); delay(5000); Serial.end(); delay(200); Serial.begin(bauds[foundBaud]); serialspeed = foundBaud; delay(200); sendResult(R_OK); } void setCarrier(byte carrier) { if (pinPolarity == P_NORMAL) carrier = !carrier; digitalWrite(DCD_PIN, carrier); } void displayNetworkStatus() { Serial.print("WIFI STATUS: "); if (WiFi.status() == WL_CONNECTED) { Serial.println("CONNECTED"); } if (WiFi.status() == WL_IDLE_STATUS) { Serial.println("OFFLINE"); } if (WiFi.status() == WL_CONNECT_FAILED) { Serial.println("CONNECT FAILED"); } if (WiFi.status() == WL_NO_SSID_AVAIL) { Serial.println("SSID UNAVAILABLE"); } if (WiFi.status() == WL_CONNECTION_LOST) { Serial.println("CONNECTION LOST"); } if (WiFi.status() == WL_DISCONNECTED) { Serial.println("DISCONNECTED"); } if (WiFi.status() == WL_SCAN_COMPLETED) { Serial.println("SCAN COMPLETED"); } yield(); Serial.print("SSID.......: "); Serial.println(WiFi.SSID()); // Serial.print("ENCRYPTION: "); // switch(WiFi.encryptionType()) { // case 2: // Serial.println("TKIP (WPA)"); // break; // case 5: // Serial.println("WEP"); // break; // case 4: // Serial.println("CCMP (WPA)"); // break; // case 7: // Serial.println("NONE"); // break; // case 8: // Serial.println("AUTO"); // break; // default: // Serial.println("UNKNOWN"); // break; // } byte mac[6]; WiFi.macAddress(mac); Serial.print("MAC ADDRESS: "); Serial.print(mac[0], HEX); Serial.print(":"); Serial.print(mac[1], HEX); Serial.print(":"); Serial.print(mac[2], HEX); Serial.print(":"); Serial.print(mac[3], HEX); Serial.print(":"); Serial.print(mac[4], HEX); Serial.print(":"); Serial.println(mac[5], HEX); yield(); Serial.print("IP ADDRESS.: "); Serial.println(WiFi.localIP()); yield(); Serial.print("GATEWAY....: "); Serial.println(WiFi.gatewayIP()); yield(); Serial.print("SUBNET MASK: "); Serial.println(WiFi.subnetMask()); yield(); Serial.print("SERVER PORT: "); Serial.println(tcpServerPort); yield(); Serial.print("WEB CONFIG.: HTTP://"); Serial.println(WiFi.localIP()); yield(); Serial.print("CALL STATUS: "); yield(); if (callConnected) { Serial.print("CONNECTED TO "); Serial.println(ipToString(tcpClient.remoteIP())); yield(); Serial.print("CALL LENGTH: "); Serial.println(connectTimeString()); yield(); } else { Serial.println("NOT CONNECTED"); } } void displayCurrentSettings() { Serial.println("ACTIVE PROFILE:"); yield(); Serial.print("BAUD: "); Serial.println(bauds[serialspeed]); yield(); Serial.print("SSID: "); Serial.println(ssid); yield(); Serial.print("PASS: "); Serial.println(password); yield(); //Serial.print("SERVER TCP PORT: "); Serial.println(tcpServerPort); yield(); Serial.print("BUSY MSG: "); Serial.println(busyMsg); yield(); Serial.print("E"); Serial.print(echo); Serial.print(" "); yield(); Serial.print("V"); Serial.print(verboseResults); Serial.print(" "); yield(); Serial.print("&K"); Serial.print(flowControl); Serial.print(" "); yield(); Serial.print("&P"); Serial.print(pinPolarity); Serial.print(" "); yield(); Serial.print("NET"); Serial.print(telnet); Serial.print(" "); yield(); Serial.print("PET"); Serial.print(petTranslate); Serial.print(" "); yield(); Serial.print("S0:"); Serial.print(autoAnswer); Serial.print(" "); yield(); Serial.println(); yield(); Serial.println("SPEED DIAL:"); for (int i = 0; i < 10; i++) { Serial.print(i); Serial.print(": "); Serial.println(speedDials[i]); yield(); } Serial.println(); } void displayStoredSettings() { Serial.println("STORED PROFILE:"); yield(); Serial.print("BAUD: "); Serial.println(bauds[EEPROM.read(BAUD_ADDRESS)]); yield(); Serial.print("SSID: "); Serial.println(getEEPROM(SSID_ADDRESS, SSID_LEN)); yield(); Serial.print("PASS: "); Serial.println(getEEPROM(PASS_ADDRESS, PASS_LEN)); yield(); //Serial.print("SERVER TCP PORT: "); Serial.println(word(EEPROM.read(SERVER_PORT_ADDRESS), EEPROM.read(SERVER_PORT_ADDRESS+1))); yield(); Serial.print("BUSY MSG: "); Serial.println(getEEPROM(BUSY_MSG_ADDRESS, BUSY_MSG_LEN)); yield(); Serial.print("E"); Serial.print(EEPROM.read(ECHO_ADDRESS)); Serial.print(" "); yield(); Serial.print("V"); Serial.print(EEPROM.read(VERBOSE_ADDRESS)); Serial.print(" "); yield(); Serial.print("&K"); Serial.print(EEPROM.read(FLOW_CONTROL_ADDRESS)); Serial.print(" "); yield(); Serial.print("&P"); Serial.print(EEPROM.read(PIN_POLARITY_ADDRESS)); Serial.print(" "); yield(); Serial.print("NET"); Serial.print(EEPROM.read(TELNET_ADDRESS)); Serial.print(" "); yield(); Serial.print("PET"); Serial.print(EEPROM.read(PET_TRANSLATE_ADDRESS)); Serial.print(" "); yield(); Serial.print("S0:"); Serial.print(EEPROM.read(AUTO_ANSWER_ADDRESS)); Serial.print(" "); yield(); Serial.println(); yield(); Serial.println("STORED SPEED DIAL:"); for (int i = 0; i < 10; i++) { Serial.print(i); Serial.print(": "); Serial.println(getEEPROM(speedDialAddresses[i], 50)); yield(); } Serial.println(); } void storeSpeedDial(byte num, String location) { //if (num < 0 || num > 9) { return; } speedDials[num] = location; //Serial.print("STORED "); Serial.print(num); Serial.print(": "); Serial.println(location); } /** Arduino main init function */ void setup() { pinMode(LED1, OUTPUT); digitalWrite(LED1, LOW); // off pinMode(LED2, OUTPUT); digitalWrite(LED2, LOW); // off pinMode(LED3, OUTPUT); digitalWrite(LED3, LOW); // off pinMode(LED4, OUTPUT); digitalWrite(LED4, LOW); // off pinMode(SWITCH_PIN, INPUT); digitalWrite(SWITCH_PIN, HIGH); pinMode(DCD_PIN, OUTPUT); pinMode(RTS_PIN, OUTPUT); digitalWrite(RTS_PIN, HIGH); pinMode(CTS_PIN, INPUT); setCarrier(false); delay(100); for (int cnt = 0; cnt < 3; cnt++) { digitalWrite(LED1, HIGH); // off delay(100); digitalWrite(LED1, LOW); // off digitalWrite(LED2, HIGH); // on delay(100); digitalWrite(LED2, LOW); // on digitalWrite(LED3, HIGH); // off delay(100); digitalWrite(LED3, LOW); // off digitalWrite(LED4, HIGH); // on delay(100); digitalWrite(LED4, LOW); // on } digitalWrite(LED1, HIGH); // on digitalWrite(LED2, LOW); // off digitalWrite(LED3, LOW); // off digitalWrite(LED3, LOW); // off EEPROM.begin(LAST_ADDRESS + 1); delay(10); if (EEPROM.read(VERSION_ADDRESS) != VERSIONA || EEPROM.read(VERSION_ADDRESS + 1) != VERSIONB) { defaultEEPROM(); } readSettings(); // Fetch baud rate from EEPROM serialspeed = EEPROM.read(BAUD_ADDRESS); // Check if it's out of bounds-- we have to be able to talk if (serialspeed < 0 || serialspeed > sizeof(bauds)) { serialspeed = 0; } Serial.begin(bauds[serialspeed]); char c = 0x00; //unsigned long startMillis = millis(); //while (c != 8 && c != 127 && c!= 20) { // Check for the backspace key to begin //while (c != 32) { // Check for space to begin while (c != 0x0a && c != 0x0d) { if (Serial.available() > 0) { c = Serial.read(); if (petTranslate == true){ if (c > 127) c-= 128; } } if (checkButton() == 1) { break; // button pressed, we're setting to 300 baud and moving on } //if (millis() - startMillis > 2000) { //digitalWrite(LED2, !digitalRead(LED2)); //startMillis = millis(); //} yield(); } welcome(); if (tcpServerPort > 0) tcpServer.begin(); WiFi.mode(WIFI_STA); connectWiFi(); sendResult(R_OK); //tcpServer(tcpServerPort); // can't start tcpServer inside a function-- must live outside digitalWrite(LED2, LOW); // on webServer.on("/", handleRoot); webServer.on("/ath", handleWebHangUp); webServer.begin(); mdns.begin("C64WiFi", WiFi.localIP()); } String ipToString(IPAddress ip) { char s[16]; sprintf(s, "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); return s; } void hangUp() { tcpClient.stop(); callConnected = false; setCarrier(callConnected); sendResult(R_NOCARRIER); connectTime = 0; } void handleWebHangUp() { String t = "NO CARRIER (" + connectTimeString() + ")"; hangUp(); webServer.send(200, "text/plain", t); } void handleRoot() { String page = "WIFI STATUS: "; if (WiFi.status() == WL_CONNECTED) { page.concat("CONNECTED"); } if (WiFi.status() == WL_IDLE_STATUS) { page.concat("OFFLINE"); } if (WiFi.status() == WL_CONNECT_FAILED) { page.concat("CONNECT FAILED"); } if (WiFi.status() == WL_NO_SSID_AVAIL) { page.concat("SSID UNAVAILABLE"); } if (WiFi.status() == WL_CONNECTION_LOST) { page.concat("CONNECTION LOST"); } if (WiFi.status() == WL_DISCONNECTED) { page.concat("DISCONNECTED"); } if (WiFi.status() == WL_SCAN_COMPLETED) { page.concat("SCAN COMPLETED"); } yield(); page.concat("\nSSID.......: " + WiFi.SSID()); byte mac[6]; WiFi.macAddress(mac); page.concat("\nMAC ADDRESS: "); page.concat(String(mac[0], HEX)); page.concat(":"); page.concat(String(mac[1], HEX)); page.concat(":"); page.concat(String(mac[2], HEX)); page.concat(":"); page.concat(String(mac[3], HEX)); page.concat(":"); page.concat(String(mac[4], HEX)); page.concat(":"); page.concat(String(mac[5], HEX)); yield(); page.concat("\nIP ADDRESS.: "); page.concat(ipToString(WiFi.localIP())); page.concat("\nGATEWAY....: "); page.concat(ipToString(WiFi.gatewayIP())); yield(); page.concat("\nSUBNET MASK: "); page.concat(ipToString(WiFi.subnetMask())); yield(); page.concat("\nSERVER PORT: "); page.concat(tcpServerPort); page.concat("\nCALL STATUS: "); if (callConnected) { page.concat("CONNECTED TO "); page.concat(ipToString(tcpClient.remoteIP())); page.concat("\nCALL LENGTH: "); page.concat(connectTimeString()); yield(); } else { page.concat("NOT CONNECTED"); } page.concat("\n"); webServer.send(200, "text/plain", page); delay(100); } /** Turn on the LED and store the time, so the LED will be shortly after turned off */ void led_on() { digitalWrite(LED2, !digitalRead(LED2)); ledTime = millis(); } void answerCall() { tcpClient = tcpServer.available(); tcpClient.setNoDelay(true); // try to disable naggle //tcpServer.stop(); sendResult(R_CONNECT); connectTime = millis(); cmdMode = false; callConnected = true; setCarrier(callConnected); Serial.flush(); } void handleIncomingConnection() { if (callConnected == 1 || (autoAnswer == false && ringCount > 3)) { // We're in a call already or didn't answer the call after three rings // We didn't answer the call. Notify our party we're busy and disconnect ringCount = lastRingMs = 0; WiFiClient anotherClient = tcpServer.available(); anotherClient.print(busyMsg); anotherClient.print("\r\n"); anotherClient.print("CURRENT CALL LENGTH: "); anotherClient.print(connectTimeString()); anotherClient.print("\r\n"); anotherClient.print("\r\n"); anotherClient.flush(); anotherClient.stop(); return; } if (autoAnswer == false) { if (millis() - lastRingMs > 6000 || lastRingMs == 0) { lastRingMs = millis(); sendResult(R_RING); ringCount++; } return; } if (autoAnswer == true) { WiFiClient tempClient = tcpServer.available(); // this is the key to keeping the connection open tcpClient = tempClient; // hand over the new connection to the global client tempClient.stop(); // stop the temporary one sendString(String("RING ") + ipToString(tcpClient.remoteIP())); delay(1000); sendResult(R_CONNECT); connectTime = millis(); cmdMode = false; tcpClient.flush(); callConnected = true; setCarrier(callConnected); } } void dialOut(String upCmd) { // Can't place a call while in a call if (callConnected) { sendResult(R_ERROR); return; } String host, port; int portIndex; // Dialing a stored number if (upCmd.indexOf("ATDS") == 0) { byte speedNum = upCmd.substring(4, 5).toInt(); portIndex = speedDials[speedNum].indexOf(':'); if (portIndex != -1) { host = speedDials[speedNum].substring(0, portIndex); port = speedDials[speedNum].substring(portIndex + 1); } else { port = "23"; } } else { // Dialing an ad-hoc number int portIndex = cmd.indexOf(":"); 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 } } host.trim(); // remove leading or trailing spaces port.trim(); Serial.print("DIALING "); 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 sendResult(R_CONNECT); connectTime = millis(); cmdMode = false; Serial.flush(); callConnected = true; setCarrier(callConnected); //if (tcpServerPort > 0) tcpServer.stop(); } else { sendResult(R_NOANSWER); callConnected = false; setCarrier(callConnected); } delete hostChr; } void waitForSpace() { Serial.print("PRESS SPACE"); char c = 0; while (c != 0x20) { if (Serial.available() > 0) { c = Serial.read(); if (petTranslate == true){ if (c > 127) c-= 128; } } } Serial.print("\r"); } /** Perform a command given in command mode */ void command() { cmd.trim(); if (cmd == "") return; Serial.println(); String upCmd = cmd; upCmd.toUpperCase(); /**** Just AT ****/ if (upCmd == "AT") sendResult(R_OK); /**** Dial to host ****/ else if ((upCmd.indexOf("ATDT") == 0) || (upCmd.indexOf("ATDP") == 0) || (upCmd.indexOf("ATDI") == 0) || (upCmd.indexOf("ATDS") == 0)) { dialOut(upCmd); } /**** Change telnet mode ****/ else if (upCmd == "ATNET0") { telnet = false; sendResult(R_OK); } else if (upCmd == "ATNET1") { telnet = true; sendResult(R_OK); } else if (upCmd == "ATNET?") { Serial.println(String(telnet)); sendResult(R_OK); } /**** Answer to incoming connection ****/ else if ((upCmd == "ATA") && tcpServer.hasClient()) { answerCall(); } /**** Display Help ****/ else if (upCmd == "AT?" || upCmd == "ATHELP") { displayHelp(); sendResult(R_OK); } /**** Reset, reload settings from EEPROM ****/ else if (upCmd == "ATZ") { readSettings(); sendResult(R_OK); } /**** Disconnect WiFi ****/ else if (upCmd == "ATC0") { disconnectWiFi(); sendResult(R_OK); } /**** Connect WiFi ****/ else if (upCmd == "ATC1") { connectWiFi(); sendResult(R_OK); } /**** Control local echo in command mode ****/ else if (upCmd.indexOf("ATE") == 0) { if (upCmd.substring(3, 4) == "?") { sendString(String(echo)); sendResult(R_OK); } else if (upCmd.substring(3, 4) == "0") { echo = 0; sendResult(R_OK); } else if (upCmd.substring(3, 4) == "1") { echo = 1; sendResult(R_OK); } else { sendResult(R_ERROR); } } /**** Control verbosity ****/ else if (upCmd.indexOf("ATV") == 0) { if (upCmd.substring(3, 4) == "?") { sendString(String(verboseResults)); sendResult(R_OK); } else if (upCmd.substring(3, 4) == "0") { verboseResults = 0; sendResult(R_OK); } else if (upCmd.substring(3, 4) == "1") { verboseResults = 1; sendResult(R_OK); } else { sendResult(R_ERROR); } } /**** Control pin polarity of CTS, RTS, DCD ****/ else if (upCmd.indexOf("AT&P") == 0) { if (upCmd.substring(4, 5) == "?") { sendString(String(pinPolarity)); sendResult(R_OK); } else if (upCmd.substring(4, 5) == "0") { pinPolarity = P_INVERTED; sendResult(R_OK); setCarrier(callConnected); } else if (upCmd.substring(4, 5) == "1") { pinPolarity = P_NORMAL; sendResult(R_OK); setCarrier(callConnected); } else { sendResult(R_ERROR); } } /**** Control Flow Control ****/ else if (upCmd.indexOf("AT&K") == 0) { if (upCmd.substring(4, 5) == "?") { sendString(String(flowControl)); sendResult(R_OK); } else if (upCmd.substring(4, 5) == "0") { flowControl = 0; sendResult(R_OK); } else if (upCmd.substring(4, 5) == "1") { flowControl = 1; sendResult(R_OK); } else if (upCmd.substring(4, 5) == "2") { flowControl = 2; sendResult(R_OK); } else { sendResult(R_ERROR); } } /**** Set current baud rate ****/ else if (upCmd.indexOf("AT$SB=") == 0) { setBaudRate(upCmd.substring(6).toInt()); } /**** Display current baud rate ****/ else if (upCmd.indexOf("AT$SB?") == 0) { sendString(String(bauds[serialspeed]));; } /**** Set busy message ****/ else if (upCmd.indexOf("AT$BM=") == 0) { busyMsg = cmd.substring(6); sendResult(R_OK); } /**** Display busy message ****/ else if (upCmd.indexOf("AT$BM?") == 0) { sendString(busyMsg); sendResult(R_OK); } /**** Display Network settings ****/ else if (upCmd == "ATI") { displayNetworkStatus(); sendResult(R_OK); } /**** Display profile settings ****/ else if (upCmd == "AT&V") { displayCurrentSettings(); waitForSpace(); displayStoredSettings(); sendResult(R_OK); } /**** Save (write) current settings to EEPROM ****/ else if (upCmd == "AT&W") { writeSettings(); sendResult(R_OK); } /**** Set or display a speed dial number ****/ else if (upCmd.indexOf("AT&Z") == 0) { byte speedNum = upCmd.substring(4, 5).toInt(); if (speedNum >= 0 && speedNum <= 9) { if (upCmd.substring(5, 6) == "=") { String speedDial = cmd; storeSpeedDial(speedNum, speedDial.substring(6)); sendResult(R_OK); } if (upCmd.substring(5, 6) == "?") { sendString(speedDials[speedNum]); sendResult(R_OK); } } else { sendResult(R_ERROR); } } /**** Set WiFi SSID ****/ else if (upCmd.indexOf("AT$SSID=") == 0) { ssid = cmd.substring(8); sendResult(R_OK); } /**** Display WiFi SSID ****/ else if (upCmd == "AT$SSID?") { sendString(ssid); sendResult(R_OK); } /**** Set WiFi Password ****/ else if (upCmd.indexOf("AT$PASS=") == 0) { password = cmd.substring(8); sendResult(R_OK); } /**** Display WiFi Password ****/ else if (upCmd == "AT$PASS?") { sendString(password); sendResult(R_OK); } /**** Reset EEPROM and current settings to factory defaults ****/ else if (upCmd == "AT&F") { defaultEEPROM(); readSettings(); sendResult(R_OK); } /**** Set auto answer off ****/ else if (upCmd == "ATS0=0") { autoAnswer = false; sendResult(R_OK); } /**** Set auto answer on ****/ else if (upCmd == "ATS0=1") { autoAnswer = true; sendResult(R_OK); } /**** Display auto answer setting ****/ else if (upCmd == "ATS0?") { sendString(String(autoAnswer)); sendResult(R_OK); } /**** Set PET MCTerm Translate On ****/ else if (upCmd == "ATPET=1") { petTranslate = true; sendResult(R_OK); } /**** Set PET MCTerm Translate Off ****/ else if (upCmd == "ATPET=0") { petTranslate = false; sendResult(R_OK); } /**** Display PET MCTerm Translate Setting ****/ else if (upCmd == "ATPET?") { sendString(String(petTranslate)); sendResult(R_OK); } /**** Set HEX Translate On ****/ else if (upCmd == "ATHEX=1") { hex = true; sendResult(R_OK); } /**** Set HEX Translate Off ****/ else if (upCmd == "ATHEX=0") { hex = false; sendResult(R_OK); } /**** Hang up a call ****/ else if (upCmd.indexOf("ATH") == 0) { hangUp(); } /**** Hang up a call ****/ else if (upCmd.indexOf("AT$RB") == 0) { sendResult(R_OK); Serial.flush(); delay(500); ESP.reset(); } /**** Exit modem command mode, go online ****/ else if (upCmd == "ATO") { if (callConnected == 1) { sendResult(R_CONNECT); cmdMode = false; } else { sendResult(R_ERROR); } } /**** Set incoming TCP server port ****/ else if (upCmd.indexOf("AT$SP=") == 0) { tcpServerPort = upCmd.substring(6).toInt(); sendString("CHANGES REQUIRES NV SAVE (AT&W) AND RESTART"); sendResult(R_OK); } /**** Display icoming TCP server port ****/ else if (upCmd == "AT$SP?") { sendString(String(tcpServerPort)); sendResult(R_OK); } /**** See my IP address ****/ else if (upCmd == "ATIP?") { Serial.println(WiFi.localIP()); sendResult(R_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); // Establish connection if (!tcpClient.connect(hostChr, port)) { sendResult(R_NOCARRIER); connectTime = 0; callConnected = false; setCarrier(callConnected); } else { sendResult(R_CONNECT); connectTime = millis(); cmdMode = false; callConnected = true; setCarrier(callConnected); // 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 sendResult(R_ERROR); cmd = ""; } // RTS/CTS protocol is a method of handshaking which uses one wire in each direction to allow each // device to indicate to the other whether or not it is ready to receive data at any given moment. // One device sends on RTS and listens on CTS; the other does the reverse. A device should drive // its handshake-output wire low when it is ready to receive data, and high when it is not. A device // that wishes to send data should not start sending any bytes while the handshake-input wire is low; // if it sees the handshake wire go high, it should finish transmitting the current byte and then wait // for the handshake wire to go low before transmitting any more. // http://electronics.stackexchange.com/questions/38022/what-is-rts-and-cts-flow-control void handleFlowControl() { if (flowControl == F_NONE) return; if (flowControl == F_HARDWARE) { if (digitalRead(CTS_PIN) == pinPolarity) txPaused = true; else txPaused = false; } if (flowControl == F_SOFTWARE) { } } /** Arduino main loop function */ void loop() { // Check flow control handleFlowControl(); // Service the Web server webServer.handleClient(); // Check to see if user is requesting rate change to 300 baud checkButton(); // New unanswered incoming connection on server listen socket if (tcpServer.hasClient()) { handleIncomingConnection(); } /**** AT command mode ****/ if (cmdMode == true) { // In command mode - don't exchange with TCP but gather characters to a string if (Serial.available()) { char chr = Serial.read(); if (petTranslate == true) { // Fix PET MCTerm 1.26C Pet->ASCII encoding to actual ASCII if (chr > 127) chr-= 128; } else // Convert uppercase PETSCII to lowercase ASCII (C64) in command mode only if ((chr >= 193) && (chr <= 218)) chr-= 96; // 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) || (chr == 20)) { cmd.remove(cmd.length() - 1); if (echo == true) { Serial.write(chr); } } else { if (cmd.length() < MAX_CMD_LENGTH) cmd.concat(chr); if (echo == true) { Serial.write(chr); } if (hex) { Serial.print(chr, HEX); } } } } /**** 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); // Enter command 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++; } } } // Fix PET MCTerm 1.26C Pet->ASCII encoding to actual ASCII if (petTranslate == true) { for (int i = len - 1; i >= 0; i--) { if (txBuf[i] > 127) txBuf[i]-= 128; } } // Write the buffer to TCP finally tcpClient.write(&txBuf[0], len); yield(); } // Transmit from TCP to terminal while (tcpClient.available() && txPaused == false) { led_on(); uint8_t rxByte = tcpClient.read(); // Is a telnet control code starting? if ((telnet == true) && (rxByte == 0xff)) { #ifdef DEBUG Serial.print(""); #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(""); #endif } else { // Non-control codes pass through freely Serial.write(rxByte); yield(); Serial.flush(); yield(); } handleFlowControl(); } } // If we have received "+++" as last bytes from serial port and there // has been over a second without any more bytes if (plusCount >= 3) { if (millis() - plusTime > 1000) { //tcpClient.stop(); cmdMode = true; sendResult(R_OK); plusCount = 0; } } // Go to command mode if TCP disconnected and not in command mode if ((!tcpClient.connected()) && (cmdMode == false) && callConnected == true) { cmdMode = true; sendResult(R_NOCARRIER); connectTime = 0; callConnected = false; setCarrier(callConnected); //if (tcpServerPort > 0) tcpServer.begin(); } // Turn off tx/rx led if it has been lit long enough to be visible if (millis() - ledTime > LED_TIME) digitalWrite(LED2, !digitalRead(LED2)); // toggle LED state }