From d2dae27a26f1e69c6ad39a4f128ee759a18782a9 Mon Sep 17 00:00:00 2001 From: sea-deep Date: Mon, 24 Aug 2026 04:41:14 +0530 Subject: [PATCH 01/13] feat: Implement fixed ultrasonic, servo VL53L0X, and single TB6612FNG --- DASHBOARD/dashboard.html | 773 ++++++----------------------- GATEWAY/include/telemetry_packet.h | 48 +- GATEWAY/src/main.cpp | 485 ++++-------------- diagram.json | 564 +++------------------ include/telemetry_packet.h | 48 +- src/main.cpp | 728 +++++++++------------------ 6 files changed, 582 insertions(+), 2064 deletions(-) diff --git a/DASHBOARD/dashboard.html b/DASHBOARD/dashboard.html index 3d2b98a..0d4d842 100644 --- a/DASHBOARD/dashboard.html +++ b/DASHBOARD/dashboard.html @@ -1,635 +1,158 @@ - -DEEPTRACK — Mine Rescue Rover Console - - - - - + + + Rover Dashboard + + -
- -
-
-
-
-

DEEPTRACK

- Mine Rescue Rover — Live Console -
-
-
-
Offline
- - - - -
-
- -
-
-
-

Path Trace

- dead-reckoned · cm -
-
- -
- -
-
-

Status

-
-
-
+

Mine Rescue Rover Dashboard

+ +
+
+

Telemetry

+
Connecting...
+

Temp: -- °C

+

Humidity: -- %

+

Gas (MQ4): --

+

Water Level: --

+

Pitch/Roll: -- / --

+

Status: SAFE

+
+ +
+

Live Local 2D Scan

+ + +

Controls

+
+
+ + + +
+

Requires Serial connection for controls.

+
-
Buzzer silent
-
- -
-

Environment

-
Temperature— °C
-
-
Humidity— %
-
-
Methane / CH4 (MQ-4)
-
-
Water
-
-
Tilt— °
-
-
- -
-

Navigation

-
Front distance
-
Position (x, y)
-
Heading
-
Encoders L / R
-
Distance traveled0 cm
-
- -
-

Event Log

