diff --git a/Arduino/esp32_gateway/udp_forward_only/esp32_gateway/Esp32.ino b/Arduino/esp32_gateway/udp_forward_only/esp32_gateway/Esp32.ino new file mode 100644 index 0000000..65182bd --- /dev/null +++ b/Arduino/esp32_gateway/udp_forward_only/esp32_gateway/Esp32.ino @@ -0,0 +1,168 @@ +#include +#include + +#define RXD2 16 // ESP32 RX2 (connect to Arduino TX through a divider!) +#define TXD2 17 // ESP32 TX2 (connect to Arduino RX) +#define BAUD2 9600 + +// ====== Config ====== +static const bool USE_AP_MODE = true; // true = ESP32 creates AP; false = join STA + +// AP credentials (if USE_AP_MODE) +const char* AP_SSID = "BridgeControl-ESP32"; +const char* AP_PASS = "bridge1234"; + +// STA credentials (if !USE_AP_MODE) +const char* STA_SSID = "YOUR_WIFI"; +const char* STA_PASS = "YOUR_PASSWORD"; + +// ====== HTTP Server ====== +WebServer server(80); + +// ---- Minimal UI (optional) ---- +static const char INDEX_HTML[] = R"HTML( + +Bridge Control + +

Bridge Control (ESP32)

