From e65c811d4920a80f0fff47a1ef3f2ef8fd3c4f0b Mon Sep 17 00:00:00 2001 From: Ty Fiero Date: Tue, 12 Mar 2024 01:41:46 -0700 Subject: [PATCH] Default to ngrok, fix portal, fix tunneling --- 01OS/01OS/clients/esp32/src/client/client.ino | 237 +++++++++++++----- 01OS/01OS/server/tunnel.py | 76 ++++-- 01OS/start.py | 2 +- 3 files changed, 239 insertions(+), 76 deletions(-) diff --git a/01OS/01OS/clients/esp32/src/client/client.ino b/01OS/01OS/clients/esp32/src/client/client.ino index 576ff38..d028be7 100644 --- a/01OS/01OS/clients/esp32/src/client/client.ino +++ b/01OS/01OS/clients/esp32/src/client/client.ino @@ -19,7 +19,7 @@ int server_port = 8000; // Pre reading on the fundamentals of captive portals https://textslashplain.com/2022/06/24/captive-portals/ -const char *ssid = "captive"; // FYI The SSID can't have a space in it. +const char *ssid = "01-Light"; // FYI The SSID can't have a space in it. // const char * password = "12345678"; //Atleast 8 chars const char *password = NULL; // no password @@ -38,7 +38,21 @@ const int kNetworkDelay = 1000; String generateHTMLWithSSIDs() { - String html = "

Select Wi-Fi Network

"; int n = WiFi.scanComplete(); for (int i = 0; i < n; ++i) @@ -46,55 +60,122 @@ String generateHTMLWithSSIDs() html += ""; } - html += "

"; + html += "