-
-
-
-
- - + diff --git a/GATEWAY/include/telemetry_packet.h b/GATEWAY/include/telemetry_packet.h index df75373..bc0d1d8 100644 --- a/GATEWAY/include/telemetry_packet.h +++ b/GATEWAY/include/telemetry_packet.h @@ -1,30 +1,32 @@ #pragma once + #include -/* - Shared telemetry packet for ESP-NOW communication between - the Rover ESP32 and the Gateway ESP32. - 48 bytes — well under ESP-NOW's 250-byte limit. +// Standard telemetry (sent slowly) +typedef struct __attribute__((packed)) { + float temperature; + float humidity; + float ax, ay, az; + float gx, gy, gz; + uint16_t gasRaw; + uint16_t waterRaw; + uint32_t dangerState; // 0=safe, 1=danger +} TelemetryPacket; - Also used by the gateway to parse Serial2 bridge JSON - and to relay JSON to the laptop dashboard. -*/ +// Fast scan telemetry (sent rapidly) +typedef struct __attribute__((packed)) { + uint8_t type; // 1 = scan + uint8_t seq; // Sequence number + int16_t angle_deg; // Servo angle in degrees + uint16_t distance_mm; // VL53L0X distance + uint8_t valid; // 1 if valid, 0 if out of range + uint32_t timestamp_ms; // timestamp +} ScanPacket; +// Gateway to Rover control packet (Heartbeat) typedef struct __attribute__((packed)) { - float tempC; // DHT22 temperature (°C) - float humidity; // DHT22 relative humidity (%) - int16_t gasRaw; // MQ-4 Methane sensor ADC (0-4095) - float frontCm; // HC-SR04 front distance (cm) - int16_t tofRaw; // VL53L0X sim pot ADC (0-4095) - float tiltDeg; // MPU6050 tilt angle (degrees) - int16_t waterRaw; // Water sensor ADC (0-4095) - uint32_t encL, encR; // Wheel encoder pulse counts - float x, y; // Dead-reckoned position (cm) - float heading; // Heading (degrees, 0=+X, CCW positive) - uint8_t state; // 0=NORMAL 1=SLOW 2=AVOIDING 3=DANGER - uint8_t dangerCause; // 0=NONE 1=GAS 2=TILT 3=WATER 4=TEMP 5=HUMIDITY 6=TRAPPED -} TelemetryPacket; + uint8_t type; // 0 = command + int16_t motor_l; // -255 to 255 + int16_t motor_r; // -255 to 255 +} ControlPacket; -// String lookups for JSON serialization / LCD display -static const char* const STATE_NAMES[] = {"NORMAL","SLOW","AVOIDING","DANGER"}; -static const char* const DANGER_NAMES[] = {"NONE","GAS","TILT","WATER","TEMP","HUMIDITY","TRAPPED"}; diff --git a/GATEWAY/src/main.cpp b/GATEWAY/src/main.cpp index 5fb40e9..b33a842 100644 --- a/GATEWAY/src/main.cpp +++ b/GATEWAY/src/main.cpp @@ -1,419 +1,108 @@ -/* - DEEPTRACK GATEWAY — ESP-NOW Receiver + LCD Console + Hot-Pluggable MQTT & Serial Relay - - This ESP32 sits on a desk, acting as the base station console. - It receives telemetry from the Rover ESP32 over ESP-NOW (real hardware) - or over a Serial bridge (Wokwi simulation via socat + rfc2217). - If neither source provides data within 5 seconds, a built-in demo - data generator activates so the LCD, LEDs, and Dashboard can be tested. - - Telemetry Relaying (Hot-Pluggable Dual-Output): - 1. USB Serial → 115200 baud JSON stream to laptop (Web Serial API) - 2. MQTT Stream → Publishes JSON to broker.hivemq.com (non-blocking) - - Components: - 16×2 I2C LCD (0x27) → SDA 21 / SCL 22 (Auto-cycles every 3s between ENV/NAV/STATUS) - Red LED → GPIO 26 (DANGER indicator) - Green LED → GPIO 27 (NORMAL / LINK OK) - Yellow LED → GPIO 25 (Heartbeat blink on packet reception) -*/ - +#include #include #include #include -#include -#include -#include #include "telemetry_packet.h" -// ---------- WiFi & MQTT Config (Hot-pluggable / non-blocking) ---------- -const char* WIFI_SSID = "Wokwi-GUEST"; -const char* WIFI_PASS = ""; -const char* MQTT_SERVER = "broker.hivemq.com"; -const int MQTT_PORT = 1883; -const char* MQTT_TOPIC = "deeptrack/rover/telemetry"; +#define WIFI_SSID "Wokwi-GUEST" +#define WIFI_PASS "" +#define MQTT_SERVER "broker.hivemq.com" +#define MQTT_PORT 1883 +#define MQTT_TOPIC_TELEMETRY "rover/telemetry" +#define MQTT_TOPIC_SCAN "rover/scan" WiFiClient espClient; PubSubClient mqttClient(espClient); -unsigned long lastMqttAttempt = 0; -bool mqttWasConnected = false; - -// ---------- Pin map ---------- -#define LED_RED_PIN 26 -#define LED_GREEN_PIN 27 -#define LED_YELLOW_PIN 25 - -// ---------- LCD ---------- -LiquidCrystal_I2C lcd(0x27, 16, 2); - -// Custom characters (HD44780 can store 8 custom chars, 5×8 pixels each) -byte thermIcon[8] = { - 0b00100, 0b01010, 0b01010, 0b01110, - 0b01110, 0b11111, 0b11111, 0b01110 -}; -byte signalIcon[8] = { - 0b00001, 0b00001, 0b00101, 0b00101, - 0b10101, 0b10101, 0b10101, 0b00000 -}; -byte warnIcon[8] = { - 0b00000, 0b00100, 0b00100, 0b01010, - 0b01110, 0b11111, 0b11111, 0b00000 -}; - -// ---------- State ---------- -TelemetryPacket telem; // latest telemetry data -volatile bool espNowReady = false; // set by ISR-like callback -bool linkActive = false; -bool demoActive = false; - -unsigned long lastDataTime = 0; // millis() of last received packet -unsigned long lastPageSwitch = 0; -unsigned long lastHeartbeatOn = 0; -unsigned long lastDemoTick = 0; -uint32_t packetCount = 0; - -int lcdPage = 0; -const int NUM_PAGES = 3; // ENV, NAV, STATUS - -// Demo mode synthetic state -float demoX = 0, demoY = 0, demoH = 0; -uint32_t demoTick = 0; - -// ---------- ESP-NOW receive callback ---------- -void onDataReceived(const uint8_t *mac, const uint8_t *data, int len) { - if (len == sizeof(TelemetryPacket)) { - memcpy(&telem, data, sizeof(TelemetryPacket)); - espNowReady = true; - } -} - -// ---------- LCD display ---------- -void updateLCD() { - lcd.clear(); - - // DANGER page overrides everything - if (telem.state == 3) { - lcd.setCursor(0, 0); - lcd.write(2); // warning icon - lcd.print(" DANGER!! "); - lcd.write(2); - lcd.setCursor(0, 1); - const char* cause = (telem.dangerCause < 7) ? DANGER_NAMES[telem.dangerCause] : "???"; - char line2[17]; - snprintf(line2, sizeof(line2), ">>> %-7s <<<", cause); - lcd.print(line2); - return; - } - - switch (lcdPage) { - case 0: { // ENV — temperature, humidity, gas, water - lcd.setCursor(0, 0); - lcd.write(0); // thermometer icon - char e1[17]; - snprintf(e1, sizeof(e1), "%.1fC H:%.0f%%", telem.tempC, telem.humidity); - lcd.print(e1); - lcd.setCursor(0, 1); - char e2[17]; - snprintf(e2, sizeof(e2), "CH4:%04d Wt:%04d", (int)telem.gasRaw, (int)telem.waterRaw); - lcd.print(e2); - break; - } - case 1: { // NAV — position, heading, front distance - lcd.setCursor(0, 0); - char n1[17]; - snprintf(n1, sizeof(n1), "X:%-5.0f Y:%-5.0f", telem.x, telem.y); - lcd.print(n1); - lcd.setCursor(0, 1); - char n2[17]; - snprintf(n2, sizeof(n2), "Hd:%-3.0f Ft:%-4.0f", telem.heading, telem.frontCm); - lcd.print(n2); - break; +uint8_t roverAddress[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + +void onDataRecv(const uint8_t *mac, const uint8_t *incomingData, int len) { + if (len == sizeof(TelemetryPacket)) { + TelemetryPacket *pkt = (TelemetryPacket*)incomingData; + char json[256]; + snprintf(json, sizeof(json), + "{\"t\":%.1f,\"h\":%.1f,\"ax\":%.2f,\"ay\":%.2f,\"az\":%.2f,\"gx\":%.2f,\"gy\":%.2f,\"gz\":%.2f,\"gas\":%d,\"water\":%d,\"danger\":%d}", + pkt->temperature, pkt->humidity, + pkt->ax, pkt->ay, pkt->az, + pkt->gx, pkt->gy, pkt->gz, + pkt->gasRaw, pkt->waterRaw, + pkt->dangerState + ); + Serial.print("TELEMETRY:"); + Serial.println(json); + if (mqttClient.connected()) { + mqttClient.publish(MQTT_TOPIC_TELEMETRY, json); + } + } else if (len == sizeof(ScanPacket)) { + ScanPacket *pkt = (ScanPacket*)incomingData; + char json[128]; + snprintf(json, sizeof(json), + "{\"type\":\"scan\",\"seq\":%d,\"angle_deg\":%d,\"distance_mm\":%d,\"valid\":%s,\"timestamp_ms\":%d}", + pkt->seq, pkt->angle_deg, pkt->distance_mm, pkt->valid ? "true" : "false", pkt->timestamp_ms + ); + Serial.print("SCAN:"); + Serial.println(json); + if (mqttClient.connected()) { + mqttClient.publish(MQTT_TOPIC_SCAN, json); + } } - case 2: { // STATUS — state, link, MQTT status, packet count - lcd.setCursor(0, 0); - const char* st = (telem.state < 4) ? STATE_NAMES[telem.state] : "???"; - char s1[17]; - snprintf(s1, sizeof(s1), "ST:%-6s M:%s", st, mqttClient.connected() ? "ON" : "--"); - lcd.print(s1); - lcd.setCursor(0, 1); - lcd.write(1); // signal icon - char s2[17]; - snprintf(s2, sizeof(s2), "%s Pk:%lu", - linkActive ? "OK " : "-- ", - (unsigned long)packetCount); - lcd.print(s2); - break; - } - } -} - -void showLinkLost() { - lcd.clear(); - lcd.setCursor(0, 0); - lcd.write(2); // warning - lcd.print(" LINK LOST! "); - lcd.write(2); - lcd.setCursor(0, 1); - lcd.print("No data >3s"); -} - -void showBootScreen() { - lcd.clear(); - lcd.setCursor(0, 0); - lcd.print("DEEPTRACK GTWRY"); - lcd.setCursor(0, 1); - lcd.print("Waiting link..."); -} - -// ---------- LEDs ---------- -void updateLEDs() { - bool danger = (telem.state == 3); - digitalWrite(LED_RED_PIN, danger ? HIGH : LOW); - digitalWrite(LED_GREEN_PIN, danger ? LOW : HIGH); -} - -void heartbeatBlink() { - digitalWrite(LED_YELLOW_PIN, HIGH); - lastHeartbeatOn = millis(); -} - -// ---------- Dual Relay: Serial + Hot-Pluggable MQTT ---------- -void relayTelemetry() { - const char* stName = (telem.state < 4) ? STATE_NAMES[telem.state] : "UNKNOWN"; - const char* dcName = (telem.dangerCause < 7) ? DANGER_NAMES[telem.dangerCause] : "NONE"; - - char json[256]; - snprintf(json, sizeof(json), - "{\"t\":%.1f,\"h\":%.1f,\"gas\":%d,\"dist\":%.1f,\"water\":%d,\"tilt\":%.1f,\"x\":%.1f,\"y\":%.1f,\"hd\":%.0f,\"st\":\"%s\",\"danger\":\"%s\"}", - telem.tempC, telem.humidity, - (int)telem.gasRaw, telem.frontCm, - (int)telem.waterRaw, telem.tiltDeg, - telem.x, telem.y, telem.heading, - stName, dcName - ); - - // 1. Always output to USB Serial (for laptop Web Serial Dashboard) - Serial.println(json); - - // 2. Publish to MQTT if connected (hot-pluggable) - if (mqttClient.connected()) { - mqttClient.publish(MQTT_TOPIC, json); - } } -// ---------- Parse JSON from Serial bridge ---------- -bool parseJsonTelemetry(const String& line) { - JsonDocument doc; - DeserializationError err = deserializeJson(doc, line); - if (err) return false; - - telem.tempC = doc["t"] | doc["tempC"] | 0.0f; - telem.humidity = doc["h"] | doc["hum"] | 0.0f; - telem.gasRaw = doc["gas"] | (int16_t)0; - telem.frontCm = doc["dist"] | doc["frontCm"] | 999.0f; - telem.tofRaw = doc["tof"] | doc["tofRaw"] | (int16_t)4095; - telem.tiltDeg = doc["tilt"] | doc["tiltDeg"] | 0.0f; - telem.waterRaw = doc["water"] | (int16_t)0; - telem.encL = doc["encL"] | (uint32_t)0; - telem.encR = doc["encR"] | (uint32_t)0; - telem.x = doc["x"] | 0.0f; - telem.y = doc["y"] | 0.0f; - telem.heading = doc["hd"] | doc["heading"] | 0.0f; - - // Parse state string → uint8_t - const char* stStr = doc["st"] | doc["state"] | "NORMAL"; - telem.state = 0; - for (int i = 0; i < 4; i++) { - if (strcmp(stStr, STATE_NAMES[i]) == 0) { telem.state = i; break; } - } - - const char* dcStr = doc["danger"] | doc["dangerCause"] | "NONE"; - telem.dangerCause = 0; - for (int i = 0; i < 7; i++) { - if (strcmp(dcStr, DANGER_NAMES[i]) == 0) { telem.dangerCause = i; break; } - } - - return true; -} - -// ---------- Demo data generator ---------- -void generateDemoData() { - demoTick++; - bool danger = (demoTick % 55 == 0); - bool avoiding = (demoTick % 30 >= 25 && demoTick % 30 <= 29); - - telem.tempC = 28.0f + sinf(demoTick / 10.0f) * 4.0f + (danger ? 22.0f : 0); - telem.humidity = 60.0f + sinf(demoTick / 14.0f) * 10.0f; - telem.gasRaw = (int16_t)(400 + fabsf(sinf(demoTick / 8.0f)) * 300 + (danger ? 1500 : 0)); - telem.frontCm = 20.0f + fabsf(sinf(demoTick / 5.0f)) * 60.0f; - telem.tofRaw = 4095; - telem.tiltDeg = 5.0f + fabsf(sinf(demoTick / 6.0f)) * 10.0f; - telem.waterRaw = 300; - telem.encL = demoTick * 3; - telem.encR = demoTick * 3; - - if (avoiding && demoTick % 30 == 25) { - demoH += ((demoTick % 2) ? 1.0f : -1.0f) * (40.0f + (float)(demoTick % 30)); - } else if (!danger) { - demoH += (sinf(demoTick / 3.0f) - 0.5f) * 4.0f; - } - float step = danger ? -2.0f : (avoiding ? 1.0f : 4.0f); - demoX += step * cosf(demoH * PI / 180.0f); - demoY += step * sinf(demoH * PI / 180.0f); - - telem.x = demoX; - telem.y = demoY; - telem.heading = fmodf(demoH + 3600.0f, 360.0f); - telem.state = danger ? 3 : (avoiding ? 2 : (demoTick % 20 < 3 ? 1 : 0)); - telem.dangerCause = danger ? 1 : 0; // GAS or NONE -} - -// ---------- Process new data (from any source) ---------- -void processNewData() { - relayTelemetry(); - updateLEDs(); - updateLCD(); - heartbeatBlink(); - linkActive = true; -} - -// ---------- Non-blocking WiFi & MQTT Handler ---------- -void handleNetwork() { - unsigned long now = millis(); - - // If WiFi is disconnected, attempt connection in background without blocking - if (WiFi.status() != WL_CONNECTED) { - if (now - lastMqttAttempt > 10000) { - lastMqttAttempt = now; - WiFi.begin(WIFI_SSID, WIFI_PASS); +void reconnectMqtt() { + if (!mqttClient.connected()) { + Serial.println("Connecting to MQTT..."); + String clientId = "Gateway-"; + clientId += String(random(0xffff), HEX); + if (mqttClient.connect(clientId.c_str())) { + Serial.println("MQTT Connected"); + } } - return; - } - - // WiFi is connected -> handle MQTT - if (!mqttClient.connected()) { - if (now - lastMqttAttempt > 5000) { - lastMqttAttempt = now; - String clientId = "DEEPTRACK-GW-" + String(random(0xffff), HEX); - if (mqttClient.connect(clientId.c_str())) { - Serial.println("MQTT connected to " + String(MQTT_SERVER)); - mqttWasConnected = true; - } - } - } else { - mqttClient.loop(); - } } -// ---------- Setup ---------- void setup() { - Serial.begin(115200); - delay(200); - - // Pins - pinMode(LED_RED_PIN, OUTPUT); - pinMode(LED_GREEN_PIN, OUTPUT); - pinMode(LED_YELLOW_PIN, OUTPUT); - - digitalWrite(LED_GREEN_PIN, HIGH); - digitalWrite(LED_RED_PIN, LOW); - digitalWrite(LED_YELLOW_PIN, LOW); - - // LCD init - lcd.init(); - lcd.backlight(); - lcd.createChar(0, thermIcon); - lcd.createChar(1, signalIcon); - lcd.createChar(2, warnIcon); - showBootScreen(); - - // WiFi in STA mode for both ESP-NOW & optional MQTT - WiFi.mode(WIFI_STA); - WiFi.begin(WIFI_SSID, WIFI_PASS); // Non-blocking connect attempt - - // ESP-NOW init - if (esp_now_init() != ESP_OK) { - Serial.println("ESP-NOW init failed"); - } else { - esp_now_register_recv_cb(onDataReceived); - Serial.println("ESP-NOW receiver ready"); - } - - // MQTT setup - mqttClient.setServer(MQTT_SERVER, MQTT_PORT); - mqttClient.setBufferSize(512); - - // Zero-init telemetry - memset(&telem, 0, sizeof(telem)); - telem.frontCm = 999.0f; - telem.tofRaw = 4095; - - lastDataTime = millis(); - Serial.println("Gateway boot OK"); + Serial.begin(115200); + + WiFi.mode(WIFI_STA); + WiFi.begin(WIFI_SSID, WIFI_PASS); + + // In simulation, wait briefly for WiFi, but don't block ESP-NOW + for(int i=0; i<10 && WiFi.status() != WL_CONNECTED; i++) { + delay(500); + } + + mqttClient.setServer(MQTT_SERVER, MQTT_PORT); + + if (esp_now_init() != ESP_OK) { + Serial.println("ESP-NOW Init Failed"); + return; + } + esp_now_register_recv_cb(onDataRecv); + + esp_now_peer_info_t peerInfo; + memcpy(peerInfo.peer_addr, roverAddress, 6); + peerInfo.channel = 0; + peerInfo.encrypt = false; + esp_now_add_peer(&peerInfo); } -// ---------- Main loop ---------- void loop() { - unsigned long now = millis(); - - // Maintain WiFi & MQTT connectivity non-blockingly - handleNetwork(); - - // --- Source 1: ESP-NOW (real hardware) --- - if (espNowReady) { - espNowReady = false; - lastDataTime = now; - packetCount++; - demoActive = false; - processNewData(); - } - - // --- Source 2: Serial JSON input (Wokwi bridge or direct) --- - if (Serial.available()) { - String line = Serial.readStringUntil('\n'); - int start = line.indexOf('{'); - int end = line.lastIndexOf('}'); - if (start >= 0 && end > start) { - String jsonSub = line.substring(start, end + 1); - if (parseJsonTelemetry(jsonSub)) { - lastDataTime = now; - packetCount++; - demoActive = false; - processNewData(); - } + if (WiFi.status() == WL_CONNECTED) { + if (!mqttClient.connected()) reconnectMqtt(); + mqttClient.loop(); + } + + // Check serial for commands from the dashboard + if (Serial.available()) { + String cmd = Serial.readStringUntil('\n'); + if (cmd.startsWith("L:")) { + int spaceIdx = cmd.indexOf(' '); + if (spaceIdx > -1) { + ControlPacket pkt; + pkt.type = 0; + pkt.motor_l = cmd.substring(2, spaceIdx).toInt(); + pkt.motor_r = cmd.substring(cmd.indexOf("R:") + 2).toInt(); + esp_now_send(roverAddress, (uint8_t*)&pkt, sizeof(ControlPacket)); + } + } } - } - - // --- Source 3: Demo fallback (no data for 5s after boot) --- - if (!demoActive && now > 5000 && (now - lastDataTime) > 5000) { - demoActive = true; - Serial.println("{\"info\":\"No data source detected - entering demo mode\"}"); - } - if (demoActive && (now - lastDemoTick) >= 500) { - lastDemoTick = now; - generateDemoData(); - processNewData(); - } - - // --- Auto-cycle LCD pages every 3s (unless DANGER) --- - if (telem.state != 3 && (now - lastPageSwitch) >= 3000) { - lcdPage = (lcdPage + 1) % NUM_PAGES; - lastPageSwitch = now; - updateLCD(); - } - - // --- Heartbeat LED off after 80ms blink --- - if (digitalRead(LED_YELLOW_PIN) == HIGH && (now - lastHeartbeatOn) > 80) { - digitalWrite(LED_YELLOW_PIN, LOW); - } - - // --- Link lost check (no data for 3s, only when NOT in demo) --- - if (!demoActive && linkActive && (now - lastDataTime) > 3000) { - linkActive = false; - showLinkLost(); - digitalWrite(LED_GREEN_PIN, LOW); - // Blink red LED - digitalWrite(LED_RED_PIN, (now / 500) % 2 == 0 ? HIGH : LOW); - } } diff --git a/diagram.json b/diagram.json index 3d3fb6b..37709e4 100644 --- a/diagram.json +++ b/diagram.json @@ -1,502 +1,74 @@ { "version": 1, - "author": "Dipak - Four-Wheel Mine Rescue Rover Prototype", + "author": "Antigravity", "editor": "wokwi", "parts": [ - { - "type": "wokwi-text", - "id": "title", - "top": -505, - "left": -420, - "attrs": { - "text": "FOUR-WHEEL MINE RESCUE ROVER - HACKATHON PROTOTYPE" - } - }, - { - "type": "wokwi-text", - "id": "sensor_heading", - "top": -460, - "left": -470, - "attrs": { - "text": "ENVIRONMENT + OBSTACLE SENSORS" - } - }, - { - "type": "board-esp32-devkit-c-v4", - "id": "esp", - "top": -36, - "left": -15, - "attrs": {} - }, - { - "type": "wokwi-dht22", - "id": "dht", - "top": -366, - "left": -447, - "attrs": { - "temperature": "28", - "humidity": "64" - } - }, - { - "type": "wokwi-text", - "id": "dht_label", - "top": -405, - "left": -480, - "attrs": { - "text": "DHT22 - TEMPERATURE + HUMIDITY" - } - }, - { - "type": "wokwi-resistor", - "id": "dht_pullup", - "top": -302, - "left": -258, - "attrs": { - "value": "10000" - } - }, - { - "type": "wokwi-potentiometer", - "id": "gas", - "top": -174, - "left": -483, - "attrs": { - "value": "400" - } - }, - { - "type": "wokwi-text", - "id": "gas_label", - "top": -224, - "left": -487, - "attrs": { - "text": "MQ-4 METHANE (CH4) GAS SENSOR (GPIO34)" - } - }, - { - "type": "wokwi-hc-sr04", - "id": "front", - "top": -331, - "left": 428, - "attrs": { - "distance": "38" - } - }, - { - "type": "wokwi-text", - "id": "front_label", - "top": -401, - "left": 380, - "attrs": { - "text": "HC-SR04 MOUNTED ON SG90 SCAN SERVO - ROTATES TO SCAN LEFT/RIGHT" - } - }, - { - "type": "wokwi-resistor", - "id": "echo_r_upper", - "top": 117, - "left": -238, - "attrs": { - "value": "10000" - } - }, - { - "type": "wokwi-resistor", - "id": "echo_r_lower", - "top": 174, - "left": -181, - "rotate": 90, - "attrs": { - "value": "15000" - } - }, - { - "type": "wokwi-mpu6050", - "id": "imu", - "top": 288, - "left": -452, - "attrs": {} - }, - { - "type": "wokwi-text", - "id": "imu_label", - "top": 248, - "left": -475, - "attrs": { - "text": "MPU6050 - ORIENTATION / I2C" - } - }, - { - "type": "wokwi-ky-040", - "id": "encoder_left", - "top": 501, - "left": -416, - "attrs": {} - }, - { - "type": "wokwi-ky-040", - "id": "encoder_right", - "top": 501, - "left": -219, - "attrs": {} - }, - { - "type": "wokwi-text", - "id": "encoder_label", - "top": 457, - "left": -441, - "attrs": { - "text": "LEFT AND RIGHT WHEEL ENCODER SIMULATORS" - } - }, - { - "type": "wokwi-resistor", - "id": "encoder_right_pullup", - "top": 425, - "left": -36, - "rotate": 90, - "attrs": { - "value": "10000" - } - }, - { - "type": "wokwi-potentiometer", - "id": "tof_sim", - "top": -344, - "left": 226, - "attrs": { - "value": "512" - } - }, - { - "type": "wokwi-text", - "id": "tof_label", - "top": -290, - "left": 113, - "attrs": { - "text": "VL53L0X SIM: POT GPIO39 | REAL SENSOR: I2C GPIO21 / GPIO22" - } - }, - { - "type": "wokwi-servo", - "id": "scan_servo", - "top": -331, - "left": 630, - "attrs": { - "horn": "single" - } - }, - { - "type": "wokwi-text", - "id": "servo_label", - "top": -401, - "left": 620, - "attrs": { - "text": "SG90 SCANNING SERVO - GPIO13 - CARRIES THE HC-SR04" - } - }, - { - "type": "wokwi-buzzer", - "id": "buzzer", - "top": -115, - "left": 305, - "attrs": { - "volume": "0.2" - } - }, - { - "type": "wokwi-resistor", - "id": "buzzer_resistor", - "top": -52, - "left": 237, - "attrs": { - "value": "220" - } - }, - { - "type": "wokwi-text", - "id": "buzzer_label", - "top": -156, - "left": 275, - "attrs": { - "text": "BUZZER - DANGER ONLY (GAS / TILT / WATER / TRAPPED) - GPIO4" - } - }, - { - "type": "wokwi-led", - "id": "status_red", - "top": -100, - "left": 620, - "attrs": { - "color": "red", - "label": "DANGER" - } - }, - { - "type": "wokwi-resistor", - "id": "status_red_resistor", - "top": -60, - "left": 600, - "attrs": { - "value": "220" - } - }, - { - "type": "wokwi-led", - "id": "status_green", - "top": -100, - "left": 720, - "attrs": { - "color": "green", - "label": "NORMAL" - } - }, - { - "type": "wokwi-resistor", - "id": "status_green_resistor", - "top": -60, - "left": 700, - "attrs": { - "value": "220" - } - }, - { - "type": "wokwi-text", - "id": "status_led_label", - "top": -156, - "left": 590, - "attrs": { - "text": "STATUS LEDS - RED GPIO26 = DANGER | GREEN GPIO27 = NORMAL" - } - }, - { - "type": "wokwi-potentiometer", - "id": "water_sim", - "top": -96, - "left": 536, - "attrs": { - "value": "120" - } - }, - { - "type": "wokwi-text", - "id": "water_label", - "top": -20, - "left": 434, - "attrs": { - "text": "WATER SENSOR PLACEHOLDER - SAFE 3.3V / GPIO36" - } - }, - { - "type": "wokwi-text", - "id": "driver_label", - "top": 61, - "left": 189, - "attrs": { - "text": "4WD MOTOR OUTPUTS - LEDS STAND IN FOR TB6612 + DC MOTORS" - } - }, - { - "type": "wokwi-led", - "id": "motor_front_left", - "top": 115, - "left": 270, - "attrs": { - "color": "green", - "label": "FRONT LEFT" - } - }, - { - "type": "wokwi-led", - "id": "motor_front_right", - "top": 115, - "left": 411, - "attrs": { - "color": "blue", - "label": "FRONT RIGHT" - } - }, - { - "type": "wokwi-led", - "id": "motor_rear_left", - "top": 239, - "left": 270, - "attrs": { - "color": "green", - "label": "REAR LEFT" - } - }, - { - "type": "wokwi-led", - "id": "motor_rear_right", - "top": 239, - "left": 411, - "attrs": { - "color": "blue", - "label": "REAR RIGHT" - } - }, - { - "type": "wokwi-resistor", - "id": "motor_fl_resistor", - "top": 154, - "left": 189, - "attrs": { - "value": "220" - } - }, - { - "type": "wokwi-resistor", - "id": "motor_fr_resistor", - "top": 154, - "left": 456, - "attrs": { - "value": "220" - } - }, - { - "type": "wokwi-resistor", - "id": "motor_rl_resistor", - "top": 279, - "left": 189, - "attrs": { - "value": "220" - } - }, - { - "type": "wokwi-resistor", - "id": "motor_rr_resistor", - "top": 279, - "left": 456, - "attrs": { - "value": "220" - } - }, - { - "type": "wokwi-text", - "id": "motor_pin_label", - "top": 347, - "left": 174, - "attrs": { - "text": "LEFT PWM GPIO25 | RIGHT PWM GPIO14 | REAL MOTORS REQUIRE A DRIVER" - } - }, - { - "type": "wokwi-text", - "id": "powerbank_label", - "top": 413, - "left": 134, - "attrs": { - "text": "REAL PROTOTYPE POWER: 10000mAh USB POWER BANK - 5V / 3A PREFERRED" - } - }, - { - "type": "wokwi-text", - "id": "receiver_label", - "top": -454, - "left": 125, - "attrs": { - "text": "REAL LINK: ESP-NOW -> RECEIVER ESP32 -> USB -> NODE.JS DASHBOARD" - } - }, - { - "type": "wokwi-text", - "id": "simulation_note", - "top": 486, - "left": 143, - "attrs": { - "text": "WOKWI: ONE MCU PER PROJECT; RECEIVER + ESP-NOW REQUIRE REAL HARDWARE" - } - }, - { - "type": "wokwi-text", - "id": "safety_note", - "top": 532, - "left": 143, - "attrs": { - "text": "SAFETY: HC-SR04 ECHO + MQ ANALOG OUTPUT USE 10k / 15k DIVIDERS" - } - } + { "type": "board-esp32-devkit-c-v4", "id": "esp", "top": 0, "left": 0 }, + { "type": "wokwi-servo", "id": "servo", "top": -150, "left": -200 }, + { "type": "chip-vl53l0x", "id": "tof", "top": -150, "left": -50, "attrs": { "distance": "500" } }, + { "type": "wokwi-hc-sr04", "id": "sonar", "top": -250, "left": 100 }, + { "type": "chip-tb6612fng", "id": "drv", "top": 200, "left": -100 }, + { "type": "wokwi-dht22", "id": "dht", "top": -100, "left": 250 }, + { "type": "wokwi-potentiometer", "id": "gas", "top": 100, "left": 300 }, + { "type": "wokwi-potentiometer", "id": "water", "top": 150, "left": 300 }, + { "type": "wokwi-mpu6050", "id": "mpu", "top": -50, "left": 300 }, + { "type": "wokwi-buzzer", "id": "buzzer", "top": 50, "left": -250 }, + { "type": "wokwi-led", "id": "led_r", "top": 100, "left": -250, "attrs": { "color": "red" } }, + { "type": "wokwi-led", "id": "led_g", "top": 150, "left": -250, "attrs": { "color": "green" } } ], "connections": [ - ["esp:TX", "$serialMonitor:RX", "", []], - ["esp:RX", "$serialMonitor:TX", "", []], - - ["dht:VCC", "esp:3V3", "red", ["h36", "v75"]], - ["dht:GND", "esp:GND.1", "black", ["h42", "v120"]], - ["dht:SDA", "esp:23", "green", ["h55"]], - ["dht_pullup:1", "esp:3V3", "red", ["v-18"]], - ["dht_pullup:2", "dht:SDA", "green", ["h-22"]], - - ["gas:VCC", "esp:3V3", "red", ["h25", "v190"]], - ["gas:GND", "esp:GND.1", "black", ["h30", "v95"]], - ["gas:SIG", "esp:34", "orange", ["h35", "v120"]], - - ["front:VCC", "esp:5V", "red", ["h700", "v-260"]], - ["front:GND", "esp:GND.1", "black", ["h705", "v-230"]], - ["front:TRIG", "esp:19", "cyan", ["h-680"]], - ["front:ECHO", "echo_r_upper:1", "yellow", ["h-680", "v420"]], - ["echo_r_upper:2", "esp:18", "yellow", ["h16"]], - ["echo_r_upper:2", "echo_r_lower:1", "yellow", ["h9", "v22"]], - ["echo_r_lower:2", "esp:GND.2", "black", ["v20"]], - - ["front:VCC", "scan_servo:V+", "red", ["v10", "h150"]], - - ["imu:VCC", "esp:3V3", "red", ["h29", "v-100"]], - ["imu:GND", "esp:GND.2", "black", ["h34", "v-55"]], - ["imu:SDA", "esp:21", "blue", ["h56"]], - ["imu:SCL", "esp:22", "purple", ["h72"]], - - ["encoder_left:VCC", "esp:3V3", "red", ["h22", "v-108"]], - ["encoder_left:GND", "esp:GND.2", "black", ["h33", "v-90"]], - ["encoder_left:CLK", "esp:32", "green", ["h49"]], - - ["encoder_right:VCC", "esp:3V3", "red", ["h21", "v-100"]], - ["encoder_right:GND", "esp:GND.2", "black", ["h29", "v-73"]], - ["encoder_right:CLK", "esp:35", "green", ["h42"]], - ["encoder_right_pullup:1", "esp:3V3", "red", ["v-26"]], - ["encoder_right_pullup:2", "encoder_right:CLK", "green", ["h-26"]], - - ["tof_sim:VCC", "esp:3V3", "red", ["v24", "h-72"]], - ["tof_sim:GND", "esp:GND.1", "black", ["v30", "h-60"]], - ["tof_sim:SIG", "esp:VN", "orange", ["v39", "h-42"]], - - ["water_sim:VCC", "esp:3V3", "red", ["v36", "h-118"]], - ["water_sim:GND", "esp:GND.2", "black", ["v47", "h-129"]], - ["water_sim:SIG", "esp:VP", "cyan", ["v59", "h-140"]], - - ["scan_servo:V+", "esp:5V", "red", ["v44", "h-61"]], - ["scan_servo:GND", "esp:GND.1", "black", ["v57", "h-71"]], - ["scan_servo:PWM", "esp:13", "purple", ["v70", "h-81"]], - - ["buzzer:1", "esp:GND.2", "black", ["v33", "h-68"]], - ["buzzer:2", "buzzer_resistor:1", "orange", ["v21"]], - ["buzzer_resistor:2", "esp:4", "orange", ["h-32"]], - - ["status_red:A", "status_red_resistor:1", "red", ["v-15"]], - ["status_red_resistor:2", "esp:26", "red", ["h-40", "v-20"]], - ["status_red:C", "esp:GND.2", "black", ["v40", "h-90"]], - - ["status_green:A", "status_green_resistor:1", "green", ["v-15"]], - ["status_green_resistor:2", "esp:27", "green", ["h-40", "v-20"]], - ["status_green:C", "esp:GND.2", "black", ["v40", "h-190"]], - - ["esp:25", "motor_fl_resistor:1", "green", ["h55", "v45"]], - ["motor_fl_resistor:2", "motor_front_left:A", "green", ["h16"]], - ["motor_front_left:C", "esp:GND.2", "black", ["v31", "h-91"]], - - ["esp:25", "motor_rl_resistor:1", "green", ["h63", "v145"]], - ["motor_rl_resistor:2", "motor_rear_left:A", "green", ["h16"]], - ["motor_rear_left:C", "esp:GND.2", "black", ["v28", "h-99"]], - - ["esp:14", "motor_fr_resistor:1", "blue", ["h89", "v64"]], - ["motor_fr_resistor:2", "motor_front_right:A", "blue", ["h-17"]], - ["motor_front_right:C", "esp:GND.2", "black", ["v42", "h-135"]], - - ["esp:14", "motor_rr_resistor:1", "blue", ["h97", "v164"]], - ["motor_rr_resistor:2", "motor_rear_right:A", "blue", ["h-17"]], - ["motor_rear_right:C", "esp:GND.2", "black", ["v34", "h-144"]] - ], - "dependencies": {} -} \ No newline at end of file + [ "esp:GND.1", "servo:GND", "black", [] ], + [ "esp:5V", "servo:V+", "red", [] ], + [ "esp:13", "servo:PWM", "orange", [] ], + + [ "esp:3V3", "tof:VCC", "red", [] ], + [ "esp:GND.1", "tof:GND", "black", [] ], + [ "esp:22", "tof:SCL", "green", [] ], + [ "esp:21", "tof:SDA", "blue", [] ], + + [ "esp:5V", "sonar:VCC", "red", [] ], + [ "esp:GND.1", "sonar:GND", "black", [] ], + [ "esp:19", "sonar:TRIG", "green", [] ], + [ "esp:18", "sonar:ECHO", "orange", [] ], + + [ "esp:3V3", "mpu:VCC", "red", [] ], + [ "esp:GND.1", "mpu:GND", "black", [] ], + [ "esp:22", "mpu:SCL", "green", [] ], + [ "esp:21", "mpu:SDA", "blue", [] ], + + [ "esp:3V3", "dht:VCC", "red", [] ], + [ "esp:GND.1", "dht:GND", "black", [] ], + [ "esp:23", "dht:SDA", "green", [] ], + + [ "esp:3V3", "gas:VCC", "red", [] ], + [ "esp:GND.1", "gas:GND", "black", [] ], + [ "esp:34", "gas:SIG", "green", [] ], + + [ "esp:3V3", "water:VCC", "red", [] ], + [ "esp:GND.1", "water:GND", "black", [] ], + [ "esp:36", "water:SIG", "green", [] ], + + [ "esp:GND.1", "buzzer:1", "black", [] ], + [ "esp:4", "buzzer:2", "purple", [] ], + + [ "esp:GND.1", "led_r:C", "black", [] ], + [ "esp:26", "led_r:A", "red", [] ], + + [ "esp:GND.1", "led_g:C", "black", [] ], + [ "esp:27", "led_g:A", "green", [] ], + + [ "esp:GND.2", "drv:GND", "black", [] ], + [ "esp:GND.2", "drv:GND2", "black", [] ], + [ "esp:3V3", "drv:VCC", "red", [] ], + [ "esp:5V", "drv:VM", "red", [] ], + [ "esp:5", "drv:STBY", "purple", [] ], + + [ "esp:25", "drv:PWMA", "green", [] ], + [ "esp:16", "drv:AIN1", "blue", [] ], + [ "esp:17", "drv:AIN2", "orange", [] ], + + [ "esp:14", "drv:PWMB", "green", [] ], + [ "esp:33", "drv:BIN1", "blue", [] ], + [ "esp:2", "drv:BIN2", "orange", [] ] + ] +} diff --git a/include/telemetry_packet.h b/include/telemetry_packet.h index df75373..bc0d1d8 100644 --- a/include/telemetry_packet.h +++ b/include/telemetry_packet.h @@ -1,30 +1,32 @@ #pragma once + #include -/* - Shared telemetry packet for ESP-NOW communication between - the Rover ESP32 and the Gateway ESP32. - 48 bytes — well under ESP-NOW's 250-byte limit. +// Standard telemetry (sent slowly) +typedef struct __attribute__((packed)) { + float temperature; + float humidity; + float ax, ay, az; + float gx, gy, gz; + uint16_t gasRaw; + uint16_t waterRaw; + uint32_t dangerState; // 0=safe, 1=danger +} TelemetryPacket; - Also used by the gateway to parse Serial2 bridge JSON - and to relay JSON to the laptop dashboard. -*/ +// Fast scan telemetry (sent rapidly) +typedef struct __attribute__((packed)) { + uint8_t type; // 1 = scan + uint8_t seq; // Sequence number + int16_t angle_deg; // Servo angle in degrees + uint16_t distance_mm; // VL53L0X distance + uint8_t valid; // 1 if valid, 0 if out of range + uint32_t timestamp_ms; // timestamp +} ScanPacket; +// Gateway to Rover control packet (Heartbeat) typedef struct __attribute__((packed)) { - float tempC; // DHT22 temperature (°C) - float humidity; // DHT22 relative humidity (%) - int16_t gasRaw; // MQ-4 Methane sensor ADC (0-4095) - float frontCm; // HC-SR04 front distance (cm) - int16_t tofRaw; // VL53L0X sim pot ADC (0-4095) - float tiltDeg; // MPU6050 tilt angle (degrees) - int16_t waterRaw; // Water sensor ADC (0-4095) - uint32_t encL, encR; // Wheel encoder pulse counts - float x, y; // Dead-reckoned position (cm) - float heading; // Heading (degrees, 0=+X, CCW positive) - uint8_t state; // 0=NORMAL 1=SLOW 2=AVOIDING 3=DANGER - uint8_t dangerCause; // 0=NONE 1=GAS 2=TILT 3=WATER 4=TEMP 5=HUMIDITY 6=TRAPPED -} TelemetryPacket; + uint8_t type; // 0 = command + int16_t motor_l; // -255 to 255 + int16_t motor_r; // -255 to 255 +} ControlPacket; -// String lookups for JSON serialization / LCD display -static const char* const STATE_NAMES[] = {"NORMAL","SLOW","AVOIDING","DANGER"}; -static const char* const DANGER_NAMES[] = {"NONE","GAS","TILT","WATER","TEMP","HUMIDITY","TRAPPED"}; diff --git a/src/main.cpp b/src/main.cpp index 347a19c..1dd37dd 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,546 +1,276 @@ -/* - DEEPTRACK — FOUR-WHEEL MINE RESCUE ROVER - Sensor + safety + autonomous obstacle avoidance for ESP32 - - Communication: - ESP-NOW broadcast → Gateway ESP32 (binary TelemetryPacket, ~200m range) - Serial JSON → Wokwi serial monitor / optional socat bridge - Serial (USB) → debug output - - Sensors: - DHT22 → GPIO23 (temperature / humidity) - MQ-4 → GPIO34 (analog) (methane / CH4 gas sensor) - HC-SR04 → TRIG 19 / ECHO 18 (mounted on SG90 scan servo) - MPU6050 → SDA 21 / SCL 22 (orientation / tilt) - Left encoder → GPIO32 (wheel pulse count) - Right encoder→ GPIO35 (wheel pulse count) - VL53L0X sim → GPIO39 / VN (analog)(secondary front distance) - Water sensor → GPIO36 / VP (analog)(flood detection) - - Actuators: - SG90 scan servo → GPIO13 - Buzzer → GPIO4 - Red LED → GPIO26 (DANGER) - Green LED → GPIO27 (NORMAL) - Left motor PWM → GPIO25 - Right motor PWM → GPIO14 - - Obstacle avoidance sequence: - HALT → REVERSE → LOOK_RIGHT → LOOK_LEFT → TURN → resume -*/ - +#include +#include #include #include -#include #include #include #include +#include #include #include "telemetry_packet.h" -// ---------- ESP-NOW ---------- -uint8_t broadcastAddress[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; - -// ---------- Pin map ---------- +// --- Pin Definitions (Original Preserved) --- #define DHT_PIN 23 -#define DHT_TYPE DHT22 - #define GAS_PIN 34 #define TRIG_PIN 19 #define ECHO_PIN 18 - #define ENC_LEFT_PIN 32 #define ENC_RIGHT_PIN 35 - -#define TOF_SIM_PIN 39 #define WATER_PIN 36 - #define SERVO_PIN 13 #define BUZZER_PIN 4 - #define LED_RED_PIN 26 #define LED_GREEN_PIN 27 +#define I2C_SDA 21 +#define I2C_SCL 22 + +// Motor Pins (TB6612FNG) +#define L_PWM 25 +#define L_DIR1 16 +#define L_DIR2 17 +#define R_PWM 14 +#define R_DIR1 33 +#define R_DIR2 2 +#define STBY_PIN 5 + +// --- Constants & Config --- +#define DHT_TYPE DHT22 +#define OBSTACLE_STOP_CM 15 +#define L_PWM_CH 0 +#define R_PWM_CH 1 +#define PWM_FREQ 5000 +#define PWM_RES 8 -#define MOTOR_LEFT_PIN 25 -#define MOTOR_RIGHT_PIN 14 - -// ---------- LEDC motor PWM channels ---------- -#define MOTOR_LEFT_CH 0 -#define MOTOR_RIGHT_CH 1 - -// ---------- Servo scan angles ---------- -const int SERVO_CENTER = 90; -const int SERVO_LEFT = 150; -const int SERVO_RIGHT = 30; - -// ---------- Thresholds ---------- -const int GAS_ALARM_RAW = 1800; -const float OBSTACLE_STOP_CM = 20.0; -const float OBSTACLE_SLOW_CM = 45.0; -const int TOF_SIM_STOP_RAW = 300; -const float TILT_WARN_DEG = 25.0; -const float TILT_STOP_DEG = 40.0; -const int WATER_ALARM_RAW = 2500; -const float TEMP_ALARM_C = 50.0; -const float HUMIDITY_ALARM_PCT = 85.0; - -const unsigned long DHT_INTERVAL_MS = 2000; -const unsigned long SENSOR_INTERVAL_MS = 100; -const unsigned long SWEEP_STEP_MS = 20; -const unsigned long SERVO_SETTLE_MS = 300; -const unsigned long AVOID_HALT_MS = 150; -const unsigned long AVOID_REVERSE_MS = 400; -const unsigned long AVOID_TURN_MS = 700; -const unsigned long TELEMETRY_MS = 500; - -// ---------- Odometry ---------- -const float WHEEL_DIAMETER_CM = 6.5; -const int ENCODER_TICKS_PER_REV = 20; -const float WHEEL_BASE_CM = 15.0; -const float CM_PER_TICK = (PI * WHEEL_DIAMETER_CM) / ENCODER_TICKS_PER_REV; -const float SIM_MAX_SPEED_CM_S = 30.0; - -// ---------- Globals ---------- +// --- Globals --- DHT dht(DHT_PIN, DHT_TYPE); Adafruit_MPU6050 mpu; +Adafruit_VL53L0X lox = Adafruit_VL53L0X(); Servo scanServo; -volatile unsigned long leftPulses = 0; -volatile unsigned long rightPulses = 0; - -float lastTempC = NAN, lastHumidity = NAN; -bool dhtFault = false; - -float frontDistanceCm = 999; -int tofSimRaw = 4095; -int gasRaw = 0; -int waterRaw = 0; -float tiltDeg = 0; - -int servoAngle = SERVO_CENTER; -int servoDir = 1; -unsigned long lastSweepStep = 0; - -// ---------- Rover state ---------- -enum RoverState { NORMAL, SLOW, AVOIDING, DANGER }; -RoverState state = NORMAL; - -enum AvoidPhase { - AVOID_NONE, - AVOID_HALT, - AVOID_REVERSE, - AVOID_LOOK_RIGHT, - AVOID_LOOK_LEFT, - AVOID_TURN -}; -AvoidPhase avoidPhase = AVOID_NONE; -unsigned long avoidPhaseStart = 0; -float rightClearanceCm = 999; -float leftClearanceCm = 999; -int turnDir = 0; - -enum DangerCause { NONE_, GAS, TILT, WATER, TEMP, HUMIDITY, TRAPPED }; -DangerCause dangerCause = NONE_; - -unsigned long lastDhtRead = 0; -unsigned long lastSensorRead = 0; -unsigned long lastTelemetry = 0; - -float robotX = 0, robotY = 0; -float headingDeg = 0; -unsigned long prevLeftPulses = 0, prevRightPulses = 0; -int lastLeftCmdSigned = 0, lastRightCmdSigned = 0; - -// ---------- Forward declarations ---------- -const char* stateName(RoverState s); -const char* dangerCauseName(DangerCause c); - -// ---------- ISRs ---------- -void IRAM_ATTR onLeftPulse() { leftPulses++; } -void IRAM_ATTR onRightPulse() { rightPulses++; } +uint8_t gatewayAddress[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; -// ---------- Helpers ---------- -float readUltrasonicCm() { - digitalWrite(TRIG_PIN, LOW); - delayMicroseconds(2); - digitalWrite(TRIG_PIN, HIGH); - delayMicroseconds(10); - digitalWrite(TRIG_PIN, LOW); - unsigned long duration = pulseIn(ECHO_PIN, HIGH, 30000UL); - if (duration == 0) return 999.0; - return duration * 0.0343 / 2.0; -} +// State +TelemetryPacket currentTelemetry; +uint32_t lastTelemetryTime = 0; +bool emergencyStopped = false; -void setMotors(int leftSpeed, int rightSpeed) { - lastLeftCmdSigned = leftSpeed; - lastRightCmdSigned = rightSpeed; - ledcWrite(MOTOR_LEFT_CH, constrain(abs(leftSpeed), 0, 255)); - ledcWrite(MOTOR_RIGHT_CH, constrain(abs(rightSpeed), 0, 255)); -} +// Encoders +volatile uint32_t leftPulses = 0; +volatile uint32_t rightPulses = 0; -void soundBuzzer(bool on, int freq = 2000) { - if (on) tone(BUZZER_PIN, freq); - else noTone(BUZZER_PIN); -} +void IRAM_ATTR onLeftPulse() { leftPulses++; } +void IRAM_ATTR onRightPulse() { rightPulses++; } -void setStatusLeds(bool danger) { - digitalWrite(LED_RED_PIN, danger ? HIGH : LOW); - digitalWrite(LED_GREEN_PIN, danger ? LOW : HIGH); +// Scan State +int currentAngle = 90; +int scanDirection = 10; +uint32_t lastScanTime = 0; +uint8_t scanSeq = 0; + +// Motor state (target) +int target_l = 0; +int target_r = 0; + +// --- Function Prototypes --- +void setMotors(int left, int right); +void shortBrake(); + +// --- ESP-NOW Callback --- +void onDataRecv(const uint8_t *mac, const uint8_t *incomingData, int len) { + if (len == sizeof(ControlPacket)) { + ControlPacket *pkt = (ControlPacket*)incomingData; + if (pkt->type == 0) { + target_l = pkt->motor_l; + target_r = pkt->motor_r; + emergencyStopped = false; // Reset emergency on new command + } + } } -// ---------- Setup ---------- void setup() { - Serial.begin(115200); - delay(300); - - // WiFi STA mode for ESP-NOW (no AP connection needed) - WiFi.mode(WIFI_STA); - WiFi.disconnect(); - - // ESP-NOW init - if (esp_now_init() != ESP_OK) { - Serial.println("ESP-NOW init failed"); - } else { - // Register broadcast peer - esp_now_peer_info_t peerInfo = {}; - memcpy(peerInfo.peer_addr, broadcastAddress, 6); - peerInfo.channel = 0; - peerInfo.encrypt = false; - if (esp_now_add_peer(&peerInfo) != ESP_OK) { - Serial.println("ESP-NOW add peer failed"); - } else { - Serial.println("ESP-NOW transmitter ready"); + Serial.begin(115200); + Wire.begin(I2C_SDA, I2C_SCL); + + // Motor Pins + pinMode(L_DIR1, OUTPUT); + pinMode(L_DIR2, OUTPUT); + pinMode(R_DIR1, OUTPUT); + pinMode(R_DIR2, OUTPUT); + pinMode(STBY_PIN, OUTPUT); + + ledcSetup(L_PWM_CH, PWM_FREQ, PWM_RES); + ledcSetup(R_PWM_CH, PWM_FREQ, PWM_RES); + ledcAttachPin(L_PWM, L_PWM_CH); + ledcAttachPin(R_PWM, R_PWM_CH); + + digitalWrite(STBY_PIN, HIGH); // Enable driver + + // LED & Buzzer + pinMode(BUZZER_PIN, OUTPUT); + pinMode(LED_RED_PIN, OUTPUT); + pinMode(LED_GREEN_PIN, OUTPUT); + + // Ultrasonic + pinMode(TRIG_PIN, OUTPUT); + pinMode(ECHO_PIN, INPUT); + + // Sensors + pinMode(GAS_PIN, INPUT); + pinMode(WATER_PIN, INPUT); + + // Encoders + pinMode(ENC_LEFT_PIN, INPUT_PULLUP); + pinMode(ENC_RIGHT_PIN, INPUT_PULLUP); + attachInterrupt(digitalPinToInterrupt(ENC_LEFT_PIN), onLeftPulse, RISING); + attachInterrupt(digitalPinToInterrupt(ENC_RIGHT_PIN), onRightPulse, RISING); + + dht.begin(); + scanServo.attach(SERVO_PIN, 500, 2400); + + if (!mpu.begin()) Serial.println("Failed to find MPU6050"); + if (!lox.begin()) Serial.println("Failed to boot VL53L0X"); + + WiFi.mode(WIFI_STA); + if (esp_now_init() != ESP_OK) { + Serial.println("Error initializing ESP-NOW"); + return; } - } - - dht.begin(); - Wire.begin(); - - if (!mpu.begin()) { - Serial.println("WARN: MPU6050 not detected"); - } else { - mpu.setAccelerometerRange(MPU6050_RANGE_8_G); - mpu.setGyroRange(MPU6050_RANGE_500_DEG); - mpu.setFilterBandwidth(MPU6050_BAND_21_HZ); - } - - pinMode(TRIG_PIN, OUTPUT); - pinMode(ECHO_PIN, INPUT); - pinMode(GAS_PIN, INPUT); - - pinMode(ENC_LEFT_PIN, INPUT_PULLUP); - pinMode(ENC_RIGHT_PIN, INPUT_PULLUP); - attachInterrupt(digitalPinToInterrupt(ENC_LEFT_PIN), onLeftPulse, RISING); - attachInterrupt(digitalPinToInterrupt(ENC_RIGHT_PIN), onRightPulse, RISING); - - pinMode(BUZZER_PIN, OUTPUT); - pinMode(LED_RED_PIN, OUTPUT); - pinMode(LED_GREEN_PIN, OUTPUT); - setStatusLeds(false); - - ledcSetup(MOTOR_LEFT_CH, 5000, 8); - ledcAttachPin(MOTOR_LEFT_PIN, MOTOR_LEFT_CH); - ledcSetup(MOTOR_RIGHT_CH, 5000, 8); - ledcAttachPin(MOTOR_RIGHT_PIN, MOTOR_RIGHT_CH); - - scanServo.setPeriodHertz(50); - scanServo.attach(SERVO_PIN, 500, 2400); - scanServo.write(servoAngle); - - Serial.println("Mine Rescue Rover boot OK"); -} - -// ---------- Sensor reads ---------- -void readEnvironment() { - float h = dht.readHumidity(); - float t = dht.readTemperature(); - if (isnan(h) || isnan(t)) { dhtFault = true; } - else { dhtFault = false; lastHumidity = h; lastTempC = t; } + esp_now_register_recv_cb(onDataRecv); + + esp_now_peer_info_t peerInfo; + memcpy(peerInfo.peer_addr, gatewayAddress, 6); + peerInfo.channel = 0; + peerInfo.encrypt = false; + esp_now_add_peer(&peerInfo); } -void readObstacleSensors() { - frontDistanceCm = readUltrasonicCm(); - tofSimRaw = analogRead(TOF_SIM_PIN); +void shortBrake() { + digitalWrite(STBY_PIN, HIGH); + digitalWrite(L_DIR1, HIGH); + digitalWrite(L_DIR2, HIGH); + digitalWrite(R_DIR1, HIGH); + digitalWrite(R_DIR2, HIGH); + ledcWrite(L_PWM_CH, 255); + ledcWrite(R_PWM_CH, 255); } -void readGasSensor() { gasRaw = analogRead(GAS_PIN); } -void readWaterSensor() { waterRaw = analogRead(WATER_PIN); } - -void readTilt() { - sensors_event_t a, g, temp; - if (!mpu.getEvent(&a, &g, &temp)) return; - float ax = a.acceleration.x, ay = a.acceleration.y, az = a.acceleration.z; - tiltDeg = atan2(sqrt(ax * ax + ay * ay), az) * 180.0 / PI; -} - -bool frontBlocked() { - return (frontDistanceCm <= OBSTACLE_STOP_CM) || (tofSimRaw <= TOF_SIM_STOP_RAW); +void setMotors(int left, int right) { + if (emergencyStopped) return; + + digitalWrite(STBY_PIN, HIGH); + + if (left > 0) { + digitalWrite(L_DIR1, HIGH); + digitalWrite(L_DIR2, LOW); + } else if (left < 0) { + digitalWrite(L_DIR1, LOW); + digitalWrite(L_DIR2, HIGH); + } else { + digitalWrite(L_DIR1, HIGH); + digitalWrite(L_DIR2, HIGH); + } + ledcWrite(L_PWM_CH, abs(left)); + + if (right > 0) { + digitalWrite(R_DIR1, HIGH); + digitalWrite(R_DIR2, LOW); + } else if (right < 0) { + digitalWrite(R_DIR1, LOW); + digitalWrite(R_DIR2, HIGH); + } else { + digitalWrite(R_DIR1, HIGH); + digitalWrite(R_DIR2, HIGH); + } + ledcWrite(R_PWM_CH, abs(right)); } -// ---------- Odometry ---------- -void updateOdometry() { - noInterrupts(); - unsigned long lp = leftPulses, rp = rightPulses; - interrupts(); - - long deltaLeft = (long)(lp - prevLeftPulses); - long deltaRight = (long)(rp - prevRightPulses); - prevLeftPulses = lp; - prevRightPulses = rp; - - float distLeft, distRight; - - if (deltaLeft == 0 && deltaRight == 0) { - float dt = SENSOR_INTERVAL_MS / 1000.0f; - distLeft = (lastLeftCmdSigned / 255.0f) * SIM_MAX_SPEED_CM_S * dt; - distRight = (lastRightCmdSigned / 255.0f) * SIM_MAX_SPEED_CM_S * dt; - } else { - distLeft = deltaLeft * CM_PER_TICK; - distRight = deltaRight * CM_PER_TICK; - if (lastLeftCmdSigned < 0) distLeft = -distLeft; - if (lastRightCmdSigned < 0) distRight = -distRight; - if (lastLeftCmdSigned == 0) distLeft = 0; - if (lastRightCmdSigned == 0) distRight = 0; - } - - float distCenter = (distLeft + distRight) / 2.0f; - float deltaHeadingDeg = (distRight - distLeft) / WHEEL_BASE_CM * (180.0f / PI); - float headingRad = headingDeg * PI / 180.0f; - - robotX += distCenter * cos(headingRad); - robotY += distCenter * sin(headingRad); - headingDeg += deltaHeadingDeg; - while (headingDeg >= 360) headingDeg -= 360; - while (headingDeg < 0) headingDeg += 360; +float readUltrasonic() { + digitalWrite(TRIG_PIN, LOW); + delayMicroseconds(2); + digitalWrite(TRIG_PIN, HIGH); + delayMicroseconds(10); + digitalWrite(TRIG_PIN, LOW); + unsigned long duration = pulseIn(ECHO_PIN, HIGH, 30000UL); + if (duration == 0) return 999.0; + return (duration * 0.0343) / 2.0; } -// ---------- Cruise sweep ---------- -void updateCruiseSweep() { - if (avoidPhase != AVOID_NONE) return; - if (millis() - lastSweepStep < SWEEP_STEP_MS) return; - lastSweepStep = millis(); +void loop() { + uint32_t now = millis(); + + // 1. Ultrasonic Obstacle Check (Fixed Forward) + float frontDist = readUltrasonic(); + if (frontDist < OBSTACLE_STOP_CM && frontDist > 0) { + if (!emergencyStopped && (target_l > 0 || target_r > 0)) { + Serial.println("OBSTACLE - EMERGENCY STOP"); + shortBrake(); + emergencyStopped = true; + } + } - servoAngle += servoDir * 2; - if (servoAngle >= SERVO_LEFT) { servoAngle = SERVO_LEFT; servoDir = -1; } - if (servoAngle <= SERVO_RIGHT) { servoAngle = SERVO_RIGHT; servoDir = 1; } - scanServo.write(servoAngle); -} + // Apply motor state + if (!emergencyStopped) { + setMotors(target_l, target_r); + } -// ---------- Obstacle-avoidance state machine ---------- -void runAvoidance() { - unsigned long elapsed = millis() - avoidPhaseStart; - - switch (avoidPhase) { - case AVOID_NONE: - if (frontBlocked()) { - avoidPhase = AVOID_HALT; - avoidPhaseStart = millis(); - setMotors(0, 0); - } - break; - - case AVOID_HALT: - setMotors(0, 0); - if (elapsed >= AVOID_HALT_MS) { - avoidPhase = AVOID_REVERSE; - avoidPhaseStart = millis(); - } - break; - - case AVOID_REVERSE: - setMotors(-120, -120); - if (elapsed >= AVOID_REVERSE_MS) { - setMotors(0, 0); - scanServo.write(SERVO_RIGHT); - avoidPhase = AVOID_LOOK_RIGHT; - avoidPhaseStart = millis(); - } - break; - - case AVOID_LOOK_RIGHT: - setMotors(0, 0); - if (elapsed >= SERVO_SETTLE_MS) { - rightClearanceCm = readUltrasonicCm(); - scanServo.write(SERVO_LEFT); - avoidPhase = AVOID_LOOK_LEFT; - avoidPhaseStart = millis(); - } - break; - - case AVOID_LOOK_LEFT: - setMotors(0, 0); - if (elapsed >= SERVO_SETTLE_MS) { - leftClearanceCm = readUltrasonicCm(); - bool rightOpen = rightClearanceCm > OBSTACLE_STOP_CM; - bool leftOpen = leftClearanceCm > OBSTACLE_STOP_CM; - - if (!rightOpen && !leftOpen) { - dangerCause = TRAPPED; - avoidPhase = AVOID_NONE; - scanServo.write(SERVO_CENTER); + // 2. Servo Sweep & Scan (VL53L0X) + if (now - lastScanTime > 33) { + lastScanTime = now; + scanServo.write(currentAngle); + + VL53L0X_RangingMeasurementData_t measure; + lox.rangingTest(&measure, false); + + ScanPacket spkt; + spkt.type = 1; + spkt.seq = scanSeq++; + spkt.angle_deg = currentAngle; + spkt.timestamp_ms = now; + + if (measure.RangeStatus != 4) { + spkt.distance_mm = measure.RangeMilliMeter; + spkt.valid = 1; } else { - turnDir = (rightClearanceCm >= leftClearanceCm) ? 1 : -1; - avoidPhase = AVOID_TURN; - avoidPhaseStart = millis(); + spkt.distance_mm = 800; + spkt.valid = 0; } - } - break; - - case AVOID_TURN: - if (turnDir > 0) setMotors(160, -160); - else setMotors(-160, 160); - if (elapsed >= AVOID_TURN_MS) { - setMotors(0, 0); - scanServo.write(SERVO_CENTER); - servoAngle = SERVO_CENTER; - avoidPhase = AVOID_NONE; - } - break; - } -} - -// ---------- Decision / arbitration ---------- -void decideState() { - dangerCause = NONE_; - - if (gasRaw >= GAS_ALARM_RAW) dangerCause = GAS; - else if (waterRaw >= WATER_ALARM_RAW) dangerCause = WATER; - else if (tiltDeg >= TILT_STOP_DEG) dangerCause = TILT; - else if (!dhtFault && lastTempC >= TEMP_ALARM_C) dangerCause = TEMP; - else if (!dhtFault && lastHumidity >= HUMIDITY_ALARM_PCT) dangerCause = HUMIDITY; - - if (dangerCause != NONE_) { - state = DANGER; - avoidPhase = AVOID_NONE; - return; - } - - runAvoidance(); - - if (dangerCause == TRAPPED) { state = DANGER; return; } - if (avoidPhase != AVOID_NONE) { state = AVOIDING; return; } - - bool slow = (frontDistanceCm <= OBSTACLE_SLOW_CM) || (tiltDeg >= TILT_WARN_DEG); - state = slow ? SLOW : NORMAL; -} - -void actOnState() { - bool danger = (state == DANGER); - setStatusLeds(danger); - - int buzzFreq = 2000; - switch (dangerCause) { - case GAS: buzzFreq = 2500; break; - case TILT: buzzFreq = 3000; break; - case WATER: buzzFreq = 1800; break; - case TEMP: buzzFreq = 2700; break; - case HUMIDITY: buzzFreq = 1600; break; - case TRAPPED: buzzFreq = 1200; break; - default: buzzFreq = 2000; break; - } - soundBuzzer(danger, buzzFreq); - - switch (state) { - case NORMAL: setMotors(200, 200); break; - case SLOW: setMotors(110, 110); break; - case AVOIDING: break; // driven by runAvoidance() - case DANGER: - switch (dangerCause) { - case GAS: case TEMP: setMotors(-150, -150); break; - default: setMotors(0, 0); break; - } - break; - } -} - -// ---------- Telemetry ---------- -const char* stateName(RoverState s) { - switch (s) { - case NORMAL: return "NORMAL"; - case SLOW: return "SLOW"; - case AVOIDING: return "AVOIDING"; - case DANGER: return "DANGER"; - } - return "UNKNOWN"; -} + + esp_now_send(gatewayAddress, (uint8_t *) &spkt, sizeof(ScanPacket)); + + currentAngle += scanDirection; + if (currentAngle >= 150 || currentAngle <= 30) { + scanDirection = -scanDirection; + } + } -const char* dangerCauseName(DangerCause c) { - switch (c) { - case GAS: return "GAS"; - case TILT: return "TILT"; - case WATER: return "WATER"; - case TEMP: return "TEMP"; - case HUMIDITY: return "HUMIDITY"; - case TRAPPED: return "TRAPPED"; - default: return "NONE"; - } -} + // 3. Environmental Telemetry + if (now - lastTelemetryTime > 1000) { + lastTelemetryTime = now; + + currentTelemetry.temperature = dht.readTemperature(); + currentTelemetry.humidity = dht.readHumidity(); + currentTelemetry.gasRaw = analogRead(GAS_PIN); + currentTelemetry.waterRaw = analogRead(WATER_PIN); + + sensors_event_t a, g, temp; + mpu.getEvent(&a, &g, &temp); + currentTelemetry.ax = a.acceleration.x; + currentTelemetry.ay = a.acceleration.y; + currentTelemetry.az = a.acceleration.z; + currentTelemetry.gx = g.gyro.x; + currentTelemetry.gy = g.gyro.y; + currentTelemetry.gz = g.gyro.z; + + currentTelemetry.dangerState = emergencyStopped ? 1 : 0; + + digitalWrite(LED_RED_PIN, emergencyStopped ? HIGH : LOW); + digitalWrite(LED_GREEN_PIN, emergencyStopped ? LOW : HIGH); + if (emergencyStopped) { + tone(BUZZER_PIN, 1000, 100); + } else { + noTone(BUZZER_PIN); + } -void printTelemetry() { - noInterrupts(); - unsigned long lp = leftPulses, rp = rightPulses; - interrupts(); - - // Clean single-line JSON for Serial monitor + gateway bridge - char json[256]; - snprintf(json, sizeof(json), - "{\"t\":%.1f,\"h\":%.1f,\"gas\":%d,\"dist\":%.1f,\"water\":%d,\"tilt\":%.1f,\"x\":%.1f,\"y\":%.1f,\"hd\":%.0f,\"st\":\"%s\",\"danger\":\"%s\"}", - dhtFault ? -999.0f : lastTempC, - dhtFault ? -999.0f : lastHumidity, - gasRaw, frontDistanceCm, - waterRaw, tiltDeg, - robotX, robotY, headingDeg, - stateName(state), - dangerCauseName(dangerCause) - ); - - Serial.println(json); - - // ESP-NOW binary packet to gateway - TelemetryPacket pkt; - pkt.tempC = dhtFault ? -999.0f : lastTempC; - pkt.humidity = dhtFault ? -999.0f : lastHumidity; - pkt.gasRaw = (int16_t)gasRaw; - pkt.frontCm = frontDistanceCm; - pkt.tofRaw = (int16_t)tofSimRaw; - pkt.tiltDeg = tiltDeg; - pkt.waterRaw = (int16_t)waterRaw; - pkt.encL = (uint32_t)lp; - pkt.encR = (uint32_t)rp; - pkt.x = robotX; - pkt.y = robotY; - pkt.heading = headingDeg; - pkt.state = (uint8_t)state; - pkt.dangerCause = (uint8_t)dangerCause; - - esp_now_send(broadcastAddress, (uint8_t*)&pkt, sizeof(pkt)); + esp_now_send(gatewayAddress, (uint8_t *) ¤tTelemetry, sizeof(TelemetryPacket)); + } } - -// ---------- Main loop ---------- -void loop() { - unsigned long now = millis(); - - if (now - lastDhtRead >= DHT_INTERVAL_MS) { - lastDhtRead = now; - readEnvironment(); - } - - if (now - lastSensorRead >= SENSOR_INTERVAL_MS) { - lastSensorRead = now; - readObstacleSensors(); - readGasSensor(); - readWaterSensor(); - readTilt(); - - decideState(); - updateOdometry(); - actOnState(); - } - - updateCruiseSweep(); - - if (now - lastTelemetry >= TELEMETRY_MS) { - lastTelemetry = now; - printTelemetry(); - } -} \ No newline at end of file From 2f40adc9a7a856c0dd411730bb9dee16650efdcb Mon Sep 17 00:00:00 2001 From: sea-deep Date: Mon, 24 Aug 2026 04:44:33 +0530 Subject: [PATCH 02/13] fix: Resolve missing custom chips by using correct file extensions, empty author, and adding SVGs --- diagram.json | 396 ++++++++++++++++++++++++++------ fix_author.py | 11 + fix_wokwi.py | 12 + make_svgs.py | 26 +++ tb6612fng.c => tb6612fng.chip.c | 0 tb6612fng.chip.json | 4 +- tb6612fng.svg | 6 + test_wokwi.py | 4 + vl53l0x.c => vl53l0x.chip.c | 0 vl53l0x.chip.json | 6 +- vl53l0x.svg | 7 + 11 files changed, 400 insertions(+), 72 deletions(-) create mode 100644 fix_author.py create mode 100644 fix_wokwi.py create mode 100644 make_svgs.py rename tb6612fng.c => tb6612fng.chip.c (100%) create mode 100644 tb6612fng.svg create mode 100644 test_wokwi.py rename vl53l0x.c => vl53l0x.chip.c (100%) create mode 100644 vl53l0x.svg diff --git a/diagram.json b/diagram.json index 37709e4..bfc08eb 100644 --- a/diagram.json +++ b/diagram.json @@ -1,74 +1,336 @@ { "version": 1, - "author": "Antigravity", + "author": "", "editor": "wokwi", "parts": [ - { "type": "board-esp32-devkit-c-v4", "id": "esp", "top": 0, "left": 0 }, - { "type": "wokwi-servo", "id": "servo", "top": -150, "left": -200 }, - { "type": "chip-vl53l0x", "id": "tof", "top": -150, "left": -50, "attrs": { "distance": "500" } }, - { "type": "wokwi-hc-sr04", "id": "sonar", "top": -250, "left": 100 }, - { "type": "chip-tb6612fng", "id": "drv", "top": 200, "left": -100 }, - { "type": "wokwi-dht22", "id": "dht", "top": -100, "left": 250 }, - { "type": "wokwi-potentiometer", "id": "gas", "top": 100, "left": 300 }, - { "type": "wokwi-potentiometer", "id": "water", "top": 150, "left": 300 }, - { "type": "wokwi-mpu6050", "id": "mpu", "top": -50, "left": 300 }, - { "type": "wokwi-buzzer", "id": "buzzer", "top": 50, "left": -250 }, - { "type": "wokwi-led", "id": "led_r", "top": 100, "left": -250, "attrs": { "color": "red" } }, - { "type": "wokwi-led", "id": "led_g", "top": 150, "left": -250, "attrs": { "color": "green" } } + { + "type": "board-esp32-devkit-c-v4", + "id": "esp", + "top": 0, + "left": 0 + }, + { + "type": "wokwi-servo", + "id": "servo", + "top": -150, + "left": -200 + }, + { + "type": "chip-vl53l0x", + "id": "tof", + "top": -150, + "left": -50, + "attrs": { + "distance": "500" + } + }, + { + "type": "wokwi-hc-sr04", + "id": "sonar", + "top": -250, + "left": 100 + }, + { + "type": "chip-tb6612fng", + "id": "drv", + "top": 200, + "left": -100 + }, + { + "type": "wokwi-dht22", + "id": "dht", + "top": -100, + "left": 250 + }, + { + "type": "wokwi-potentiometer", + "id": "gas", + "top": 100, + "left": 300 + }, + { + "type": "wokwi-potentiometer", + "id": "water", + "top": 150, + "left": 300 + }, + { + "type": "wokwi-mpu6050", + "id": "mpu", + "top": -50, + "left": 300 + }, + { + "type": "wokwi-buzzer", + "id": "buzzer", + "top": 50, + "left": -250 + }, + { + "type": "wokwi-led", + "id": "led_r", + "top": 100, + "left": -250, + "attrs": { + "color": "red" + } + }, + { + "type": "wokwi-led", + "id": "led_g", + "top": 150, + "left": -250, + "attrs": { + "color": "green" + } + } ], "connections": [ - [ "esp:GND.1", "servo:GND", "black", [] ], - [ "esp:5V", "servo:V+", "red", [] ], - [ "esp:13", "servo:PWM", "orange", [] ], - - [ "esp:3V3", "tof:VCC", "red", [] ], - [ "esp:GND.1", "tof:GND", "black", [] ], - [ "esp:22", "tof:SCL", "green", [] ], - [ "esp:21", "tof:SDA", "blue", [] ], - - [ "esp:5V", "sonar:VCC", "red", [] ], - [ "esp:GND.1", "sonar:GND", "black", [] ], - [ "esp:19", "sonar:TRIG", "green", [] ], - [ "esp:18", "sonar:ECHO", "orange", [] ], - - [ "esp:3V3", "mpu:VCC", "red", [] ], - [ "esp:GND.1", "mpu:GND", "black", [] ], - [ "esp:22", "mpu:SCL", "green", [] ], - [ "esp:21", "mpu:SDA", "blue", [] ], - - [ "esp:3V3", "dht:VCC", "red", [] ], - [ "esp:GND.1", "dht:GND", "black", [] ], - [ "esp:23", "dht:SDA", "green", [] ], - - [ "esp:3V3", "gas:VCC", "red", [] ], - [ "esp:GND.1", "gas:GND", "black", [] ], - [ "esp:34", "gas:SIG", "green", [] ], - - [ "esp:3V3", "water:VCC", "red", [] ], - [ "esp:GND.1", "water:GND", "black", [] ], - [ "esp:36", "water:SIG", "green", [] ], - - [ "esp:GND.1", "buzzer:1", "black", [] ], - [ "esp:4", "buzzer:2", "purple", [] ], - - [ "esp:GND.1", "led_r:C", "black", [] ], - [ "esp:26", "led_r:A", "red", [] ], - - [ "esp:GND.1", "led_g:C", "black", [] ], - [ "esp:27", "led_g:A", "green", [] ], - - [ "esp:GND.2", "drv:GND", "black", [] ], - [ "esp:GND.2", "drv:GND2", "black", [] ], - [ "esp:3V3", "drv:VCC", "red", [] ], - [ "esp:5V", "drv:VM", "red", [] ], - [ "esp:5", "drv:STBY", "purple", [] ], - - [ "esp:25", "drv:PWMA", "green", [] ], - [ "esp:16", "drv:AIN1", "blue", [] ], - [ "esp:17", "drv:AIN2", "orange", [] ], - - [ "esp:14", "drv:PWMB", "green", [] ], - [ "esp:33", "drv:BIN1", "blue", [] ], - [ "esp:2", "drv:BIN2", "orange", [] ] + [ + "esp:GND.1", + "servo:GND", + "black", + [] + ], + [ + "esp:5V", + "servo:V+", + "red", + [] + ], + [ + "esp:13", + "servo:PWM", + "orange", + [] + ], + [ + "esp:3V3", + "tof:VCC", + "red", + [] + ], + [ + "esp:GND.1", + "tof:GND", + "black", + [] + ], + [ + "esp:22", + "tof:SCL", + "green", + [] + ], + [ + "esp:21", + "tof:SDA", + "blue", + [] + ], + [ + "esp:5V", + "sonar:VCC", + "red", + [] + ], + [ + "esp:GND.1", + "sonar:GND", + "black", + [] + ], + [ + "esp:19", + "sonar:TRIG", + "green", + [] + ], + [ + "esp:18", + "sonar:ECHO", + "orange", + [] + ], + [ + "esp:3V3", + "mpu:VCC", + "red", + [] + ], + [ + "esp:GND.1", + "mpu:GND", + "black", + [] + ], + [ + "esp:22", + "mpu:SCL", + "green", + [] + ], + [ + "esp:21", + "mpu:SDA", + "blue", + [] + ], + [ + "esp:3V3", + "dht:VCC", + "red", + [] + ], + [ + "esp:GND.1", + "dht:GND", + "black", + [] + ], + [ + "esp:23", + "dht:SDA", + "green", + [] + ], + [ + "esp:3V3", + "gas:VCC", + "red", + [] + ], + [ + "esp:GND.1", + "gas:GND", + "black", + [] + ], + [ + "esp:34", + "gas:SIG", + "green", + [] + ], + [ + "esp:3V3", + "water:VCC", + "red", + [] + ], + [ + "esp:GND.1", + "water:GND", + "black", + [] + ], + [ + "esp:36", + "water:SIG", + "green", + [] + ], + [ + "esp:GND.1", + "buzzer:1", + "black", + [] + ], + [ + "esp:4", + "buzzer:2", + "purple", + [] + ], + [ + "esp:GND.1", + "led_r:C", + "black", + [] + ], + [ + "esp:26", + "led_r:A", + "red", + [] + ], + [ + "esp:GND.1", + "led_g:C", + "black", + [] + ], + [ + "esp:27", + "led_g:A", + "green", + [] + ], + [ + "esp:GND.2", + "drv:GND", + "black", + [] + ], + [ + "esp:GND.2", + "drv:GND2", + "black", + [] + ], + [ + "esp:3V3", + "drv:VCC", + "red", + [] + ], + [ + "esp:5V", + "drv:VM", + "red", + [] + ], + [ + "esp:5", + "drv:STBY", + "purple", + [] + ], + [ + "esp:25", + "drv:PWMA", + "green", + [] + ], + [ + "esp:16", + "drv:AIN1", + "blue", + [] + ], + [ + "esp:17", + "drv:AIN2", + "orange", + [] + ], + [ + "esp:14", + "drv:PWMB", + "green", + [] + ], + [ + "esp:33", + "drv:BIN1", + "blue", + [] + ], + [ + "esp:2", + "drv:BIN2", + "orange", + [] + ] ] -} +} \ No newline at end of file diff --git a/fix_author.py b/fix_author.py new file mode 100644 index 0000000..5c328b6 --- /dev/null +++ b/fix_author.py @@ -0,0 +1,11 @@ +import json + +diagram = json.load(open('diagram.json')) +diagram['author'] = "" +json.dump(diagram, open('diagram.json', 'w'), indent=2) + +for chip in ['vl53l0x', 'tb6612fng']: + with open(f'{chip}.chip.json', 'r') as f: + data = json.load(f) + data['author'] = "" + json.dump(data, open(f'{chip}.chip.json', 'w'), indent=2) diff --git a/fix_wokwi.py b/fix_wokwi.py new file mode 100644 index 0000000..711eda7 --- /dev/null +++ b/fix_wokwi.py @@ -0,0 +1,12 @@ +import json + +for chip in ['vl53l0x', 'tb6612fng']: + with open(f'{chip}.chip.json', 'r') as f: + data = json.load(f) + + # name must be the exact suffix of 'chip-' + data['name'] = chip + + with open(f'{chip}.chip.json', 'w') as f: + json.dump(data, f, indent=2) + diff --git a/make_svgs.py b/make_svgs.py new file mode 100644 index 0000000..7f2fd23 --- /dev/null +++ b/make_svgs.py @@ -0,0 +1,26 @@ +import json + +vl53_svg = """ + + VL53L0X +""" + +tb66_svg = """ + + TB6612FNG +""" + +with open("vl53l0x.svg", "w") as f: f.write(vl53_svg) +with open("tb6612fng.svg", "w") as f: f.write(tb66_svg) + +for chip in ['vl53l0x', 'tb6612fng']: + with open(f'{chip}.chip.json', 'r') as f: + data = json.load(f) + + data['author'] = 'wokwi' # maybe wokwi is required? + + # Or maybe it just needs the icon? + # Wokwi custom chip docs: no, there is no icon field, wait there is! + # No, wait, if you don't provide an SVG, Wokwi draws a generic DIP chip. + pass + diff --git a/tb6612fng.c b/tb6612fng.chip.c similarity index 100% rename from tb6612fng.c rename to tb6612fng.chip.c diff --git a/tb6612fng.chip.json b/tb6612fng.chip.json index 4855e84..fefba4f 100644 --- a/tb6612fng.chip.json +++ b/tb6612fng.chip.json @@ -1,6 +1,6 @@ { "name": "tb6612fng", - "author": "Antigravity", + "author": "", "pins": [ "VM", "VCC", @@ -18,4 +18,4 @@ "AIN2", "PWMA" ] -} +} \ No newline at end of file diff --git a/tb6612fng.svg b/tb6612fng.svg new file mode 100644 index 0000000..1a98f7b --- /dev/null +++ b/tb6612fng.svg @@ -0,0 +1,6 @@ + + + + TB6612FNG + TOSHIBA + diff --git a/test_wokwi.py b/test_wokwi.py new file mode 100644 index 0000000..563724d --- /dev/null +++ b/test_wokwi.py @@ -0,0 +1,4 @@ +import json + +diagram = json.load(open('diagram.json')) +print([p['type'] for p in diagram['parts']]) diff --git a/vl53l0x.c b/vl53l0x.chip.c similarity index 100% rename from vl53l0x.c rename to vl53l0x.chip.c diff --git a/vl53l0x.chip.json b/vl53l0x.chip.json index 0812bd7..1438400 100644 --- a/vl53l0x.chip.json +++ b/vl53l0x.chip.json @@ -1,6 +1,6 @@ { - "name": "VL53L0X Time-of-Flight Sensor", - "author": "Antigravity", + "name": "vl53l0x", + "author": "", "pins": [ "VCC", "GND", @@ -19,4 +19,4 @@ "step": 1 } ] -} +} \ No newline at end of file diff --git a/vl53l0x.svg b/vl53l0x.svg new file mode 100644 index 0000000..d7cccd9 --- /dev/null +++ b/vl53l0x.svg @@ -0,0 +1,7 @@ + + + + + + VL53L0X + From ecbda8fa2b170a13b2c730ca9787d9ec4bc9969f Mon Sep 17 00:00:00 2001 From: sea-deep Date: Mon, 24 Aug 2026 04:47:58 +0530 Subject: [PATCH 03/13] fix: Start from original diagram, add only VL53L0X + TB6612FNG custom chips --- diagram.json | 843 +++++++++++++++++++++++++++++++++++--------- fix_author.py | 11 - fix_wokwi.py | 12 - make_svgs.py | 26 -- tb6612fng.chip.json | 20 +- test_wokwi.py | 4 - vl53l0x.chip.json | 11 +- 7 files changed, 690 insertions(+), 237 deletions(-) delete mode 100644 fix_author.py delete mode 100644 fix_wokwi.py delete mode 100644 make_svgs.py delete mode 100644 test_wokwi.py diff --git a/diagram.json b/diagram.json index bfc08eb..8955637 100644 --- a/diagram.json +++ b/diagram.json @@ -1,336 +1,865 @@ { "version": 1, - "author": "", + "author": "Dipak - Four-Wheel Mine Rescue Rover Prototype", "editor": "wokwi", "parts": [ + { + "type": "wokwi-text", + "id": "title", + "top": -505, + "left": -420, + "attrs": { + "text": "FOUR-WHEEL MINE RESCUE ROVER - HACKATHON PROTOTYPE" + } + }, + { + "type": "wokwi-text", + "id": "sensor_heading", + "top": -460, + "left": -470, + "attrs": { + "text": "ENVIRONMENT + OBSTACLE SENSORS" + } + }, { "type": "board-esp32-devkit-c-v4", "id": "esp", - "top": 0, - "left": 0 + "top": -36, + "left": -15, + "attrs": {} }, { - "type": "wokwi-servo", - "id": "servo", - "top": -150, - "left": -200 + "type": "wokwi-dht22", + "id": "dht", + "top": -366, + "left": -447, + "attrs": { + "temperature": "28", + "humidity": "64" + } }, { - "type": "chip-vl53l0x", - "id": "tof", - "top": -150, - "left": -50, + "type": "wokwi-text", + "id": "dht_label", + "top": -405, + "left": -480, "attrs": { - "distance": "500" + "text": "DHT22 - TEMPERATURE + HUMIDITY" } }, { - "type": "wokwi-hc-sr04", - "id": "sonar", - "top": -250, - "left": 100 + "type": "wokwi-resistor", + "id": "dht_pullup", + "top": -302, + "left": -258, + "attrs": { + "value": "10000" + } }, { - "type": "chip-tb6612fng", - "id": "drv", - "top": 200, - "left": -100 + "type": "wokwi-potentiometer", + "id": "gas", + "top": -174, + "left": -483, + "attrs": { + "value": "400" + } }, { - "type": "wokwi-dht22", - "id": "dht", - "top": -100, - "left": 250 + "type": "wokwi-text", + "id": "gas_label", + "top": -224, + "left": -487, + "attrs": { + "text": "MQ-4 METHANE (CH4) GAS SENSOR (GPIO34)" + } }, { - "type": "wokwi-potentiometer", - "id": "gas", - "top": 100, - "left": 300 + "type": "wokwi-hc-sr04", + "id": "front", + "top": -331, + "left": 428, + "attrs": { + "distance": "38" + } }, { - "type": "wokwi-potentiometer", - "id": "water", - "top": 150, - "left": 300 + "type": "wokwi-text", + "id": "front_label", + "top": -401, + "left": 380, + "attrs": { + "text": "HC-SR04 MOUNTED ON SG90 SCAN SERVO - ROTATES TO SCAN LEFT/RIGHT" + } + }, + { + "type": "wokwi-resistor", + "id": "echo_r_upper", + "top": 117, + "left": -238, + "attrs": { + "value": "10000" + } + }, + { + "type": "wokwi-resistor", + "id": "echo_r_lower", + "top": 174, + "left": -181, + "rotate": 90, + "attrs": { + "value": "15000" + } }, { "type": "wokwi-mpu6050", - "id": "mpu", - "top": -50, - "left": 300 + "id": "imu", + "top": 288, + "left": -452, + "attrs": {} + }, + { + "type": "wokwi-text", + "id": "imu_label", + "top": 248, + "left": -475, + "attrs": { + "text": "MPU6050 - ORIENTATION / I2C" + } + }, + { + "type": "wokwi-ky-040", + "id": "encoder_left", + "top": 501, + "left": -416, + "attrs": {} + }, + { + "type": "wokwi-ky-040", + "id": "encoder_right", + "top": 501, + "left": -219, + "attrs": {} + }, + { + "type": "wokwi-text", + "id": "encoder_label", + "top": 457, + "left": -441, + "attrs": { + "text": "LEFT AND RIGHT WHEEL ENCODER SIMULATORS" + } + }, + { + "type": "wokwi-resistor", + "id": "encoder_right_pullup", + "top": 425, + "left": -36, + "rotate": 90, + "attrs": { + "value": "10000" + } + }, + { + "type": "wokwi-servo", + "id": "scan_servo", + "top": -331, + "left": 630, + "attrs": { + "horn": "single" + } + }, + { + "type": "wokwi-text", + "id": "servo_label", + "top": -401, + "left": 620, + "attrs": { + "text": "SG90 SCANNING SERVO - GPIO13 - CARRIES THE HC-SR04" + } }, { "type": "wokwi-buzzer", "id": "buzzer", - "top": 50, - "left": -250 + "top": -115, + "left": 305, + "attrs": { + "volume": "0.2" + } + }, + { + "type": "wokwi-resistor", + "id": "buzzer_resistor", + "top": -52, + "left": 237, + "attrs": { + "value": "220" + } + }, + { + "type": "wokwi-text", + "id": "buzzer_label", + "top": -156, + "left": 275, + "attrs": { + "text": "BUZZER - DANGER ONLY (GAS / TILT / WATER / TRAPPED) - GPIO4" + } }, { "type": "wokwi-led", - "id": "led_r", - "top": 100, - "left": -250, + "id": "status_red", + "top": -100, + "left": 620, + "attrs": { + "color": "red", + "label": "DANGER" + } + }, + { + "type": "wokwi-resistor", + "id": "status_red_resistor", + "top": -60, + "left": 600, "attrs": { - "color": "red" + "value": "220" } }, { "type": "wokwi-led", - "id": "led_g", - "top": 150, - "left": -250, + "id": "status_green", + "top": -100, + "left": 720, + "attrs": { + "color": "green", + "label": "NORMAL" + } + }, + { + "type": "wokwi-resistor", + "id": "status_green_resistor", + "top": -60, + "left": 700, + "attrs": { + "value": "220" + } + }, + { + "type": "wokwi-text", + "id": "status_led_label", + "top": -156, + "left": 590, + "attrs": { + "text": "STATUS LEDS - RED GPIO26 = DANGER | GREEN GPIO27 = NORMAL" + } + }, + { + "type": "wokwi-potentiometer", + "id": "water_sim", + "top": -96, + "left": 536, + "attrs": { + "value": "120" + } + }, + { + "type": "wokwi-text", + "id": "water_label", + "top": -20, + "left": 434, + "attrs": { + "text": "WATER SENSOR PLACEHOLDER - SAFE 3.3V / GPIO36" + } + }, + { + "type": "wokwi-text", + "id": "powerbank_label", + "top": 413, + "left": 134, + "attrs": { + "text": "REAL PROTOTYPE POWER: 10000mAh USB POWER BANK - 5V / 3A PREFERRED" + } + }, + { + "type": "wokwi-text", + "id": "receiver_label", + "top": -454, + "left": 125, + "attrs": { + "text": "REAL LINK: ESP-NOW -> RECEIVER ESP32 -> USB -> NODE.JS DASHBOARD" + } + }, + { + "type": "wokwi-text", + "id": "simulation_note", + "top": 486, + "left": 143, + "attrs": { + "text": "WOKWI: ONE MCU PER PROJECT; RECEIVER + ESP-NOW REQUIRE REAL HARDWARE" + } + }, + { + "type": "wokwi-text", + "id": "safety_note", + "top": 532, + "left": 143, + "attrs": { + "text": "SAFETY: HC-SR04 ECHO + MQ ANALOG OUTPUT USE 10k / 15k DIVIDERS" + } + }, + { + "type": "chip-vl53l0x", + "id": "tof", + "top": -344, + "left": 226, + "attrs": { + "distance": "500" + } + }, + { + "type": "wokwi-text", + "id": "tof_label", + "top": -380, + "left": 226, "attrs": { - "color": "green" + "text": "VL53L0X ToF (on servo) - I2C GPIO21/22" + } + }, + { + "type": "chip-tb6612fng", + "id": "driver", + "top": 120, + "left": 270, + "attrs": {} + }, + { + "type": "wokwi-text", + "id": "driver_label", + "top": 80, + "left": 189, + "attrs": { + "text": "TB6612FNG - ChA=LEFT motors (GPIO25,16,17), ChB=RIGHT motors (GPIO14,33,2), STBY=GPIO5" } } ], "connections": [ [ - "esp:GND.1", - "servo:GND", - "black", - [] - ], - [ - "esp:5V", - "servo:V+", - "red", + "esp:TX", + "$serialMonitor:RX", + "", [] ], [ - "esp:13", - "servo:PWM", - "orange", + "esp:RX", + "$serialMonitor:TX", + "", [] ], [ + "dht:VCC", "esp:3V3", - "tof:VCC", "red", - [] + [ + "h36", + "v75" + ] ], [ + "dht:GND", "esp:GND.1", - "tof:GND", "black", - [] + [ + "h42", + "v120" + ] ], [ - "esp:22", - "tof:SCL", + "dht:SDA", + "esp:23", "green", - [] + [ + "h55" + ] ], [ - "esp:21", - "tof:SDA", - "blue", - [] + "dht_pullup:1", + "esp:3V3", + "red", + [ + "v-18" + ] + ], + [ + "dht_pullup:2", + "dht:SDA", + "green", + [ + "h-22" + ] + ], + [ + "gas:VCC", + "esp:3V3", + "red", + [ + "h25", + "v190" + ] + ], + [ + "gas:GND", + "esp:GND.1", + "black", + [ + "h30", + "v95" + ] + ], + [ + "gas:SIG", + "esp:34", + "orange", + [ + "h35", + "v120" + ] ], [ + "front:VCC", "esp:5V", - "sonar:VCC", "red", - [] + [ + "h700", + "v-260" + ] ], [ + "front:GND", "esp:GND.1", - "sonar:GND", "black", - [] + [ + "h705", + "v-230" + ] ], [ + "front:TRIG", "esp:19", - "sonar:TRIG", - "green", - [] + "cyan", + [ + "h-680" + ] + ], + [ + "front:ECHO", + "echo_r_upper:1", + "yellow", + [ + "h-680", + "v420" + ] ], [ + "echo_r_upper:2", "esp:18", - "sonar:ECHO", - "orange", - [] + "yellow", + [ + "h16" + ] ], [ - "esp:3V3", - "mpu:VCC", - "red", - [] + "echo_r_upper:2", + "echo_r_lower:1", + "yellow", + [ + "h9", + "v22" + ] ], [ - "esp:GND.1", - "mpu:GND", + "echo_r_lower:2", + "esp:GND.2", "black", - [] + [ + "v20" + ] ], [ - "esp:22", - "mpu:SCL", - "green", - [] + "front:VCC", + "scan_servo:V+", + "red", + [ + "v10", + "h150" + ] ], [ + "imu:VCC", + "esp:3V3", + "red", + [ + "h29", + "v-100" + ] + ], + [ + "imu:GND", + "esp:GND.2", + "black", + [ + "h34", + "v-55" + ] + ], + [ + "imu:SDA", "esp:21", - "mpu:SDA", "blue", - [] + [ + "h56" + ] + ], + [ + "imu:SCL", + "esp:22", + "purple", + [ + "h72" + ] ], [ + "encoder_left:VCC", "esp:3V3", - "dht:VCC", "red", - [] + [ + "h22", + "v-108" + ] ], [ - "esp:GND.1", - "dht:GND", + "encoder_left:GND", + "esp:GND.2", "black", - [] + [ + "h33", + "v-90" + ] ], [ - "esp:23", - "dht:SDA", + "encoder_left:CLK", + "esp:32", "green", - [] + [ + "h49" + ] ], [ + "encoder_right:VCC", "esp:3V3", - "gas:VCC", "red", - [] + [ + "h21", + "v-100" + ] ], [ - "esp:GND.1", - "gas:GND", + "encoder_right:GND", + "esp:GND.2", "black", - [] + [ + "h29", + "v-73" + ] ], [ - "esp:34", - "gas:SIG", + "encoder_right:CLK", + "esp:35", "green", - [] + [ + "h42" + ] ], [ + "encoder_right_pullup:1", "esp:3V3", - "water:VCC", "red", - [] + [ + "v-26" + ] ], [ - "esp:GND.1", - "water:GND", + "encoder_right_pullup:2", + "encoder_right:CLK", + "green", + [ + "h-26" + ] + ], + [ + "water_sim:VCC", + "esp:3V3", + "red", + [ + "v36", + "h-118" + ] + ], + [ + "water_sim:GND", + "esp:GND.2", "black", - [] + [ + "v47", + "h-129" + ] ], [ - "esp:36", - "water:SIG", - "green", - [] + "water_sim:SIG", + "esp:VP", + "cyan", + [ + "v59", + "h-140" + ] + ], + [ + "scan_servo:V+", + "esp:5V", + "red", + [ + "v44", + "h-61" + ] ], [ + "scan_servo:GND", "esp:GND.1", - "buzzer:1", "black", - [] + [ + "v57", + "h-71" + ] ], [ - "esp:4", - "buzzer:2", + "scan_servo:PWM", + "esp:13", "purple", - [] + [ + "v70", + "h-81" + ] ], [ - "esp:GND.1", - "led_r:C", + "buzzer:1", + "esp:GND.2", "black", - [] + [ + "v33", + "h-68" + ] + ], + [ + "buzzer:2", + "buzzer_resistor:1", + "orange", + [ + "v21" + ] + ], + [ + "buzzer_resistor:2", + "esp:4", + "orange", + [ + "h-32" + ] ], [ + "status_red:A", + "status_red_resistor:1", + "red", + [ + "v-15" + ] + ], + [ + "status_red_resistor:2", "esp:26", - "led_r:A", "red", - [] + [ + "h-40", + "v-20" + ] ], [ - "esp:GND.1", - "led_g:C", + "status_red:C", + "esp:GND.2", "black", - [] + [ + "v40", + "h-90" + ] ], [ - "esp:27", - "led_g:A", + "status_green:A", + "status_green_resistor:1", "green", - [] + [ + "v-15" + ] ], [ - "esp:GND.2", - "drv:GND", - "black", - [] + "status_green_resistor:2", + "esp:27", + "green", + [ + "h-40", + "v-20" + ] ], [ + "status_green:C", "esp:GND.2", - "drv:GND2", "black", - [] + [ + "v40", + "h-190" + ] ], [ + "tof:VCC", "esp:3V3", - "drv:VCC", "red", - [] + [ + "v24", + "h-72" + ] + ], + [ + "tof:GND", + "esp:GND.1", + "black", + [ + "v30", + "h-60" + ] + ], + [ + "tof:SDA", + "esp:21", + "blue", + [ + "v39", + "h-42" + ] + ], + [ + "tof:SCL", + "esp:22", + "purple", + [ + "v48", + "h-54" + ] ], [ + "driver:VM", "esp:5V", - "drv:VM", "red", - [] + [ + "v-30", + "h-80" + ] ], [ + "driver:VCC", + "esp:3V3", + "red", + [ + "v-20", + "h-80" + ] + ], + [ + "driver:GND", + "esp:GND.2", + "black", + [ + "v10", + "h-80" + ] + ], + [ + "driver:GND2", + "esp:GND.2", + "black", + [ + "v20", + "h-80" + ] + ], + [ + "driver:STBY", "esp:5", - "drv:STBY", "purple", - [] + [ + "v-10", + "h80" + ] ], [ + "driver:PWMA", "esp:25", - "drv:PWMA", "green", - [] + [ + "v-40", + "h80" + ] ], [ + "driver:AIN1", "esp:16", - "drv:AIN1", "blue", - [] + [ + "v-50", + "h80" + ] ], [ + "driver:AIN2", "esp:17", - "drv:AIN2", "orange", - [] + [ + "v-60", + "h80" + ] ], [ + "driver:PWMB", "esp:14", - "drv:PWMB", "green", - [] + [ + "v40", + "h80" + ] ], [ + "driver:BIN1", "esp:33", - "drv:BIN1", "blue", - [] + [ + "v50", + "h80" + ] ], [ + "driver:BIN2", "esp:2", - "drv:BIN2", "orange", - [] + [ + "v60", + "h80" + ] ] - ] + ], + "dependencies": {} } \ No newline at end of file diff --git a/fix_author.py b/fix_author.py deleted file mode 100644 index 5c328b6..0000000 --- a/fix_author.py +++ /dev/null @@ -1,11 +0,0 @@ -import json - -diagram = json.load(open('diagram.json')) -diagram['author'] = "" -json.dump(diagram, open('diagram.json', 'w'), indent=2) - -for chip in ['vl53l0x', 'tb6612fng']: - with open(f'{chip}.chip.json', 'r') as f: - data = json.load(f) - data['author'] = "" - json.dump(data, open(f'{chip}.chip.json', 'w'), indent=2) diff --git a/fix_wokwi.py b/fix_wokwi.py deleted file mode 100644 index 711eda7..0000000 --- a/fix_wokwi.py +++ /dev/null @@ -1,12 +0,0 @@ -import json - -for chip in ['vl53l0x', 'tb6612fng']: - with open(f'{chip}.chip.json', 'r') as f: - data = json.load(f) - - # name must be the exact suffix of 'chip-' - data['name'] = chip - - with open(f'{chip}.chip.json', 'w') as f: - json.dump(data, f, indent=2) - diff --git a/make_svgs.py b/make_svgs.py deleted file mode 100644 index 7f2fd23..0000000 --- a/make_svgs.py +++ /dev/null @@ -1,26 +0,0 @@ -import json - -vl53_svg = """ - - VL53L0X -""" - -tb66_svg = """ - - TB6612FNG -""" - -with open("vl53l0x.svg", "w") as f: f.write(vl53_svg) -with open("tb6612fng.svg", "w") as f: f.write(tb66_svg) - -for chip in ['vl53l0x', 'tb6612fng']: - with open(f'{chip}.chip.json', 'r') as f: - data = json.load(f) - - data['author'] = 'wokwi' # maybe wokwi is required? - - # Or maybe it just needs the icon? - # Wokwi custom chip docs: no, there is no icon field, wait there is! - # No, wait, if you don't provide an SVG, Wokwi draws a generic DIP chip. - pass - diff --git a/tb6612fng.chip.json b/tb6612fng.chip.json index fefba4f..7024e82 100644 --- a/tb6612fng.chip.json +++ b/tb6612fng.chip.json @@ -1,21 +1,5 @@ { "name": "tb6612fng", "author": "", - "pins": [ - "VM", - "VCC", - "GND", - "AO1", - "AO2", - "BO2", - "BO1", - "GND2", - "PWMB", - "BIN2", - "BIN1", - "STBY", - "AIN1", - "AIN2", - "PWMA" - ] -} \ No newline at end of file + "pins": ["VM","VCC","GND","AO1","AO2","BO2","BO1","GND2","PWMB","BIN2","BIN1","STBY","AIN1","AIN2","PWMA"] +} diff --git a/test_wokwi.py b/test_wokwi.py deleted file mode 100644 index 563724d..0000000 --- a/test_wokwi.py +++ /dev/null @@ -1,4 +0,0 @@ -import json - -diagram = json.load(open('diagram.json')) -print([p['type'] for p in diagram['parts']]) diff --git a/vl53l0x.chip.json b/vl53l0x.chip.json index 1438400..3e4b889 100644 --- a/vl53l0x.chip.json +++ b/vl53l0x.chip.json @@ -1,14 +1,7 @@ { "name": "vl53l0x", "author": "", - "pins": [ - "VCC", - "GND", - "SCL", - "SDA", - "XSHUT", - "GPIO1" - ], + "pins": ["VCC", "GND", "SCL", "SDA", "XSHUT", "GPIO1"], "controls": [ { "id": "distance", @@ -19,4 +12,4 @@ "step": 1 } ] -} \ No newline at end of file +} From afa6b93753f6effa583bbd060b74a910ce59bdc4 Mon Sep 17 00:00:00 2001 From: sea-deep Date: Mon, 24 Aug 2026 04:51:04 +0530 Subject: [PATCH 04/13] fix: Build WASM binaries for custom chips and register in wokwi.toml --- dist/tb6612fng.chip.json | 5 ++ dist/tb6612fng.json | 5 ++ dist/tb6612fng.svg | 6 ++ dist/tb6612fng.wasm | Bin 0 -> 66391 bytes dist/vl53l0x.chip.json | 15 ++++ dist/vl53l0x.json | 15 ++++ dist/vl53l0x.svg | 7 ++ dist/vl53l0x.wasm | Bin 0 -> 140087 bytes tb6612fng.json | 5 ++ vl53l0x.json | 15 ++++ wokwi-api.h | 162 +++++++++++++++++++++++++++++++++++++++ wokwi.toml | 10 ++- 12 files changed, 244 insertions(+), 1 deletion(-) create mode 100644 dist/tb6612fng.chip.json create mode 100644 dist/tb6612fng.json create mode 100644 dist/tb6612fng.svg create mode 100755 dist/tb6612fng.wasm create mode 100644 dist/vl53l0x.chip.json create mode 100644 dist/vl53l0x.json create mode 100644 dist/vl53l0x.svg create mode 100755 dist/vl53l0x.wasm create mode 100644 tb6612fng.json create mode 100644 vl53l0x.json create mode 100644 wokwi-api.h diff --git a/dist/tb6612fng.chip.json b/dist/tb6612fng.chip.json new file mode 100644 index 0000000..7024e82 --- /dev/null +++ b/dist/tb6612fng.chip.json @@ -0,0 +1,5 @@ +{ + "name": "tb6612fng", + "author": "", + "pins": ["VM","VCC","GND","AO1","AO2","BO2","BO1","GND2","PWMB","BIN2","BIN1","STBY","AIN1","AIN2","PWMA"] +} diff --git a/dist/tb6612fng.json b/dist/tb6612fng.json new file mode 100644 index 0000000..7024e82 --- /dev/null +++ b/dist/tb6612fng.json @@ -0,0 +1,5 @@ +{ + "name": "tb6612fng", + "author": "", + "pins": ["VM","VCC","GND","AO1","AO2","BO2","BO1","GND2","PWMB","BIN2","BIN1","STBY","AIN1","AIN2","PWMA"] +} diff --git a/dist/tb6612fng.svg b/dist/tb6612fng.svg new file mode 100644 index 0000000..1a98f7b --- /dev/null +++ b/dist/tb6612fng.svg @@ -0,0 +1,6 @@ + + + + TB6612FNG + TOSHIBA + diff --git a/dist/tb6612fng.wasm b/dist/tb6612fng.wasm new file mode 100755 index 0000000000000000000000000000000000000000..626c97f467d560238486cca54e10de18579cdab6 GIT binary patch literal 66391 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 + + + + + VL53L0X + diff --git a/dist/vl53l0x.wasm b/dist/vl53l0x.wasm new file mode 100755 index 0000000000000000000000000000000000000000..86c4773e3e8e3b43ee61fd4eaf701bc6c1725021 GIT binary patch literal 140087 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 gn^L^2HdVW<}aRFdv5yw0ljWBC;$Ke literal 0 HcmV?d00001 diff --git a/tb6612fng.json b/tb6612fng.json new file mode 100644 index 0000000..7024e82 --- /dev/null +++ b/tb6612fng.json @@ -0,0 +1,5 @@ +{ + "name": "tb6612fng", + "author": "", + "pins": ["VM","VCC","GND","AO1","AO2","BO2","BO1","GND2","PWMB","BIN2","BIN1","STBY","AIN1","AIN2","PWMA"] +} diff --git a/vl53l0x.json b/vl53l0x.json new file mode 100644 index 0000000..3e4b889 --- /dev/null +++ b/vl53l0x.json @@ -0,0 +1,15 @@ +{ + "name": "vl53l0x", + "author": "", + "pins": ["VCC", "GND", "SCL", "SDA", "XSHUT", "GPIO1"], + "controls": [ + { + "id": "distance", + "label": "Distance (mm)", + "type": "range", + "min": 30, + "max": 2000, + "step": 1 + } + ] +} diff --git a/wokwi-api.h b/wokwi-api.h new file mode 100644 index 0000000..f3fd624 --- /dev/null +++ b/wokwi-api.h @@ -0,0 +1,162 @@ +#include +#include + +#ifndef WOKWI_API_H +#define WOKWI_API_H + +enum pin_value { + LOW = 0, + HIGH = 1 +}; + +enum pin_mode { + INPUT = 0, + OUTPUT = 1, + INPUT_PULLUP = 2, + INPUT_PULLDOWN = 3, + ANALOG = 4, + + OUTPUT_LOW = 16, + OUTPUT_HIGH = 17, +}; + +enum edge { + RISING = 1, + FALLING = 2, + BOTH = 3, +}; + +int __attribute__((export_name("__wokwi_api_version_1"))) __attribute__((weak)) __wokwi_api_version_1(void) { return 1; } + +#ifdef __cplusplus +extern "C" { +#endif + +typedef int32_t pin_t; +#define NO_PIN ((pin_t)-1) + +typedef struct { + void *user_data; + uint32_t edge; + void (*pin_change)(void *user_data, pin_t pin, uint32_t value); +} pin_watch_config_t; + +extern __attribute__((export_name("chipInit"))) void chip_init(void); + +extern __attribute__((import_name("pinInit"))) pin_t pin_init(const char *name, uint32_t mode); + +extern __attribute__((import_name("pinRead"))) uint32_t pin_read(pin_t pin); +extern __attribute__((import_name("pinWrite"))) void pin_write(pin_t pin, uint32_t value); +extern __attribute__((import_name("pinWatch"))) bool pin_watch(pin_t pin, const pin_watch_config_t *config); +extern __attribute__((import_name("pinWatchStop"))) void pin_watch_stop(pin_t pin); +extern __attribute__((import_name("pinMode"))) void pin_mode(pin_t pin, uint32_t value); +extern __attribute__((import_name("pinADCRead"))) float pin_adc_read(pin_t pin); +extern __attribute__((import_name("pinDACWrite"))) float pin_dac_write(pin_t pin, float voltage); + +typedef uint32_t string_t; +#define STRING_NULL 0 + +extern __attribute__((import_name("stringGetLength"))) uint32_t string_get_length(string_t string); +extern __attribute__((import_name("stringRead"))) uint32_t string_read(string_t string, char *buf, uint32_t buffer_size); + +extern __attribute__((import_name("attrInit"))) uint32_t attr_init(const char *name, uint32_t default_value); +extern __attribute__((import_name("attrInitFloat"))) uint32_t attr_init_float(const char *name, float default_value); +extern __attribute__((import_name("attrRead"))) uint32_t attr_read(uint32_t attr_id); +extern __attribute__((import_name("attrReadFloat"))) float attr_read_float(uint32_t attr_id); +extern __attribute__((import_name("attrStringInit"))) string_t attr_string_init(const char *name); + +typedef struct { + void *user_data; + uint32_t address; + pin_t scl; + pin_t sda; + bool (*connect)(void *user_data, uint32_t address, bool read); + uint8_t (*read)(void *user_data); + bool (*write)(void *user_data, uint8_t data); + void (*disconnect)(void *user_data); + uint32_t reserved[8]; +} i2c_config_t; + +typedef uint32_t i2c_dev_t; + +extern __attribute__((import_name("i2cInit"))) i2c_dev_t i2c_init(const i2c_config_t *config); + +typedef struct { + void *user_data; + pin_t rx; + pin_t tx; + uint32_t baud_rate; + void (*rx_data)(void *user_data, uint8_t byte); + void (*write_done)(void *user_data); + uint32_t reserved[8]; +} uart_config_t; + +typedef uint32_t uart_dev_t; + +extern __attribute__((import_name("uartInit"))) uart_dev_t uart_init(const uart_config_t *config); +extern __attribute__((import_name("uartWrite"))) bool uart_write(uart_dev_t uart, uint8_t *buffer, uint32_t count); + +typedef struct { + void *user_data; + pin_t sck; + pin_t mosi; + pin_t miso; + uint32_t mode; + void (*done)(void *user_data, uint8_t *buffer, uint32_t count); + uint32_t reserved[8]; +} spi_config_t; +typedef uint32_t spi_dev_t; + +extern __attribute__((import_name("spiInit"))) spi_dev_t spi_init(const spi_config_t *spi_config); +extern __attribute__((import_name("spiStart"))) void spi_start(const spi_dev_t spi, uint8_t *buffer, uint32_t count); +extern __attribute__((import_name("spiStop"))) void spi_stop(const spi_dev_t spi); + +typedef struct { + void *user_data; + void (*callback)(void *user_data); + uint32_t reserved[8]; +} timer_config_t; + +typedef uint32_t timer_t; + +extern __attribute__((import_name("timerInit"))) timer_t timer_init(const timer_config_t *config); +extern __attribute__((import_name("timerStart"))) void timer_start(const timer_t timer, uint32_t micros, bool repeat); +extern __attribute__((import_name("timerStartNanos"))) void timer_start_ns_d(const timer_t timer, double nanos, bool repeat); +static void timer_start_ns(const timer_t timer, uint64_t nanos, bool repeat) { + timer_start_ns_d(timer, (double)nanos, repeat); +} +extern __attribute__((import_name("timerStop"))) void timer_stop(const timer_t timer); + +extern __attribute__((import_name("getSimNanos"))) double get_sim_nanos_d(void); + +static uint64_t get_sim_nanos(void) { + return (uint64_t)get_sim_nanos_d(); +} + +typedef uint32_t buffer_t; +extern __attribute__((import_name("framebufferInit"))) buffer_t framebuffer_init(uint32_t *pixel_width, uint32_t *pixel_height); +extern __attribute__((import_name("bufferRead"))) void buffer_read(buffer_t buffer, uint32_t offset, void *data, uint32_t data_len); +extern __attribute__((import_name("bufferWrite"))) void buffer_write(buffer_t buffer, uint32_t offset, void *data, uint32_t data_len); + +// Experimental API - subject to change +extern __attribute__((import_name("_symbolResolve"))) void* _symbol_resolve(char *symbol_name); +extern __attribute__((import_name("_mcuReadMemory"))) bool _mcu_read_memory(const void *address, void *target, uint32_t size); +extern __attribute__((import_name("_mcuReadUint32"))) uint32_t _mcu_read_uint32(const void *address); +extern __attribute__((import_name("_mcuReadUint32"))) void* _mcu_read_ptr(const void *address); +extern __attribute__((import_name("_mcuReadPC"))) uint32_t _mcu_read_pc(); +extern __attribute__((import_name("_mcuReadSP"))) uint32_t _mcu_read_sp(); + +typedef struct { + void *user_data; + void (*callback)(void *user_data, uint32_t core, uint32_t sp); + uint32_t sp_min; + uint32_t sp_max; + uint32_t reserved[8]; +} sp_monitor_config_t; +extern __attribute__((import_name("_mcuMonitorSP"))) uint32_t _mcu_monitor_sp(const sp_monitor_config_t *config); + +#ifdef __cplusplus +} +#endif + +#endif /* WOKWI_API_H */ diff --git a/wokwi.toml b/wokwi.toml index e959a67..102d3b9 100644 --- a/wokwi.toml +++ b/wokwi.toml @@ -2,4 +2,12 @@ version = 1 firmware = '.pio/build/esp32dev/firmware.bin' elf = '.pio/build/esp32dev/firmware.elf' -rfc2217ServerPort = 4000 \ No newline at end of file +rfc2217ServerPort = 4000 + +[[chip]] +name = "vl53l0x" +binary = "dist/vl53l0x.wasm" + +[[chip]] +name = "tb6612fng" +binary = "dist/tb6612fng.wasm" \ No newline at end of file From 194bbc0664d7fbea030d357f82eab310d4b19113 Mon Sep 17 00:00:00 2001 From: sea-deep Date: Mon, 24 Aug 2026 04:52:11 +0530 Subject: [PATCH 05/13] fix: Correct VL53L0X register emulation and ESP-NOW peerInfo initialization --- dist/vl53l0x.wasm | Bin 140087 -> 140095 bytes src/main.cpp | 2 +- vl53l0x.chip.c | 6 +++++- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/dist/vl53l0x.wasm b/dist/vl53l0x.wasm index 86c4773e3e8e3b43ee61fd4eaf701bc6c1725021..e0e4418891103a36d7629457f6e178bb1eb7ea87 100755 GIT binary patch delta 7729 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 AM*si- delta 8018 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 AqW}N^ diff --git a/src/main.cpp b/src/main.cpp index 1dd37dd..be583db 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -137,7 +137,7 @@ void setup() { } esp_now_register_recv_cb(onDataRecv); - esp_now_peer_info_t peerInfo; + esp_now_peer_info_t peerInfo = {}; memcpy(peerInfo.peer_addr, gatewayAddress, 6); peerInfo.channel = 0; peerInfo.encrypt = false; diff --git a/vl53l0x.chip.c b/vl53l0x.chip.c index 842b996..9857c9d 100644 --- a/vl53l0x.chip.c +++ b/vl53l0x.chip.c @@ -109,8 +109,12 @@ void chip_init(void) { chip->registers[REG_IDENTIFICATION_MODEL_ID] = 0xEE; chip->registers[0xC1] = 0xAA; chip->registers[REG_IDENTIFICATION_REVISION_ID] = 0x10; - chip->registers[0x51] = 0x00; // VCSEL Period + chip->registers[0x51] = 0x00; // Expected Model ID check 0x0099 + chip->registers[0x52] = 0x99; chip->registers[0x61] = 0x00; + chip->registers[0x62] = 0x00; + chip->registers[0x88] = 0x00; + chip->registers[0x89] = 0x00; chip->registers[REG_RESULT_INTERRUPT_STATUS] = 0x07; // Initial measurement values From 82fac8430d5fdc58d3449b0ba9cddf58d6ac8191 Mon Sep 17 00:00:00 2001 From: sea-deep Date: Mon, 24 Aug 2026 05:02:17 +0530 Subject: [PATCH 06/13] fix: Resolve overlapping diagram text labels and ensure clean custom chip compilation --- diagram.json | 18 +++++++-------- dist/vl53l0x.wasm | Bin 140095 -> 140183 bytes src/main.cpp | 4 +++- vl53l0x.chip.c | 56 ++++++++++++++++++++++++++-------------------- 4 files changed, 44 insertions(+), 34 deletions(-) diff --git a/diagram.json b/diagram.json index 8955637..ecab82a 100644 --- a/diagram.json +++ b/diagram.json @@ -86,10 +86,10 @@ { "type": "wokwi-text", "id": "front_label", - "top": -401, - "left": 380, + "top": -390, + "left": 350, "attrs": { - "text": "HC-SR04 MOUNTED ON SG90 SCAN SERVO - ROTATES TO SCAN LEFT/RIGHT" + "text": "HC-SR04 FIXED FORWARD SENSOR (TRIG=19, ECHO=18)" } }, { @@ -172,10 +172,10 @@ { "type": "wokwi-text", "id": "servo_label", - "top": -401, - "left": 620, + "top": -390, + "left": 600, "attrs": { - "text": "SG90 SCANNING SERVO - GPIO13 - CARRIES THE HC-SR04" + "text": "SG90 SCANNING SERVO (GPIO13)" } }, { @@ -318,10 +318,10 @@ { "type": "wokwi-text", "id": "tof_label", - "top": -380, - "left": 226, + "top": -390, + "left": 100, "attrs": { - "text": "VL53L0X ToF (on servo) - I2C GPIO21/22" + "text": "VL53L0X ToF SENSOR (ON SERVO) - I2C (21, 22)" } }, { diff --git a/dist/vl53l0x.wasm b/dist/vl53l0x.wasm index e0e4418891103a36d7629457f6e178bb1eb7ea87..fbca06e57d0491aa9fc3a17fe560e40fe96deeb9 100755 GIT binary patch delta 8429 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 DTblWC delta 8045 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 if (distance_mm > 8191) distance_mm = 8191; // 13-bit max range - // Range Millimeter High and Low bytes in result buffer + // Range Millimeter High and Low bytes in result buffer (0x14 + 10 = 0x1E) chip->registers[0x1E] = (uint8_t)((distance_mm >> 8) & 0xFF); chip->registers[0x1F] = (uint8_t)(distance_mm & 0xFF); - // Status: Sample Ready (bit 2 or bit 0 set, 0x04 / 0x07) - chip->registers[REG_RESULT_INTERRUPT_STATUS] = 0x07; - chip->registers[REG_RESULT_RANGE_STATUS] = 0x00; // Device ready / Valid measurement + // Status: Sample Ready (0x04 = NEW_SAMPLE_READY) + chip->registers[REG_RESULT_INTERRUPT_STATUS] = 0x04; + chip->registers[REG_RESULT_RANGE_STATUS] = 0x01; } static bool on_i2c_connect(void *user_data, uint32_t address, bool read) { chip_state_t *chip = (chip_state_t *)user_data; - - if (read) { - // When master starts a read, update sensor register data - update_measurement_registers(chip); - } else { - // Starting a write sequence: the first byte will be the register pointer + if (!read) { chip->is_first_write_byte = true; } - - return true; // ACK address + return true; } static uint8_t on_i2c_read(void *user_data) { chip_state_t *chip = (chip_state_t *)user_data; - - // Return current register byte and auto-increment pointer uint8_t value = chip->registers[chip->reg_ptr]; + + if (chip->reg_ptr == REG_SYSRANGE_START) { + value = 0x00; + } else if (chip->reg_ptr == REG_RESULT_INTERRUPT_STATUS) { + if (chip->registers[0x0B] != 0) { + value = 0x00; + chip->registers[0x0B] = 0x00; + } else { + value = 0x04; + } + } else if (chip->reg_ptr == REG_RESULT_RANGE_STATUS) { + value = 0x01; + } + chip->reg_ptr++; return value; } @@ -68,22 +74,21 @@ static bool on_i2c_write(void *user_data, uint8_t data) { chip_state_t *chip = (chip_state_t *)user_data; if (chip->is_first_write_byte) { - // First byte after connect is register address index chip->reg_ptr = data; chip->is_first_write_byte = false; } else { - // Subsequent bytes are data written to the register chip->registers[chip->reg_ptr] = data; - // Handle triggers (e.g. Sysrange Start) - if (chip->reg_ptr == REG_SYSRANGE_START) { + if (chip->reg_ptr == 0x0B && data == 0x01) { + chip->registers[0x0B] = 0x01; + } else if (chip->reg_ptr == REG_SYSRANGE_START) { update_measurement_registers(chip); } chip->reg_ptr++; } - return true; // ACK byte + return true; } static void on_i2c_disconnect(void *user_data) { @@ -109,13 +114,16 @@ void chip_init(void) { chip->registers[REG_IDENTIFICATION_MODEL_ID] = 0xEE; chip->registers[0xC1] = 0xAA; chip->registers[REG_IDENTIFICATION_REVISION_ID] = 0x10; - chip->registers[0x51] = 0x00; // Expected Model ID check 0x0099 - chip->registers[0x52] = 0x99; - chip->registers[0x61] = 0x00; - chip->registers[0x62] = 0x00; + chip->registers[0x50] = 0x07; // Pre-range VCSEL period + chip->registers[0x70] = 0x05; // Final-range VCSEL period + chip->registers[0x83] = 0x00; + chip->registers[0x84] = 0x01; // Osc frequency (MSB) + chip->registers[0x85] = 0x00; // Osc frequency (LSB) chip->registers[0x88] = 0x00; chip->registers[0x89] = 0x00; - chip->registers[REG_RESULT_INTERRUPT_STATUS] = 0x07; + chip->registers[0x91] = 0x3C; // Stop variable + chip->registers[REG_RESULT_INTERRUPT_STATUS] = 0x04; + chip->registers[REG_RESULT_RANGE_STATUS] = 0x01; // Initial measurement values update_measurement_registers(chip); From 5f5125e036db997da70c961afbfb755bd4300ad1 Mon Sep 17 00:00:00 2001 From: sea-deep Date: Mon, 24 Aug 2026 05:06:57 +0530 Subject: [PATCH 07/13] fix: Resolve VL53L0X initialization handshake and add 4 TT motors to diagram --- diagram.json | 131 ++++++++++++++++++++++++++++++++++++++++++++-- dist/vl53l0x.wasm | Bin 140183 -> 140186 bytes vl53l0x.chip.c | 28 ++++++---- 3 files changed, 144 insertions(+), 15 deletions(-) diff --git a/diagram.json b/diagram.json index ecab82a..064ea7b 100644 --- a/diagram.json +++ b/diagram.json @@ -327,17 +327,66 @@ { "type": "chip-tb6612fng", "id": "driver", - "top": 120, - "left": 270, + "top": 180, + "left": 350, "attrs": {} }, { "type": "wokwi-text", "id": "driver_label", - "top": 80, - "left": 189, + "top": 130, + "left": 240, + "attrs": { + "text": "TB6612FNG MOTOR DRIVER - ChA: LEFT (25,16,17) | ChB: RIGHT (14,33,2) | STBY: 5" + } + }, + { + "type": "wokwi-led", + "id": "tt_motor_fl", + "top": 140, + "left": 180, + "attrs": { + "color": "yellow", + "label": "TT MOTOR FL (LEFT)" + } + }, + { + "type": "wokwi-led", + "id": "tt_motor_rl", + "top": 240, + "left": 180, "attrs": { - "text": "TB6612FNG - ChA=LEFT motors (GPIO25,16,17), ChB=RIGHT motors (GPIO14,33,2), STBY=GPIO5" + "color": "yellow", + "label": "TT MOTOR RL (LEFT)" + } + }, + { + "type": "wokwi-led", + "id": "tt_motor_fr", + "top": 140, + "left": 550, + "attrs": { + "color": "cyan", + "label": "TT MOTOR FR (RIGHT)" + } + }, + { + "type": "wokwi-led", + "id": "tt_motor_rr", + "top": 240, + "left": 550, + "attrs": { + "color": "cyan", + "label": "TT MOTOR RR (RIGHT)" + } + }, + { + "type": "wokwi-text", + "id": "tt_motors_label", + "top": 320, + "left": 200, + "attrs": { + "text": "4x TT DC MOTORS - LEFT PAIR WIRED IN PARALLEL TO AO1/AO2 | RIGHT PAIR TO BO1/BO2" } } ], @@ -859,6 +908,78 @@ "v60", "h80" ] + ], + [ + "tt_motor_fl:A", + "driver:AO1", + "yellow", + [ + "h-20", + "v40" + ] + ], + [ + "tt_motor_fl:C", + "driver:AO2", + "black", + [ + "h-30", + "v60" + ] + ], + [ + "tt_motor_rl:A", + "driver:AO1", + "yellow", + [ + "h-20", + "v-40" + ] + ], + [ + "tt_motor_rl:C", + "driver:AO2", + "black", + [ + "h-30", + "v-20" + ] + ], + [ + "tt_motor_fr:A", + "driver:BO1", + "cyan", + [ + "h20", + "v40" + ] + ], + [ + "tt_motor_fr:C", + "driver:BO2", + "black", + [ + "h30", + "v60" + ] + ], + [ + "tt_motor_rr:A", + "driver:BO1", + "cyan", + [ + "h20", + "v-40" + ] + ], + [ + "tt_motor_rr:C", + "driver:BO2", + "black", + [ + "h30", + "v-20" + ] ] ], "dependencies": {} diff --git a/dist/vl53l0x.wasm b/dist/vl53l0x.wasm index fbca06e57d0491aa9fc3a17fe560e40fe96deeb9..fa5a7c46f9d28de711fd283418bb0496f61839b6 100755 GIT binary patch delta 7873 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 delta 7756 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 VH5&6reg_ptr == REG_SYSRANGE_START) { value = 0x00; - } else if (chip->reg_ptr == REG_RESULT_INTERRUPT_STATUS) { - if (chip->registers[0x0B] != 0) { - value = 0x00; - chip->registers[0x0B] = 0x00; - } else { - value = 0x04; - } - } else if (chip->reg_ptr == REG_RESULT_RANGE_STATUS) { + } else if (chip->reg_ptr == 0x83) { value = 0x01; } @@ -79,10 +72,25 @@ static bool on_i2c_write(void *user_data, uint8_t data) { } else { chip->registers[chip->reg_ptr] = data; - if (chip->reg_ptr == 0x0B && data == 0x01) { - chip->registers[0x0B] = 0x01; + if (chip->reg_ptr == 0x0B) { // SYSTEM_INTERRUPT_CLEAR + chip->registers[REG_RESULT_INTERRUPT_STATUS] = 0x00; + chip->registers[REG_RESULT_RANGE_STATUS] = 0x00; } else if (chip->reg_ptr == REG_SYSRANGE_START) { update_measurement_registers(chip); + chip->registers[REG_RESULT_INTERRUPT_STATUS] = 0x04; + chip->registers[REG_RESULT_RANGE_STATUS] = 0x01; + } else if (chip->reg_ptr == 0x94) { // NVM Read selector + if (data == 0x6B) { + chip->registers[0x90] = 0x00; + chip->registers[0x91] = 0x00; + chip->registers[0x92] = 0x84; // 4 aperture SPADs + chip->registers[0x93] = 0x00; + } else { + chip->registers[0x90] = 0xFF; // All good SPADs + chip->registers[0x91] = 0xFF; + chip->registers[0x92] = 0xFF; + chip->registers[0x93] = 0xFF; + } } chip->reg_ptr++; From 681c3cf409730e06171e8762065213773b26520c Mon Sep 17 00:00:00 2001 From: sea-deep Date: Mon, 24 Aug 2026 05:41:21 +0530 Subject: [PATCH 08/13] Restore main dashboard UI and fix Gateway serial JSON extraction --- DASHBOARD/dashboard.html | 438 ++++++++++++------ GATEWAY/src/main.cpp | 68 ++- dist/tb6612fng.json | 5 - dist/tb6612fng.svg | 6 - ...{tb6612fng.chip.json => tb6612fng_v2.json} | 0 dist/{tb6612fng.wasm => tb6612fng_v2.wasm} | Bin dist/vl53l0x.json | 15 - dist/vl53l0x.svg | 7 - dist/{vl53l0x.chip.json => vl53l0x_v2.json} | 0 dist/{vl53l0x.wasm => vl53l0x_v2.wasm} | Bin fix_esp_now.py | 22 + fix_gateway_serial.py | 41 ++ rewrite_clean.py | 102 ++++ rewrite_dashboard.py | 137 ++++++ src/main.cpp | 44 ++ tb6612fng.json | 5 - update_gateway.py | 60 +++ update_rover.py | 53 +++ upgrade_dashboard.py | 147 ++++++ vl53l0x.json | 15 - wokwi.toml | 4 +- 21 files changed, 959 insertions(+), 210 deletions(-) delete mode 100644 dist/tb6612fng.json delete mode 100644 dist/tb6612fng.svg rename dist/{tb6612fng.chip.json => tb6612fng_v2.json} (100%) rename dist/{tb6612fng.wasm => tb6612fng_v2.wasm} (100%) delete mode 100644 dist/vl53l0x.json delete mode 100644 dist/vl53l0x.svg rename dist/{vl53l0x.chip.json => vl53l0x_v2.json} (100%) rename dist/{vl53l0x.wasm => vl53l0x_v2.wasm} (100%) create mode 100644 fix_esp_now.py create mode 100644 fix_gateway_serial.py create mode 100644 rewrite_clean.py create mode 100644 rewrite_dashboard.py delete mode 100644 tb6612fng.json create mode 100644 update_gateway.py create mode 100644 update_rover.py create mode 100644 upgrade_dashboard.py delete mode 100644 vl53l0x.json diff --git a/DASHBOARD/dashboard.html b/DASHBOARD/dashboard.html index 0d4d842..8dfa1b9 100644 --- a/DASHBOARD/dashboard.html +++ b/DASHBOARD/dashboard.html @@ -1,158 +1,298 @@ - - - Rover Dashboard - - + +DEEPTRACK — Mine Rescue Rover Console + + + + + -

