├── LICENSE ├── README.md └── TelnetClient-M5Cardputer.ino /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 aat440hz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | M5Cardputer Telnet Client 2 | 3 | This is a simple Telnet client example for the M5Cardputer, an ESP32-based development board with a built-in screen and keyboard. This sketch allows you to connect to a Telnet server over Wi-Fi and send/receive text data. 4 | 5 | -Prerequisites 6 | 7 | Before using this sketch, make sure you have the following: 8 | 9 | An M5Cardputer board. 10 | Arduino IDE installed with the necessary libraries. 11 | Access to a Wi-Fi network. 12 | A Telnet server to connect to. 13 | Getting Started 14 | Open the Arduino IDE. 15 | 16 | Install the required libraries if you haven't already. You can install them using the Arduino Library Manager. Search for and install the following libraries: 17 | 18 | WiFi 19 | M5Cardputer 20 | M5GFX 21 | Upload the provided sketch (M5Cardputer_Telnet_Client.ino) to your M5Cardputer board. 22 | 23 | Replace the following placeholders in the sketch with your Wi-Fi and Telnet server information: 24 | 25 | const char* ssid = "Your_SSID"; // Replace with your WiFi SSID 26 | 27 | const char* password = "Your_Password"; // Replace with your WiFi password 28 | 29 | const char* host = "Your_Telnet_Server_IP"; // Replace with your Telnet server address 30 | 31 | const uint16_t port = 23; // Telnet default port 32 | 33 | Upload the modified sketch to your M5Cardputer board. 34 | 35 | The M5Cardputer should connect to the specified Wi-Fi network and the Telnet server. 36 | 37 | Use the M5Cardputer's keyboard to input text. Press keys to type, use the Enter key to send a message to the Telnet server, and use the Del key to delete characters. 38 | 39 | -How It Works 40 | 41 | This sketch initializes the M5Cardputer, connects to the Wi-Fi network, and establishes a Telnet connection to the specified server. It then continuously monitors the keyboard for input and sends messages to the Telnet server when the Enter key is pressed. 42 | 43 | Received messages from the Telnet server are displayed on the M5Cardputer's screen. 44 | 45 | https://github.com/aat440hz/TelnetServer-M5StickCPlus 46 | 47 | https://github.com/aat440hz/RF433-TelnetChatMorseCode-M5StickCPlus 48 | 49 | https://github.com/aat440hz/RF433-TelnetChat-M5StickCPlus 50 | 51 | -License 52 | 53 | This project is licensed under the MIT License - see the LICENSE file for details. 54 | 55 | -Acknowledgments 56 | 57 | This sketch was created for educational purposes and is based on the M5Cardputer library. 58 | M5Stack - https://github.com/m5stack/M5Cardputer 59 | Feel free to customize and extend this sketch according to your project requirements. 60 | 61 | -Telnet Servers 62 | 63 | Here is a list of Telnet servers online you can test this script out with! 64 | 65 | horizons.jpl.nasa.gov port 6775 66 | 67 | telehack.com port 23 68 | 69 | resort.org port 2323 70 | 71 | -------------------------------------------------------------------------------- /TelnetClient-M5Cardputer.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include "M5Cardputer.h" 3 | 4 | // WiFi configurations 5 | const char* ssid = "Your_SSID"; // Replace with your WiFi SSID 6 | const char* password = "Your_Password"; // Replace with your WiFi password 7 | 8 | // Variables for server address and port 9 | String serverAddress; 10 | uint16_t port = 23; // Default Telnet port 11 | 12 | // M5Cardputer setup 13 | M5Canvas canvas(&M5Cardputer.Display); 14 | String data = "> "; 15 | int cursorY = 0; 16 | const int lineHeight = 8; 17 | unsigned long lastKeyPressMillis = 0; 18 | const unsigned long debounceDelay = 200; // Adjust debounce delay as needed 19 | 20 | // Telnet Command Codes 21 | const byte IAC = 255; 22 | const byte DO = 253; 23 | const byte DONT = 254; 24 | const byte WILL = 251; 25 | const byte WONT = 252; 26 | 27 | WiFiClient telnetClient; 28 | 29 | // Compile-time flag for ANSI sequence filtering 30 | const bool ansiFilteringEnabled = true; // Set to true or false based on server requirements 31 | 32 | void setup() { 33 | auto cfg = M5.config(); 34 | M5Cardputer.begin(cfg, true); 35 | M5Cardputer.Display.setRotation(1); 36 | M5Cardputer.Display.setTextSize(1); // Set text size 37 | 38 | // Connect to WiFi 39 | WiFi.begin(ssid, password); 40 | while (WiFi.status() != WL_CONNECTED) { 41 | delay(500); 42 | } 43 | 44 | // Prompt for server address and optional port 45 | M5Cardputer.Display.print("Address:Port: "); 46 | String serverInput = waitForInput(); 47 | parseServerInput(serverInput); // Parse and set serverAddress and port 48 | 49 | // Connect to Telnet server 50 | if (!telnetClient.connect(serverAddress.c_str(), port)) { 51 | // Handle connection failure 52 | M5Cardputer.Display.println("Failed to connect"); 53 | } else { 54 | M5Cardputer.Display.println("Connected to " + serverAddress + ":" + String(port)); 55 | } 56 | 57 | // Initialize the cursor Y position 58 | cursorY = M5Cardputer.Display.getCursorY(); 59 | } 60 | 61 | void loop() { 62 | M5Cardputer.update(); 63 | 64 | // Check for and handle any user input first 65 | handleUserInput(); // A new function to encapsulate all user input handling 66 | 67 | // Handle any incoming data from the server 68 | readAndProcessServerData(); 69 | } 70 | 71 | String waitForInput() { 72 | String input = ""; 73 | M5Cardputer.Display.setCursor(90, cursorY); // Set initial cursor position for input 74 | unsigned long lastKeyPressMillis = 0; 75 | const unsigned long debounceDelay = 200; 76 | 77 | while (!M5Cardputer.Keyboard.keysState().enter) { 78 | M5Cardputer.update(); 79 | if (M5Cardputer.Keyboard.isChange()) { 80 | Keyboard_Class::KeysState status = M5Cardputer.Keyboard.keysState(); 81 | 82 | if (status.del && input.length() > 0) { 83 | input.remove(input.length() - 1); // Remove last character from input 84 | // Visually remove character from display: 85 | M5Cardputer.Display.setCursor(M5Cardputer.Display.getCursorX() - 6, cursorY); 86 | M5Cardputer.Display.print(" "); 87 | M5Cardputer.Display.setCursor(M5Cardputer.Display.getCursorX() - 6, cursorY); 88 | lastKeyPressMillis = millis(); 89 | } 90 | 91 | for (auto i : status.word) { 92 | if (millis() - lastKeyPressMillis >= debounceDelay) { 93 | lastKeyPressMillis = millis(); // Update the last key press time 94 | 95 | if (isPrintable(i)) { 96 | input += i; // Append the character to input string 97 | M5Cardputer.Display.print(i); // Show the character on display 98 | } 99 | } 100 | } 101 | 102 | if (status.enter) { 103 | M5Cardputer.Display.println(); // Move to the next line 104 | break; // Break the loop as enter has been pressed 105 | } 106 | } 107 | } 108 | return input; 109 | } 110 | 111 | void parseServerInput(String serverInput) { 112 | int colonIndex = serverInput.indexOf(':'); 113 | if (colonIndex != -1) { 114 | // Split the input into address and port 115 | serverAddress = serverInput.substring(0, colonIndex); 116 | String portStr = serverInput.substring(colonIndex + 1); 117 | port = (portStr.length() > 0) ? portStr.toInt() : 23; // Use default port if none specified 118 | } else { 119 | serverAddress = serverInput; // Entire input is treated as address 120 | port = 23; // Default to port 23 121 | } 122 | } 123 | 124 | void handleUserInput() { 125 | bool controlMode = M5Cardputer.BtnA.isPressed(); 126 | if (M5Cardputer.Keyboard.isChange() && M5Cardputer.Keyboard.isPressed()) { 127 | unsigned long currentMillis = millis(); 128 | if (currentMillis - lastKeyPressMillis >= debounceDelay) { 129 | lastKeyPressMillis = currentMillis; // Update the last key press time 130 | 131 | Keyboard_Class::KeysState status = M5Cardputer.Keyboard.keysState(); 132 | for (auto i : status.word) { 133 | if (controlMode) { 134 | // If BtnA is pressed, modify the character to a control character 135 | char ctrlChar = mapToControlCharacter(i); 136 | telnetClient.write(ctrlChar); // Send control character to Telnet server 137 | 138 | // Display the conventional representation for control characters (e.g., '^C') 139 | M5Cardputer.Display.print('^'); 140 | M5Cardputer.Display.print(toupper(i)); // Display the uppercase letter 141 | } else { 142 | // Normal character handling 143 | data += i; // Add character to the data buffer 144 | M5Cardputer.Display.print(i); // Display the character 145 | } 146 | cursorY = M5Cardputer.Display.getCursorY(); // Update cursor Y position 147 | } 148 | 149 | if (status.del && data.length() > 2) { 150 | data.remove(data.length() - 1); 151 | M5Cardputer.Display.setCursor(M5Cardputer.Display.getCursorX() - 6, M5Cardputer.Display.getCursorY()); 152 | M5Cardputer.Display.print(" "); 153 | M5Cardputer.Display.setCursor(M5Cardputer.Display.getCursorX() - 6, M5Cardputer.Display.getCursorY()); 154 | cursorY = M5Cardputer.Display.getCursorY(); // Update cursor Y position 155 | } 156 | 157 | if (status.enter) { 158 | String message = data.substring(2) + "\r\n"; // Use "\r\n" for newline 159 | telnetClient.write(message.c_str()); // Send message to Telnet server 160 | 161 | data = "> "; 162 | M5Cardputer.Display.print('\n'); // Move to the next line 163 | cursorY = M5Cardputer.Display.getCursorY(); // Update cursor Y position 164 | } 165 | } 166 | } 167 | } 168 | 169 | void readAndProcessServerData() { 170 | bool isAnsiSequence = false; // Track if currently processing an ANSI sequence 171 | while (telnetClient.available()) { 172 | char c = telnetClient.read(); 173 | 174 | if (c == IAC) { 175 | // Handle Telnet command received from the server 176 | handleTelnetCommand(); 177 | } else if (ansiFilteringEnabled && c == '\033') { // Start of an ANSI sequence 178 | isAnsiSequence = true; 179 | } else if (isAnsiSequence) { 180 | if (isalpha(c) || c == '@') { // End of an ANSI sequence ('@' is included for edge cases) 181 | isAnsiSequence = false; // End of ANSI sequence 182 | } 183 | // Don't print ANSI sequence characters 184 | } else { 185 | M5Cardputer.Display.print(c); 186 | cursorY = M5Cardputer.Display.getCursorY(); // Update cursor Y position 187 | } 188 | 189 | // Check for display scrolling 190 | if (cursorY > M5Cardputer.Display.height() - lineHeight) { 191 | M5Cardputer.Display.scroll(0, -lineHeight); 192 | cursorY -= lineHeight; 193 | M5Cardputer.Display.setCursor(M5Cardputer.Display.getCursorX(), cursorY); 194 | } 195 | } 196 | } 197 | 198 | void handleTelnetCommand() { 199 | byte command = telnetClient.read(); 200 | byte option = telnetClient.read(); 201 | 202 | switch (command) { 203 | case DO: 204 | case DONT: 205 | respondToCommand(WONT, option); 206 | break; 207 | case WILL: 208 | case WONT: 209 | respondToCommand(DONT, option); 210 | break; 211 | } 212 | } 213 | 214 | void respondToCommand(byte response, byte option) { 215 | telnetClient.write(IAC); 216 | telnetClient.write(response); 217 | telnetClient.write(option); 218 | } 219 | 220 | char mapToControlCharacter(char key) { 221 | // Map alphabetic characters to ASCII control characters (e.g., Ctrl + A to 1) 222 | if (key >= 'a' && key <= 'z') { 223 | return key - 'a' + 1; // 'a' maps to 1 ('Ctrl+A'), 'z' maps to 26 ('Ctrl+Z') 224 | } else if (key >= 'A' && key <= 'Z') { 225 | return key - 'A' + 1; // 'A' maps to 1 ('Ctrl+A'), 'Z' maps to 26 ('Ctrl+Z') 226 | } else { 227 | // Return the original key if it's not alphabetic 228 | return key; 229 | } 230 | } 231 | --------------------------------------------------------------------------------