Procházet zdrojové kódy

Adding Log and port control,fixing docs

Aldino Arya Pramana před 7 měsíci
rodič
revize
8d20123d4f

+ 16 - 1
README.md

@@ -45,4 +45,19 @@ This project focuses on the development of a **Bluetooth Low Energy (LE) Audio T
 ├─ /docs # Documentation and diagrams
 ├─ /test # Program for function Test
 ├─ README.md
-└─ LICENSE
+└─ LICENSE
+
+## How to simulate
+1. Open the src folder.
+2. The ESP32 program is located in src/main.
+3. The Node.js backend program is located in src/web_side/read_serial.js.
+4. The web interface is located in src/web_side/index.html.
+5. Connect the ESP32 to your computer using a Serial-to-USB cable.
+6. Open read_serial.js and adjust the ESP32 port based on the port shown in Device Manager.
+7. Open a terminal and navigate to:
+cd ../ble_auracast/src/web_side
+8. Once the ESP32 is connected, run:
+node read_serial.js
+9. Open index.html in your browser.
+10. Click the "Open Port" button.
+11. After the port is open, all logs and BLE connection information will appear when the ESP32 connects to a BLE client.

binární
docs/Screenshot 2025-11-15 002043.png


binární
docs/Project Docs.docx → docs/ble_auracat_Project_Docs.docx


+ 51 - 38
src/main/main.ino

@@ -12,7 +12,6 @@ uint8_t audioBuffer[AUDIO_FRAME_SIZE];
 int bufferIndex = 0;
 bool readingAudio = false;
 
-// Simpan data scan: MAC -> RSSI/TX/Name
 struct DeviceInfo {
   int RSSI;
   int TXPower;
@@ -20,13 +19,14 @@ struct DeviceInfo {
 };
 std::map<String, DeviceInfo> scannedDevices;
 
-// Simpan device yang connect: connId -> JSON index
 std::map<int, int> connIdToIndex;
 
 BLEServer *pServer = nullptr;
 BLECharacteristic *pCharacteristic = nullptr;
+BLEScan *pBLEScan;
 
 int connectedClients = 0;
+int lastconnectedClients;
 
 StaticJsonDocument<2048> doc;
 JsonArray devices;
@@ -34,34 +34,33 @@ JsonArray devices;
 #define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
 #define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"
 
+// ------------------ CALLBACKS -------------------
 class MyServerCallbacks : public BLEServerCallbacks {
   void onConnect(BLEServer *pServer, ble_gap_conn_desc *desc) override {
     connectedClients++;
-
     if (devices.isNull()) devices = doc.createNestedArray("Devices");
 
     JsonObject device = devices.createNestedObject();
     int connId = pServer->getConnId();
     device["connectedID"] = connId;
     device["connInterval"] = desc->conn_itvl;
+    device["mac"] = BLEAddress(desc->peer_ota_addr).toString();
     device["latency"] = desc->conn_latency;
     device["timeout"] = desc->supervision_timeout;
 
-    // Gunakan MAC dari scannedDevices jika ada RSSI terakhir
-    // Biasanya kita tidak punya MAC real connect, bisa pakai RSSI terkuat
-    if (!scannedDevices.empty()) {
-      auto strongest = scannedDevices.begin();
-      for (auto it = scannedDevices.begin(); it != scannedDevices.end(); ++it) {
-        if (it->second.RSSI > strongest->second.RSSI) strongest = it;
-      }
-      device["mac"] = strongest->first;
-      device["name"] = strongest->second.name;
-      device["RSSI"] = strongest->second.RSSI;
-      device["TXPower"] = strongest->second.TXPower;
-    } else {
-      device["mac"] = "Unknown";
-      device["name"] = "Unknown";
-    }
+    // if (!scannedDevices.empty()) {
+    //   auto strongest = scannedDevices.begin();
+    //   for (auto it = scannedDevices.begin(); it != scannedDevices.end(); ++it) {
+    //     if (it->second.RSSI > strongest->second.RSSI) strongest = it;
+    //   }
+    //   device["mac"] = strongest->first;
+    //   device["name"] = strongest->second.name;
+    //   device["RSSI"] = strongest->second.RSSI;
+    //   device["TXPower"] = strongest->second.TXPower;
+    // } else {
+    //   device["mac"] = "Unknown";
+    //   device["name"] = "Unknown";
+    // }
 
     connIdToIndex[connId] = devices.size() - 1;
 
@@ -70,6 +69,7 @@ class MyServerCallbacks : public BLEServerCallbacks {
 
     serializeJson(doc, Serial);
     Serial.println();
+    BLEDevice::startAdvertising();
   }
 
   void onDisconnect(BLEServer *pServer) override {
@@ -107,19 +107,15 @@ class MyAdvertisedDeviceCallbacks : public BLEAdvertisedDeviceCallbacks {
   void onResult(BLEAdvertisedDevice advertisedDevice) override {
     Serial.printf("Advertised Device: %s \n", advertisedDevice.toString().c_str());
     String mac = advertisedDevice.getAddress().toString();
-    // String name = advertisedDevice.getName();
     String name = advertisedDevice.haveName() ? advertisedDevice.getName() : "Unknown";
     int rssi = advertisedDevice.getRSSI();
     int txPower = advertisedDevice.getTXPower();
 
-    // Simpan ke scannedDevices
     scannedDevices[mac] = { rssi, txPower, name };
 
     for (auto &pair : connIdToIndex) {
       int index = pair.second;
       JsonObject device = devices[index];
-      // Di sini kita tidak bisa cocokkan MAC karena connect MAC random,
-      // tapi bisa update semua RSSI terakhir dari scannedDevices
       if (scannedDevices.count(mac)) {
         device["Name"] = scannedDevices[mac].name;
         device["RSSI"] = scannedDevices[mac].RSSI;
@@ -129,22 +125,19 @@ class MyAdvertisedDeviceCallbacks : public BLEAdvertisedDeviceCallbacks {
   }
 };
 
-BLEScan *pBLEScan;
-
+// ------------------ SETUP -------------------
 void setup() {
   Serial.begin(115200);
   pinMode(LED_BUILTIN, OUTPUT);
 
   BLEDevice::init("ESP32");
 
-  // Setup scan
   pBLEScan = BLEDevice::getScan();
   pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
   pBLEScan->setActiveScan(true);
   pBLEScan->setInterval(100);
   pBLEScan->setWindow(99);
 
-  // Setup server
   pServer = BLEDevice::createServer();
   pServer->setCallbacks(new MyServerCallbacks());
 
@@ -162,25 +155,45 @@ void setup() {
   BLEDevice::startAdvertising();
 }
 
-unsigned long lastScanTime = 0;
-const unsigned long scanInterval = 1000;  // 1 detik
+// ------------------ LOOP -------------------
+unsigned long lastJsonTime = 0;
+const unsigned long scanInterval = 1000;
 
 void loop() {
   digitalWrite(LED_BUILTIN, connectedClients > 0 ? HIGH : LOW);
 
-  // Scan BLE setiap 1 detik
-  // if (millis() - lastScanTime > scanInterval) {
-  //   lastScanTime = millis();
-  //   pBLEScan->start(0, nullptr, false);  // non-blocking scan
+  // kirim update JSON tiap 1 detik
+  // if (millis() - lastJsonTime > scanInterval) {
+  //   if (connectedClients == 0) {
+  //     doc["Status"] = "NO_CONNECTED";
+  //   }
+  //   lastJsonTime = millis();
+  //   doc["connectedClients"] = connectedClients;
+  //   // serializeJsonPretty(doc, Serial);
+  //   serializeJson(doc, Serial);
+  //   Serial.println();
   // }
 
-  pBLEScan->start(2, false);  // non-blocking scan
-  // Kirim JSON update ke Serial
-  static unsigned long lastJsonTime = 0;
-  if (millis() - lastJsonTime > scanInterval) {
-    lastJsonTime = millis();
-    doc["connectedClients"] = connectedClients;
+  if (lastconnectedClients != connectedClients && connectedClients == 0) {
+    doc["Status"] = "NO_CONNECTED";
     serializeJson(doc, Serial);
     Serial.println();
+    lastconnectedClients = connectedClients;
+  }
+
+  // cek input dari serial untuk disconnect
+  if (Serial.available()) {
+    String input = Serial.readStringUntil('\n');
+    input.trim();
+
+    if (input.startsWith("DISCONNECT")) {
+      int id = input.substring(10).toInt();
+      if (id > 0) {
+        Serial.printf("Disconnecting client %d...\n", id);
+        pServer->disconnect(id);
+      } else {
+        Serial.println("Format salah! Gunakan: DISCONNECT <connId>");
+      }
+    }
   }
 }

+ 133 - 52
src/web_side/index.html

@@ -6,6 +6,28 @@
   <meta name="viewport" content="width=device-width, initial-scale=1.0">
   <title>BLE Device Monitor + Serial</title>
   <style>
+    #logPanel {
+      width: 300px;
+      height: 90vh;
+      overflow-y: auto;
+      background: white;
+      padding: 15px;
+      border-radius: 10px;
+      box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
+      position: fixed;
+      left: 20px;
+      top: 20px;
+    }
+
+    #logPanel h2 {
+      margin-top: 0;
+    }
+
+    #logContent p {
+      margin: 4px 0;
+      font-size: 14px;
+    }
+
     body {
       font-family: Arial;
       padding: 20px;
@@ -67,12 +89,21 @@
 </head>
 
 <body>
+  <div id="logPanel">
+    <h2>Connection Log</h2>
+    <div id="logContent"></div>
+  </div>
+
   <h1>BLE Device Monitor</h1>
 
   <div id="controls">
-    <button id="selectPortBtn">Select Arduino Port</button>
-    <button id="openPortBtn" disabled>Open Port</button>
-    <button id="micBtn">🎤 Start Mic</button>
+
+    <button id="openPortBtn">Open Port</button>
+    <button id="closePortBtn">Close Port</button>
+    <p id="serialStatus" style="text-align:center; font-weight:bold; color:gray;">
+      Serial Status: Not Connected
+    </p>
+
   </div>
 
   <div id="deviceContainer"></div>
@@ -88,43 +119,119 @@
     const micBtn = document.getElementById('micBtn');
 
     const ws = new WebSocket("ws://localhost:8080");
+    const statusLabel = document.getElementById("serialStatus");
+
+    document.getElementById("openPortBtn").onclick = () => {
+      ws.send(JSON.stringify({ type: "OPEN_SERIAL" }));
+
+      statusLabel.textContent = "Serial Status: Connecting...";
+      statusLabel.style.color = "orange";
+
+      console.log("Request open serial sent to Node.js");
+    };
+
+    document.getElementById("closePortBtn").onclick = () => {
+      ws.send(JSON.stringify({ type: "CLOSE_SERIAL" }));
+
+      console.log("Request close serial sent to Node.js");
+    };
+
+
+    function updateDeviceCards(devices) {
+      const deviceContainer = document.getElementById('deviceContainer');
+
+      if (!devices || devices.length === 0) {
+        deviceContainer.innerHTML = '';
+        return;
+      }
+
+      devices.forEach(d => {
+        if (document.getElementById(`device-${d.connectedID}`)) return; // sudah ada
+
+        const card = document.createElement('div');
+        card.className = 'deviceCard';
+        card.id = `device-${d.connectedID}`;
+
+        const title = document.createElement('h3');
+        title.textContent = `Device ${d.connectedID}`;
+        card.appendChild(title);
+
+        const btn = document.createElement('button');
+        btn.textContent = 'Disconnect';
+        btn.onclick = () => {
+          ws.send(JSON.stringify({ type: "DISCONNECT", connID: d.connectedID }));
+        };
+        card.appendChild(btn);
+
+        const dataDiv = document.createElement('div');
+        dataDiv.className = 'deviceData';
+        dataDiv.innerHTML = `
+      <p><b>MAC:</b> ${d.mac}</p>
+      <p><b>ConnID:</b> ${d.connectedID}</p>
+      <p><b>Interval:</b> ${d.connInterval}</p>
+      <p><b>Latency:</b> ${d.latency}</p>
+      <p><b>Timeout:</b> ${d.timeout}</p>
+    `;
+        card.appendChild(dataDiv);
+
+        deviceContainer.appendChild(card);
+      });
+    }
 
     ws.onmessage = (event) => {
       const data = JSON.parse(event.data);
 
       if (data.Status === 'NEW_CONNECT') {
-        data.Devices.forEach(d => addDevice(d));
+        updateDeviceCards(data.Devices);
+        data.Devices.forEach(d => addLog(`Device ${d.connectedID} connected (MAC: ${d.mac})`));
+
       }
-      if (data.Status === 'DEVICE_DISCONNECTED') {
-        data.Devices.forEach(d => removeDevice(d.connectedID));
+
+      if (data.Status === 'DEVICE_DISCONNECTED' || data.Status === 'NO_CONNECTED') {
+        updateDeviceCards(data.Devices); 
+        data.Devices.forEach(d => addLog(`Device ${d.connectedID} disconnected`));
+
       }
-    };
 
-    // Pilih port Arduino
-    selectPortBtn.onclick = async () => {
-      try {
-        port = await navigator.serial.requestPort();
-        console.log('Port selected:', port);
-        openPortBtn.disabled = false;
-      } catch (err) {
-        console.error('Port selection cancelled', err);
+      if (data.Status === "SERIAL_OPENING") {
+        statusLabel.textContent = "Serial Status: Connecting...";
+        statusLabel.style.color = "orange";
       }
-    };
 
-    // Buka port dan mulai membaca
-    openPortBtn.onclick = async () => {
-      if (!port) return;
-      await port.open({ baudRate: 115200 });
-      console.log('Port opened');
+      if (data.Status === "SERIAL_OPENED") {
+        statusLabel.textContent = "Serial Status: Connected";
+        statusLabel.style.color = "green";
+      }
+
+      if (data.Status === "SERIAL_ALREADY_OPEN") {
+        statusLabel.textContent = "Serial Status: Already Connected";
+        statusLabel.style.color = "blue";
+      }
 
-      const textDecoder = new TextDecoderStream();
-      const readableStreamClosed = port.readable.pipeTo(textDecoder.writable);
-      reader = textDecoder.readable.getReader();
-      keepReading = true;
+      if (data.Status === "SERIAL_ERROR") {
+        statusLabel.textContent = "Serial Error: " + data.message;
+        statusLabel.style.color = "red";
+      }
+
+      if (data.Status === "SERIAL_CLOSED") {
+        statusLabel.textContent = "Serial Status: Not COnneccted";
+        statusLabel.style.color = "gray";
 
-      readLoop();
+      }
     };
 
+    function addLog(text) {
+      const logContent = document.getElementById("logContent");
+      const p = document.createElement("p");
+      const time = new Date().toLocaleTimeString();
+
+      p.textContent = `[${time}] ${text}`;
+      logContent.appendChild(p);
+
+      logContent.scrollTop = logContent.scrollHeight;
+    }
+
+
     async function readLoop() {
       while (keepReading) {
         try {
@@ -138,23 +245,6 @@
       }
     }
 
-    // Parse JSON dari Arduino
-    function parseSerialData(line) {
-      try {
-        const data = JSON.parse(line);
-        if (data.Status === 'NEW_CONNECT') {
-          data.Devices.forEach(d => addDevice(d));
-        }
-        if (data.Status === 'DEVICE_DISCONNECTED') {
-          // bisa hapus semua device yang disconnect
-          data.Devices.forEach(d => removeDevice(d.connectedID));
-        }
-      } catch (err) {
-        console.warn('Invalid JSON:', line);
-      }
-    }
-
-    // Tambah device card
     function addDevice(device) {
       if (document.getElementById(`device-${device.connectedID}`)) return;
 
@@ -198,15 +288,6 @@
       if (card) card.remove();
     }
 
-    // Mic input placeholder
-    let micStream;
-    micBtn.onclick = async () => {
-      if (!micStream) {
-        micStream = await navigator.mediaDevices.getUserMedia({ audio: true });
-        console.log('Mic started', micStream);
-        micBtn.textContent = '🎤 Mic Active';
-      }
-    };
   </script>
 </body>
 

+ 107 - 18
src/web_side/read_serial.js

@@ -6,6 +6,9 @@ import { ReadlineParser } from "@serialport/parser-readline";
 const app = express();
 const portHTTP = 8080;
 
+let serial = null;
+let parser = null;
+
 // === SERVE FRONTEND ===
 app.use(express.static("public"));
 const server = app.listen(portHTTP, () =>
@@ -16,34 +19,118 @@ const server = app.listen(portHTTP, () =>
 const wss = new WebSocketServer({ server });
 
 // === SERIAL ===
-const serial = new SerialPort({ path: "COM8", baudRate: 115200 });
-const parser = serial.pipe(new ReadlineParser({ delimiter: "\n" }));
-
-// Saat ESP32 kirim JSON lewat Serial
-parser.on("data", (line) => {
-  console.log("📥 Dari ESP32:", line);
-  try {
-    const json = JSON.parse(line);
-    // kirim ke semua client WebSocket
-    wss.clients.forEach((ws) => {
-      if (ws.readyState === ws.OPEN) ws.send(JSON.stringify(json));
-    });
-  } catch (e) {
-    console.log("Bukan JSON valid:", line);
+function openSerialPort() {
+  if (serial) {
+    console.log("Serial already opened.");
+    return;
   }
-});
+  broadcast({ Status: "SERIAL_OPENING" });
+
+  serial = new SerialPort({ path: "COM8", baudRate: 115200 });
+
+  serial.on("open", () => {
+    console.log("Serial port opened");
+    broadcast({ Status: "SERIAL_OPENED" });
+  });
+
+  serial.on("error", (err) => {
+    console.error("Serial error:", err);
+    broadcast({ Status: "SERIAL_ERROR", message: err.message });
+  });
+
+  parser = serial.pipe(new ReadlineParser({ delimiter: "\n" }));
+
+  console.log("Serial port opened.");
+
+  parser.on("data", (line) => {
+    console.log("📥 Dari ESP32:", line);
+
+    try {
+      const json = JSON.parse(line);
+      broadcast(json);
+    } catch (e) {
+      console.log("Bukan JSON valid:", line);
+    }
+  });
+}
+
+function closeSerial() {
+  if (!serial) {
+    console.log("Serial is not opened.");
+    broadcast({ Status: "SERIAL_NOT_OPEN" });
+    return;
+  }
+
+  if (!serial.isOpen) {
+    console.log("Serial already closed.");
+    broadcast({ Status: "SERIAL_ALREADY_CLOSED" });
+    return;
+  }
+
+  serial.close((err) => {
+    if (err) {
+      console.log("Error closing serial:", err.message);
+      broadcast({ Status: "SERIAL_CLOSE_ERROR", message: err.message });
+    } else {
+      console.log("Serial closed.");
+      broadcast({ Status: "SERIAL_CLOSED" });
+
+      // bersihkan parser & object
+      serial = null;
+      parser = null;
+    }
+  });
+}
+
+function broadcast(data) {
+  const msg = JSON.stringify(data);
+  wss.clients.forEach(ws => {
+    if (ws.readyState === ws.OPEN) ws.send(msg);
+  });
+}
+
+let connectionLogs = [];
+
+function addLog(message) {
+  const time = new Date().toLocaleTimeString();
+  const logEntry = `[${time}] ${message}`;
+  connectionLogs.push(logEntry);
+
+  // broadcast log baru
+  const json = JSON.stringify({
+    Status: "LOG_EVENT",
+    log: logEntry
+  });
+
+  wss.clients.forEach((ws) => {
+    if (ws.readyState === ws.OPEN) ws.send(json);
+  });
+}
 
 // Saat web kirim command (misal: delete)
 wss.on("connection", (ws) => {
+  console.log('Browser connected via WebSocket')
+  addLog("Browser connected via WebSocket");
+
+  ws.send(JSON.stringify({
+    Status: "LOG_HISTORY",
+    logs: connectionLogs
+  }));
+
   ws.on("message", (msg) => {
     const command = JSON.parse(msg);
     console.log("📤 Dari Browser:", command);
     // console.log(command.type);
-
-
+    if (command.type === "OPEN_SERIAL") {
+      openSerialPort();
+    }
+    if (command.type === "CLOSE_SERIAL") {
+      closeSerial();
+    }
     if (command.type === "DISCONNECT") {
-      const data = `DISCONNECT:${command.connID}\n`;
+      const data = `DISCONNECT ${command.connID}\n`;
       console.log("DISCONNECTING...", data);
+      addLog(`Browser requested DISCONNECT for ID ${command.connID}`);
 
       // Kirim tiap karakter satu per satu
       for (let i = 0; i < data.length; i++) {
@@ -60,3 +147,5 @@ wss.on("connection", (ws) => {
 
   });
 });
+
+