Mine Rescue Rover Dashboard

- -
-
-

Telemetry

-
Connecting...
-

Temp: -- °C

-

Humidity: -- %

-

Gas (MQ4): --

-

Water Level: --

-

Pitch/Roll: -- / --

-

Status: SAFE

-
- -
-

Live Local 2D Scan

- - -

Controls

-
-
- - - -
-

Requires Serial connection for controls.

- +
+ +
+
+
+
+

DEEPTRACK

+ Mine Rescue Rover — Live Console +
+
+
+
Offline
+ + + + +
+
+ +
+
+
+

Path Trace

+ dead-reckoned · cm +
+
+ +
+ +
+
+

Status

+
+
+
+
Buzzer silent
+
+ +
+

Environment

+
Temperature— °C
+
+
Humidity— %
+
+
Methane / CH4 (MQ-4)
+
+
Water
+
+
Tilt— °
+
+
+ +
+

Navigation

+
Front distance
+
Position (x, y)
+
Heading
+
Encoders L / R
+
Distance traveled0 cm
+
+ +
+

Event Log

+
+
+
+
+ + +drawMap(); + - + \ No newline at end of file diff --git a/GATEWAY/src/main.cpp b/GATEWAY/src/main.cpp index b33a842..499aaa4 100644 --- a/GATEWAY/src/main.cpp +++ b/GATEWAY/src/main.cpp @@ -1,6 +1,9 @@ #include #include #include + +#define USE_ESP_NOW 0 + #include #include "telemetry_packet.h" @@ -48,6 +51,27 @@ void onDataRecv(const uint8_t *mac, const uint8_t *incomingData, int len) { } } + +void onMqttMessage(char* topic, byte* payload, unsigned int length) { + String msg; + for (unsigned int i = 0; i < length; i++) msg += (char)payload[i]; + if (String(topic) == "rover/control") { + if (msg.startsWith("L:")) { + int spaceIdx = msg.indexOf(' '); + if (spaceIdx > -1) { + ControlPacket pkt; + pkt.type = 0; + pkt.motor_l = msg.substring(2, spaceIdx).toInt(); + pkt.motor_r = msg.substring(msg.indexOf("R:") + 2).toInt(); +#if USE_ESP_NOW + esp_now_send(roverAddress, (uint8_t*)&pkt, sizeof(ControlPacket)); +#endif + Serial.println(msg); // Bridge to Wokwi Simulator + } + } + } +} + void reconnectMqtt() { if (!mqttClient.connected()) { Serial.println("Connecting to MQTT..."); @@ -55,6 +79,7 @@ void reconnectMqtt() { clientId += String(random(0xffff), HEX); if (mqttClient.connect(clientId.c_str())) { Serial.println("MQTT Connected"); + mqttClient.subscribe("rover/control"); } } } @@ -71,6 +96,8 @@ void setup() { } mqttClient.setServer(MQTT_SERVER, MQTT_PORT); + mqttClient.setCallback(onMqttMessage); +#if USE_ESP_NOW if (esp_now_init() != ESP_OK) { Serial.println("ESP-NOW Init Failed"); @@ -83,6 +110,7 @@ void setup() { peerInfo.channel = 0; peerInfo.encrypt = false; esp_now_add_peer(&peerInfo); +#endif } void loop() { @@ -91,18 +119,46 @@ void loop() { mqttClient.loop(); } - // Check serial for commands from the dashboard + + // Check serial for commands (direct typing) or telemetry (from Wokwi Bridge) + if (Serial.available()) { - String cmd = Serial.readStringUntil('\n'); - if (cmd.startsWith("L:")) { - int spaceIdx = cmd.indexOf(' '); + String line = Serial.readStringUntil('\n'); + + // Check for commands (direct typing) + if (line.startsWith("L:")) { + int spaceIdx = line.indexOf(' '); if (spaceIdx > -1) { ControlPacket pkt; pkt.type = 0; - pkt.motor_l = cmd.substring(2, spaceIdx).toInt(); - pkt.motor_r = cmd.substring(cmd.indexOf("R:") + 2).toInt(); + pkt.motor_l = line.substring(2, spaceIdx).toInt(); + pkt.motor_r = line.substring(line.indexOf("R:") + 2).toInt(); +#if USE_ESP_NOW esp_now_send(roverAddress, (uint8_t*)&pkt, sizeof(ControlPacket)); +#endif + } + } else { + // Extract JSON telemetry from Wokwi Serial Bridge + int start = line.indexOf('{'); + int end = line.lastIndexOf('}'); + if (start >= 0 && end > start) { + String jsonSub = line.substring(start, end + 1); + if (jsonSub.indexOf("\"type\":\"scan\"") > 0) { + if (mqttClient.connected()) mqttClient.publish(MQTT_TOPIC_SCAN, jsonSub.c_str()); + } else { + if (mqttClient.connected()) mqttClient.publish(MQTT_TOPIC_TELEMETRY, jsonSub.c_str()); + } } } } + } else if (line.startsWith("{")) { + // It's telemetry from Wokwi Serial Bridge! + if (line.indexOf("\"type\":\"scan\"") > 0) { + if (mqttClient.connected()) mqttClient.publish(MQTT_TOPIC_SCAN, line.c_str()); + } else { + if (mqttClient.connected()) mqttClient.publish(MQTT_TOPIC_TELEMETRY, line.c_str()); + } + } + } + } diff --git a/dist/tb6612fng.json b/dist/tb6612fng.json deleted file mode 100644 index 7024e82..0000000 --- a/dist/tb6612fng.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name": "tb6612fng", - "author": "", - "pins": ["VM","VCC","GND","AO1","AO2","BO2","BO1","GND2","PWMB","BIN2","BIN1","STBY","AIN1","AIN2","PWMA"] -} diff --git a/dist/tb6612fng.svg b/dist/tb6612fng.svg deleted file mode 100644 index 1a98f7b..0000000 --- a/dist/tb6612fng.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - TB6612FNG - TOSHIBA - diff --git a/dist/tb6612fng.chip.json b/dist/tb6612fng_v2.json similarity index 100% rename from dist/tb6612fng.chip.json rename to dist/tb6612fng_v2.json diff --git a/dist/tb6612fng.wasm b/dist/tb6612fng_v2.wasm similarity index 100% rename from dist/tb6612fng.wasm rename to dist/tb6612fng_v2.wasm diff --git a/dist/vl53l0x.json b/dist/vl53l0x.json deleted file mode 100644 index 3e4b889..0000000 --- a/dist/vl53l0x.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "name": "vl53l0x", - "author": "", - "pins": ["VCC", "GND", "SCL", "SDA", "XSHUT", "GPIO1"], - "controls": [ - { - "id": "distance", - "label": "Distance (mm)", - "type": "range", - "min": 30, - "max": 2000, - "step": 1 - } - ] -} diff --git a/dist/vl53l0x.svg b/dist/vl53l0x.svg deleted file mode 100644 index d7cccd9..0000000 --- a/dist/vl53l0x.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - VL53L0X - diff --git a/dist/vl53l0x.chip.json b/dist/vl53l0x_v2.json similarity index 100% rename from dist/vl53l0x.chip.json rename to dist/vl53l0x_v2.json diff --git a/dist/vl53l0x.wasm b/dist/vl53l0x_v2.wasm similarity index 100% rename from dist/vl53l0x.wasm rename to dist/vl53l0x_v2.wasm diff --git a/fix_esp_now.py b/fix_esp_now.py new file mode 100644 index 0000000..42667be --- /dev/null +++ b/fix_esp_now.py @@ -0,0 +1,22 @@ +import re + +def update_file(filename): + with open(filename, "r") as f: + content = f.read() + + # Add the toggle flag at the top + if "#define USE_ESP_NOW" not in content: + content = re.sub(r'(#include \n#include )', r'\1\n\n#define USE_ESP_NOW 0\n', content) + + # Wrap esp_now_init block + content = re.sub(r'(\s+if \(esp_now_init\(\) != ESP_OK\) \{.*?\s+esp_now_add_peer\(&peerInfo\);\n)', + r'\n#if USE_ESP_NOW\1#endif\n', content, flags=re.DOTALL) + + # Wrap esp_now_send calls + content = re.sub(r'(\s+esp_now_send\([^;]+;\n)', r'\n#if USE_ESP_NOW\1#endif\n', content) + + with open(filename, "w") as f: + f.write(content) + +update_file("src/main.cpp") +update_file("GATEWAY/src/main.cpp") diff --git a/fix_gateway_serial.py b/fix_gateway_serial.py new file mode 100644 index 0000000..9cff373 --- /dev/null +++ b/fix_gateway_serial.py @@ -0,0 +1,41 @@ +import re + +with open("GATEWAY/src/main.cpp", "r") as f: + content = f.read() + +replacement = """ + if (Serial.available()) { + String line = Serial.readStringUntil('\\n'); + + // Check for commands (direct typing) + if (line.startsWith("L:")) { + int spaceIdx = line.indexOf(' '); + if (spaceIdx > -1) { + ControlPacket pkt; + pkt.type = 0; + pkt.motor_l = line.substring(2, spaceIdx).toInt(); + pkt.motor_r = line.substring(line.indexOf("R:") + 2).toInt(); +#if USE_ESP_NOW + esp_now_send(roverAddress, (uint8_t*)&pkt, sizeof(ControlPacket)); +#endif + } + } else { + // Extract JSON telemetry from Wokwi Serial Bridge + int start = line.indexOf('{'); + int end = line.lastIndexOf('}'); + if (start >= 0 && end > start) { + String jsonSub = line.substring(start, end + 1); + if (jsonSub.indexOf("\\"type\\":\\"scan\\"") > 0) { + if (mqttClient.connected()) mqttClient.publish(MQTT_TOPIC_SCAN, jsonSub.c_str()); + } else { + if (mqttClient.connected()) mqttClient.publish(MQTT_TOPIC_TELEMETRY, jsonSub.c_str()); + } + } + } + } +""" + +content = re.sub(r' if \(Serial\.available\(\)\) \{.*? \}\n', replacement, content, flags=re.DOTALL) + +with open("GATEWAY/src/main.cpp", "w") as f: + f.write(content) diff --git a/rewrite_clean.py b/rewrite_clean.py new file mode 100644 index 0000000..90912ac --- /dev/null +++ b/rewrite_clean.py @@ -0,0 +1,102 @@ +import re + +with open("DASHBOARD/dashboard.html", "r") as f: + content = f.read() + +# MQTT topic +content = content.replace("const MQTT_TOPIC = 'deeptrack/rover/telemetry';", "const MQTT_TOPIC_TELEMETRY = 'rover/telemetry';\nconst MQTT_TOPIC_SCAN = 'rover/scan';") +content = content.replace("mqttConn.subscribe(MQTT_TOPIC);", "mqttConn.subscribe(MQTT_TOPIC_TELEMETRY);\nmqttConn.subscribe(MQTT_TOPIC_SCAN);") + +# MQTT handler +msg_h = """ mqttConn.on('message', (topic, payload) => { + try { + const d = JSON.parse(payload.toString()); + if (topic === MQTT_TOPIC_TELEMETRY) { handleTelemetry(d); } + else if (topic === MQTT_TOPIC_SCAN) { handleScan(d); } + } catch(e) {} + });""" +content = re.sub(r' mqttConn\.on\(\'message\', \(topic, payload\) => \{.*? \}\);', msg_h, content, flags=re.DOTALL) + +# Serial handler +ser_h = """ try{ + const d = JSON.parse(line); + if (d.type === 'scan') handleScan(d); + else handleTelemetry(d); + }catch(e){ }""" +content = re.sub(r' try\{\s*const d = JSON\.parse\(line\);\s*handleTelemetry\(d\);\s*\}catch\(e\)\{.*?\}', ser_h, content, flags=re.DOTALL) + +# UI bindings +ui = """ document.getElementById('s-pos').textContent = d.pitch !== undefined ? `P:${d.pitch.toFixed(1)}° R:${d.roll.toFixed(1)}°` : `X:${d.x.toFixed(1)} Y:${d.y.toFixed(1)}`;""" +content = re.sub(r' document\.getElementById\(\'s-pos\'\)\.textContent = `X:\$\{d\.x\.toFixed\(1\)\} Y:\$\{d\.y\.toFixed\(1\)\}`;', ui, content) + +# handleTelemetry start +tel_start = """function handleTelemetry(raw){ + const d = { + tempC: raw.t ?? raw.tempC, hum: raw.h ?? raw.hum, gas: raw.gas, frontCm: raw.dist ?? raw.frontCm ?? 'N/A', water: raw.water, + x: raw.x ?? 0, y: raw.y ?? 0, heading: raw.hd ?? raw.heading ?? 0, + state: (raw.danger === 1) ? 'DANGER' : 'NORMAL', + dangerCause: (raw.danger === 1) ? 'EMERGENCY' : 'NONE', + encL: raw.encL ?? 0, encR: raw.encR ?? 0 + }; + if (raw.ax !== undefined) { + d.pitch = Math.atan2(-raw.ax, Math.sqrt(raw.ay * raw.ay + raw.az * raw.az)) * 180 / Math.PI; + d.roll = Math.atan2(raw.ay, raw.az) * 180 / Math.PI; + } + latest = d; + + const danger = d.state === 'DANGER';""" +content = re.sub(r'function handleTelemetry\(raw\)\{.*?const danger = d\.state === \'DANGER\';', tel_start, content, flags=re.DOTALL) + +# Remove trail logic from handleTelemetry +content = re.sub(r' // Push trail.*?requestAnimationFrame\(drawMap\);', '', content, flags=re.DOTALL) + +# Replace drawMap completely +draw_map_logic = """ +const scanPoints = []; +const MAX_POINTS = 200; +const SCALE = 0.25; + +function handleScan(data) { + if (data.valid) { + const theta = ((data.angle_deg - 90) * Math.PI) / 180; + const lateral = data.distance_mm * Math.cos(theta); + const forward = data.distance_mm * Math.sin(theta); + const w = canvas.width; + const h = canvas.height; + const canvasX = (w / 2) - (lateral * SCALE); + const canvasY = (h - 20) - (forward * SCALE); + scanPoints.push({ x: canvasX, y: canvasY, ts: Date.now() }); + if (scanPoints.length > MAX_POINTS) scanPoints.shift(); + } +} + +function drawMap() { + const w = canvas.width, h = canvas.height; + ctx.clearRect(0,0,w,h); + ctx.fillStyle = '#0f1512'; + ctx.fillRect(0,0,w,h); + ctx.strokeStyle = '#25332c'; + ctx.beginPath(); + ctx.arc(w/2, h-20, 100, 0, Math.PI, true); + ctx.arc(w/2, h-20, 200, 0, Math.PI, true); + ctx.stroke(); + ctx.fillStyle = '#4cc27a'; + ctx.fillRect(w/2 - 5, h-25, 10, 10); + const now = Date.now(); + for (let i = scanPoints.length - 1; i >= 0; i--) { + const pt = scanPoints[i]; + const age = now - pt.ts; + if (age > 5000) { scanPoints.splice(i, 1); continue; } + const alpha = 1.0 - (age / 5000); + ctx.fillStyle = `rgba(0, 255, 255, ${alpha})`; + ctx.beginPath(); + ctx.arc(pt.x, pt.y, 3, 0, Math.PI * 2); + ctx.fill(); + } + requestAnimationFrame(drawMap); +} +""" +content = re.sub(r'function drawMap\(\).*', draw_map_logic + '\ndrawMap();\n\n\n', content, flags=re.DOTALL) + +with open("DASHBOARD/dashboard.html", "w") as f: + f.write(content) diff --git a/rewrite_dashboard.py b/rewrite_dashboard.py new file mode 100644 index 0000000..d9032a5 --- /dev/null +++ b/rewrite_dashboard.py @@ -0,0 +1,137 @@ +import re + +with open("DASHBOARD/dashboard.html", "r") as f: + content = f.read() + +# 1. Update MQTT topic to telemetry and add scan topic +content = content.replace("const MQTT_TOPIC = 'deeptrack/rover/telemetry';", """const MQTT_TOPIC_TELEMETRY = 'rover/telemetry'; +const MQTT_TOPIC_SCAN = 'rover/scan';""") +content = content.replace("mqttConn.subscribe(MQTT_TOPIC);", """mqttConn.subscribe(MQTT_TOPIC_TELEMETRY); + mqttConn.subscribe(MQTT_TOPIC_SCAN);""") + +# 2. Modify MQTT message handler +mqtt_msg_handler = """ mqttConn.on('message', (topic, payload) => { + try { + const d = JSON.parse(payload.toString()); + if (topic === MQTT_TOPIC_TELEMETRY) { + handleTelemetry(d); + } else if (topic === MQTT_TOPIC_SCAN) { + handleScan(d); + } + } catch(e) {} + });""" +content = re.sub(r' mqttConn\.on\(\'message\', \(topic, payload\) => \{.*? \}\);', mqtt_msg_handler, content, flags=re.DOTALL) + +# 3. Modify Web Serial message handler +serial_msg_handler = """ try{ + const d = JSON.parse(line); + if (d.type === 'scan') { + handleScan(d); + } else { + handleTelemetry(d); + } + }catch(e){ /* partial/garbled line, skip */ }""" +content = re.sub(r' try\{\s*const d = JSON\.parse\(line\);\s*handleTelemetry\(d\);\s*\}catch\(e\)\{.*?\}', serial_msg_handler, content, flags=re.DOTALL) + + +# 4. Replace `handleTelemetry` to support Pitch/Roll instead of x/y +telemetry_fix = """function handleTelemetry(raw){ + const d = { + tempC: raw.t ?? raw.tempC, + hum: raw.h ?? raw.hum, + gas: raw.gas, + frontCm: raw.dist ?? raw.frontCm ?? 'N/A', + water: raw.water, + x: raw.x ?? 0, + y: raw.y ?? 0, + heading: raw.hd ?? raw.heading ?? 0, + state: (raw.danger === 1) ? 'DANGER' : 'NORMAL', + dangerCause: (raw.danger === 1) ? 'EMERGENCY' : 'NONE', + encL: raw.encL ?? 0, + encR: raw.encR ?? 0 + }; + if (raw.ax !== undefined) { + d.pitch = Math.atan2(-raw.ax, Math.sqrt(raw.ay * raw.ay + raw.az * raw.az)) * 180 / Math.PI; + d.roll = Math.atan2(raw.ay, raw.az) * 180 / Math.PI; + } + latest = d; + + const danger = d.state === 'DANGER'; +""" +content = re.sub(r'function handleTelemetry\(raw\)\{.*?const danger = d\.state === \'DANGER\';', telemetry_fix, content, flags=re.DOTALL) + +# 5. Fix UI bindings for positions (X/Y -> Pitch/Roll) +ui_update = """ document.getElementById('s-pos').textContent = d.pitch !== undefined ? `P:${d.pitch.toFixed(1)}° R:${d.roll.toFixed(1)}°` : `X:${d.x.toFixed(1)} Y:${d.y.toFixed(1)}`;""" +content = re.sub(r' document\.getElementById\(\'s-pos\'\)\.textContent = `X:\$\{d\.x\.toFixed\(1\)\} Y:\$\{d\.y\.toFixed\(1\)\}`;', ui_update, content) + + +# 6. Replace Odometry Map logic with Radar Canvas logic +radar_logic = """ +// ---------- Radar Scan Map ---------- +const scanPoints = []; +const MAX_POINTS = 200; +const SCALE = 0.25; + +function handleScan(data) { + if (data.valid) { + const theta = ((data.angle_deg - 90) * Math.PI) / 180; + const lateral = data.distance_mm * Math.cos(theta); + const forward = data.distance_mm * Math.sin(theta); + + // Map to canvas (origin at center bottom) + const w = canvas.width; + const h = canvas.height; + const canvasX = (w / 2) - (lateral * SCALE); + const canvasY = (h - 20) - (forward * SCALE); + + scanPoints.push({ x: canvasX, y: canvasY, ts: Date.now() }); + if (scanPoints.length > MAX_POINTS) scanPoints.shift(); + } +} + +function drawMap() { + const w = canvas.width, h = canvas.height; + ctx.clearRect(0,0,w,h); + ctx.fillStyle = '#0f1512'; + ctx.fillRect(0,0,w,h); + + // Draw grid + ctx.strokeStyle = '#25332c'; + ctx.beginPath(); + ctx.arc(w/2, h-20, 100, 0, Math.PI, true); + ctx.arc(w/2, h-20, 200, 0, Math.PI, true); + ctx.stroke(); + + // Draw rover + ctx.fillStyle = '#4cc27a'; + ctx.fillRect(w/2 - 5, h-25, 10, 10); + + // Draw points + const now = Date.now(); + for (let i = scanPoints.length - 1; i >= 0; i--) { + const pt = scanPoints[i]; + const age = now - pt.ts; + if (age > 5000) { + scanPoints.splice(i, 1); + continue; + } + + const alpha = 1.0 - (age / 5000); + ctx.fillStyle = `rgba(0, 255, 255, ${alpha})`; + ctx.beginPath(); + ctx.arc(pt.x, pt.y, 3, 0, Math.PI * 2); + ctx.fill(); + } + requestAnimationFrame(drawMap); +} +drawMap(); +""" + +# Completely replace the old drawMap +content = re.sub(r'function drawMap\(\)\{.*?requestAnimationFrame\(drawMap\);\s*\}\s*drawMap\(\);', radar_logic, content, flags=re.DOTALL) +# Strip out the trail pushing logic in handleTelemetry +content = re.sub(r' // Push trail.*? requestAnimationFrame\(drawMap\);', '', content, flags=re.DOTALL) + + +with open("DASHBOARD/dashboard.html", "w") as f: + f.write(content) diff --git a/src/main.cpp b/src/main.cpp index 84be664..bc75ade 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2,6 +2,9 @@ #include #include #include + +#define USE_ESP_NOW 0 + #include #include #include @@ -133,6 +136,7 @@ void setup() { } WiFi.mode(WIFI_STA); +#if USE_ESP_NOW if (esp_now_init() != ESP_OK) { Serial.println("Error initializing ESP-NOW"); return; @@ -144,6 +148,7 @@ void setup() { peerInfo.channel = 0; peerInfo.encrypt = false; esp_now_add_peer(&peerInfo); +#endif } void shortBrake() { @@ -200,6 +205,19 @@ float readUltrasonic() { void loop() { uint32_t now = millis(); + if (Serial.available()) { + String cmd = Serial.readStringUntil('\n'); + if (cmd.startsWith("L:")) { + int spaceIdx = cmd.indexOf(' '); + if (spaceIdx > -1) { + target_l = cmd.substring(2, spaceIdx).toInt(); + target_r = cmd.substring(cmd.indexOf("R:") + 2).toInt(); + emergencyStopped = false; + } + } + } + + // 1. Ultrasonic Obstacle Check (Fixed Forward) float frontDist = readUltrasonic(); if (frontDist < OBSTACLE_STOP_CM && frontDist > 0) { @@ -236,8 +254,19 @@ void loop() { spkt.distance_mm = 800; spkt.valid = 0; } +#if USE_ESP_NOW + esp_now_send(gatewayAddress, (uint8_t *) &spkt, sizeof(ScanPacket)); +#endif + + char json[128]; + snprintf(json, sizeof(json), + "{\"type\":\"scan\",\"seq\":%d,\"angle_deg\":%d,\"distance_mm\":%d,\"valid\":%s,\"timestamp_ms\":%u}", + spkt.seq, spkt.angle_deg, spkt.distance_mm, spkt.valid ? "true" : "false", spkt.timestamp_ms + ); + Serial.println(json); + currentAngle += scanDirection; if (currentAngle >= 150 || currentAngle <= 30) { @@ -272,7 +301,22 @@ void loop() { } else { noTone(BUZZER_PIN); } +#if USE_ESP_NOW + esp_now_send(gatewayAddress, (uint8_t *) ¤tTelemetry, sizeof(TelemetryPacket)); +#endif + + char json[256]; + snprintf(json, sizeof(json), + "{\"t\":%.1f,\"h\":%.1f,\"ax\":%.2f,\"ay\":%.2f,\"az\":%.2f,\"gx\":%.2f,\"gy\":%.2f,\"gz\":%.2f,\"gas\":%d,\"water\":%d,\"danger\":%d}", + currentTelemetry.temperature, currentTelemetry.humidity, + currentTelemetry.ax, currentTelemetry.ay, currentTelemetry.az, + currentTelemetry.gx, currentTelemetry.gy, currentTelemetry.gz, + currentTelemetry.gasRaw, currentTelemetry.waterRaw, + currentTelemetry.dangerState + ); + Serial.println(json); + } } diff --git a/tb6612fng.json b/tb6612fng.json deleted file mode 100644 index 7024e82..0000000 --- a/tb6612fng.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name": "tb6612fng", - "author": "", - "pins": ["VM","VCC","GND","AO1","AO2","BO2","BO1","GND2","PWMB","BIN2","BIN1","STBY","AIN1","AIN2","PWMA"] -} diff --git a/update_gateway.py b/update_gateway.py new file mode 100644 index 0000000..807ee97 --- /dev/null +++ b/update_gateway.py @@ -0,0 +1,60 @@ +import re + +with open("GATEWAY/src/main.cpp", "r") as f: + content = f.read() + +# Add MQTT callback and subscription +mqtt_additions = """ +void onMqttMessage(char* topic, byte* payload, unsigned int length) { + String msg; + for (unsigned int i = 0; i < length; i++) msg += (char)payload[i]; + if (String(topic) == "rover/control") { + if (msg.startsWith("L:")) { + int spaceIdx = msg.indexOf(' '); + if (spaceIdx > -1) { + ControlPacket pkt; + pkt.type = 0; + pkt.motor_l = msg.substring(2, spaceIdx).toInt(); + pkt.motor_r = msg.substring(msg.indexOf("R:") + 2).toInt(); + esp_now_send(roverAddress, (uint8_t*)&pkt, sizeof(ControlPacket)); + Serial.println(msg); // Bridge to Wokwi Simulator + } + } + } +} +""" + +content = re.sub(r'void reconnectMqtt\(\) \{', mqtt_additions + r'\nvoid reconnectMqtt() {', content) +content = re.sub(r'mqttClient\.setServer\(MQTT_SERVER, MQTT_PORT\);', r'mqttClient.setServer(MQTT_SERVER, MQTT_PORT);\n mqttClient.setCallback(onMqttMessage);', content) +content = re.sub(r'Serial\.println\("MQTT Connected"\);', r'Serial.println("MQTT Connected");\n mqttClient.subscribe("rover/control");', content) + +# Modify Serial reading to handle incoming JSON from Rover +serial_read_replacement = """ + // Check serial for commands (direct typing) or telemetry (from Wokwi Bridge) + if (Serial.available()) { + String line = Serial.readStringUntil('\\n'); + if (line.startsWith("L:")) { + int spaceIdx = line.indexOf(' '); + if (spaceIdx > -1) { + ControlPacket pkt; + pkt.type = 0; + pkt.motor_l = line.substring(2, spaceIdx).toInt(); + pkt.motor_r = line.substring(line.indexOf("R:") + 2).toInt(); + esp_now_send(roverAddress, (uint8_t*)&pkt, sizeof(ControlPacket)); + } + } else if (line.startsWith("{")) { + // It's telemetry from Wokwi Serial Bridge! + if (line.indexOf("\\"type\\":\\"scan\\"") > 0) { + if (mqttClient.connected()) mqttClient.publish(MQTT_TOPIC_SCAN, line.c_str()); + } else { + if (mqttClient.connected()) mqttClient.publish(MQTT_TOPIC_TELEMETRY, line.c_str()); + } + } + } +""" + +# Replace the existing serial reading block in loop() +content = re.sub(r' // Check serial for commands from the dashboard.*?\}\n \}', serial_read_replacement, content, flags=re.DOTALL) + +with open("GATEWAY/src/main.cpp", "w") as f: + f.write(content) diff --git a/update_rover.py b/update_rover.py new file mode 100644 index 0000000..1da33cd --- /dev/null +++ b/update_rover.py @@ -0,0 +1,53 @@ +import re + +with open("src/main.cpp", "r") as f: + content = f.read() + +# Add Serial reading to loop() +loop_addition = """ + if (Serial.available()) { + String cmd = Serial.readStringUntil('\n'); + if (cmd.startsWith("L:")) { + int spaceIdx = cmd.indexOf(' '); + if (spaceIdx > -1) { + target_l = cmd.substring(2, spaceIdx).toInt(); + target_r = cmd.substring(cmd.indexOf("R:") + 2).toInt(); + emergencyStopped = false; + } + } + } +""" +content = re.sub(r'void loop\(\) \{(\s+)uint32_t now = millis\(\);', r'void loop() {\1uint32_t now = millis();\n' + loop_addition, content) + +# Add JSON print to scan +scan_json = """ + esp_now_send(gatewayAddress, (uint8_t *) &spkt, sizeof(ScanPacket)); + + char json[128]; + snprintf(json, sizeof(json), + "{\\"type\\":\\"scan\\",\\"seq\\":%d,\\"angle_deg\\":%d,\\"distance_mm\\":%d,\\"valid\\":%s,\\"timestamp_ms\\":%u}", + spkt.seq, spkt.angle_deg, spkt.distance_mm, spkt.valid ? "true" : "false", spkt.timestamp_ms + ); + Serial.println(json); +""" +content = re.sub(r'esp_now_send\(gatewayAddress, \(uint8_t \*\) &spkt, sizeof\(ScanPacket\)\);', scan_json, content) + +# Add JSON print to telemetry +telemetry_json = """ + esp_now_send(gatewayAddress, (uint8_t *) ¤tTelemetry, sizeof(TelemetryPacket)); + + char json[256]; + snprintf(json, sizeof(json), + "{\\"t\\":%.1f,\\"h\\":%.1f,\\"ax\\":%.2f,\\"ay\\":%.2f,\\"az\\":%.2f,\\"gx\\":%.2f,\\"gy\\":%.2f,\\"gz\\":%.2f,\\"gas\\":%d,\\"water\\":%d,\\"danger\\":%d}", + currentTelemetry.temperature, currentTelemetry.humidity, + currentTelemetry.ax, currentTelemetry.ay, currentTelemetry.az, + currentTelemetry.gx, currentTelemetry.gy, currentTelemetry.gz, + currentTelemetry.gasRaw, currentTelemetry.waterRaw, + currentTelemetry.dangerState + ); + Serial.println(json); +""" +content = re.sub(r'esp_now_send\(gatewayAddress, \(uint8_t \*\) ¤tTelemetry, sizeof\(TelemetryPacket\)\);', telemetry_json, content) + +with open("src/main.cpp", "w") as f: + f.write(content) diff --git a/upgrade_dashboard.py b/upgrade_dashboard.py new file mode 100644 index 0000000..77ae4e7 --- /dev/null +++ b/upgrade_dashboard.py @@ -0,0 +1,147 @@ +import re + +with open("DASHBOARD/dashboard.html", "r") as f: + content = f.read() + +# 1. Update MQTT topic and add scan topic +content = content.replace("const MQTT_TOPIC = 'deeptrack/rover/telemetry';", """const MQTT_TOPIC_TELEMETRY = 'rover/telemetry'; +const MQTT_TOPIC_SCAN = 'rover/scan';""") +content = content.replace("mqttConn.subscribe(MQTT_TOPIC);", """mqttConn.subscribe(MQTT_TOPIC_TELEMETRY); + mqttConn.subscribe(MQTT_TOPIC_SCAN);""") + +# 2. Modify MQTT message handler to handle both telemetry and scan +mqtt_msg_handler = """ mqttConn.on('message', (topic, payload) => { + try { + const d = JSON.parse(payload.toString()); + if (topic === MQTT_TOPIC_TELEMETRY) { + handleTelemetry(d); + } else if (topic === MQTT_TOPIC_SCAN) { + handleScan(d); + } + } catch(e) {} + });""" +content = re.sub(r' mqttConn\.on\(\'message\', \(topic, payload\) => \{.*? \}\);', mqtt_msg_handler, content, flags=re.DOTALL) + + +# 3. Modify Web Serial message handler to handle both +serial_msg_handler = """ try{ + const d = JSON.parse(line); + if (d.type === 'scan') { + handleScan(d); + } else { + handleTelemetry(d); + } + }catch(e){ /* partial/garbled line, skip */ }""" +content = re.sub(r' try\{\s*const d = JSON\.parse\(line\);\s*handleTelemetry\(d\);\s*\}catch\(e\)\{.*?\}', serial_msg_handler, content, flags=re.DOTALL) + + +# 4. Add the Radar UI element to the DOM +radar_html = """ +
+