" + "
" + ""; return html; } -const char index_html[] PROGMEM = R"=====( +const char post_connected_html[] PROGMEM = R"=====( - WiFi Setup + 01OS Setup -

WiFi Setup

-
-
-
-
-

- +

01OS Setup

+ +
+

+

+
+ + + + +

)====="; -const char post_connected_html[] PROGMEM = R"=====( +String successHtml = R"=====( 01OS Setup -

01OS Setup

-
-
-
- -
+

Connected to 01OS!

+

You can now close this window

)====="; @@ -167,28 +248,50 @@ void connectToWifi(String ssid, String password) bool connectTo01OS(String server_address) { int err = 0; + int port = 80; String domain; String portStr; - if (server_address.indexOf(":") != -1) { - domain = server_address.substring(0, server_address.indexOf(':')); - portStr = server_address.substring(server_address.indexOf(':') + 1); + // Remove http and https, as it causes errors in HttpClient, the library relies on adding the host header itself + if (server_address.startsWith("http://")) { + server_address.remove(0, 7); + + } else if (server_address.startsWith("https://")) { + server_address.remove(0, 8); + + } + + // Remove trailing slash, causes issues + if (server_address.endsWith("/")) { + server_address.remove(server_address.length() - 1); + } + + int colonIndex = server_address.indexOf(':'); + if (colonIndex != -1) { + domain = server_address.substring(0, colonIndex); + portStr = server_address.substring(colonIndex + 1); } else { domain = server_address; - portStr = ""; // or any default value you want to assign + portStr = ""; } - int port = 0; // Default port value + + WiFiClient c; + + + //If there is a port, set it if (portStr.length() > 0) { port = portStr.toInt(); } - WiFiClient c; - HttpClient http(c, domain.c_str(), port); - + HttpClient http(c, domain.c_str(), port); Serial.println("Connecting to 01OS at " + domain + ":" + port + "/ping"); + + if (domain.indexOf("ngrok") != -1) { + http.sendHeader("ngrok-skip-browser-warning", "80"); + } + err = http.get("/ping"); - // err = http.get("arduino.cc", "/"); bool connectionSuccess = false; if (err == 0) @@ -215,20 +318,21 @@ bool connectTo01OS(String server_address) Serial.print("Content length is: "); Serial.println(bodyLen); Serial.println(); - Serial.println("Body returned follows:"); + Serial.println("Body:"); // Now we've got to the body, so we can print it out unsigned long timeoutStart = millis(); char c; // Whilst we haven't timed out & haven't reached the end of the body while ((http.connected() || http.available()) && - ((millis() - timeoutStart) < kNetworkTimeout)) + ((millis() - timeoutStart) < kNetworkTimeout)) { if (http.available()) { c = http.read(); // Print out this character Serial.print(c); + Serial.print(""); bodyLen--; // We read something, reset the timeout counter @@ -256,7 +360,7 @@ bool connectTo01OS(String server_address) } else { - Serial.print("Connect failed: "); + Serial.print("Connection failed: "); Serial.println(err); } return connectionSuccess; @@ -304,18 +408,18 @@ void setUpWebserver(AsyncWebServer &server, const IPAddress &localIP) // Serve Basic HTML Page server.on("/", HTTP_ANY, [](AsyncWebServerRequest *request) { - String htmlContent = index_html; - Serial.printf("wifi scan complete: %d . WIFI_SCAN_RUNNING: %d", WiFi.scanComplete(), WIFI_SCAN_RUNNING); + String htmlContent = ""; + Serial.printf("Wifi scan complete: %d . WIFI_SCAN_RUNNING: %d", WiFi.scanComplete(), WIFI_SCAN_RUNNING); if(WiFi.scanComplete() > 0) { // Scan complete, process results - Serial.println("done scanning wifi"); + Serial.println("Done scanning wifi"); htmlContent = generateHTMLWithSSIDs(); // WiFi.scanNetworks(true); // Start a new scan in async mode } AsyncWebServerResponse *response = request->beginResponse(200, "text/html", htmlContent); response->addHeader("Cache-Control", "public,max-age=31536000"); // save this file to cache for 1 year (unless you refresh) request->send(response); - Serial.println("Served Basic HTML Page"); }); + Serial.println("Served HTML Page"); }); // the catch all server.onNotFound([](AsyncWebServerRequest *request) @@ -335,12 +439,19 @@ void setUpWebserver(AsyncWebServer &server, const IPAddress &localIP) // Check if SSID parameter exists and assign it if(request->hasParam("ssid", true)) { ssid = request->getParam("ssid", true)->value(); + // If "OTHER" is selected, use the value from "otherSSID" + if(ssid == "OTHER" && request->hasParam("otherSSID", true)) { + ssid = request->getParam("otherSSID", true)->value(); + Serial.println("OTHER SSID SELECTED: " + ssid); + } } - + // Check if Password parameter exists and assign it if(request->hasParam("password", true)) { password = request->getParam("password", true)->value(); } + // Serial.println(ssid); + // Serial.println(password); // Attempt to connect to the Wi-Fi network with these credentials connectToWifi(ssid, password); @@ -373,13 +484,27 @@ void setUpWebserver(AsyncWebServer &server, const IPAddress &localIP) if (connectedToServer) { - connectionMessage = "Connected to 01OS " + server_address; + AsyncWebServerResponse *response = request->beginResponse(200, "text/html", successHtml); + response->addHeader("Cache-Control", "no-cache, no-store, must-revalidate"); // Prevent caching of this page + request->send(response); + Serial.println(" "); + Serial.println("Connected to 01 websocket!"); + Serial.println(" "); + Serial.println("Served success HTML Page"); } else { - connectionMessage = "Couldn't connect to 01OS " + server_address; + // If connection fails, serve the error page instead of sending plain text + String htmlContent = String(post_connected_html); // Load your HTML template + // Inject the error message + htmlContent.replace("

", "

Error connecting, please try again.

