├── .github └── workflows │ └── build.yml ├── .gitignore ├── LICENSE ├── README.md ├── platformio.ini └── src └── ESP32_cli_eeprom.ino /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build the firmware 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'master' 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-20.04 11 | 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v2 15 | 16 | - name: Installing platformio 17 | run: pip3 install -U platformio 18 | 19 | - name: Building a firmware 20 | run: | 21 | pio lib install 22 | pio run -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .pio 2 | .vscode 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 DominikN 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 | # ESP32_CLI_eeprom 2 | 3 | [![GitHub stars](https://img.shields.io/github/stars/dominikn/ESP32_CLI_eeprom?style=social)](https://github.com/DominikN/ESP32_CLI_eeprom/stargazers/) 4 | 5 | [![Build firmware](https://github.com/DominikN/ESP32_CLI_eeprom/actions/workflows/build.yml/badge.svg)](https://github.com/DominikN/ESP32_CLI_eeprom/actions/workflows/build.yml) 6 | [![GitHub license](https://img.shields.io/github/license/dominikn/ESP32_CLI_eeprom.svg)](https://github.com/dominikn/ESP32_CLI_eeprom/blob/master/LICENSE) 7 | 8 | **_Communicate with ESP32 over the internet using command line interface to save network credentials in the non-volatile memory and control LED. Written using Arduino framework._** 9 | 10 | Command line interface is in many cases the most universal and comfortable way to control things. With CLI you can control your things over the terminal application and in easy way integrate it with your desktop/mobile application. 11 | 12 | In this simple project I will demonstrate: 13 | 14 | - How to **use CLI to write network credentials to non-volatile memory**. You will be able to change Wi-Fi network credentials without reprogramming your board. 15 | - How to **connect over the internet to your ESP32** through a Linux terminal 16 | - How to **control LED over the Internet through CLI** 17 | 18 | 19 | In the project we use a few interesing tools: 20 | 21 | - **[SimpleCLI library](https://github.com/spacehuhn/SimpleCLI)** - 22 | to easily create a command line interface. You don't need to play with parsing strings. That library do that (and much more) for you in a few lines. 23 | - **[Preferences library](https://github.com/espressif/arduino-esp32/blob/master/libraries/Preferences/src/Preferences.h)** - the most elegant way that I have found to interface to non-volatile/EEPROM/Flash memory during run-time from user code. We use that library to store network credentials in the memory and read them after power-on to connent to the network. 24 | - **[Husarnet](https://github.com/husarnet/husarnet)** - Open source, peer-to-peer Virtual LAN (VPN) network thanks to which you can access ESP32 both from LAN network and through the internet, without static IP adressess, setting port forwarding on your router etc. 25 | 26 | 27 | To run the project follow these steps: 28 | 29 | **1. Flashing ESP32** 30 | 31 | Clone the repo and open it using [Visual Studio Code](https://code.visualstudio.com/) with [PlatformIO extension](https://platformio.org/install/ide?install=vscode) installed. 32 | 33 | Connect ESP32 with your laptop using USB cable, and execute: 34 | 35 | ```bash 36 | # Build project 37 | $ pio run 38 | 39 | # Upload firmware 40 | $ pio run --target upload 41 | ``` 42 | 43 | **2. Get Husarnet join code** 44 | 45 | - Register at https://app.husarnet.com/ 46 | - Click **Create network** button, name it (eg. `mynet`), and click **Create** button 47 | - Click **Add element** button and go to the `join code` tab 48 | - Copy your join code (looking like `fc94:b01d:1803:8dd8:b293:5c7d:7639:932a/xxxxxxxxxxxxxxxxxxxxxxxxx`) and save it for later 49 | 50 | **3. Open serial monitor in Arduino IDE:** 51 | 52 | Click the **"PlatformIO: Serial Monitor"** button and access ESP32 CLI. 53 | 54 | **4. Save Wi-Fi network credentials in ESP32 non-volatile memory by typing in the Serial Monitor:** 55 | 56 | `con_wifi -ssid "" -pass ""` 57 | 58 | where in `` and `` place your Wi-Fi credentials. 59 | 60 | **5. Save Husarnet network credentials in ESP32 non-volatile memory by typing in the Serial Monitor:** 61 | 62 | `hnet_join XXXXXXXXXXXXXXXXXXXXXX myesp32"` 63 | 64 | where in `XXXXXXXXXXXXXXXXXXXXXX` is husarnet join code from point 2. 65 | 66 | **6. Reset ESP32, on the serial monitor you should see that is trying to connect to Wi-Fi and Husarnet network.** 67 | 68 | **7. Install Husarnet on your laptop and add it to the same network as ESP32** 69 | 70 | - open Linux terminal and type this line to install Husarnet: `$ curl https://install.husarnet.com/install.sh | sudo bash` 71 | - connect your Linux computer to Husarnet network by executing this command: `$ sudo husarnet join XXXXXXXXXXXXXXXXXXXXXX mycomputer"`, where in `XXXXXXXXXXXXXXXXXXXXXX` is husarnet join code from point 2. 72 | 73 | > Other instalation methods and other platforms are described in: https://husarnet.com/docs/ 74 | 75 | **8. Control your ESP32 through CLI over the Internet:** 76 | 77 | - open Linux terminal and type this line to connect to ESP32: `$ socat - tcp:myesp32:8001` 78 | - type `set_led 1` to turn on LED connected to pin 16. 79 | 80 | Enjoy! :) 81 | -------------------------------------------------------------------------------- /platformio.ini: -------------------------------------------------------------------------------- 1 | 2 | [env] 3 | platform = espressif32 4 | framework = arduino 5 | platform_packages = 6 | framework-arduinoespressif32 @ https://github.com/husarnet/arduino-esp32/releases/download/1.0.4-1/arduino-husarnet-esp32.zip 7 | lib_deps = 8 | https://github.com/spacehuhn/SimpleCLI 9 | 10 | [env:esp32dev] 11 | board = esp32dev 12 | monitor_speed = 115200 13 | upload_speed = 460800 14 | 15 | monitor_filters = esp32_exception_decoder, default 16 | 17 | board_build.partitions = min_spiffs.csv 18 | -------------------------------------------------------------------------------- /src/ESP32_cli_eeprom.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | const int CLI_PORT = 8001; 7 | const int LED_PIN = 16; 8 | 9 | HusarnetServer server(CLI_PORT); 10 | 11 | Preferences preferences; 12 | 13 | SimpleCLI cli; 14 | 15 | Command con_wifi; 16 | Command hnet_join; 17 | Command set_led; 18 | 19 | void taskInternetCLI( void * parameter ); 20 | 21 | void con_wifi_callback(cmd* c); 22 | void hnet_join_callback(cmd* c); 23 | void set_led_callback(cmd* c); 24 | 25 | void setup() { 26 | Serial.begin(115200); 27 | 28 | pinMode(LED_PIN, OUTPUT); 29 | digitalWrite(LED_PIN, LOW); 30 | 31 | // Read Wi-Fi and Husarnet credentials from NVS memory 32 | 33 | preferences.begin("my-app", false); 34 | String ssid = preferences.getString("ssid"); 35 | String pass = preferences.getString("pass"); 36 | String hostname = preferences.getString("hostname"); 37 | String joincode = preferences.getString("joincode"); 38 | preferences.end(); 39 | 40 | 41 | // Configure a command line interface (CLI) 42 | 43 | con_wifi = cli.addCmd("con_wifi", con_wifi_callback); 44 | con_wifi.addArg("ssid"); 45 | con_wifi.addArg("pass"); 46 | 47 | hnet_join = cli.addCmd("hnet_join", hnet_join_callback); 48 | hnet_join.addPosArg("joincode"); 49 | hnet_join.addPosArg("hostname"); 50 | 51 | set_led = cli.addCmd("set_led", set_led_callback); 52 | set_led.addPosArg("state"); 53 | 54 | 55 | // Connect to Wi-Fi 56 | 57 | Serial.println("Connecting to Wi-Fi: "); 58 | if (ssid != "") { 59 | Serial.print("SSID:"); 60 | Serial.println(ssid); 61 | Serial.print("PASS:"); 62 | Serial.println(pass); 63 | 64 | WiFi.begin(ssid.c_str(), pass.c_str()); 65 | for (int j = 0; j < 10; j++) { 66 | if (WiFi.status() != WL_CONNECTED) { 67 | delay(500); 68 | Serial.print("."); 69 | if (j == 9) { 70 | Serial.printf("\r\nwrong Wi-Fi credentials. Going to commandline:\r\n"); 71 | Serial.println("ESP32 commandline"); 72 | Serial.print("# "); 73 | return; 74 | } 75 | } else { 76 | Serial.println("done"); 77 | Serial.print("IP address: "); 78 | Serial.println(WiFi.localIP()); 79 | break; 80 | } 81 | } 82 | } else { 83 | Serial.println("no credentials in memory"); 84 | } 85 | 86 | 87 | // Connect to Husarnet network 88 | 89 | Serial.println("Connecting to Husarnet: "); 90 | if (hostname != "") { 91 | Serial.print("joincode:"); 92 | Serial.println(joincode); 93 | Serial.print("hostname:"); 94 | Serial.println(hostname); 95 | 96 | Husarnet.selfHostedSetup("default"); 97 | Husarnet.join(joincode.c_str(), hostname.c_str()); 98 | Husarnet.start(); 99 | 100 | xTaskCreate( 101 | taskInternetCLI, /* Task function. */ 102 | "taskWifi", /* String with name of task. */ 103 | 20000, /* Stack size in bytes. */ 104 | NULL, /* Parameter passed as input of the task */ 105 | 2, /* Priority of the task. */ 106 | NULL); /* Task handle. */ 107 | 108 | } else { 109 | Serial.println("no credentials in memory"); 110 | } 111 | 112 | Serial.println("ESP32 commandline"); 113 | Serial.print("# "); 114 | } 115 | 116 | String input; 117 | 118 | void loop() { 119 | 120 | // Check if user typed something into the serial monitor 121 | if (Serial.available()) { 122 | char c = Serial.read(); 123 | input += c; 124 | if (c == '\r') { 125 | cli.parse(input); 126 | input = ""; 127 | } else { 128 | Serial.print(c); 129 | } 130 | } 131 | 132 | // Check for parsing errors 133 | if (cli.errored()) { 134 | // Get error out of queue 135 | CommandError cmdError = cli.getError(); 136 | 137 | // Print the error 138 | Serial.print("ERROR: "); 139 | Serial.println(cmdError.toString()); 140 | 141 | // Print correct command structure 142 | if (cmdError.hasCommand()) { 143 | Serial.print("Did you mean \""); 144 | Serial.print(cmdError.getCommand().toString()); 145 | Serial.println("\"?"); 146 | } 147 | Serial.print("\r\n# "); 148 | } 149 | } 150 | 151 | 152 | void con_wifi_callback(cmd* c) { 153 | Command cmd(c); 154 | 155 | String ssid = cmd.getArg("ssid").getValue(); 156 | String pass = cmd.getArg("pass").getValue(); 157 | 158 | preferences.begin("my-app", false); 159 | preferences.putString("ssid", ssid); 160 | preferences.putString("pass", pass); 161 | preferences.end(); 162 | 163 | Serial.println("\r\nWi-Fi credentials saved. Reset to connect."); 164 | } 165 | 166 | void hnet_join_callback(cmd* c) { 167 | Command cmd(c); 168 | 169 | String hostname = cmd.getArg("hostname").getValue(); 170 | String joincode = cmd.getArg("joincode").getValue(); 171 | 172 | preferences.begin("my-app", false); 173 | preferences.putString("hostname", hostname); 174 | preferences.putString("joincode", joincode); 175 | preferences.end(); 176 | 177 | Serial.println("\r\nHusarnet credentials saved. Reset to connect."); 178 | } 179 | 180 | void set_led_callback(cmd* c) { 181 | Command cmd(c); 182 | 183 | String state = cmd.getArg("state").getValue(); 184 | 185 | switch (state.toInt()) { 186 | case 0: 187 | digitalWrite(LED_PIN, LOW); 188 | break; 189 | case 1: 190 | digitalWrite(LED_PIN, HIGH); 191 | break; 192 | } 193 | 194 | Serial.println("\r\nHusarnet credentials saved. Reset to connect."); 195 | } 196 | 197 | void taskInternetCLI( void * parameter ) 198 | { 199 | server.begin(); 200 | 201 | while (1) { 202 | HusarnetClient client = server.available(); 203 | if (client) { 204 | Serial.println("New Client."); 205 | String currentLine = ""; 206 | Serial.printf("connected: %d\r\n", client.connected()); 207 | while (client.connected()) { 208 | 209 | if (client.available()) { 210 | char c = client.read(); 211 | currentLine += c; 212 | 213 | if (c == '\n') { 214 | cli.parse(currentLine); 215 | currentLine = ""; 216 | client.println("ok"); 217 | } 218 | } 219 | } 220 | 221 | client.stop(); 222 | Serial.println("Client disconnected."); 223 | Serial.println(""); 224 | } else { 225 | delay(200); 226 | } 227 | } 228 | } 229 | --------------------------------------------------------------------------------