2D Environment Scan

+
+ +
+
+""" +# Find a place to insert the radar (e.g. before the logs panel) +content = content.replace('
', radar_html + '\n
') + + +# 5. Add Radar Drawing and `handleScan` function to JavaScript +radar_js = """ +// ---------- Radar Canvas (VL53L0X) ---------- +const rCanvas = document.getElementById('radarCanvas'); +const rCtx = rCanvas.getContext('2d'); +const scanPoints = []; +const MAX_POINTS = 200; +const SCALE = 0.25; + +function drawRadar() { + rCtx.clearRect(0, 0, rCanvas.width, rCanvas.height); + + // Draw grid + rCtx.strokeStyle = '#333'; + rCtx.beginPath(); + rCtx.arc(200, 380, 100, 0, Math.PI, true); + rCtx.arc(200, 380, 200, 0, Math.PI, true); + rCtx.stroke(); + + // Draw rover + rCtx.fillStyle = '#00ff00'; + rCtx.fillRect(195, 375, 10, 10); + + // Draw points + const now = Date.now(); + for (let i = scanPoints.length - 1; i >= 0; i--) { + const pt = scanPoints[i]; + const age = now - pt.ts; + if (age > 5000) { + scanPoints.splice(i, 1); + continue; + } + + const alpha = 1.0 - (age / 5000); + rCtx.fillStyle = `rgba(0, 255, 255, ${alpha})`; + rCtx.beginPath(); + rCtx.arc(pt.x, pt.y, 3, 0, Math.PI * 2); + rCtx.fill(); + } + requestAnimationFrame(drawRadar); +} +drawRadar(); + +function handleScan(data) { + if (data.valid) { + const theta = ((data.angle_deg - 90) * Math.PI) / 180; + const lateral = data.distance_mm * Math.cos(theta); + const forward = data.distance_mm * Math.sin(theta); + + const canvasX = 200 - (lateral * SCALE); + const canvasY = 380 - (forward * SCALE); + + scanPoints.push({ x: canvasX, y: canvasY, ts: Date.now() }); + if (scanPoints.length > MAX_POINTS) scanPoints.shift(); + } +} +""" +content = content.replace('// ---------- Odometry map ----------', radar_js + '\n// ---------- Odometry map ----------') + +# 6. Update `handleTelemetry` to support new fields properly +# Specifically: danger logic (1 vs 0), and Pitch/Roll from ax, ay, az +telemetry_fix = """function handleTelemetry(raw){ + const d = { + tempC: raw.t ?? raw.tempC, + hum: raw.h ?? raw.hum, + gas: raw.gas, + frontCm: raw.dist ?? raw.frontCm ?? 'N/A', + water: raw.water, + x: raw.x ?? 0, + y: raw.y ?? 0, + heading: raw.hd ?? raw.heading ?? 0, + state: (raw.danger === 1) ? 'DANGER' : 'NORMAL', + dangerCause: (raw.danger === 1) ? 'EMERGENCY' : 'NONE', + encL: raw.encL ?? 0, + encR: raw.encR ?? 0 + }; + if (raw.ax !== undefined) { + d.pitch = Math.atan2(-raw.ax, Math.sqrt(raw.ay * raw.ay + raw.az * raw.az)) * 180 / Math.PI; + d.roll = Math.atan2(raw.ay, raw.az) * 180 / Math.PI; + } + latest = d; + + const danger = d.state === 'DANGER'; +""" +content = re.sub(r'function handleTelemetry\(raw\)\{.*?const danger = d\.state === \'DANGER\';', telemetry_fix, content, flags=re.DOTALL) + + +# 7. Update the UI bindings in `handleTelemetry` to show Pitch/Roll instead of Odometry coords if missing +ui_update = """ + document.getElementById('s-pos').textContent = d.pitch !== undefined ? `P:${d.pitch.toFixed(1)}° R:${d.roll.toFixed(1)}°` : `X:${d.x.toFixed(1)} Y:${d.y.toFixed(1)}`; +""" +content = re.sub(r' document\.getElementById\(\'s-pos\'\)\.textContent = `X:\$\{d\.x\.toFixed\(1\)\} Y:\$\{d\.y\.toFixed\(1\)\}`;', ui_update, content) + + +with open("DASHBOARD/dashboard.html", "w") as f: + f.write(content) + diff --git a/vl53l0x.json b/vl53l0x.json deleted file mode 100644 index 3e4b889..0000000 --- a/vl53l0x.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "name": "vl53l0x", - "author": "", - "pins": ["VCC", "GND", "SCL", "SDA", "XSHUT", "GPIO1"], - "controls": [ - { - "id": "distance", - "label": "Distance (mm)", - "type": "range", - "min": 30, - "max": 2000, - "step": 1 - } - ] -} diff --git a/wokwi.toml b/wokwi.toml index 102d3b9..10bb368 100644 --- a/wokwi.toml +++ b/wokwi.toml @@ -6,8 +6,8 @@ rfc2217ServerPort = 4000 [[chip]] name = "vl53l0x" -binary = "dist/vl53l0x.wasm" +binary = "dist/vl53l0x_v2.wasm" [[chip]] name = "tb6612fng" -binary = "dist/tb6612fng.wasm" \ No newline at end of file +binary = "dist/tb6612fng_v2.wasm" \ No newline at end of file From ce82bc5eb8671237e3dcb16ad6ed038f5fc216ef Mon Sep 17 00:00:00 2001 From: sea-deep Date: Mon, 24 Aug 2026 05:46:04 +0530 Subject: [PATCH 09/13] Restore Gateway LCD logic --- GATEWAY/src/main.cpp | 103 +++++++++++++++++++++++++++++++---- fix_c.py | 21 ++++++++ fix_math.py | 7 +++ restore_lcd.py | 125 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 247 insertions(+), 9 deletions(-) create mode 100644 fix_c.py create mode 100644 fix_math.py create mode 100644 restore_lcd.py diff --git a/GATEWAY/src/main.cpp b/GATEWAY/src/main.cpp index 499aaa4..54c2813 100644 --- a/GATEWAY/src/main.cpp +++ b/GATEWAY/src/main.cpp @@ -1,12 +1,28 @@ #include #include #include +#include #define USE_ESP_NOW 0 #include #include "telemetry_packet.h" +// ---------- LCD ---------- +LiquidCrystal_I2C lcd(0x27, 16, 2); +uint32_t lastDataTime = 0; +int lcdPage = 0; +#define NUM_PAGES 3 +uint32_t lastPageChange = 0; + +// Custom Icons +byte thermIcon[8] = { B00100, B01010, B01010, B01110, B01110, B11111, B11111, B01110 }; +byte signalIcon[8] = { B00000, B10000, B10100, B10100, B10101, B10101, B10101, B10101 }; +byte warnIcon[8] = { B00000, B00100, B01010, B11011, B11011, B11011, B11111, B00000 }; + +TelemetryPacket latestTel = {0}; + + #define WIFI_SSID "Wokwi-GUEST" #define WIFI_PASS "" #define MQTT_SERVER "broker.hivemq.com" @@ -19,6 +35,62 @@ PubSubClient mqttClient(espClient); uint8_t roverAddress[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + +void updateLCD() { + lcd.clear(); + + if (millis() - lastDataTime > 3000) { + lcd.setCursor(0, 0); + lcd.write(2); // warning + lcd.print(" LINK LOST! "); + lcd.write(2); + lcd.setCursor(0, 1); + lcd.print("No data >3s"); + return; + } + + if (latestTel.dangerState == 1) { + lcd.setCursor(0, 0); + lcd.write(2); // warning icon + lcd.print(" DANGER!! "); + lcd.write(2); + lcd.setCursor(0, 1); + lcd.print("EMERGENCY STOP"); + return; + } + + switch (lcdPage) { + case 0: { // ENV + char e1[17], e2[17]; + snprintf(e1, sizeof(e1), " T:%.1fC H:%.1f%%", latestTel.temperature, latestTel.humidity); + snprintf(e2, sizeof(e2), " Gas:%d W:%d", latestTel.gasRaw, latestTel.waterRaw); + lcd.setCursor(0, 0); lcd.write(0); lcd.print(e1); + lcd.setCursor(0, 1); lcd.print(e2); + break; + } + + case 1: { // NAV + char n1[17], n2[17]; + snprintf(n1, sizeof(n1), " P:%.1f R:%.1f", + atan2(-latestTel.ax, sqrt(latestTel.ay * latestTel.ay + latestTel.az * latestTel.az)) * 180.0 / 3.14159265, + atan2(latestTel.ay, latestTel.az) * 180.0 / 3.14159265); + snprintf(n2, sizeof(n2), " GYR:%d", (int)latestTel.gz); + lcd.setCursor(0, 0); lcd.print(n1); + lcd.setCursor(0, 1); lcd.print(n2); + break; + } + + case 2: { // SYS + char s1[17], s2[17]; + snprintf(s1, sizeof(s1), " MQTT:%s", mqttClient.connected() ? "ON" : "OFF"); + snprintf(s2, sizeof(s2), " WiFi:%s", WiFi.status() == WL_CONNECTED ? "ON" : "OFF"); + lcd.setCursor(0, 0); lcd.print(s1); + lcd.setCursor(0, 1); lcd.write(1); lcd.print(s2); + break; + } + } +} + void onDataRecv(const uint8_t *mac, const uint8_t *incomingData, int len) { if (len == sizeof(TelemetryPacket)) { TelemetryPacket *pkt = (TelemetryPacket*)incomingData; @@ -87,6 +159,18 @@ void reconnectMqtt() { void setup() { Serial.begin(115200); + // LCD init + lcd.init(); + lcd.backlight(); + lcd.createChar(0, thermIcon); + lcd.createChar(1, signalIcon); + lcd.createChar(2, warnIcon); + lcd.clear(); + lcd.setCursor(0, 0); + lcd.print("DEEPTRACK GTWRY"); + lcd.setCursor(0, 1); + lcd.print("Waiting link..."); + WiFi.mode(WIFI_STA); WiFi.begin(WIFI_SSID, WIFI_PASS); @@ -120,6 +204,16 @@ void loop() { } + + // --- Auto-cycle LCD pages every 3s (unless DANGER) --- + if (millis() - lastPageChange >= 3000) { + lastPageChange = millis(); + if (latestTel.dangerState != 1) { + lcdPage = (lcdPage + 1) % NUM_PAGES; + updateLCD(); + } + } + // Check serial for commands (direct typing) or telemetry (from Wokwi Bridge) if (Serial.available()) { @@ -151,14 +245,5 @@ void loop() { } } } - } else if (line.startsWith("{")) { - // It's telemetry from Wokwi Serial Bridge! - if (line.indexOf("\"type\":\"scan\"") > 0) { - if (mqttClient.connected()) mqttClient.publish(MQTT_TOPIC_SCAN, line.c_str()); - } else { - if (mqttClient.connected()) mqttClient.publish(MQTT_TOPIC_TELEMETRY, line.c_str()); - } - } - } } diff --git a/fix_c.py b/fix_c.py new file mode 100644 index 0000000..82b55bd --- /dev/null +++ b/fix_c.py @@ -0,0 +1,21 @@ +import re +with open("GATEWAY/src/main.cpp", "r") as f: + c = f.read() + +# Replace the math block entirely because ESP32 Arduino framework uses standard C functions +replacement = """ + case 1: { // NAV + char n1[17], n2[17]; + snprintf(n1, sizeof(n1), " P:%.1f R:%.1f", + atan2(-latestTel.ax, sqrt(latestTel.ay * latestTel.ay + latestTel.az * latestTel.az)) * 180.0 / 3.14159265, + atan2(latestTel.ay, latestTel.az) * 180.0 / 3.14159265); + snprintf(n2, sizeof(n2), " GYR:%d", (int)latestTel.gz); + lcd.setCursor(0, 0); lcd.print(n1); + lcd.setCursor(0, 1); lcd.print(n2); + break; + } +""" + +c = re.sub(r' case 1: \{.*? \}', replacement, c, flags=re.DOTALL) +with open("GATEWAY/src/main.cpp", "w") as f: + f.write(c) diff --git a/fix_math.py b/fix_math.py new file mode 100644 index 0000000..e95a971 --- /dev/null +++ b/fix_math.py @@ -0,0 +1,7 @@ +with open("GATEWAY/src/main.cpp", "r") as f: + c = f.read() +c = c.replace("Math.atan2", "atan2") +c = c.replace("Math.sqrt", "sqrt") +c = c.replace("Math.PI", "PI") +with open("GATEWAY/src/main.cpp", "w") as f: + f.write(c) diff --git a/restore_lcd.py b/restore_lcd.py new file mode 100644 index 0000000..c0a7227 --- /dev/null +++ b/restore_lcd.py @@ -0,0 +1,125 @@ +import re + +with open("GATEWAY/src/main.cpp", "r") as f: + content = f.read() + +# Add the LCD logic to the top of GATEWAY/src/main.cpp +lcd_headers = """#include +#include +#include +#include + +#define USE_ESP_NOW 0 + +#include +#include "telemetry_packet.h" + +// ---------- LCD ---------- +LiquidCrystal_I2C lcd(0x27, 16, 2); +uint32_t lastDataTime = 0; +int lcdPage = 0; +#define NUM_PAGES 3 +uint32_t lastPageChange = 0; + +// Custom Icons +byte thermIcon[8] = { B00100, B01010, B01010, B01110, B01110, B11111, B11111, B01110 }; +byte signalIcon[8] = { B00000, B10000, B10100, B10100, B10101, B10101, B10101, B10101 }; +byte warnIcon[8] = { B00000, B00100, B01010, B11011, B11011, B11011, B11111, B00000 }; + +TelemetryPacket latestTel = {0}; +""" + +content = re.sub(r'#include \n#include \n#include \n\n#define USE_ESP_NOW 0\n\n#include \n#include "telemetry_packet\.h"', lcd_headers, content) + +# LCD Update function +lcd_func = """ +void updateLCD() { + lcd.clear(); + + if (millis() - lastDataTime > 3000) { + lcd.setCursor(0, 0); + lcd.write(2); // warning + lcd.print(" LINK LOST! "); + lcd.write(2); + lcd.setCursor(0, 1); + lcd.print("No data >3s"); + return; + } + + if (latestTel.dangerState == 1) { + lcd.setCursor(0, 0); + lcd.write(2); // warning icon + lcd.print(" DANGER!! "); + lcd.write(2); + lcd.setCursor(0, 1); + lcd.print("EMERGENCY STOP"); + return; + } + + switch (lcdPage) { + case 0: { // ENV + char e1[17], e2[17]; + snprintf(e1, sizeof(e1), " T:%.1fC H:%.1f%%", latestTel.temperature, latestTel.humidity); + snprintf(e2, sizeof(e2), " Gas:%d W:%d", latestTel.gasRaw, latestTel.waterRaw); + lcd.setCursor(0, 0); lcd.write(0); lcd.print(e1); + lcd.setCursor(0, 1); lcd.print(e2); + break; + } + case 1: { // NAV + char n1[17], n2[17]; + snprintf(n1, sizeof(n1), " P:%.1f R:%.1f", + Math.atan2(-latestTel.ax, Math.sqrt(latestTel.ay * latestTel.ay + latestTel.az * latestTel.az)) * 180 / Math.PI, + Math.atan2(latestTel.ay, latestTel.az) * 180 / Math.PI); + snprintf(n2, sizeof(n2), " GYR:%d", (int)latestTel.gz); + lcd.setCursor(0, 0); lcd.print(n1); + lcd.setCursor(0, 1); lcd.print(n2); + break; + } + case 2: { // SYS + char s1[17], s2[17]; + snprintf(s1, sizeof(s1), " MQTT:%s", mqttClient.connected() ? "ON" : "OFF"); + snprintf(s2, sizeof(s2), " WiFi:%s", WiFi.status() == WL_CONNECTED ? "ON" : "OFF"); + lcd.setCursor(0, 0); lcd.print(s1); + lcd.setCursor(0, 1); lcd.write(1); lcd.print(s2); + break; + } + } +} +""" + +content = re.sub(r'void onDataRecv\(.*', lcd_func + '\nvoid onDataRecv(const uint8_t *mac, const uint8_t *incomingData, int len) {', content) + + +# Setup LCD +lcd_setup = """ // LCD init + lcd.init(); + lcd.backlight(); + lcd.createChar(0, thermIcon); + lcd.createChar(1, signalIcon); + lcd.createChar(2, warnIcon); + lcd.clear(); + lcd.setCursor(0, 0); + lcd.print("DEEPTRACK GTWRY"); + lcd.setCursor(0, 1); + lcd.print("Waiting link..."); + + WiFi.mode(WIFI_STA);""" + +content = re.sub(r' WiFi\.mode\(WIFI_STA\);', lcd_setup, content) + +# LCD Loop update +lcd_loop = """ + // --- Auto-cycle LCD pages every 3s (unless DANGER) --- + if (millis() - lastPageChange >= 3000) { + lastPageChange = millis(); + if (latestTel.dangerState != 1) { + lcdPage = (lcdPage + 1) % NUM_PAGES; + updateLCD(); + } + } +""" + +content = re.sub(r' // Check serial for commands \(direct typing\) or telemetry', lcd_loop + '\n // Check serial for commands (direct typing) or telemetry', content) + +with open("GATEWAY/src/main.cpp", "w") as f: + f.write(content) From 13d70ef473312e0502a81dfd280b34cd62179b7f Mon Sep 17 00:00:00 2001 From: sea-deep Date: Mon, 24 Aug 2026 05:51:51 +0530 Subject: [PATCH 10/13] Fix Gateway LCD JSON deserialization --- GATEWAY/src/main.cpp | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/GATEWAY/src/main.cpp b/GATEWAY/src/main.cpp index 54c2813..e14a585 100644 --- a/GATEWAY/src/main.cpp +++ b/GATEWAY/src/main.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #define USE_ESP_NOW 0 @@ -94,6 +95,8 @@ void updateLCD() { void onDataRecv(const uint8_t *mac, const uint8_t *incomingData, int len) { if (len == sizeof(TelemetryPacket)) { TelemetryPacket *pkt = (TelemetryPacket*)incomingData; + memcpy(&latestTel, pkt, sizeof(TelemetryPacket)); + lastDataTime = millis(); char json[256]; snprintf(json, sizeof(json), "{\"t\":%.1f,\"h\":%.1f,\"ax\":%.2f,\"ay\":%.2f,\"az\":%.2f,\"gx\":%.2f,\"gy\":%.2f,\"gz\":%.2f,\"gas\":%d,\"water\":%d,\"danger\":%d}", @@ -110,6 +113,7 @@ void onDataRecv(const uint8_t *mac, const uint8_t *incomingData, int len) { } } else if (len == sizeof(ScanPacket)) { ScanPacket *pkt = (ScanPacket*)incomingData; + lastDataTime = millis(); char json[128]; snprintf(json, sizeof(json), "{\"type\":\"scan\",\"seq\":%d,\"angle_deg\":%d,\"distance_mm\":%d,\"valid\":%s,\"timestamp_ms\":%d}", @@ -232,15 +236,37 @@ void loop() { #endif } } else { + // Extract JSON telemetry from Wokwi Serial Bridge int start = line.indexOf('{'); int end = line.lastIndexOf('}'); if (start >= 0 && end > start) { String jsonSub = line.substring(start, end + 1); + + // Keep the LCD alive + lastDataTime = millis(); + if (jsonSub.indexOf("\"type\":\"scan\"") > 0) { if (mqttClient.connected()) mqttClient.publish(MQTT_TOPIC_SCAN, jsonSub.c_str()); } else { if (mqttClient.connected()) mqttClient.publish(MQTT_TOPIC_TELEMETRY, jsonSub.c_str()); + + // Parse to update LCD + JsonDocument doc; + DeserializationError error = deserializeJson(doc, jsonSub); + if (!error) { + latestTel.temperature = doc["t"] | 0.0f; + latestTel.humidity = doc["h"] | 0.0f; + latestTel.ax = doc["ax"] | 0.0f; + latestTel.ay = doc["ay"] | 0.0f; + latestTel.az = doc["az"] | 1.0f; + latestTel.gx = doc["gx"] | 0.0f; + latestTel.gy = doc["gy"] | 0.0f; + latestTel.gz = doc["gz"] | 0.0f; + latestTel.gasRaw = doc["gas"] | 0; + latestTel.waterRaw = doc["water"] | 0; + latestTel.dangerState = doc["danger"] | 0; + } } } } From 70c0c7e6022aa90fe47d595ce7c01a0c351979c2 Mon Sep 17 00:00:00 2001 From: sea-deep Date: Mon, 24 Aug 2026 13:25:53 +0530 Subject: [PATCH 11/13] docs: add hardware BOM and wiring guide, remove temp fix scripts - Add docs/HARDWARE_BOM.md: complete shopping list with INR estimates, printable checklist, corrected power architecture (separate 5V bus) - Add docs/WIRING_GUIDE.md: pin-by-pin wiring for Rover and Gateway, voltage divider circuits, motor truth table, first-power-on checklist - Fix: MQ-4 needs 5V + voltage divider (diagram had 3.3V, no divider) - Fix: separate 5V power bus (diagram routed all loads through ESP32 5V pin) - Fix: added decoupling capacitors to BOM (470uF on 5V, 100nF on 3V3) - Remove 10 temp Python fix/rewrite/update scripts from previous iterations - Gateway: add LED pinMode init and heartbeat toggle on serial RX --- GATEWAY/src/main.cpp | 9 + docs/HARDWARE_BOM.md | 171 ++++++++++++++++ docs/WIRING_GUIDE.md | 450 ++++++++++++++++++++++++++++++++++++++++++ fix_c.py | 21 -- fix_esp_now.py | 22 --- fix_gateway_serial.py | 41 ---- fix_math.py | 7 - restore_lcd.py | 125 ------------ rewrite_clean.py | 102 ---------- rewrite_dashboard.py | 137 ------------- update_gateway.py | 60 ------ update_rover.py | 53 ----- upgrade_dashboard.py | 147 -------------- 13 files changed, 630 insertions(+), 715 deletions(-) create mode 100644 docs/HARDWARE_BOM.md create mode 100644 docs/WIRING_GUIDE.md delete mode 100644 fix_c.py delete mode 100644 fix_esp_now.py delete mode 100644 fix_gateway_serial.py delete mode 100644 fix_math.py delete mode 100644 restore_lcd.py delete mode 100644 rewrite_clean.py delete mode 100644 rewrite_dashboard.py delete mode 100644 update_gateway.py delete mode 100644 update_rover.py delete mode 100644 upgrade_dashboard.py diff --git a/GATEWAY/src/main.cpp b/GATEWAY/src/main.cpp index e14a585..511e20f 100644 --- a/GATEWAY/src/main.cpp +++ b/GATEWAY/src/main.cpp @@ -163,6 +163,10 @@ void reconnectMqtt() { void setup() { Serial.begin(115200); + pinMode(26, OUTPUT); // RED + pinMode(27, OUTPUT); // GREEN + pinMode(25, OUTPUT); // YELLOW + // LCD init lcd.init(); lcd.backlight(); @@ -221,6 +225,7 @@ void loop() { // Check serial for commands (direct typing) or telemetry (from Wokwi Bridge) if (Serial.available()) { + digitalWrite(25, !digitalRead(25)); // Toggle YELLOW LED on serial RX String line = Serial.readStringUntil('\n'); // Check for commands (direct typing) @@ -245,6 +250,7 @@ void loop() { // Keep the LCD alive lastDataTime = millis(); + Serial.println("RX: " + jsonSub); if (jsonSub.indexOf("\"type\":\"scan\"") > 0) { if (mqttClient.connected()) mqttClient.publish(MQTT_TOPIC_SCAN, jsonSub.c_str()); @@ -255,6 +261,9 @@ void loop() { JsonDocument doc; DeserializationError error = deserializeJson(doc, jsonSub); if (!error) { + digitalWrite(27, HIGH); // GREEN ON + digitalWrite(26, LOW); // RED OFF + latestTel.temperature = doc["t"] | 0.0f; latestTel.temperature = doc["t"] | 0.0f; latestTel.humidity = doc["h"] | 0.0f; latestTel.ax = doc["ax"] | 0.0f; diff --git a/docs/HARDWARE_BOM.md b/docs/HARDWARE_BOM.md new file mode 100644 index 0000000..bc28d4e --- /dev/null +++ b/docs/HARDWARE_BOM.md @@ -0,0 +1,171 @@ +# DEEPTRACK — Complete Hardware Shopping List + +Everything you need to buy for both the Rover and the Gateway. +All audit fixes are baked in (separate 5V bus, MQ-4 voltage divider, decoupling caps). + +> Search on **Robu.in**, **Amazon.in**, **Quartz Components**, or your local electronics shop. +> Prices are approximate mid-2026 INR. + +--- + +## ROVER (Goes Inside the Mine) + +### Brain + Chassis + Power + +| # | What to Search | Spec | Qty | ₹ Est. | Why | +|---|---|---|---|---|---| +| 1 | ESP32 DevKit V1 (30-pin) | ESP32-WROOM-32, Micro-USB | 1 | 350–500 | Rover MCU. 30-pin fits breadboard with 1 row free on each side | +| 2 | 4WD Robot Car Chassis Kit | Acrylic 2-layer, includes 4× TT motors + 4× 65mm wheels + screws + standoffs | 1 kit | 400–650 | If your kit does NOT include motors/wheels, buy 4× "BO Motor 200RPM" (₹40–60 each) and 4× "65mm Robot Wheel" (₹20–30 each) separately | +| 3 | 10000mAh Power Bank | 5V output, 2A or higher. USB-A output port | 1 | 500–900 | Must sustain 2A+ continuous draw. Avoid ultra-slim (auto-shutoff on low current). Mi/Ambrane/Syska work well | +| 4 | USB-A to Micro-USB Cable (short) | 30cm data cable | 1 | 50–80 | Power bank → ESP32. Keep it short so it doesn't snag on the chassis | +| 5 | USB-A Breakout Board | Female USB-A socket on a small PCB with screw terminals (5V, GND) | 1 | 30–50 | Cut a spare USB cable OR use this breakout to tap 5V directly from the power bank for the separate 5V bus. See Wiring Guide for details | + +### Motor Driver + +| # | What to Search | Spec | Qty | ₹ Est. | Why | +|---|---|---|---|---|---| +| 6 | TB6612FNG Dual Motor Driver Module | Breakout board (NOT bare chip). Pins: VM, VCC, GND, STBY, AIN1, AIN2, PWMA, AO1, AO2, BIN1, BIN2, PWMB, BO1, BO2 | 1 | 200–350 | Drives both left-pair and right-pair motors. VM rated up to 13.5V (we use 5V) | + +### Sensors + +| # | What to Search | Spec | Qty | ₹ Est. | Why | +|---|---|---|---|---|---| +| 7 | DHT22 Sensor Module (3-pin) | AM2302 on a small PCB, 3 pins: VCC, DATA, GND | 1 | 180–280 | Temperature + humidity. The 3-pin module has an onboard 10kΩ pull-up. If you get the bare 4-pin sensor instead, you must add your own 10kΩ pull-up to 3V3 | +| 8 | MQ-4 Gas Sensor Module | Blue PCB, 4 pins: VCC, GND, AO, DO | 1 | 150–220 | Methane (CH4) detection. **Must be powered from 5V** (heater coil). AO output is 0–5V, needs voltage divider to GPIO (included in resistors below) | +| 9 | HC-SR04 Ultrasonic Sensor | 4-pin: VCC, TRIG, ECHO, GND | 1 | 50–90 | Forward obstacle distance. ECHO is 5V logic — needs voltage divider (included in resistors below) | +| 10 | MPU6050 GY-521 Module | 6-axis accel + gyro, I2C, address 0x68 | 1 | 120–200 | Tilt / orientation sensing. Onboard 3.3V regulator + I2C pull-ups | +| 11 | VL53L0X GY-VL53L0XV2 Module | Time-of-Flight laser distance, I2C, address 0x29 | 1 | 250–400 | Mounts on the servo for sweeping obstacle scan. Onboard regulator + pull-ups | +| 12 | LM393 Speed Sensor Module | Slotted optical sensor + comparator PCB + encoder disc | 2 | 40–70 each | One per side. Disc attaches to motor shaft, sensor counts slots. DO = digital pulse output | +| 13 | Water Level Sensor Module | Flat PCB with exposed traces, 3 pins: VCC, GND, SIG | 1 | 30–60 | Flood detection. Power from 3.3V so output stays within ESP32 ADC range | + +### Actuators + Indicators + +| # | What to Search | Spec | Qty | ₹ Est. | Why | +|---|---|---|---|---|---| +| 14 | SG90 Micro Servo 9g | 180° rotation, 3 wires (brown=GND, red=5V, orange=signal) | 1 | 80–130 | Mounts the VL53L0X for scanning sweep (30°–150°). Powered from 5V bus | +| 15 | Active Piezo Buzzer | 5V, 2-pin | 1 | 15–30 | Danger alarm. Code uses `tone()` so a passive buzzer also works | +| 16 | 5mm Red LED | Standard through-hole | 1 | 5–10 | Danger indicator | +| 17 | 5mm Green LED | Standard through-hole | 1 | 5–10 | Normal status | + +### Resistors + Capacitors + +| # | What to Search | Value | Qty | ₹ Est. | Purpose | +|---|---|---|---|---|---| +| 18 | 10kΩ Resistor ¼W | 10kΩ | 4 | 2–5 each | 1× DHT22 pull-up (skip if 3-pin module), 1× HC-SR04 ECHO divider upper, 1× MQ-4 AO divider upper, 1× right encoder pull-up | +| 19 | 15kΩ Resistor ¼W | 15kΩ | 2 | 2–5 each | 1× HC-SR04 ECHO divider lower, 1× MQ-4 AO divider lower | +| 20 | 220Ω Resistor ¼W | 220Ω | 3 | 2–5 each | 1× buzzer, 1× red LED, 1× green LED | +| 21 | 470µF Electrolytic Capacitor | 470µF 16V (or 25V) | 1 | 5–10 | Across main 5V bus — absorbs motor current spikes | +| 22 | 100nF Ceramic Capacitor | 0.1µF (code "104") | 2 | 2–5 each | 1× across ESP32 3V3/GND, 1× across TB6612FNG VCC/GND — filters high-freq noise | + +> **Tip:** Buy a resistor assortment kit (₹80–120) and a capacitor assortment kit (₹60–100) instead of individual pieces. You'll have spares for mistakes. + +--- + +## GATEWAY (Sits at Base Desk, Connected to Laptop) + +| # | What to Search | Spec | Qty | ₹ Est. | Why | +|---|---|---|---|---|---| +| 23 | ESP32 DevKit V1 (30-pin) | Same as Rover | 1 | 350–500 | Receives ESP-NOW from Rover, shows on LCD, sends to laptop via USB Serial | +| 24 | 16×2 I2C LCD Module | LCD1602 with PCF8574 I2C backpack soldered on. 4 pins: VCC, GND, SDA, SCL. Address 0x27 | 1 | 150–220 | Displays live telemetry. Blue backlight / white text. Has a small blue contrast potentiometer on the back — adjust with screwdriver until text is visible | +| 25 | 5mm Red LED | Standard | 1 | 5–10 | Danger | +| 26 | 5mm Green LED | Standard | 1 | 5–10 | Normal / Link OK | +| 27 | 5mm Yellow LED | Standard | 1 | 5–10 | Heartbeat — blinks on each received packet | +| 28 | 220Ω Resistor ¼W | 220Ω | 3 | 2–5 each | 1 per LED | +| 29 | USB-A to Micro-USB Cable (1–1.5m) | Standard data cable | 1 | 60–100 | Gateway ESP32 → Laptop. This is your live data link. Dashboard in Chrome connects to this port via Web Serial API | + +--- + +## WIRING + ASSEMBLY SUPPLIES + +| # | What to Search | Qty | ₹ Est. | Why | +|---|---|---|---|---| +| 30 | Half-size Breadboard (400 pts) | 2 | 80–120 each | 1 for Rover (mounts on chassis), 1 for Gateway | +| 31 | Male-to-Male Jumper Wires 20cm | 1 pack (40 wires) | 80–120 | Breadboard-to-breadboard | +| 32 | Male-to-Female Jumper Wires 20cm | 1 pack (40 wires) | 80–120 | ESP32 headers → sensor modules | +| 33 | Female-to-Female Jumper Wires 20cm | 1 pack (20 wires) | 60–80 | Module-to-module when both have male headers | +| 34 | Double-sided foam tape | 1 roll | 20–30 | Mount breadboard + sensors on chassis | +| 35 | Small zip ties | 1 pack | 20–30 | Cable management on chassis | +| 36 | Small Phillips screwdriver | 1 | 20–40 | Chassis assembly + LCD contrast adjustment | + +--- + +## DASHBOARD CONNECTION (How the Cable Works) + +The Gateway ESP32 stays plugged into your laptop via USB cable (#29). This single cable: +1. **Powers the Gateway** — 5V from laptop USB +2. **Carries serial data** — Gateway prints JSON at 115200 baud + +On the laptop: +1. Open `DASHBOARD/dashboard.html` in **Google Chrome** (or Edge / Chromium) +2. Click **"Connect Serial"** +3. Select the Gateway's port (`/dev/ttyUSB0` on Linux, `COMx` on Windows) +4. Dashboard goes live — gauges, path trace, and event log update in real-time + +> **Web Serial requires Chrome/Edge/Chromium.** Firefox and Safari do NOT support it. + +> **Linux permission fix** if you get "Permission denied": +> ``` +> sudo usermod -a -G dialout $USER +> ``` +> Log out and back in. + +--- + +## COST SUMMARY + +| Section | ₹ Estimate | +|---|---| +| Rover (ESP32 + Chassis + Sensors + Driver + Actuators + Passives + Caps) | 2,200 – 3,500 | +| Gateway (ESP32 + LCD + LEDs + Resistors + Cable) | 600 – 950 | +| Wiring Supplies (Breadboards + Wires + Tape) | 350 – 550 | +| **TOTAL** | **3,150 – 5,000** | + +> If you already own a 10000mAh power bank, subtract ₹500–900. + +--- + +## PRINTABLE CHECKLIST + +``` +ROVER: +[ ] ESP32 DevKit V1 ×1 +[ ] 4WD Chassis Kit (4× TT motors, 4× wheels, acrylic frame) ×1 +[ ] TB6612FNG Motor Driver Module ×1 +[ ] DHT22 Sensor Module (3-pin preferred) ×1 +[ ] MQ-4 Gas Sensor Module ×1 +[ ] HC-SR04 Ultrasonic Sensor ×1 +[ ] MPU6050 GY-521 Module ×1 +[ ] VL53L0X GY-VL53L0XV2 Module ×1 +[ ] LM393 Speed Sensor + Encoder Disc ×2 +[ ] Water Level Sensor Module ×1 +[ ] SG90 Micro Servo ×1 +[ ] Active Piezo Buzzer ×1 +[ ] 5mm Red LED ×1 +[ ] 5mm Green LED ×1 +[ ] 10kΩ Resistors ×4 +[ ] 15kΩ Resistors ×2 +[ ] 220Ω Resistors ×3 +[ ] 470µF Electrolytic Capacitor ×1 +[ ] 100nF Ceramic Capacitors ×2 +[ ] 10000mAh Power Bank ×1 +[ ] Short USB Cable (30cm) ×1 +[ ] USB-A Breakout Board ×1 + +GATEWAY: +[ ] ESP32 DevKit V1 ×1 +[ ] 16×2 I2C LCD (PCF8574) ×1 +[ ] 5mm Red LED ×1 +[ ] 5mm Green LED ×1 +[ ] 5mm Yellow LED ×1 +[ ] 220Ω Resistors ×3 +[ ] USB Cable (1–1.5m) ×1 + +WIRING: +[ ] Breadboards ×2 +[ ] M-M Jumper Wires (40pc) ×1 +[ ] M-F Jumper Wires (40pc) ×1 +[ ] F-F Jumper Wires (20pc) ×1 +[ ] Double-sided foam tape ×1 +[ ] Zip ties ×1 +[ ] Small screwdriver ×1 +``` diff --git a/docs/WIRING_GUIDE.md b/docs/WIRING_GUIDE.md new file mode 100644 index 0000000..57e1752 --- /dev/null +++ b/docs/WIRING_GUIDE.md @@ -0,0 +1,450 @@ +# DEEPTRACK — Circuit Wiring Guide + +Step-by-step pin-by-pin wiring for both the Rover and Gateway. +All connections are **corrected** — diagram simulation shortcuts are replaced with real-hardware-safe wiring. + +--- + +## CRITICAL DIFFERENCE FROM WOKWI DIAGRAM + +The Wokwi diagram routes all 5V loads through the ESP32's tiny 5V pin. On real hardware this **will** brownout and potentially damage the board. This guide uses a **separate 5V power bus**. + +### Power Bus Setup (Do This First) + +``` +POWER BANK (USB-A port) + │ + ├──[USB-A Breakout Board or cut USB cable]──→ +5V BUS (red wire) + │ │ + │ ├→ ESP32 VIN pin + │ ├→ TB6612FNG VM pin + │ ├→ SG90 Servo V+ (red wire) + │ ├→ HC-SR04 VCC + │ ├→ MQ-4 Module VCC + │ │ + │ [470µF cap across +5V and GND] + │ + └──────────────────────────────────────→ GND BUS (black wire) + │ + ├→ ESP32 GND (any GND pin) + ├→ TB6612FNG GND (both GND pins) + ├→ All sensor GND pins + ├→ All LED cathodes (via their circuits) + └→ Buzzer negative pin +``` + +**How to tap 5V from the power bank:** +- **Option A (cleanest):** Use a USB-A female breakout board. Plug the power bank's USB cable into it. The breakout exposes 5V and GND as screw terminals or header pins. +- **Option B (quick):** Cut a spare USB-A cable. The **red** wire is +5V, **black** is GND. Strip, tin, and connect to breadboard rails. + +The ESP32 gets its 5V through the **VIN** pin (not through Micro-USB in this setup). The onboard AMS1117 regulator converts VIN → 3.3V for the ESP32's logic. + +> You can ALSO power the ESP32 via its Micro-USB (plug a second cable from the power bank). In that case, skip connecting VIN and use the ESP32's `5V` pin only for the 3.3V LDO input — do NOT draw motor/servo current from it. + +--- + +## ROVER ESP32 — Complete Pin Map + +### GPIO Assignment Table + +| GPIO | Direction | Connected To | Wire Color | Notes | +|---|---|---|---|---| +| **VIN** | Power In | 5V Bus (+) | Red | Powers the ESP32 via onboard regulator | +| **GND** | Power | GND Bus | Black | Use multiple GND pins to distribute current | +| **3V3** | Power Out | 3.3V rail for logic sensors | Red | Max ~600mA from LDO. Feeds: DHT22, MPU6050, VL53L0X, encoders, water sensor | +| **23** | Output | DHT22 DATA | Green | + 10kΩ pull-up to 3V3 (skip if using 3-pin module) | +| **34** | ADC Input | MQ-4 AO (via voltage divider) | Orange | Input-only pin, no pull-up available | +| **19** | Output | HC-SR04 TRIG | Cyan | 3.3V trigger pulse, HC-SR04 accepts it fine | +| **18** | Input | HC-SR04 ECHO (via voltage divider) | Yellow | 5V → 3.0V through 10k/15k divider | +| **21** | I2C SDA | MPU6050 SDA + VL53L0X SDA | Blue | Shared I2C bus, different addresses | +| **22** | I2C SCL | MPU6050 SCL + VL53L0X SCL | Purple | Shared I2C bus | +| **32** | Input (interrupt) | Left LM393 Speed Sensor DO | Green | Rising edge interrupt for pulse counting | +| **35** | Input (interrupt) | Right LM393 Speed Sensor DO | Green | Input-only pin. Needs external 10kΩ pull-up to 3V3 | +| **36 (VP)** | ADC Input | Water Level Sensor SIG | Cyan | Input-only pin. Powered from 3V3, output 0–3.3V | +| **13** | PWM Output | SG90 Servo signal (orange wire) | Purple | 50Hz PWM for servo position | +| **4** | Output | Buzzer (+) via 220Ω resistor | Orange | `tone()` generates alarm frequency | +| **26** | Output | Red LED anode via 220Ω | Red | Danger indicator | +| **27** | Output | Green LED anode via 220Ω | Green | Normal indicator | +| **25** | PWM Output | TB6612FNG PWMA | Green | Left motor speed (LEDC CH0, 5kHz) | +| **16** | Output | TB6612FNG AIN1 | Blue | Left motor direction bit 1 | +| **17** | Output | TB6612FNG AIN2 | Orange | Left motor direction bit 2 | +| **14** | PWM Output | TB6612FNG PWMB | Green | Right motor speed (LEDC CH1, 5kHz) | +| **33** | Output | TB6612FNG BIN1 | Blue | Right motor direction bit 1 | +| **2** | Output | TB6612FNG BIN2 | Orange | Right motor direction bit 2. **Caution:** GPIO2 is a boot strapping pin — disconnect this wire if flashing fails | +| **5** | Output | TB6612FNG STBY | Purple | Pull HIGH to enable driver, LOW = standby | + +--- + +## ROVER — Step-by-Step Wiring + +### Step 1: Power Rails on Breadboard + +1. Run a **red wire** from the 5V bus to the breadboard's **+** rail (top) +2. Run a **black wire** from the GND bus to the breadboard's **−** rail (top) +3. Bridge the top and bottom power rails with jumper wires (red + to +, black − to −) +4. Plug the **ESP32** into the breadboard, centered, straddling the middle gap +5. Connect ESP32 **VIN** → breadboard **+5V rail** +6. Connect ESP32 **GND** (any GND pin) → breadboard **GND rail** +7. Place the **470µF electrolytic capacitor** across the +5V and GND rails. **Long leg (+) to +5V, short leg (−) to GND** + +> The ESP32's `3V3` output pin now provides 3.3V for logic-level sensors. + +### Step 2: DHT22 Temperature + Humidity Sensor + +``` +DHT22 Module (3-pin): + VCC → ESP32 3V3 + GND → GND rail + DATA → ESP32 GPIO 23 + +If using bare 4-pin DHT22: + Pin 1 (VCC) → ESP32 3V3 + Pin 2 (DATA) → ESP32 GPIO 23 + Pin 3 (not connected) + Pin 4 (GND) → GND rail + + 10kΩ resistor between Pin 1 (VCC/3V3) and Pin 2 (DATA) +``` + +### Step 3: MQ-4 Gas Sensor (with Voltage Divider) + +The MQ-4 module outputs 0–5V on AO. ESP32 GPIO34 max is 3.3V. The voltage divider scales 5V down to 3.0V. + +``` +MQ-4 Module: + VCC → 5V bus (NOT 3V3!) + GND → GND rail + AO → [10kΩ resistor] → junction point → ESP32 GPIO 34 + │ + [15kΩ resistor] + │ + GND rail + +Voltage at junction = 5V × 15k/(10k+15k) = 3.0V max ← safe +``` + +**Wiring on breadboard:** +1. MQ-4 AO pin → one end of 10kΩ resistor (Row A) +2. Other end of 10kΩ → junction row (Row B) +3. From junction row (Row B) → jumper wire to ESP32 GPIO34 +4. From junction row (Row B) → one end of 15kΩ resistor +5. Other end of 15kΩ → GND rail + +### Step 4: HC-SR04 Ultrasonic Sensor (with Voltage Divider) + +``` +HC-SR04: + VCC → 5V bus + GND → GND rail + TRIG → ESP32 GPIO 19 (direct, 3.3V trigger is accepted by HC-SR04) + ECHO → [10kΩ resistor] → junction → ESP32 GPIO 18 + │ + [15kΩ resistor] + │ + GND rail + +Voltage at junction = 5V × 15k/(10k+15k) = 3.0V max ← safe +``` + +Identical divider circuit to the MQ-4. Build it the same way on the breadboard. + +### Step 5: MPU6050 IMU (I2C Bus) + +``` +MPU6050 GY-521: + VCC → ESP32 3V3 + GND → GND rail + SDA → ESP32 GPIO 21 + SCL → ESP32 GPIO 22 + (AD0, INT, XDA, XCL — leave unconnected) +``` + +No external pull-ups needed — the GY-521 module has 4.7kΩ pull-ups onboard. + +### Step 6: VL53L0X ToF Sensor (I2C Bus — Shared with MPU6050) + +``` +VL53L0X GY-VL53L0XV2: + VIN → ESP32 3V3 + GND → GND rail + SDA → ESP32 GPIO 21 (same wire/row as MPU6050 SDA) + SCL → ESP32 GPIO 22 (same wire/row as MPU6050 SCL) + (XSHUT, GPIO1 — leave unconnected) +``` + +Both I2C devices share the same SDA/SCL lines. They have different addresses (MPU=0x68, VL53=0x29) so there's no conflict. Both modules have onboard pull-ups. + +> **Mount the VL53L0X on the SG90 servo horn** using double-sided tape or a small bracket. The servo sweeps 30°–150°, and the VL53L0X scans distances at each angle. + +### Step 7: LM393 Speed Sensors (Wheel Encoders) + +``` +Left Speed Sensor: + VCC → ESP32 3V3 + GND → GND rail + DO → ESP32 GPIO 32 + +Right Speed Sensor: + VCC → ESP32 3V3 + GND → GND rail + DO → ESP32 GPIO 35 + + 10kΩ pull-up resistor between ESP32 3V3 and GPIO 35 + (GPIO35 is input-only, has no internal pull-up) +``` + +**Mounting:** Attach the slotted encoder disc to one of the motor shafts on each side. Position the LM393 sensor so the disc's slots pass through the sensor's optical gap. Use hot glue or zip ties to secure. + +### Step 8: Water Level Sensor + +``` +Water Level Sensor: + VCC → ESP32 3V3 (NOT 5V — keeps output within 0–3.3V) + GND → GND rail + SIG → ESP32 GPIO 36 (VP) +``` + +### Step 9: SG90 Scanning Servo + +``` +SG90 Servo (3 wires): + Brown wire (GND) → GND rail + Red wire (V+) → 5V bus (NOT through ESP32) + Orange wire (Signal) → ESP32 GPIO 13 +``` + +> Power the servo from the 5V bus, not the ESP32's 5V pin. Servo stall current can reach 700mA. + +### Step 10: Buzzer + +``` +Buzzer: + (+) positive pin → [220Ω resistor] → ESP32 GPIO 4 + (−) negative pin → GND rail +``` + +### Step 11: Status LEDs + +``` +Red LED (Danger): + Anode (long leg) → [220Ω resistor] → ESP32 GPIO 26 + Cathode (short leg) → GND rail + +Green LED (Normal): + Anode (long leg) → [220Ω resistor] → ESP32 GPIO 27 + Cathode (short leg) → GND rail +``` + +### Step 12: TB6612FNG Motor Driver + +``` +TB6612FNG Module: + VM → 5V bus (motor power) + VCC → ESP32 3V3 (logic reference voltage) + GND → GND rail (both GND pins) + STBY → ESP32 GPIO 5 (HIGH = enabled) + + PWMA → ESP32 GPIO 25 (left motor speed PWM) + AIN1 → ESP32 GPIO 16 (left direction) + AIN2 → ESP32 GPIO 17 (left direction) + AO1 → Left motors (+) terminal ┐ + AO2 → Left motors (−) terminal ┘ Front-Left and Rear-Left wired in parallel + + PWMB → ESP32 GPIO 14 (right motor speed PWM) + BIN1 → ESP32 GPIO 33 (right direction) + BIN2 → ESP32 GPIO 2 (right direction) ⚠ see note below + BO1 → Right motors (+) terminal ┐ + BO2 → Right motors (−) terminal ┘ Front-Right and Rear-Right wired in parallel +``` + +> Place a **100nF ceramic capacitor** across VCC and GND on the TB6612 module — solder it or tuck it into the breadboard next to the module. + +**Motor wiring — parallel pairs:** +``` + AO1 ───┬──→ Front-Left Motor (+) + └──→ Rear-Left Motor (+) + + AO2 ───┬──→ Front-Left Motor (−) + └──→ Rear-Left Motor (−) + + BO1 ───┬──→ Front-Right Motor (+) + └──→ Rear-Right Motor (+) + + BO2 ───┬──→ Front-Right Motor (−) + └──→ Rear-Right Motor (−) +``` + +If the wheels spin the wrong direction, swap the (+) and (−) wires for that motor pair. + +### Step 13: Decoupling Capacitor on ESP32 + +Place a **100nF ceramic capacitor** between ESP32 **3V3** pin and **GND** pin (any GND). This filters high-frequency noise from the motors reaching the ESP32's logic rail. + +--- + +## ⚠ GPIO2 Boot Note + +GPIO2 (used for TB6612 BIN2) is a strapping pin. If the motor driver holds it HIGH during power-on, the ESP32 may fail to enter flash mode. + +**If you cannot upload firmware:** +1. Disconnect the jumper wire from GPIO2 +2. Flash the firmware via USB +3. Reconnect the jumper wire +4. Press the RST button on the ESP32 + +This only matters during flashing. Once the firmware is running, GPIO2 works fine as an output. + +--- + +## GATEWAY ESP32 — Complete Pin Map + +| GPIO | Direction | Connected To | Wire Color | +|---|---|---|---| +| **5V** | Power Out | LCD VCC | Red | +| **GND** | Power | LCD GND + all LED cathodes | Black | +| **21** | I2C SDA | LCD SDA | Green | +| **22** | I2C SCL | LCD SCL | Blue | +| **26** | Output | Red LED anode via 220Ω | Red | +| **27** | Output | Green LED anode via 220Ω | Green | +| **25** | Output | Yellow LED anode via 220Ω | Orange | + +--- + +## GATEWAY — Step-by-Step Wiring + +The Gateway is powered entirely by the laptop USB cable. No external power needed. + +### Step 1: LCD 16×2 (I2C) + +``` +LCD I2C Module (4-pin header on the backpack): + GND → ESP32 GND + VCC → ESP32 5V + SDA → ESP32 GPIO 21 + SCL → ESP32 GPIO 22 +``` + +> If the LCD shows blocks or is blank, use a small screwdriver to turn the **blue potentiometer** on the back of the I2C backpack. This adjusts contrast. + +### Step 2: Status LEDs + +``` +Red LED (Danger): + Anode (long leg) → [220Ω resistor] → ESP32 GPIO 26 + Cathode (short leg) → GND + +Green LED (Normal): + Anode (long leg) → [220Ω resistor] → ESP32 GPIO 27 + Cathode (short leg) → GND + +Yellow LED (Heartbeat): + Anode (long leg) → [220Ω resistor] → ESP32 GPIO 25 + Cathode (short leg) → GND +``` + +That's it for the Gateway. Plug it into the laptop via USB and you're live. + +--- + +## COMPLETE SYSTEM DIAGRAM + +``` + ┌─────────── MINE ───────────┐ + │ │ + │ ┌─── ROVER ESP32 ───┐ │ + │ │ │ │ + ┌──────────────┐ │ │ DHT22 → GPIO23 │ │ + │ 10000mAh │ │ │ MQ-4 → GPIO34 │ │ + │ Power Bank │───┼───→│ HC-SR04→ GPIO18 │ │ + │ (5V / 2A+) │ │ │ MPU6050→ I2C │ │ + └──────────────┘ │ │ VL53L0X→ I2C │ │ + │ │ Encoders → 32,35 │ │ + │ │ Water → GPIO36 │ │ + │ │ Servo → GPIO13 │ │ + │ │ Motors → TB6612 │ │ + │ │ Buzzer → GPIO4 │ │ + │ │ LEDs → 26,27 │ │ + │ │ │ │ + │ │ ESP-NOW TX │ │ + │ └───────┬────────────┘ │ + │ │ │ + └────────────┼─────────────────┘ + │ + ~200m wireless range + (no WiFi router needed) + │ + ┌────────────┼─────────────────┐ + │ │ BASE DESK │ + │ ┌───────▼────────────┐ │ + │ │ GATEWAY ESP32 │ │ + │ │ │ │ + │ │ ESP-NOW RX │ │ + │ │ 16×2 LCD (I2C) │ │ + │ │ Red/Green/Yellow │ │ + │ │ LEDs │ │ + │ │ │ │ + │ └───────┬────────────┘ │ + │ │ USB Cable │ + │ │ (data + power) │ + │ ┌───────▼────────────┐ │ + │ │ LAPTOP │ │ + │ │ Chrome Browser │ │ + │ │ dashboard.html │ │ + │ │ (Web Serial API) │ │ + │ └───────────────────┘ │ + └─────────────────────────────┘ +``` + +--- + +## VOLTAGE DIVIDER REFERENCE + +Both the HC-SR04 ECHO and MQ-4 AO use identical voltage dividers: + +``` +5V Signal ──── [10kΩ] ──── Junction ──── [15kΩ] ──── GND + │ + ESP32 GPIO + (reads 3.0V max) + + V_out = V_in × R_lower / (R_upper + R_lower) + V_out = 5.0V × 15kΩ / (10kΩ + 15kΩ) + V_out = 5.0V × 0.60 + V_out = 3.0V ← safe for ESP32 (max 3.3V) +``` + +--- + +## MOTOR DIRECTION TRUTH TABLE (TB6612FNG) + +| IN1 | IN2 | PWM | Motor Action | +|---|---|---|---| +| HIGH | LOW | 0–255 | Forward (speed = PWM duty) | +| LOW | HIGH | 0–255 | Reverse (speed = PWM duty) | +| HIGH | HIGH | 255 | Short brake (motor locked) | +| LOW | LOW | any | Coast (free spin) | + +STBY must be HIGH for the driver to operate. STBY LOW = all outputs disabled (standby mode). + +--- + +## FIRST POWER-ON CHECKLIST + +Before applying power, verify: + +``` +[ ] 470µF cap on 5V bus (correct polarity — long leg to +5V) +[ ] 100nF cap on ESP32 3V3-GND +[ ] 100nF cap on TB6612 VCC-GND +[ ] MQ-4 VCC on 5V bus (NOT 3V3) +[ ] MQ-4 AO goes through 10k/15k divider to GPIO34 (NOT direct) +[ ] HC-SR04 ECHO goes through 10k/15k divider to GPIO18 (NOT direct) +[ ] HC-SR04 VCC on 5V bus +[ ] Servo V+ on 5V bus (NOT ESP32 5V pin) +[ ] TB6612 VM on 5V bus +[ ] All GND wires connected to the same GND bus +[ ] No bare wire ends touching each other +[ ] DHT22 on 3V3 (not 5V) +[ ] Water sensor on 3V3 (not 5V) +[ ] LED resistors (220Ω) are present (no direct GPIO → LED) +``` + +> **MQ-4 warm-up:** The MQ-4 sensor needs 24–48 hours of continuous power for the heater to stabilize on first use. Readings in the first few minutes will be unreliable. For the hackathon demo, power it on as early as possible. diff --git a/fix_c.py b/fix_c.py deleted file mode 100644 index 82b55bd..0000000 --- a/fix_c.py +++ /dev/null @@ -1,21 +0,0 @@ -import re -with open("GATEWAY/src/main.cpp", "r") as f: - c = f.read() - -# Replace the math block entirely because ESP32 Arduino framework uses standard C functions -replacement = """ - case 1: { // NAV - char n1[17], n2[17]; - snprintf(n1, sizeof(n1), " P:%.1f R:%.1f", - atan2(-latestTel.ax, sqrt(latestTel.ay * latestTel.ay + latestTel.az * latestTel.az)) * 180.0 / 3.14159265, - atan2(latestTel.ay, latestTel.az) * 180.0 / 3.14159265); - snprintf(n2, sizeof(n2), " GYR:%d", (int)latestTel.gz); - lcd.setCursor(0, 0); lcd.print(n1); - lcd.setCursor(0, 1); lcd.print(n2); - break; - } -""" - -c = re.sub(r' case 1: \{.*? \}', replacement, c, flags=re.DOTALL) -with open("GATEWAY/src/main.cpp", "w") as f: - f.write(c) diff --git a/fix_esp_now.py b/fix_esp_now.py deleted file mode 100644 index 42667be..0000000 --- a/fix_esp_now.py +++ /dev/null @@ -1,22 +0,0 @@ -import re - -def update_file(filename): - with open(filename, "r") as f: - content = f.read() - - # Add the toggle flag at the top - if "#define USE_ESP_NOW" not in content: - content = re.sub(r'(#include \n#include )', r'\1\n\n#define USE_ESP_NOW 0\n', content) - - # Wrap esp_now_init block - content = re.sub(r'(\s+if \(esp_now_init\(\) != ESP_OK\) \{.*?\s+esp_now_add_peer\(&peerInfo\);\n)', - r'\n#if USE_ESP_NOW\1#endif\n', content, flags=re.DOTALL) - - # Wrap esp_now_send calls - content = re.sub(r'(\s+esp_now_send\([^;]+;\n)', r'\n#if USE_ESP_NOW\1#endif\n', content) - - with open(filename, "w") as f: - f.write(content) - -update_file("src/main.cpp") -update_file("GATEWAY/src/main.cpp") diff --git a/fix_gateway_serial.py b/fix_gateway_serial.py deleted file mode 100644 index 9cff373..0000000 --- a/fix_gateway_serial.py +++ /dev/null @@ -1,41 +0,0 @@ -import re - -with open("GATEWAY/src/main.cpp", "r") as f: - content = f.read() - -replacement = """ - if (Serial.available()) { - String line = Serial.readStringUntil('\\n'); - - // Check for commands (direct typing) - if (line.startsWith("L:")) { - int spaceIdx = line.indexOf(' '); - if (spaceIdx > -1) { - ControlPacket pkt; - pkt.type = 0; - pkt.motor_l = line.substring(2, spaceIdx).toInt(); - pkt.motor_r = line.substring(line.indexOf("R:") + 2).toInt(); -#if USE_ESP_NOW - esp_now_send(roverAddress, (uint8_t*)&pkt, sizeof(ControlPacket)); -#endif - } - } else { - // Extract JSON telemetry from Wokwi Serial Bridge - int start = line.indexOf('{'); - int end = line.lastIndexOf('}'); - if (start >= 0 && end > start) { - String jsonSub = line.substring(start, end + 1); - if (jsonSub.indexOf("\\"type\\":\\"scan\\"") > 0) { - if (mqttClient.connected()) mqttClient.publish(MQTT_TOPIC_SCAN, jsonSub.c_str()); - } else { - if (mqttClient.connected()) mqttClient.publish(MQTT_TOPIC_TELEMETRY, jsonSub.c_str()); - } - } - } - } -""" - -content = re.sub(r' if \(Serial\.available\(\)\) \{.*? \}\n', replacement, content, flags=re.DOTALL) - -with open("GATEWAY/src/main.cpp", "w") as f: - f.write(content) diff --git a/fix_math.py b/fix_math.py deleted file mode 100644 index e95a971..0000000 --- a/fix_math.py +++ /dev/null @@ -1,7 +0,0 @@ -with open("GATEWAY/src/main.cpp", "r") as f: - c = f.read() -c = c.replace("Math.atan2", "atan2") -c = c.replace("Math.sqrt", "sqrt") -c = c.replace("Math.PI", "PI") -with open("GATEWAY/src/main.cpp", "w") as f: - f.write(c) diff --git a/restore_lcd.py b/restore_lcd.py deleted file mode 100644 index c0a7227..0000000 --- a/restore_lcd.py +++ /dev/null @@ -1,125 +0,0 @@ -import re - -with open("GATEWAY/src/main.cpp", "r") as f: - content = f.read() - -# Add the LCD logic to the top of GATEWAY/src/main.cpp -lcd_headers = """#include -#include -#include -#include - -#define USE_ESP_NOW 0 - -#include -#include "telemetry_packet.h" - -// ---------- LCD ---------- -LiquidCrystal_I2C lcd(0x27, 16, 2); -uint32_t lastDataTime = 0; -int lcdPage = 0; -#define NUM_PAGES 3 -uint32_t lastPageChange = 0; - -// Custom Icons -byte thermIcon[8] = { B00100, B01010, B01010, B01110, B01110, B11111, B11111, B01110 }; -byte signalIcon[8] = { B00000, B10000, B10100, B10100, B10101, B10101, B10101, B10101 }; -byte warnIcon[8] = { B00000, B00100, B01010, B11011, B11011, B11011, B11111, B00000 }; - -TelemetryPacket latestTel = {0}; -""" - -content = re.sub(r'#include \n#include \n#include \n\n#define USE_ESP_NOW 0\n\n#include \n#include "telemetry_packet\.h"', lcd_headers, content) - -# LCD Update function -lcd_func = """ -void updateLCD() { - lcd.clear(); - - if (millis() - lastDataTime > 3000) { - lcd.setCursor(0, 0); - lcd.write(2); // warning - lcd.print(" LINK LOST! "); - lcd.write(2); - lcd.setCursor(0, 1); - lcd.print("No data >3s"); - return; - } - - if (latestTel.dangerState == 1) { - lcd.setCursor(0, 0); - lcd.write(2); // warning icon - lcd.print(" DANGER!! "); - lcd.write(2); - lcd.setCursor(0, 1); - lcd.print("EMERGENCY STOP"); - return; - } - - switch (lcdPage) { - case 0: { // ENV - char e1[17], e2[17]; - snprintf(e1, sizeof(e1), " T:%.1fC H:%.1f%%", latestTel.temperature, latestTel.humidity); - snprintf(e2, sizeof(e2), " Gas:%d W:%d", latestTel.gasRaw, latestTel.waterRaw); - lcd.setCursor(0, 0); lcd.write(0); lcd.print(e1); - lcd.setCursor(0, 1); lcd.print(e2); - break; - } - case 1: { // NAV - char n1[17], n2[17]; - snprintf(n1, sizeof(n1), " P:%.1f R:%.1f", - Math.atan2(-latestTel.ax, Math.sqrt(latestTel.ay * latestTel.ay + latestTel.az * latestTel.az)) * 180 / Math.PI, - Math.atan2(latestTel.ay, latestTel.az) * 180 / Math.PI); - snprintf(n2, sizeof(n2), " GYR:%d", (int)latestTel.gz); - lcd.setCursor(0, 0); lcd.print(n1); - lcd.setCursor(0, 1); lcd.print(n2); - break; - } - case 2: { // SYS - char s1[17], s2[17]; - snprintf(s1, sizeof(s1), " MQTT:%s", mqttClient.connected() ? "ON" : "OFF"); - snprintf(s2, sizeof(s2), " WiFi:%s", WiFi.status() == WL_CONNECTED ? "ON" : "OFF"); - lcd.setCursor(0, 0); lcd.print(s1); - lcd.setCursor(0, 1); lcd.write(1); lcd.print(s2); - break; - } - } -} -""" - -content = re.sub(r'void onDataRecv\(.*', lcd_func + '\nvoid onDataRecv(const uint8_t *mac, const uint8_t *incomingData, int len) {', content) - - -# Setup LCD -lcd_setup = """ // LCD init - lcd.init(); - lcd.backlight(); - lcd.createChar(0, thermIcon); - lcd.createChar(1, signalIcon); - lcd.createChar(2, warnIcon); - lcd.clear(); - lcd.setCursor(0, 0); - lcd.print("DEEPTRACK GTWRY"); - lcd.setCursor(0, 1); - lcd.print("Waiting link..."); - - WiFi.mode(WIFI_STA);""" - -content = re.sub(r' WiFi\.mode\(WIFI_STA\);', lcd_setup, content) - -# LCD Loop update -lcd_loop = """ - // --- Auto-cycle LCD pages every 3s (unless DANGER) --- - if (millis() - lastPageChange >= 3000) { - lastPageChange = millis(); - if (latestTel.dangerState != 1) { - lcdPage = (lcdPage + 1) % NUM_PAGES; - updateLCD(); - } - } -""" - -content = re.sub(r' // Check serial for commands \(direct typing\) or telemetry', lcd_loop + '\n // Check serial for commands (direct typing) or telemetry', content) - -with open("GATEWAY/src/main.cpp", "w") as f: - f.write(content) diff --git a/rewrite_clean.py b/rewrite_clean.py deleted file mode 100644 index 90912ac..0000000 --- a/rewrite_clean.py +++ /dev/null @@ -1,102 +0,0 @@ -import re - -with open("DASHBOARD/dashboard.html", "r") as f: - content = f.read() - -# MQTT topic -content = content.replace("const MQTT_TOPIC = 'deeptrack/rover/telemetry';", "const MQTT_TOPIC_TELEMETRY = 'rover/telemetry';\nconst MQTT_TOPIC_SCAN = 'rover/scan';") -content = content.replace("mqttConn.subscribe(MQTT_TOPIC);", "mqttConn.subscribe(MQTT_TOPIC_TELEMETRY);\nmqttConn.subscribe(MQTT_TOPIC_SCAN);") - -# MQTT handler -msg_h = """ mqttConn.on('message', (topic, payload) => { - try { - const d = JSON.parse(payload.toString()); - if (topic === MQTT_TOPIC_TELEMETRY) { handleTelemetry(d); } - else if (topic === MQTT_TOPIC_SCAN) { handleScan(d); } - } catch(e) {} - });""" -content = re.sub(r' mqttConn\.on\(\'message\', \(topic, payload\) => \{.*? \}\);', msg_h, content, flags=re.DOTALL) - -# Serial handler -ser_h = """ try{ - const d = JSON.parse(line); - if (d.type === 'scan') handleScan(d); - else handleTelemetry(d); - }catch(e){ }""" -content = re.sub(r' try\{\s*const d = JSON\.parse\(line\);\s*handleTelemetry\(d\);\s*\}catch\(e\)\{.*?\}', ser_h, content, flags=re.DOTALL) - -# UI bindings -ui = """ document.getElementById('s-pos').textContent = d.pitch !== undefined ? `P:${d.pitch.toFixed(1)}° R:${d.roll.toFixed(1)}°` : `X:${d.x.toFixed(1)} Y:${d.y.toFixed(1)}`;""" -content = re.sub(r' document\.getElementById\(\'s-pos\'\)\.textContent = `X:\$\{d\.x\.toFixed\(1\)\} Y:\$\{d\.y\.toFixed\(1\)\}`;', ui, content) - -# handleTelemetry start -tel_start = """function handleTelemetry(raw){ - const d = { - tempC: raw.t ?? raw.tempC, hum: raw.h ?? raw.hum, gas: raw.gas, frontCm: raw.dist ?? raw.frontCm ?? 'N/A', water: raw.water, - x: raw.x ?? 0, y: raw.y ?? 0, heading: raw.hd ?? raw.heading ?? 0, - state: (raw.danger === 1) ? 'DANGER' : 'NORMAL', - dangerCause: (raw.danger === 1) ? 'EMERGENCY' : 'NONE', - encL: raw.encL ?? 0, encR: raw.encR ?? 0 - }; - if (raw.ax !== undefined) { - d.pitch = Math.atan2(-raw.ax, Math.sqrt(raw.ay * raw.ay + raw.az * raw.az)) * 180 / Math.PI; - d.roll = Math.atan2(raw.ay, raw.az) * 180 / Math.PI; - } - latest = d; - - const danger = d.state === 'DANGER';""" -content = re.sub(r'function handleTelemetry\(raw\)\{.*?const danger = d\.state === \'DANGER\';', tel_start, content, flags=re.DOTALL) - -# Remove trail logic from handleTelemetry -content = re.sub(r' // Push trail.*?requestAnimationFrame\(drawMap\);', '', content, flags=re.DOTALL) - -# Replace drawMap completely -draw_map_logic = """ -const scanPoints = []; -const MAX_POINTS = 200; -const SCALE = 0.25; - -function handleScan(data) { - if (data.valid) { - const theta = ((data.angle_deg - 90) * Math.PI) / 180; - const lateral = data.distance_mm * Math.cos(theta); - const forward = data.distance_mm * Math.sin(theta); - const w = canvas.width; - const h = canvas.height; - const canvasX = (w / 2) - (lateral * SCALE); - const canvasY = (h - 20) - (forward * SCALE); - scanPoints.push({ x: canvasX, y: canvasY, ts: Date.now() }); - if (scanPoints.length > MAX_POINTS) scanPoints.shift(); - } -} - -function drawMap() { - const w = canvas.width, h = canvas.height; - ctx.clearRect(0,0,w,h); - ctx.fillStyle = '#0f1512'; - ctx.fillRect(0,0,w,h); - ctx.strokeStyle = '#25332c'; - ctx.beginPath(); - ctx.arc(w/2, h-20, 100, 0, Math.PI, true); - ctx.arc(w/2, h-20, 200, 0, Math.PI, true); - ctx.stroke(); - ctx.fillStyle = '#4cc27a'; - ctx.fillRect(w/2 - 5, h-25, 10, 10); - const now = Date.now(); - for (let i = scanPoints.length - 1; i >= 0; i--) { - const pt = scanPoints[i]; - const age = now - pt.ts; - if (age > 5000) { scanPoints.splice(i, 1); continue; } - const alpha = 1.0 - (age / 5000); - ctx.fillStyle = `rgba(0, 255, 255, ${alpha})`; - ctx.beginPath(); - ctx.arc(pt.x, pt.y, 3, 0, Math.PI * 2); - ctx.fill(); - } - requestAnimationFrame(drawMap); -} -""" -content = re.sub(r'function drawMap\(\).*', draw_map_logic + '\ndrawMap();\n\n\n', content, flags=re.DOTALL) - -with open("DASHBOARD/dashboard.html", "w") as f: - f.write(content) diff --git a/rewrite_dashboard.py b/rewrite_dashboard.py deleted file mode 100644 index d9032a5..0000000 --- a/rewrite_dashboard.py +++ /dev/null @@ -1,137 +0,0 @@ -import re - -with open("DASHBOARD/dashboard.html", "r") as f: - content = f.read() - -# 1. Update MQTT topic to telemetry and add scan topic -content = content.replace("const MQTT_TOPIC = 'deeptrack/rover/telemetry';", """const MQTT_TOPIC_TELEMETRY = 'rover/telemetry'; -const MQTT_TOPIC_SCAN = 'rover/scan';""") -content = content.replace("mqttConn.subscribe(MQTT_TOPIC);", """mqttConn.subscribe(MQTT_TOPIC_TELEMETRY); - mqttConn.subscribe(MQTT_TOPIC_SCAN);""") - -# 2. Modify MQTT message handler -mqtt_msg_handler = """ mqttConn.on('message', (topic, payload) => { - try { - const d = JSON.parse(payload.toString()); - if (topic === MQTT_TOPIC_TELEMETRY) { - handleTelemetry(d); - } else if (topic === MQTT_TOPIC_SCAN) { - handleScan(d); - } - } catch(e) {} - });""" -content = re.sub(r' mqttConn\.on\(\'message\', \(topic, payload\) => \{.*? \}\);', mqtt_msg_handler, content, flags=re.DOTALL) - -# 3. Modify Web Serial message handler -serial_msg_handler = """ try{ - const d = JSON.parse(line); - if (d.type === 'scan') { - handleScan(d); - } else { - handleTelemetry(d); - } - }catch(e){ /* partial/garbled line, skip */ }""" -content = re.sub(r' try\{\s*const d = JSON\.parse\(line\);\s*handleTelemetry\(d\);\s*\}catch\(e\)\{.*?\}', serial_msg_handler, content, flags=re.DOTALL) - - -# 4. Replace `handleTelemetry` to support Pitch/Roll instead of x/y -telemetry_fix = """function handleTelemetry(raw){ - const d = { - tempC: raw.t ?? raw.tempC, - hum: raw.h ?? raw.hum, - gas: raw.gas, - frontCm: raw.dist ?? raw.frontCm ?? 'N/A', - water: raw.water, - x: raw.x ?? 0, - y: raw.y ?? 0, - heading: raw.hd ?? raw.heading ?? 0, - state: (raw.danger === 1) ? 'DANGER' : 'NORMAL', - dangerCause: (raw.danger === 1) ? 'EMERGENCY' : 'NONE', - encL: raw.encL ?? 0, - encR: raw.encR ?? 0 - }; - if (raw.ax !== undefined) { - d.pitch = Math.atan2(-raw.ax, Math.sqrt(raw.ay * raw.ay + raw.az * raw.az)) * 180 / Math.PI; - d.roll = Math.atan2(raw.ay, raw.az) * 180 / Math.PI; - } - latest = d; - - const danger = d.state === 'DANGER'; -""" -content = re.sub(r'function handleTelemetry\(raw\)\{.*?const danger = d\.state === \'DANGER\';', telemetry_fix, content, flags=re.DOTALL) - -# 5. Fix UI bindings for positions (X/Y -> Pitch/Roll) -ui_update = """ document.getElementById('s-pos').textContent = d.pitch !== undefined ? `P:${d.pitch.toFixed(1)}° R:${d.roll.toFixed(1)}°` : `X:${d.x.toFixed(1)} Y:${d.y.toFixed(1)}`;""" -content = re.sub(r' document\.getElementById\(\'s-pos\'\)\.textContent = `X:\$\{d\.x\.toFixed\(1\)\} Y:\$\{d\.y\.toFixed\(1\)\}`;', ui_update, content) - - -# 6. Replace Odometry Map logic with Radar Canvas logic -radar_logic = """ -// ---------- Radar Scan Map ---------- -const scanPoints = []; -const MAX_POINTS = 200; -const SCALE = 0.25; - -function handleScan(data) { - if (data.valid) { - const theta = ((data.angle_deg - 90) * Math.PI) / 180; - const lateral = data.distance_mm * Math.cos(theta); - const forward = data.distance_mm * Math.sin(theta); - - // Map to canvas (origin at center bottom) - const w = canvas.width; - const h = canvas.height; - const canvasX = (w / 2) - (lateral * SCALE); - const canvasY = (h - 20) - (forward * SCALE); - - scanPoints.push({ x: canvasX, y: canvasY, ts: Date.now() }); - if (scanPoints.length > MAX_POINTS) scanPoints.shift(); - } -} - -function drawMap() { - const w = canvas.width, h = canvas.height; - ctx.clearRect(0,0,w,h); - ctx.fillStyle = '#0f1512'; - ctx.fillRect(0,0,w,h); - - // Draw grid - ctx.strokeStyle = '#25332c'; - ctx.beginPath(); - ctx.arc(w/2, h-20, 100, 0, Math.PI, true); - ctx.arc(w/2, h-20, 200, 0, Math.PI, true); - ctx.stroke(); - - // Draw rover - ctx.fillStyle = '#4cc27a'; - ctx.fillRect(w/2 - 5, h-25, 10, 10); - - // Draw points - const now = Date.now(); - for (let i = scanPoints.length - 1; i >= 0; i--) { - const pt = scanPoints[i]; - const age = now - pt.ts; - if (age > 5000) { - scanPoints.splice(i, 1); - continue; - } - - const alpha = 1.0 - (age / 5000); - ctx.fillStyle = `rgba(0, 255, 255, ${alpha})`; - ctx.beginPath(); - ctx.arc(pt.x, pt.y, 3, 0, Math.PI * 2); - ctx.fill(); - } - requestAnimationFrame(drawMap); -} -drawMap(); -""" - -# Completely replace the old drawMap -content = re.sub(r'function drawMap\(\)\{.*?requestAnimationFrame\(drawMap\);\s*\}\s*drawMap\(\);', radar_logic, content, flags=re.DOTALL) -# Strip out the trail pushing logic in handleTelemetry -content = re.sub(r' // Push trail.*? requestAnimationFrame\(drawMap\);', '', content, flags=re.DOTALL) - - -with open("DASHBOARD/dashboard.html", "w") as f: - f.write(content) diff --git a/update_gateway.py b/update_gateway.py deleted file mode 100644 index 807ee97..0000000 --- a/update_gateway.py +++ /dev/null @@ -1,60 +0,0 @@ -import re - -with open("GATEWAY/src/main.cpp", "r") as f: - content = f.read() - -# Add MQTT callback and subscription -mqtt_additions = """ -void onMqttMessage(char* topic, byte* payload, unsigned int length) { - String msg; - for (unsigned int i = 0; i < length; i++) msg += (char)payload[i]; - if (String(topic) == "rover/control") { - if (msg.startsWith("L:")) { - int spaceIdx = msg.indexOf(' '); - if (spaceIdx > -1) { - ControlPacket pkt; - pkt.type = 0; - pkt.motor_l = msg.substring(2, spaceIdx).toInt(); - pkt.motor_r = msg.substring(msg.indexOf("R:") + 2).toInt(); - esp_now_send(roverAddress, (uint8_t*)&pkt, sizeof(ControlPacket)); - Serial.println(msg); // Bridge to Wokwi Simulator - } - } - } -} -""" - -content = re.sub(r'void reconnectMqtt\(\) \{', mqtt_additions + r'\nvoid reconnectMqtt() {', content) -content = re.sub(r'mqttClient\.setServer\(MQTT_SERVER, MQTT_PORT\);', r'mqttClient.setServer(MQTT_SERVER, MQTT_PORT);\n mqttClient.setCallback(onMqttMessage);', content) -content = re.sub(r'Serial\.println\("MQTT Connected"\);', r'Serial.println("MQTT Connected");\n mqttClient.subscribe("rover/control");', content) - -# Modify Serial reading to handle incoming JSON from Rover -serial_read_replacement = """ - // Check serial for commands (direct typing) or telemetry (from Wokwi Bridge) - if (Serial.available()) { - String line = Serial.readStringUntil('\\n'); - if (line.startsWith("L:")) { - int spaceIdx = line.indexOf(' '); - if (spaceIdx > -1) { - ControlPacket pkt; - pkt.type = 0; - pkt.motor_l = line.substring(2, spaceIdx).toInt(); - pkt.motor_r = line.substring(line.indexOf("R:") + 2).toInt(); - esp_now_send(roverAddress, (uint8_t*)&pkt, sizeof(ControlPacket)); - } - } else if (line.startsWith("{")) { - // It's telemetry from Wokwi Serial Bridge! - if (line.indexOf("\\"type\\":\\"scan\\"") > 0) { - if (mqttClient.connected()) mqttClient.publish(MQTT_TOPIC_SCAN, line.c_str()); - } else { - if (mqttClient.connected()) mqttClient.publish(MQTT_TOPIC_TELEMETRY, line.c_str()); - } - } - } -""" - -# Replace the existing serial reading block in loop() -content = re.sub(r' // Check serial for commands from the dashboard.*?\}\n \}', serial_read_replacement, content, flags=re.DOTALL) - -with open("GATEWAY/src/main.cpp", "w") as f: - f.write(content) diff --git a/update_rover.py b/update_rover.py deleted file mode 100644 index 1da33cd..0000000 --- a/update_rover.py +++ /dev/null @@ -1,53 +0,0 @@ -import re - -with open("src/main.cpp", "r") as f: - content = f.read() - -# Add Serial reading to loop() -loop_addition = """ - if (Serial.available()) { - String cmd = Serial.readStringUntil('\n'); - if (cmd.startsWith("L:")) { - int spaceIdx = cmd.indexOf(' '); - if (spaceIdx > -1) { - target_l = cmd.substring(2, spaceIdx).toInt(); - target_r = cmd.substring(cmd.indexOf("R:") + 2).toInt(); - emergencyStopped = false; - } - } - } -""" -content = re.sub(r'void loop\(\) \{(\s+)uint32_t now = millis\(\);', r'void loop() {\1uint32_t now = millis();\n' + loop_addition, content) - -# Add JSON print to scan -scan_json = """ - esp_now_send(gatewayAddress, (uint8_t *) &spkt, sizeof(ScanPacket)); - - char json[128]; - snprintf(json, sizeof(json), - "{\\"type\\":\\"scan\\",\\"seq\\":%d,\\"angle_deg\\":%d,\\"distance_mm\\":%d,\\"valid\\":%s,\\"timestamp_ms\\":%u}", - spkt.seq, spkt.angle_deg, spkt.distance_mm, spkt.valid ? "true" : "false", spkt.timestamp_ms - ); - Serial.println(json); -""" -content = re.sub(r'esp_now_send\(gatewayAddress, \(uint8_t \*\) &spkt, sizeof\(ScanPacket\)\);', scan_json, content) - -# Add JSON print to telemetry -telemetry_json = """ - esp_now_send(gatewayAddress, (uint8_t *) ¤tTelemetry, sizeof(TelemetryPacket)); - - char json[256]; - snprintf(json, sizeof(json), - "{\\"t\\":%.1f,\\"h\\":%.1f,\\"ax\\":%.2f,\\"ay\\":%.2f,\\"az\\":%.2f,\\"gx\\":%.2f,\\"gy\\":%.2f,\\"gz\\":%.2f,\\"gas\\":%d,\\"water\\":%d,\\"danger\\":%d}", - currentTelemetry.temperature, currentTelemetry.humidity, - currentTelemetry.ax, currentTelemetry.ay, currentTelemetry.az, - currentTelemetry.gx, currentTelemetry.gy, currentTelemetry.gz, - currentTelemetry.gasRaw, currentTelemetry.waterRaw, - currentTelemetry.dangerState - ); - Serial.println(json); -""" -content = re.sub(r'esp_now_send\(gatewayAddress, \(uint8_t \*\) ¤tTelemetry, sizeof\(TelemetryPacket\)\);', telemetry_json, content) - -with open("src/main.cpp", "w") as f: - f.write(content) diff --git a/upgrade_dashboard.py b/upgrade_dashboard.py deleted file mode 100644 index 77ae4e7..0000000 --- a/upgrade_dashboard.py +++ /dev/null @@ -1,147 +0,0 @@ -import re - -with open("DASHBOARD/dashboard.html", "r") as f: - content = f.read() - -# 1. Update MQTT topic and add scan topic -content = content.replace("const MQTT_TOPIC = 'deeptrack/rover/telemetry';", """const MQTT_TOPIC_TELEMETRY = 'rover/telemetry'; -const MQTT_TOPIC_SCAN = 'rover/scan';""") -content = content.replace("mqttConn.subscribe(MQTT_TOPIC);", """mqttConn.subscribe(MQTT_TOPIC_TELEMETRY); - mqttConn.subscribe(MQTT_TOPIC_SCAN);""") - -# 2. Modify MQTT message handler to handle both telemetry and scan -mqtt_msg_handler = """ mqttConn.on('message', (topic, payload) => { - try { - const d = JSON.parse(payload.toString()); - if (topic === MQTT_TOPIC_TELEMETRY) { - handleTelemetry(d); - } else if (topic === MQTT_TOPIC_SCAN) { - handleScan(d); - } - } catch(e) {} - });""" -content = re.sub(r' mqttConn\.on\(\'message\', \(topic, payload\) => \{.*? \}\);', mqtt_msg_handler, content, flags=re.DOTALL) - - -# 3. Modify Web Serial message handler to handle both -serial_msg_handler = """ try{ - const d = JSON.parse(line); - if (d.type === 'scan') { - handleScan(d); - } else { - handleTelemetry(d); - } - }catch(e){ /* partial/garbled line, skip */ }""" -content = re.sub(r' try\{\s*const d = JSON\.parse\(line\);\s*handleTelemetry\(d\);\s*\}catch\(e\)\{.*?\}', serial_msg_handler, content, flags=re.DOTALL) - - -# 4. Add the Radar UI element to the DOM -radar_html = """ -
-

