Repository: upsidedownlabs/npg-lite-firmware
Pull request #3: Add car firmware for BLE server and client to enable control via EMG and EEG
Author: login: Amanmahe
Created: 2025-05-12T10:17:19Z | Merged: 2025-05-12T13:13:14Z

This PR adds firmware support for a car control system using BLE (Bluetooth Low Energy), enabling both server and client functionality. The system integrates EMG (Electromyography) and EEG (Electroencephalography) signal processing to enable brain- and muscle-activity-based control of the car.

@lorforlinux, please review and merge. Let me know if any changes are needed.

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Introduced a BLE-enabled Brain-Computer Interface (BCI) remote system that uses EEG and EMG signals to control a car's movement.
  - Added a BLE client for the car that receives commands and translates them into motor actions, enabling remote control via BLE notifications.

- **Documentation**
  - Updated the README with new example entries for both the BLE BCI remote and BLE client car control sketches.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/BLE-BCI-Car-Remote/BLE-BCI-Car-Remote.ino b/BLE-BCI-Car-Remote/BLE-BCI-Car-Remote.ino
new file mode 100644
index 0000000..38e5a72
--- /dev/null
+++ b/BLE-BCI-Car-Remote/BLE-BCI-Car-Remote.ino
@@ -0,0 +1,488 @@
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+// Copyright (c) 2025 Aman Maheshwari - Aman@upsidedownlabs.tech
+// Copyright (c) 2025 Upside Down Labs - contact@upsidedownlabs.tech
+
+// At Upside Down Labs, we create open-source DIY neuroscience hardware and software.
+// Our mission is to make neuroscience affordable and accessible for everyone.
+// By supporting us with your purchase, you help spread innovation and open science.
+// Thank you for being part of this journey with us!
+
+/*
+ This example is adapted from the client and server code provided by MoThunderz
+ Firmware: https://github.com/mo-thunderz/Esp32BlePart2
+ YouTube video: https://www.youtube.com/watch?v=s3yoZa6kzus
+*/
+
+#include <BLEDevice.h>
+#include <BLEServer.h>
+#include <BLEUtils.h>
+#include <BLE2902.h>
+#include <Arduino.h>
+#include "esp_dsp.h"
+
+// constants won't change. They're used here to set pin numbers:
+const int buttonPin = 9;  // the number of the pushbutton pin
+const int ledPin = 7;     // the number of the LED pin
+
+// Variables will change:
+int ledState = HIGH;        // the current state of the output pin
+uint32_t buttonState;       // the current reading from the input pin
+int lastButtonState = LOW;  // the previous reading from the input pin
+uint32_t bci_val = 0;       // EEG-based control value (0=stop, 3=forward)
+uint32_t emg1_val1 = 0;     // Left EMG control value (0=inactive, 1=left turn)
+uint32_t emg2_val2 = 0;     // Right EMG control value (0=inactive, 2=right turn)
+uint32_t bootback_val = 4;  // Button toggle value (4=toggle state)
+
+// the following variables are unsigned longs because the time, measured in
+// milliseconds, will quickly become a bigger number than can be stored in an int.
+unsigned long lastDebounceTime = 0;  // the last time the output pin was toggled
+unsigned long debounceDelay = 50;    // the debounce time; increase if the output flickers
+
+// Initialize all pointers
+BLEServer *pServer = NULL;                    // Pointer to the server
+BLECharacteristic *pCharacteristic_1 = NULL;  // Pointer to Characteristic 1
+BLECharacteristic *pCharacteristic_2 = NULL;  // Pointer to Characteristic 2
+BLEDescriptor *pDescr_1;                      // Pointer to Descriptor of Characteristic 1
+BLE2902 *pBLE2902_1;                          // Pointer to BLE2902 of Characteristic 1
+BLE2902 *pBLE2902_2;                          // Pointer to BLE2902 of Characteristic 2
+
+// Some variables to keep track on device connected
+bool deviceConnected = false;
+bool oldDeviceConnected = false;
+
+// Variable that will continuously be increased and written to the client
+uint32_t value = 0;
+
+// See the following for generating UUIDs:
+// https://www.uuidgenerator.net/
+// UUIDs used in this example:
+#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
+#define CHARACTERISTIC_UUID_1 "beb5483e-36e1-4688-b7f5-ea07361b26a8"
+#define CHARACTERISTIC_UUID_2 "1c95d5e3-d8f7-413a-bf3d-7a2e5d7be87e"
+
+// Callback function that is called whenever a client is connected or disconnected
+class MyServerCallbacks : public BLEServerCallbacks {
+  void onConnect(BLEServer *pServer) {
+    deviceConnected = true;
+  };
+
+  void onDisconnect(BLEServer *pServer) {
+    deviceConnected = false;
+  }
+};
+
+// ----------------- USER CONFIGURATION -----------------
+#define SAMPLE_RATE 512  // samples per second
+#define FFT_SIZE 512     // must be a power of two
+#define BAUD_RATE 115200
+#define INPUT_PIN1 A0  // EEG input pin
+#define INPUT_PIN2 A1  // Left hand EMG input pin
+#define INPUT_PIN3 A2  // Right hand EMG input pin
+
+// EEG bands (Hz)
+#define DELTA_LOW 0.5f
+#define DELTA_HIGH 4.0f
+#define THETA_LOW 4.0f
+#define THETA_HIGH 8.0f
+#define ALPHA_LOW 8.0f
+#define ALPHA_HIGH 13.0f
+#define BETA_LOW 13.0f
+#define BETA_HIGH 30.0f
+#define GAMMA_LOW 30.0f
+#define GAMMA_HIGH 45.0f
+
+#define SMOOTHING_FACTOR 0.63f
+#define EPS 1e-7f
+
+// ----------------- BUFFERS & TYPES -----------------
+float inputBuffer[FFT_SIZE];
+float powerSpectrum[FFT_SIZE / 2];
+
+// For two-real FFT trick
+__attribute__((aligned(16))) float y_cf[FFT_SIZE * 2];
+float *y1_cf = &y_cf[0];
+
+typedef struct
+{
+  float delta, theta, alpha, beta, gamma, total;
+} BandpowerResults;
+
+BandpowerResults smoothedPowers = { 0 };
+
+// ----------------- NOTCH FILTER CLASSES -----------------
+// For 50Hz AC noise removal
+// Band-Stop Butterworth IIR digital filter, generated using filter_gen.py.
+// Sampling rate: 500.0 Hz, frequency: [48.0, 52.0] Hz.
+// Filter is order 2, implemented as second-order sections (biquads).
+// Reference: https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.butter.html
+class NotchFilter {
+private:
+  struct BiquadState {
+    float z1 = 0;
+    float z2 = 0;
+  };
+
+  BiquadState state1;
+  BiquadState state2;
+
+public:
+  float process(float input) {
+    float output = input;
+
+    // First biquad section
+    float x = output - (-1.56858163f * state1.z1) - (0.96424138f * state1.z2);
+    output = 0.96508099f * x + (-1.56202714f * state1.z1) + (0.96508099f * state1.z2);
+    state1.z2 = state1.z1;
+    state1.z1 = x;
+
+    // Second biquad section
+    x = output - (-1.61100358f * state2.z1) - (0.96592171f * state2.z2);
+    output = 1.00000000f * x + (-1.61854514f * state2.z1) + (1.00000000f * state2.z2);
+    state2.z2 = state2.z1;
+    state2.z1 = x;
+
+    return output;
+  }
+
+  void reset() {
+    state1.z1 = state1.z2 = 0;
+    state2.z1 = state2.z2 = 0;
+  }
+};
+
+// ----------------- EMG FILTER CLASSES -----------------
+// High-Pass Butterworth IIR digital filter, generated using filter_gen.py.
+// Sampling rate: 500.0 Hz, frequency: 70.0 Hz.
+// Filter is order 2, implemented as second-order sections (biquads).
+// Reference: https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.butter.html
+class EMGHighPassFilter {
+private:
+  // Filter state for a single channel
+  double z1 = 0.0;
+  double z2 = 0.0;
+
+public:
+  // Process a single sample
+  double process(double input) {
+    const double x = input - -0.82523238 * z1 - 0.29463653 * z2;
+    const double output = 0.52996723 * x + -1.05993445 * z1 + 0.52996723 * z2;
+
+    // Update state
+    z2 = z1;
+    z1 = x;
+
+    return output;
+  }
+
+  // Reset filter state
+  void reset() {
+    z1 = 0.0;
+    z2 = 0.0;
+  }
+};
+
+// Class to calculate EMG Envelope
+class EnvelopeFilter {
+private:
+  std::vector<double> circularBuffer;NOTCH
+  double sum = 0.0;
+  int dataIndex = 0;
+  const int bufferSize;
+
+public:
+  EnvelopeFilter(int bufferSize)
+    : bufferSize(bufferSize) {
+    circularBuffer.resize(bufferSize, 0.0);
+  }
+
+  double getEnvelope(double absEmg) {
+    sum -= circularBuffer[dataIndex];
+    sum += absEmg;
+    circularBuffer[dataIndex] = absEmg;
+    dataIndex = (dataIndex + 1) % bufferSize;
+    return (sum / bufferSize);
+  }
+};
+
+// NEW: Low-pass filter for EEG signals
+float EEGFilter(float input) {
+  float output = input;
+  {
+    static float z1 = 0, z2 = 0;
+    float x = output - -1.22465158 * z1 - 0.45044543 * z2;
+    output = 0.05644846 * x + 0.11289692 * z1 + 0.05644846 * z2;
+    z2 = z1;
+    z1 = x;
+  }
+  return output;
+}
+NotchFilter filters[3];              // Notch filters for all 3 input channels
+EMGHighPassFilter emgfilters[2];     // High-pass filters for EMG channels
+EnvelopeFilter Envelopefilter1(16);  // Envelope detector for left EMG
+EnvelopeFilter Envelopefilter2(16);  // Envelope detector for right EMG
+
+// ----------------- BANDPOWER & SMOOTHING -----------------
+BandpowerResults calculateBandpower(float *ps, float binRes, int halfSize) {
+  BandpowerResults r = { 0 };
+  for (int i = 1; i < halfSize; i++) {
+    float freq = i * binRes;
+    float p = ps[i];
+    r.total += p;
+    if (freq >= DELTA_LOW && freq < DELTA_HIGH)
+      r.delta += p;
+    else if (freq >= THETA_LOW && freq < THETA_HIGH)
+      r.theta += p;
+    else if (freq >= ALPHA_LOW && freq < ALPHA_HIGH)
+      r.alpha += p;
+    else if (freq >= BETA_LOW && freq < BETA_HIGH)
+      r.beta += p;
+    else if (freq >= GAMMA_LOW && freq < GAMMA_HIGH)
+      r.gamma += p;
+  }
+  return r;
+}
+
+void smoothBandpower(const BandpowerResults *raw, BandpowerResults *s) {
+  s->delta = SMOOTHING_FACTOR * raw->delta + (1 - SMOOTHING_FACTOR) * s->delta;
+  s->theta = SMOOTHING_FACTOR * raw->theta + (1 - SMOOTHING_FACTOR) * s->theta;
+  s->alpha = SMOOTHING_FACTOR * raw->alpha + (1 - SMOOTHING_FACTOR) * s->alpha;
+  s->beta = SMOOTHING_FACTOR * raw->beta + (1 - SMOOTHING_FACTOR) * s->beta;
+  s->gamma = SMOOTHING_FACTOR * raw->gamma + (1 - SMOOTHING_FACTOR) * s->gamma;
+  s->total = SMOOTHING_FACTOR * raw->total + (1 - SMOOTHING_FACTOR) * s->total;
+}
+
+// ----------------- DSP FFT SETUP -----------------
+void initFFT() {
+  // initialize esp-dsp real-FFT (two-real trick)
+  esp_err_t err = dsps_fft2r_init_fc32(NULL, FFT_SIZE);
+  if (err != ESP_OK) {
+    Serial.println("FFT init failed");
+    while (1)
+      delay(10);
+  }
+}
+
+// ----------------- FFT + BANDPOWER + PEAK -----------------
+void processFFT() {
+  // pack real→complex: real=inputBuffer, imag=0
+  for (int i = 0; i < FFT_SIZE; i++) {
+    y_cf[2 * i] = inputBuffer[i];
+    y_cf[2 * i + 1] = 0.0f;
+  }
+
+  // FFT
+  dsps_fft2r_fc32(y_cf, FFT_SIZE);
+  dsps_bit_rev_fc32(y_cf, FFT_SIZE);
+  dsps_cplx2reC_fc32(y_cf, FFT_SIZE);
+
+  // magnitude² spectrum
+  int half = FFT_SIZE / 2;
+  for (int i = 0; i < half; i++) {
+    float re = y1_cf[2 * i];
+    float im = y1_cf[2 * i + 1];
+    powerSpectrum[i] = re * re + im * im;
+  }
+
+  // detect peak bin (skip i=0)
+  int maxIdx = 1;
+  float maxP = powerSpectrum[1];
+  for (int i = 2; i < half; i++) {
+    if (powerSpectrum[i] > maxP) {
+      maxP = powerSpectrum[i];
+      maxIdx = i;
+    }
+  }
+  float binRes = float(SAMPLE_RATE) / FFT_SIZE;
+  float peakHz = maxIdx * binRes;
+
+  // bandpower & smoothing
+  BandpowerResults raw = calculateBandpower(powerSpectrum, binRes, half);
+  smoothBandpower(&raw, &smoothedPowers);
+  float T = smoothedPowers.total + EPS;
+  Serial.println(((smoothedPowers.beta / T) * 100));  // for debugging purpose only
+
+  // If the power exceeds the threshold (set as 2% of the total power), the threshold value can be adjusted based on your beta parameters.
+  if (((smoothedPowers.beta / T) * 100) > 2) {
+    bci_val = 3;
+    Serial.println("send 3");
+    pCharacteristic_1->setValue(bci_val);  // for forward moving car
+    pCharacteristic_1->notify();
+    digitalWrite(7, HIGH);  // Visual feedback
+  } else {
+    bci_val = 0;
+    pCharacteristic_1->setValue(bci_val);
+    pCharacteristic_1->notify();
+    digitalWrite(7, LOW);
+  }
+}
+
+void setup() {
+  Serial.begin(BAUD_RATE);
+  while (!Serial) {
+    delay(1);
+  }
+  pinMode(INPUT_PIN1, INPUT);
+  pinMode(INPUT_PIN2, INPUT);
+  pinMode(INPUT_PIN3, INPUT);
+
+  pinMode(7, OUTPUT);
+
+  initFFT();
+  pinMode(buttonPin, INPUT_PULLUP);
+  pinMode(ledPin, OUTPUT);
+
+  // set initial LED state
+  digitalWrite(ledPin, ledState);
+  // Create the BLE Device
+  BLEDevice::init("ESP32");
+
+  // Create the BLE Server
+  pServer = BLEDevice::createServer();
+  pServer->setCallbacks(new MyServerCallbacks());
+
+  // Create the BLE Service
+  BLEService *pService = pServer->createService(SERVICE_UUID);
+
+  // Create a BLE Characteristic
+  pCharacteristic_1 = pService->createCharacteristic(
+    CHARACTERISTIC_UUID_1,
+    BLECharacteristic::PROPERTY_NOTIFY);
+
+  pCharacteristic_2 = pService->createCharacteristic(
+    CHARACTERISTIC_UUID_2,
+    BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_NOTIFY);
+
+  // Create a BLE Descriptor
+  pDescr_1 = new BLEDescriptor((uint16_t)0x2901);
+  pDescr_1->setValue("A very interesting variable");
+  pCharacteristic_1->addDescriptor(pDescr_1);
+
+  // Add the BLE2902 Descriptor because we are using "PROPERTY_NOTIFY"
+  pBLE2902_1 = new BLE2902();
+  pBLE2902_1->setNotifications(true);
+  pCharacteristic_1->addDescriptor(pBLE2902_1);
+
+  pBLE2902_2 = new BLE2902();
+  pBLE2902_2->setNotifications(true);
+  pCharacteristic_2->addDescriptor(pBLE2902_2);
+
+  // Start the service
+  pService->start();
+
+  // Start advertising
+  BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
+  pAdvertising->addServiceUUID(SERVICE_UUID);
+  pAdvertising->setScanResponse(false);
+  pAdvertising->setMinPreferred(0x0);  // set value to 0x00 to not advertise this parameter
+  BLEDevice::startAdvertising();
+  Serial.println("Waiting a client connection to notify...");
+}
+
+void loop() {
+  static uint16_t idx = 0;
+  static unsigned long lastMicros = micros();
+  unsigned long now = micros(), dt = now - lastMicros;
+  lastMicros = now;
+
+  static long timer = 0;
+  timer -= dt;
+  if (timer <= 0) {
+    timer += 1000000L / SAMPLE_RATE;
+    int raw1 = analogRead(INPUT_PIN1);
+    int raw2 = analogRead(INPUT_PIN2);
+    int raw3 = analogRead(INPUT_PIN3);
+
+    float filteeg = EEGFilter(filters[0].process(raw1));
+    float filtemg1 = emgfilters[0].process(filters[1].process(raw2));
+    float filtemg2 = emgfilters[1].process(filters[2].process(raw3));
+    inputBuffer[idx++] = filteeg;
+
+    float env1 = Envelopefilter1.getEnvelope(abs(filtemg1));
+    float env2 = Envelopefilter2.getEnvelope(abs(filtemg2));
+
+    // If `env1` exceeds 150, trigger left turn command (send value 1).the threshold value can be adjusted based on your emg parameters.
+    if (env1 > 150) {
+      emg1_val1 = 1;
+      Serial.println("sent 1");                // for debugging purpose only
+      pCharacteristic_1->setValue(emg1_val1);  // for left turn car
+      pCharacteristic_1->notify();
+    }
+    // If `env2` exceeds 150, trigger right turn command (send value 2).the threshold value can be adjusted based on your emg parameters.
+    else if (env2 > 150) {
+      emg2_val2 = 2;
+      Serial.println("sent 2");                // for debugging purpose only
+      pCharacteristic_1->setValue(emg2_val2);  // for right turn car
+      pCharacteristic_1->notify();
+    }
+  }
+
+  if (idx >= FFT_SIZE) {
+    processFFT();
+    idx = 0;
+  }
+
+  // read the state of the switch into a local variable:
+  int reading = digitalRead(buttonPin);
+
+  // check to see if you just pressed the button
+  // (i.e. the input went from LOW to HIGH), and you've waited long enough
+  // since the last press to ignore any noise:
+
+  // If the switch changed, due to noise or pressing:
+  if (reading != lastButtonState) {
+    // reset the debouncing timer
+    lastDebounceTime = millis();
+  }
+
+  if ((millis() - lastDebounceTime) > debounceDelay) {
+    // whatever the reading is at, it's been there for longer than the debounce
+    // delay, so take it as the actual current state:
+
+    // if the button state has changed:
+    if (reading != buttonState) {
+      buttonState = reading;
+
+      // only toggle the LED if the new button state is HIGH
+      if (buttonState == HIGH) {
+        ledState = !ledState;
+        bootback_val = (bootback_val == 4) ? 0 : 4;
+        Serial.println(bootback_val);
+        if (deviceConnected) {
+          pCharacteristic_1->setValue(bootback_val);
+          pCharacteristic_1->notify();
+        }
+      }
+    }
+  }
+
+  // Set the LED State
+  digitalWrite(ledPin, ledState);
+
+  // The code below keeps the connection status up-to-date:
+  // Disconnecting
+  if (!deviceConnected && oldDeviceConnected) {
+    delay(500);                   // give the bluetooth stack the chance to get things ready
+    pServer->startAdvertising();  // restart advertising
+    Serial.println("start advertising");
+    oldDeviceConnected = deviceConnected;
+  }
+  // Connecting
+  if (deviceConnected && !oldDeviceConnected) {
+    // do stuff here on connecting
+    oldDeviceConnected = deviceConnected;
+  }
+  // Save the reading. Next time through the loop, it'll be the lastButtonState:
+  lastButtonState = reading;
+}
diff --git a/BLE-BCI-Car/BLE-BCI-Car.ino b/BLE-BCI-Car/BLE-BCI-Car.ino
new file mode 100644
index 0000000..62fd482
--- /dev/null
+++ b/BLE-BCI-Car/BLE-BCI-Car.ino
@@ -0,0 +1,233 @@
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+// Copyright (c) 2025 Aman Maheshwari - Aman@upsidedownlabs.tech
+// Copyright (c) 2025 Upside Down Labs - contact@upsidedownlabs.tech
+
+// At Upside Down Labs, we create open-source DIY neuroscience hardware and software.
+// Our mission is to make neuroscience affordable and accessible for everyone.
+// By supporting us with your purchase, you help spread innovation and open science.
+// Thank you for being part of this journey with us!
+
+/*
+ This example is adapted from the client and server code provided by MoThunderz
+ Firmware: https://github.com/mo-thunderz/Esp32BlePart2
+ YouTube video: https://www.youtube.com/watch?v=s3yoZa6kzus
+*/
+
+#include <Arduino.h>
+#include "BLEDevice.h"
+
+// Define UUIDs:
+static BLEUUID serviceUUID("4fafc201-1fb5-459e-8fcc-c5c9c331914b");
+static BLEUUID charUUID_1("beb5483e-36e1-4688-b7f5-ea07361b26a8");
+static BLEUUID charUUID_2("1c95d5e3-d8f7-413a-bf3d-7a2e5d7be87e");
+
+// Some variables to keep track on device connected
+static boolean doConnect = false;
+static boolean connected = false;
+static boolean notify = false;
+static boolean doScan = false;
+
+// Define wheel control pins
+const int leftWheelForward = 23;
+const int leftWheelBackward = 24;
+const int rightWheelForward = 8;
+const int rightWheelBackward = 9;
+
+// Define pointer for the BLE connection
+static BLEAdvertisedDevice* myDevice;
+BLERemoteCharacteristic* pRemoteChar_1;
+BLERemoteCharacteristic* pRemoteChar_2;
+
+// Callback function for Notify function
+static void notifyCallback(BLERemoteCharacteristic* pBLERemoteCharacteristic,
+                           uint8_t* pData,
+                           size_t length,
+                           bool isNotify) {
+  if (pBLERemoteCharacteristic->getUUID().toString() == charUUID_1.toString()) {
+
+    // convert received bytes to integer
+    uint32_t counter = pData[0];
+    for (int i = 1; i < length; i++) {
+      counter = counter | (pData[i] << i * 8);
+    }
+
+    // print to Serial
+    Serial.print("Characteristic 1 (Notify) from server: ");
+    Serial.println(counter);  //for debugging purposes
+
+    // Control car movement based on received command
+    if (counter == 1) {  // Command for left turn
+      digitalWrite(leftWheelForward, HIGH);
+      digitalWrite(leftWheelBackward, LOW);
+      digitalWrite(rightWheelForward, LOW);
+      digitalWrite(rightWheelBackward, LOW);
+
+    } else if (counter == 2) {  // Command for right turn
+      digitalWrite(rightWheelForward, HIGH);
+      digitalWrite(rightWheelBackward, LOW);
+      digitalWrite(leftWheelForward, LOW);
+      digitalWrite(leftWheelBackward, LOW);
+    } else if (counter == 3) {  // Command to move forward
+      digitalWrite(leftWheelForward, HIGH);
+      digitalWrite(leftWheelBackward, LOW);
+      digitalWrite(rightWheelForward, HIGH);
+      digitalWrite(rightWheelBackward, LOW);
+    } else if (counter == 4) {  // Command to move backward
+      digitalWrite(leftWheelForward, LOW);
+      digitalWrite(leftWheelBackward, HIGH);
+      digitalWrite(rightWheelForward, LOW);
+      digitalWrite(rightWheelBackward, HIGH);
+    } else {  // Default case: stop the car
+      digitalWrite(leftWheelForward, LOW);
+      digitalWrite(leftWheelBackward, LOW);
+      digitalWrite(rightWheelForward, LOW);
+      digitalWrite(rightWheelBackward, LOW);
+    }
+  }
+}
+
+// Callback function that is called whenever a client is connected or disconnected
+class MyClientCallback : public BLEClientCallbacks {
+  void onConnect(BLEClient* pclient) {
+  }
+
+  void onDisconnect(BLEClient* pclient) {
+    connected = false;
+    Serial.println("onDisconnect");  //for debugging purposes
+  }
+};
+
+// Function that is run whenever the server is connected
+bool connectToServer() {
+  Serial.print("Forming a connection to ");
+  Serial.println(myDevice->getAddress().toString().c_str());
+
+  BLEClient* pClient = BLEDevice::createClient();
+  Serial.println(" - Created client");
+
+  pClient->setClientCallbacks(new MyClientCallback());
+
+  // Connect to the remove BLE Server.
+  pClient->connect(myDevice);  // if you pass BLEAdvertisedDevice instead of address, it will be recognized type of peer device address (public or private)
+  Serial.println(" - Connected to server");
+
+  // Obtain a reference to the service we are after in the remote BLE server.
+  BLERemoteService* pRemoteService = pClient->getService(serviceUUID);
+  if (pRemoteService == nullptr) {
+    Serial.print("Failed to find our service UUID: ");
+    Serial.println(serviceUUID.toString().c_str());
+    pClient->disconnect();
+    return false;
+  }
+  Serial.println(" - Found our service");
+
+  connected = true;
+  pRemoteChar_1 = pRemoteService->getCharacteristic(charUUID_1);
+  pRemoteChar_2 = pRemoteService->getCharacteristic(charUUID_2);
+  if (connectCharacteristic(pRemoteService, pRemoteChar_1) == false)
+    connected = false;
+  else if (connectCharacteristic(pRemoteService, pRemoteChar_2) == false)
+    connected = false;
+
+  if (connected == false) {
+    pClient->disconnect();
+    Serial.println("At least one characteristic UUID not found");
+    return false;
+  }
+  return true;
+}
+
+// Function to chech Characteristic
+bool connectCharacteristic(BLERemoteService* pRemoteService, BLERemoteCharacteristic* l_BLERemoteChar) {
+  // Obtain a reference to the characteristic in the service of the remote BLE server.
+  if (l_BLERemoteChar == nullptr) {
+    Serial.print("Failed to find one of the characteristics");
+    Serial.print(l_BLERemoteChar->getUUID().toString().c_str());
+    return false;
+  }
+  Serial.println(" - Found characteristic: " + String(l_BLERemoteChar->getUUID().toString().c_str()));
+
+  if (l_BLERemoteChar->canNotify())
+    l_BLERemoteChar->registerForNotify(notifyCallback);
+
+  return true;
+}
+
+// Scan for BLE servers and find the first one that advertises the service we are looking for.
+class MyAdvertisedDeviceCallbacks : public BLEAdvertisedDeviceCallbacks {
+  //Called for each advertising BLE server.
+  void onResult(BLEAdvertisedDevice advertisedDevice) {
+    Serial.print("BLE Advertised Device found: ");
+    Serial.println(advertisedDevice.toString().c_str());
+
+    // We have found a device, let us now see if it contains the service we are looking for.
+    if (advertisedDevice.haveServiceUUID() && advertisedDevice.isAdvertisingService(serviceUUID)) {
+
+      BLEDevice::getScan()->stop();
+      myDevice = new BLEAdvertisedDevice(advertisedDevice);
+      doConnect = true;
+      doScan = true;
+
+    }  // Found our server
+  }    // onResult
+};     // MyAdvertisedDeviceCallbacks
+
+void setup() {
+  Serial.begin(115200);
+  Serial.println("Starting Arduino BLE Client application...");
+  BLEDevice::init("");
+
+  // Retrieve a Scanner and set the callback we want to use to be informed when we
+  // have detected a new device.  Specify that we want active scanning and start the
+  // scan to run for 5 seconds.
+  BLEScan* pBLEScan = BLEDevice::getScan();
+  pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
+  pBLEScan->setInterval(1349);
+  pBLEScan->setWindow(449);
+  pBLEScan->setActiveScan(true);
+  pBLEScan->start(5, false);
+  // put your setup code here, to run once:
+  // Initialize all motor control pins as OUTPUTs
+  pinMode(leftWheelForward, OUTPUT);
+  pinMode(leftWheelBackward, OUTPUT);
+  pinMode(rightWheelForward, OUTPUT);
+  pinMode(rightWheelBackward, OUTPUT);
+
+  // Start with all pins LOW (motors off)
+  digitalWrite(leftWheelForward, LOW);
+  digitalWrite(leftWheelBackward, LOW);
+  digitalWrite(rightWheelForward, LOW);
+  digitalWrite(rightWheelBackward, LOW);
+}  // End of setup.
+
+void loop() {
+
+  // If the flag "doConnect" is true then we have scanned for and found the desired
+  // BLE Server with which we wish to connect.  Now we connect to it.  Once we are
+  // connected we set the connected flag to be true.
+  if (doConnect == true) {
+
+    if (connectToServer()) {
+      Serial.println("We are now connected to the BLE Server.");
+    } else {
+      Serial.println("We have failed to connect to the server; there is nothin more we will do.");
+    }
+    doConnect = false;
+  }
+
+  if (doScan) { BLEDevice::getScan()->start(0); }
+
+  delay(1000);
+}
diff --git a/README.md b/README.md
index e67ef4d..eb2bb74 100644
--- a/README.md
+++ b/README.md
@@ -14,4 +14,5 @@ Neuro PlayGround (NPG) Lite firmware repository.
 | InfraRed (IR) reciever code to identify LG AC remote button commands | [IR-LG-Receive.ino](IR-LG-Receive/IR-LG-Receive.ino) |
 | IR signal send example code to control LG AC to toggle ON/OFF using user button | [IR-LG-Send.ino](IR-LG-Send/IR-LG-Send.ino) |
 | Brain Computer Interface (BCI) to toggle LG AC ON/OFF using EEG band (beta) power | [BCI-IR-Send.ino](BCI-IR-Send/BCI-IR-Send.ino) |
-
+| BLE client that receives notifications from the server and triggers GPIO to control the car | [BLE-BCI-Car.ino](BLE-BCI-Car/BLE-BCI-Car.ino) |
+| BCI remote (server) to drive the BLE car using EEG band (beta) power and EMG (envelope) data | [BLE-BCI-Car-Remote.ino](BLE-BCI-Car-Remote/BLE-BCI-Car-Remote.ino) |


Reviews:
user: login: coderabbitai[bot]
body: **Actionable comments posted: 6**

<details>
<summary>🧹 Nitpick comments (3)</summary><blockquote>

<details>
<summary>README.md (1)</summary><blockquote>

`17-18`: **Keep example descriptions consistent & fix minor typos**

1. “receives” should be “receive” to match the infinitive pattern used in previous rows (“to take”, “to notify”, …).  
2. “triggers GPIO to control the car” is OK, but we already use “trigger GPIO” in the earlier BLE-Client row; consider dropping the second “to” for consistency.  
3. Optional: wrap the file names in back-ticks to match other Markdown conventions.

</blockquote></details>
<details>
<summary>BLE-BCI-Car-Remote/BLE-BCI-Car-Remote.ino (1)</summary><blockquote>

`335-338`: **Magic number `7` hides intent**

`digitalWrite(7, …)` works but reduces readability and risks accidental drift if
`ledPin` ever changes.

```diff
-        digitalWrite(7, HIGH); // Visual feedback
+        digitalWrite(ledPin, HIGH); // Visual feedback
```
Repeat similarly for the LOW call on line 344.

</blockquote></details>
<details>
<summary>BLE-BCI-Car/BLE-BCI-Car.ino (1)</summary><blockquote>

`38-42`: **Unused variables clutter the code**

`notify` is never read. Please remove or implement its intended logic.

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 288bf72622335e516e9da5385da023563b8ae18f and 244a51e331f348d3dcedfb13887553beb2c2ed38.

</details>

<details>
<summary>📒 Files selected for processing (3)</summary>

* `BLE-BCI-Car-Remote/BLE-BCI-Car-Remote.ino` (1 hunks)
* `BLE-BCI-Car/BLE-BCI-Car.ino` (1 hunks)
* `README.md` (1 hunks)

</details>

<details>
<summary>🔇 Additional comments (2)</summary><blockquote>

<details>
<summary>BLE-BCI-Car-Remote/BLE-BCI-Car-Remote.ino (1)</summary>

`35-38`: **GPIO 7 & 9 are flash/strapping pins on most ESP32 modules**

Driving them may block boot or brown-out the board. Please verify your hardware
variant or switch to safe GPIOs (e.g. 2, 4, 16, 17 …).

</details>
<details>
<summary>BLE-BCI-Car/BLE-BCI-Car.ino (1)</summary>

`43-47`: **Verify wheel-pin mapping**

Pins 23/24/8/9 are OK on some boards but clash with PSRAM, strapping or JTAG on
others. Double-check against your specific ESP32 module.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-12T10:21:06Z
user: login: lorforlinux
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-12T12:34:49Z
user: login: lorforlinux
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-12T12:41:18Z
user: login: lorforlinux
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-12T12:42:16Z
user: login: lorforlinux
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-12T12:43:30Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 1**

<details>
<summary>♻️ Duplicate comments (3)</summary><blockquote>

<details>
<summary>BLE-BCI-Car-Remote/BLE-BCI-Car-Remote.ino (3)</summary><blockquote>

`28-33`: **Missing standard headers required for compilation.**

The code includes many libraries but is missing critical standard headers needed for proper compilation across various toolchains.


The code uses `std::vector` at line 209 but doesn't include the `<vector>` header. It also uses `abs()` function but doesn't include `<cmath>` which provides proper floating-point overloads. This can cause build failures on many toolchains that don't implicitly include these headers.

```diff
 #include <BLEDevice.h>
 #include <BLEServer.h>
 #include <BLEUtils.h>
 #include <BLE2902.h>
 #include <Arduino.h>
+#include <vector>
+#include <cmath>
 #include "esp_dsp.h"
```

---

`442-443`: **Using `abs()` on floating-point values will truncate the signal.**

The `abs()` function for EMG envelope detection is problematic as it truncates floating-point values to integers.


Using `abs(float)` promotes to `int abs(int)` on many embedded tool-chains, which truncates the signal (`123.7 → 123`). This degrades EMG signal quality. Use `fabsf()` or `std::abs` overload for floating-point values.

```diff
-        float env1 = Envelopefilter1.getEnvelope(abs(filtemg1));
-        float env2 = Envelopefilter2.getEnvelope(abs(filtemg2));
+        float env1 = Envelopefilter1.getEnvelope(fabsf(filtemg1));
+        float env2 = Envelopefilter2.getEnvelope(fabsf(filtemg2));
```

---

`445-460`: **Missing stop command for EMG turning.**

After sending a turn command with value 1 or 2, the code never sends a stop command, potentially causing the car to turn indefinitely.


When the EMG envelope falls below the threshold, the code should send a stop command (value 0) to prevent perpetual turning. Currently, once a turn command is sent, it remains active until another command overwrites it.

```diff
         else if (env2 > 150)
         {
             emg2_val2 = 2;
             Serial.println("sent 2");               // for debugging purpose only
             pCharacteristic_1->setValue(emg2_val2); // for right turn car
             pCharacteristic_1->notify();
         }
+        // Send stop command when EMG signals fall below threshold
+        else if (deviceConnected && (emg1_val1 || emg2_val2))
+        {
+            uint32_t stop = 0;
+            pCharacteristic_1->setValue(stop);
+            pCharacteristic_1->notify();
+            emg1_val1 = emg2_val2 = 0;
+            Serial.println("sent 0"); // for debugging purpose only
+        }
```

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (3)</summary><blockquote>

<details>
<summary>BLE-BCI-Car-Remote/BLE-BCI-Car-Remote.ino (3)</summary><blockquote>

`127-170`: **Good filter implementation but documentation could be improved.**

The NotchFilter class implements a Band-Stop Butterworth IIR digital filter correctly with proper biquad sections. 


The filter comments should be consistent for all filter classes. Consider reformatting the comments for all filters to have a similar style and include complete information.

---

`232-243`: **EEGFilter has floating-point precision inconsistency.**

The EEGFilter function uses different precision (float vs double) compared to other filter implementations.


Consider making the EEGFilter class consistent with other filter classes by:
1. Converting to a proper class structure like other filters
2. Using the same floating-point precision (double) as the EMG filters
3. Improving documentation to match other filter classes

```diff
-// NEW: Low-pass filter for EEG signals
-float EEGFilter(float input)
-{
-    float output = input;
-    {
-        static float z1 = 0, z2 = 0;
-        float x = output - -1.22465158 * z1 - 0.45044543 * z2;
-        output = 0.05644846 * x + 0.11289692 * z1 + 0.05644846 * z2;
-        z2 = z1;
-        z1 = x;
-    }
-    return output;
-}
+// Low-Pass Butterworth IIR digital filter, generated using filter_gen.py.
+// Sampling rate: 500.0 Hz, frequency: ~45.0 Hz.
+// Filter is order 2, implemented as second-order sections (biquads).
+// Reference: https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.butter.html
+class EEGLowPassFilter
+{
+private:
+    double z1 = 0.0;
+    double z2 = 0.0;
+
+public:
+    double process(double input)
+    {
+        const double x = input - -1.22465158 * z1 - 0.45044543 * z2;
+        const double output = 0.05644846 * x + 0.11289692 * z1 + 0.05644846 * z2;
+        z2 = z1;
+        z1 = x;
+        return output;
+    }
+
+    void reset()
+    {
+        z1 = 0.0;
+        z2 = 0.0;
+    }
+};
```

---

`296-355`: **Good FFT processing implementation but notification redundancy.**

The FFT processing is correctly implemented, but the BCI threshold detection code has redundant BLE notifications.


Consider optimizing the notification logic to only send when the state changes, reducing radio traffic:

```diff
-    if (((smoothedPowers.beta / T) * 100) > 2)
-    {
-        bci_val = 3;
-        Serial.println("send 3");
-        pCharacteristic_1->setValue(bci_val); // for forward moving car
-        pCharacteristic_1->notify();
-        digitalWrite(7, HIGH); // Visual feedback
-    }
-    else
-    {
-        bci_val = 0;
-        pCharacteristic_1->setValue(bci_val);
-        pCharacteristic_1->notify();
-        digitalWrite(7, LOW);
-    }
+    uint32_t new_bci_val = (((smoothedPowers.beta / T) * 100) > 2) ? 3 : 0;
+    if (new_bci_val != bci_val)
+    {
+        bci_val = new_bci_val;
+        Serial.println(bci_val == 3 ? "send 3" : "send 0");
+        pCharacteristic_1->setValue(bci_val);
+        pCharacteristic_1->notify();
+        digitalWrite(7, bci_val == 3 ? HIGH : LOW); // Visual feedback
+    }
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between ce5d18f9c3eac4d9ab6f35afe7dcd8536908f16c and 5ef02ae442195aaee8a384dcc0bfb6e6badd9da2.

</details>

<details>
<summary>📒 Files selected for processing (1)</summary>

* `BLE-BCI-Car-Remote/BLE-BCI-Car-Remote.ino` (1 hunks)

</details>

<details>
<summary>🔇 Additional comments (7)</summary><blockquote>

<details>
<summary>BLE-BCI-Car-Remote/BLE-BCI-Car-Remote.ino (7)</summary>

`172-203`: **EMGHighPassFilter implemented correctly with proper filter coefficients.**

The implementation follows good practices with separate state variables for maintaining filter history.

---

`206-229`: **Envelope filter implementation uses moving average algorithm correctly.**

The EnvelopeFilter class appropriately uses std::vector for a circular buffer to calculate the moving average envelope of EMG signals.

---

`250-270`: **Bandpower calculation is correctly implemented.**

The function correctly calculates power in different EEG frequency bands based on the power spectrum.

---

`272-280`: **Smoothing algorithm properly implements exponential smoothing.**

The exponential smoothing implementation is mathematically correct and efficiently updates all frequency bands.

---

`283-293`: **FFT initialization properly handles errors.**

The function correctly initializes the ESP32 DSP FFT functionality and has appropriate error handling.

---

`357-419`: **Setup function initializes BLE and hardware correctly.**

The function properly configures the hardware, initializes BLE services and characteristics, and sets up advertising.

---

`468-528`: **Button debounce and BLE connection handling implemented properly.**

The button handling with debounce logic and BLE connection state management is well-implemented.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-12T12:50:53Z
user: login: lorforlinux
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-12T12:53:16Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-12T12:53:57Z
user: login: lorforlinux
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-12T12:54:53Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-12T12:55:43Z
user: login: lorforlinux
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-12T12:56:45Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-12T12:56:51Z

Review comments:
diff_hunk: @@ -0,0 +1,519 @@
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+// Copyright (c) 2025 Aman Maheshwari - Aman@upsidedownlabs.tech
+// Copyright (c) 2025 Upside Down Labs - contact@upsidedownlabs.tech
+
+// At Upside Down Labs, we create open-source DIY neuroscience hardware and software.
+// Our mission is to make neuroscience affordable and accessible for everyone.
+// By supporting us with your purchase, you help spread innovation and open science.
+// Thank you for being part of this journey with us!
+
+/*
+ This example is adapted from the client and server code provided by MoThunderz
+ Firmware: https://github.com/mo-thunderz/Esp32BlePart2
+ YouTube video: https://www.youtube.com/watch?v=s3yoZa6kzus
+*/
+
+#include <BLEDevice.h>
+#include <BLEServer.h>
+#include <BLEUtils.h>
+#include <BLE2902.h>
+#include <Arduino.h>
+#include "esp_dsp.h"
+
path: BLE-BCI-Car-Remote/BLE-BCI-Car-Remote.ino
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Missing standard headers cause build failures on many cores**

`<vector>` is required for `std::vector` (line 200) and `<cmath>` for `fabs/fabsf`.  
Failing to include them breaks compilation on tool-chains that do not implicitly pull them in.

```diff
 #include <BLEDevice.h>
 #include <BLEServer.h>
 #include <BLEUtils.h>
 #include <BLE2902.h>
 #include <Arduino.h>
+#include <vector>
+#include <cmath>
 #include "esp_dsp.h"
```



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>
#include <Arduino.h>
#include <vector>
#include <cmath>
#include "esp_dsp.h"
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-12T10:21:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,519 @@
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+// Copyright (c) 2025 Aman Maheshwari - Aman@upsidedownlabs.tech
+// Copyright (c) 2025 Upside Down Labs - contact@upsidedownlabs.tech
+
+// At Upside Down Labs, we create open-source DIY neuroscience hardware and software.
+// Our mission is to make neuroscience affordable and accessible for everyone.
+// By supporting us with your purchase, you help spread innovation and open science.
+// Thank you for being part of this journey with us!
+
+/*
+ This example is adapted from the client and server code provided by MoThunderz
+ Firmware: https://github.com/mo-thunderz/Esp32BlePart2
+ YouTube video: https://www.youtube.com/watch?v=s3yoZa6kzus
+*/
+
+#include <BLEDevice.h>
+#include <BLEServer.h>
+#include <BLEUtils.h>
+#include <BLE2902.h>
+#include <Arduino.h>
+#include "esp_dsp.h"
+
+// constants won't change. They're used here to set pin numbers:
+const int buttonPin = 9; // the number of the pushbutton pin
+const int ledPin = 7;    // the number of the LED pin
+
+// Variables will change:
+int ledState = HIGH;       // the current state of the output pin
+uint32_t buttonState;      // the current reading from the input pin
+int lastButtonState = LOW; // the previous reading from the input pin
+uint32_t bci_val = 0;      // EEG-based control value (0=stop, 3=forward)
+uint32_t emg1_val1 = 0;    // Left EMG control value (0=inactive, 1=left turn)
+uint32_t emg2_val2 = 0;    // Right EMG control value (0=inactive, 2=right turn)
+uint32_t bootback_val = 4; // Button toggle value (4=toggle state)
+
+// the following variables are unsigned longs because the time, measured in
+// milliseconds, will quickly become a bigger number than can be stored in an int.
+unsigned long lastDebounceTime = 0; // the last time the output pin was toggled
+unsigned long debounceDelay = 50;   // the debounce time; increase if the output flickers
+
+// Initialize all pointers
+BLEServer *pServer = NULL;                   // Pointer to the server
+BLECharacteristic *pCharacteristic_1 = NULL; // Pointer to Characteristic 1
+BLECharacteristic *pCharacteristic_2 = NULL; // Pointer to Characteristic 2
+BLEDescriptor *pDescr_1;                     // Pointer to Descriptor of Characteristic 1
+BLE2902 *pBLE2902_1;                         // Pointer to BLE2902 of Characteristic 1
+BLE2902 *pBLE2902_2;                         // Pointer to BLE2902 of Characteristic 2
+
+// Some variables to keep track on device connected
+bool deviceConnected = false;
+bool oldDeviceConnected = false;
+
+// Variable that will continuously be increased and written to the client
+uint32_t value = 0;
+
+// See the following for generating UUIDs:
+// https://www.uuidgenerator.net/
+// UUIDs used in this example:
+#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
+#define CHARACTERISTIC_UUID_1 "beb5483e-36e1-4688-b7f5-ea07361b26a8"
+#define CHARACTERISTIC_UUID_2 "1c95d5e3-d8f7-413a-bf3d-7a2e5d7be87e"
+
+// Callback function that is called whenever a client is connected or disconnected
+class MyServerCallbacks : public BLEServerCallbacks
+{
+    void onConnect(BLEServer *pServer)
+    {
+        deviceConnected = true;
+    };
+
+    void onDisconnect(BLEServer *pServer)
+    {
+        deviceConnected = false;
+    }
+};
+
+// ----------------- USER CONFIGURATION -----------------
+#define SAMPLE_RATE 512 // samples per second
+#define FFT_SIZE 512    // must be a power of two
+#define BAUD_RATE 115200
+#define INPUT_PIN1 A0 // EEG input pin
+#define INPUT_PIN2 A1 // Left hand EMG input pin
+#define INPUT_PIN3 A2 // Right hand EMG input pin
+
+// EEG bands (Hz)
+#define DELTA_LOW 0.5f
+#define DELTA_HIGH 4.0f
+#define THETA_LOW 4.0f
+#define THETA_HIGH 8.0f
+#define ALPHA_LOW 8.0f
+#define ALPHA_HIGH 13.0f
+#define BETA_LOW 13.0f
+#define BETA_HIGH 30.0f
+#define GAMMA_LOW 30.0f
+#define GAMMA_HIGH 45.0f
+
+#define SMOOTHING_FACTOR 0.63f
+#define EPS 1e-7f
+
+// ----------------- BUFFERS & TYPES -----------------
+float inputBuffer[FFT_SIZE];
+float powerSpectrum[FFT_SIZE / 2];
+
+// For two-real FFT trick
+__attribute__((aligned(16))) float y_cf[FFT_SIZE * 2];
+float *y1_cf = &y_cf[0];
+
+typedef struct
+{
+    float delta, theta, alpha, beta, gamma, total;
+} BandpowerResults;
+
+BandpowerResults smoothedPowers = {0};
+
+// ----------------- FILTER CLASSES -----------------
+// NEW: Notch filter for 50Hz noise removal
+class NotchFilter
+{
+private:
+    struct BiquadState
+    {
+        float z1 = 0;
+        float z2 = 0;
+    };
+
+    BiquadState state1;
+    BiquadState state2;
+
+public:
+    float process(float input)
+    {
+        float output = input;
+
+        // First biquad section
+        float x = output - (-1.56858163f * state1.z1) - (0.96424138f * state1.z2);
+        output = 0.96508099f * x + (-1.56202714f * state1.z1) + (0.96508099f * state1.z2);
+        state1.z2 = state1.z1;
+        state1.z1 = x;
+
+        // Second biquad section
+        x = output - (-1.61100358f * state2.z1) - (0.96592171f * state2.z2);
+        output = 1.00000000f * x + (-1.61854514f * state2.z1) + (1.00000000f * state2.z2);
+        state2.z2 = state2.z1;
+        state2.z1 = x;
+
+        return output;
+    }
+
+    void reset()
+    {
+        state1.z1 = state1.z2 = 0;
+        state2.z1 = state2.z2 = 0;
+    }
+};
+
+// NEW: High-pass filter for EMG signals
+class EMGHighPassFilter
+{
+private:
+    // Filter state for a single channel
+    double z1 = 0.0;
+    double z2 = 0.0;
+
+public:
+    // Process a single sample
+    double process(double input)
+    {
+        const double x = input - -0.82523238 * z1 - 0.29463653 * z2;
+        const double output = 0.52996723 * x + -1.05993445 * z1 + 0.52996723 * z2;
+
+        // Update state
+        z2 = z1;
+        z1 = x;
+
+        return output;
+    }
+
+    // Reset filter state
+    void reset()
+    {
+        z1 = 0.0;
+        z2 = 0.0;
+    }
+};
+class EnvelopeFilter
+{
+private:
+    std::vector<double> circularBuffer;
+    double sum = 0.0;
+    int dataIndex = 0;
+    const int bufferSize;
+
+public:
+    EnvelopeFilter(int bufferSize)
+        : bufferSize(bufferSize)
+    {
+        circularBuffer.resize(bufferSize, 0.0);
+    }
+
+    double getEnvelope(double absEmg)
+    {
+        sum -= circularBuffer[dataIndex];
+        sum += absEmg;
+        circularBuffer[dataIndex] = absEmg;
+        dataIndex = (dataIndex + 1) % bufferSize;
+        return (sum / bufferSize);
+    }
+};
+
+// NEW: Low-pass filter for EEG signals
+float EEGFilter(float input)
+{
+    float output = input;
+    {
+        static float z1 = 0, z2 = 0;
+        float x = output - -1.22465158 * z1 - 0.45044543 * z2;
+        output = 0.05644846 * x + 0.11289692 * z1 + 0.05644846 * z2;
+        z2 = z1;
+        z1 = x;
+    }
+    return output;
+}
+NotchFilter filters[3];             // Notch filters for all 3 input channels
+EMGHighPassFilter emgfilters[2];    // High-pass filters for EMG channels
+EnvelopeFilter Envelopefilter1(16); // Envelope detector for left EMG
+EnvelopeFilter Envelopefilter2(16); // Envelope detector for right EMG
+
+// ----------------- BANDPOWER & SMOOTHING -----------------
+BandpowerResults calculateBandpower(float *ps, float binRes, int halfSize)
+{
+    BandpowerResults r = {0};
+    for (int i = 1; i < halfSize; i++)
+    {
+        float freq = i * binRes;
+        float p = ps[i];
+        r.total += p;
+        if (freq >= DELTA_LOW && freq < DELTA_HIGH)
+            r.delta += p;
+        else if (freq >= THETA_LOW && freq < THETA_HIGH)
+            r.theta += p;
+        else if (freq >= ALPHA_LOW && freq < ALPHA_HIGH)
+            r.alpha += p;
+        else if (freq >= BETA_LOW && freq < BETA_HIGH)
+            r.beta += p;
+        else if (freq >= GAMMA_LOW && freq < GAMMA_HIGH)
+            r.gamma += p;
+    }
+    return r;
+}
+
+void smoothBandpower(const BandpowerResults *raw, BandpowerResults *s)
+{
+    s->delta = SMOOTHING_FACTOR * raw->delta + (1 - SMOOTHING_FACTOR) * s->delta;
+    s->theta = SMOOTHING_FACTOR * raw->theta + (1 - SMOOTHING_FACTOR) * s->theta;
+    s->alpha = SMOOTHING_FACTOR * raw->alpha + (1 - SMOOTHING_FACTOR) * s->alpha;
+    s->beta = SMOOTHING_FACTOR * raw->beta + (1 - SMOOTHING_FACTOR) * s->beta;
+    s->gamma = SMOOTHING_FACTOR * raw->gamma + (1 - SMOOTHING_FACTOR) * s->gamma;
+    s->total = SMOOTHING_FACTOR * raw->total + (1 - SMOOTHING_FACTOR) * s->total;
+}
+
+// ----------------- DSP FFT SETUP -----------------
+void initFFT()
+{
+    // initialize esp-dsp real-FFT (two-real trick)
+    esp_err_t err = dsps_fft2r_init_fc32(NULL, FFT_SIZE);
+    if (err != ESP_OK)
+    {
+        Serial.println("FFT init failed");
+        while (1)
+            delay(10);
+    }
+}
+
+// ----------------- FFT + BANDPOWER + PEAK -----------------
+void processFFT()
+{
+    // pack real→complex: real=inputBuffer, imag=0
+    for (int i = 0; i < FFT_SIZE; i++)
+    {
+        y_cf[2 * i] = inputBuffer[i];
+        y_cf[2 * i + 1] = 0.0f;
+    }
+
+    // FFT
+    dsps_fft2r_fc32(y_cf, FFT_SIZE);
+    dsps_bit_rev_fc32(y_cf, FFT_SIZE);
+    dsps_cplx2reC_fc32(y_cf, FFT_SIZE);
+
+    // magnitude² spectrum
+    int half = FFT_SIZE / 2;
+    for (int i = 0; i < half; i++)
+    {
+        float re = y1_cf[2 * i];
+        float im = y1_cf[2 * i + 1];
+        powerSpectrum[i] = re * re + im * im;
+    }
+
+    // detect peak bin (skip i=0)
+    int maxIdx = 1;
+    float maxP = powerSpectrum[1];
+    for (int i = 2; i < half; i++)
+    {
+        if (powerSpectrum[i] > maxP)
+        {
+            maxP = powerSpectrum[i];
+            maxIdx = i;
+        }
+    }
+    float binRes = float(SAMPLE_RATE) / FFT_SIZE;
+    float peakHz = maxIdx * binRes;
+
+    // bandpower & smoothing
+    BandpowerResults raw = calculateBandpower(powerSpectrum, binRes, half);
+    smoothBandpower(&raw, &smoothedPowers);
+    float T = smoothedPowers.total + EPS;
+    Serial.println(((smoothedPowers.beta / T) * 100)); // for debugging purpose only
+
+    // If the power exceeds the threshold (set as 2% of the total power), the threshold value can be adjusted based on your beta parameters.
+    if (((smoothedPowers.beta / T) * 100) > 2)
+    {
+        bci_val = 3;
+        Serial.println("send 3");
+        pCharacteristic_1->setValue(bci_val); // for forward moving car
+        pCharacteristic_1->notify();
+        digitalWrite(7, HIGH); // Visual feedback
+    }
+    else
+    {
+        bci_val = 0;
+        pCharacteristic_1->setValue(bci_val);
+        pCharacteristic_1->notify();
+        digitalWrite(7, LOW);
+    }
+}
+
+void setup()
+{
+    Serial.begin(BAUD_RATE);
+    while (!Serial)
+    {
+        delay(1);
+    }
+    pinMode(INPUT_PIN1, INPUT);
+    pinMode(INPUT_PIN2, INPUT);
+    pinMode(INPUT_PIN3, INPUT);
+
+    pinMode(7, OUTPUT);
+
+    initFFT();
+    pinMode(buttonPin, INPUT_PULLUP);
+    pinMode(ledPin, OUTPUT);
+
+    // set initial LED state
+    digitalWrite(ledPin, ledState);
+    // Create the BLE Device
+    BLEDevice::init("ESP32");
+
+    // Create the BLE Server
+    pServer = BLEDevice::createServer();
+    pServer->setCallbacks(new MyServerCallbacks());
+
+    // Create the BLE Service
+    BLEService *pService = pServer->createService(SERVICE_UUID);
+
+    // Create a BLE Characteristic
+    pCharacteristic_1 = pService->createCharacteristic(
+        CHARACTERISTIC_UUID_1,
+        BLECharacteristic::PROPERTY_NOTIFY);
+
+    pCharacteristic_2 = pService->createCharacteristic(
+        CHARACTERISTIC_UUID_2,
+        BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_NOTIFY);
+
+    // Create a BLE Descriptor
+    pDescr_1 = new BLEDescriptor((uint16_t)0x2901);
+    pDescr_1->setValue("A very interesting variable");
+    pCharacteristic_1->addDescriptor(pDescr_1);
+
+    // Add the BLE2902 Descriptor because we are using "PROPERTY_NOTIFY"
+    pBLE2902_1 = new BLE2902();
+    pBLE2902_1->setNotifications(true);
+    pCharacteristic_1->addDescriptor(pBLE2902_1);
+
+    pBLE2902_2 = new BLE2902();
+    pBLE2902_2->setNotifications(true);
+    pCharacteristic_2->addDescriptor(pBLE2902_2);
+
+    // Start the service
+    pService->start();
+
+    // Start advertising
+    BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
+    pAdvertising->addServiceUUID(SERVICE_UUID);
+    pAdvertising->setScanResponse(false);
+    pAdvertising->setMinPreferred(0x0); // set value to 0x00 to not advertise this parameter
+    BLEDevice::startAdvertising();
+    Serial.println("Waiting a client connection to notify...");
+}
+
+void loop()
+{
+    static uint16_t idx = 0;
+    static unsigned long lastMicros = micros();
+    unsigned long now = micros(), dt = now - lastMicros;
+    lastMicros = now;
+
+    static long timer = 0;
+    timer -= dt;
+    if (timer <= 0)
+    {
+        timer += 1000000L / SAMPLE_RATE;
+        int raw1 = analogRead(INPUT_PIN1);
+        int raw2 = analogRead(INPUT_PIN2);
+        int raw3 = analogRead(INPUT_PIN3);
+
+        float filteeg = EEGFilter(filters[0].process(raw1));
+        float filtemg1 = emgfilters[0].process(filters[1].process(raw2));
+        float filtemg2 = emgfilters[1].process(filters[2].process(raw3));
+        inputBuffer[idx++] = filteeg;
+
+        float env1 = Envelopefilter1.getEnvelope(abs(filtemg1));
+        float env2 = Envelopefilter2.getEnvelope(abs(filtemg2));
+        
+        // If `env1` exceeds 150, trigger left turn command (send value 1).the threshold value can be adjusted based on your emg parameters.
+        if (env1 > 150)
+        {
+            emg1_val1 = 1;
+            Serial.println("sent 1");               // for debugging purpose only
+            pCharacteristic_1->setValue(emg1_val1); // for left turn car
+            pCharacteristic_1->notify();
+        }
+        // If `env2` exceeds 150, trigger right turn command (send value 2).the threshold value can be adjusted based on your emg parameters.
+        else if (env2 > 150)
+        {
path: BLE-BCI-Car-Remote/BLE-BCI-Car-Remote.ino
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**`abs()` converts to `int` → destroys EMG dynamic range**

`abs(float)` promotes to `int abs(int)` on many embedded tool-chains, truncating the
signal (`123.7 → 123`). Use `fabsf` or `std::abs` overload for `double/float`.

```diff
-        float env1 = Envelopefilter1.getEnvelope(abs(filtemg1));
-        float env2 = Envelopefilter2.getEnvelope(abs(filtemg2));
+        float env1 = Envelopefilter1.getEnvelope(fabsf(filtemg1));
+        float env2 = Envelopefilter2.getEnvelope(fabsf(filtemg2));
```



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
        float env1 = Envelopefilter1.getEnvelope(fabsf(filtemg1));
        float env2 = Envelopefilter2.getEnvelope(fabsf(filtemg2));
        
        // If `env1` exceeds 150, trigger left turn command (send value 1).the threshold value can be adjusted based on your emg parameters.
        if (env1 > 150)
        {
            emg1_val1 = 1;
            Serial.println("sent 1");               // for debugging purpose only
            pCharacteristic_1->setValue(emg1_val1); // for left turn car
            pCharacteristic_1->notify();
        }
        // If `env2` exceeds 150, trigger right turn command (send value 2).the threshold value can be adjusted based on your emg parameters.
        else if (env2 > 150)
        {
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-12T10:21:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,519 @@
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+// Copyright (c) 2025 Aman Maheshwari - Aman@upsidedownlabs.tech
+// Copyright (c) 2025 Upside Down Labs - contact@upsidedownlabs.tech
+
+// At Upside Down Labs, we create open-source DIY neuroscience hardware and software.
+// Our mission is to make neuroscience affordable and accessible for everyone.
+// By supporting us with your purchase, you help spread innovation and open science.
+// Thank you for being part of this journey with us!
+
+/*
+ This example is adapted from the client and server code provided by MoThunderz
+ Firmware: https://github.com/mo-thunderz/Esp32BlePart2
+ YouTube video: https://www.youtube.com/watch?v=s3yoZa6kzus
+*/
+
+#include <BLEDevice.h>
+#include <BLEServer.h>
+#include <BLEUtils.h>
+#include <BLE2902.h>
+#include <Arduino.h>
+#include "esp_dsp.h"
+
+// constants won't change. They're used here to set pin numbers:
+const int buttonPin = 9; // the number of the pushbutton pin
+const int ledPin = 7;    // the number of the LED pin
+
+// Variables will change:
+int ledState = HIGH;       // the current state of the output pin
+uint32_t buttonState;      // the current reading from the input pin
+int lastButtonState = LOW; // the previous reading from the input pin
+uint32_t bci_val = 0;      // EEG-based control value (0=stop, 3=forward)
+uint32_t emg1_val1 = 0;    // Left EMG control value (0=inactive, 1=left turn)
+uint32_t emg2_val2 = 0;    // Right EMG control value (0=inactive, 2=right turn)
+uint32_t bootback_val = 4; // Button toggle value (4=toggle state)
+
+// the following variables are unsigned longs because the time, measured in
+// milliseconds, will quickly become a bigger number than can be stored in an int.
+unsigned long lastDebounceTime = 0; // the last time the output pin was toggled
+unsigned long debounceDelay = 50;   // the debounce time; increase if the output flickers
+
+// Initialize all pointers
+BLEServer *pServer = NULL;                   // Pointer to the server
+BLECharacteristic *pCharacteristic_1 = NULL; // Pointer to Characteristic 1
+BLECharacteristic *pCharacteristic_2 = NULL; // Pointer to Characteristic 2
+BLEDescriptor *pDescr_1;                     // Pointer to Descriptor of Characteristic 1
+BLE2902 *pBLE2902_1;                         // Pointer to BLE2902 of Characteristic 1
+BLE2902 *pBLE2902_2;                         // Pointer to BLE2902 of Characteristic 2
+
+// Some variables to keep track on device connected
+bool deviceConnected = false;
+bool oldDeviceConnected = false;
+
+// Variable that will continuously be increased and written to the client
+uint32_t value = 0;
+
+// See the following for generating UUIDs:
+// https://www.uuidgenerator.net/
+// UUIDs used in this example:
+#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
+#define CHARACTERISTIC_UUID_1 "beb5483e-36e1-4688-b7f5-ea07361b26a8"
+#define CHARACTERISTIC_UUID_2 "1c95d5e3-d8f7-413a-bf3d-7a2e5d7be87e"
+
+// Callback function that is called whenever a client is connected or disconnected
+class MyServerCallbacks : public BLEServerCallbacks
+{
+    void onConnect(BLEServer *pServer)
+    {
+        deviceConnected = true;
+    };
+
+    void onDisconnect(BLEServer *pServer)
+    {
+        deviceConnected = false;
+    }
+};
+
+// ----------------- USER CONFIGURATION -----------------
+#define SAMPLE_RATE 512 // samples per second
+#define FFT_SIZE 512    // must be a power of two
+#define BAUD_RATE 115200
+#define INPUT_PIN1 A0 // EEG input pin
+#define INPUT_PIN2 A1 // Left hand EMG input pin
+#define INPUT_PIN3 A2 // Right hand EMG input pin
+
+// EEG bands (Hz)
+#define DELTA_LOW 0.5f
+#define DELTA_HIGH 4.0f
+#define THETA_LOW 4.0f
+#define THETA_HIGH 8.0f
+#define ALPHA_LOW 8.0f
+#define ALPHA_HIGH 13.0f
+#define BETA_LOW 13.0f
+#define BETA_HIGH 30.0f
+#define GAMMA_LOW 30.0f
+#define GAMMA_HIGH 45.0f
+
+#define SMOOTHING_FACTOR 0.63f
+#define EPS 1e-7f
+
+// ----------------- BUFFERS & TYPES -----------------
+float inputBuffer[FFT_SIZE];
+float powerSpectrum[FFT_SIZE / 2];
+
+// For two-real FFT trick
+__attribute__((aligned(16))) float y_cf[FFT_SIZE * 2];
+float *y1_cf = &y_cf[0];
+
+typedef struct
+{
+    float delta, theta, alpha, beta, gamma, total;
+} BandpowerResults;
+
+BandpowerResults smoothedPowers = {0};
+
+// ----------------- FILTER CLASSES -----------------
+// NEW: Notch filter for 50Hz noise removal
+class NotchFilter
+{
+private:
+    struct BiquadState
+    {
+        float z1 = 0;
+        float z2 = 0;
+    };
+
+    BiquadState state1;
+    BiquadState state2;
+
+public:
+    float process(float input)
+    {
+        float output = input;
+
+        // First biquad section
+        float x = output - (-1.56858163f * state1.z1) - (0.96424138f * state1.z2);
+        output = 0.96508099f * x + (-1.56202714f * state1.z1) + (0.96508099f * state1.z2);
+        state1.z2 = state1.z1;
+        state1.z1 = x;
+
+        // Second biquad section
+        x = output - (-1.61100358f * state2.z1) - (0.96592171f * state2.z2);
+        output = 1.00000000f * x + (-1.61854514f * state2.z1) + (1.00000000f * state2.z2);
+        state2.z2 = state2.z1;
+        state2.z1 = x;
+
+        return output;
+    }
+
+    void reset()
+    {
+        state1.z1 = state1.z2 = 0;
+        state2.z1 = state2.z2 = 0;
+    }
+};
+
+// NEW: High-pass filter for EMG signals
+class EMGHighPassFilter
+{
+private:
+    // Filter state for a single channel
+    double z1 = 0.0;
+    double z2 = 0.0;
+
+public:
+    // Process a single sample
+    double process(double input)
+    {
+        const double x = input - -0.82523238 * z1 - 0.29463653 * z2;
+        const double output = 0.52996723 * x + -1.05993445 * z1 + 0.52996723 * z2;
+
+        // Update state
+        z2 = z1;
+        z1 = x;
+
+        return output;
+    }
+
+    // Reset filter state
+    void reset()
+    {
+        z1 = 0.0;
+        z2 = 0.0;
+    }
+};
+class EnvelopeFilter
+{
+private:
+    std::vector<double> circularBuffer;
+    double sum = 0.0;
+    int dataIndex = 0;
+    const int bufferSize;
+
+public:
+    EnvelopeFilter(int bufferSize)
+        : bufferSize(bufferSize)
+    {
+        circularBuffer.resize(bufferSize, 0.0);
+    }
+
+    double getEnvelope(double absEmg)
+    {
+        sum -= circularBuffer[dataIndex];
+        sum += absEmg;
+        circularBuffer[dataIndex] = absEmg;
+        dataIndex = (dataIndex + 1) % bufferSize;
+        return (sum / bufferSize);
+    }
+};
+
+// NEW: Low-pass filter for EEG signals
+float EEGFilter(float input)
+{
+    float output = input;
+    {
+        static float z1 = 0, z2 = 0;
+        float x = output - -1.22465158 * z1 - 0.45044543 * z2;
+        output = 0.05644846 * x + 0.11289692 * z1 + 0.05644846 * z2;
+        z2 = z1;
+        z1 = x;
+    }
+    return output;
+}
+NotchFilter filters[3];             // Notch filters for all 3 input channels
+EMGHighPassFilter emgfilters[2];    // High-pass filters for EMG channels
+EnvelopeFilter Envelopefilter1(16); // Envelope detector for left EMG
+EnvelopeFilter Envelopefilter2(16); // Envelope detector for right EMG
+
+// ----------------- BANDPOWER & SMOOTHING -----------------
+BandpowerResults calculateBandpower(float *ps, float binRes, int halfSize)
+{
+    BandpowerResults r = {0};
+    for (int i = 1; i < halfSize; i++)
+    {
+        float freq = i * binRes;
+        float p = ps[i];
+        r.total += p;
+        if (freq >= DELTA_LOW && freq < DELTA_HIGH)
+            r.delta += p;
+        else if (freq >= THETA_LOW && freq < THETA_HIGH)
+            r.theta += p;
+        else if (freq >= ALPHA_LOW && freq < ALPHA_HIGH)
+            r.alpha += p;
+        else if (freq >= BETA_LOW && freq < BETA_HIGH)
+            r.beta += p;
+        else if (freq >= GAMMA_LOW && freq < GAMMA_HIGH)
+            r.gamma += p;
+    }
+    return r;
+}
+
+void smoothBandpower(const BandpowerResults *raw, BandpowerResults *s)
+{
+    s->delta = SMOOTHING_FACTOR * raw->delta + (1 - SMOOTHING_FACTOR) * s->delta;
+    s->theta = SMOOTHING_FACTOR * raw->theta + (1 - SMOOTHING_FACTOR) * s->theta;
+    s->alpha = SMOOTHING_FACTOR * raw->alpha + (1 - SMOOTHING_FACTOR) * s->alpha;
+    s->beta = SMOOTHING_FACTOR * raw->beta + (1 - SMOOTHING_FACTOR) * s->beta;
+    s->gamma = SMOOTHING_FACTOR * raw->gamma + (1 - SMOOTHING_FACTOR) * s->gamma;
+    s->total = SMOOTHING_FACTOR * raw->total + (1 - SMOOTHING_FACTOR) * s->total;
+}
+
+// ----------------- DSP FFT SETUP -----------------
+void initFFT()
+{
+    // initialize esp-dsp real-FFT (two-real trick)
+    esp_err_t err = dsps_fft2r_init_fc32(NULL, FFT_SIZE);
+    if (err != ESP_OK)
+    {
+        Serial.println("FFT init failed");
+        while (1)
+            delay(10);
+    }
+}
+
+// ----------------- FFT + BANDPOWER + PEAK -----------------
+void processFFT()
+{
+    // pack real→complex: real=inputBuffer, imag=0
+    for (int i = 0; i < FFT_SIZE; i++)
+    {
+        y_cf[2 * i] = inputBuffer[i];
+        y_cf[2 * i + 1] = 0.0f;
+    }
+
+    // FFT
+    dsps_fft2r_fc32(y_cf, FFT_SIZE);
+    dsps_bit_rev_fc32(y_cf, FFT_SIZE);
+    dsps_cplx2reC_fc32(y_cf, FFT_SIZE);
+
+    // magnitude² spectrum
+    int half = FFT_SIZE / 2;
+    for (int i = 0; i < half; i++)
+    {
+        float re = y1_cf[2 * i];
+        float im = y1_cf[2 * i + 1];
+        powerSpectrum[i] = re * re + im * im;
+    }
+
+    // detect peak bin (skip i=0)
+    int maxIdx = 1;
+    float maxP = powerSpectrum[1];
+    for (int i = 2; i < half; i++)
+    {
+        if (powerSpectrum[i] > maxP)
+        {
+            maxP = powerSpectrum[i];
+            maxIdx = i;
+        }
+    }
+    float binRes = float(SAMPLE_RATE) / FFT_SIZE;
+    float peakHz = maxIdx * binRes;
+
+    // bandpower & smoothing
+    BandpowerResults raw = calculateBandpower(powerSpectrum, binRes, half);
+    smoothBandpower(&raw, &smoothedPowers);
+    float T = smoothedPowers.total + EPS;
+    Serial.println(((smoothedPowers.beta / T) * 100)); // for debugging purpose only
+
+    // If the power exceeds the threshold (set as 2% of the total power), the threshold value can be adjusted based on your beta parameters.
+    if (((smoothedPowers.beta / T) * 100) > 2)
+    {
+        bci_val = 3;
+        Serial.println("send 3");
+        pCharacteristic_1->setValue(bci_val); // for forward moving car
+        pCharacteristic_1->notify();
+        digitalWrite(7, HIGH); // Visual feedback
+    }
+    else
+    {
+        bci_val = 0;
+        pCharacteristic_1->setValue(bci_val);
+        pCharacteristic_1->notify();
+        digitalWrite(7, LOW);
+    }
+}
+
+void setup()
+{
+    Serial.begin(BAUD_RATE);
+    while (!Serial)
+    {
+        delay(1);
+    }
+    pinMode(INPUT_PIN1, INPUT);
+    pinMode(INPUT_PIN2, INPUT);
+    pinMode(INPUT_PIN3, INPUT);
+
+    pinMode(7, OUTPUT);
+
+    initFFT();
+    pinMode(buttonPin, INPUT_PULLUP);
+    pinMode(ledPin, OUTPUT);
+
+    // set initial LED state
+    digitalWrite(ledPin, ledState);
+    // Create the BLE Device
+    BLEDevice::init("ESP32");
+
+    // Create the BLE Server
+    pServer = BLEDevice::createServer();
+    pServer->setCallbacks(new MyServerCallbacks());
+
+    // Create the BLE Service
+    BLEService *pService = pServer->createService(SERVICE_UUID);
+
+    // Create a BLE Characteristic
+    pCharacteristic_1 = pService->createCharacteristic(
+        CHARACTERISTIC_UUID_1,
+        BLECharacteristic::PROPERTY_NOTIFY);
+
+    pCharacteristic_2 = pService->createCharacteristic(
+        CHARACTERISTIC_UUID_2,
+        BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_NOTIFY);
+
+    // Create a BLE Descriptor
+    pDescr_1 = new BLEDescriptor((uint16_t)0x2901);
+    pDescr_1->setValue("A very interesting variable");
+    pCharacteristic_1->addDescriptor(pDescr_1);
+
+    // Add the BLE2902 Descriptor because we are using "PROPERTY_NOTIFY"
+    pBLE2902_1 = new BLE2902();
+    pBLE2902_1->setNotifications(true);
+    pCharacteristic_1->addDescriptor(pBLE2902_1);
+
+    pBLE2902_2 = new BLE2902();
+    pBLE2902_2->setNotifications(true);
+    pCharacteristic_2->addDescriptor(pBLE2902_2);
+
+    // Start the service
+    pService->start();
+
+    // Start advertising
+    BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
+    pAdvertising->addServiceUUID(SERVICE_UUID);
+    pAdvertising->setScanResponse(false);
+    pAdvertising->setMinPreferred(0x0); // set value to 0x00 to not advertise this parameter
+    BLEDevice::startAdvertising();
+    Serial.println("Waiting a client connection to notify...");
+}
+
+void loop()
+{
+    static uint16_t idx = 0;
+    static unsigned long lastMicros = micros();
+    unsigned long now = micros(), dt = now - lastMicros;
+    lastMicros = now;
+
+    static long timer = 0;
+    timer -= dt;
+    if (timer <= 0)
+    {
+        timer += 1000000L / SAMPLE_RATE;
+        int raw1 = analogRead(INPUT_PIN1);
+        int raw2 = analogRead(INPUT_PIN2);
+        int raw3 = analogRead(INPUT_PIN3);
+
+        float filteeg = EEGFilter(filters[0].process(raw1));
+        float filtemg1 = emgfilters[0].process(filters[1].process(raw2));
+        float filtemg2 = emgfilters[1].process(filters[2].process(raw3));
+        inputBuffer[idx++] = filteeg;
+
+        float env1 = Envelopefilter1.getEnvelope(abs(filtemg1));
+        float env2 = Envelopefilter2.getEnvelope(abs(filtemg2));
+        
+        // If `env1` exceeds 150, trigger left turn command (send value 1).the threshold value can be adjusted based on your emg parameters.
+        if (env1 > 150)
+        {
+            emg1_val1 = 1;
+            Serial.println("sent 1");               // for debugging purpose only
+            pCharacteristic_1->setValue(emg1_val1); // for left turn car
+            pCharacteristic_1->notify();
+        }
+        // If `env2` exceeds 150, trigger right turn command (send value 2).the threshold value can be adjusted based on your emg parameters.
+        else if (env2 > 150)
+        {
+            emg2_val2 = 2;
+            Serial.println("sent 2");               // for debugging purpose only
+            pCharacteristic_1->setValue(emg2_val2); // for right turn car
+            pCharacteristic_1->notify();
+        }
path: BLE-BCI-Car-Remote/BLE-BCI-Car-Remote.ino
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**One-shot EMG commands never send a stop code**

After a turn command (`1` or `2`) the client keeps the wheel high until another
notification arrives.  To avoid perpetual spinning, send `0` once the envelope
falls back below threshold.

```diff
-        } // If env2 exceeds…
-        else if (env2 > 150)
+        }
+        else if (env2 > 150)
         {
             …
         }
