Skip to content

Added NPG-Lite examples: Server/Client, FFT, IR AC control and some other BCI examples. #2

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 19 commits into from
May 10, 2025

Conversation

CIumsy
Copy link
Contributor

@CIumsy CIumsy commented May 8, 2025

Added Arduino sketches for bidirectional TCP communication between 2 NPG-Lite (Server & Client), a real-time FFT example processing EEG signals on-device, and an IR-ON-OFF sketch that generates IR codes to toggle an AC unit’s power. Some more BCI examples to be added.

Summary by CodeRabbit

  • New Features
    • Introduced BLE client and server sketches for ESP32, enabling wireless device discovery, connection, and characteristic notifications.
    • Added real-time EEG signal processing with FFT and bandpower analysis, including visual feedback via NeoPixel LEDs and hardware toggling based on brainwave activity.
    • Implemented IR remote receive and send functionality for LG air conditioners.
    • Integrated sound and LED effects with melody playback and animated NeoPixel patterns.
    • Provided a comprehensive header for musical note frequencies to support audio features.

Copy link

coderabbitai bot commented May 8, 2025

Walkthrough

Multiple new Arduino/ESP32 sketches have been introduced, each targeting specific hardware functionalities: BLE client/server communication, EEG signal processing with FFT and bandpower analysis, IR remote control for LG air conditioners, and NeoPixel LED/buzzer effects. The programs utilize various libraries for BLE, IR, FFT, and NeoPixel control, and include real-time signal processing, visual/audio feedback, and hardware interfacing.

Changes

File(s) / Path(s) Change Summary
BLE-Client/BLE-Client.ino New BLE client sketch for ESP32: scans for devices advertising a target service UUID, connects, discovers characteristics, subscribes to notifications, processes received data, and controls an LED based on notification values. Includes custom callback classes for device discovery and connection events.
BLE-Server/BLE-Server.ino New BLE server sketch for ESP32: advertises a custom service with two characteristics, manages client connections, reads a pushbutton with debounce, toggles an LED, and notifies clients of LED state changes. Handles BLE advertising and connection lifecycle.
Serial-FFT/Serial-FFT.ino New EEG signal processing sketch: samples analog input, applies notch and low-pass filters, buffers samples, performs FFT with ESP-DSP, computes and smooths EEG bandpowers, controls an output pin based on beta power, and prints results over serial.
NPG-Default/NPG-Default.ino,
NPG-Default/pitches.h,
NPG-Default/toneMelody.txt
New NeoPixel and buzzer sketch: plays a melody on a buzzer, controls a 6-pixel NeoPixel strip with various animations, and blinks a motor LED. Includes a header file with musical note frequency constants and a text note about playing melodies.
IR-LG-Receive/IR-LG-Receive.ino New sketch for receiving and decoding LG air conditioner IR remote signals: uses Arduino-IRremote library, reverses bits of received data, prints detailed frame info, and resumes listening for new codes.
IR-LG-Send/IR-LG-Send.ino New sketch for sending LG air conditioner IR remote signals: toggles between ON/OFF codes on each button press, sends IR commands using Arduino-IRremote, and prints status messages.
BCI-IR-Send/BCI-IR-Send.ino New EEG-to-IR/NeoPixel integration sketch: samples EEG, applies filters, performs FFT, calculates and smooths bandpowers, lights NeoPixels based on beta power, and toggles LG AC IR commands if beta power exceeds threshold for 1 second.
BLE-BCI-Server-Toggle/BLE-BCI-Server-Toggle.ino New BLE server sketch with EEG processing: samples EEG, applies filters, performs FFT, calculates and smooths bandpowers, lights NeoPixels based on beta power, and toggles a digital output and BLE characteristic when beta power exceeds threshold for 1 second. Manages BLE connection and advertising lifecycle.

Sequence Diagram(s)

BLE Client-Server Connection and Notification Flow

sequenceDiagram
    participant BLE_Client
    participant BLE_Server
    participant User

    User->>BLE_Client: Starts scan
    BLE_Client->>BLE_Server: Scans for advertised service UUID
    BLE_Server->>BLE_Client: Advertises service/characteristics
    BLE_Client->>BLE_Server: Connects to server
    BLE_Client->>BLE_Server: Discovers service and characteristics
    BLE_Client->>BLE_Server: Subscribes to notifications
    User->>BLE_Server: Presses button (toggles LED)
    BLE_Server->>BLE_Client: Sends notification with LED state
    BLE_Client->>BLE_Client: Processes notification, updates local LED
Loading

EEG Signal Processing and IR/BLE/LED Control

sequenceDiagram
    participant MCU
    participant EEG_Sensor
    participant NeoPixel
    participant IR_Device
    participant BLE_Client

    loop Every sample interval
        EEG_Sensor->>MCU: Analog EEG sample
        MCU->>MCU: Filter, buffer, FFT, bandpower calculation
        MCU->>NeoPixel: Update LEDs based on beta power
        alt Beta power > threshold for 1s
            MCU->>IR_Device: Send IR ON/OFF command (BCI-IR-Send)
            MCU->>BLE_Client: Notify/toggle BLE characteristic (BLE-BCI-Server-Toggle)
        end
    end
Loading

Poem

🐇
New sketches hop onto the scene,
With signals, lights, and code so keen.
BLE, IR, and FFT—
Each rabbit’s trick, a sight to see!
Melodies play, LEDs gleam bright,
Brainwaves and remotes take flight.
In circuits’ warren, all’s alight!

✨ Finishing Touches
  • 📝 Generate Docstrings

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.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • 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 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 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 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 for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🧹 Nitpick comments (3)
NPG-Server/server.ino (1)

80-87: Button logic triggers on release, not press – verify intention.

Because buttonPin is configured INPUT_PULLUP, the line is HIGH while idle and LOW when pressed.
The code toggles the LED when buttonState == HIGH, i.e. on button release.
If you want the toggle on actual press, change the test to LOW.

NPG-Client/client.ino (1)

176-189: Scanning never stops after a successful connection – wastes power.

doScan is set to true on discovery and you start a forever scan (start(0)) every second in loop().
Once connected becomes true, consider calling BLEDevice::getScan()->stop(); and resetting doScan = false;.

NPG-FFT/NPG-FFT.ino (1)

38-56: Filter design comment disagrees with configured sample rate.

SAMPLE_RATE is 512 Hz but the filter comments state 500 Hz. That mismatch shifts the notch/EEG band edges by ≈2.4 %.
Either regenerate the coefficients for 512 Hz or set SAMPLE_RATE 500 to avoid spectral bias.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 5b1f2c9 and 2787514.

📒 Files selected for processing (4)
  • IR-ON-OFF/IR-ON-OFF.ino (1 hunks)
  • NPG-Client/client.ino (1 hunks)
  • NPG-FFT/NPG-FFT.ino (1 hunks)
  • NPG-Server/server.ino (1 hunks)

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
NPG-FFT/NPG-FFT.ino (1)

207-208: ⚠️ Potential issue

GPIO 7 is a flash line – choose a different output pin.

Driving GPIO 7 on ESP32 will crash the MCU at boot. Use a safe GPIO pin instead.

- pinMode(7, OUTPUT);
+ const uint8_t alertPin = 25;  // Define a constant for the pin
+ pinMode(alertPin, OUTPUT);

Also update the digitalWrite calls:

- digitalWrite(7, HIGH);
+ digitalWrite(alertPin, HIGH);
- digitalWrite(7, LOW);
+ digitalWrite(alertPin, LOW);
🧹 Nitpick comments (6)
NPG-Server/NPG-Server.ino (1)

23-23: Fix typo in comment.

There's a typo in "examle" which should be "example".

- This examle is adapted from the client and server code provided by MoThunderz
+ This example is adapted from the client and server code provided by MoThunderz
NPG-Client/NPG-Client.ino (3)

23-23: Fix typo in comment.

There's a typo in "examle" which should be "example".

- This examle is adapted from the client and server code provided by MoThunderz
+ This example is adapted from the client and server code provided by MoThunderz

29-29: Remove commented-out include directive.

The commented-out include appears to be unnecessary. Either uncomment it if needed or remove it to keep the code clean.

-//#include "BLEScan.h"

170-190: Consider reducing the delay time in the main loop.

The 1-second delay in the main loop might reduce responsiveness. For BLE applications requiring quicker responses, consider using a shorter delay.

- delay(1000);
+ delay(100); // Shorter delay for more responsive operation
NPG-FFT/NPG-FFT.ino (2)

97-100: Incorrect filter description in comments.

The comment for the EEG filter incorrectly describes it as a band-stop filter when it appears to be a bandpass filter based on the implementation.

-// 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
+// Bandpass Butterworth IIR digital filter, generated using filter_gen.py.
+// Sampling rate: 500.0 Hz, frequency: [0.5, 30.0] Hz for EEG signals.
+// Filter is order 2, implemented as second-order sections (biquads).
+// Reference: https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.butter.html

186-186: Add explicit float suffix for numeric constant.

For consistency with other float comparisons in the code, add an 'f' suffix to the numeric constant.

- if(((smoothedPowers.beta/ T)*100)>20.0)
+ if(((smoothedPowers.beta/ T)*100)>20.0f)
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 2787514 and 33752d6.