+
+
+ + + + + + +
+
Ready…
+
+ +)HTML"; + +// ====== Helpers ====== +String readLineSerial2(uint32_t timeout_ms=1000){ + String line; uint32_t t0 = millis(); + while (millis() - t0 < timeout_ms){ + while (Serial2.available()){ + char c = (char)Serial2.read(); + if (c == '\n'){ line.trim(); return line; } + if (c != '\r') line += c; + } + delay(2); + } + line.trim(); return line; // may be empty (timeout) +} + +// super-light JSON extractor for a top-level string value +String jsonGetString(const String& body, const char* key){ + int pos = 0; + while (true){ + int q1 = body.indexOf('"', pos); if (q1 < 0) break; + int q2 = body.indexOf('"', q1+1); if (q2 < 0) break; + String k = body.substring(q1+1, q2); + int colon = body.indexOf(':', q2+1); if (colon < 0) break; + if (k == key){ + // value as string "..." + int vq1 = body.indexOf('"', colon+1); if (vq1 < 0) return ""; + int vq2 = body.indexOf('"', vq1+1); if (vq2 < 0) return ""; + return body.substring(vq1+1, vq2); + } + pos = q2 + 1; + } + return ""; +} + +void sendCORS(){ + server.sendHeader("Access-Control-Allow-Origin", "*"); + server.sendHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS"); + server.sendHeader("Access-Control-Allow-Headers", "Content-Type"); +} + +// ====== Handlers ====== +void handleIndex(){ server.send(200, "text/html", INDEX_HTML); } + +void handleOptions(){ sendCORS(); server.send(204); } + +void handleCmd(){ + sendCORS(); + if (server.method() != HTTP_POST){ + server.send(405, "application/json", "{\"ok\":false,\"reason\":\"use POST\"}"); + return; + } + + String body = server.arg("plain"); + String op = jsonGetString(body, "op"); // expect string "5", "6", ... + if (op.length() == 0){ + server.send(400, "application/json", "{\"ok\":false,\"reason\":\"missing op\"}"); + return; + } + + // Forward to Arduino (newline-terminated) + Serial.print("[SEND] "); Serial.println(op); + Serial2.print(op); Serial2.print('\n'); + + // Wait for Arduino one-line ACK/response + String nano = readLineSerial2(1000); + + // minimal escaping of \ and " + nano.replace("\\", "\\\\"); nano.replace("\"", "\\\""); + + String json = String("{\"ok\":") + (nano.length()? "true":"false") + + ",\"op\":\"" + op + "\"" + + ",\"nano_reply\":\"" + nano + "\"}"; + server.send(200, "application/json", json); +} + +// ====== Setup / Loop ====== +void setup(){ + Serial.begin(115200); + Serial2.begin(BAUD2, SERIAL_8N1, RXD2, TXD2); + delay(200); + Serial.println("\n[ESP32] Booting…"); + + if (USE_AP_MODE){ + WiFi.mode(WIFI_AP); + WiFi.softAP(AP_SSID, AP_PASS); + Serial.print("[AP] SSID: "); Serial.print(AP_SSID); + Serial.print(" PASS: "); Serial.println(AP_PASS); + Serial.print("[AP] Open: http://"); Serial.println(WiFi.softAPIP()); + } else { + WiFi.mode(WIFI_STA); + WiFi.begin(STA_SSID, STA_PASS); + Serial.print("[STA] Connecting"); + for (int i=0; i<60 && WiFi.status()!=WL_CONNECTED; i++){ delay(250); Serial.print('.'); } + Serial.println(); + if (WiFi.status()==WL_CONNECTED){ + Serial.print("[STA] IP: "); Serial.println(WiFi.localIP()); + } else { + Serial.println("[STA] Failed; falling back to AP"); + WiFi.mode(WIFI_AP); + WiFi.softAP(AP_SSID, AP_PASS); + Serial.print("[AP] IP: "); Serial.println(WiFi.softAPIP()); + } + } + + server.on("/", HTTP_GET, handleIndex); + server.on("/cmd", HTTP_OPTIONS, handleOptions); + server.on("/cmd", HTTP_POST, handleCmd); + server.begin(); + Serial.println("[HTTP] Server started"); +} + +void loop(){ server.handleClient(); } diff --git a/Arduino/esp32_gateway_udp_ack/esp32_udp_ack.ino b/Arduino/esp32_gateway_udp_ack/esp32_udp_ack.ino new file mode 100644 index 0000000..f18a5be --- /dev/null +++ b/Arduino/esp32_gateway_udp_ack/esp32_udp_ack.ino @@ -0,0 +1,101 @@ +#include +#include + +// ===== Choose ONE ===== +// A) ESP32 as Access Point (PC connects to it) +const char* AP_SSID = "ESP32_AP"; +const char* AP_PASS = "esp32pass"; + +// B) Station mode (ESP joins router) +// const char* STA_SSID = "YourRouterSSID"; +// const char* STA_PASS = "YourRouterPassword"; + +WiFiUDP udp; +const uint16_t PORT = 42100; + +void setup() { + Serial.begin(115200); + delay(300); + + // --- AP mode (simple for testing) + WiFi.mode(WIFI_AP); + bool ok = WiFi.softAP(AP_SSID, AP_PASS); + Serial.println(ok ? "[AP] started" : "[AP] failed"); + Serial.print("[AP] IP: "); Serial.println(WiFi.softAPIP()); + + // --- Station mode (use instead of AP if needed) + /* + WiFi.mode(WIFI_STA); + WiFi.begin(STA_SSID, STA_PASS); + Serial.print("Joining WiFi"); + while (WiFi.status() != WL_CONNECTED) { delay(400); Serial.print("."); } + Serial.println("\n[STA] connected"); + Serial.print("[STA] IP: "); Serial.println(WiFi.localIP()); + */ + + if (udp.begin(PORT)) { + Serial.print("UDP listening on "); Serial.println(PORT); + } else { + Serial.println("udp.begin failed"); + } +} + +static String tinyValue(const String& key, const String& json) { + // naive: find "key": then read value (string or bare) + String pat = "\"" + key + "\""; + int i = json.indexOf(pat); + if (i < 0) return ""; + i = json.indexOf(':', i); + if (i < 0) return ""; + i++; + while (i < (int)json.length() && isspace(json[i])) i++; + if (i >= (int)json.length()) return ""; + if (json[i] == '"') { + int j = json.indexOf('"', i + 1); + if (j < 0) return ""; + return json.substring(i + 1, j); + } else { + int j = i; + while (j < (int)json.length() && json[j] != ',' && json[j] != '}' && !isspace(json[j])) j++; + return json.substring(i, j); + } +} + +void loop() { + int size = udp.parsePacket(); + if (size > 0) { + char buf[1500]; + int len = udp.read(buf, sizeof(buf) - 1); + if (len <= 0) return; + buf[len] = '\0'; + + String body(buf); + IPAddress rip = udp.remoteIP(); + uint16_t rport = udp.remotePort(); + + Serial.print("Recv: "); Serial.println(body); + + String type = tinyValue("type", body); + String response; + + if (type == "handshake") { + response = String("{\"type\":\"handshake_ack\",\"ack\":true}"); + } else if (type == "command") { + // minimal echo-ack + String op = tinyValue("op", body); + response = String("{\"type\":\"ack\",\"ack\":true,\"op\":\"") + op + "\"}"; + } else if (type == "ack") { + // optional final ack from PC; just show it + Serial.println("PC sent final ack."); + return; + } else { + response = String("{\"type\":\"error\",\"ack\":false}"); + } + + udp.beginPacket(rip, rport); + udp.write((const uint8_t*)response.c_str(), response.length()); + udp.endPacket(); + + Serial.print("Sent: "); Serial.println(response); + } +} diff --git a/Arduino/esp32_week_7_code.ino b/Arduino/esp32_week_7_code.ino index edad611..5a385f8 100644 --- a/Arduino/esp32_week_7_code.ino +++ b/Arduino/esp32_week_7_code.ino @@ -109,4 +109,4 @@ void loop() { udp.endPacket(); Serial.print("Sent: "); Serial.println(response); -} \ No newline at end of file +} diff --git a/ui/src/app/page.tsx b/ui/src/app/page.tsx index b901853..41070d8 100644 --- a/ui/src/app/page.tsx +++ b/ui/src/app/page.tsx @@ -461,7 +461,7 @@ export default function DemoPage() { .env.local to the device static IP. - Upload sketch from /Arduino/esp32. Ensure Wi-Fi creds are correct. + Upload sketch from /Arduino/esp32_gateway. Ensure Wi-Fi creds are correct. Flash the logic controller. Connect Tx/Rx pins between boards.