+        else if (deviceConnected && (emg1_val1 || emg2_val2))
+        {
+            uint32_t stop = 0;
+            pCharacteristic_1->setValue(stop);
+            pCharacteristic_1->notify();
+            emg1_val1 = emg2_val2 = 0;
+        }
```



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
        // If `env1` exceeds 150, trigger left turn command (send value 1). The threshold can be adjusted.
        if (env1 > 150)
        {
            emg1_val1 = 1;
            Serial.println("sent 1");               // for debugging purpose only
            pCharacteristic_1->setValue(emg1_val1); // for left turn car
            pCharacteristic_1->notify();
        }
        // If `env2` exceeds 150, trigger right turn command (send value 2). The threshold can be adjusted.
        else if (env2 > 150)
        {
            emg2_val2 = 2;
            Serial.println("sent 2");               // for debugging purpose only
            pCharacteristic_1->setValue(emg2_val2); // for right turn car
            pCharacteristic_1->notify();
        }
        // Once neither env1 nor env2 is above threshold, send a stop (0) if we previously sent 1 or 2
        else if (deviceConnected && (emg1_val1 || emg2_val2))
        {
            uint32_t stop = 0;
            pCharacteristic_1->setValue(stop);
            pCharacteristic_1->notify();
            emg1_val1 = emg2_val2 = 0;
        }
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-12T10:21:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,235 @@
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+// Copyright (c) 2025 Aman Maheshwari - Aman@upsidedownlabs.tech
+// Copyright (c) 2025 Upside Down Labs - contact@upsidedownlabs.tech
+
+// At Upside Down Labs, we create open-source DIY neuroscience hardware and software.
+// Our mission is to make neuroscience affordable and accessible for everyone.
+// By supporting us with your purchase, you help spread innovation and open science.
+// Thank you for being part of this journey with us!
+
+/*
+ This example is adapted from the client and server code provided by MoThunderz
+ Firmware: https://github.com/mo-thunderz/Esp32BlePart2
+ YouTube video: https://www.youtube.com/watch?v=s3yoZa6kzus
+*/
+
+ #include <Arduino.h>
+ #include "BLEDevice.h"
+ //#include "BLEScan.h"
+ 
+ // Define UUIDs:
+ static BLEUUID serviceUUID("4fafc201-1fb5-459e-8fcc-c5c9c331914b");
+ static BLEUUID charUUID_1("beb5483e-36e1-4688-b7f5-ea07361b26a8");
+ static BLEUUID charUUID_2("1c95d5e3-d8f7-413a-bf3d-7a2e5d7be87e");
+ 
+ // Some variables to keep track on device connected
+ static boolean doConnect = false;
+ static boolean connected = false;
+ static boolean notify = false;
+ static boolean doScan = false;
+ // Define wheel control pins
+ const int leftWheelForward = 23;
+ const int leftWheelBackward = 24;
+ const int rightWheelForward = 8;
+ const int rightWheelBackward = 9;
+ // Define pointer for the BLE connection
+ static BLEAdvertisedDevice* myDevice;
+ BLERemoteCharacteristic* pRemoteChar_1;
+ BLERemoteCharacteristic* pRemoteChar_2;
+ 
+ // Callback function for Notify function
+ static void notifyCallback(BLERemoteCharacteristic* pBLERemoteCharacteristic,
+                            uint8_t* pData,
+                            size_t length,
+                            bool isNotify) {
+   if (pBLERemoteCharacteristic->getUUID().toString() == charUUID_1.toString()) {
+ 
+     // convert received bytes to integer
+     uint32_t counter = pData[0];
+     for (int i = 1; i < length; i++) {
+       counter = counter | (pData[i] << i * 8);
+     }
+ 
+     // print to Serial
+     Serial.print("Characteristic 1 (Notify) from server: ");
+     Serial.println(counter);//for debugging purposes
+    
+     // Control car movement based on received command
+     if (counter == 1) { // Command for left turn
+       digitalWrite(leftWheelForward, HIGH);
+       digitalWrite(leftWheelBackward, LOW);
+       digitalWrite(rightWheelForward, LOW);
+       digitalWrite(rightWheelBackward, LOW);
+ 
+     } else if (counter == 2) { // Command for right turn
+       digitalWrite(rightWheelForward, HIGH);
+       digitalWrite(rightWheelBackward, LOW);
+       digitalWrite(leftWheelForward, LOW);
+       digitalWrite(leftWheelBackward, LOW);
+     } else if (counter == 3) {  // Command to move forward
+       digitalWrite(leftWheelForward, HIGH);
+       digitalWrite(leftWheelBackward, LOW);
+       digitalWrite(rightWheelForward, HIGH);
+       digitalWrite(rightWheelBackward, LOW);
+     }
+     else if (counter == 4) { // Command to move backward
+       digitalWrite(leftWheelForward, LOW);
+       digitalWrite(leftWheelBackward, HIGH);
+       digitalWrite(rightWheelForward, LOW);
+       digitalWrite(rightWheelBackward, HIGH);
+     }else {// Default case: stop the car
+       digitalWrite(leftWheelForward, LOW);
+       digitalWrite(leftWheelBackward, LOW);
+       digitalWrite(rightWheelForward, LOW);
+       digitalWrite(rightWheelBackward, LOW);
+     }
+   }
+ }
+ 
+ // Callback function that is called whenever a client is connected or disconnected
+ class MyClientCallback : public BLEClientCallbacks {
+   void onConnect(BLEClient* pclient) {
+   }
+ 
+   void onDisconnect(BLEClient* pclient) {
+     connected = false;
+     Serial.println("onDisconnect");//for debugging purposes
+   }
+ };
+ 
+ // Function that is run whenever the server is connected
+ bool connectToServer() {
+   Serial.print("Forming a connection to ");
+   Serial.println(myDevice->getAddress().toString().c_str());
+ 
+   BLEClient* pClient = BLEDevice::createClient();
+   Serial.println(" - Created client");
+ 
+   pClient->setClientCallbacks(new MyClientCallback());
+ 
+   // Connect to the remove BLE Server.
+   pClient->connect(myDevice);  // if you pass BLEAdvertisedDevice instead of address, it will be recognized type of peer device address (public or private)
+   Serial.println(" - Connected to server");
+ 
+   // Obtain a reference to the service we are after in the remote BLE server.
+   BLERemoteService* pRemoteService = pClient->getService(serviceUUID);
+   if (pRemoteService == nullptr) {
+     Serial.print("Failed to find our service UUID: ");
+     Serial.println(serviceUUID.toString().c_str());
+     pClient->disconnect();
+     return false;
+   }
+   Serial.println(" - Found our service");
+ 
+   connected = true;
+   pRemoteChar_1 = pRemoteService->getCharacteristic(charUUID_1);
+   pRemoteChar_2 = pRemoteService->getCharacteristic(charUUID_2);
+   if (connectCharacteristic(pRemoteService, pRemoteChar_1) == false)
+     connected = false;
+   else if (connectCharacteristic(pRemoteService, pRemoteChar_2) == false)
+     connected = false;
+ 
+   if (connected == false) {
+     pClient->disconnect();
+     Serial.println("At least one characteristic UUID not found");
+     return false;
+   }
+   return true;
+ }
+ 
+ // Function to chech Characteristic
+ bool connectCharacteristic(BLERemoteService* pRemoteService, BLERemoteCharacteristic* l_BLERemoteChar) {
+   // Obtain a reference to the characteristic in the service of the remote BLE server.
+   if (l_BLERemoteChar == nullptr) {
+     Serial.print("Failed to find one of the characteristics");
+     Serial.print(l_BLERemoteChar->getUUID().toString().c_str());
+     return false;
+   }
+   Serial.println(" - Found characteristic: " + String(l_BLERemoteChar->getUUID().toString().c_str()));
+ 
+   if (l_BLERemoteChar->canNotify())
+     l_BLERemoteChar->registerForNotify(notifyCallback);
+ 
+   return true;
+ }
+ 
+ // Scan for BLE servers and find the first one that advertises the service we are looking for.
+ class MyAdvertisedDeviceCallbacks : public BLEAdvertisedDeviceCallbacks {
+   //Called for each advertising BLE server.
+   void onResult(BLEAdvertisedDevice advertisedDevice) {
+     Serial.print("BLE Advertised Device found: ");
+     Serial.println(advertisedDevice.toString().c_str());
+ 
+     // We have found a device, let us now see if it contains the service we are looking for.
+     if (advertisedDevice.haveServiceUUID() && advertisedDevice.isAdvertisingService(serviceUUID)) {
+ 
+       BLEDevice::getScan()->stop();
+       myDevice = new BLEAdvertisedDevice(advertisedDevice);
+       doConnect = true;
+       doScan = true;
+ 
+     }  // Found our server
+   }    // onResult
+ };     // MyAdvertisedDeviceCallbacks
+ 
+ void setup() {
+   Serial.begin(115200);
+   Serial.println("Starting Arduino BLE Client application...");
+   BLEDevice::init("");
+ 
+   pinMode(1, OUTPUT);
+   // Retrieve a Scanner and set the callback we want to use to be informed when we
+   // have detected a new device.  Specify that we want active scanning and start the
path: BLE-BCI-Car/BLE-BCI-Car.ino
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Stray `pinMode(1, OUTPUT);` risks TX pin lock-up**

GPIO 1 is the hardware‐UART TX pin on ESP32; driving it as a generic output
breaks Serial or even boot. If you intended an LED, use `LED_BUILTIN` instead,
otherwise remove the line.

<!-- This is an auto-generated reply by CodeRabbit -->

✅ Addressed in commit a787a7c

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-12T10:21:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,235 @@
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+// Copyright (c) 2025 Aman Maheshwari - Aman@upsidedownlabs.tech
+// Copyright (c) 2025 Upside Down Labs - contact@upsidedownlabs.tech
+
+// At Upside Down Labs, we create open-source DIY neuroscience hardware and software.
+// Our mission is to make neuroscience affordable and accessible for everyone.
+// By supporting us with your purchase, you help spread innovation and open science.
+// Thank you for being part of this journey with us!
+
+/*
+ This example is adapted from the client and server code provided by MoThunderz
+ Firmware: https://github.com/mo-thunderz/Esp32BlePart2
+ YouTube video: https://www.youtube.com/watch?v=s3yoZa6kzus
+*/
+
+ #include <Arduino.h>
+ #include "BLEDevice.h"
+ //#include "BLEScan.h"
+ 
+ // Define UUIDs:
+ static BLEUUID serviceUUID("4fafc201-1fb5-459e-8fcc-c5c9c331914b");
+ static BLEUUID charUUID_1("beb5483e-36e1-4688-b7f5-ea07361b26a8");
+ static BLEUUID charUUID_2("1c95d5e3-d8f7-413a-bf3d-7a2e5d7be87e");
+ 
+ // Some variables to keep track on device connected
+ static boolean doConnect = false;
+ static boolean connected = false;
+ static boolean notify = false;
+ static boolean doScan = false;
+ // Define wheel control pins
+ const int leftWheelForward = 23;
+ const int leftWheelBackward = 24;
+ const int rightWheelForward = 8;
+ const int rightWheelBackward = 9;
+ // Define pointer for the BLE connection
+ static BLEAdvertisedDevice* myDevice;
+ BLERemoteCharacteristic* pRemoteChar_1;
+ BLERemoteCharacteristic* pRemoteChar_2;
+ 
+ // Callback function for Notify function
+ static void notifyCallback(BLERemoteCharacteristic* pBLERemoteCharacteristic,
+                            uint8_t* pData,
+                            size_t length,
+                            bool isNotify) {
+   if (pBLERemoteCharacteristic->getUUID().toString() == charUUID_1.toString()) {
+ 
+     // convert received bytes to integer
+     uint32_t counter = pData[0];
+     for (int i = 1; i < length; i++) {
+       counter = counter | (pData[i] << i * 8);
+     }
+ 
+     // print to Serial
+     Serial.print("Characteristic 1 (Notify) from server: ");
+     Serial.println(counter);//for debugging purposes
+    
+     // Control car movement based on received command
+     if (counter == 1) { // Command for left turn
+       digitalWrite(leftWheelForward, HIGH);
+       digitalWrite(leftWheelBackward, LOW);
+       digitalWrite(rightWheelForward, LOW);
+       digitalWrite(rightWheelBackward, LOW);
+ 
+     } else if (counter == 2) { // Command for right turn
+       digitalWrite(rightWheelForward, HIGH);
+       digitalWrite(rightWheelBackward, LOW);
+       digitalWrite(leftWheelForward, LOW);
+       digitalWrite(leftWheelBackward, LOW);
+     } else if (counter == 3) {  // Command to move forward
+       digitalWrite(leftWheelForward, HIGH);
+       digitalWrite(leftWheelBackward, LOW);
+       digitalWrite(rightWheelForward, HIGH);
+       digitalWrite(rightWheelBackward, LOW);
+     }
+     else if (counter == 4) { // Command to move backward
+       digitalWrite(leftWheelForward, LOW);
+       digitalWrite(leftWheelBackward, HIGH);
+       digitalWrite(rightWheelForward, LOW);
+       digitalWrite(rightWheelBackward, HIGH);
+     }else {// Default case: stop the car
+       digitalWrite(leftWheelForward, LOW);
+       digitalWrite(leftWheelBackward, LOW);
+       digitalWrite(rightWheelForward, LOW);
+       digitalWrite(rightWheelBackward, LOW);
+     }
+   }
+ }
+ 
+ // Callback function that is called whenever a client is connected or disconnected
+ class MyClientCallback : public BLEClientCallbacks {
+   void onConnect(BLEClient* pclient) {
+   }
+ 
+   void onDisconnect(BLEClient* pclient) {
+     connected = false;
+     Serial.println("onDisconnect");//for debugging purposes
+   }
+ };
+ 
+ // Function that is run whenever the server is connected
+ bool connectToServer() {
+   Serial.print("Forming a connection to ");
+   Serial.println(myDevice->getAddress().toString().c_str());
+ 
+   BLEClient* pClient = BLEDevice::createClient();
+   Serial.println(" - Created client");
+ 
+   pClient->setClientCallbacks(new MyClientCallback());
+ 
+   // Connect to the remove BLE Server.
+   pClient->connect(myDevice);  // if you pass BLEAdvertisedDevice instead of address, it will be recognized type of peer device address (public or private)
+   Serial.println(" - Connected to server");
+ 
+   // Obtain a reference to the service we are after in the remote BLE server.
+   BLERemoteService* pRemoteService = pClient->getService(serviceUUID);
+   if (pRemoteService == nullptr) {
+     Serial.print("Failed to find our service UUID: ");
+     Serial.println(serviceUUID.toString().c_str());
+     pClient->disconnect();
+     return false;
+   }
+   Serial.println(" - Found our service");
+ 
+   connected = true;
+   pRemoteChar_1 = pRemoteService->getCharacteristic(charUUID_1);
+   pRemoteChar_2 = pRemoteService->getCharacteristic(charUUID_2);
+   if (connectCharacteristic(pRemoteService, pRemoteChar_1) == false)
+     connected = false;
+   else if (connectCharacteristic(pRemoteService, pRemoteChar_2) == false)
+     connected = false;
+ 
+   if (connected == false) {
+     pClient->disconnect();
+     Serial.println("At least one characteristic UUID not found");
+     return false;
+   }
+   return true;
+ }
+ 
+ // Function to chech Characteristic
+ bool connectCharacteristic(BLERemoteService* pRemoteService, BLERemoteCharacteristic* l_BLERemoteChar) {
+   // Obtain a reference to the characteristic in the service of the remote BLE server.
+   if (l_BLERemoteChar == nullptr) {
+     Serial.print("Failed to find one of the characteristics");
+     Serial.print(l_BLERemoteChar->getUUID().toString().c_str());
+     return false;
+   }
+   Serial.println(" - Found characteristic: " + String(l_BLERemoteChar->getUUID().toString().c_str()));
path: BLE-BCI-Car/BLE-BCI-Car.ino
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Dereferencing a null pointer in the error path**

If `l_BLERemoteChar == nullptr`, the subsequent `getUUID()` call is undefined
behaviour. Guard it or remove the print.

```diff
-    Serial.print(l_BLERemoteChar->getUUID().toString().c_str());
+    // l_BLERemoteChar is null – cannot access its UUID
```



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
 bool connectCharacteristic(BLERemoteService* pRemoteService, BLERemoteCharacteristic* l_BLERemoteChar) {
   // Obtain a reference to the characteristic in the service of the remote BLE server.
   if (l_BLERemoteChar == nullptr) {
     Serial.print("Failed to find one of the characteristics");
     // l_BLERemoteChar is null – cannot access its UUID
     return false;
   }
   Serial.println(" - Found characteristic: " + String(l_BLERemoteChar->getUUID().toString().c_str()));
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-12T10:21:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,235 @@
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+// Copyright (c) 2025 Aman Maheshwari - Aman@upsidedownlabs.tech
+// Copyright (c) 2025 Upside Down Labs - contact@upsidedownlabs.tech
+
+// At Upside Down Labs, we create open-source DIY neuroscience hardware and software.
+// Our mission is to make neuroscience affordable and accessible for everyone.
+// By supporting us with your purchase, you help spread innovation and open science.
+// Thank you for being part of this journey with us!
+
+/*
+ This example is adapted from the client and server code provided by MoThunderz
+ Firmware: https://github.com/mo-thunderz/Esp32BlePart2
+ YouTube video: https://www.youtube.com/watch?v=s3yoZa6kzus
+*/
+
+ #include <Arduino.h>
+ #include "BLEDevice.h"
+ //#include "BLEScan.h"
+ 
+ // Define UUIDs:
+ static BLEUUID serviceUUID("4fafc201-1fb5-459e-8fcc-c5c9c331914b");
+ static BLEUUID charUUID_1("beb5483e-36e1-4688-b7f5-ea07361b26a8");
+ static BLEUUID charUUID_2("1c95d5e3-d8f7-413a-bf3d-7a2e5d7be87e");
+ 
+ // Some variables to keep track on device connected
+ static boolean doConnect = false;
+ static boolean connected = false;
+ static boolean notify = false;
+ static boolean doScan = false;
+ // Define wheel control pins
+ const int leftWheelForward = 23;
+ const int leftWheelBackward = 24;
+ const int rightWheelForward = 8;
+ const int rightWheelBackward = 9;
+ // Define pointer for the BLE connection
+ static BLEAdvertisedDevice* myDevice;
+ BLERemoteCharacteristic* pRemoteChar_1;
+ BLERemoteCharacteristic* pRemoteChar_2;
+ 
+ // Callback function for Notify function
+ static void notifyCallback(BLERemoteCharacteristic* pBLERemoteCharacteristic,
+                            uint8_t* pData,
+                            size_t length,
+                            bool isNotify) {
+   if (pBLERemoteCharacteristic->getUUID().toString() == charUUID_1.toString()) {
+ 
+     // convert received bytes to integer
+     uint32_t counter = pData[0];
+     for (int i = 1; i < length; i++) {
+       counter = counter | (pData[i] << i * 8);
+     }
+ 
+     // print to Serial
+     Serial.print("Characteristic 1 (Notify) from server: ");
+     Serial.println(counter);//for debugging purposes
+    
+     // Control car movement based on received command
+     if (counter == 1) { // Command for left turn
+       digitalWrite(leftWheelForward, HIGH);
+       digitalWrite(leftWheelBackward, LOW);
+       digitalWrite(rightWheelForward, LOW);
+       digitalWrite(rightWheelBackward, LOW);
+ 
+     } else if (counter == 2) { // Command for right turn
+       digitalWrite(rightWheelForward, HIGH);
+       digitalWrite(rightWheelBackward, LOW);
+       digitalWrite(leftWheelForward, LOW);
+       digitalWrite(leftWheelBackward, LOW);
+     } else if (counter == 3) {  // Command to move forward
+       digitalWrite(leftWheelForward, HIGH);
+       digitalWrite(leftWheelBackward, LOW);
+       digitalWrite(rightWheelForward, HIGH);
+       digitalWrite(rightWheelBackward, LOW);
+     }
+     else if (counter == 4) { // Command to move backward
+       digitalWrite(leftWheelForward, LOW);
+       digitalWrite(leftWheelBackward, HIGH);
+       digitalWrite(rightWheelForward, LOW);
+       digitalWrite(rightWheelBackward, HIGH);
+     }else {// Default case: stop the car
+       digitalWrite(leftWheelForward, LOW);
+       digitalWrite(leftWheelBackward, LOW);
+       digitalWrite(rightWheelForward, LOW);
+       digitalWrite(rightWheelBackward, LOW);
+     }
+   }
+ }
+ 
+ // Callback function that is called whenever a client is connected or disconnected
+ class MyClientCallback : public BLEClientCallbacks {
+   void onConnect(BLEClient* pclient) {
+   }
+ 
+   void onDisconnect(BLEClient* pclient) {
+     connected = false;
+     Serial.println("onDisconnect");//for debugging purposes
+   }
+ };
+ 
+ // Function that is run whenever the server is connected
+ bool connectToServer() {
+   Serial.print("Forming a connection to ");
+   Serial.println(myDevice->getAddress().toString().c_str());
+ 
+   BLEClient* pClient = BLEDevice::createClient();
+   Serial.println(" - Created client");
+ 
+   pClient->setClientCallbacks(new MyClientCallback());
+ 
+   // Connect to the remove BLE Server.
+   pClient->connect(myDevice);  // if you pass BLEAdvertisedDevice instead of address, it will be recognized type of peer device address (public or private)
+   Serial.println(" - Connected to server");
+ 
+   // Obtain a reference to the service we are after in the remote BLE server.
+   BLERemoteService* pRemoteService = pClient->getService(serviceUUID);
+   if (pRemoteService == nullptr) {
+     Serial.print("Failed to find our service UUID: ");
+     Serial.println(serviceUUID.toString().c_str());
+     pClient->disconnect();
+     return false;
+   }
+   Serial.println(" - Found our service");
+ 
+   connected = true;
+   pRemoteChar_1 = pRemoteService->getCharacteristic(charUUID_1);
+   pRemoteChar_2 = pRemoteService->getCharacteristic(charUUID_2);
+   if (connectCharacteristic(pRemoteService, pRemoteChar_1) == false)
+     connected = false;
+   else if (connectCharacteristic(pRemoteService, pRemoteChar_2) == false)
+     connected = false;
path: BLE-BCI-Car/BLE-BCI-Car.ino
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**`connectCharacteristic` used before it is declared – compilation fails**

C++ requires a prior declaration. Either add a forward declaration or move the
function above `connectToServer`.

```diff
+// Forward declaration
+bool connectCharacteristic(BLERemoteService* pRemoteService,
+                           BLERemoteCharacteristic* l_BLERemoteChar);
```
Place this just before `connectToServer()`.

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-12T10:21:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -14,4 +14,5 @@ Neuro PlayGround (NPG) Lite firmware repository.
 | InfraRed (IR) reciever code to identify LG AC remote button commands | [IR-LG-Receive.ino](IR-LG-Receive/IR-LG-Receive.ino) |
 | IR signal send example code to control LG AC to toggle ON/OFF using user button | [IR-LG-Send.ino](IR-LG-Send/IR-LG-Send.ino) |
 | Brain Computer Interface (BCI) to toggle LG AC ON/OFF using EEG band (beta) power | [BCI-IR-Send.ino](BCI-IR-Send/BCI-IR-Send.ino) |
-
+| BLE client that receives notifications from the server and triggers GPIO to control the car | [BLE-BCI-Car.ino](BLE-BCI-Car/BLE-BCI-Car.ino) |
+| Brain Computer Interface (BCI) to drive car using EEG band (beta) power and EMG (envelope) | [BLE-BCI-Car-Remote.ino](BLE-BCI-Car-Remote/BLE-BCI-Car-Remote.ino) |
path: README.md
user: login: lorforlinux
body: ```suggestion
| BCI remote (server) to drive the BLE car using EEG band (beta) power and EMG (envelope) data | [BLE-BCI-Car-Remote.ino](BLE-BCI-Car-Remote/BLE-BCI-Car-Remote.ino) |
```
created_at: 2025-05-12T12:34:49Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,519 @@
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+// Copyright (c) 2025 Aman Maheshwari - Aman@upsidedownlabs.tech
+// Copyright (c) 2025 Upside Down Labs - contact@upsidedownlabs.tech
+
+// At Upside Down Labs, we create open-source DIY neuroscience hardware and software.
+// Our mission is to make neuroscience affordable and accessible for everyone.
+// By supporting us with your purchase, you help spread innovation and open science.
+// Thank you for being part of this journey with us!
+
+/*
+ This example is adapted from the client and server code provided by MoThunderz
+ Firmware: https://github.com/mo-thunderz/Esp32BlePart2
+ YouTube video: https://www.youtube.com/watch?v=s3yoZa6kzus
+*/
+
+#include <BLEDevice.h>
+#include <BLEServer.h>
+#include <BLEUtils.h>
+#include <BLE2902.h>
+#include <Arduino.h>
+#include "esp_dsp.h"
+
+// constants won't change. They're used here to set pin numbers:
+const int buttonPin = 9; // the number of the pushbutton pin
+const int ledPin = 7;    // the number of the LED pin
+
+// Variables will change:
+int ledState = HIGH;       // the current state of the output pin
+uint32_t buttonState;      // the current reading from the input pin
+int lastButtonState = LOW; // the previous reading from the input pin
+uint32_t bci_val = 0;      // EEG-based control value (0=stop, 3=forward)
+uint32_t emg1_val1 = 0;    // Left EMG control value (0=inactive, 1=left turn)
+uint32_t emg2_val2 = 0;    // Right EMG control value (0=inactive, 2=right turn)
+uint32_t bootback_val = 4; // Button toggle value (4=toggle state)
+
+// the following variables are unsigned longs because the time, measured in
+// milliseconds, will quickly become a bigger number than can be stored in an int.
+unsigned long lastDebounceTime = 0; // the last time the output pin was toggled
+unsigned long debounceDelay = 50;   // the debounce time; increase if the output flickers
+
+// Initialize all pointers
+BLEServer *pServer = NULL;                   // Pointer to the server
+BLECharacteristic *pCharacteristic_1 = NULL; // Pointer to Characteristic 1
+BLECharacteristic *pCharacteristic_2 = NULL; // Pointer to Characteristic 2
+BLEDescriptor *pDescr_1;                     // Pointer to Descriptor of Characteristic 1
+BLE2902 *pBLE2902_1;                         // Pointer to BLE2902 of Characteristic 1
+BLE2902 *pBLE2902_2;                         // Pointer to BLE2902 of Characteristic 2
+
+// Some variables to keep track on device connected
+bool deviceConnected = false;
+bool oldDeviceConnected = false;
+
+// Variable that will continuously be increased and written to the client
+uint32_t value = 0;
+
+// See the following for generating UUIDs:
+// https://www.uuidgenerator.net/
+// UUIDs used in this example:
+#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
+#define CHARACTERISTIC_UUID_1 "beb5483e-36e1-4688-b7f5-ea07361b26a8"
+#define CHARACTERISTIC_UUID_2 "1c95d5e3-d8f7-413a-bf3d-7a2e5d7be87e"
+
+// Callback function that is called whenever a client is connected or disconnected
+class MyServerCallbacks : public BLEServerCallbacks
+{
+    void onConnect(BLEServer *pServer)
+    {
+        deviceConnected = true;
+    };
+
+    void onDisconnect(BLEServer *pServer)
+    {
+        deviceConnected = false;
+    }
+};
+
+// ----------------- USER CONFIGURATION -----------------
+#define SAMPLE_RATE 512 // samples per second
+#define FFT_SIZE 512    // must be a power of two
+#define BAUD_RATE 115200
+#define INPUT_PIN1 A0 // EEG input pin
+#define INPUT_PIN2 A1 // Left hand EMG input pin
+#define INPUT_PIN3 A2 // Right hand EMG input pin
+
+// EEG bands (Hz)
+#define DELTA_LOW 0.5f
+#define DELTA_HIGH 4.0f
+#define THETA_LOW 4.0f
+#define THETA_HIGH 8.0f
+#define ALPHA_LOW 8.0f
+#define ALPHA_HIGH 13.0f
+#define BETA_LOW 13.0f
+#define BETA_HIGH 30.0f
+#define GAMMA_LOW 30.0f
+#define GAMMA_HIGH 45.0f
+
+#define SMOOTHING_FACTOR 0.63f
+#define EPS 1e-7f
+
+// ----------------- BUFFERS & TYPES -----------------
+float inputBuffer[FFT_SIZE];
+float powerSpectrum[FFT_SIZE / 2];
+
+// For two-real FFT trick
+__attribute__((aligned(16))) float y_cf[FFT_SIZE * 2];
+float *y1_cf = &y_cf[0];
+
+typedef struct
+{
+    float delta, theta, alpha, beta, gamma, total;
+} BandpowerResults;
+
+BandpowerResults smoothedPowers = {0};
+
+// ----------------- FILTER CLASSES -----------------
+// NEW: Notch filter for 50Hz noise removal
path: BLE-BCI-Car-Remote/BLE-BCI-Car-Remote.ino
user: login: lorforlinux
body: ```suggestion
// ----------------- FILTER CLASSES -----------------
// For 50Hz AC noise removal 
// Band-Stop Butterworth IIR digital filter, generated using filter_gen.py.
// Sampling rate: 500.0 Hz, frequency: [48.0, 52.0] Hz.
// Filter is order 2, implemented as second-order sections (biquads).
// Reference: https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.butter.html
```
created_at: 2025-05-12T12:41:17Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,523 @@
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+// Copyright (c) 2025 Aman Maheshwari - Aman@upsidedownlabs.tech
+// Copyright (c) 2025 Upside Down Labs - contact@upsidedownlabs.tech
+
+// At Upside Down Labs, we create open-source DIY neuroscience hardware and software.
+// Our mission is to make neuroscience affordable and accessible for everyone.
+// By supporting us with your purchase, you help spread innovation and open science.
+// Thank you for being part of this journey with us!
+
+/*
+ This example is adapted from the client and server code provided by MoThunderz
+ Firmware: https://github.com/mo-thunderz/Esp32BlePart2
+ YouTube video: https://www.youtube.com/watch?v=s3yoZa6kzus
+*/
+
+#include <BLEDevice.h>
+#include <BLEServer.h>
+#include <BLEUtils.h>
+#include <BLE2902.h>
+#include <Arduino.h>
+#include "esp_dsp.h"
+
+// constants won't change. They're used here to set pin numbers:
+const int buttonPin = 9; // the number of the pushbutton pin
+const int ledPin = 7;    // the number of the LED pin
+
+// Variables will change:
+int ledState = HIGH;       // the current state of the output pin
+uint32_t buttonState;      // the current reading from the input pin
+int lastButtonState = LOW; // the previous reading from the input pin
+uint32_t bci_val = 0;      // EEG-based control value (0=stop, 3=forward)
+uint32_t emg1_val1 = 0;    // Left EMG control value (0=inactive, 1=left turn)
+uint32_t emg2_val2 = 0;    // Right EMG control value (0=inactive, 2=right turn)
+uint32_t bootback_val = 4; // Button toggle value (4=toggle state)
+
+// the following variables are unsigned longs because the time, measured in
+// milliseconds, will quickly become a bigger number than can be stored in an int.
+unsigned long lastDebounceTime = 0; // the last time the output pin was toggled
+unsigned long debounceDelay = 50;   // the debounce time; increase if the output flickers
+
+// Initialize all pointers
+BLEServer *pServer = NULL;                   // Pointer to the server
+BLECharacteristic *pCharacteristic_1 = NULL; // Pointer to Characteristic 1
+BLECharacteristic *pCharacteristic_2 = NULL; // Pointer to Characteristic 2
+BLEDescriptor *pDescr_1;                     // Pointer to Descriptor of Characteristic 1
+BLE2902 *pBLE2902_1;                         // Pointer to BLE2902 of Characteristic 1
+BLE2902 *pBLE2902_2;                         // Pointer to BLE2902 of Characteristic 2
+
+// Some variables to keep track on device connected
+bool deviceConnected = false;
+bool oldDeviceConnected = false;
+
+// Variable that will continuously be increased and written to the client
+uint32_t value = 0;
+
+// See the following for generating UUIDs:
+// https://www.uuidgenerator.net/
+// UUIDs used in this example:
+#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
+#define CHARACTERISTIC_UUID_1 "beb5483e-36e1-4688-b7f5-ea07361b26a8"
+#define CHARACTERISTIC_UUID_2 "1c95d5e3-d8f7-413a-bf3d-7a2e5d7be87e"
+
+// Callback function that is called whenever a client is connected or disconnected
+class MyServerCallbacks : public BLEServerCallbacks
+{
+    void onConnect(BLEServer *pServer)
+    {
+        deviceConnected = true;
+    };
+
+    void onDisconnect(BLEServer *pServer)
+    {
+        deviceConnected = false;
+    }
+};
+
+// ----------------- USER CONFIGURATION -----------------
+#define SAMPLE_RATE 512 // samples per second
+#define FFT_SIZE 512    // must be a power of two
+#define BAUD_RATE 115200
+#define INPUT_PIN1 A0 // EEG input pin
+#define INPUT_PIN2 A1 // Left hand EMG input pin
+#define INPUT_PIN3 A2 // Right hand EMG input pin
+
+// EEG bands (Hz)
+#define DELTA_LOW 0.5f
+#define DELTA_HIGH 4.0f
+#define THETA_LOW 4.0f
+#define THETA_HIGH 8.0f
+#define ALPHA_LOW 8.0f
+#define ALPHA_HIGH 13.0f
+#define BETA_LOW 13.0f
+#define BETA_HIGH 30.0f
+#define GAMMA_LOW 30.0f
+#define GAMMA_HIGH 45.0f
+
+#define SMOOTHING_FACTOR 0.63f
+#define EPS 1e-7f
+
+// ----------------- BUFFERS & TYPES -----------------
+float inputBuffer[FFT_SIZE];
+float powerSpectrum[FFT_SIZE / 2];
+
+// For two-real FFT trick
+__attribute__((aligned(16))) float y_cf[FFT_SIZE * 2];
+float *y1_cf = &y_cf[0];
+
+typedef struct
+{
+    float delta, theta, alpha, beta, gamma, total;
+} BandpowerResults;
+
+BandpowerResults smoothedPowers = {0};
+
+// ----------------- FILTER CLASSES -----------------
+// For 50Hz AC noise removal 
+// Band-Stop Butterworth IIR digital filter, generated using filter_gen.py.
+// Sampling rate: 500.0 Hz, frequency: [48.0, 52.0] Hz.
+// Filter is order 2, implemented as second-order sections (biquads).
+// Reference: https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.butter.html
+class NotchFilter
+{
+private:
+    struct BiquadState
+    {
+        float z1 = 0;
+        float z2 = 0;
+    };
+
+    BiquadState state1;
+    BiquadState state2;
+
+public:
+    float process(float input)
+    {
+        float output = input;
+
+        // First biquad section
+        float x = output - (-1.56858163f * state1.z1) - (0.96424138f * state1.z2);
+        output = 0.96508099f * x + (-1.56202714f * state1.z1) + (0.96508099f * state1.z2);
+        state1.z2 = state1.z1;
+        state1.z1 = x;
+
+        // Second biquad section
+        x = output - (-1.61100358f * state2.z1) - (0.96592171f * state2.z2);
+        output = 1.00000000f * x + (-1.61854514f * state2.z1) + (1.00000000f * state2.z2);
+        state2.z2 = state2.z1;
+        state2.z1 = x;
+
+        return output;
+    }
+
+    void reset()
+    {
+        state1.z1 = state1.z2 = 0;
+        state2.z1 = state2.z2 = 0;
+    }
+};
+
+// NEW: High-pass filter for EMG signals
+class EMGHighPassFilter
path: BLE-BCI-Car-Remote/BLE-BCI-Car-Remote.ino
user: login: lorforlinux
body: ```suggestion