"); + + AsyncWebServerResponse *response = request->beginResponse(200, "text/html", htmlContent); + response->addHeader("Cache-Control", "no-cache, no-store, must-revalidate"); // Prevent caching of this page + request->send(response); + Serial.println("Served Post connection HTML Page with error message"); } - request->send(200, "text/plain", connectionMessage); }); + }); } // ----------------------- END OF WIFI CAPTIVE PORTAL ------------------- @@ -444,7 +569,7 @@ void InitI2SSpeakerOrMic(int mode) #if ESP_IDF_VERSION > ESP_IDF_VERSION_VAL(4, 1, 0) .communication_format = I2S_COMM_FORMAT_STAND_I2S, // Set the format of the communication. -#else // 设置通讯格式 +#else .communication_format = I2S_COMM_FORMAT_I2S, #endif .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, @@ -478,7 +603,7 @@ void InitI2SSpeakerOrMic(int mode) err += i2s_set_pin(SPEAKER_I2S_NUMBER, &tx_pin_config); // Serial.println("Init i2s_set_clk"); err += i2s_set_clk(SPEAKER_I2S_NUMBER, 16000, I2S_BITS_PER_SAMPLE_16BIT, - I2S_CHANNEL_MONO); + I2S_CHANNEL_MONO); } void speaker_play(uint8_t *payload, uint32_t len) @@ -487,7 +612,7 @@ void speaker_play(uint8_t *payload, uint32_t len) size_t bytes_written; InitI2SSpeakerOrMic(MODE_SPK); i2s_write(SPEAKER_I2S_NUMBER, payload, len, - &bytes_written, portMAX_DELAY); + &bytes_written, portMAX_DELAY); } void webSocketEvent(WStype_t type, uint8_t *payload, size_t length) @@ -551,7 +676,7 @@ void websocket_setup(String server_domain, int port) return; } Serial.println("connected to WiFi"); - webSocket.begin(server_domain, port, "/"); + webSocket.begin(server_domain, 80, "/"); webSocket.onEvent(webSocketEvent); // webSocket.setAuthorization("user", "Password"); webSocket.setReconnectInterval(5000); @@ -559,7 +684,7 @@ void websocket_setup(String server_domain, int port) void flush_microphone() { - Serial.printf("[microphone] flushing %d bytes of data\n", data_offset); + Serial.printf("[microphone] flushing and sending %d bytes of data\n", data_offset); if (data_offset == 0) return; webSocket.sendBIN(microphonedata0, data_offset); @@ -601,7 +726,7 @@ void setup() Serial.print("\n"); M5.begin(true, false, true); - M5.dis.drawpix(0, CRGB(128, 128, 0)); + M5.dis.drawpix(0, CRGB(255, 0, 50)); } void loop() @@ -615,18 +740,18 @@ void loop() if (server_domain != "") { Serial.println("Setting up websocket to 01OS " + server_domain + ":" + server_port); - websocket_setup(server_domain, server_port); InitI2SSpeakerOrMic(MODE_SPK); hasSetupWebsocket = true; + M5.dis.drawpix(0, CRGB(0, 128, 150)); Serial.println("Websocket connection flow completed"); } - else - { - Serial.println("No valid 01OS server address yet..."); - } + // else + // { + // // Serial.println("No valid 01OS server address yet..."); + // } // If connected, you might want to do something, like printing the IP address // Serial.println("Connected to WiFi!"); // Serial.println("IP Address: " + WiFi.localIP().toString()); diff --git a/01OS/01OS/server/tunnel.py b/01OS/01OS/server/tunnel.py index 25f3e52..abbfcb8 100644 --- a/01OS/01OS/server/tunnel.py +++ b/01OS/01OS/server/tunnel.py @@ -5,7 +5,7 @@ import shutil import time from ..utils.print_markdown import print_markdown -def create_tunnel(tunnel_method='bore', server_host='localhost', server_port=8000): +def create_tunnel(tunnel_method='ngrok', server_host='localhost', server_port=8000): print_markdown(f"Exposing server to the internet...") if tunnel_method == "bore": @@ -17,41 +17,79 @@ def create_tunnel(tunnel_method='bore', server_host='localhost', server_port=800 exit(1) time.sleep(6) - output = subprocess.check_output(f'bore local {server_port} --to bore.pub', shell=True) + # output = subprocess.check_output(f'bore local {server_port} --to bore.pub', shell=True) + process = subprocess.Popen(f'bore local {server_port} --to bore.pub', shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True) - for line in output.split('\n'): + while True: + line = process.stdout.readline() + print(line) + if not line: + break if "listening at bore.pub:" in line: remote_port = re.search('bore.pub:([0-9]*)', line).group(1) print_markdown(f"Your server is being hosted at the following URL: bore.pub:{remote_port}") break - + elif tunnel_method == "localtunnel": - if not subprocess.call('command -v lt', shell=True): + if subprocess.call('command -v lt', shell=True): print("The 'lt' command is not available.") print("Please ensure you have Node.js installed, then run 'npm install -g localtunnel'.") print("For more information, see https://github.com/localtunnel/localtunnel") exit(1) else: - output = subprocess.check_output(f'npx localtunnel --port {server_port}', shell=True) - for line in output.split('\n'): - if "your url is: https://" in line: - remote_url = re.search('https://([a-zA-Z0-9.-]*)', line).group(0).replace('https://', '') - print(f"Your server is being hosted at the following URL: {remote_url}") - break + process = subprocess.Popen(f'npx localtunnel --port {server_port}', shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True) + + found_url = False + url_pattern = re.compile(r'your url is: https://[a-zA-Z0-9.-]+') + + while True: + line = process.stdout.readline() + if not line: + break # Break out of the loop if no more output + match = url_pattern.search(line) + if match: + found_url = True + remote_url = match.group(0).replace('your url is: ', '') + + print(f"\nYour server is being hosted at the following URL: {remote_url}") + break # Exit the loop once the URL is found + + if not found_url: + print("Failed to extract the localtunnel URL. Please check localtunnel's output for details.") elif tunnel_method == "ngrok": - if not subprocess.call('command -v ngrok', shell=True): + + # Check if ngrok is installed + is_installed = subprocess.check_output('command -v ngrok', shell=True).decode().strip() + if not is_installed: print("The ngrok command is not available.") print("Please install ngrok using the instructions at https://ngrok.com/docs/getting-started/") exit(1) - else: - output = subprocess.check_output(f'ngrok http {server_port} --log stdout', shell=True) - for line in output.split('\n'): - if "started tunnel" in line: - remote_url = re.search('https://([a-zA-Z0-9.-]*)', line).group(0).replace('https://', '') - print(f"Your server is being hosted at the following URL: {remote_url}") - break + # If ngrok is installed, start it on the specified port + # process = subprocess.Popen(f'ngrok http {server_port} --log=stdout', shell=True, stdout=subprocess.PIPE) + process = subprocess.Popen(f'ngrok http {server_port} --scheme http,https --log=stdout', shell=True, stdout=subprocess.PIPE) + + # Initially, no URL is found + found_url = False + # Regular expression to match the ngrok URL + url_pattern = re.compile(r'https://[a-zA-Z0-9-]+\.ngrok(-free)?\.app') + + # Read the output line by line + while True: + line = process.stdout.readline().decode('utf-8') + if not line: + break # Break out of the loop if no more output + match = url_pattern.search(line) + if match: + found_url = True + remote_url = match.group(0) + + print(f"\nYour server is being hosted at the following URL: {remote_url}") + break # Exit the loop once the URL is found + + if not found_url: + print("Failed to extract the ngrok tunnel URL. Please check ngrok's output for details.") diff --git a/01OS/start.py b/01OS/start.py index d506501..759698a 100644 --- a/01OS/start.py +++ b/01OS/start.py @@ -15,7 +15,7 @@ def run( server_host: str = typer.Option("0.0.0.0", "--server-host", help="Specify the server host where the server will deploy"), server_port: int = typer.Option(8000, "--server-port", help="Specify the server port where the server will deploy"), - tunnel_service: str = typer.Option("bore", "--tunnel-service", help="Specify the tunnel service"), + tunnel_service: str = typer.Option("ngrok", "--tunnel-service", help="Specify the tunnel service"), expose: bool = typer.Option(False, "--expose", help="Expose server to internet"), client: bool = typer.Option(False, "--client", help="Run client"),