diff --git a/.gitignore b/.gitignore index 1784fe7e..02595cce 100644 --- a/.gitignore +++ b/.gitignore @@ -45,5 +45,9 @@ node_modules/ __pycache__/ *.pyc + +# Firmware related: +firmware/panorama/.cache/ + # Runtime directory rundir/ \ No newline at end of file diff --git a/firmware/panorama/.clangd b/firmware/panorama/.clangd new file mode 100644 index 00000000..e3c57092 --- /dev/null +++ b/firmware/panorama/.clangd @@ -0,0 +1,9 @@ +# Strip ESP32/toolchain flags that clang (x86 host) doesn't support when used for IDE diagnostics. +# Use headers from the compiler in compile_commands.json so stdlib.h and other toolchain headers are found. +CompileFlags: + BuiltinHeaders: QueryDriver + Remove: + - -mlongcalls + - -mlong-calls + - -fstrict-volatile-bitfields + - -fno-tree-switch-conversion diff --git a/firmware/panorama/src/main.cpp b/firmware/panorama/src/main.cpp deleted file mode 100644 index c227be3c..00000000 --- a/firmware/panorama/src/main.cpp +++ /dev/null @@ -1,106 +0,0 @@ -#include -#include - -// ===== Wi-Fi Access Point (ESP32 hosts itself) ===== -const char* AP_SSID = "ESP32-JSON"; -const char* AP_PASS = "esp32json"; // must be 8+ chars - -// ===== TCP server ===== -const uint16_t SERVER_PORT = 9000; -WiFiServer server(SERVER_PORT); - -// ===== State ===== -WiFiClient client; // single client for simplicity -unsigned long lastSendMs = 0; // pacing JSON sends -const uint32_t SEND_PERIOD_MS = 1000; - -// make a tiny JSON line without ArduinoJson -String makeJson() { - // dummy data — tweak as you like - static int seq = 0; - float temp = 20.0 + sin(millis() / 1000.0) * 2.5; - int humidity = 40 + (millis()/1000) % 20; - - // ISO-ish time in ms since boot for now - String s = "{"; - s += "\"seq\":" + String(seq++) + ","; - s += "\"ts_ms\":" + String(millis()) + ","; - s += "\"temp_c\":" + String(temp, 2) + ","; - s += "\"humidity\":" + String(humidity) + ","; - s += "\"status\":\"ok\""; - s += "}\n"; // newline-delimited JSON (NDJSON) - return s; -} - -void setup() { - Serial.begin(115200); - delay(200); - - // Start AP (self-hosted) - Serial.println("[WiFi] Starting AP…"); - bool ok = WiFi.softAP(AP_SSID, AP_PASS); - if (!ok) { - Serial.println("[WiFi] AP start failed!"); - } - IPAddress ip = WiFi.softAPIP(); // usually 192.168.4.1 - Serial.print("[WiFi] AP SSID: "); Serial.println(AP_SSID); - Serial.print("[WiFi] AP PASS: "); Serial.println(AP_PASS); - Serial.print("[WiFi] AP IP: "); Serial.println(ip); - - // Start TCP server - server.begin(); - server.setNoDelay(true); - Serial.print("[TCP] Listening on port "); Serial.println(SERVER_PORT); -} - -void handleNewClient() { - WiFiClient incoming = server.available(); - if (!incoming) return; - - // If we already have a client, drop the older one - if (client && client.connected()) { - client.stop(); - } - - client = incoming; - client.setTimeout(50); - Serial.print("[TCP] Client connected from "); - Serial.println(client.remoteIP()); - - // optional greeting / one-shot JSON blob - client.print("{\"hello\":\"welcome\",\"port\":"); - client.print(SERVER_PORT); - client.print(",\"hint\":\"I will stream one JSON per second. Each line is a JSON object.\"}\n"); -} - -void streamJsonIfTime() { - if (!client || !client.connected()) return; - - // read & ignore any input (you could add commands here) - while (client.available()) { - (void)client.read(); // drain input - } - - unsigned long now = millis(); - if (now - lastSendMs >= SEND_PERIOD_MS) { - lastSendMs = now; - String line = makeJson(); - client.print(line); // send one JSON line - Serial.print("[TX] "); // mirror to serial - Serial.print(line); - } -} - -void loop() { - // accept new client connections - handleNewClient(); - - // send JSON periodically if a client is connected - streamJsonIfTime(); - - // clean up if disconnected - if (client && !client.connected()) { - Serial.println("[TCP] Client disconnected"); - client.stop(); - } -} diff --git a/firmware/panorama/src/test.cpp b/firmware/panorama/src/test.cpp new file mode 100644 index 00000000..68dd8184 --- /dev/null +++ b/firmware/panorama/src/test.cpp @@ -0,0 +1,127 @@ +#include +#include + +// Wi-Fi Access Point credentials +const char* SSID = "ESP32-Interface"; +const char* PASS = "12345678"; +const uint16_t PORT = 9000; + +// HC-SR04 pins (adjust to match your wiring) +const int TRIG_PIN = 5; +const int ECHO_PIN = 18; + +WiFiServer server(PORT); +WiFiClient client; + +bool sendEnabled = false; +unsigned long sampleInterval = 1000; // ms +unsigned long lastSend = 0; +unsigned long startTime = 0; + +uint32_t seq = 0; +const uint16_t SENSOR_ID = 1; +const char* SENSOR_NAME = "ultrasonic_distance_cm"; + +// Return distance in cm, or -1.0 if no echo / timeout +float readUltrasonicCm() { + // ensure trigger is low + digitalWrite(TRIG_PIN, LOW); + delayMicroseconds(2); + + // 10 µs HIGH pulse to trigger measurement + digitalWrite(TRIG_PIN, HIGH); + delayMicroseconds(10); + digitalWrite(TRIG_PIN, LOW); + + // measure echo time (timeout 30 ms ≈ 5 m) + unsigned long duration = pulseIn(ECHO_PIN, HIGH, 30000); + if (duration == 0) { + return -1.0f; // no echo + } + + // speed of sound ≈ 0.0343 cm/µs, divide by 2 (out and back) + float distanceCm = (duration * 0.0343f) / 2.0f; + return distanceCm; +} + +void setup() { + Serial.begin(115200); + + // ultrasonic sensor pins + pinMode(TRIG_PIN, OUTPUT); + pinMode(ECHO_PIN, INPUT); + digitalWrite(TRIG_PIN, LOW); + + WiFi.mode(WIFI_AP); + WiFi.softAP(SSID, PASS); + IPAddress ip = WiFi.softAPIP(); + Serial.printf("AP started: %s (%s)\n", SSID, ip.toString().c_str()); + + server.begin(); + server.setNoDelay(true); +} + +void handleCommand(String cmd) { + cmd.trim(); + cmd.toUpperCase(); + + if (cmd.startsWith("START")) { + int spaceIdx = cmd.indexOf(' '); + if (spaceIdx > 0) { + float freq = cmd.substring(spaceIdx + 1).toFloat(); + if (freq > 0) sampleInterval = 1000.0 / freq; + } + startTime = millis(); + seq = 0; // reset sequence on start + sendEnabled = true; + Serial.printf("Signal ON, freq=%.1f Hz\n", 1000.0 / sampleInterval); + + } else if (cmd.startsWith("STOP")) { + sendEnabled = false; + Serial.println("Signal OFF"); + + } else { + Serial.printf("Unknown cmd: %s\n", cmd.c_str()); + } +} + +void loop() { + // accept new client + WiFiClient newClient = server.available(); + if (newClient) { + if (client && client.connected()) client.stop(); + client = newClient; + client.print(F("{\"type\":\"status\",\"msg\":\"connected\"}\n")); + Serial.println("Backend connected"); + } + + // read commands + if (client && client.connected() && client.available()) { + String cmd = client.readStringUntil('\n'); + handleCommand(cmd); + } + + // send JSON data packets + if (sendEnabled && client && client.connected()) { + unsigned long now = millis(); + if (now - lastSend >= sampleInterval) { + lastSend = now; + + float distanceCm = readUltrasonicCm(); + unsigned long timestamp = now - startTime; + + String json = + "{" + "\"sensor\":\"" + String(SENSOR_NAME) + "\"," + "\"sensor_id\":" + String(SENSOR_ID) + "," + "\"seq\":" + String(seq++) + "," + "\"timestamp_ms\":" + String(timestamp) + "," + "\"value\":" + String(distanceCm, 2) + + "}\n"; + + client.print(json); + Serial.print("Sent: "); + Serial.print(json); + } + } +} \ No newline at end of file diff --git a/firmware/panorama/src/test_server.py b/firmware/panorama/src/test_server.py new file mode 100644 index 00000000..e77bfa14 --- /dev/null +++ b/firmware/panorama/src/test_server.py @@ -0,0 +1,98 @@ +import socket +import sys +import json +import threading + +ESP_IP = "192.168.4.1" # ESP32 softAP IP (printed in Serial Monitor) +PORT = 9000 + +# Commands the ESP32 understands (sent as one line, newline-terminated): +# START [freq] - start streaming at freq Hz (default 5); e.g. "START 10" +# STOP - stop streaming +# Type QUIT or EXIT to close the connection and exit. + + +def read_commands(sock, stop_event): + """Read lines from stdin and send them as commands to the ESP32.""" + try: + while not stop_event.is_set(): + line = sys.stdin.readline() + if not line: + break + line = line.strip() + if not line: + continue + if line.upper() in ("QUIT", "EXIT", "Q"): + stop_event.set() + break + # Send command to ESP32 (one line, newline-terminated) + try: + sock.sendall((line + "\n").encode()) + print(f"[sent] {line}") + except OSError as e: + print(f"[error] send failed: {e}", file=sys.stderr) + stop_event.set() + break + except (KeyboardInterrupt, EOFError): + stop_event.set() + + +def main(): + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.connect((ESP_IP, PORT)) + print(f"Connected to {ESP_IP}:{PORT}") + print("Send commands: START [freq], STOP. Type QUIT to exit.\n") + + # Read optional status line from ESP32 + s.settimeout(0.5) + try: + status = s.recv(1024).decode().strip() + if status: + print("Status from ESP32:", status) + except socket.timeout: + pass + except OSError: + pass + s.settimeout(None) + + stop_event = threading.Event() + cmd_thread = threading.Thread(target=read_commands, args=(s, stop_event), daemon=True) + cmd_thread.start() + + try: + buffer = "" + while not stop_event.is_set(): + try: + data = s.recv(1024) + except (OSError, socket.timeout): + continue + if not data: + print("Connection closed by ESP32") + break + + buffer += data.decode(errors="replace") + while "\n" in buffer: + line, buffer = buffer.split("\n", 1) + line = line.strip() + if not line: + continue + if not line.startswith("{"): + # Status or debug line + print("ESP32:", line) + continue + try: + msg = json.loads(line) + print("JSON:", msg) + except json.JSONDecodeError: + print("Bad JSON:", line) + finally: + stop_event.set() + try: + s.sendall(b"STOP\n") + except OSError: + pass + print("Sent STOP; exiting.") + + +if __name__ == "__main__": + main()