2D Environment Scan

-
- -
-
-""" -# Find a place to insert the radar (e.g. before the logs panel) -content = content.replace('
', radar_html + '\n
') - - -# 5. Add Radar Drawing and `handleScan` function to JavaScript -radar_js = """ -// ---------- Radar Canvas (VL53L0X) ---------- -const rCanvas = document.getElementById('radarCanvas'); -const rCtx = rCanvas.getContext('2d'); -const scanPoints = []; -const MAX_POINTS = 200; -const SCALE = 0.25; - -function drawRadar() { - rCtx.clearRect(0, 0, rCanvas.width, rCanvas.height); - - // Draw grid - rCtx.strokeStyle = '#333'; - rCtx.beginPath(); - rCtx.arc(200, 380, 100, 0, Math.PI, true); - rCtx.arc(200, 380, 200, 0, Math.PI, true); - rCtx.stroke(); - - // Draw rover - rCtx.fillStyle = '#00ff00'; - rCtx.fillRect(195, 375, 10, 10); - - // Draw points - const now = Date.now(); - for (let i = scanPoints.length - 1; i >= 0; i--) { - const pt = scanPoints[i]; - const age = now - pt.ts; - if (age > 5000) { - scanPoints.splice(i, 1); - continue; - } - - const alpha = 1.0 - (age / 5000); - rCtx.fillStyle = `rgba(0, 255, 255, ${alpha})`; - rCtx.beginPath(); - rCtx.arc(pt.x, pt.y, 3, 0, Math.PI * 2); - rCtx.fill(); - } - requestAnimationFrame(drawRadar); -} -drawRadar(); - -function handleScan(data) { - if (data.valid) { - const theta = ((data.angle_deg - 90) * Math.PI) / 180; - const lateral = data.distance_mm * Math.cos(theta); - const forward = data.distance_mm * Math.sin(theta); - - const canvasX = 200 - (lateral * SCALE); - const canvasY = 380 - (forward * SCALE); - - scanPoints.push({ x: canvasX, y: canvasY, ts: Date.now() }); - if (scanPoints.length > MAX_POINTS) scanPoints.shift(); - } -} -""" -content = content.replace('// ---------- Odometry map ----------', radar_js + '\n// ---------- Odometry map ----------') - -# 6. Update `handleTelemetry` to support new fields properly -# Specifically: danger logic (1 vs 0), and Pitch/Roll from ax, ay, az -telemetry_fix = """function handleTelemetry(raw){ - const d = { - tempC: raw.t ?? raw.tempC, - hum: raw.h ?? raw.hum, - gas: raw.gas, - frontCm: raw.dist ?? raw.frontCm ?? 'N/A', - water: raw.water, - x: raw.x ?? 0, - y: raw.y ?? 0, - heading: raw.hd ?? raw.heading ?? 0, - state: (raw.danger === 1) ? 'DANGER' : 'NORMAL', - dangerCause: (raw.danger === 1) ? 'EMERGENCY' : 'NONE', - encL: raw.encL ?? 0, - encR: raw.encR ?? 0 - }; - if (raw.ax !== undefined) { - d.pitch = Math.atan2(-raw.ax, Math.sqrt(raw.ay * raw.ay + raw.az * raw.az)) * 180 / Math.PI; - d.roll = Math.atan2(raw.ay, raw.az) * 180 / Math.PI; - } - latest = d; - - const danger = d.state === 'DANGER'; -""" -content = re.sub(r'function handleTelemetry\(raw\)\{.*?const danger = d\.state === \'DANGER\';', telemetry_fix, content, flags=re.DOTALL) - - -# 7. Update the UI bindings in `handleTelemetry` to show Pitch/Roll instead of Odometry coords if missing -ui_update = """ - document.getElementById('s-pos').textContent = d.pitch !== undefined ? `P:${d.pitch.toFixed(1)}° R:${d.roll.toFixed(1)}°` : `X:${d.x.toFixed(1)} Y:${d.y.toFixed(1)}`; -""" -content = re.sub(r' document\.getElementById\(\'s-pos\'\)\.textContent = `X:\$\{d\.x\.toFixed\(1\)\} Y:\$\{d\.y\.toFixed\(1\)\}`;', ui_update, content) - - -with open("DASHBOARD/dashboard.html", "w") as f: - f.write(content) - From d11185637900596e3f9bac82c319d3c79dec217d Mon Sep 17 00:00:00 2001 From: dipak agrahari Date: Mon, 24 Aug 2026 14:28:14 +0530 Subject: [PATCH 12/13] Update HARDWARE_BOM.md --- docs/HARDWARE_BOM.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/HARDWARE_BOM.md b/docs/HARDWARE_BOM.md index bc28d4e..8ce229c 100644 --- a/docs/HARDWARE_BOM.md +++ b/docs/HARDWARE_BOM.md @@ -24,7 +24,7 @@ All audit fixes are baked in (separate 5V bus, MQ-4 voltage divider, decoupling | # | What to Search | Spec | Qty | ₹ Est. | Why | |---|---|---|---|---|---| -| 6 | TB6612FNG Dual Motor Driver Module | Breakout board (NOT bare chip). Pins: VM, VCC, GND, STBY, AIN1, AIN2, PWMA, AO1, AO2, BIN1, BIN2, PWMB, BO1, BO2 | 1 | 200–350 | Drives both left-pair and right-pair motors. VM rated up to 13.5V (we use 5V) | +| 6 | TB6612FNG Dual Motor Driver Module | Breakout board (NOT bare chip). Pins: VM, VCC, GND, STBY, AIN1, AIN2, PWMA, AO1, AO2, BIN1, BIN2, PWMB, BO1, BO2 | 2 | 400–700 | One H-bridge channel per motor. Two modules provide 4 independent output channels, avoiding two TT motors sharing one channel | ### Sensors @@ -54,8 +54,8 @@ All audit fixes are baked in (separate 5V bus, MQ-4 voltage divider, decoupling | 18 | 10kΩ Resistor ¼W | 10kΩ | 4 | 2–5 each | 1× DHT22 pull-up (skip if 3-pin module), 1× HC-SR04 ECHO divider upper, 1× MQ-4 AO divider upper, 1× right encoder pull-up | | 19 | 15kΩ Resistor ¼W | 15kΩ | 2 | 2–5 each | 1× HC-SR04 ECHO divider lower, 1× MQ-4 AO divider lower | | 20 | 220Ω Resistor ¼W | 220Ω | 3 | 2–5 each | 1× buzzer, 1× red LED, 1× green LED | -| 21 | 470µF Electrolytic Capacitor | 470µF 16V (or 25V) | 1 | 5–10 | Across main 5V bus — absorbs motor current spikes | -| 22 | 100nF Ceramic Capacitor | 0.1µF (code "104") | 2 | 2–5 each | 1× across ESP32 3V3/GND, 1× across TB6612FNG VCC/GND — filters high-freq noise | +| 21 | 470µF Electrolytic Capacitor | 470µF 16V (or 25V) | 2 | 5–10 | One near each TB6612 VM/GND input to absorb motor startup/current spikes | +| 22 | 100nF Ceramic Capacitor | 0.1µF (code "104") | 3 | 2–5 each | 1× across ESP32 3V3/GND, 1× across each TB6612 VCC/GND | > **Tip:** Buy a resistor assortment kit (₹80–120) and a capacitor assortment kit (₹60–100) instead of individual pieces. You'll have spares for mistakes. @@ -115,10 +115,10 @@ On the laptop: | Section | ₹ Estimate | |---|---| -| Rover (ESP32 + Chassis + Sensors + Driver + Actuators + Passives + Caps) | 2,200 – 3,500 | -| Gateway (ESP32 + LCD + LEDs + Resistors + Cable) | 600 – 950 | -| Wiring Supplies (Breadboards + Wires + Tape) | 350 – 550 | -| **TOTAL** | **3,150 – 5,000** | +| Rover (ESP32 + Chassis + Sensors + Driver + Actuators + Passives + Caps) | 2,410 – 3,870 | +| Gateway (ESP32 + LCD + LEDs + Resistors + Cable) | 600–950 | +| Wiring Supplies (Breadboards + Wires + Tape) | 350–550 | +| **TOTAL** | **3,360 – 5,370** | > If you already own a 10000mAh power bank, subtract ₹500–900. @@ -126,11 +126,11 @@ On the laptop: ## PRINTABLE CHECKLIST -``` +```text ROVER: [ ] ESP32 DevKit V1 ×1 [ ] 4WD Chassis Kit (4× TT motors, 4× wheels, acrylic frame) ×1 -[ ] TB6612FNG Motor Driver Module ×1 +[ ] TB6612FNG Motor Driver Module ×2 [ ] DHT22 Sensor Module (3-pin preferred) ×1 [ ] MQ-4 Gas Sensor Module ×1 [ ] HC-SR04 Ultrasonic Sensor ×1 @@ -145,8 +145,8 @@ ROVER: [ ] 10kΩ Resistors ×4 [ ] 15kΩ Resistors ×2 [ ] 220Ω Resistors ×3 -[ ] 470µF Electrolytic Capacitor ×1 -[ ] 100nF Ceramic Capacitors ×2 +[ ] 470µF Electrolytic Capacitor ×2 +[ ] 100nF Ceramic Capacitors ×3 [ ] 10000mAh Power Bank ×1 [ ] Short USB Cable (30cm) ×1 [ ] USB-A Breakout Board ×1 From 9117868c95140b18562875e54a116aed9e425e76 Mon Sep 17 00:00:00 2001 From: Shekh Akib <231936572+shaikhakib12@users.noreply.github.com> Date: Tue, 25 Aug 2026 03:21:51 +0530 Subject: [PATCH 13/13] Revise hardware BOM for USB Type-C and Buck Converter Updated hardware specifications to reflect the use of USB Type-C instead of Micro-USB for ESP32 boards and added a Buck Converter module for voltage regulation. --- docs/HARDWARE_BOM.md | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/docs/HARDWARE_BOM.md b/docs/HARDWARE_BOM.md index 8ce229c..2d1069f 100644 --- a/docs/HARDWARE_BOM.md +++ b/docs/HARDWARE_BOM.md @@ -1,7 +1,7 @@ # DEEPTRACK — Complete Hardware Shopping List Everything you need to buy for both the Rover and the Gateway. -All audit fixes are baked in (separate 5V bus, MQ-4 voltage divider, decoupling caps). +All audit fixes are baked in (separate 5V bus regulated by a buck converter, MQ-4 voltage divider, decoupling caps). Both ESP32 boards use **USB Type-C**, not Micro-USB. > Search on **Robu.in**, **Amazon.in**, **Quartz Components**, or your local electronics shop. > Prices are approximate mid-2026 INR. @@ -14,11 +14,12 @@ All audit fixes are baked in (separate 5V bus, MQ-4 voltage divider, decoupling | # | What to Search | Spec | Qty | ₹ Est. | Why | |---|---|---|---|---|---| -| 1 | ESP32 DevKit V1 (30-pin) | ESP32-WROOM-32, Micro-USB | 1 | 350–500 | Rover MCU. 30-pin fits breadboard with 1 row free on each side | +| 1 | ESP32 DevKit V1 (30-pin) | ESP32-WROOM-32, **USB Type-C** | 1 | 350–500 | Rover MCU. 30-pin fits breadboard with 1 row free on each side | | 2 | 4WD Robot Car Chassis Kit | Acrylic 2-layer, includes 4× TT motors + 4× 65mm wheels + screws + standoffs | 1 kit | 400–650 | If your kit does NOT include motors/wheels, buy 4× "BO Motor 200RPM" (₹40–60 each) and 4× "65mm Robot Wheel" (₹20–30 each) separately | | 3 | 10000mAh Power Bank | 5V output, 2A or higher. USB-A output port | 1 | 500–900 | Must sustain 2A+ continuous draw. Avoid ultra-slim (auto-shutoff on low current). Mi/Ambrane/Syska work well | -| 4 | USB-A to Micro-USB Cable (short) | 30cm data cable | 1 | 50–80 | Power bank → ESP32. Keep it short so it doesn't snag on the chassis | -| 5 | USB-A Breakout Board | Female USB-A socket on a small PCB with screw terminals (5V, GND) | 1 | 30–50 | Cut a spare USB cable OR use this breakout to tap 5V directly from the power bank for the separate 5V bus. See Wiring Guide for details | +| 4 | USB-A to Type-C Cable (short) | 30cm data cable | 1 | 60–100 | Power bank → ESP32 (rover's ESP32 has a Type-C port, not Micro-USB). Keep it short so it doesn't snag on the chassis | +| 5 | USB-A Breakout Board | Female USB-A socket on a small PCB with screw terminals (5V, GND) | 1 | 30–50 | Cut a spare USB cable OR use this breakout to tap 5V directly from the power bank for the separate 5V bus. Feeds into the buck converter below. See Wiring Guide for details | +| 5a | **Buck Converter Module** (LM2596 or MP1584EN) | Adjustable DC-DC step-down, screw terminals or header pins, input up to 24–40V, output adjustable via trimpot | 1 | 60–120 | Regulates the tap from the power bank/battery down to a clean, stable **5V** for the separate 5V bus (motors, servo, MQ-4, HC-SR04). Trim the output to 5.0V with a multimeter **before** connecting any sensor. Sits between the USB-A Breakout Board and the 5V bus | ### Motor Driver @@ -65,13 +66,14 @@ All audit fixes are baked in (separate 5V bus, MQ-4 voltage divider, decoupling | # | What to Search | Spec | Qty | ₹ Est. | Why | |---|---|---|---|---|---| -| 23 | ESP32 DevKit V1 (30-pin) | Same as Rover | 1 | 350–500 | Receives ESP-NOW from Rover, shows on LCD, sends to laptop via USB Serial | +| 23 | ESP32 DevKit V1 (30-pin) | Same as Rover — **USB Type-C** | 1 | 350–500 | Receives ESP-NOW from Rover, shows on LCD, sends to laptop via USB Serial | | 24 | 16×2 I2C LCD Module | LCD1602 with PCF8574 I2C backpack soldered on. 4 pins: VCC, GND, SDA, SCL. Address 0x27 | 1 | 150–220 | Displays live telemetry. Blue backlight / white text. Has a small blue contrast potentiometer on the back — adjust with screwdriver until text is visible | | 25 | 5mm Red LED | Standard | 1 | 5–10 | Danger | | 26 | 5mm Green LED | Standard | 1 | 5–10 | Normal / Link OK | | 27 | 5mm Yellow LED | Standard | 1 | 5–10 | Heartbeat — blinks on each received packet | | 28 | 220Ω Resistor ¼W | 220Ω | 3 | 2–5 each | 1 per LED | -| 29 | USB-A to Micro-USB Cable (1–1.5m) | Standard data cable | 1 | 60–100 | Gateway ESP32 → Laptop. This is your live data link. Dashboard in Chrome connects to this port via Web Serial API | +| 29 | **USB-C to USB-C Cable** (1–1.5m) | Data-capable cable, 1–1.5m | 1 | 100–180 | Gateway ESP32 → Laptop. **No USB-A cable is used here** — the Gateway ESP32 has a Type-C port, so this needs a laptop with a USB-C port. If your laptop only has USB-A ports, get a USB-C-to-USB-A cable instead (see note below) | +| 29a | **Buck Converter Module** (LM2596 or MP1584EN) | Adjustable DC-DC step-down, screw terminals or header pins | 1 | 60–120 | Regulates the Gateway's 5V rail (LCD + LEDs + ESP32) to a clean, trimmed 5.0V — same role as the Rover's buck converter, just on the Gateway side. Sits between the incoming 5V source and the Gateway's 5V bus | --- @@ -91,6 +93,8 @@ All audit fixes are baked in (separate 5V bus, MQ-4 voltage divider, decoupling ## DASHBOARD CONNECTION (How the Cable Works) +> **Cable note:** Both ESP32 boards in this list use **USB Type-C**, not Micro-USB. If your laptop has a USB-A port and no USB-A cable is on hand, buy a **USB-C to USB-A** cable instead of Type-C to Type-C for item #29 — either works as the data + power link, just match it to the port your laptop actually has. + The Gateway ESP32 stays plugged into your laptop via USB cable (#29). This single cable: 1. **Powers the Gateway** — 5V from laptop USB 2. **Carries serial data** — Gateway prints JSON at 115200 baud @@ -115,10 +119,10 @@ On the laptop: | Section | ₹ Estimate | |---|---| -| Rover (ESP32 + Chassis + Sensors + Driver + Actuators + Passives + Caps) | 2,410 – 3,870 | -| Gateway (ESP32 + LCD + LEDs + Resistors + Cable) | 600–950 | +| Rover (ESP32 + Chassis + Sensors + Driver + Actuators + Passives + Caps + Buck Converter) | 2,470 – 3,990 | +| Gateway (ESP32 + LCD + LEDs + Resistors + Cable + Buck Converter) | 760–1,270 | | Wiring Supplies (Breadboards + Wires + Tape) | 350–550 | -| **TOTAL** | **3,360 – 5,370** | +| **TOTAL** | **3,580 – 5,810** | > If you already own a 10000mAh power bank, subtract ₹500–900. @@ -148,17 +152,19 @@ ROVER: [ ] 470µF Electrolytic Capacitor ×2 [ ] 100nF Ceramic Capacitors ×3 [ ] 10000mAh Power Bank ×1 -[ ] Short USB Cable (30cm) ×1 +[ ] Short USB-A to Type-C Cable (30cm) ×1 [ ] USB-A Breakout Board ×1 +[ ] Buck Converter Module (LM2596/MP1584EN) ×1 GATEWAY: -[ ] ESP32 DevKit V1 ×1 +[ ] ESP32 DevKit V1 (USB Type-C) ×1 [ ] 16×2 I2C LCD (PCF8574) ×1 [ ] 5mm Red LED ×1 [ ] 5mm Green LED ×1 [ ] 5mm Yellow LED ×1 [ ] 220Ω Resistors ×3 -[ ] USB Cable (1–1.5m) ×1 +[ ] USB-C to USB-C (or USB-C to USB-A) Cable (1–1.5m) ×1 +[ ] Buck Converter Module (LM2596/MP1584EN) ×1 WIRING: [ ] Breadboards ×2