// High-Pass Butterworth IIR digital filter, generated using filter_gen.py.
// Sampling rate: 500.0 Hz, frequency: 70.0 Hz.
// Filter is order 2, implemented as second-order sections (biquads).
// Reference: https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.butter.html
class EMGHighPassFilter
```
created_at: 2025-05-12T12:42:16Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,527 @@
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+// Copyright (c) 2025 Aman Maheshwari - Aman@upsidedownlabs.tech
+// Copyright (c) 2025 Upside Down Labs - contact@upsidedownlabs.tech
+
+// At Upside Down Labs, we create open-source DIY neuroscience hardware and software.
+// Our mission is to make neuroscience affordable and accessible for everyone.
+// By supporting us with your purchase, you help spread innovation and open science.
+// Thank you for being part of this journey with us!
+
+/*
+ This example is adapted from the client and server code provided by MoThunderz
+ Firmware: https://github.com/mo-thunderz/Esp32BlePart2
+ YouTube video: https://www.youtube.com/watch?v=s3yoZa6kzus
+*/
+
+#include <BLEDevice.h>
+#include <BLEServer.h>
+#include <BLEUtils.h>
+#include <BLE2902.h>
+#include <Arduino.h>
+#include "esp_dsp.h"
+
+// constants won't change. They're used here to set pin numbers:
+const int buttonPin = 9; // the number of the pushbutton pin
+const int ledPin = 7;    // the number of the LED pin
+
+// Variables will change:
+int ledState = HIGH;       // the current state of the output pin
+uint32_t buttonState;      // the current reading from the input pin
+int lastButtonState = LOW; // the previous reading from the input pin
+uint32_t bci_val = 0;      // EEG-based control value (0=stop, 3=forward)
+uint32_t emg1_val1 = 0;    // Left EMG control value (0=inactive, 1=left turn)
+uint32_t emg2_val2 = 0;    // Right EMG control value (0=inactive, 2=right turn)
+uint32_t bootback_val = 4; // Button toggle value (4=toggle state)
+
+// the following variables are unsigned longs because the time, measured in
+// milliseconds, will quickly become a bigger number than can be stored in an int.
+unsigned long lastDebounceTime = 0; // the last time the output pin was toggled
+unsigned long debounceDelay = 50;   // the debounce time; increase if the output flickers
+
+// Initialize all pointers
+BLEServer *pServer = NULL;                   // Pointer to the server
+BLECharacteristic *pCharacteristic_1 = NULL; // Pointer to Characteristic 1
+BLECharacteristic *pCharacteristic_2 = NULL; // Pointer to Characteristic 2
+BLEDescriptor *pDescr_1;                     // Pointer to Descriptor of Characteristic 1
+BLE2902 *pBLE2902_1;                         // Pointer to BLE2902 of Characteristic 1
+BLE2902 *pBLE2902_2;                         // Pointer to BLE2902 of Characteristic 2
+
+// Some variables to keep track on device connected
+bool deviceConnected = false;
+bool oldDeviceConnected = false;
+
+// Variable that will continuously be increased and written to the client
+uint32_t value = 0;
+
+// See the following for generating UUIDs:
+// https://www.uuidgenerator.net/
+// UUIDs used in this example:
+#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
+#define CHARACTERISTIC_UUID_1 "beb5483e-36e1-4688-b7f5-ea07361b26a8"
+#define CHARACTERISTIC_UUID_2 "1c95d5e3-d8f7-413a-bf3d-7a2e5d7be87e"
+
+// Callback function that is called whenever a client is connected or disconnected
+class MyServerCallbacks : public BLEServerCallbacks
+{
+    void onConnect(BLEServer *pServer)
+    {
+        deviceConnected = true;
+    };
+
+    void onDisconnect(BLEServer *pServer)
+    {
+        deviceConnected = false;
+    }
+};
+
+// ----------------- USER CONFIGURATION -----------------
+#define SAMPLE_RATE 512 // samples per second
+#define FFT_SIZE 512    // must be a power of two
+#define BAUD_RATE 115200
+#define INPUT_PIN1 A0 // EEG input pin
+#define INPUT_PIN2 A1 // Left hand EMG input pin
+#define INPUT_PIN3 A2 // Right hand EMG input pin
+
+// EEG bands (Hz)
+#define DELTA_LOW 0.5f
+#define DELTA_HIGH 4.0f
+#define THETA_LOW 4.0f
+#define THETA_HIGH 8.0f
+#define ALPHA_LOW 8.0f
+#define ALPHA_HIGH 13.0f
+#define BETA_LOW 13.0f
+#define BETA_HIGH 30.0f
+#define GAMMA_LOW 30.0f
+#define GAMMA_HIGH 45.0f
+
+#define SMOOTHING_FACTOR 0.63f
+#define EPS 1e-7f
+
+// ----------------- BUFFERS & TYPES -----------------
+float inputBuffer[FFT_SIZE];
+float powerSpectrum[FFT_SIZE / 2];
+
+// For two-real FFT trick
+__attribute__((aligned(16))) float y_cf[FFT_SIZE * 2];
+float *y1_cf = &y_cf[0];
+
+typedef struct
+{
+    float delta, theta, alpha, beta, gamma, total;
+} BandpowerResults;
+
+BandpowerResults smoothedPowers = {0};
+
+// ----------------- FILTER CLASSES -----------------
+// For 50Hz AC noise removal 
+// Band-Stop Butterworth IIR digital filter, generated using filter_gen.py.
+// Sampling rate: 500.0 Hz, frequency: [48.0, 52.0] Hz.
+// Filter is order 2, implemented as second-order sections (biquads).
+// Reference: https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.butter.html
+class NotchFilter
+{
+private:
+    struct BiquadState
+    {
+        float z1 = 0;
+        float z2 = 0;
+    };
+
+    BiquadState state1;
+    BiquadState state2;
+
+public:
+    float process(float input)
+    {
+        float output = input;
+
+        // First biquad section
+        float x = output - (-1.56858163f * state1.z1) - (0.96424138f * state1.z2);
+        output = 0.96508099f * x + (-1.56202714f * state1.z1) + (0.96508099f * state1.z2);
+        state1.z2 = state1.z1;
+        state1.z1 = x;
+
+        // Second biquad section
+        x = output - (-1.61100358f * state2.z1) - (0.96592171f * state2.z2);
+        output = 1.00000000f * x + (-1.61854514f * state2.z1) + (1.00000000f * state2.z2);
+        state2.z2 = state2.z1;
+        state2.z1 = x;
+
+        return output;
+    }
+
+    void reset()
+    {
+        state1.z1 = state1.z2 = 0;
+        state2.z1 = state2.z2 = 0;
+    }
+};
+
+
+// High-Pass Butterworth IIR digital filter, generated using filter_gen.py.
+// Sampling rate: 500.0 Hz, frequency: 70.0 Hz.
+// Filter is order 2, implemented as second-order sections (biquads).
+// Reference: https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.butter.html
+class EMGHighPassFilter
+{
+private:
+    // Filter state for a single channel
+    double z1 = 0.0;
+    double z2 = 0.0;
+
+public:
+    // Process a single sample
+    double process(double input)
+    {
+        const double x = input - -0.82523238 * z1 - 0.29463653 * z2;
+        const double output = 0.52996723 * x + -1.05993445 * z1 + 0.52996723 * z2;
+
+        // Update state
+        z2 = z1;
+        z1 = x;
+
+        return output;
+    }
+
+    // Reset filter state
+    void reset()
+    {
+        z1 = 0.0;
+        z2 = 0.0;
+    }
+};
+class EnvelopeFilter
+{
+private:
+    std::vector<double> circularBuffer;
+    double sum = 0.0;
+    int dataIndex = 0;
+    const int bufferSize;
path: BLE-BCI-Car-Remote/BLE-BCI-Car-Remote.ino
user: login: lorforlinux
body: ```suggestion
    }
};

// High-Pass Butterworth IIR digital filter, generated using filter_gen.py.
// Sampling rate: 500.0 Hz, frequency: 70.0 Hz.
// Filter is order 2, implemented as second-order sections (biquads).
// Reference: https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.butter.html
class EMGHighPassFilter
{
private:
    // Filter state for a single channel
    double z1 = 0.0;
    double z2 = 0.0;

public:
    // Process a single sample
    double process(double input)
    {
        const double x = input - -0.82523238 * z1 - 0.29463653 * z2;
        const double output = 0.52996723 * x + -1.05993445 * z1 + 0.52996723 * z2;

        // Update state
        z2 = z1;
        z1 = x;

        return output;
    }

    // Reset filter state
    void reset()
    {
        z1 = 0.0;
        z2 = 0.0;
    }
};

// Class to calculate EMG Envelope
class EnvelopeFilter
{
private:
    std::vector<double> circularBuffer;
    double sum = 0.0;
    int dataIndex = 0;
    const int bufferSize;
```
created_at: 2025-05-12T12:43:30Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,528 @@
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+// Copyright (c) 2025 Aman Maheshwari - Aman@upsidedownlabs.tech
+// Copyright (c) 2025 Upside Down Labs - contact@upsidedownlabs.tech
+
+// At Upside Down Labs, we create open-source DIY neuroscience hardware and software.
+// Our mission is to make neuroscience affordable and accessible for everyone.
+// By supporting us with your purchase, you help spread innovation and open science.
+// Thank you for being part of this journey with us!
+
+/*
+ This example is adapted from the client and server code provided by MoThunderz
+ Firmware: https://github.com/mo-thunderz/Esp32BlePart2
+ YouTube video: https://www.youtube.com/watch?v=s3yoZa6kzus
+*/
+
+#include <BLEDevice.h>
+#include <BLEServer.h>
+#include <BLEUtils.h>
+#include <BLE2902.h>
+#include <Arduino.h>
+#include "esp_dsp.h"
+
+// constants won't change. They're used here to set pin numbers:
+const int buttonPin = 9; // the number of the pushbutton pin
+const int ledPin = 7;    // the number of the LED pin
+
+// Variables will change:
+int ledState = HIGH;       // the current state of the output pin
+uint32_t buttonState;      // the current reading from the input pin
+int lastButtonState = LOW; // the previous reading from the input pin
+uint32_t bci_val = 0;      // EEG-based control value (0=stop, 3=forward)
+uint32_t emg1_val1 = 0;    // Left EMG control value (0=inactive, 1=left turn)
+uint32_t emg2_val2 = 0;    // Right EMG control value (0=inactive, 2=right turn)
+uint32_t bootback_val = 4; // Button toggle value (4=toggle state)
+
+// the following variables are unsigned longs because the time, measured in
+// milliseconds, will quickly become a bigger number than can be stored in an int.
+unsigned long lastDebounceTime = 0; // the last time the output pin was toggled
+unsigned long debounceDelay = 50;   // the debounce time; increase if the output flickers
+
+// Initialize all pointers
+BLEServer *pServer = NULL;                   // Pointer to the server
+BLECharacteristic *pCharacteristic_1 = NULL; // Pointer to Characteristic 1
+BLECharacteristic *pCharacteristic_2 = NULL; // Pointer to Characteristic 2
+BLEDescriptor *pDescr_1;                     // Pointer to Descriptor of Characteristic 1
+BLE2902 *pBLE2902_1;                         // Pointer to BLE2902 of Characteristic 1
+BLE2902 *pBLE2902_2;                         // Pointer to BLE2902 of Characteristic 2
+
+// Some variables to keep track on device connected
+bool deviceConnected = false;
+bool oldDeviceConnected = false;
+
+// Variable that will continuously be increased and written to the client
+uint32_t value = 0;
+
+// See the following for generating UUIDs:
+// https://www.uuidgenerator.net/
+// UUIDs used in this example:
+#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
+#define CHARACTERISTIC_UUID_1 "beb5483e-36e1-4688-b7f5-ea07361b26a8"
+#define CHARACTERISTIC_UUID_2 "1c95d5e3-d8f7-413a-bf3d-7a2e5d7be87e"
+
+// Callback function that is called whenever a client is connected or disconnected
+class MyServerCallbacks : public BLEServerCallbacks
+{
+    void onConnect(BLEServer *pServer)
+    {
+        deviceConnected = true;
+    };
+
+    void onDisconnect(BLEServer *pServer)
+    {
+        deviceConnected = false;
+    }
+};
+
+// ----------------- USER CONFIGURATION -----------------
+#define SAMPLE_RATE 512 // samples per second
+#define FFT_SIZE 512    // must be a power of two
+#define BAUD_RATE 115200
+#define INPUT_PIN1 A0 // EEG input pin
+#define INPUT_PIN2 A1 // Left hand EMG input pin
+#define INPUT_PIN3 A2 // Right hand EMG input pin
+
+// EEG bands (Hz)
+#define DELTA_LOW 0.5f
+#define DELTA_HIGH 4.0f
+#define THETA_LOW 4.0f
+#define THETA_HIGH 8.0f
+#define ALPHA_LOW 8.0f
+#define ALPHA_HIGH 13.0f
+#define BETA_LOW 13.0f
+#define BETA_HIGH 30.0f
+#define GAMMA_LOW 30.0f
+#define GAMMA_HIGH 45.0f
+
+#define SMOOTHING_FACTOR 0.63f
+#define EPS 1e-7f
+
+// ----------------- BUFFERS & TYPES -----------------
+float inputBuffer[FFT_SIZE];
+float powerSpectrum[FFT_SIZE / 2];
+
+// For two-real FFT trick
+__attribute__((aligned(16))) float y_cf[FFT_SIZE * 2];
+float *y1_cf = &y_cf[0];
+
+typedef struct
+{
+    float delta, theta, alpha, beta, gamma, total;
+} BandpowerResults;
+
+BandpowerResults smoothedPowers = {0};
+
+// ----------------- FILTER CLASSES -----------------
+// For 50Hz AC noise removal 
+// Band-Stop Butterworth IIR digital filter, generated using filter_gen.py.
+// Sampling rate: 500.0 Hz, frequency: [48.0, 52.0] Hz.
+// Filter is order 2, implemented as second-order sections (biquads).
+// Reference: https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.butter.html
+class NotchFilter
+{
+private:
+    struct BiquadState
+    {
+        float z1 = 0;
+        float z2 = 0;
+    };
+
+    BiquadState state1;
+    BiquadState state2;
+
+public:
+    float process(float input)
+    {
+        float output = input;
+
+        // First biquad section
+        float x = output - (-1.56858163f * state1.z1) - (0.96424138f * state1.z2);
+        output = 0.96508099f * x + (-1.56202714f * state1.z1) + (0.96508099f * state1.z2);
+        state1.z2 = state1.z1;
+        state1.z1 = x;
+
+        // Second biquad section
+        x = output - (-1.61100358f * state2.z1) - (0.96592171f * state2.z2);
+        output = 1.00000000f * x + (-1.61854514f * state2.z1) + (1.00000000f * state2.z2);
+        state2.z2 = state2.z1;
+        state2.z1 = x;
+
+        return output;
+    }
+
+    void reset()
+    {
+        state1.z1 = state1.z2 = 0;
+        state2.z1 = state2.z2 = 0;
+    }
+};
+
+// High-Pass Butterworth IIR digital filter, generated using filter_gen.py.
+// Sampling rate: 500.0 Hz, frequency: 70.0 Hz.
+// Filter is order 2, implemented as second-order sections (biquads).
+// Reference: https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.butter.html
+class EMGHighPassFilter
+{
+private:
+    // Filter state for a single channel
+    double z1 = 0.0;
+    double z2 = 0.0;
+
+public:
+    // Process a single sample
+    double process(double input)
+    {
+        const double x = input - -0.82523238 * z1 - 0.29463653 * z2;
+        const double output = 0.52996723 * x + -1.05993445 * z1 + 0.52996723 * z2;
+
+        // Update state
+        z2 = z1;
+        z1 = x;
+
+        return output;
+    }
+
+    // Reset filter state
+    void reset()
+    {
+        z1 = 0.0;
+        z2 = 0.0;
+    }
+};
+
+// Class to calculate EMG Envelope
+class EnvelopeFilter
+{
+private:
+    std::vector<double> circularBuffer;
+    double sum = 0.0;
+    int dataIndex = 0;
+    const int bufferSize;
+
+public:
+    EnvelopeFilter(int bufferSize)
+        : bufferSize(bufferSize)
+    {
+        circularBuffer.resize(bufferSize, 0.0);
+    }
+
+    double getEnvelope(double absEmg)
+    {
+        sum -= circularBuffer[dataIndex];
+        sum += absEmg;
+        circularBuffer[dataIndex] = absEmg;
+        dataIndex = (dataIndex + 1) % bufferSize;
+        return (sum / bufferSize);
+    }
+};
+
+// NEW: Low-pass filter for EEG signals
+float EEGFilter(float input)
+{
+    float output = input;
+    {
+        static float z1 = 0, z2 = 0;
+        float x = output - -1.22465158 * z1 - 0.45044543 * z2;
+        output = 0.05644846 * x + 0.11289692 * z1 + 0.05644846 * z2;
+        z2 = z1;
+        z1 = x;
+    }
+    return output;
+}
+NotchFilter filters[3];             // Notch filters for all 3 input channels
+EMGHighPassFilter emgfilters[2];    // High-pass filters for EMG channels
+EnvelopeFilter Envelopefilter1(16); // Envelope detector for left EMG
+EnvelopeFilter Envelopefilter2(16); // Envelope detector for right EMG
+
+// ----------------- BANDPOWER & SMOOTHING -----------------
+BandpowerResults calculateBandpower(float *ps, float binRes, int halfSize)
+{
+    BandpowerResults r = {0};
+    for (int i = 1; i < halfSize; i++)
+    {
+        float freq = i * binRes;
+        float p = ps[i];
+        r.total += p;
+        if (freq >= DELTA_LOW && freq < DELTA_HIGH)
+            r.delta += p;
+        else if (freq >= THETA_LOW && freq < THETA_HIGH)
+            r.theta += p;
+        else if (freq >= ALPHA_LOW && freq < ALPHA_HIGH)
+            r.alpha += p;
+        else if (freq >= BETA_LOW && freq < BETA_HIGH)
+            r.beta += p;
+        else if (freq >= GAMMA_LOW && freq < GAMMA_HIGH)
+            r.gamma += p;
+    }
+    return r;
+}
+
+void smoothBandpower(const BandpowerResults *raw, BandpowerResults *s)
+{
+    s->delta = SMOOTHING_FACTOR * raw->delta + (1 - SMOOTHING_FACTOR) * s->delta;
+    s->theta = SMOOTHING_FACTOR * raw->theta + (1 - SMOOTHING_FACTOR) * s->theta;
+    s->alpha = SMOOTHING_FACTOR * raw->alpha + (1 - SMOOTHING_FACTOR) * s->alpha;
+    s->beta = SMOOTHING_FACTOR * raw->beta + (1 - SMOOTHING_FACTOR) * s->beta;
+    s->gamma = SMOOTHING_FACTOR * raw->gamma + (1 - SMOOTHING_FACTOR) * s->gamma;
+    s->total = SMOOTHING_FACTOR * raw->total + (1 - SMOOTHING_FACTOR) * s->total;
+}
+
+// ----------------- DSP FFT SETUP -----------------
+void initFFT()
+{
+    // initialize esp-dsp real-FFT (two-real trick)
+    esp_err_t err = dsps_fft2r_init_fc32(NULL, FFT_SIZE);
+    if (err != ESP_OK)
+    {
+        Serial.println("FFT init failed");
+        while (1)
+            delay(10);
+    }
+}
+
+// ----------------- FFT + BANDPOWER + PEAK -----------------
+void processFFT()
+{
+    // pack real→complex: real=inputBuffer, imag=0
+    for (int i = 0; i < FFT_SIZE; i++)
+    {
+        y_cf[2 * i] = inputBuffer[i];
+        y_cf[2 * i + 1] = 0.0f;
+    }
+
+    // FFT
+    dsps_fft2r_fc32(y_cf, FFT_SIZE);
+    dsps_bit_rev_fc32(y_cf, FFT_SIZE);
+    dsps_cplx2reC_fc32(y_cf, FFT_SIZE);
+
+    // magnitude² spectrum
+    int half = FFT_SIZE / 2;
+    for (int i = 0; i < half; i++)
+    {
+        float re = y1_cf[2 * i];
+        float im = y1_cf[2 * i + 1];
+        powerSpectrum[i] = re * re + im * im;
+    }
+
+    // detect peak bin (skip i=0)
+    int maxIdx = 1;
+    float maxP = powerSpectrum[1];
+    for (int i = 2; i < half; i++)
+    {
+        if (powerSpectrum[i] > maxP)
+        {
+            maxP = powerSpectrum[i];
+            maxIdx = i;
+        }
+    }
+    float binRes = float(SAMPLE_RATE) / FFT_SIZE;
+    float peakHz = maxIdx * binRes;
+
+    // bandpower & smoothing
+    BandpowerResults raw = calculateBandpower(powerSpectrum, binRes, half);
+    smoothBandpower(&raw, &smoothedPowers);
+    float T = smoothedPowers.total + EPS;
+    Serial.println(((smoothedPowers.beta / T) * 100)); // for debugging purpose only
+
+    // If the power exceeds the threshold (set as 2% of the total power), the threshold value can be adjusted based on your beta parameters.
+    if (((smoothedPowers.beta / T) * 100) > 2)
+    {
+        bci_val = 3;
+        Serial.println("send 3");
+        pCharacteristic_1->setValue(bci_val); // for forward moving car
+        pCharacteristic_1->notify();
+        digitalWrite(7, HIGH); // Visual feedback
+    }
+    else
+    {
+        bci_val = 0;
+        pCharacteristic_1->setValue(bci_val);
+        pCharacteristic_1->notify();
+        digitalWrite(7, LOW);
+    }
+}
+
+void setup()
+{
+    Serial.begin(BAUD_RATE);
+    while (!Serial)
+    {
+        delay(1);
+    }
+    pinMode(INPUT_PIN1, INPUT);
+    pinMode(INPUT_PIN2, INPUT);
+    pinMode(INPUT_PIN3, INPUT);
+
+    pinMode(7, OUTPUT);
+
+    initFFT();
+    pinMode(buttonPin, INPUT_PULLUP);
+    pinMode(ledPin, OUTPUT);
+
+    // set initial LED state
+    digitalWrite(ledPin, ledState);
+    // Create the BLE Device
+    BLEDevice::init("ESP32");
+
+    // Create the BLE Server
+    pServer = BLEDevice::createServer();
+    pServer->setCallbacks(new MyServerCallbacks());
+
+    // Create the BLE Service
+    BLEService *pService = pServer->createService(SERVICE_UUID);
+
+    // Create a BLE Characteristic
+    pCharacteristic_1 = pService->createCharacteristic(
+        CHARACTERISTIC_UUID_1,
+        BLECharacteristic::PROPERTY_NOTIFY);
+
+    pCharacteristic_2 = pService->createCharacteristic(
+        CHARACTERISTIC_UUID_2,
+        BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_NOTIFY);
+
+    // Create a BLE Descriptor
+    pDescr_1 = new BLEDescriptor((uint16_t)0x2901);
+    pDescr_1->setValue("A very interesting variable");
+    pCharacteristic_1->addDescriptor(pDescr_1);
+
+    // Add the BLE2902 Descriptor because we are using "PROPERTY_NOTIFY"
+    pBLE2902_1 = new BLE2902();
+    pBLE2902_1->setNotifications(true);
+    pCharacteristic_1->addDescriptor(pBLE2902_1);
+
+    pBLE2902_2 = new BLE2902();
+    pBLE2902_2->setNotifications(true);
+    pCharacteristic_2->addDescriptor(pBLE2902_2);
+
+    // Start the service
+    pService->start();
+
+    // Start advertising
+    BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
+    pAdvertising->addServiceUUID(SERVICE_UUID);
+    pAdvertising->setScanResponse(false);
+    pAdvertising->setMinPreferred(0x0); // set value to 0x00 to not advertise this parameter
+    BLEDevice::startAdvertising();
+    Serial.println("Waiting a client connection to notify...");
+}
+
+void loop()
+{
+    static uint16_t idx = 0;
+    static unsigned long lastMicros = micros();
+    unsigned long now = micros(), dt = now - lastMicros;
+    lastMicros = now;
+
+    static long timer = 0;
+    timer -= dt;
+    if (timer <= 0)
+    {
+        timer += 1000000L / SAMPLE_RATE;
+        int raw1 = analogRead(INPUT_PIN1);
+        int raw2 = analogRead(INPUT_PIN2);
+        int raw3 = analogRead(INPUT_PIN3);
+
+        float filteeg = EEGFilter(filters[0].process(raw1));
+        float filtemg1 = emgfilters[0].process(filters[1].process(raw2));
+        float filtemg2 = emgfilters[1].process(filters[2].process(raw3));
+        inputBuffer[idx++] = filteeg;
+
+        float env1 = Envelopefilter1.getEnvelope(abs(filtemg1));
+        float env2 = Envelopefilter2.getEnvelope(abs(filtemg2));
+        
+        // If `env1` exceeds 150, trigger left turn command (send value 1).the threshold value can be adjusted based on your emg parameters.
+        if (env1 > 150)
+        {
+            emg1_val1 = 1;
+            Serial.println("sent 1");               // for debugging purpose only
+            pCharacteristic_1->setValue(emg1_val1); // for left turn car
+            pCharacteristic_1->notify();
+        }
+        // If `env2` exceeds 150, trigger right turn command (send value 2).the threshold value can be adjusted based on your emg parameters.
+        else if (env2 > 150)
+        {
+            emg2_val2 = 2;
+            Serial.println("sent 2");               // for debugging purpose only
+            pCharacteristic_1->setValue(emg2_val2); // for right turn car
+            pCharacteristic_1->notify();
+        }
+    }
+
+    if (idx >= FFT_SIZE)
+    {
+        processFFT();
+        idx = 0;
+    }
path: BLE-BCI-Car-Remote/BLE-BCI-Car-Remote.ino
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Timer-based sampling has potential drift issues.**

The sampling timing mechanism can drift over time, affecting signal processing accuracy.


Consider using hardware timers or a more precise timing mechanism to ensure consistent sampling:

```diff
-    static long timer = 0;
-    timer -= dt;
-    if (timer <= 0)
-    {
-        timer += 1000000L / SAMPLE_RATE;
+    // For more consistent timing
+    static unsigned long nextSampleTime = 0;
+    if (now >= nextSampleTime)
+    {
+        nextSampleTime = now + (1000000L / SAMPLE_RATE);
```

Additionally, your EMG and EEG sampling rates appear to mismatch (EEG gets processed through FFT at 512Hz but all signals are sampled in the same timing loop). Consider clarifying the intended sampling rates for each type of signal.


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
void loop()
{
    static uint16_t idx = 0;
    static unsigned long lastMicros = micros();
    unsigned long now = micros(), dt = now - lastMicros;
    lastMicros = now;

    // For more consistent timing
    static unsigned long nextSampleTime = 0;
    if (now >= nextSampleTime)
    {
        nextSampleTime = now + (1000000L / SAMPLE_RATE);
        int raw1 = analogRead(INPUT_PIN1);
        int raw2 = analogRead(INPUT_PIN2);
        int raw3 = analogRead(INPUT_PIN3);

        float filteeg    = EEGFilter(  filters[0].process(raw1));
        float filtemg1   = emgfilters[0].process(filters[1].process(raw2));
        float filtemg2   = emgfilters[1].process(filters[2].process(raw3));
        inputBuffer[idx++] = filteeg;

        float env1 = Envelopefilter1.getEnvelope(abs(filtemg1));
        float env2 = Envelopefilter2.getEnvelope(abs(filtemg2));
        
        // If `env1` exceeds 150, trigger left turn
        if (env1 > 150)
        {
            emg1_val1 = 1;
            Serial.println("sent 1");               
            pCharacteristic_1->setValue(emg1_val1);
            pCharacteristic_1->notify();
        }
        // If `env2` exceeds 150, trigger right turn
        else if (env2 > 150)
        {
            emg2_val2 = 2;
            Serial.println("sent 2");               
            pCharacteristic_1->setValue(emg2_val2);
            pCharacteristic_1->notify();
        }
    }

    if (idx >= FFT_SIZE)
    {
        processFFT();
        idx = 0;
    }
}
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-12T12:50:52Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,519 @@
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+// Copyright (c) 2025 Aman Maheshwari - Aman@upsidedownlabs.tech
+// Copyright (c) 2025 Upside Down Labs - contact@upsidedownlabs.tech
+
+// At Upside Down Labs, we create open-source DIY neuroscience hardware and software.
+// Our mission is to make neuroscience affordable and accessible for everyone.
+// By supporting us with your purchase, you help spread innovation and open science.
+// Thank you for being part of this journey with us!
+
+/*
+ This example is adapted from the client and server code provided by MoThunderz
+ Firmware: https://github.com/mo-thunderz/Esp32BlePart2
+ YouTube video: https://www.youtube.com/watch?v=s3yoZa6kzus
+*/
+
+#include <BLEDevice.h>
+#include <BLEServer.h>
+#include <BLEUtils.h>
+#include <BLE2902.h>
+#include <Arduino.h>
+#include "esp_dsp.h"
+
+// constants won't change. They're used here to set pin numbers:
+const int buttonPin = 9; // the number of the pushbutton pin
+const int ledPin = 7;    // the number of the LED pin
+
+// Variables will change:
+int ledState = HIGH;       // the current state of the output pin
+uint32_t buttonState;      // the current reading from the input pin
+int lastButtonState = LOW; // the previous reading from the input pin
+uint32_t bci_val = 0;      // EEG-based control value (0=stop, 3=forward)
+uint32_t emg1_val1 = 0;    // Left EMG control value (0=inactive, 1=left turn)
+uint32_t emg2_val2 = 0;    // Right EMG control value (0=inactive, 2=right turn)
+uint32_t bootback_val = 4; // Button toggle value (4=toggle state)
+
+// the following variables are unsigned longs because the time, measured in
+// milliseconds, will quickly become a bigger number than can be stored in an int.
+unsigned long lastDebounceTime = 0; // the last time the output pin was toggled
+unsigned long debounceDelay = 50;   // the debounce time; increase if the output flickers
+
+// Initialize all pointers
+BLEServer *pServer = NULL;                   // Pointer to the server
+BLECharacteristic *pCharacteristic_1 = NULL; // Pointer to Characteristic 1
+BLECharacteristic *pCharacteristic_2 = NULL; // Pointer to Characteristic 2
+BLEDescriptor *pDescr_1;                     // Pointer to Descriptor of Characteristic 1
+BLE2902 *pBLE2902_1;                         // Pointer to BLE2902 of Characteristic 1
+BLE2902 *pBLE2902_2;                         // Pointer to BLE2902 of Characteristic 2
+
+// Some variables to keep track on device connected
+bool deviceConnected = false;
+bool oldDeviceConnected = false;
+
+// Variable that will continuously be increased and written to the client
+uint32_t value = 0;
+
+// See the following for generating UUIDs:
+// https://www.uuidgenerator.net/
+// UUIDs used in this example:
+#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
+#define CHARACTERISTIC_UUID_1 "beb5483e-36e1-4688-b7f5-ea07361b26a8"
+#define CHARACTERISTIC_UUID_2 "1c95d5e3-d8f7-413a-bf3d-7a2e5d7be87e"
+
+// Callback function that is called whenever a client is connected or disconnected
+class MyServerCallbacks : public BLEServerCallbacks
+{
+    void onConnect(BLEServer *pServer)
+    {
+        deviceConnected = true;
+    };
+
+    void onDisconnect(BLEServer *pServer)
+    {
+        deviceConnected = false;
+    }
+};
+
+// ----------------- USER CONFIGURATION -----------------
+#define SAMPLE_RATE 512 // samples per second
+#define FFT_SIZE 512    // must be a power of two
+#define BAUD_RATE 115200
+#define INPUT_PIN1 A0 // EEG input pin
+#define INPUT_PIN2 A1 // Left hand EMG input pin
+#define INPUT_PIN3 A2 // Right hand EMG input pin
+
+// EEG bands (Hz)
+#define DELTA_LOW 0.5f
+#define DELTA_HIGH 4.0f
+#define THETA_LOW 4.0f
+#define THETA_HIGH 8.0f
+#define ALPHA_LOW 8.0f
+#define ALPHA_HIGH 13.0f
+#define BETA_LOW 13.0f
+#define BETA_HIGH 30.0f
+#define GAMMA_LOW 30.0f
+#define GAMMA_HIGH 45.0f
+
+#define SMOOTHING_FACTOR 0.63f
+#define EPS 1e-7f
+
+// ----------------- BUFFERS & TYPES -----------------
+float inputBuffer[FFT_SIZE];
+float powerSpectrum[FFT_SIZE / 2];
+
+// For two-real FFT trick
+__attribute__((aligned(16))) float y_cf[FFT_SIZE * 2];
+float *y1_cf = &y_cf[0];
+
+typedef struct
+{
+    float delta, theta, alpha, beta, gamma, total;
+} BandpowerResults;
+
+BandpowerResults smoothedPowers = {0};
+
+// ----------------- FILTER CLASSES -----------------
+// NEW: Notch filter for 50Hz noise removal
+class NotchFilter
+{
+private:
+    struct BiquadState
+    {
+        float z1 = 0;
+        float z2 = 0;
+    };
+
+    BiquadState state1;
+    BiquadState state2;
+
+public:
+    float process(float input)
+    {
+        float output = input;
+
+        // First biquad section
+        float x = output - (-1.56858163f * state1.z1) - (0.96424138f * state1.z2);
+        output = 0.96508099f * x + (-1.56202714f * state1.z1) + (0.96508099f * state1.z2);
+        state1.z2 = state1.z1;
+        state1.z1 = x;
+
+        // Second biquad section
+        x = output - (-1.61100358f * state2.z1) - (0.96592171f * state2.z2);
+        output = 1.00000000f * x + (-1.61854514f * state2.z1) + (1.00000000f * state2.z2);
+        state2.z2 = state2.z1;
+        state2.z1 = x;
+
+        return output;
+    }
+
+    void reset()
+    {
+        state1.z1 = state1.z2 = 0;
+        state2.z1 = state2.z2 = 0;
+    }
+};
+
+// NEW: High-pass filter for EMG signals
+class EMGHighPassFilter
+{
+private:
+    // Filter state for a single channel
+    double z1 = 0.0;
+    double z2 = 0.0;
+
+public:
+    // Process a single sample
+    double process(double input)
+    {
+        const double x = input - -0.82523238 * z1 - 0.29463653 * z2;
+        const double output = 0.52996723 * x + -1.05993445 * z1 + 0.52996723 * z2;
+
+        // Update state
+        z2 = z1;
+        z1 = x;
+
+        return output;
+    }
+
+    // Reset filter state
+    void reset()
+    {
+        z1 = 0.0;
+        z2 = 0.0;
+    }
+};
+class EnvelopeFilter
+{
+private:
+    std::vector<double> circularBuffer;
+    double sum = 0.0;
+    int dataIndex = 0;
+    const int bufferSize;
+
+public:
+    EnvelopeFilter(int bufferSize)
+        : bufferSize(bufferSize)
+    {
+        circularBuffer.resize(bufferSize, 0.0);
+    }
+
+    double getEnvelope(double absEmg)
+    {
+        sum -= circularBuffer[dataIndex];
+        sum += absEmg;
+        circularBuffer[dataIndex] = absEmg;
+        dataIndex = (dataIndex + 1) % bufferSize;
+        return (sum / bufferSize);
+    }
+};
+
+// NEW: Low-pass filter for EEG signals
+float EEGFilter(float input)
+{
+    float output = input;
+    {
+        static float z1 = 0, z2 = 0;
+        float x = output - -1.22465158 * z1 - 0.45044543 * z2;
+        output = 0.05644846 * x + 0.11289692 * z1 + 0.05644846 * z2;
+        z2 = z1;
+        z1 = x;
+    }
+    return output;
+}
+NotchFilter filters[3];             // Notch filters for all 3 input channels
+EMGHighPassFilter emgfilters[2];    // High-pass filters for EMG channels
+EnvelopeFilter Envelopefilter1(16); // Envelope detector for left EMG
+EnvelopeFilter Envelopefilter2(16); // Envelope detector for right EMG
+
+// ----------------- BANDPOWER & SMOOTHING -----------------
+BandpowerResults calculateBandpower(float *ps, float binRes, int halfSize)
+{
+    BandpowerResults r = {0};
+    for (int i = 1; i < halfSize; i++)
+    {
+        float freq = i * binRes;
+        float p = ps[i];
+        r.total += p;
+        if (freq >= DELTA_LOW && freq < DELTA_HIGH)
+            r.delta += p;
+        else if (freq >= THETA_LOW && freq < THETA_HIGH)
+            r.theta += p;
+        else if (freq >= ALPHA_LOW && freq < ALPHA_HIGH)
+            r.alpha += p;
+        else if (freq >= BETA_LOW && freq < BETA_HIGH)
+            r.beta += p;
+        else if (freq >= GAMMA_LOW && freq < GAMMA_HIGH)
+            r.gamma += p;
+    }
+    return r;
+}
+
+void smoothBandpower(const BandpowerResults *raw, BandpowerResults *s)
+{
+    s->delta = SMOOTHING_FACTOR * raw->delta + (1 - SMOOTHING_FACTOR) * s->delta;
+    s->theta = SMOOTHING_FACTOR * raw->theta + (1 - SMOOTHING_FACTOR) * s->theta;
+    s->alpha = SMOOTHING_FACTOR * raw->alpha + (1 - SMOOTHING_FACTOR) * s->alpha;
+    s->beta = SMOOTHING_FACTOR * raw->beta + (1 - SMOOTHING_FACTOR) * s->beta;
+    s->gamma = SMOOTHING_FACTOR * raw->gamma + (1 - SMOOTHING_FACTOR) * s->gamma;
+    s->total = SMOOTHING_FACTOR * raw->total + (1 - SMOOTHING_FACTOR) * s->total;
+}
+
+// ----------------- DSP FFT SETUP -----------------
+void initFFT()
+{
+    // initialize esp-dsp real-FFT (two-real trick)
+    esp_err_t err = dsps_fft2r_init_fc32(NULL, FFT_SIZE);
+    if (err != ESP_OK)
+    {
+        Serial.println("FFT init failed");
+        while (1)
+            delay(10);
+    }
+}
+
+// ----------------- FFT + BANDPOWER + PEAK -----------------
+void processFFT()
+{
+    // pack real→complex: real=inputBuffer, imag=0
+    for (int i = 0; i < FFT_SIZE; i++)
+    {
+        y_cf[2 * i] = inputBuffer[i];
+        y_cf[2 * i + 1] = 0.0f;
+    }
+
+    // FFT
+    dsps_fft2r_fc32(y_cf, FFT_SIZE);
+    dsps_bit_rev_fc32(y_cf, FFT_SIZE);
+    dsps_cplx2reC_fc32(y_cf, FFT_SIZE);
+
+    // magnitude² spectrum
+    int half = FFT_SIZE / 2;
+    for (int i = 0; i < half; i++)
+    {
+        float re = y1_cf[2 * i];
+        float im = y1_cf[2 * i + 1];
+        powerSpectrum[i] = re * re + im * im;
+    }
+
+    // detect peak bin (skip i=0)
+    int maxIdx = 1;
+    float maxP = powerSpectrum[1];
+    for (int i = 2; i < half; i++)
+    {
+        if (powerSpectrum[i] > maxP)
+        {
+            maxP = powerSpectrum[i];
+            maxIdx = i;
+        }
+    }
+    float binRes = float(SAMPLE_RATE) / FFT_SIZE;
+    float peakHz = maxIdx * binRes;
+
+    // bandpower & smoothing
+    BandpowerResults raw = calculateBandpower(powerSpectrum, binRes, half);
+    smoothBandpower(&raw, &smoothedPowers);
+    float T = smoothedPowers.total + EPS;
+    Serial.println(((smoothedPowers.beta / T) * 100)); // for debugging purpose only
+
+    // If the power exceeds the threshold (set as 2% of the total power), the threshold value can be adjusted based on your beta parameters.
+    if (((smoothedPowers.beta / T) * 100) > 2)
+    {
+        bci_val = 3;
+        Serial.println("send 3");
+        pCharacteristic_1->setValue(bci_val); // for forward moving car
+        pCharacteristic_1->notify();
+        digitalWrite(7, HIGH); // Visual feedback
+    }
+    else
+    {
+        bci_val = 0;
+        pCharacteristic_1->setValue(bci_val);
+        pCharacteristic_1->notify();
+        digitalWrite(7, LOW);
+    }
+}
+
+void setup()
+{
+    Serial.begin(BAUD_RATE);
+    while (!Serial)
+    {
+        delay(1);
+    }
+    pinMode(INPUT_PIN1, INPUT);
+    pinMode(INPUT_PIN2, INPUT);
+    pinMode(INPUT_PIN3, INPUT);
+
+    pinMode(7, OUTPUT);
+
+    initFFT();
+    pinMode(buttonPin, INPUT_PULLUP);
+    pinMode(ledPin, OUTPUT);
+
+    // set initial LED state
+    digitalWrite(ledPin, ledState);
+    // Create the BLE Device
+    BLEDevice::init("ESP32");
+
+    // Create the BLE Server
+    pServer = BLEDevice::createServer();
+    pServer->setCallbacks(new MyServerCallbacks());
+
+    // Create the BLE Service
+    BLEService *pService = pServer->createService(SERVICE_UUID);
+
+    // Create a BLE Characteristic
+    pCharacteristic_1 = pService->createCharacteristic(
+        CHARACTERISTIC_UUID_1,
+        BLECharacteristic::PROPERTY_NOTIFY);
+
+    pCharacteristic_2 = pService->createCharacteristic(
+        CHARACTERISTIC_UUID_2,
+        BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_NOTIFY);
+
+    // Create a BLE Descriptor
+    pDescr_1 = new BLEDescriptor((uint16_t)0x2901);
+    pDescr_1->setValue("A very interesting variable");
+    pCharacteristic_1->addDescriptor(pDescr_1);
+
+    // Add the BLE2902 Descriptor because we are using "PROPERTY_NOTIFY"
+    pBLE2902_1 = new BLE2902();
+    pBLE2902_1->setNotifications(true);
+    pCharacteristic_1->addDescriptor(pBLE2902_1);
+
+    pBLE2902_2 = new BLE2902();
+    pBLE2902_2->setNotifications(true);
+    pCharacteristic_2->addDescriptor(pBLE2902_2);
+
+    // Start the service
+    pService->start();
+
+    // Start advertising
+    BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
+    pAdvertising->addServiceUUID(SERVICE_UUID);
+    pAdvertising->setScanResponse(false);
+    pAdvertising->setMinPreferred(0x0); // set value to 0x00 to not advertise this parameter
+    BLEDevice::startAdvertising();
+    Serial.println("Waiting a client connection to notify...");
+}
+
+void loop()
+{
+    static uint16_t idx = 0;
+    static unsigned long lastMicros = micros();
+    unsigned long now = micros(), dt = now - lastMicros;
+    lastMicros = now;
+
+    static long timer = 0;
+    timer -= dt;
+    if (timer <= 0)
+    {
+        timer += 1000000L / SAMPLE_RATE;
+        int raw1 = analogRead(INPUT_PIN1);
+        int raw2 = analogRead(INPUT_PIN2);
+        int raw3 = analogRead(INPUT_PIN3);
+
+        float filteeg = EEGFilter(filters[0].process(raw1));
+        float filtemg1 = emgfilters[0].process(filters[1].process(raw2));
+        float filtemg2 = emgfilters[1].process(filters[2].process(raw3));
+        inputBuffer[idx++] = filteeg;
+
+        float env1 = Envelopefilter1.getEnvelope(abs(filtemg1));
+        float env2 = Envelopefilter2.getEnvelope(abs(filtemg2));
+        
+        // If `env1` exceeds 150, trigger left turn command (send value 1).the threshold value can be adjusted based on your emg parameters.
+        if (env1 > 150)
+        {
+            emg1_val1 = 1;
+            Serial.println("sent 1");               // for debugging purpose only
+            pCharacteristic_1->setValue(emg1_val1); // for left turn car
+            pCharacteristic_1->notify();
+        }
+        // If `env2` exceeds 150, trigger right turn command (send value 2).the threshold value can be adjusted based on your emg parameters.
+        else if (env2 > 150)
+        {
path: BLE-BCI-Car-Remote/BLE-BCI-Car-Remote.ino
user: login: lorforlinux
body: Not required
created_at: 2025-05-12T12:53:15Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,519 @@
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+// Copyright (c) 2025 Aman Maheshwari - Aman@upsidedownlabs.tech
+// Copyright (c) 2025 Upside Down Labs - contact@upsidedownlabs.tech
+
+// At Upside Down Labs, we create open-source DIY neuroscience hardware and software.
+// Our mission is to make neuroscience affordable and accessible for everyone.
+// By supporting us with your purchase, you help spread innovation and open science.
+// Thank you for being part of this journey with us!
+
+/*
+ This example is adapted from the client and server code provided by MoThunderz
+ Firmware: https://github.com/mo-thunderz/Esp32BlePart2
+ YouTube video: https://www.youtube.com/watch?v=s3yoZa6kzus
+*/
+
+#include <BLEDevice.h>
+#include <BLEServer.h>
+#include <BLEUtils.h>
+#include <BLE2902.h>
+#include <Arduino.h>
+#include "esp_dsp.h"
+
+// constants won't change. They're used here to set pin numbers:
+const int buttonPin = 9; // the number of the pushbutton pin
+const int ledPin = 7;    // the number of the LED pin
+
+// Variables will change:
+int ledState = HIGH;       // the current state of the output pin
+uint32_t buttonState;      // the current reading from the input pin
+int lastButtonState = LOW; // the previous reading from the input pin
+uint32_t bci_val = 0;      // EEG-based control value (0=stop, 3=forward)
+uint32_t emg1_val1 = 0;    // Left EMG control value (0=inactive, 1=left turn)
+uint32_t emg2_val2 = 0;    // Right EMG control value (0=inactive, 2=right turn)
+uint32_t bootback_val = 4; // Button toggle value (4=toggle state)
+
+// the following variables are unsigned longs because the time, measured in
+// milliseconds, will quickly become a bigger number than can be stored in an int.
+unsigned long lastDebounceTime = 0; // the last time the output pin was toggled
+unsigned long debounceDelay = 50;   // the debounce time; increase if the output flickers
+
+// Initialize all pointers
+BLEServer *pServer = NULL;                   // Pointer to the server
+BLECharacteristic *pCharacteristic_1 = NULL; // Pointer to Characteristic 1
+BLECharacteristic *pCharacteristic_2 = NULL; // Pointer to Characteristic 2
+BLEDescriptor *pDescr_1;                     // Pointer to Descriptor of Characteristic 1
+BLE2902 *pBLE2902_1;                         // Pointer to BLE2902 of Characteristic 1
+BLE2902 *pBLE2902_2;                         // Pointer to BLE2902 of Characteristic 2
+
+// Some variables to keep track on device connected
+bool deviceConnected = false;
+bool oldDeviceConnected = false;
+
+// Variable that will continuously be increased and written to the client
+uint32_t value = 0;
+
+// See the following for generating UUIDs:
+// https://www.uuidgenerator.net/
+// UUIDs used in this example:
+#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
+#define CHARACTERISTIC_UUID_1 "beb5483e-36e1-4688-b7f5-ea07361b26a8"
+#define CHARACTERISTIC_UUID_2 "1c95d5e3-d8f7-413a-bf3d-7a2e5d7be87e"
+
+// Callback function that is called whenever a client is connected or disconnected
+class MyServerCallbacks : public BLEServerCallbacks
+{
+    void onConnect(BLEServer *pServer)
+    {
+        deviceConnected = true;
+    };
+
+    void onDisconnect(BLEServer *pServer)
+    {
+        deviceConnected = false;
+    }
+};
+
+// ----------------- USER CONFIGURATION -----------------
+#define SAMPLE_RATE 512 // samples per second
+#define FFT_SIZE 512    // must be a power of two
+#define BAUD_RATE 115200
+#define INPUT_PIN1 A0 // EEG input pin
+#define INPUT_PIN2 A1 // Left hand EMG input pin
+#define INPUT_PIN3 A2 // Right hand EMG input pin
+
+// EEG bands (Hz)
+#define DELTA_LOW 0.5f
+#define DELTA_HIGH 4.0f
+#define THETA_LOW 4.0f
+#define THETA_HIGH 8.0f
+#define ALPHA_LOW 8.0f
+#define ALPHA_HIGH 13.0f
+#define BETA_LOW 13.0f
+#define BETA_HIGH 30.0f
+#define GAMMA_LOW 30.0f
+#define GAMMA_HIGH 45.0f
+
+#define SMOOTHING_FACTOR 0.63f
+#define EPS 1e-7f
+
+// ----------------- BUFFERS & TYPES -----------------
+float inputBuffer[FFT_SIZE];
+float powerSpectrum[FFT_SIZE / 2];
+
+// For two-real FFT trick
+__attribute__((aligned(16))) float y_cf[FFT_SIZE * 2];
+float *y1_cf = &y_cf[0];
+
+typedef struct
+{
+    float delta, theta, alpha, beta, gamma, total;
+} BandpowerResults;
+
+BandpowerResults smoothedPowers = {0};
+
+// ----------------- FILTER CLASSES -----------------
+// NEW: Notch filter for 50Hz noise removal
+class NotchFilter
+{
+private:
+    struct BiquadState
+    {
+        float z1 = 0;
+        float z2 = 0;
+    };
+
+    BiquadState state1;
+    BiquadState state2;
+
+public:
+    float process(float input)
+    {
+        float output = input;
+
+        // First biquad section
+        float x = output - (-1.56858163f * state1.z1) - (0.96424138f * state1.z2);
+        output = 0.96508099f * x + (-1.56202714f * state1.z1) + (0.96508099f * state1.z2);
+        state1.z2 = state1.z1;
+        state1.z1 = x;
+
+        // Second biquad section
+        x = output - (-1.61100358f * state2.z1) - (0.96592171f * state2.z2);
+        output = 1.00000000f * x + (-1.61854514f * state2.z1) + (1.00000000f * state2.z2);
+        state2.z2 = state2.z1;
+        state2.z1 = x;
+
+        return output;
+    }
+
+    void reset()
+    {
+        state1.z1 = state1.z2 = 0;
+        state2.z1 = state2.z2 = 0;
+    }
+};
+
+// NEW: High-pass filter for EMG signals
+class EMGHighPassFilter
+{
+private:
+    // Filter state for a single channel
+    double z1 = 0.0;
+    double z2 = 0.0;
+
+public:
+    // Process a single sample
+    double process(double input)
+    {
+        const double x = input - -0.82523238 * z1 - 0.29463653 * z2;
+        const double output = 0.52996723 * x + -1.05993445 * z1 + 0.52996723 * z2;
+
+        // Update state
+        z2 = z1;
+        z1 = x;
+
+        return output;
+    }
+
+    // Reset filter state
+    void reset()
+    {
+        z1 = 0.0;
+        z2 = 0.0;
+    }
+};
+class EnvelopeFilter
+{
+private:
+    std::vector<double> circularBuffer;
+    double sum = 0.0;
+    int dataIndex = 0;
+    const int bufferSize;
+
+public:
+    EnvelopeFilter(int bufferSize)
+        : bufferSize(bufferSize)
+    {
+        circularBuffer.resize(bufferSize, 0.0);
+    }
+
+    double getEnvelope(double absEmg)
+    {
+        sum -= circularBuffer[dataIndex];
+        sum += absEmg;
+        circularBuffer[dataIndex] = absEmg;
+        dataIndex = (dataIndex + 1) % bufferSize;
+        return (sum / bufferSize);
+    }
+};
+
+// NEW: Low-pass filter for EEG signals
+float EEGFilter(float input)
+{
+    float output = input;
+    {
+        static float z1 = 0, z2 = 0;
+        float x = output - -1.22465158 * z1 - 0.45044543 * z2;
+        output = 0.05644846 * x + 0.11289692 * z1 + 0.05644846 * z2;
+        z2 = z1;
+        z1 = x;
+    }
+    return output;
+}
+NotchFilter filters[3];             // Notch filters for all 3 input channels
+EMGHighPassFilter emgfilters[2];    // High-pass filters for EMG channels
+EnvelopeFilter Envelopefilter1(16); // Envelope detector for left EMG
+EnvelopeFilter Envelopefilter2(16); // Envelope detector for right EMG
+
+// ----------------- BANDPOWER & SMOOTHING -----------------
+BandpowerResults calculateBandpower(float *ps, float binRes, int halfSize)
+{
+    BandpowerResults r = {0};
+    for (int i = 1; i < halfSize; i++)
+    {
+        float freq = i * binRes;
+        float p = ps[i];
+        r.total += p;
+        if (freq >= DELTA_LOW && freq < DELTA_HIGH)
+            r.delta += p;
+        else if (freq >= THETA_LOW && freq < THETA_HIGH)
+            r.theta += p;
+        else if (freq >= ALPHA_LOW && freq < ALPHA_HIGH)
+            r.alpha += p;
+        else if (freq >= BETA_LOW && freq < BETA_HIGH)
+            r.beta += p;
+        else if (freq >= GAMMA_LOW && freq < GAMMA_HIGH)
+            r.gamma += p;
+    }
+    return r;
+}
+
+void smoothBandpower(const BandpowerResults *raw, BandpowerResults *s)
+{
+    s->delta = SMOOTHING_FACTOR * raw->delta + (1 - SMOOTHING_FACTOR) * s->delta;
+    s->theta = SMOOTHING_FACTOR * raw->theta + (1 - SMOOTHING_FACTOR) * s->theta;
+    s->alpha = SMOOTHING_FACTOR * raw->alpha + (1 - SMOOTHING_FACTOR) * s->alpha;
+    s->beta = SMOOTHING_FACTOR * raw->beta + (1 - SMOOTHING_FACTOR) * s->beta;
+    s->gamma = SMOOTHING_FACTOR * raw->gamma + (1 - SMOOTHING_FACTOR) * s->gamma;
+    s->total = SMOOTHING_FACTOR * raw->total + (1 - SMOOTHING_FACTOR) * s->total;
+}
+
+// ----------------- DSP FFT SETUP -----------------
+void initFFT()
+{
+    // initialize esp-dsp real-FFT (two-real trick)
+    esp_err_t err = dsps_fft2r_init_fc32(NULL, FFT_SIZE);
+    if (err != ESP_OK)
+    {
+        Serial.println("FFT init failed");
+        while (1)
+            delay(10);
+    }
+}
+
+// ----------------- FFT + BANDPOWER + PEAK -----------------
+void processFFT()
+{
+    // pack real→complex: real=inputBuffer, imag=0
+    for (int i = 0; i < FFT_SIZE; i++)
+    {
+        y_cf[2 * i] = inputBuffer[i];
+        y_cf[2 * i + 1] = 0.0f;
+    }
+
+    // FFT
+    dsps_fft2r_fc32(y_cf, FFT_SIZE);
+    dsps_bit_rev_fc32(y_cf, FFT_SIZE);
+    dsps_cplx2reC_fc32(y_cf, FFT_SIZE);
+
+    // magnitude² spectrum
+    int half = FFT_SIZE / 2;
+    for (int i = 0; i < half; i++)
+    {
+        float re = y1_cf[2 * i];
+        float im = y1_cf[2 * i + 1];
+        powerSpectrum[i] = re * re + im * im;
+    }
+
+    // detect peak bin (skip i=0)
+    int maxIdx = 1;
+    float maxP = powerSpectrum[1];
+    for (int i = 2; i < half; i++)
+    {
+        if (powerSpectrum[i] > maxP)
+        {
+            maxP = powerSpectrum[i];
+            maxIdx = i;
+        }
+    }
+    float binRes = float(SAMPLE_RATE) / FFT_SIZE;
+    float peakHz = maxIdx * binRes;
+
+    // bandpower & smoothing
+    BandpowerResults raw = calculateBandpower(powerSpectrum, binRes, half);
+    smoothBandpower(&raw, &smoothedPowers);
+    float T = smoothedPowers.total + EPS;
+    Serial.println(((smoothedPowers.beta / T) * 100)); // for debugging purpose only
+
+    // If the power exceeds the threshold (set as 2% of the total power), the threshold value can be adjusted based on your beta parameters.
+    if (((smoothedPowers.beta / T) * 100) > 2)
+    {
+        bci_val = 3;
+        Serial.println("send 3");
+        pCharacteristic_1->setValue(bci_val); // for forward moving car
+        pCharacteristic_1->notify();
+        digitalWrite(7, HIGH); // Visual feedback
+    }
+    else
+    {
+        bci_val = 0;
+        pCharacteristic_1->setValue(bci_val);
+        pCharacteristic_1->notify();
+        digitalWrite(7, LOW);
+    }
+}
+
+void setup()
+{
+    Serial.begin(BAUD_RATE);
+    while (!Serial)
+    {
+        delay(1);
+    }
+    pinMode(INPUT_PIN1, INPUT);
+    pinMode(INPUT_PIN2, INPUT);
+    pinMode(INPUT_PIN3, INPUT);
+
+    pinMode(7, OUTPUT);
+
+    initFFT();
+    pinMode(buttonPin, INPUT_PULLUP);
+    pinMode(ledPin, OUTPUT);
+
+    // set initial LED state
+    digitalWrite(ledPin, ledState);
+    // Create the BLE Device
+    BLEDevice::init("ESP32");
+
+    // Create the BLE Server
+    pServer = BLEDevice::createServer();
+    pServer->setCallbacks(new MyServerCallbacks());
+
+    // Create the BLE Service
+    BLEService *pService = pServer->createService(SERVICE_UUID);
+
+    // Create a BLE Characteristic
+    pCharacteristic_1 = pService->createCharacteristic(
+        CHARACTERISTIC_UUID_1,
+        BLECharacteristic::PROPERTY_NOTIFY);
+
+    pCharacteristic_2 = pService->createCharacteristic(
+        CHARACTERISTIC_UUID_2,
+        BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_NOTIFY);
+
+    // Create a BLE Descriptor
+    pDescr_1 = new BLEDescriptor((uint16_t)0x2901);
+    pDescr_1->setValue("A very interesting variable");
+    pCharacteristic_1->addDescriptor(pDescr_1);
+
+    // Add the BLE2902 Descriptor because we are using "PROPERTY_NOTIFY"
+    pBLE2902_1 = new BLE2902();
+    pBLE2902_1->setNotifications(true);
+    pCharacteristic_1->addDescriptor(pBLE2902_1);
+
+    pBLE2902_2 = new BLE2902();
+    pBLE2902_2->setNotifications(true);
+    pCharacteristic_2->addDescriptor(pBLE2902_2);
+
+    // Start the service
+    pService->start();
+
+    // Start advertising
+    BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
+    pAdvertising->addServiceUUID(SERVICE_UUID);
+    pAdvertising->setScanResponse(false);
+    pAdvertising->setMinPreferred(0x0); // set value to 0x00 to not advertise this parameter
+    BLEDevice::startAdvertising();
+    Serial.println("Waiting a client connection to notify...");
+}
+
+void loop()
+{
+    static uint16_t idx = 0;
+    static unsigned long lastMicros = micros();
+    unsigned long now = micros(), dt = now - lastMicros;
+    lastMicros = now;
+
+    static long timer = 0;
+    timer -= dt;
+    if (timer <= 0)
+    {
+        timer += 1000000L / SAMPLE_RATE;
+        int raw1 = analogRead(INPUT_PIN1);
+        int raw2 = analogRead(INPUT_PIN2);
+        int raw3 = analogRead(INPUT_PIN3);
+
+        float filteeg = EEGFilter(filters[0].process(raw1));
+        float filtemg1 = emgfilters[0].process(filters[1].process(raw2));
+        float filtemg2 = emgfilters[1].process(filters[2].process(raw3));
+        inputBuffer[idx++] = filteeg;
+
+        float env1 = Envelopefilter1.getEnvelope(abs(filtemg1));
+        float env2 = Envelopefilter2.getEnvelope(abs(filtemg2));
+        
+        // If `env1` exceeds 150, trigger left turn command (send value 1).the threshold value can be adjusted based on your emg parameters.
+        if (env1 > 150)
+        {
+            emg1_val1 = 1;
+            Serial.println("sent 1");               // for debugging purpose only
+            pCharacteristic_1->setValue(emg1_val1); // for left turn car
+            pCharacteristic_1->notify();
+        }
+        // If `env2` exceeds 150, trigger right turn command (send value 2).the threshold value can be adjusted based on your emg parameters.
+        else if (env2 > 150)
+        {
path: BLE-BCI-Car-Remote/BLE-BCI-Car-Remote.ino
user: login: coderabbitai[bot]
body: @lorforlinux Understood! I'll respect your decision regarding the `abs()` function usage in your code. Thank you for reviewing the suggestion.

🐰


<!-- <agentComponentType>chat-answer</agentComponentType> -->

<!-- <rawResChunk><answer>@lorforlinux Understood! I'll respect your decision regarding the `abs()` function usage in your code. Thank you for reviewing the suggestion.

🐰</answer></rawResChunk> -->

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-12T12:53:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,519 @@
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+// Copyright (c) 2025 Aman Maheshwari - Aman@upsidedownlabs.tech
+// Copyright (c) 2025 Upside Down Labs - contact@upsidedownlabs.tech
+
+// At Upside Down Labs, we create open-source DIY neuroscience hardware and software.
+// Our mission is to make neuroscience affordable and accessible for everyone.
+// By supporting us with your purchase, you help spread innovation and open science.
+// Thank you for being part of this journey with us!
+
+/*
+ This example is adapted from the client and server code provided by MoThunderz
+ Firmware: https://github.com/mo-thunderz/Esp32BlePart2
+ YouTube video: https://www.youtube.com/watch?v=s3yoZa6kzus
+*/
+
+#include <BLEDevice.h>
+#include <BLEServer.h>
+#include <BLEUtils.h>
+#include <BLE2902.h>
+#include <Arduino.h>
+#include "esp_dsp.h"
+
+// constants won't change. They're used here to set pin numbers:
+const int buttonPin = 9; // the number of the pushbutton pin
+const int ledPin = 7;    // the number of the LED pin
+
+// Variables will change:
+int ledState = HIGH;       // the current state of the output pin
+uint32_t buttonState;      // the current reading from the input pin
+int lastButtonState = LOW; // the previous reading from the input pin
+uint32_t bci_val = 0;      // EEG-based control value (0=stop, 3=forward)
+uint32_t emg1_val1 = 0;    // Left EMG control value (0=inactive, 1=left turn)
+uint32_t emg2_val2 = 0;    // Right EMG control value (0=inactive, 2=right turn)
+uint32_t bootback_val = 4; // Button toggle value (4=toggle state)
+
+// the following variables are unsigned longs because the time, measured in
+// milliseconds, will quickly become a bigger number than can be stored in an int.
+unsigned long lastDebounceTime = 0; // the last time the output pin was toggled
+unsigned long debounceDelay = 50;   // the debounce time; increase if the output flickers
+
+// Initialize all pointers
+BLEServer *pServer = NULL;                   // Pointer to the server
+BLECharacteristic *pCharacteristic_1 = NULL; // Pointer to Characteristic 1
+BLECharacteristic *pCharacteristic_2 = NULL; // Pointer to Characteristic 2
+BLEDescriptor *pDescr_1;                     // Pointer to Descriptor of Characteristic 1
+BLE2902 *pBLE2902_1;                         // Pointer to BLE2902 of Characteristic 1
+BLE2902 *pBLE2902_2;                         // Pointer to BLE2902 of Characteristic 2
+
+// Some variables to keep track on device connected
+bool deviceConnected = false;
+bool oldDeviceConnected = false;
+
+// Variable that will continuously be increased and written to the client
+uint32_t value = 0;
+
+// See the following for generating UUIDs:
+// https://www.uuidgenerator.net/
+// UUIDs used in this example:
+#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
+#define CHARACTERISTIC_UUID_1 "beb5483e-36e1-4688-b7f5-ea07361b26a8"
+#define CHARACTERISTIC_UUID_2 "1c95d5e3-d8f7-413a-bf3d-7a2e5d7be87e"
+
+// Callback function that is called whenever a client is connected or disconnected
+class MyServerCallbacks : public BLEServerCallbacks
+{
+    void onConnect(BLEServer *pServer)
+    {
+        deviceConnected = true;
+    };
+
+    void onDisconnect(BLEServer *pServer)
+    {
+        deviceConnected = false;
+    }
+};
+
+// ----------------- USER CONFIGURATION -----------------
+#define SAMPLE_RATE 512 // samples per second
+#define FFT_SIZE 512    // must be a power of two
+#define BAUD_RATE 115200
+#define INPUT_PIN1 A0 // EEG input pin
+#define INPUT_PIN2 A1 // Left hand EMG input pin
+#define INPUT_PIN3 A2 // Right hand EMG input pin
+
+// EEG bands (Hz)
+#define DELTA_LOW 0.5f
+#define DELTA_HIGH 4.0f
+#define THETA_LOW 4.0f
+#define THETA_HIGH 8.0f
+#define ALPHA_LOW 8.0f
+#define ALPHA_HIGH 13.0f
+#define BETA_LOW 13.0f
+#define BETA_HIGH 30.0f
+#define GAMMA_LOW 30.0f
+#define GAMMA_HIGH 45.0f
+
+#define SMOOTHING_FACTOR 0.63f
+#define EPS 1e-7f
+
+// ----------------- BUFFERS & TYPES -----------------
+float inputBuffer[FFT_SIZE];
+float powerSpectrum[FFT_SIZE / 2];
+
+// For two-real FFT trick
+__attribute__((aligned(16))) float y_cf[FFT_SIZE * 2];
+float *y1_cf = &y_cf[0];
+
+typedef struct
+{
+    float delta, theta, alpha, beta, gamma, total;
+} BandpowerResults;
+
+BandpowerResults smoothedPowers = {0};
+
+// ----------------- FILTER CLASSES -----------------
+// NEW: Notch filter for 50Hz noise removal
+class NotchFilter
+{
+private:
+    struct BiquadState
+    {
+        float z1 = 0;
+        float z2 = 0;
+    };
+
+    BiquadState state1;
+    BiquadState state2;
+
+public:
+    float process(float input)
+    {
+        float output = input;
+
+        // First biquad section
+        float x = output - (-1.56858163f * state1.z1) - (0.96424138f * state1.z2);
+        output = 0.96508099f * x + (-1.56202714f * state1.z1) + (0.96508099f * state1.z2);
+        state1.z2 = state1.z1;
+        state1.z1 = x;
+
+        // Second biquad section
+        x = output - (-1.61100358f * state2.z1) - (0.96592171f * state2.z2);
+        output = 1.00000000f * x + (-1.61854514f * state2.z1) + (1.00000000f * state2.z2);
+        state2.z2 = state2.z1;
+        state2.z1 = x;
+
+        return output;
+    }
+
+    void reset()
+    {
+        state1.z1 = state1.z2 = 0;
+        state2.z1 = state2.z2 = 0;
+    }
+};
+
+// NEW: High-pass filter for EMG signals
+class EMGHighPassFilter
+{
+private:
+    // Filter state for a single channel
+    double z1 = 0.0;
+    double z2 = 0.0;
+
+public:
+    // Process a single sample
+    double process(double input)
+    {
+        const double x = input - -0.82523238 * z1 - 0.29463653 * z2;
+        const double output = 0.52996723 * x + -1.05993445 * z1 + 0.52996723 * z2;
+
+        // Update state
+        z2 = z1;
+        z1 = x;
+
+        return output;
+    }
+
+    // Reset filter state
+    void reset()
+    {
+        z1 = 0.0;
+        z2 = 0.0;
+    }
+};
+class EnvelopeFilter
+{
+private:
+    std::vector<double> circularBuffer;
+    double sum = 0.0;
+    int dataIndex = 0;
+    const int bufferSize;
+
+public:
+    EnvelopeFilter(int bufferSize)
+        : bufferSize(bufferSize)
+    {
+        circularBuffer.resize(bufferSize, 0.0);
+    }
+
+    double getEnvelope(double absEmg)
+    {
+        sum -= circularBuffer[dataIndex];
+        sum += absEmg;
+        circularBuffer[dataIndex] = absEmg;
+        dataIndex = (dataIndex + 1) % bufferSize;
+        return (sum / bufferSize);
+    }
+};
+
+// NEW: Low-pass filter for EEG signals
+float EEGFilter(float input)
+{
+    float output = input;
+    {
+        static float z1 = 0, z2 = 0;
+        float x = output - -1.22465158 * z1 - 0.45044543 * z2;
+        output = 0.05644846 * x + 0.11289692 * z1 + 0.05644846 * z2;
+        z2 = z1;
+        z1 = x;
+    }
+    return output;
+}
+NotchFilter filters[3];             // Notch filters for all 3 input channels
+EMGHighPassFilter emgfilters[2];    // High-pass filters for EMG channels
+EnvelopeFilter Envelopefilter1(16); // Envelope detector for left EMG
+EnvelopeFilter Envelopefilter2(16); // Envelope detector for right EMG
+
+// ----------------- BANDPOWER & SMOOTHING -----------------
+BandpowerResults calculateBandpower(float *ps, float binRes, int halfSize)
+{
+    BandpowerResults r = {0};
+    for (int i = 1; i < halfSize; i++)
+    {
+        float freq = i * binRes;
+        float p = ps[i];
+        r.total += p;
+        if (freq >= DELTA_LOW && freq < DELTA_HIGH)
+            r.delta += p;
+        else if (freq >= THETA_LOW && freq < THETA_HIGH)
+            r.theta += p;
+        else if (freq >= ALPHA_LOW && freq < ALPHA_HIGH)
+            r.alpha += p;
+        else if (freq >= BETA_LOW && freq < BETA_HIGH)
+            r.beta += p;
+        else if (freq >= GAMMA_LOW && freq < GAMMA_HIGH)
+            r.gamma += p;
+    }
+    return r;
+}
+
+void smoothBandpower(const BandpowerResults *raw, BandpowerResults *s)
+{
+    s->delta = SMOOTHING_FACTOR * raw->delta + (1 - SMOOTHING_FACTOR) * s->delta;
+    s->theta = SMOOTHING_FACTOR * raw->theta + (1 - SMOOTHING_FACTOR) * s->theta;
+    s->alpha = SMOOTHING_FACTOR * raw->alpha + (1 - SMOOTHING_FACTOR) * s->alpha;
+    s->beta = SMOOTHING_FACTOR * raw->beta + (1 - SMOOTHING_FACTOR) * s->beta;
+    s->gamma = SMOOTHING_FACTOR * raw->gamma + (1 - SMOOTHING_FACTOR) * s->gamma;
+    s->total = SMOOTHING_FACTOR * raw->total + (1 - SMOOTHING_FACTOR) * s->total;
+}
+
+// ----------------- DSP FFT SETUP -----------------
+void initFFT()
+{
+    // initialize esp-dsp real-FFT (two-real trick)
+    esp_err_t err = dsps_fft2r_init_fc32(NULL, FFT_SIZE);
+    if (err != ESP_OK)
+    {
+        Serial.println("FFT init failed");
+        while (1)
+            delay(10);
+    }
+}
+
+// ----------------- FFT + BANDPOWER + PEAK -----------------
+void processFFT()
+{
+    // pack real→complex: real=inputBuffer, imag=0
+    for (int i = 0; i < FFT_SIZE; i++)
+    {
+        y_cf[2 * i] = inputBuffer[i];
+        y_cf[2 * i + 1] = 0.0f;
+    }
+
+    // FFT
+    dsps_fft2r_fc32(y_cf, FFT_SIZE);
+    dsps_bit_rev_fc32(y_cf, FFT_SIZE);
+    dsps_cplx2reC_fc32(y_cf, FFT_SIZE);
+
+    // magnitude² spectrum
+    int half = FFT_SIZE / 2;
+    for (int i = 0; i < half; i++)
+    {
+        float re = y1_cf[2 * i];
+        float im = y1_cf[2 * i + 1];
+        powerSpectrum[i] = re * re + im * im;
+    }
+
+    // detect peak bin (skip i=0)
+    int maxIdx = 1;
+    float maxP = powerSpectrum[1];
+    for (int i = 2; i < half; i++)
+    {
+        if (powerSpectrum[i] > maxP)
+        {
+            maxP = powerSpectrum[i];
+            maxIdx = i;
+        }
+    }
+    float binRes = float(SAMPLE_RATE) / FFT_SIZE;
+    float peakHz = maxIdx * binRes;
+
+    // bandpower & smoothing
+    BandpowerResults raw = calculateBandpower(powerSpectrum, binRes, half);
+    smoothBandpower(&raw, &smoothedPowers);
+    float T = smoothedPowers.total + EPS;
+    Serial.println(((smoothedPowers.beta / T) * 100)); // for debugging purpose only
+
+    // If the power exceeds the threshold (set as 2% of the total power), the threshold value can be adjusted based on your beta parameters.
+    if (((smoothedPowers.beta / T) * 100) > 2)
+    {
+        bci_val = 3;
+        Serial.println("send 3");
+        pCharacteristic_1->setValue(bci_val); // for forward moving car
+        pCharacteristic_1->notify();
+        digitalWrite(7, HIGH); // Visual feedback
+    }
+    else
+    {
+        bci_val = 0;
+        pCharacteristic_1->setValue(bci_val);
+        pCharacteristic_1->notify();
+        digitalWrite(7, LOW);
+    }
+}
+
+void setup()
+{
+    Serial.begin(BAUD_RATE);
+    while (!Serial)
+    {
+        delay(1);
+    }
+    pinMode(INPUT_PIN1, INPUT);
+    pinMode(INPUT_PIN2, INPUT);
+    pinMode(INPUT_PIN3, INPUT);
+
+    pinMode(7, OUTPUT);
+
+    initFFT();
+    pinMode(buttonPin, INPUT_PULLUP);
+    pinMode(ledPin, OUTPUT);
+
+    // set initial LED state
+    digitalWrite(ledPin, ledState);
+    // Create the BLE Device
+    BLEDevice::init("ESP32");
+
+    // Create the BLE Server
+    pServer = BLEDevice::createServer();
+    pServer->setCallbacks(new MyServerCallbacks());
+
+    // Create the BLE Service
+    BLEService *pService = pServer->createService(SERVICE_UUID);
+
+    // Create a BLE Characteristic
+    pCharacteristic_1 = pService->createCharacteristic(
+        CHARACTERISTIC_UUID_1,
+        BLECharacteristic::PROPERTY_NOTIFY);
+
+    pCharacteristic_2 = pService->createCharacteristic(
+        CHARACTERISTIC_UUID_2,
+        BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_NOTIFY);
+
+    // Create a BLE Descriptor
+    pDescr_1 = new BLEDescriptor((uint16_t)0x2901);
+    pDescr_1->setValue("A very interesting variable");
+    pCharacteristic_1->addDescriptor(pDescr_1);
+
+    // Add the BLE2902 Descriptor because we are using "PROPERTY_NOTIFY"
+    pBLE2902_1 = new BLE2902();
+    pBLE2902_1->setNotifications(true);
+    pCharacteristic_1->addDescriptor(pBLE2902_1);
+
+    pBLE2902_2 = new BLE2902();
+    pBLE2902_2->setNotifications(true);
+    pCharacteristic_2->addDescriptor(pBLE2902_2);
+
+    // Start the service
+    pService->start();
+
+    // Start advertising
+    BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
+    pAdvertising->addServiceUUID(SERVICE_UUID);
+    pAdvertising->setScanResponse(false);
+    pAdvertising->setMinPreferred(0x0); // set value to 0x00 to not advertise this parameter
+    BLEDevice::startAdvertising();
+    Serial.println("Waiting a client connection to notify...");
+}
+
+void loop()
+{
+    static uint16_t idx = 0;
+    static unsigned long lastMicros = micros();
+    unsigned long now = micros(), dt = now - lastMicros;
+    lastMicros = now;
+
+    static long timer = 0;
+    timer -= dt;
+    if (timer <= 0)
+    {
+        timer += 1000000L / SAMPLE_RATE;
+        int raw1 = analogRead(INPUT_PIN1);
+        int raw2 = analogRead(INPUT_PIN2);
+        int raw3 = analogRead(INPUT_PIN3);
+
+        float filteeg = EEGFilter(filters[0].process(raw1));
+        float filtemg1 = emgfilters[0].process(filters[1].process(raw2));
+        float filtemg2 = emgfilters[1].process(filters[2].process(raw3));
+        inputBuffer[idx++] = filteeg;
+
+        float env1 = Envelopefilter1.getEnvelope(abs(filtemg1));
+        float env2 = Envelopefilter2.getEnvelope(abs(filtemg2));
+        
+        // If `env1` exceeds 150, trigger left turn command (send value 1).the threshold value can be adjusted based on your emg parameters.
+        if (env1 > 150)
+        {
+            emg1_val1 = 1;
+            Serial.println("sent 1");               // for debugging purpose only
+            pCharacteristic_1->setValue(emg1_val1); // for left turn car
+            pCharacteristic_1->notify();
+        }
+        // If `env2` exceeds 150, trigger right turn command (send value 2).the threshold value can be adjusted based on your emg parameters.
+        else if (env2 > 150)
+        {
+            emg2_val2 = 2;
+            Serial.println("sent 2");               // for debugging purpose only
+            pCharacteristic_1->setValue(emg2_val2); // for right turn car
+            pCharacteristic_1->notify();
+        }
path: BLE-BCI-Car-Remote/BLE-BCI-Car-Remote.ino
user: login: lorforlinux
body: We control that with EEG, not EMG, not required.
created_at: 2025-05-12T12:54:53Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,519 @@
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+// Copyright (c) 2025 Aman Maheshwari - Aman@upsidedownlabs.tech
+// Copyright (c) 2025 Upside Down Labs - contact@upsidedownlabs.tech
+
+// At Upside Down Labs, we create open-source DIY neuroscience hardware and software.
+// Our mission is to make neuroscience affordable and accessible for everyone.
+// By supporting us with your purchase, you help spread innovation and open science.
+// Thank you for being part of this journey with us!
+
+/*
+ This example is adapted from the client and server code provided by MoThunderz
+ Firmware: https://github.com/mo-thunderz/Esp32BlePart2
+ YouTube video: https://www.youtube.com/watch?v=s3yoZa6kzus
+*/
+
+#include <BLEDevice.h>
+#include <BLEServer.h>
+#include <BLEUtils.h>
+#include <BLE2902.h>
+#include <Arduino.h>
+#include "esp_dsp.h"
+
+// constants won't change. They're used here to set pin numbers:
+const int buttonPin = 9; // the number of the pushbutton pin
+const int ledPin = 7;    // the number of the LED pin
+
+// Variables will change:
+int ledState = HIGH;       // the current state of the output pin
+uint32_t buttonState;      // the current reading from the input pin
+int lastButtonState = LOW; // the previous reading from the input pin
+uint32_t bci_val = 0;      // EEG-based control value (0=stop, 3=forward)
+uint32_t emg1_val1 = 0;    // Left EMG control value (0=inactive, 1=left turn)
+uint32_t emg2_val2 = 0;    // Right EMG control value (0=inactive, 2=right turn)
+uint32_t bootback_val = 4; // Button toggle value (4=toggle state)
+
+// the following variables are unsigned longs because the time, measured in
+// milliseconds, will quickly become a bigger number than can be stored in an int.
+unsigned long lastDebounceTime = 0; // the last time the output pin was toggled
+unsigned long debounceDelay = 50;   // the debounce time; increase if the output flickers
+
+// Initialize all pointers
+BLEServer *pServer = NULL;                   // Pointer to the server
+BLECharacteristic *pCharacteristic_1 = NULL; // Pointer to Characteristic 1
+BLECharacteristic *pCharacteristic_2 = NULL; // Pointer to Characteristic 2
+BLEDescriptor *pDescr_1;                     // Pointer to Descriptor of Characteristic 1
+BLE2902 *pBLE2902_1;                         // Pointer to BLE2902 of Characteristic 1
+BLE2902 *pBLE2902_2;                         // Pointer to BLE2902 of Characteristic 2
+
+// Some variables to keep track on device connected
+bool deviceConnected = false;
+bool oldDeviceConnected = false;
+
+// Variable that will continuously be increased and written to the client
+uint32_t value = 0;
+
+// See the following for generating UUIDs:
+// https://www.uuidgenerator.net/
+// UUIDs used in this example:
+#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
+#define CHARACTERISTIC_UUID_1 "beb5483e-36e1-4688-b7f5-ea07361b26a8"
+#define CHARACTERISTIC_UUID_2 "1c95d5e3-d8f7-413a-bf3d-7a2e5d7be87e"
+
+// Callback function that is called whenever a client is connected or disconnected
+class MyServerCallbacks : public BLEServerCallbacks
+{
+    void onConnect(BLEServer *pServer)
+    {
+        deviceConnected = true;
+    };
+
+    void onDisconnect(BLEServer *pServer)
+    {
+        deviceConnected = false;
+    }
+};
+
+// ----------------- USER CONFIGURATION -----------------
+#define SAMPLE_RATE 512 // samples per second
+#define FFT_SIZE 512    // must be a power of two
+#define BAUD_RATE 115200
+#define INPUT_PIN1 A0 // EEG input pin
+#define INPUT_PIN2 A1 // Left hand EMG input pin
+#define INPUT_PIN3 A2 // Right hand EMG input pin
+
+// EEG bands (Hz)
+#define DELTA_LOW 0.5f
+#define DELTA_HIGH 4.0f
+#define THETA_LOW 4.0f
+#define THETA_HIGH 8.0f
+#define ALPHA_LOW 8.0f
+#define ALPHA_HIGH 13.0f
+#define BETA_LOW 13.0f
+#define BETA_HIGH 30.0f
+#define GAMMA_LOW 30.0f
+#define GAMMA_HIGH 45.0f
+
+#define SMOOTHING_FACTOR 0.63f
+#define EPS 1e-7f
+
+// ----------------- BUFFERS & TYPES -----------------
+float inputBuffer[FFT_SIZE];
+float powerSpectrum[FFT_SIZE / 2];
+
+// For two-real FFT trick
+__attribute__((aligned(16))) float y_cf[FFT_SIZE * 2];
+float *y1_cf = &y_cf[0];
+
+typedef struct
+{
+    float delta, theta, alpha, beta, gamma, total;
+} BandpowerResults;
+
+BandpowerResults smoothedPowers = {0};
+
+// ----------------- FILTER CLASSES -----------------
+// NEW: Notch filter for 50Hz noise removal
+class NotchFilter
+{
+private:
+    struct BiquadState
+    {
+        float z1 = 0;
+        float z2 = 0;
+    };
+
+    BiquadState state1;
+    BiquadState state2;
+
+public:
+    float process(float input)
+    {
+        float output = input;
+
+        // First biquad section
+        float x = output - (-1.56858163f * state1.z1) - (0.96424138f * state1.z2);
+        output = 0.96508099f * x + (-1.56202714f * state1.z1) + (0.96508099f * state1.z2);
+        state1.z2 = state1.z1;
+        state1.z1 = x;
+
+        // Second biquad section
+        x = output - (-1.61100358f * state2.z1) - (0.96592171f * state2.z2);
+        output = 1.00000000f * x + (-1.61854514f * state2.z1) + (1.00000000f * state2.z2);
+        state2.z2 = state2.z1;
+        state2.z1 = x;
+
+        return output;
+    }
+
+    void reset()
+    {
+        state1.z1 = state1.z2 = 0;
+        state2.z1 = state2.z2 = 0;
+    }
+};
+
+// NEW: High-pass filter for EMG signals
+class EMGHighPassFilter
+{
+private:
+    // Filter state for a single channel
+    double z1 = 0.0;
+    double z2 = 0.0;
+
+public:
+    // Process a single sample
+    double process(double input)
+    {
+        const double x = input - -0.82523238 * z1 - 0.29463653 * z2;
+        const double output = 0.52996723 * x + -1.05993445 * z1 + 0.52996723 * z2;
+
+        // Update state
+        z2 = z1;
+        z1 = x;
+
+        return output;
+    }
+
+    // Reset filter state
+    void reset()
+    {
+        z1 = 0.0;
+        z2 = 0.0;
+    }
+};
+class EnvelopeFilter
+{
+private:
+    std::vector<double> circularBuffer;
+    double sum = 0.0;
+    int dataIndex = 0;
+    const int bufferSize;
+
+public:
+    EnvelopeFilter(int bufferSize)
+        : bufferSize(bufferSize)
+    {
+        circularBuffer.resize(bufferSize, 0.0);
+    }
+
+    double getEnvelope(double absEmg)
+    {
+        sum -= circularBuffer[dataIndex];
+        sum += absEmg;
+        circularBuffer[dataIndex] = absEmg;
+        dataIndex = (dataIndex + 1) % bufferSize;
+        return (sum / bufferSize);
+    }
+};
+
+// NEW: Low-pass filter for EEG signals
+float EEGFilter(float input)
+{
+    float output = input;
+    {
+        static float z1 = 0, z2 = 0;
+        float x = output - -1.22465158 * z1 - 0.45044543 * z2;
+        output = 0.05644846 * x + 0.11289692 * z1 + 0.05644846 * z2;
+        z2 = z1;
+        z1 = x;
+    }
+    return output;
+}
+NotchFilter filters[3];             // Notch filters for all 3 input channels
+EMGHighPassFilter emgfilters[2];    // High-pass filters for EMG channels
+EnvelopeFilter Envelopefilter1(16); // Envelope detector for left EMG
+EnvelopeFilter Envelopefilter2(16); // Envelope detector for right EMG
+
+// ----------------- BANDPOWER & SMOOTHING -----------------
+BandpowerResults calculateBandpower(float *ps, float binRes, int halfSize)
+{
+    BandpowerResults r = {0};
+    for (int i = 1; i < halfSize; i++)
+    {
+        float freq = i * binRes;
+        float p = ps[i];
+        r.total += p;
+        if (freq >= DELTA_LOW && freq < DELTA_HIGH)
+            r.delta += p;
+        else if (freq >= THETA_LOW && freq < THETA_HIGH)
+            r.theta += p;
+        else if (freq >= ALPHA_LOW && freq < ALPHA_HIGH)
+            r.alpha += p;
+        else if (freq >= BETA_LOW && freq < BETA_HIGH)
+            r.beta += p;
+        else if (freq >= GAMMA_LOW && freq < GAMMA_HIGH)
+            r.gamma += p;
+    }
+    return r;
+}
+
+void smoothBandpower(const BandpowerResults *raw, BandpowerResults *s)
+{
+    s->delta = SMOOTHING_FACTOR * raw->delta + (1 - SMOOTHING_FACTOR) * s->delta;
+    s->theta = SMOOTHING_FACTOR * raw->theta + (1 - SMOOTHING_FACTOR) * s->theta;
+    s->alpha = SMOOTHING_FACTOR * raw->alpha + (1 - SMOOTHING_FACTOR) * s->alpha;
+    s->beta = SMOOTHING_FACTOR * raw->beta + (1 - SMOOTHING_FACTOR) * s->beta;
+    s->gamma = SMOOTHING_FACTOR * raw->gamma + (1 - SMOOTHING_FACTOR) * s->gamma;
+    s->total = SMOOTHING_FACTOR * raw->total + (1 - SMOOTHING_FACTOR) * s->total;
+}
+
+// ----------------- DSP FFT SETUP -----------------
+void initFFT()
+{
+    // initialize esp-dsp real-FFT (two-real trick)
+    esp_err_t err = dsps_fft2r_init_fc32(NULL, FFT_SIZE);
+    if (err != ESP_OK)
+    {
+        Serial.println("FFT init failed");
+        while (1)
+            delay(10);
+    }
+}
+
+// ----------------- FFT + BANDPOWER + PEAK -----------------
+void processFFT()
+{
+    // pack real→complex: real=inputBuffer, imag=0
+    for (int i = 0; i < FFT_SIZE; i++)
+    {
+        y_cf[2 * i] = inputBuffer[i];
+        y_cf[2 * i + 1] = 0.0f;
+    }
+
+    // FFT
+    dsps_fft2r_fc32(y_cf, FFT_SIZE);
+    dsps_bit_rev_fc32(y_cf, FFT_SIZE);
+    dsps_cplx2reC_fc32(y_cf, FFT_SIZE);
+
+    // magnitude² spectrum
+    int half = FFT_SIZE / 2;
+    for (int i = 0; i < half; i++)
+    {
+        float re = y1_cf[2 * i];
+        float im = y1_cf[2 * i + 1];
+        powerSpectrum[i] = re * re + im * im;
+    }
+
+    // detect peak bin (skip i=0)
+    int maxIdx = 1;
+    float maxP = powerSpectrum[1];
+    for (int i = 2; i < half; i++)
+    {
+        if (powerSpectrum[i] > maxP)
+        {
+            maxP = powerSpectrum[i];
+            maxIdx = i;
+        }
+    }
+    float binRes = float(SAMPLE_RATE) / FFT_SIZE;
+    float peakHz = maxIdx * binRes;
+
+    // bandpower & smoothing
+    BandpowerResults raw = calculateBandpower(powerSpectrum, binRes, half);
+    smoothBandpower(&raw, &smoothedPowers);
+    float T = smoothedPowers.total + EPS;
+    Serial.println(((smoothedPowers.beta / T) * 100)); // for debugging purpose only
+
+    // If the power exceeds the threshold (set as 2% of the total power), the threshold value can be adjusted based on your beta parameters.
+    if (((smoothedPowers.beta / T) * 100) > 2)
+    {
+        bci_val = 3;
+        Serial.println("send 3");
+        pCharacteristic_1->setValue(bci_val); // for forward moving car
+        pCharacteristic_1->notify();
+        digitalWrite(7, HIGH); // Visual feedback
+    }
+    else
+    {
+        bci_val = 0;
+        pCharacteristic_1->setValue(bci_val);
+        pCharacteristic_1->notify();
+        digitalWrite(7, LOW);
+    }
+}
+
+void setup()
+{
+    Serial.begin(BAUD_RATE);
+    while (!Serial)
+    {
+        delay(1);
+    }
+    pinMode(INPUT_PIN1, INPUT);
+    pinMode(INPUT_PIN2, INPUT);
+    pinMode(INPUT_PIN3, INPUT);
+
+    pinMode(7, OUTPUT);
+
+    initFFT();
+    pinMode(buttonPin, INPUT_PULLUP);
+    pinMode(ledPin, OUTPUT);
+
+    // set initial LED state
+    digitalWrite(ledPin, ledState);
+    // Create the BLE Device
+    BLEDevice::init("ESP32");
+
+    // Create the BLE Server
+    pServer = BLEDevice::createServer();
+    pServer->setCallbacks(new MyServerCallbacks());
+
+    // Create the BLE Service
+    BLEService *pService = pServer->createService(SERVICE_UUID);
+
+    // Create a BLE Characteristic
+    pCharacteristic_1 = pService->createCharacteristic(
+        CHARACTERISTIC_UUID_1,
+        BLECharacteristic::PROPERTY_NOTIFY);
+
+    pCharacteristic_2 = pService->createCharacteristic(
+        CHARACTERISTIC_UUID_2,
+        BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_NOTIFY);
+
+    // Create a BLE Descriptor
+    pDescr_1 = new BLEDescriptor((uint16_t)0x2901);
+    pDescr_1->setValue("A very interesting variable");
+    pCharacteristic_1->addDescriptor(pDescr_1);
+
+    // Add the BLE2902 Descriptor because we are using "PROPERTY_NOTIFY"
+    pBLE2902_1 = new BLE2902();
+    pBLE2902_1->setNotifications(true);
+    pCharacteristic_1->addDescriptor(pBLE2902_1);
+
+    pBLE2902_2 = new BLE2902();
+    pBLE2902_2->setNotifications(true);
+    pCharacteristic_2->addDescriptor(pBLE2902_2);
+
+    // Start the service
+    pService->start();
+
+    // Start advertising
+    BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
+    pAdvertising->addServiceUUID(SERVICE_UUID);
+    pAdvertising->setScanResponse(false);
+    pAdvertising->setMinPreferred(0x0); // set value to 0x00 to not advertise this parameter
+    BLEDevice::startAdvertising();
+    Serial.println("Waiting a client connection to notify...");
+}
+
+void loop()
+{
+    static uint16_t idx = 0;
+    static unsigned long lastMicros = micros();
+    unsigned long now = micros(), dt = now - lastMicros;
+    lastMicros = now;
+
+    static long timer = 0;
+    timer -= dt;
+    if (timer <= 0)
+    {
+        timer += 1000000L / SAMPLE_RATE;
+        int raw1 = analogRead(INPUT_PIN1);
+        int raw2 = analogRead(INPUT_PIN2);
+        int raw3 = analogRead(INPUT_PIN3);
+
+        float filteeg = EEGFilter(filters[0].process(raw1));
+        float filtemg1 = emgfilters[0].process(filters[1].process(raw2));
+        float filtemg2 = emgfilters[1].process(filters[2].process(raw3));
+        inputBuffer[idx++] = filteeg;
+
+        float env1 = Envelopefilter1.getEnvelope(abs(filtemg1));
+        float env2 = Envelopefilter2.getEnvelope(abs(filtemg2));
+        
+        // If `env1` exceeds 150, trigger left turn command (send value 1).the threshold value can be adjusted based on your emg parameters.
+        if (env1 > 150)
+        {
+            emg1_val1 = 1;
+            Serial.println("sent 1");               // for debugging purpose only
+            pCharacteristic_1->setValue(emg1_val1); // for left turn car
+            pCharacteristic_1->notify();
+        }
+        // If `env2` exceeds 150, trigger right turn command (send value 2).the threshold value can be adjusted based on your emg parameters.
+        else if (env2 > 150)
+        {
+            emg2_val2 = 2;
+            Serial.println("sent 2");               // for debugging purpose only
+            pCharacteristic_1->setValue(emg2_val2); // for right turn car
+            pCharacteristic_1->notify();
+        }
path: BLE-BCI-Car-Remote/BLE-BCI-Car-Remote.ino
user: login: coderabbitai[bot]
body: @lorforlinux I understand now - your control architecture uses EEG signals to manage the stop/forward functionality, while EMG is only used for turning left/right. This design choice makes sense and means the additional stop code for EMG isn't necessary. Thanks for clarifying!

(￣ ￣) () ()
     (>ω<)


<!-- <agentComponentType>chat-answer</agentComponentType> -->

<!-- <rawResChunk><answer>@lorforlinux I understand now - your control architecture uses EEG signals to manage the stop/forward functionality, while EMG is only used for turning left/right. This design choice makes sense and means the additional stop code for EMG isn't necessary. Thanks for clarifying!

(￣ ￣) () ()
     (>ω<)</answer></rawResChunk> -->

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-12T12:55:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,235 @@
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+// Copyright (c) 2025 Aman Maheshwari - Aman@upsidedownlabs.tech
+// Copyright (c) 2025 Upside Down Labs - contact@upsidedownlabs.tech
+
+// At Upside Down Labs, we create open-source DIY neuroscience hardware and software.
+// Our mission is to make neuroscience affordable and accessible for everyone.
+// By supporting us with your purchase, you help spread innovation and open science.
+// Thank you for being part of this journey with us!
+
+/*
+ This example is adapted from the client and server code provided by MoThunderz
+ Firmware: https://github.com/mo-thunderz/Esp32BlePart2
+ YouTube video: https://www.youtube.com/watch?v=s3yoZa6kzus
+*/
+
+ #include <Arduino.h>
+ #include "BLEDevice.h"
+ //#include "BLEScan.h"
+ 
+ // Define UUIDs:
+ static BLEUUID serviceUUID("4fafc201-1fb5-459e-8fcc-c5c9c331914b");
+ static BLEUUID charUUID_1("beb5483e-36e1-4688-b7f5-ea07361b26a8");
+ static BLEUUID charUUID_2("1c95d5e3-d8f7-413a-bf3d-7a2e5d7be87e");
+ 
+ // Some variables to keep track on device connected
+ static boolean doConnect = false;
+ static boolean connected = false;
+ static boolean notify = false;
+ static boolean doScan = false;
+ // Define wheel control pins
+ const int leftWheelForward = 23;
+ const int leftWheelBackward = 24;
+ const int rightWheelForward = 8;
+ const int rightWheelBackward = 9;
+ // Define pointer for the BLE connection
+ static BLEAdvertisedDevice* myDevice;
+ BLERemoteCharacteristic* pRemoteChar_1;
+ BLERemoteCharacteristic* pRemoteChar_2;
+ 
+ // Callback function for Notify function
+ static void notifyCallback(BLERemoteCharacteristic* pBLERemoteCharacteristic,
+                            uint8_t* pData,
+                            size_t length,
+                            bool isNotify) {
+   if (pBLERemoteCharacteristic->getUUID().toString() == charUUID_1.toString()) {
+ 
+     // convert received bytes to integer
+     uint32_t counter = pData[0];
+     for (int i = 1; i < length; i++) {
+       counter = counter | (pData[i] << i * 8);
+     }
+ 
+     // print to Serial
+     Serial.print("Characteristic 1 (Notify) from server: ");
+     Serial.println(counter);//for debugging purposes
+    
+     // Control car movement based on received command
+     if (counter == 1) { // Command for left turn
+       digitalWrite(leftWheelForward, HIGH);
+       digitalWrite(leftWheelBackward, LOW);
+       digitalWrite(rightWheelForward, LOW);
+       digitalWrite(rightWheelBackward, LOW);
+ 
+     } else if (counter == 2) { // Command for right turn
+       digitalWrite(rightWheelForward, HIGH);
+       digitalWrite(rightWheelBackward, LOW);
+       digitalWrite(leftWheelForward, LOW);
+       digitalWrite(leftWheelBackward, LOW);
+     } else if (counter == 3) {  // Command to move forward
+       digitalWrite(leftWheelForward, HIGH);
+       digitalWrite(leftWheelBackward, LOW);
+       digitalWrite(rightWheelForward, HIGH);
+       digitalWrite(rightWheelBackward, LOW);
+     }
+     else if (counter == 4) { // Command to move backward
+       digitalWrite(leftWheelForward, LOW);
+       digitalWrite(leftWheelBackward, HIGH);
+       digitalWrite(rightWheelForward, LOW);
+       digitalWrite(rightWheelBackward, HIGH);
+     }else {// Default case: stop the car
+       digitalWrite(leftWheelForward, LOW);
+       digitalWrite(leftWheelBackward, LOW);
+       digitalWrite(rightWheelForward, LOW);
+       digitalWrite(rightWheelBackward, LOW);
+     }
+   }
+ }
+ 
+ // Callback function that is called whenever a client is connected or disconnected
+ class MyClientCallback : public BLEClientCallbacks {
+   void onConnect(BLEClient* pclient) {
+   }
+ 
+   void onDisconnect(BLEClient* pclient) {
+     connected = false;
+     Serial.println("onDisconnect");//for debugging purposes
+   }
+ };
+ 
+ // Function that is run whenever the server is connected
+ bool connectToServer() {
+   Serial.print("Forming a connection to ");
+   Serial.println(myDevice->getAddress().toString().c_str());
+ 
+   BLEClient* pClient = BLEDevice::createClient();
+   Serial.println(" - Created client");
+ 
+   pClient->setClientCallbacks(new MyClientCallback());
+ 
+   // Connect to the remove BLE Server.
+   pClient->connect(myDevice);  // if you pass BLEAdvertisedDevice instead of address, it will be recognized type of peer device address (public or private)
+   Serial.println(" - Connected to server");
+ 
+   // Obtain a reference to the service we are after in the remote BLE server.
+   BLERemoteService* pRemoteService = pClient->getService(serviceUUID);
+   if (pRemoteService == nullptr) {
+     Serial.print("Failed to find our service UUID: ");
+     Serial.println(serviceUUID.toString().c_str());
+     pClient->disconnect();
+     return false;
+   }
+   Serial.println(" - Found our service");
+ 
+   connected = true;
+   pRemoteChar_1 = pRemoteService->getCharacteristic(charUUID_1);
+   pRemoteChar_2 = pRemoteService->getCharacteristic(charUUID_2);
+   if (connectCharacteristic(pRemoteService, pRemoteChar_1) == false)
+     connected = false;
+   else if (connectCharacteristic(pRemoteService, pRemoteChar_2) == false)
+     connected = false;
+ 
+   if (connected == false) {
+     pClient->disconnect();
+     Serial.println("At least one characteristic UUID not found");
+     return false;
+   }
+   return true;
+ }
+ 
+ // Function to chech Characteristic
+ bool connectCharacteristic(BLERemoteService* pRemoteService, BLERemoteCharacteristic* l_BLERemoteChar) {
+   // Obtain a reference to the characteristic in the service of the remote BLE server.
+   if (l_BLERemoteChar == nullptr) {
+     Serial.print("Failed to find one of the characteristics");
+     Serial.print(l_BLERemoteChar->getUUID().toString().c_str());
+     return false;
+   }
+   Serial.println(" - Found characteristic: " + String(l_BLERemoteChar->getUUID().toString().c_str()));
+ 
+   if (l_BLERemoteChar->canNotify())
+     l_BLERemoteChar->registerForNotify(notifyCallback);
+ 
+   return true;
+ }
+ 
+ // Scan for BLE servers and find the first one that advertises the service we are looking for.
+ class MyAdvertisedDeviceCallbacks : public BLEAdvertisedDeviceCallbacks {
+   //Called for each advertising BLE server.
+   void onResult(BLEAdvertisedDevice advertisedDevice) {
+     Serial.print("BLE Advertised Device found: ");
+     Serial.println(advertisedDevice.toString().c_str());
+ 
+     // We have found a device, let us now see if it contains the service we are looking for.
+     if (advertisedDevice.haveServiceUUID() && advertisedDevice.isAdvertisingService(serviceUUID)) {
+ 
+       BLEDevice::getScan()->stop();
+       myDevice = new BLEAdvertisedDevice(advertisedDevice);
+       doConnect = true;
+       doScan = true;
+ 
+     }  // Found our server
+   }    // onResult
+ };     // MyAdvertisedDeviceCallbacks
+ 
+ void setup() {
+   Serial.begin(115200);
+   Serial.println("Starting Arduino BLE Client application...");
+   BLEDevice::init("");
+ 
+   pinMode(1, OUTPUT);
+   // Retrieve a Scanner and set the callback we want to use to be informed when we
+   // have detected a new device.  Specify that we want active scanning and start the
path: BLE-BCI-Car/BLE-BCI-Car.ino
user: login: lorforlinux
body: @Amanmahe please remove this pinMode
created_at: 2025-05-12T12:56:45Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,235 @@
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+// Copyright (c) 2025 Aman Maheshwari - Aman@upsidedownlabs.tech
+// Copyright (c) 2025 Upside Down Labs - contact@upsidedownlabs.tech
+
+// At Upside Down Labs, we create open-source DIY neuroscience hardware and software.
+// Our mission is to make neuroscience affordable and accessible for everyone.
+// By supporting us with your purchase, you help spread innovation and open science.
+// Thank you for being part of this journey with us!
+
+/*
+ This example is adapted from the client and server code provided by MoThunderz
+ Firmware: https://github.com/mo-thunderz/Esp32BlePart2
+ YouTube video: https://www.youtube.com/watch?v=s3yoZa6kzus
+*/
+
+ #include <Arduino.h>
+ #include "BLEDevice.h"
+ //#include "BLEScan.h"
+ 
+ // Define UUIDs:
+ static BLEUUID serviceUUID("4fafc201-1fb5-459e-8fcc-c5c9c331914b");
+ static BLEUUID charUUID_1("beb5483e-36e1-4688-b7f5-ea07361b26a8");
+ static BLEUUID charUUID_2("1c95d5e3-d8f7-413a-bf3d-7a2e5d7be87e");
+ 
+ // Some variables to keep track on device connected
+ static boolean doConnect = false;
+ static boolean connected = false;
+ static boolean notify = false;
+ static boolean doScan = false;
+ // Define wheel control pins
+ const int leftWheelForward = 23;
+ const int leftWheelBackward = 24;
+ const int rightWheelForward = 8;
+ const int rightWheelBackward = 9;
+ // Define pointer for the BLE connection
+ static BLEAdvertisedDevice* myDevice;
+ BLERemoteCharacteristic* pRemoteChar_1;
+ BLERemoteCharacteristic* pRemoteChar_2;
+ 
+ // Callback function for Notify function
+ static void notifyCallback(BLERemoteCharacteristic* pBLERemoteCharacteristic,
+                            uint8_t* pData,
+                            size_t length,
+                            bool isNotify) {
+   if (pBLERemoteCharacteristic->getUUID().toString() == charUUID_1.toString()) {
+ 
+     // convert received bytes to integer
+     uint32_t counter = pData[0];
+     for (int i = 1; i < length; i++) {
+       counter = counter | (pData[i] << i * 8);
+     }
+ 
+     // print to Serial
+     Serial.print("Characteristic 1 (Notify) from server: ");
+     Serial.println(counter);//for debugging purposes
+    
+     // Control car movement based on received command
+     if (counter == 1) { // Command for left turn
+       digitalWrite(leftWheelForward, HIGH);
+       digitalWrite(leftWheelBackward, LOW);
+       digitalWrite(rightWheelForward, LOW);
+       digitalWrite(rightWheelBackward, LOW);
+ 
+     } else if (counter == 2) { // Command for right turn
+       digitalWrite(rightWheelForward, HIGH);
+       digitalWrite(rightWheelBackward, LOW);
+       digitalWrite(leftWheelForward, LOW);
+       digitalWrite(leftWheelBackward, LOW);
+     } else if (counter == 3) {  // Command to move forward
+       digitalWrite(leftWheelForward, HIGH);
+       digitalWrite(leftWheelBackward, LOW);
+       digitalWrite(rightWheelForward, HIGH);
+       digitalWrite(rightWheelBackward, LOW);
+     }
+     else if (counter == 4) { // Command to move backward
+       digitalWrite(leftWheelForward, LOW);
+       digitalWrite(leftWheelBackward, HIGH);
+       digitalWrite(rightWheelForward, LOW);
+       digitalWrite(rightWheelBackward, HIGH);
+     }else {// Default case: stop the car
+       digitalWrite(leftWheelForward, LOW);
+       digitalWrite(leftWheelBackward, LOW);
+       digitalWrite(rightWheelForward, LOW);
+       digitalWrite(rightWheelBackward, LOW);
+     }
+   }
+ }
+ 
+ // Callback function that is called whenever a client is connected or disconnected
+ class MyClientCallback : public BLEClientCallbacks {
+   void onConnect(BLEClient* pclient) {
+   }
+ 
+   void onDisconnect(BLEClient* pclient) {
+     connected = false;
+     Serial.println("onDisconnect");//for debugging purposes
+   }
+ };
+ 
+ // Function that is run whenever the server is connected
+ bool connectToServer() {
+   Serial.print("Forming a connection to ");
+   Serial.println(myDevice->getAddress().toString().c_str());
+ 
+   BLEClient* pClient = BLEDevice::createClient();
+   Serial.println(" - Created client");
+ 
+   pClient->setClientCallbacks(new MyClientCallback());
+ 
+   // Connect to the remove BLE Server.
+   pClient->connect(myDevice);  // if you pass BLEAdvertisedDevice instead of address, it will be recognized type of peer device address (public or private)
+   Serial.println(" - Connected to server");
+ 
+   // Obtain a reference to the service we are after in the remote BLE server.
+   BLERemoteService* pRemoteService = pClient->getService(serviceUUID);
+   if (pRemoteService == nullptr) {
+     Serial.print("Failed to find our service UUID: ");
+     Serial.println(serviceUUID.toString().c_str());
+     pClient->disconnect();
+     return false;
+   }
+   Serial.println(" - Found our service");
+ 
+   connected = true;
+   pRemoteChar_1 = pRemoteService->getCharacteristic(charUUID_1);
+   pRemoteChar_2 = pRemoteService->getCharacteristic(charUUID_2);
+   if (connectCharacteristic(pRemoteService, pRemoteChar_1) == false)
+     connected = false;
+   else if (connectCharacteristic(pRemoteService, pRemoteChar_2) == false)
+     connected = false;
+ 
+   if (connected == false) {
+     pClient->disconnect();
+     Serial.println("At least one characteristic UUID not found");
+     return false;
+   }
+   return true;
+ }
+ 
+ // Function to chech Characteristic
+ bool connectCharacteristic(BLERemoteService* pRemoteService, BLERemoteCharacteristic* l_BLERemoteChar) {
+   // Obtain a reference to the characteristic in the service of the remote BLE server.
+   if (l_BLERemoteChar == nullptr) {
+     Serial.print("Failed to find one of the characteristics");
+     Serial.print(l_BLERemoteChar->getUUID().toString().c_str());
+     return false;
+   }
+   Serial.println(" - Found characteristic: " + String(l_BLERemoteChar->getUUID().toString().c_str()));
+ 
+   if (l_BLERemoteChar->canNotify())
+     l_BLERemoteChar->registerForNotify(notifyCallback);
+ 
+   return true;
+ }
+ 
+ // Scan for BLE servers and find the first one that advertises the service we are looking for.
+ class MyAdvertisedDeviceCallbacks : public BLEAdvertisedDeviceCallbacks {
+   //Called for each advertising BLE server.
+   void onResult(BLEAdvertisedDevice advertisedDevice) {
+     Serial.print("BLE Advertised Device found: ");
+     Serial.println(advertisedDevice.toString().c_str());
+ 
+     // We have found a device, let us now see if it contains the service we are looking for.
+     if (advertisedDevice.haveServiceUUID() && advertisedDevice.isAdvertisingService(serviceUUID)) {
+ 
+       BLEDevice::getScan()->stop();
+       myDevice = new BLEAdvertisedDevice(advertisedDevice);
+       doConnect = true;
+       doScan = true;
+ 
+     }  // Found our server
+   }    // onResult
+ };     // MyAdvertisedDeviceCallbacks
+ 
+ void setup() {
+   Serial.begin(115200);
+   Serial.println("Starting Arduino BLE Client application...");
+   BLEDevice::init("");
+ 
+   pinMode(1, OUTPUT);
+   // Retrieve a Scanner and set the callback we want to use to be informed when we
+   // have detected a new device.  Specify that we want active scanning and start the
path: BLE-BCI-Car/BLE-BCI-Car.ino
user: login: coderabbitai[bot]
body: > Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

<!-- This is an auto-generated mute comment by CodeRabbit -->
created_at: 2025-05-12T12:56:51Z
author_association: CONTRIBUTOR

PR comments:
user: login: coderabbitai[bot]
created_at: 2025-05-12T10:17:25Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- This is an auto-generated comment: failure by coderabbit.ai -->

> [!CAUTION]
> ## Review failed
> 
> The pull request is closed.

<!-- end of auto-generated comment: failure by coderabbit.ai -->
<!-- walkthrough_start -->

"""
## Walkthrough

Two new Arduino sketches were added: one implements a BLE-based brain-computer interface (BCI) remote for a car using EEG and EMG signals, and the other implements a BLE client to receive commands and control car motors. The README was updated to document both examples.

## Changes

| File(s)                                                     | Change Summary                                                                                              |
|-------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------|
| BLE-BCI-Car-Remote/BLE-BCI-Car-Remote.ino                   | Added a full Arduino sketch for a BLE server using EEG/EMG signals to control a car, with signal processing, filtering, FFT, and BLE notifications. |
| BLE-BCI-Car/BLE-BCI-Car.ino                                 | Added a BLE client Arduino sketch that connects to the BLE server, receives commands, and controls car motors via GPIO. |
| README.md                                                   | Added two new example entries describing the BLE client and BCI remote control sketches.                    |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant EEG/EMG Sensors
    participant ESP32 (Remote)
    participant BLE
    participant ESP32 (Car)
    participant Motors

    EEG/EMG Sensors->>ESP32 (Remote): Provide analog EEG/EMG signals
    ESP32 (Remote)->>ESP32 (Remote): Filter, process, FFT, extract features
    ESP32 (Remote)->>ESP32 (Remote): Detect command (forward/left/right/stop)
    ESP32 (Remote)-->>BLE: Send command via BLE notification
    BLE-->>ESP32 (Car): Deliver command notification
    ESP32 (Car)->>ESP32 (Car): Parse command
    ESP32 (Car)->>Motors: Set GPIOs to move/turn/stop car
```

## Poem

> In circuits and signals, a rabbit delights,  
> EEG and EMG guiding car’s flights.  
> BLE whispers, commands sent with care,  
> Motors respond—zoom here, turn there!  
> Two sketches now dance, remote and the rover,  
> The README grows richer—adventure’s not over!  
> 🐰🚗✨
"""

<!-- walkthrough_end -->
<!-- internal state start -->


<!-- DwQgtGAEAqAWCWBnSTIEMB26CuAXA9mAOYCmGJATmriQCaQDG+Ats2bgFyQAOFk+AIwBWJBrngA3EsgEBPRvlqU0AgfFwA6NPEgQAfACgjoCEYDEZyAAUASpETZWaCrKNxU3bABsvkCiQBHbGlcABpIcVwvOkgAIgBBWnoGZ0gAM3gKZgB3ZxJ0/D4AIQAZAFF7Sik+TGSveHYI/EgyFWiFDFwKfF8JeDRIMoBZAHF0DHoyspHQ2JRO7tpsBml0zJy8+2xubkLcAprGVKYFnvtZRBpmSGxEeAwiSCKvYIJ8XFhIEvxswfIKIjyAAUpTKAEoNDBYPkMllcv4UMxuNE2J0ZO9PogqpRxnUGp10tgMGJ4PgMGh6uJVtl1AgsB98qD0lQ2NlCgBrSFwfKIC5XeY0IhUGjIO5Ecm+Xj4FaIO4PA6DaJibrMWT4IVobiwYHDEZg3GK0RdfBkFZainqqhanXTMHhVoCeryk7G3z4NIRaFHPgCNBY+hkyACKj3A3MW4MdpoEl9XDyMUSxBcr0uijwAR4UlYbTMZAEFoYWCYFbejquo7cFTwSkNGTyaLVNBEe6PBlYoOksAJil52DdbBET5MpisInwFLiMnJ/JoPCwQqQIvIfxBEIxAb+PokX61Hjvdj9XxsAEt/gehmMIsPaQaIz6YzgKBkAMe2cEYhkZQ0ZIsVGcHh8IIIgxqscgKEoVCqOoWg6PeJhQO4yCoJgODvqQ/zUDEI5/lwVC/A4TguEG8hMBBKhqJo2i6GAhgPqYBigmARQAMIAJJgMxzhgDYJDMPuAD0jEsexnEUNxvH7ho9z4BwBixPJBgWJA8TsehX4xARzDOPI7qXpgpCIG4CDIOQvwZO08BIii7DIAMjG+v6QYhhgYAjp4NB8PcHlpNG+Qgmx+r+HxNBlt0vi8pcvEKgMKR8Lcp5TGMu66vY8Dij2/DZlgZQAMpWAAzAATJAzDjt0qY9NEFCQqx+z3Ooh7wAAXqsdnlJUFDVJANIfOgHV9CWu64GyelUGIlBIOIDCIPa5KOqeGDvPAGQTlmBr+GgtD8dkaYhSklZqDWN5QvkUoatcG20LZErqvM7nIGkKqZfkiUGsNzQpViGCIIUM3oDsXiyKetBpeoFJrF4HktlwAyLbgDCfOZHlNH4ElSJAACsAAMAASTWQItSD5IGFK+AjmDkF4f0DAgg5gJWsoQ8jgYpeTGCU9TEx9V4Pz036D3Vsz2XTKl6VU5Cn1pYm6AImk2AUAyfBSjKjk9Z8ZBSDz3D5EoNAkoG8XOpkDDeKkGZpGklBJkZL0i7Q1ADKg5uW/49C7sr0iOaGjrSuyyC6RjACMxWIGgVmtfsQfFXj9hh8ip7CiQ07gadlBpIUuZ9RtvgAGI59ANxyq2XrSNwYBXdwkD1MG2nhG5eCrBeuzZDiiDa8qjjhLuKReCbXiYTItTNzioaXLUziTCLj2BMExLyL6EzIECSiQ2g4QMrga/oF45rhAIJCb+ERBh1pEJPEPPw4v4DiQ7ZCKIHxGIxGr4wtAAHrs5CdIe9iPx8p4+TEIUGq54vT703kGd2l8+AkDfisOgtlICFQAKSemvvOLwL4mib0lNAruBx4T0D4lIP8CgnBcyBBICkwRID5X1KgL6+w+htQqHDZa45qBZk5m7LA5QAAi8wQYTgXKgXA8tyABgwAAbn4IrGkWJ14pl/LuVALVuhvS9PwlA/sLa3gMClDWJAtb5CoS8VYsD4Eg3lAMD46Cej0CBIHbG+ouhpVIHwaIaR9iUOofkQO+oFxpkHN40xNDCouPEWQrSi9KgEmYU8dqbCVqcLJEmZSPBbiwAzLgAgWAX5KAEPgIkJYebNgYE0IgRBoiILHiFKhaY2h+VCasLG/A+AABZ9S7m2PbEUnpGTtXJmNKGlxxzoAYEwCgViqmyC7lTZoFUfCnhQlo2pScToJIqCccg+ssC1NuNokqZJ1CFBiPma+m8FboFoNUcQRdMqQBBogbZRoDRXloO0F5uyWhSDRJCHKE1wZFNwO5Q5twYiwJoN9SQRj5Dpz4AUgczZ5Tdl8LY6QGCroGiZCQX5uA0nclCmcNIPNfhkAcNfACogiYREsgAhcocrLLK5h7WULZwheRIBqcQ8okYTQeOEPO0A94XxbnwHufcUkYFmprfA2tHkHyNFmLuXNhy/jHKtQMoYYpkh5dgIpyAeZyr0UpeIkMvxcJRheJQkZnBSp0e/XYCsYgLk8PNcpB4qSGQMFARISg6j80gAAAyGLIAFnVKCcR8L6Bgfsg3zGhLtABT0g2gnDdUKNXgY1xu6rSfg1Q0xJDIMGskzEyQ7NwCCco6bKAACowTxt3EGskfCkBfMrWm7EFB63xrYB8RQ1tfVFoDYzINAA5d4CMc6C0oPGl+fb5xYqDSS/A1AALSk9kCFd1AG0GiDRIfA8B6DXwPkCBtt4h3+sYP3UduocZpVgFYfm07zUUDnXmhdA7g20CKY6U65VN0/uwH+3dTaD1HtRliSt56fXKWHdewNQaygYFldrF9Hl329ROJcCgywCB8CQyhoxcqSDocoECLyoGuafvoEGoDf7ICkFwMh1DJBl6/uiDBy9MQ5bEknFgZdPM12JTIxQLdQm6oYHclxuDV7ePfNTaKygPEb74qOL3U2NAihKbE9u/YtbuB/T0x2DAKmOUEiLF4NIOVmokBk36njxT+PBvA/QB++AMTaYmMPMT2H9hedoD5lT3g1O1rwuEALQXpAheQLWxA9n4Pyec/uw99B6q4CFWeoNF7ZOOb42tFLEHWWIEywlq9Y8JgT2UlM7A0lCT5dSS51LlQxHcCy3u1zVcPNtZg/JWId56JCTYhxZwglyhMWG6JKSi1ZJ9cUpYFSH4MLfi2IRHSHo2YGSMqgUy1Wlh1cQOyA+CNETIl4jZPqw56iNCUAmM5CzdVhT6rFSA8TnivA871b4vxkOUEBFE2oaTaoKoyOQUU7d2HlKZFiTq44ZxcyGdGEZU1IAAFVUesT4XmZoR6DxpHkBeS5THNn9RxCotTiPxpplGdNZOZ0WTzAahSWzyBocpHZqeF+SOYX2HZxgU8Dpog8OSLcAg1xOWdR7OEfwzZIppmseprN0Z2Qo11q8zatyib0CUANY6AB1aE2YFW6+uZr+5hPnDE5hyb9HmPDnpyJLQcIzyKankuHK7hz3y1KpJjk3i3A6p5iCe4ugejUefw6BW5V/Tr34n2IITe9xlwkBdqaRuzR3qo2CvkSnyPxwPSehea3cOapMImhkRuXpeDSDT2efp7Zc8TRp9dY9XLJpWz6kkjhzme5K9jQ8/t7YPhrocDsPYBMlrJP4wSr0XfNVYF79mgUlBq9qfuCOYGDt0DXWX+4gHExwjZAQCdxZvYc/OAAOTIGIedgkYEoM8seCMKwrEADyPAk+R6NPdo5+HHlpmqDSTLXISxXXxeCUAiHEVPE8TCEgIoH53lCCVgFgOIXpQoEIXCFQPlGzQwINHdx2FPAvBe3vwPkf3+ilF4H6BCmCgXEWQ/2+kgBxlYhGBxjaS+Ffz1z0UJUO2O0+GiSbFWHbTWjWVzV6kKR6BIBQhJSbBb0XFqGqUeTbW92+VxQuzAhPRyUIKUXZm/3oGkMeHdnlzIMRUeDYFlAEJn3yC0lDCNUrn4IMi/2+WoCuAD2QCP2LQGB1zh3tyKS5l3AuWcDUxdw52sRMhIDoDoEPzzU8KMTQAJ2aErBLBI2FC4UhCGHeFoMezOG4E/02HS0ahajdn9jwHujwIPi61+HyNJniMgCOxIEriINSHwO1loD0Vf0bB8EURz0UHyE5W5VWCuzj3q12WZzjFEL4IyPFSyN8FKTGXzEF3yCCn3CJTdFfFLDe0SUn270tVihcFPBv1IWwkBz0XMAW1fXtStS9BtX7hSMa10lgSdRW1dWA3qA9W/i9TvFy3oCqUEHBlBFtwEXqX6D/UQC4CDWLxWABKDXCCDSGQBIAH1A5oTg04SMc+F4TCp414UScIT4dkgixhkm8ppB0vjGMeZfRfBxDogpD+4iBQTv18Ay0dCxBkTYTlDvxWSklZBWSf0cp2dstYMHNvjyTwYaCpjThJRQw/NMB8UwSYCDdwivAc5ChCFWT5ToQjFtNY1VSYSkDcAFSjFlT0CJ5WS9SDSvAtT2RVScshSyTfjcEJdg1VQ+FcU4d41dI4x5VU1yhEgzc6AXTdda0BTuNhT7S9wJdkAg1uAeJs9mICTETWToyJIaA4znAMT3TzxZAvTQQYz9xUzCTqcpogybTEsnMCsuTM1s0q0yhcyUyCSkciTxxa1whatOgAAOeE3AZs1KFqTsveDzLwUDVnTY8QKfNad5KqEsq9W1UdUNZia7ToSs5XeNe4RNBqXlFNUEecuPJc2NRATDT4fAAtI9JQATUtdkxtLmZtDAVtZ5C8kqA+RdEk20pLArKkxw3AaAfAGtMTXdeYseeaRAIcQZdktaXcKDG4SuRvQs/PKcvLBTd89tfMhsmChgas2skgdNOHbsnM5MkgZCqnSaJsv85oaoZaeQfwtvOXCfUc7YwMQMaCoi2nQU+DGcyM0NX0ygO5f010lYXcnNVciaMgx6FgYNUETihWLXAMuHfi/c8Y/NSgQtU8ktUzaLSGXtR8xQBUYIhAogOC+geIGrA7TeSrKZYY6fJrCDKDbYdrMDZrWw9rSyM7P8U8Ko+oJqKVMMbQLANVZk5zWYhgW8ObH1eiGwMoeIPhIYMoDQZgWgWbBSU1VST8RONzRwLSIiXSTbaQNwEaXbWBOOdodgNMakSgGceDfMC8MKiKqKq/d+Aq7oiAzeP9ZOWES4a5EGZzW7BgNMfeRBQYxoYffYfwFYGFEyLY+fAvUSgYa3MnLmVxSpDvZ/N/fgEovAbHFY57ZwcIJ0I7egCqr0cyfIb0soCbESZwabfAbLDZLEE4N2JIBqQMLqnq1qJ4ZySAYA9yHEViToNOXySAfyVifUfK8OcZMgv0LOPC/6maigFxZoWgAA/IC8YcVIQ2R4V6cBAYBeegHzA0AxIjYxR5B2ba+4XalGY606kbMSDCi6q6idd+Ii+UIq2sbqUqo5EGCvAMPgJYqQVo045Sc4iy/anWUQG4i4+4j+PYF1JWF4sZT1WsIwCdcgIKhSEK2ieCAsLBN8QgNSFKqJdgXCNAfCNK7SYiFOSCCiGCaiQwdW7CdQeEo9RAeEzcBoFuWgeEseK5e8AwdWwqdpdpNAIOEgfKfKQONIfKdpNs2gfKWgFYWgNIAQQOfKNstsgAdgxgxnyn3gEEKgYEKjoCTqtu9sfEYBIAxloEDjbLSAAE4GB8pJCGB2laAq6VAAA2MOjGNAS2FOmO2gNsjOluqurGSuwOFu8pOCYujGFPLGQqNAEgP2wqQOKuju2ekgNstAJOxuiZLGAQeOlukgFu30JIKu+2YqL2m238O2h2p2107cOgeE58K2oAA=== -->

<!-- internal state end -->
<!-- announcements_start -->

> [!TIP]
> <details>
> <summary>⚡️ Faster reviews with caching</summary>
> 
> - CodeRabbit now supports caching for code and dependencies, helping speed up reviews. This means quicker feedback, reduced wait times, and a smoother review experience overall. Cached data is encrypted and stored securely. This feature will be automatically enabled for all accounts on May 16th. To opt out, configure `Review - Disable Cache` at either the organization or repository level. If you prefer to disable all data retention across your organization, simply turn off the `Data Retention` setting under your Organization Settings.
> 
> Enjoy the performance boost—your workflow just got faster.
> 
> </details>

<!-- announcements_end -->

---

<details>
<summary>📜 Recent review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 5ef02ae442195aaee8a384dcc0bfb6e6badd9da2 and a787a7c047c38710eaa49f5e83f7c7f62ae84763.

</details>

<details>
<summary>📒 Files selected for processing (2)</summary>

* `BLE-BCI-Car-Remote/BLE-BCI-Car-Remote.ino` (1 hunks)
* `BLE-BCI-Car/BLE-BCI-Car.ino` (1 hunks)

</details>

</details>
<!-- finishing_touch_checkbox_start -->

<details open="true">
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=upsidedownlabs/npg-lite-firmware&utm_content=3):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

### Support

Need help? Create a ticket on our [support page](https://www.coderabbit.ai/contact-us/support) for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai generate sequence diagram` to generate a sequence diagram of the changes in this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: CONTRIBUTOR
user: login: lorforlinux
created_at: 2025-05-12T13:13:07Z
body: @Amanmahe Thank you so much for your contribution, I am merging this now.
author_association: MEMBER