📒 Files selected for processing (4)
  • IR-ON-OFF/IR-ON-OFF.ino (1 hunks)
  • NPG-Client/NPG-Client.ino (1 hunks)
  • NPG-FFT/NPG-FFT.ino (1 hunks)
  • NPG-Server/NPG-Server.ino (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • IR-ON-OFF/IR-ON-OFF.ino
🔇 Additional comments (10)
NPG-Server/NPG-Server.ino (5)

28-54: Well-structured BLE initialization with proper variable declaration.

The code correctly initializes all necessary BLE components with clear naming conventions and appropriate comments.


69-78: Good implementation of BLE server callbacks.

The server callback implementation properly handles connection and disconnection events, updating the deviceConnected flag appropriately.


80-134: Setup function is well-organized and follows BLE best practices.

The setup function properly initializes all components in a logical sequence, including GPIO configuration, BLE server creation, service and characteristic setup, and advertising configuration.


136-186: Loop function implements proper button debouncing and state management.

The main loop effectively handles button debouncing, LED state toggling, and BLE connection state management. The debounce implementation follows best practices for handling mechanical button input.


35-35:

❓ Verification inconclusive

GPIO 7 is connected to SPI flash on most ESP32 boards.

Using GPIO 7 for the LED may cause system instability as it's typically connected to the SPI flash on ESP32 boards.

- const int ledPin = 7;    // the number of the LED pin
+ const int ledPin = 21;    // the number of the LED pin (use a safer GPIO like 21)

🏁 Script executed:

#!/bin/bash
# Check documentation on safe GPIO pins for ESP32
rg -A 10 "GPIO pinout|GPIO restrictions|SPI flash|Strapping pins" --glob "*.md"

Length of output: 81


Verify ESP32 GPIO Usage for LED Pin
I couldn’t find any in-repo documentation confirming that GPIO7 is tied to the SPI flash on your target ESP32 board. Please check the official Espressif datasheet or your board’s schematic to be sure. If GPIO7 is indeed reserved for SPI flash, switch to a safer pin (e.g., GPIO21):

Affected location:

  • NPG-Server/NPG-Server.ino, line 35

Suggested change:

- const int ledPin = 7;    // the number of the LED pin
+ const int ledPin = 21;   // safer GPIO (not used by SPI flash)
NPG-Client/NPG-Client.ino (2)

46-65: Well-implemented notification callback with proper data conversion.

The notification callback correctly identifies the characteristic, converts the received bytes to an integer, and updates the GPIO output accordingly.


78-116: Robust server connection function with proper error handling.

The connectToServer function properly establishes a connection to the BLE server, discovers services and characteristics, and includes appropriate error handling.

NPG-FFT/NPG-FFT.ino (3)

212-231: Well-implemented timing mechanism for precise sampling rate.

The loop function uses an elegant timing approach to maintain the desired sampling rate regardless of processing time variations. This is a robust implementation for real-time signal processing.


138-146: Good error handling in FFT initialization.

The FFT initialization function properly checks for errors and halts execution if the initialization fails, which is a good practice for robust embedded systems.


113-136: Well-designed bandpower calculation with frequency band separation.

The bandpower calculation functions efficiently separate signal power into standard EEG frequency bands and implement appropriate smoothing using exponential moving average, which is ideal for real-time signal analysis.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (19)
BLE-Server/BLE-Server.ino (2)

56-60: Unused variable detected.

The variable value on line 60 is initialized but never used in the code.

-// Variable that will continuously be increased and written to the client
-uint32_t value = 0;

95-108: BLE Service and Characteristics creation.

Service and characteristics are properly created with appropriate properties. Note that pCharacteristic_2 is defined with read, write, and notify properties but is not used in the main code logic.

Consider either removing the unused characteristic or implementing its functionality in the loop.

BLE-Client/BLE-Client.ino (6)

28-30: Unnecessary commented code.

The BLEScan include is commented out but not needed since BLEDevice.h already includes it.

-//#include "BLEScan.h"

47-65: Notification callback function implementation.

The callback properly handles incoming notifications, checks the characteristic UUID, and controls the output pin based on received data.

There's a potential issue with bit shifting in the counter conversion logic that may not handle multi-byte data correctly.

Consider using a more robust method for multi-byte data conversion:

-    uint32_t counter = pData[0];
-    for(int i = 1; i<length; i++) {
-      counter = counter | (pData[i] << i*8);
-    }
+    uint32_t counter = 0;
+    for(int i = 0; i < length && i < sizeof(counter); i++) {
+      counter |= (uint32_t)pData[i] << (i * 8);
+    }

62-63: Hardcoded pin control without pinMode configuration.

Pin 6 is controlled here but its pinMode is set much later in the setup function. This is a logical ordering issue.

Ensure all pins have their modes set before they are used in callbacks that might execute out of sequence.


68-76: Client callback class implementation.

The callback class correctly handles connection and disconnection events, but the onConnect method is empty.

Consider adding connection status logging in the onConnect method for symmetry with onDisconnect:

  void onConnect(BLEClient* pclient) {
+    Serial.println("Connected to server");
  }

79-116: connectToServer function implementation.

The function correctly handles server connection, service discovery, and characteristic verification. However, there's a typo in the error message on line 180.

-    Serial.println("We have failed to connect to the server; there is nothin more we will do.");
+    Serial.println("We have failed to connect to the server; there is nothing more we will do.");

170-189: Main loop implementation.

The loop function correctly handles connection attempts and scan management, but the 1-second delay seems excessive and may affect responsiveness.

Consider reducing the delay time or implementing a more responsive approach:

-  delay(1000);
+  delay(100); // Reduced delay for better responsiveness
IR-Send/IR-Send.ino (2)

32-33: Non-descriptive variable name.

The variable c is used as a counter but has a non-descriptive name.

-int c=0;
+int toggleCounter=0;

51-70: Main loop with button detection and IR command sending.

The loop function properly detects button presses and sends IR commands. However, it uses a simple delay for debouncing instead of proper debounce logic.

Consider implementing proper debounce logic similar to the BLE-Server example:

-void loop() {
-  bool pressed = digitalRead(BOOT_BUTTON) == LOW;
-  if (pressed && !lastPressed) 
-  {          // LOW edge detected
-    if (c % 2 == 0) 
-    {
-      Serial.println("→ Sending AC ON");
-      irsend.sendLG(kAcOn, 28);
-      delay(250);
-    }
-    else 
-    {
-      Serial.println("→ Sending AC OFF");
-      irsend.sendLG(kAcOff, 28);
-      delay(250);
-    }
-    c=c+1;
-  }
-  lastPressed = pressed;
-}
+unsigned long lastDebounceTime = 0;
+unsigned long debounceDelay = 50;
+
+void loop() {
+  bool reading = digitalRead(BOOT_BUTTON) == LOW;
+  
+  if (reading != lastPressed) {
+    lastDebounceTime = millis();
+  }
+  
+  if ((millis() - lastDebounceTime) > debounceDelay) {
+    if (reading && !lastPressed) { // LOW edge detected and debounced
+      if (toggleCounter % 2 == 0) {
+        Serial.println("→ Sending AC ON");
+        irsend.sendLG(kAcOn, 28);
+      } else {
+        Serial.println("→ Sending AC OFF");
+        irsend.sendLG(kAcOff, 28);
+      }
+      toggleCounter++;
+    }
+  }
+  
+  lastPressed = reading;
+}
Serial-FFT/Serial-FFT.ino (5)

73-94: Notch filter implementation.

The 48-52 Hz notch filter is implemented as a cascade of second-order sections with fixed coefficients.

Consider adding comments about the filter's expected performance characteristics and limitations for future maintenance.


96-110: Low-pass filter implementation.

The 45 Hz low-pass filter is implemented correctly, but there's an inconsistency in coefficient formatting.

-    float x = output - -1.22465158 * z1 - 0.45044543 * z2;
+    float x = output - (-1.22465158 * z1) - (0.45044543 * z2);

185-190: Beta threshold detection and pin control.

The beta band power threshold detection is implemented correctly, but uses a hardcoded threshold value.

-  if(((smoothedPowers.beta/ T)*100)>20.0)
+  #define BETA_THRESHOLD 20.0  // Beta power threshold as percentage of total power
+  if(((smoothedPowers.beta/ T)*100) > BETA_THRESHOLD)

192-199: Serial output formatting.

The code outputs relative bandpower values and peak frequency, but doesn't include column headers.

Consider adding a periodic header row or documenting the output format in comments.


211-230: Main loop with precise timing.

The main loop implements a precise sampling timer using micros() and processes FFT when the buffer is full.

However, the code doesn't handle the case where processing takes longer than sampling, which could lead to buffer overruns.

Consider adding a check for processing time to ensure timing accuracy:

  if(idx >= FFT_SIZE){
+    unsigned long processStart = micros();
    processFFT();
+    unsigned long processTime = micros() - processStart;
+    // Log if processing is taking too long
+    if (processTime > (1000000 / SAMPLE_RATE) * 10) {
+      Serial.print("Warning: Processing time too long: ");
+      Serial.println(processTime);
+    }
    idx = 0;
  }
BCI-IR-Send/BCI-IR-Send.ino (4)

221-228: Comment says “4 s” but code waits 1 s → update one or the other
The inline comment on line 227 references a 4-second hold, yet the condition is >= 1000UL (1 s). This mismatch can confuse maintainers and leads to wrong expectations during neuro-feedback testing.

-    // 2) already timing → has 4 s elapsed?
+    // 2) already timing → has 1 s elapsed?

(or change the constant to 4000UL).


219-228: Magic numbers: 20 % threshold & 1 s dwell deserve #defines
Hard-coding behavioural parameters makes future tuning harder and obscures intent. Condense them into named constants near the user-config block:

+#define BETA_TRIGGER_PCT   20.0f   // %
+#define TRIGGER_DWELL_MS   1000UL  // ms
...
-  if (((smoothedPowers.beta/ T)*100)>20.0)
+  if (((smoothedPowers.beta / T) * 100) > BETA_TRIGGER_PCT)
...
-    else if ((millis() - timer1Start) >= 1000UL)
+    else if ((millis() - timer1Start) >= TRIGGER_DWELL_MS)

Improves readability and avoids scattering constants through DSP code.


276-279: timer variable name collides with timer-related globals – rename for clarity
The static long timer (sampling scheduler) is unrelated to timer1Start / timerRunning. Consider renaming to sampleTicker or similar to prevent mental collisions when scanning the code.


45-50: Use a boolean for AC power state instead of integer counter c
c % 2 toggles power but grows unbounded over runtime (risking overflow on very long sessions). A simple bool acOn expresses intent and removes the modulo:

-int c = 0;
+bool acOn = false;
...
-      if (c%2==0) {
+      if (!acOn) {
         Serial.println("→ Sending AC ON");
         irsend.sendLG(kAcOn, 28);
-      }
-      else {
+        acOn = true;
+      } else {
         Serial.println("→ Sending AC OFF");
         irsend.sendLG(kAcOff, 28);
+        acOn = false;
       }
-      c=c+1;

Improves readability and avoids unnecessary arithmetic.

Also applies to: 229-238

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 33752d6 and dc7c76c.

📒 Files selected for processing (5)
  • BCI-IR-Send/BCI-IR-Send.ino (1 hunks)
  • BLE-Client/BLE-Client.ino (1 hunks)
  • BLE-Server/BLE-Server.ino (1 hunks)
  • IR-Send/IR-Send.ino (1 hunks)
  • Serial-FFT/Serial-FFT.ino (1 hunks)
🔇 Additional comments (39)
BLE-Server/BLE-Server.ino (16)

1-21: Appropriate GPL v3 licensing and project attribution.

The licensing information and project attribution are well-structured, providing clear copyright information and project mission statement.


22-26: Good practice: Source attribution included.

Proper attribution to the original source code is provided, including GitHub repository link and YouTube reference, which is excellent for open-source projects.


28-31: Required BLE libraries correctly imported.

All necessary BLE libraries for ESP32 BLE server implementation are properly included.


33-36: GPIO pin assignments for button and LED.

Pin 9 for button and pin 7 for LED are assigned as constants, following good programming practices.


37-46: Well-structured variable declarations with comments.

Variables for state tracking and debounce logic are properly defined with appropriate data types and clear comments.


47-54: BLE object pointers correctly initialized.

All BLE-related pointers are properly declared for later initialization in the setup function.


62-68: Well-defined UUIDs with documentation.

Service and characteristic UUIDs are properly defined with reference to UUID generation source.


69-78: BLE server callbacks correctly implemented.

The callback class properly implements connection and disconnection event handling.


80-87: Proper initialization in setup function.

Serial communication, pin modes, and initial LED state are correctly configured.


88-93: BLE device and server initialization.

BLE device and server are properly initialized with appropriate callbacks.


110-123: BLE descriptors properly configured.

Descriptors and BLE2902 notification enablers are correctly added to the characteristics.


124-134: BLE service start and advertising configuration.

Service is properly started and advertising is correctly configured with appropriate parameters.


136-169: Button debounce logic well implemented.

The button reading and debounce logic are well-implemented, with proper timing and state tracking.


170-171: LED state update.

LED state is properly updated based on the button logic.


173-183: BLE reconnection handling.

The code properly handles disconnection and reconnection by restarting advertising when needed.


184-186: Button state tracking for next iteration.

The current button state is properly saved for comparison in the next loop iteration.

BLE-Client/BLE-Client.ino (6)

22-26: Source attribution included.

Proper attribution to the original source code, maintaining consistency with the server file.


32-34: UUIDs match server configuration.

The UUIDs correctly match those defined in the BLE-Server file, ensuring compatibility.


36-45: BLE connection state variables and pointers.

Connection state tracking variables and BLE pointers are properly defined.


119-131: Connect characteristic helper function.

The function properly verifies characteristic existence and sets up notification callbacks.


134-150: Advertised device callbacks implementation.

The callback properly filters for devices advertising the target service and sets up connection flags.


152-168: Setup function implementation.

The setup function correctly initializes serial communication, pin modes, and BLE scanning with appropriate parameters.

IR-Send/IR-Send.ino (7)

1-23: Well-structured license and attribution information.

The GPL licensing and attribution information is properly included with multiple authors credited.


24-27: Required IR libraries imported.

All necessary libraries for IR transmission on ESP32 are properly included.


28-31: Pin assignments clearly defined.

IR LED and boot button pins are clearly defined as constants.


34-34: IRsend object initialization.

IRsend object is properly initialized with the specified pin.


36-39: IR codes defined as constants.

LG AC IR codes are properly defined as constants with helpful comments.


41-47: Setup function implementation.

The setup function properly initializes pins, serial communication, and the IR sender.


49-49: Static variable for button state tracking.

A static variable is correctly used to track the last button state.

Serial-FFT/Serial-FFT.ino (10)

1-33: Thorough licensing and attribution information.

Proper GPL licensing with multiple authors credited and references to the original ESP-DSP examples.


35-57: Well-defined configuration constants and EEG bands.

Sample rate, FFT size, and EEG frequency bands are clearly defined as constants.


59-71: Buffer allocation and data structures.

Input and FFT buffers are properly allocated with appropriate data structures for EEG band powers.


112-126: Bandpower calculation function.

The function correctly calculates power in different EEG frequency bands from the FFT results.


128-135: Smoothing function for bandpower values.

Exponential smoothing is properly implemented for all bandpower values.


137-145: FFT initialization with error handling.

The FFT initialization includes proper error handling with clear user feedback.


148-166: FFT processing implementation.

The signal packing and FFT processing are implemented correctly using the two-real FFT trick.


168-178: Peak frequency detection logic.

The code correctly identifies the peak frequency in the power spectrum.


180-183: Bandpower calculation and smoothing.

The bandpower calculation and smoothing are correctly applied to the FFT results.


201-209: Setup function implementation.

The setup function correctly initializes serial, pins, and FFT.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
BCI-IR-Send/BCI-IR-Send.ino (1)

90-130: Properly tuned filters for 512 Hz sampling rate

Good job updating the filter coefficients to be properly tuned for the 512 Hz sampling rate as mentioned in the previous review comments. The notch filter targets 48-52 Hz and the low-pass filter has a 45 Hz cutoff.

🧹 Nitpick comments (10)
Serial-FFT/Serial-FFT.ino (6)

38-58: Consider defining threshold constants and documenting value choices

The code has well-defined constants for sampling rate, FFT size, and EEG bands. Consider also defining the beta threshold (20.0%) used on line 188 as a named constant for better maintainability.

+ // Threshold settings
+ #define BETA_THRESHOLD   20.0f  // Beta threshold as percentage of total power

56-57: Document the rationale behind SMOOTHING_FACTOR and EPS values

The chosen values for SMOOTHING_FACTOR (0.63f) and EPS (1e-7f) would benefit from brief comments explaining their selection criteria and impact on signal processing.

-#define SMOOTHING_FACTOR 0.63f
-#define EPS              1e-7f
+// Smoothing factor of 0.63 balances responsiveness and stability
+#define SMOOTHING_FACTOR 0.63f
+// Small epsilon to prevent division by zero
+#define EPS              1e-7f

151-202: Consider adding comments to clarify the FFT mathematics

While the FFT implementation correctly follows ESP-DSP examples, adding brief comments explaining the mathematical principles behind key steps like the two-real FFT trick would help future maintainers.

// FFT
dsps_fft2r_fc32(y_cf, FFT_SIZE);
+ // Perform bit reversal to get correct output order
dsps_bit_rev_fc32(y_cf, FFT_SIZE);
+ // Convert complex output to two real outputs using the two-real FFT trick
dsps_cplx2reC_fc32(y_cf, FFT_SIZE);

183-193: Consider improving the beta power threshold documentation and implementation

The threshold for beta power is hardcoded without explanation. Consider adding a comment explaining the significance of 20% threshold and turning this into a named constant.

- if(((smoothedPowers.beta/ T)*100)>20.0)
+ // Beta power > 20% of total power indicates cognitive engagement/focus
+ if(((smoothedPowers.beta/ T)*100) > BETA_THRESHOLD)

214-233: Consider adding overflow protection for micros() timer

While the microcontroller will run for hours before micros() overflows, a robust implementation would handle potential overflow to prevent timing issues in long-running applications.

static unsigned long lastMicros = micros();
unsigned long now = micros(), dt = now - lastMicros;
+ // Handle potential overflow
+ if (now < lastMicros) {
+   dt = (0xFFFFFFFF - lastMicros) + now + 1;
+ }
lastMicros = now;

225-226: Consider centering ADC readings before filtering

While not strictly necessary, centering the ADC readings (subtracting 2048 for 12-bit ADC) before filtering can improve filter performance by removing DC offset:

int raw = analogRead(INPUT_PIN);
-float filt = EEGFilter(Notch(raw));
+// Center ADC readings around zero
+float centered = (float)raw - 2048.0f;
+float filt = EEGFilter(Notch(centered));
BCI-IR-Send/BCI-IR-Send.ino (4)

42-54: Improve variable naming and IR code documentation

The counter variable c is not descriptive. Consider renaming it to something more meaningful like toggleCount. Also, enhance the IR code comments to explain their source and meaning more clearly.

-// Counter for On/Off
-int c=0; 
+// Counter to toggle between AC On/Off commands
+int toggleCount = 0;

// LG‐AC codes are 28 bits long. Replace these with YOUR captured values:
const uint32_t kAcOff = 0x88C0051;
-//– ON  (whatever your "power on" capture reversed to)
+// Power ON code - captured from LG AC remote control
const uint32_t kAcOn  = 0x8800B4F;

222-251: Consider extracting IR control logic into a separate function

The IR control logic embedded in processFFT() would be clearer if extracted into a dedicated function. Also, consider using a named constant for the threshold duration (1000ms).

+// Duration in milliseconds that beta power must exceed threshold before triggering IR
+#define BETA_THRESHOLD_DURATION 1000UL

+// Send IR command based on current toggle state
+void sendIRCommand() {
+  if (toggleCount % 2 == 0) {
+    Serial.println("→ Sending AC ON");
+    irsend.sendLG(kAcOn, 28);
+  } else {
+    Serial.println("→ Sending AC OFF");
+    irsend.sendLG(kAcOff, 28);
+  }
+  toggleCount++;
+}

// In processFFT():
-    else if ((millis() - timer1Start) >= 1000UL)
+    else if ((millis() - timer1Start) >= BETA_THRESHOLD_DURATION)
     {
-      if (c%2==0) {
-        Serial.println("→ Sending AC ON");
-        irsend.sendLG(kAcOn, 28);
-      }
-      else {
-        Serial.println("→ Sending AC OFF");
-        irsend.sendLG(kAcOff, 28);
-      }
-      c=c+1;
+      sendIRCommand();
       stopTimer(); // reset for next detection

262-269: Remove or explain commented out code

There's a commented out pinMode call for pin 7. Either remove it if it's unnecessary or add a comment explaining why it's disabled.

-  // pinMode(7, OUTPUT);
+  // pinMode(7, OUTPUT);  // Uncomment to enable LED indicator for debugging

or

-  // pinMode(7, OUTPUT);

273-286: Consider implementing timeout for long-running operations

The current loop implementation doesn't handle potential blocking scenarios. Adding a simple timeout mechanism could make the system more robust.

void loop() {
  static uint16_t idx = 0;
  static unsigned long lastMicros = micros();
+ static unsigned long operationStartTime = millis();

  unsigned long now = micros(), dt = now - lastMicros;
  lastMicros = now;

+ // Safety timeout check - reset if stuck for too long
+ if (millis() - operationStartTime > 5000) { // 5 second timeout
+   Serial.println("Operation timeout - resetting");
+   idx = 0;
+   operationStartTime = millis();
+ }

  static long timer = 0;
  timer -= dt;
  if(timer <= 0){
    timer += 1000000L / SAMPLE_RATE;
    int raw = analogRead(INPUT_PIN);
    float filt = EEGFilter(Notch(raw));
    inputBuffer[idx++] = filt;
  }

  if(idx >= FFT_SIZE){
    processFFT();
    idx = 0;
+   operationStartTime = millis(); // Reset timer after successful operation
  }
}
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between dc7c76c and 1a69017.

📒 Files selected for processing (2)
  • BCI-IR-Send/BCI-IR-Send.ino (1 hunks)
  • Serial-FFT/Serial-FFT.ino (1 hunks)
🔇 Additional comments (9)
Serial-FFT/Serial-FFT.ino (7)

1-33: Well-structured license and attribution header

Good job including proper GPL license headers, copyright notices, and clear attribution to the ESP-DSP examples that the FFT routines are based on. This ensures proper compliance and gives credit where due.


74-96: Properly tuned Notch filter coefficients for 512 Hz sampling rate

The notch filter is correctly implemented as a Butterworth IIR with coefficients tuned specifically for the 512 Hz sampling rate, targeting 48-52 Hz rejection (power line noise).


98-113: Properly tuned Low-pass filter for 45 Hz cutoff

The low-pass filter implementation is appropriate for EEG signal conditioning, with coefficients correctly designed for the 512 Hz sampling rate and 45 Hz cutoff.


116-129: Well-implemented bandpower calculation

The function correctly calculates power across standard EEG frequency bands (delta, theta, alpha, beta, gamma) and total power by iterating through frequency bins.


131-138: Effective exponential smoothing implementation

The smoothing function correctly applies exponential smoothing to each frequency band, creating a good balance between responsiveness and stability.


141-148: Good FFT initialization with error handling

The FFT initialization includes appropriate error handling that halts execution if initialization fails, preventing unpredictable behavior.


204-212: Standard Arduino setup with proper initialization

The setup function correctly initializes serial communication, input/output pins, and FFT processing.

BCI-IR-Send/BCI-IR-Send.ino (2)

1-35: Well-structured license and attribution header

The file includes proper GPL license headers, copyright notices, and clear attribution to both ESP-DSP examples and IRremote reference code.


167-182: Well-implemented timer functionality for IR trigger control

The timer implementation is clean and encapsulated in dedicated functions. This makes the threshold-based IR triggering logic easier to understand and maintain.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (3)
NPG-Default/NPG-Default.ino (3)

14-16: Update copyright year to current date.

The copyright notices specify the year 2025, which is in the future. Consider updating to the current year or using a range (e.g., "2023-2025") to reflect when the work began.

-// Copyright (c) 2025 Krishnanshu Mittal - krishnanshu@upsidedownlabs.tech
-// Copyright (c) 2025 Deepak Khatri - deepak@upsidedownlabs.tech
-// Copyright (c) 2025 Upside Down Labs - contact@upsidedownlabs.tech
+// Copyright (c) 2023 Krishnanshu Mittal - krishnanshu@upsidedownlabs.tech
+// Copyright (c) 2023 Deepak Khatri - deepak@upsidedownlabs.tech
+// Copyright (c) 2023 Upside Down Labs - contact@upsidedownlabs.tech

31-40: Include power management considerations.

Since this is for the NPG-Lite firmware which may operate on battery power, consider adding power management. The NeoPixel strip can consume significant power during continuous operation.

Consider adding options to:

  1. Adjust brightness dynamically
  2. Add a sleep/power-saving mode
  3. Include a timeout for animations

For example, you could add:

#define BATTERY_VOLTAGE_PIN A0
#define AUTO_SLEEP_TIMEOUT 60000  // Auto sleep after 60 seconds

// In setup():
pinMode(BATTERY_VOLTAGE_PIN, INPUT);

// Function to adjust brightness based on battery level
void adjustBrightnessForBattery() {
  int batteryLevel = analogRead(BATTERY_VOLTAGE_PIN);
  // Map battery level to brightness (adjust values based on your battery)
  int brightness = map(batteryLevel, 2800, 4200, 20, 50);
  brightness = constrain(brightness, 20, 50);
  strip.setBrightness(brightness);
}

114-149: Consider adding optional error handling in setup().

The setup function initializes hardware but doesn't check if the initialization was successful. Consider adding some simple diagnostics to verify the hardware is connected and responding.

Add basic error checking/reporting:

void setup() {
+  Serial.begin(115200);
+  Serial.println("NPG-Default initializing...");
  pinMode(LED_MOTOR_PIN, OUTPUT);
  strip.begin();           // INITIALIZE NeoPixel strip object (REQUIRED)
  strip.show();            // Turn OFF all pixels ASAP
  strip.setBrightness(50); // Set BRIGHTNESS to about 1/5 (max = 255)
+  
+  // Simple test to verify NeoPixel strip is connected
+  boolean pixelTest = false;
+  for(int i=0; i<strip.numPixels(); i++) {
+    strip.setPixelColor(i, strip.Color(255, 0, 0));
+    strip.show();
+    pixelTest = true;
+    delay(50);
+    strip.setPixelColor(i, strip.Color(0, 0, 0));
+    strip.show();
+  }
+  
+  if (!pixelTest) {
+    Serial.println("Warning: NeoPixel strip may not be connected!");
+  } else {
+    Serial.println("NeoPixel strip initialized successfully.");
+  }

  // Rest of the setup function...
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 1a69017 and 2058a3a.

⛔ Files ignored due to path filters (2)
  • NPG-Default/layout.png is excluded by !**/*.png
  • NPG-Default/schematic.png is excluded by !**/*.png
📒 Files selected for processing (3)
  • NPG-Default/NPG-Default.ino (1 hunks)
  • NPG-Default/pitches.h (1 hunks)
  • NPG-Default/toneMelody.txt (1 hunks)
✅ Files skipped from review due to trivial changes (2)
  • NPG-Default/toneMelody.txt
  • NPG-Default/pitches.h
🔇 Additional comments (1)
NPG-Default/NPG-Default.ino (1)

31-32:

✅ Verification successful

Verify dependency file is included in the PR.

This sketch depends on "pitches.h" which should be included in the PR. Make sure this file is present alongside the sketch to ensure it works properly.


🏁 Script executed:

#!/bin/bash
# Check if pitches.h exists in the same directory
if [ -f "NPG-Default/pitches.h" ]; then
  echo "Found pitches.h in the NPG-Default directory."
  echo "Content preview:"
  head -n 10 "NPG-Default/pitches.h"
else
  echo "ERROR: pitches.h not found in NPG-Default directory!"
fi

Length of output: 453


pitches.h Dependency Confirmed

The file NPG-Default/pitches.h is present in the PR and contains the expected note definitions. No further action is required.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
NPG-Default/NPG-Default.ino (1)

1-29: Clarify NPG-Default's role in the BCI ecosystem.

This sketch appears to be a default animation/sound demo, but the PR objectives mention BCI (Brain-Computer Interface) examples. Consider adding comments explaining how this fits into the NPG-Lite BCI ecosystem.

/*
  Based on:
 - Arduino ToneMelody example (public domain)
 - Adafruit strandtest example (BSD license)

  Requires Adafruit NeoPixel library (install via Arduino Library Manager)
+
+ This sketch serves as the default animation & sound demo for NPG-Lite.
+ For BCI-focused examples, see other sketches in this repository:
+ - BCI-IR-Send/BCI-IR-Send.ino - IR-based BCI example
+ - Serial-FFT/Serial-FFT.ino - EEG data FFT example
+ - [Other BCI examples in this repository]
*/
🧹 Nitpick comments (3)
NPG-Default/NPG-Default.ino (3)

14-16: Update the copyright year to current year.

The copyright notices are set to 2025, which is in the future. Please update them to reflect the current year or the year when the code was actually written.

-// Copyright (c) 2025 Krishnanshu Mittal - krishnanshu@upsidedownlabs.tech
-// Copyright (c) 2025 Deepak Khatri - deepak@upsidedownlabs.tech
-// Copyright (c) 2025 Upside Down Labs - contact@upsidedownlabs.tech
+// Copyright (c) 2023 Krishnanshu Mittal - krishnanshu@upsidedownlabs.tech
+// Copyright (c) 2023 Deepak Khatri - deepak@upsidedownlabs.tech
+// Copyright (c) 2023 Upside Down Labs - contact@upsidedownlabs.tech

163-163: Fix comment remnant in line 163.

There appears to be an incomplete comment at the end of line 163. This looks like a remnant from the previously reported code duplication issue.

-  theaterChaseRainbow(50); // Rainbow-enhanced theaterChase variant// Fill along the length of the strip in various colors...
+  theaterChaseRainbow(50); // Rainbow-enhanced theaterChase variant

114-149: Consider breaking down the setup function for better readability.

The setup function combines multiple initialization tasks. For better maintainability, consider breaking it down into smaller functions.

+void initializeHardware() {
+  pinMode(LED_MOTOR_PIN, OUTPUT);
+  strip.begin();           // INITIALIZE NeoPixel strip object (REQUIRED)
+  strip.show();            // Turn OFF all pixels ASAP
+  strip.setBrightness(50); // Set BRIGHTNESS to about 1/5 (max = 255)
+}
+
+void setStaticRainbow() {
+  // Set static rainbow colors on each pixel individually
+  strip.setPixelColor(0, strip.ColorHSV(0, 255, 255));       // Red
+  strip.setPixelColor(1, strip.ColorHSV(10922, 255, 255));   // Orange
+  strip.setPixelColor(2, strip.ColorHSV(21845, 255, 255));   // Yellow
+  strip.setPixelColor(3, strip.ColorHSV(32768, 255, 255));   // Green
+  strip.setPixelColor(4, strip.ColorHSV(43690, 255, 255));   // Blue
+  strip.setPixelColor(5, strip.ColorHSV(54613, 255, 255));   // Purple
+  
+  strip.show(); // Update the strip with the new colors
+}
+
+void playStartupMelody() {
+  // iterate over the notes of the melody:
+  for (int thisNote = 0; thisNote < 8; thisNote++) {
+    // to calculate the note duration, take one second divided by the note type.
+    //e.g. quarter note = 1000 / 4, eighth note = 1000/8, etc.
+    int noteDuration = 1000 / noteDurations[thisNote];
+    tone(BUZZER_PIN, melody[thisNote], noteDuration);
+
+    // to distinguish the notes, set a minimum time between them.
+    // the note's duration + 30% seems to work well:
+    int pauseBetweenNotes = noteDuration * 1.30;
+    digitalWrite(LED_MOTOR_PIN, HIGH);
+    delay(pauseBetweenNotes/2);
+    digitalWrite(LED_MOTOR_PIN, LOW);
+    delay(pauseBetweenNotes/2);
+
+    // stop the tone playing:
+    noTone(BUZZER_PIN);
+  }
+}
+
 void setup() {
-  pinMode(LED_MOTOR_PIN, OUTPUT);
-  strip.begin();           // INITIALIZE NeoPixel strip object (REQUIRED)
-  strip.show();            // Turn OFF all pixels ASAP
-  strip.setBrightness(50); // Set BRIGHTNESS to about 1/5 (max = 255)
-
-  // Set static rainbow colors on each pixel individually
-  strip.setPixelColor(0, strip.ColorHSV(0, 255, 255));       // Red
-  strip.setPixelColor(1, strip.ColorHSV(10922, 255, 255));   // Orange
-  strip.setPixelColor(2, strip.ColorHSV(21845, 255, 255));   // Yellow
-  strip.setPixelColor(3, strip.ColorHSV(32768, 255, 255));   // Green
-  strip.setPixelColor(4, strip.ColorHSV(43690, 255, 255));   // Blue
-  strip.setPixelColor(5, strip.ColorHSV(54613, 255, 255));   // Purple
-  
-  strip.show(); // Update the strip with the new colors
-
-  // iterate over the notes of the melody:
-  for (int thisNote = 0; thisNote < 8; thisNote++) {
-
-    // to calculate the note duration, take one second divided by the note type.
-    //e.g. quarter note = 1000 / 4, eighth note = 1000/8, etc.
-    int noteDuration = 1000 / noteDurations[thisNote];
-    tone(BUZZER_PIN, melody[thisNote], noteDuration);
-
-    // to distinguish the notes, set a minimum time between them.
-    // the note's duration + 30% seems to work well:
-    int pauseBetweenNotes = noteDuration * 1.30;
-    digitalWrite(LED_MOTOR_PIN, HIGH);
-    delay(pauseBetweenNotes/2);
-    digitalWrite(LED_MOTOR_PIN, LOW);
-    delay(pauseBetweenNotes/2);
-
-    // stop the tone playing:
-    noTone(BUZZER_PIN);
-  }
+  initializeHardware();
+  setStaticRainbow();
+  playStartupMelody();
 }
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 2058a3a and 58e143c.

📒 Files selected for processing (1)
  • NPG-Default/NPG-Default.ino (1 hunks)

Comment on lines +151 to +164
void loop() {
// Fill along the length of the strip in various colors...
colorWipe(strip.Color(255, 0, 0), 50); // Red
colorWipe(strip.Color( 0, 255, 0), 50); // Green
colorWipe(strip.Color( 0, 0, 255), 50); // Blue

// Do a theater marquee effect in various colors...
theaterChase(strip.Color(127, 127, 127), 50); // White, half brightness
theaterChase(strip.Color(127, 0, 0), 50); // Red, half brightness
theaterChase(strip.Color( 0, 0, 127), 50); // Blue, half brightness

rainbow(10); // Flowing rainbow cycle along the whole strip
theaterChaseRainbow(50); // Rainbow-enhanced theaterChase variant// Fill along the length of the strip in various colors...
}
Copy link

@coderabbitai coderabbitai bot May 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Make animations more interactive for future BCI integration.

Since this is part of a BCI project, consider structuring the animation sequence to allow for user interaction or integration with BCI components in the future.

+unsigned long lastAnimationChange = 0;
+int currentAnimation = 0;
+
+// Function to be called when a BCI event is detected
+void onBciEvent(int eventType) {
+  // Change animation based on BCI event
+  currentAnimation = eventType % 4; // Cycle through 4 animation types
+}
+
 void loop() {
-  // Fill along the length of the strip in various colors...
-  colorWipe(strip.Color(255,   0,   0), 50); // Red
-  colorWipe(strip.Color(  0, 255,   0), 50); // Green
-  colorWipe(strip.Color(  0,   0, 255), 50); // Blue
-
-  // Do a theater marquee effect in various colors...
-  theaterChase(strip.Color(127, 127, 127), 50); // White, half brightness
-  theaterChase(strip.Color(127,   0,   0), 50); // Red, half brightness
-  theaterChase(strip.Color(  0,   0, 127), 50); // Blue, half brightness
-
-  rainbow(10);             // Flowing rainbow cycle along the whole strip
-  theaterChaseRainbow(50); // Rainbow-enhanced theaterChase variant// Fill along the length of the strip in various colors...
+  // Switch animations every 10 seconds if no BCI input
+  unsigned long currentMillis = millis();
+  if (currentMillis - lastAnimationChange > 10000) {
+    currentAnimation = (currentAnimation + 1) % 4;
+    lastAnimationChange = currentMillis;
+  }
+  
+  // Run the current animation
+  switch (currentAnimation) {
+    case 0:
+      // Red, Green, Blue color wipe
+      colorWipe(strip.Color(255, 0, 0), 50); // Red
+      colorWipe(strip.Color(0, 255, 0), 50); // Green
+      colorWipe(strip.Color(0, 0, 255), 50); // Blue
+      break;
+    case 1:
+      // Theater chase effects
+      theaterChase(strip.Color(127, 127, 127), 50); // White
+      theaterChase(strip.Color(127, 0, 0), 50);     // Red
+      theaterChase(strip.Color(0, 0, 127), 50);     // Blue
+      break;
+    case 2:
+      // Rainbow cycle
+      rainbow(10);
+      break;
+    case 3:
+      // Rainbow theater chase
+      theaterChaseRainbow(50);
+      break;
+  }
+  
+  // This is where you would check for BCI input
+  // Example: if (serialAvailable()) { processBciInput(); }
 }
📝 Committable suggestion

‼️ 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.

Suggested change
void loop() {
// Fill along the length of the strip in various colors...
colorWipe(strip.Color(255, 0, 0), 50); // Red
colorWipe(strip.Color( 0, 255, 0), 50); // Green
colorWipe(strip.Color( 0, 0, 255), 50); // Blue
// Do a theater marquee effect in various colors...
theaterChase(strip.Color(127, 127, 127), 50); // White, half brightness
theaterChase(strip.Color(127, 0, 0), 50); // Red, half brightness
theaterChase(strip.Color( 0, 0, 127), 50); // Blue, half brightness
rainbow(10); // Flowing rainbow cycle along the whole strip
theaterChaseRainbow(50); // Rainbow-enhanced theaterChase variant// Fill along the length of the strip in various colors...
}
// Add at top of file (after existing includes/globals)
unsigned long lastAnimationChange = 0;
int currentAnimation = 0;
// Function to be called when a BCI event is detected
void onBciEvent(int eventType) {
// Change animation based on BCI event
currentAnimation = eventType % 4; // Cycle through 4 animation types
}
void loop() {
// Switch animations every 10 seconds if no BCI input
unsigned long currentMillis = millis();
if (currentMillis - lastAnimationChange > 10000) {
currentAnimation = (currentAnimation + 1) % 4;
lastAnimationChange = currentMillis;
}
// Run the current animation
switch (currentAnimation) {
case 0:
// Red, Green, Blue color wipe
colorWipe(strip.Color(255, 0, 0), 50);
colorWipe(strip.Color(0, 255, 0), 50);
colorWipe(strip.Color(0, 0, 255), 50);
break;
case 1:
// Theater chase effects
theaterChase(strip.Color(127, 127, 127), 50);
theaterChase(strip.Color(127, 0, 0), 50);
theaterChase(strip.Color(0, 0, 127), 50);
break;
case 2:
// Rainbow cycle
rainbow(10);
break;
case 3:
// Rainbow theater chase
theaterChaseRainbow(50);
break;
}
// Placeholder for BCI input polling
// e.g. if (serialAvailable()) { processBciInput(); }
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not required for the default program.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lorforlinux I understand your point - the default program should remain simple and focused on basic hardware demonstration without adding BCI-specific functionality. Makes sense to keep dedicated BCI features in the separate example files you're adding in this PR rather than complicating the default sketch. Thanks for the clarification.


✏️ Learnings added
Learnt from: lorforlinux
PR: upsidedownlabs/npg-lite-firmware#2
File: NPG-Default/NPG-Default.ino:151-164
Timestamp: 2025-05-10T11:44:39.422Z
Learning: The NPG-Default program should remain simple as a basic hardware demonstration without BCI functionality, as specialized features belong in dedicated example files.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Comment on lines +33 to +37
#define BUZZER_PIN 8
#define LED_MOTOR_PIN 7
#define PIXEL_PIN 15
// How many NeoPixels are attached to the Arduino?
#define PIXEL_COUNT 6
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Document pin assignments and verify for conflicts.

It's important to document why these specific pin assignments were chosen and verify they don't conflict with other NPG-Lite functionality, especially for BCI applications.

#define BUZZER_PIN 8    // Pin connected to the piezo buzzer
#define LED_MOTOR_PIN 7 // Pin connected to the motor indicator LED
#define PIXEL_PIN 15    // Pin connected to the NeoPixel data input
// How many NeoPixels are attached to the Arduino?
#define PIXEL_COUNT 6   // Number of NeoPixels in the strip

Run the following to check for potential pin conflicts in other sketches:


🏁 Script executed:

#!/bin/bash
# Check for potential pin conflicts across sketches
echo "Checking for pin usage across sketches..."
echo "Pins used in NPG-Default: 7, 8, 15"
echo "Searching for pins in other files:"
rg -n "PIN.*[78]|PIN.*15" --type ino

Length of output: 317


🏁 Script executed:

#!/bin/bash
# Check for potential pin conflicts across all .ino sketches
echo "Searching for pin usage across all .ino sketches..."
rg -n --glob '*.ino' "#define\s+\w+_PIN\s+(7|8|15)" || echo "No matching pin definitions found."

Length of output: 325


No pin conflicts detected across sketches

Only NPG-Default/NPG-Default.ino defines BUZZER_PIN (8), LED_MOTOR_PIN (7) and PIXEL_PIN (15). Please add inline comments explaining why these pins were chosen (e.g. hardware pinout constraints, BCI signal isolation) and confirm they don’t conflict with any other hardware or future extensions.

• File: NPG-Default/NPG-Default.ino (lines 33–37)

#define BUZZER_PIN 8    // Pin connected to the piezo buzzer
#define LED_MOTOR_PIN 7 // Pin connected to the motor indicator LED
#define PIXEL_PIN 15    // Pin connected to the NeoPixel data input
// How many NeoPixels are attached to the Arduino?
#define PIXEL_COUNT 6   // Number of NeoPixels in the strip
📝 Committable suggestion

‼️ 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.

Suggested change
#define BUZZER_PIN 8
#define LED_MOTOR_PIN 7
#define PIXEL_PIN 15
// How many NeoPixels are attached to the Arduino?
#define PIXEL_COUNT 6
#define BUZZER_PIN 8 // Pin connected to the piezo buzzer
#define LED_MOTOR_PIN 7 // Pin connected to the motor indicator LED
#define PIXEL_PIN 15 // Pin connected to the NeoPixel data input
// How many NeoPixels are attached to the Arduino?
#define PIXEL_COUNT 6 // Number of NeoPixels in the strip

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (3)
IR-LG-Send/IR-LG-Send.ino (3)

36-40: Consider using an enum or boolean for state tracking.

Using a counter and modulo operation to track ON/OFF state is functional but not semantically clear. A boolean or enum would better communicate the intent.

-int c = 0;   // Counter for maintaining ON/OFF state
+bool acIsOn = false;  // Boolean to track if AC is ON or OFF

// The full 28-bit MSB-first codes captured by the receiver sketch
static const uint32_t FULL_ON  = 0x8800B0B;  
static const uint32_t FULL_OFF = 0x88C0051;  

Then in the loop function:

-  if (c%2==0) {
+  if (!acIsOn) {
     Serial.println("→ Sending AC ON");
     IrSender.sendLG(FULL_ON, 28);
     Serial.println("  [Done]\n");
     delay(500);
+    acIsOn = true;
   }
   else {
     Serial.println("→ Sending AC OFF");
     IrSender.sendLG(FULL_OFF, 28);
     Serial.println("  [Done]\n");
     delay(500);
+    acIsOn = false;
   }
-  c=c+1;

33-34: Add comments explaining pin functions.

The pin definitions could use more explicit comments about what each pin does, especially for hardware-specific pins like the IR_SEND_PIN.

-#define IR_SEND_PIN 22   // your IR-LED pin (via resistor/transistor)
-#define BOOT_BUTTON  9     // NPG-Lite BOOT button (active LOW)
+#define IR_SEND_PIN 22   // NPG-Lite IR-LED pin (using RMT peripheral via resistor/transistor)
+#define BOOT_BUTTON  9   // NPG-Lite BOOT button (active LOW, triggers IR code transmission)

39-40: Add references to how the IR codes were obtained.

The IR codes are defined but there's no mention of how they were captured/determined. Adding this information would be helpful for future maintenance.

// The full 28-bit MSB-first codes captured by the receiver sketch
-static const uint32_t FULL_ON  = 0x8800B0B;  
-static const uint32_t FULL_OFF = 0x88C0051;  
+// The full 28-bit MSB-first codes captured by the IR-LG-Receive sketch
+static const uint32_t FULL_ON  = 0x8800B0B;  // Power ON for LG AC
+static const uint32_t FULL_OFF = 0x88C0051;  // Power OFF for LG AC
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 58e143c and 113b1b8.

📒 Files selected for processing (2)
  • IR-LG-Receive/IR-LG-Receive.ino (1 hunks)
  • IR-LG-Send/IR-LG-Send.ino (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • IR-LG-Receive/IR-LG-Receive.ino
🔇 Additional comments (1)
IR-LG-Send/IR-LG-Send.ino (1)

14-15: ⚠️ Potential issue

Update the copyright year to the current year.

The copyright notices show the year 2025, which is in the future. This should be updated to the current year (2023 or 2024) for accuracy.

-// Copyright (c) 2025 Krishnanshu Mittal - krishnanshu@upsidedownlabs.tech
-// Copyright (c) 2025 Upside Down Labs - contact@upsidedownlabs.tech
+// Copyright (c) 2024 Krishnanshu Mittal - krishnanshu@upsidedownlabs.tech
+// Copyright (c) 2024 Upside Down Labs - contact@upsidedownlabs.tech

Likely an incorrect or invalid review comment.

Comment on lines +52 to +71
void loop() {
// When Boot button is pressed
if(digitalRead(BOOT_BUTTON) == LOW)
{
if (c%2==0) {
Serial.println("→ Sending AC ON");
// sendLG automatically uses the correct 38 kHz carrier and timing for LG
IrSender.sendLG(FULL_ON, 28);
Serial.println(" [Done]\n");
delay(500);
}
else {
Serial.println("→ Sending AC OFF");
IrSender.sendLG(FULL_OFF, 28);
Serial.println(" [Done]\n");
delay(500);
}
c=c+1;
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve button debouncing implementation.

The current implementation uses a simple delay for debouncing, which is not optimal. A time-based debounce approach would be more reliable.

+unsigned long lastButtonPressTime = 0;
+const unsigned long debounceDelay = 500; // 500ms debounce time
+bool buttonState = HIGH;
+bool lastButtonState = HIGH;

void loop() {
+  // Read the current button state
+  bool reading = digitalRead(BOOT_BUTTON);
+  
+  // Check if the button state has changed
+  if (reading != lastButtonState) {
+    lastButtonPressTime = millis();
+  }
+  
+  // If enough time has passed, check for a stable button press
+  if ((millis() - lastButtonPressTime) > debounceDelay) {
+    // If the button state has changed and is now stable
+    if (reading != buttonState) {
+      buttonState = reading;
+      
+      // Only trigger on button press (LOW)
+      if (buttonState == LOW) {
+        if (c%2==0) {
+          Serial.println("→ Sending AC ON");
+          IrSender.sendLG(FULL_ON, 28);
+        } else {
+          Serial.println("→ Sending AC OFF");
+          IrSender.sendLG(FULL_OFF, 28);
+        }
+        Serial.println("  [Done]\n");
+        c=c+1;
+      }
+    }
+  }
+  
+  lastButtonState = reading;

-  // When Boot button is pressed
-  if(digitalRead(BOOT_BUTTON) == LOW)
-  {
-  if (c%2==0) {
-    Serial.println("→ Sending AC ON");
-    // sendLG automatically uses the correct 38 kHz carrier and timing for LG
-    IrSender.sendLG(FULL_ON, 28);
-    Serial.println("  [Done]\n");
-    delay(500);
-  }
-  else {
-    Serial.println("→ Sending AC OFF");
-    IrSender.sendLG(FULL_OFF, 28);
-    Serial.println("  [Done]\n");
-    delay(500);
-  }
-  c=c+1;
-  }
}
📝 Committable suggestion

‼️ 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.

Suggested change
void loop() {
// When Boot button is pressed
if(digitalRead(BOOT_BUTTON) == LOW)
{
if (c%2==0) {
Serial.println("→ Sending AC ON");
// sendLG automatically uses the correct 38 kHz carrier and timing for LG
IrSender.sendLG(FULL_ON, 28);
Serial.println(" [Done]\n");
delay(500);
}
else {
Serial.println("→ Sending AC OFF");
IrSender.sendLG(FULL_OFF, 28);
Serial.println(" [Done]\n");
delay(500);
}
c=c+1;
}
}
unsigned long lastButtonPressTime = 0;
const unsigned long debounceDelay = 500; // 500ms debounce time
bool buttonState = HIGH;
bool lastButtonState = HIGH;
void loop() {
// Read the current button state
bool reading = digitalRead(BOOT_BUTTON);
// Check if the button state has changed
if (reading != lastButtonState) {
lastButtonPressTime = millis();
}
// If enough time has passed, check for a stable button press
if ((millis() - lastButtonPressTime) > debounceDelay) {
// If the button state has changed and is now stable
if (reading != buttonState) {
buttonState = reading;
// Only trigger on button press (LOW)
if (buttonState == LOW) {
if (c % 2 == 0) {
Serial.println("→ Sending AC ON");
IrSender.sendLG(FULL_ON, 28);
} else {
Serial.println("→ Sending AC OFF");
IrSender.sendLG(FULL_OFF, 28);
}
Serial.println(" [Done]\n");
c = c + 1;
}
}
}
lastButtonState = reading;
}

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
BCI-Server-Toggle/BCI-Server-Toggle.ino (1)

218-222: Avoid magic numbers – use the named pin constant

toggleSwitch() writes to GPIO 7 directly, while the rest of the sketch uses the LED_PIN macro.
Using the same symbolic name everywhere reduces the risk of wiring/port-change bugs.

-  digitalWrite(7, bci_val);
+  digitalWrite(LED_PIN, bci_val);
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 113b1b8 and 198342d.

📒 Files selected for processing (1)
  • BCI-Server-Toggle/BCI-Server-Toggle.ino (1 hunks)
🔇 Additional comments (1)
BCI-Server-Toggle/BCI-Server-Toggle.ino (1)

218-221: Send the characteristic value using explicit length to avoid type-size ambiguity

BLECharacteristic::setValue(uint32_t) is not available in all ESP32 BLE variants; some only provide overloads for uint8_t, uint16_t, or raw buffers.
Casting explicitly makes the code portable and future-proof:

-  pCharacteristic_1->setValue(bci_val);
+  pCharacteristic_1->setValue(reinterpret_cast<uint8_t*>(&bci_val),
+                              sizeof(bci_val));

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (2)
BCI-IR-Send/BCI-IR-Send.ino (1)

281-298: Comment still says “4 s” while the code checks 1 s – please align the two

A similar mismatch was flagged earlier in another sketch. Here the millis() comparison is >= 1000UL (1 s) but line 297 claims “4 s”. This can mislead future maintainers.

-    // dropped below before 4 s → cancel timing
+    // dropped below before 1 s → cancel timing

(Alternatively introduce constexpr unsigned long HOLD_MS = 1000; and reference the constant in both code and comments.)

BCI-Server-Toggle/BCI-Server-Toggle.ino (1)

297-318: “4 s” vs 1 s hold-time – comment/code mismatch persists

Line 305 checks for 1 000 ms yet line 313 still references “4 s”.
Please update or remove the stale comment to avoid confusion.

-    // dropped below before 4 s → cancel timing
+    // dropped below before 1 s → cancel timing
🧹 Nitpick comments (4)
BCI-IR-Send/BCI-IR-Send.ino (2)

47-56: Replace magic pin numbers with the already-defined constants for consistency

LED_PIN is declared (line 54) but later you still hard-code 7 in pinMode() and digitalWrite(). Mixing literals and constants easily drifts out of sync during refactors.

-  pinMode(7, OUTPUT);
+  pinMode(LED_PIN, OUTPUT);
...
-  digitalWrite(7, HIGH);
+  digitalWrite(LED_PIN, HIGH);
...
-  digitalWrite(7, LOW);
+  digitalWrite(LED_PIN, LOW);

This keeps the pin assignment single-sourced and self-documenting.


190-206: Use a boolean for the ON/OFF state to avoid unbounded counter growth

The c counter increments forever and relies on c % 2 to decide ON/OFF.
While overflow is unlikely in practice, it’s conceptually simpler (and cheaper) to store a bool:

-bool acState = false;   // false = OFF, true = ON
...
-  if (c%2==0) {
+  if (!acState) {
     ...
-  } else {
+  } else {
     ...
   }
-  c = c + 1;
+  acState = !acState;

Cleaner semantics and one less modulo per trigger.

BCI-Server-Toggle/BCI-Server-Toggle.ino (2)

330-345: Consistently use the LED_PIN macro instead of hard-coding 7

You already declared #define LED_PIN 7; however, pinMode(7, …) and digitalWrite(7, …) bypass the macro. This weakens self-documentation and increases the chance of errors if the pin ever changes.

-  pinMode(7, OUTPUT);
+  pinMode(LED_PIN, OUTPUT);
...
-  digitalWrite(7, bci_val);
+  digitalWrite(LED_PIN, bci_val);

Apply the same replacement in toggleSwitch() as well.


215-222: Prefer a boolean to represent the toggle state

Like the IR example, bci_val alternates between 0 / 1 but is stored in a uint32_t.
Switching to bool clarifies intent and removes needless arithmetic:

-bci_val = (bci_val == 0) ? 1 : 0; // flip state
+bci_val = !bci_val;               // flip state
 pCharacteristic_1->setValue(static_cast<uint32_t>(bci_val));

(No functional change – just clearer.)

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between a9d01af and 9bbb295.

📒 Files selected for processing (2)
  • BCI-IR-Send/BCI-IR-Send.ino (1 hunks)
  • BCI-Server-Toggle/BCI-Server-Toggle.ino (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
BCI-Server-Toggle/BCI-Server-Toggle.ino (1)
Learnt from: CIumsy
PR: upsidedownlabs/npg-lite-firmware#2
File: BCI-Server-Toggle/BCI-Server-Toggle.ino:403-408
Timestamp: 2025-05-10T07:29:42.734Z
Learning: In the NPG-Lite firmware, raw ADC values from the ESP32 (0-4095 range) are directly passed to the IIR filter functions (Notch and EEGFilter) without explicit normalization or centering. The filter coefficients are designed to work with these un-scaled values.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (1)
BCI-IR-Send/BCI-IR-Send.ino (1)

344-346: Consider centering analog readings for improved filter performance

The ADC readings from analogRead() are in the range 0-4095 (for 12-bit resolution), but the filters are designed to work with zero-mean signals. This could affect filter performance and spectral analysis.

I see from the past review comments that you've decided this isn't necessary. While centering signals before filtering is generally a best practice in signal processing, I'll respect your implementation decision.

🧹 Nitpick comments (9)
Serial-FFT/Serial-FFT.ino (4)

14-16: Update copyright year to current year

The copyright notices use the year 2025, which is in the future. Consider updating to the current year or the year the code was written.

-// Copyright (c) 2025 Krishnanshu Mittal - krishnanshu@upsidedownlabs.tech
-// Copyright (c) 2025 Deepak Khatri - deepak@upsidedownlabs.tech
-// Copyright (c) 2025 Upside Down Labs - contact@upsidedownlabs.tech
+// Copyright (c) 2023 Krishnanshu Mittal - krishnanshu@upsidedownlabs.tech
+// Copyright (c) 2023 Deepak Khatri - deepak@upsidedownlabs.tech
+// Copyright (c) 2023 Upside Down Labs - contact@upsidedownlabs.tech

39-43: Define pin numbers as constants for readability

Pin 7 is used in the code but not defined as a constant here. Consider adding a constant for all pins used in the sketch.

#define SAMPLE_RATE       512           // samples per second
#define FFT_SIZE          512           // must be a power of two
#define BAUD_RATE         115200
#define INPUT_PIN         A0
+#define OUTPUT_PIN       7             // pin used for beta threshold detection

188-193: Define the beta threshold as a constant

The 20.0% threshold for beta power is hardcoded. Consider defining it as a configuration constant at the top of the file for better maintainability.

// Add to the configuration section:
+#define BETA_THRESHOLD   20.0f         // beta power percentage to trigger output

// Then modify this code:
-  if(((smoothedPowers.beta/ T)*100)>20.0)
+  if(((smoothedPowers.beta/ T)*100) > BETA_THRESHOLD)

209-210: Use the defined OUTPUT_PIN constant

For consistency, use the OUTPUT_PIN constant (which you should add as suggested earlier) instead of hardcoding pin 7.

void setup() {
  Serial.begin(BAUD_RATE);
  while(!Serial) { delay(1); }
  pinMode(INPUT_PIN, INPUT);
-  pinMode(7, OUTPUT);
+  pinMode(OUTPUT_PIN, OUTPUT);

  initFFT();
}
BCI-IR-Send/BCI-IR-Send.ino (5)

14-16: Update copyright year to current year

The copyright notices use the year 2025, which is in the future. Consider updating to the current year or the year the code was written.

-// Copyright (c) 2025 Krishnanshu Mittal - krishnanshu@upsidedownlabs.tech
-// Copyright (c) 2025 Deepak Khatri - deepak@upsidedownlabs.tech
-// Copyright (c) 2025 Upside Down Labs - contact@upsidedownlabs.tech
+// Copyright (c) 2023 Krishnanshu Mittal - krishnanshu@upsidedownlabs.tech
+// Copyright (c) 2023 Deepak Khatri - deepak@upsidedownlabs.tech
+// Copyright (c) 2023 Upside Down Labs - contact@upsidedownlabs.tech

281-302: Comment and code timing mismatch

The comment on line 297 mentions "4 s" but the code on line 289 checks for 1000 milliseconds (1 second). Update either the comment or the code for consistency.

  else
  {
-    // dropped below before 4 s → cancel timing
+    // dropped below before 1 s → cancel timing
    if (timerRunning)
    {
      stopTimer();
    }
  }

256-279: Optimize NeoPixel updates

The code repeatedly clears and sets the NeoPixels in every FFT cycle, which is inefficient and can cause flickering. Instead, only update the pixels when the band power changes enough to affect the display.

+  // Keep track of the previous LED count to avoid unnecessary updates
+  static int prevLedCount = 0;
+  int ledCount = 0;

-  pixels.clear();  // Sets all pixels to 0 (off)
-  pixels.show();   // Apply changes

  if (betaPerc >= 0 && betaPerc <= 2) {
-  showPixels(1);
+  ledCount = 1;
  } 
  else if (betaPerc > 2 && betaPerc <= 5) {
-  showPixels(2);
+  ledCount = 2;
  } 
  else if (betaPerc > 5 && betaPerc <= 9) {
-  showPixels(3);
+  ledCount = 3;
  } 
  else if (betaPerc > 9 && betaPerc <= 12) {
-  showPixels(4);
+  ledCount = 4;
  } 
  else if (betaPerc > 12 && betaPerc <= 15) {
-  showPixels(5);
+  ledCount = 5;
  } 
  else if (betaPerc > 15) {
-  showPixels(6);
+  ledCount = 6;
  }

-  // Update LEDs once at the end (instead of after each change)
-  pixels.show();
+  // Only update if the LED count changed
+  if (ledCount != prevLedCount) {
+    pixels.clear();
+    showPixels(ledCount);
+    pixels.show();
+    prevLedCount = ledCount;
+  }

259-276: Extract threshold values as constants

The beta percentage thresholds for the NeoPixel levels are hardcoded. Define these as constants for better readability and maintainability.

// Add to the configuration section:
+#define LEVEL1_THRESHOLD  0.0f   // min beta percentage for level 1
+#define LEVEL2_THRESHOLD  2.0f   // min beta percentage for level 2
+#define LEVEL3_THRESHOLD  5.0f   // min beta percentage for level 3
+#define LEVEL4_THRESHOLD  9.0f   // min beta percentage for level 4
+#define LEVEL5_THRESHOLD 12.0f   // min beta percentage for level 5
+#define LEVEL6_THRESHOLD 15.0f   // min beta percentage for level 6
+#define IR_TRIGGER_THRESHOLD 16.0f // beta percentage to trigger IR command

// Then use these constants in the code:
-  if (betaPerc >= 0 && betaPerc <= 2) {
+  if (betaPerc >= LEVEL1_THRESHOLD && betaPerc <= LEVEL2_THRESHOLD) {
  showPixels(1);
  } 
-  else if (betaPerc > 2 && betaPerc <= 5) {
+  else if (betaPerc > LEVEL2_THRESHOLD && betaPerc <= LEVEL3_THRESHOLD) {
  showPixels(2);
  }
  // Similarly update all the other if/else blocks

281-293: Define IR trigger threshold as a constant

The beta power threshold for triggering IR commands (16.0%) is hardcoded. Define this as a constant for better readability and maintainability.

// Add to the configuration section if not already added:
+#define IR_TRIGGER_THRESHOLD 16.0f // beta percentage to trigger IR command

// Then use this constant in the code:
-  if(betaPerc>16.0)
+  if(betaPerc > IR_TRIGGER_THRESHOLD)
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 9bbb295 and 52249ad.

📒 Files selected for processing (4)
  • BCI-IR-Send/BCI-IR-Send.ino (1 hunks)
  • BLE-Client/BLE-Client.ino (1 hunks)
  • BLE-Server/BLE-Server.ino (1 hunks)
  • Serial-FFT/Serial-FFT.ino (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • BLE-Server/BLE-Server.ino
  • BLE-Client/BLE-Client.ino

Comment on lines +197 to +204
delay(250);
}
else {
Serial.println("→ Sending AC OFF");
IrSender.sendLG(FULL_OFF, 28);
Serial.println(" [Done]\n");
delay(250);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Avoid blocking delays in IR sending function

Using delay() in the toggleSwitch() function can disrupt timing of the main EEG sampling loop. Consider using a non-blocking approach with millis().

void toggleSwitch()
{
  if (c%2==0) {
    Serial.println("→ Sending AC ON");
    // sendLG automatically uses the correct 38 kHz carrier and timing for LG
    IrSender.sendLG(FULL_ON, 28);
    Serial.println("  [Done]\n");
-    delay(250);
  }
  else {
    Serial.println("→ Sending AC OFF");
    IrSender.sendLG(FULL_OFF, 28);
    Serial.println("  [Done]\n");
-    delay(250);
  }
  c=c+1;
}

Committable suggestion skipped: line range outside the PR's diff.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
BLE-BCI-Server-Toggle/BLE-BCI-Server-Toggle.ino (3)

44-52: Remove dead-code variables to keep the global state minimal

buttonState, lastButtonState, lastDebounceTime, debounceDelay, and the never-used counter value are declared but never referenced.
In embedded work every byte of RAM matters and unused globals also make maintenance harder because future readers wonder whether they are part of the control logic.

-// Variables will change:
-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;
-// 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
+uint32_t bci_val = 0;                // current switch state (0 / 1)
...
-// Variable that will continuously be increased and written to the client
-uint32_t value = 0;

If you intend to add a physical button later, keep the variables but guard them with // TODO: so the intention is explicit.

Also applies to: 65-67


225-231: Guard showPixels() against out-of-range indices

If a future change accidentally calls showPixels() with ledCount > PIXEL_COUNT, pixels.setPixelColor() will write past the strip length, leading to undefined NeoPixel behaviour.

-for(int i=1;i<=ledCount;i++)
-{
-  pixels.setPixelColor(i-1, pixels.Color(i*10, 0, 0)); // Level 1 to 6
-}
+ledCount = constrain(ledCount, 0, PIXEL_COUNT);
+for (uint8_t i = 0; i < ledCount; ++i) {
+  pixels.setPixelColor(i, pixels.Color((i + 1) * 10, 0, 0));
+}

This tiny guard prevents hard-to-trace visual glitches.


395-409: Sampling via micros() in the loop() can drift under high BLE load

The tight timing loop achieves ~512 Hz only when the main loop() finishes within ≈2 ms.
When BLE notifications or NeoPixel DMA stretch the cycle time, dt = micros() – lastMicros spikes and causes sample-jitter. Over time this produces spectral leakage and reduces beta-band accuracy.

Consider:

• Using an hw_timer_t interrupt to push ADC data into a ring buffer, or
• Leveraging the ESP32 ADC DMA (I2S0) peripheral, as done in esp32-fft examples.

Either option decouples sampling from application-layer delays and yields much cleaner FFTs.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 52249ad and 15fe8ca.

📒 Files selected for processing (3)
  • BLE-BCI-Server-Toggle/BLE-BCI-Server-Toggle.ino (1 hunks)
  • BLE-Client/BLE-Client.ino (1 hunks)
  • BLE-Server/BLE-Server.ino (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • BLE-Server/BLE-Server.ino
  • BLE-Client/BLE-Client.ino

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
BLE-BCI-Server-Toggle/BLE-BCI-Server-Toggle.ino (2)

334-340: Use the LED_PIN macro consistently

pinMode(7, OUTPUT); hard-codes the pin number and re-introduces the maintenance risk called out in the previous review. Stick to the symbolic constant so that only one definition must change if the wiring changes.

-  pinMode(7, OUTPUT);
+  pinMode(LED_PIN, OUTPUT);

218-221: ⚠️ Potential issue

Avoid implicit narrowing when updating the BLE characteristic

BLECharacteristic::setValue() is overloaded. Passing a uint32_t directly selects the uint8_t overload, silently truncating bci_val to one byte (0 or 1).
Transmit the full 32-bit value explicitly:

-  pCharacteristic_1->setValue(bci_val);
+  pCharacteristic_1->setValue(reinterpret_cast<uint8_t*>(&bci_val),
+                              sizeof(bci_val));

This keeps the on-air payload and the local bci_val in sync and avoids undefined behaviour on little-/big-endian targets.

🧹 Nitpick comments (2)
BLE-BCI-Server-Toggle/BLE-BCI-Server-Toggle.ino (2)

64-67: Remove or use the unused value counter

uint32_t value = 0; is never read after initialisation, wasting RAM on a memory-constrained MCU.
Either delete it or implement its intended purpose (e.g. heartbeat packets).


224-231: Bound-check showPixels() to prevent out-of-range writes

If a future change passes ledCount > PIXEL_COUNT, setPixelColor() will write past the strip buffer.

 void showPixels(int ledCount)
 {
-  for(int i=1;i<=ledCount;i++)
+  ledCount = constrain(ledCount, 0, PIXEL_COUNT);
+  for(int i = 1; i <= ledCount; ++i)
   {
     pixels.setPixelColor(i-1, pixels.Color(i*10, 0, 0));
   }
 }
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 15fe8ca and 5603b1a.

📒 Files selected for processing (1)
  • BLE-BCI-Server-Toggle/BLE-BCI-Server-Toggle.ino (1 hunks)

@lorforlinux
Copy link
Member

@CIumsy Thank you so much for your contribution, This is amazing! I am merging this now

@lorforlinux lorforlinux merged commit c96e881 into upsidedownlabs:main May 10, 2025
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants