Skip to content

FFT Visualizer-Multiple EEG & FFT Visualization #43

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 34 commits into from
Jun 20, 2025

Conversation

PayalLakra
Copy link
Member

@PayalLakra PayalLakra commented May 10, 2025

Summary by CodeRabbit

  • New Features

    • Added centralized error logging and reporting for improved diagnostics.
    • Introduced multi-protocol (Wi-Fi, Bluetooth, USB) threaded data streaming with precise timing and unified lifecycle management.
    • Enabled real-time multi-channel EEG visualization, FFT analysis, and configurable channel selection in the EEG app.
    • Added periodic app status checking and enhanced UI feedback for app and connection states.
    • Provided a new web interface connection guide and detailed application descriptions in the documentation.
  • Improvements

    • Enhanced CSV logging with counters and improved filename management.
    • Refined connection cleanup and disconnection handling for all protocols.
    • Modularized and expanded EEG data processing for scalability and user control.
    • Increased connection polling timeouts and status display durations.
    • Simplified BLE sample processing and dynamically handled channel extraction.
    • Updated default Wi-Fi sampling rate to 500 Hz.
  • Bug Fixes

    • Improved error handling for uncaught exceptions and connection failures.
    • Fixed UI inconsistencies related to app launching and connection status.
  • Documentation

    • Completely overhauled the README to reflect multi-protocol support, web interface usage, and contribution guidelines.
    • Updated connection instructions and troubleshooting guidance.
    • Added new application descriptions and screenshots.
  • Style

    • Updated UI elements for better clarity, including status indicator colors and icon consistency.
  • Chores

    • Updated .gitignore to exclude log files and directories.

Copy link

coderabbitai bot commented May 10, 2025

"""

Walkthrough

The updates introduce significant enhancements across multiple components. The backend now supports multi-threaded, precise, and unified data handling for BLE, Wi-Fi, and USB protocols, with robust cleanup and sample rate monitoring. The frontend JavaScript adds centralized error logging, dynamic app status tracking, improved filename management, and resilient UI state handling. The EEG application is refactored for modular, multi-channel real-time analysis with user-configurable plots. Documentation and ignore rules are updated to reflect new features and usage patterns.

Changes

File(s) Change Summary
connection.py Added threaded data handlers for BLE, Wi-Fi, USB, and hybrid protocols; unified sampling at 500 Hz; improved sample rate tracking, cleanup, and lifecycle management; enhanced main loop and connection methods.
static/script.js Centralized error logging to server; added dynamic app status polling and UI updates; improved filename input management; enhanced disconnection and recording state handling; global error handler added.
ffteeg.py Refactored for modular, multi-channel EEG processing; introduced DataProcessor and FFTAnalyzer classes; revamped UI for channel selection and real-time plots; improved filtering and FFT logic.
templates/index.html Changed connection status indicator styling to dark green with white text; removed custom cursor Tailwind config.
app.py Added /log_error POST endpoint for error reporting; refactored /check_stream for concise connection status check.
.gitignore Added rules to ignore /logs/ directory and logging.txt file.
README.md Major rewrite to generalize for multi-protocol, multi-device support; added usage, connection, CSV logging, troubleshooting, and contribution guides.
chords_ble.py Made sample length and channel extraction dynamic based on NUM_CHANNELS; improved adaptability for channel count.
chords_wifi.py Changed default sampling rate from 250 to 500 in constructor.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Frontend (JS)
    participant Backend (Flask)
    participant DataHandler (Threads)
    participant LSL Stream

    User->>Frontend (JS): Connect via UI (Wi-Fi/BLE/USB)
    Frontend (JS)->>Backend (Flask): Request connection
    Backend (Flask)->>DataHandler (Threads): Start protocol-specific handler
    loop Every sample (500Hz)
        DataHandler (Threads)->>LSL Stream: Push timestamped sample
        DataHandler (Threads)->>Backend (Flask): Log to CSV if recording
    end
    Backend (Flask)-->>Frontend (JS): Stream status updates
    Frontend (JS)-->>User: Update UI/app status
    User->>Frontend (JS): Launch app / toggle recording
    Frontend (JS)->>Backend (Flask): App launch / recording request
    Backend (Flask)->>Frontend (JS): App status, errors
    Frontend (JS)->>Backend (Flask): Log error (on error)
Loading
sequenceDiagram
    participant EEGMonitor
    participant DataProcessor
    participant FFTAnalyzer
    participant User

    EEGMonitor->>DataProcessor: Feed new multi-channel sample
    DataProcessor->>EEGMonitor: Provide filtered, buffered data
    EEGMonitor->>FFTAnalyzer: Request FFT/band power for channel
    FFTAnalyzer->>EEGMonitor: Return FFT, band power
    EEGMonitor->>User: Update EEG/FFT/brainpower plots
    User->>EEGMonitor: Change channel selection/settings
    EEGMonitor->>EEGMonitor: Update plot visibility/config
Loading

Possibly related PRs

Poem

🐇
Threads awaken, samples fly,
Across the wires, through LSL they try.
EEG channels now dance in view,
Apps report status, filenames renew.
Errors are caught, logged with care—
This bunny’s code hops everywhere!
🌱
"""


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.
    • Explain this complex logic.
    • 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 explain this code block.
    • @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 explain its main purpose.
    • @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: 2

🧹 Nitpick comments (2)
connection.py (1)

186-194: Minor: give the USB handler thread a descriptive name

Naming the thread helps with debugging (threading.enumerate()) and improves log readability.

 self.usb_running = True
-self.usb_thread = threading.Thread(target=self.usb_data_handler)
+self.usb_thread = threading.Thread(
+    target=self.usb_data_handler,
+    name="USBDataHandler"
+)
static/script.js (1)

466-468: Expose polling timeout as a named constant

maxAttempts is now hard-coded to 15. Extracting it to the top of the file (e.g., const CONNECTION_TIMEOUT = 15;) makes it easier to tune and reuse elsewhere (e.g., UI messages).

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between c71ffe0 and 053fd6a.

📒 Files selected for processing (2)
  • connection.py (8 hunks)
  • static/script.js (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
connection.py (3)
chords_serial.py (3)
  • read_data (96-126)
  • send_command (86-94)
  • cleanup (142-148)
chords_wifi.py (1)
  • cleanup (115-124)
chords_ble.py (2)
  • cleanup (151-159)
  • stop (174-178)
🔇 Additional comments (1)
connection.py (1)

291-294: Main-loop exit condition depends solely on usb_running

With the current logic the main thread spins as long as usb_running is True, regardless of whether the handler thread is actually alive.
After addressing the flag-reset issue, also consider checking self.usb_thread.is_alive() to avoid rare edge cases where the flag and thread state diverge.

Example:

while manager.usb_running and manager.usb_thread.is_alive():
    time.sleep(1)

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 (5)
ffteeg.py (5)

3-3: Remove unused import (QGridLayout).

QGridLayout is imported but never referenced, triggering Ruff F401.
Keeping unused imports increases startup time and confuses static-analysis.

-from PyQt5.QtWidgets import (QApplication, QVBoxLayout, QHBoxLayout, QMainWindow, QWidget, QGridLayout, QScrollArea, QPushButton, QDialog, QCheckBox, QLabel, QComboBox, QFrame, QSizePolicy)
+from PyQt5.QtWidgets import (QApplication, QVBoxLayout, QHBoxLayout, QMainWindow, QWidget, QScrollArea, QPushButton, QDialog, QCheckBox, QLabel, QComboBox, QFrame, QSizePolicy)
🧰 Tools
🪛 Ruff (0.8.2)

3-3: PyQt5.QtWidgets.QGridLayout imported but unused

Remove unused import: PyQt5.QtWidgets.QGridLayout

(F401)


226-233: Colour palette runs out after 8 channels.

self.colors only contains 8 hex values, yet num_channels is determined at runtime.
If the stream exposes > 8 channels, colours will start repeating, making individual traces hard to identify.

Consider generating colours programmatically (e.g., via HSV → RGB conversion) or importing a larger palette from pyqtgraph.functions.mkColor.


248-262: Dead code & minor static-analysis issues in update_plot_visibility.

  1. visible_count is assigned (line 253) but never used → Ruff F841.
  2. Loop variable curve is unused (line 254) → Ruff B007.
-        visible_count = len(self.selected_eeg_channels)
-        for idx, (ch, curve) in enumerate(self.eeg_curves):
+        for idx, (ch, _curve) in enumerate(self.eeg_curves):

If you intended to use visible_count for dynamic stretch factors, fold it into the logic, otherwise drop it.

🧰 Tools
🪛 Ruff (0.8.2)

253-253: Local variable visible_count is assigned to but never used

Remove assignment to unused variable visible_count

(F841)


254-254: Loop control variable curve not used within loop body

(B007)


335-343: Unused loop variable in FFT pre-processing.

curve is never referenced inside this loop, again triggering Ruff B007.

-for ch, curve in self.fft_curves:
+for ch, _ in self.fft_curves:

This also documents that the first pass is concerned only with data collection, not plotting.

🧰 Tools
🪛 Ruff (0.8.2)

335-335: Loop control variable curve not used within loop body

(B007)


381-393: Formula choice for band power is unconventional.

Using sqrt(sum(power²)/N) deviates from typical RMS/PSD or band-integrated power metrics and makes inter-band comparisons non-intuitive.
If intentional, please document the rationale; otherwise consider switching to Welch’s method or band integration of the PSD.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 053fd6a and f269693.

📒 Files selected for processing (1)
  • ffteeg.py (4 hunks)
🧰 Additional context used
🪛 Ruff (0.8.2)
ffteeg.py

3-3: PyQt5.QtWidgets.QGridLayout imported but unused

Remove unused import: PyQt5.QtWidgets.QGridLayout

(F401)


253-253: Local variable visible_count is assigned to but never used

Remove assignment to unused variable visible_count

(F841)


254-254: Loop control variable curve not used within loop body

(B007)


335-335: Loop control variable curve not used within loop body

(B007)

🔇 Additional comments (1)
ffteeg.py (1)

365-378: Potential freqs None path.

If no visible channels have accumulated enough samples (all_fft_results empty) but the brain-power channel has, freqs is still None, yet used later for min-length slicing.

Safest fix: initialise freqs unconditionally inside the brain-power block when it is None.

if freqs is None:
    freqs = np.fft.rfftfreq(len(buffer_windowed), 1 / self.sampling_rate)

(identical to the code above, but ensure it always runs).
Otherwise you risk a TypeError the first time the FFT curves are hidden.

@PayalLakra PayalLakra changed the title FFT Visulaizer FFT Visualizer May 10, 2025
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)
ffteeg.py (1)

267-281: Fixed brainpower buffer reset logic.

The previous issue with the brainpower buffer reset has been fixed correctly. Now the code properly stores the old channel value before updating the selected channel, checks if it changed, and only resets the buffer when necessary.

🧹 Nitpick comments (3)
ffteeg.py (3)

3-3: Remove unused import QGridLayout.

The QGridLayout import is never used in the code. Removing unused imports helps maintain a cleaner codebase.

-from PyQt5.QtWidgets import (QApplication, QVBoxLayout, QHBoxLayout, QMainWindow, QWidget, QGridLayout, QScrollArea, QPushButton, QDialog, QCheckBox, QLabel, QComboBox, QFrame, QSizePolicy)
+from PyQt5.QtWidgets import (QApplication, QVBoxLayout, QHBoxLayout, QMainWindow, QWidget, QScrollArea, QPushButton, QDialog, QCheckBox, QLabel, QComboBox, QFrame, QSizePolicy)
🧰 Tools
🪛 Ruff (0.8.2)

3-3: PyQt5.QtWidgets.QGridLayout imported but unused

Remove unused import: PyQt5.QtWidgets.QGridLayout

(F401)


204-247: Remove unused variable and improve loop efficiency.

The variable visible_count is assigned but never used, and there's an unused loop variable in the initialization code.

-        visible_count = len(self.selected_eeg_channels)
-        for idx, (ch, curve) in enumerate(self.eeg_curves):
+        for idx, (ch, _) in enumerate(self.eeg_curves):

329-378: FFT processing code improvement opportunity.

The FFT processing code contains an unused loop variable and some duplicated code between the visible channels processing and brainpower channel processing.

-        for ch, curve in self.fft_curves:
+        for ch, _ in self.fft_curves:

Consider refactoring to reduce duplication between the FFT calculation for visible channels and the brainpower channel:

def calculate_fft(self, channel):
    """Calculate FFT for the given channel and return freqs and fft_result."""
    if len(self.moving_windows[channel]) < 10:
        return None, None
        
    window = np.hanning(len(self.moving_windows[channel]))
    buffer_windowed = np.array(self.moving_windows[channel]) * window
    fft_result = np.abs(np.fft.rfft(buffer_windowed))
    fft_result /= len(buffer_windowed)
    
    freqs = np.fft.rfftfreq(len(buffer_windowed), 1 / self.sampling_rate)
    
    min_len = min(len(freqs), len(fft_result))
    freqs = freqs[:min_len]
    fft_result = fft_result[:min_len]
    
    return freqs, fft_result
🧰 Tools
🪛 Ruff (0.8.2)

334-334: Loop control variable curve not used within loop body

(B007)

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between f269693 and 2d70312.

📒 Files selected for processing (1)
  • ffteeg.py (4 hunks)
🧰 Additional context used
🪛 Ruff (0.8.2)
ffteeg.py

3-3: PyQt5.QtWidgets.QGridLayout imported but unused

Remove unused import: PyQt5.QtWidgets.QGridLayout

(F401)


253-253: Local variable visible_count is assigned to but never used

Remove assignment to unused variable visible_count

(F841)


254-254: Loop control variable curve not used within loop body

(B007)


334-334: Loop control variable curve not used within loop body

(B007)

🔇 Additional comments (4)
ffteeg.py (4)

14-58: Settings dialog implementation looks good!

The new SettingBox dialog class is well-structured and provides a clear interface for users to select which EEG channels to display and which channel to use for brainpower calculations. The validation logic ensures that at least one EEG channel is always selected.


73-114: Great UI restructuring for multi-channel visualization.

The layout changes effectively split the UI into a scrollable area for EEG plots on the left and FFT/brainpower plots on the right. The settings button is well-positioned and styled.


174-185: Sound implementation of per-channel buffers and filter states.

The changes correctly implement per-channel circular buffers, indices, and filter states to support independent processing of multiple EEG channels. This is a necessary foundation for the multi-channel visualization feature.


379-392: Improved brainwave power calculation with frequency range checks.

The changes correctly add checks to ensure frequency ranges exist before calculating power values, and improve the debug output to include the selected channel information.

@PayalLakra PayalLakra changed the title FFT Visualizer FFT Visualizer-Multiple EEG & FFT Visualization May 12, 2025
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

🧹 Nitpick comments (2)
static/script.js (2)

142-170: Fix class inconsistency in status update function

There's an inconsistency in handling opacity classes: line 157 adds 'opacity-60' while line 164 removes 'opacity-80'.

-                cardElement.classList.remove('opacity-80');
+                cardElement.classList.remove('opacity-60');

173-176: Consider increasing the status polling interval

The current 200ms polling interval is quite aggressive and might cause unnecessary server load. Consider increasing to 500ms or 1s for better performance while still maintaining responsive UI updates.

-    setInterval(checkAllAppStatuses, 200);
+    setInterval(checkAllAppStatuses, 500);
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 2d70312 and 9776489.

📒 Files selected for processing (2)
  • static/script.js (4 hunks)
  • templates/index.html (0 hunks)
💤 Files with no reviewable changes (1)
  • templates/index.html
🔇 Additional comments (8)
static/script.js (8)

51-51: Good addition of periodic app status checking

Adding startAppStatusChecker() during initialization ensures the app running status is constantly monitored from startup.


75-76: Good UI enhancement with status indicators

The card structure has been improved with:

  1. Unique card IDs for targeting specific elements
  2. Status indicators to show running state
  3. Better organized layout structure

This enables visual feedback on running apps.

Also applies to: 84-89


95-95: Good initialization of app status

Calling updateAppStatus when rendering each app ensures the correct initial state is displayed.


97-97: LGTM: Clean code refactoring

Moving from an inline event handler to a named function improves code organization and maintainability.


104-140: Good implementation of app launching with status checking

The handleAppClick function correctly:

  • Prevents launching already running apps
  • Verifies device connection
  • Shows loading state during launch
  • Updates status after launch
  • Handles errors appropriately

This provides a much better user experience.


179-188: Well-structured status checking function

The function correctly:

  • Gets all elements with IDs starting with "status-"
  • Extracts app names from the IDs
  • Updates status for each app

Good implementation.


241-241: Good addition of status refresh after filtering

Calling checkAllAppStatuses() after filtering ensures the app statuses are refreshed, maintaining UI consistency.


526-526: Improved connection reliability with extended timeout

Increasing the connection polling timeout from 5 to 15 seconds gives more time for connections to establish, which should improve reliability, especially for slower connections.

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

🔭 Outside diff range comments (1)
static/script.js (1)

825-856: 🛠️ Refactor suggestion

Redundant code in checkStreamStatus for disconnection handling

The disconnection logic in checkStreamStatus essentially duplicates what's now in the handleDisconnection function. Consider simplifying by just calling the function.

// If not connected, update the frontend
if (isConnected) {
    handleDisconnection();
-   isConnected = false;
-   disconnectBtn.classList.add('hidden');
-   disconnectingBtn.classList.add('hidden');
-   connectingBtn.classList.add('hidden');
-   connectBtn.classList.remove('hidden');
    showStatus('Disconnected!', 'fa-times-circle', 'text-red-500');
-   
-   // Re-enable protocol buttons
-   setProtocolButtonsDisabled(false);
-   
-   // Stop recording if active and update button
-   if (isRecording) {
-       isRecording = false;
-       recordBtn.innerHTML = 'Start Recording';
-       recordBtn.classList.remove('bg-gray-500');
-       recordBtn.classList.add('bg-red-500', 'hover:bg-red-600');
-       recordingStatus.classList.add('hidden');
-       
-       // Enable filename input if recording was stopped due to disconnection
-       filenameInput.disabled = false;
-       filenameInput.classList.remove('bg-gray-100', 'dark:bg-gray-700', 'cursor-not-allowed');
-       filenameInput.classList.add('dark:bg-gray-800');
-       showStatus('Recording stopped (connection lost)', 'fa-stop-circle', 'text-red-500');
-   }
-   
-   // Stop console updates
-   if (eventSource) {
-       eventSource.close();
-       eventSource = null;
-   }
}
♻️ Duplicate comments (2)
connection.py (2)

245-252: Thread shutdown race condition with short timeout

The USB thread is given only 1 second to exit, which may not be enough if it's blocked on I/O operations. The code then proceeds to close the serial port, which can cause exceptions in the still-running thread.

Does the USB thread reliably shut down within the 1-second timeout? You can check this by modifying the cleanup method to monitor if the thread is still alive after the timeout:

#!/bin/bash
# Search for any logging related to thread state during cleanup
rg -A 5 -B 5 "thread.*is_alive|alive.*thread" --no-heading

159-176: ⚠️ Potential issue

USB thread terminates without resetting usb_running flag

When an exception occurs, the thread exits with break but leaves usb_running as True. This could cause the main thread to hang indefinitely in the while manager.usb_running: loop at lines 294-295.

def usb_data_handler(self):
    while self.usb_running:
        try:
            if self.usb_connection and hasattr(self.usb_connection, 'ser') and self.usb_connection.ser.is_open:
                self.usb_connection.read_data()
                
                if hasattr(self.usb_connection, 'data'):
                    sample = self.usb_connection.data[:, -1]
                    if self.lsl_connection:
                        self.lsl_connection.push_sample(sample)
                    if self.recording_active:
                        self.log_to_csv(sample.tolist())
                time.sleep(0.001)  # Small delay to prevent CPU overload
            else:
                time.sleep(0.1)
        except Exception as e:
            print(f"USB data handler error: {str(e)}")
            break
+        # Ensure we reset the flag if we exit the loop for any reason
+    self.usb_running = False
🧹 Nitpick comments (3)
static/script.js (3)

173-176: High frequency polling may cause performance issues

The app status is checked every 200ms, which results in 5 requests per second per app. This could lead to excessive network traffic and server load, especially with many apps.

// Periodically check all app statuses
function startAppStatusChecker() {
    checkAllAppStatuses();
-   setInterval(checkAllAppStatuses, 200);
+   setInterval(checkAllAppStatuses, 1000); // Check once per second instead
}

104-140: App click handler does not use fetched app status data

The function fetches the app status but doesn't use that data to determine if the app is already running before launching it. It only checks the visual indicator through the DOM.

async function handleAppClick(app, card) {
    const statusElement = document.getElementById(`status-${app.script}`);
    if (statusElement && !statusElement.classList.contains('hidden')) {
        return;
    }
    
    if (!isConnected) {
        showAlert('Please connect to a device first using USB, WiFi or Bluetooth');
        return;
    }
    
    const originalContent = card.innerHTML;            // Add loading state to the clicked card
    card.innerHTML = `
        <div class="h-full flex items-center justify-center p-4">
            <i class="fas fa-circle-notch fa-spin text-${app.color}-500 text-xl mr-2"></i>
            <span>Launching ${app.title}...</span>
        </div>
    `;
    
    try {
        const response = await fetch(`/check_app_status/${app.script}`);
        
        if (!response.ok) {
            throw new Error('Failed to check app status');
        }
        
        const data = await response.json();
+       
+       // Verify app is not already running based on server response
+       if (data.status === 'running') {
+           showAlert(`${app.title} is already running`);
+           card.innerHTML = originalContent;
+           updateAppStatus(app.script);
+           return;
+       }
        
        await launchApplication(app.script);
        card.innerHTML = originalContent;
        updateAppStatus(app.script); // Update status after launch
    } catch (error) {
        console.error('Error launching app:', error);
        showAlert(`Failed to launch ${app.title}: ${error.message}`);
        card.innerHTML = originalContent;
    }
}

865-895: Inconsistent disconnection status messages

The code uses different status messages for disconnection in different places: "Disconnected!" vs "Stream disconnected!". Consider using consistent messaging for better user experience.

function handleDisconnection() {
    isConnected = false;
    disconnectBtn.classList.add('hidden');
    disconnectingBtn.classList.add('hidden');
    connectingBtn.classList.add('hidden');
    connectBtn.classList.remove('hidden');
-   showStatus('Stream disconnected!', 'fa-times-circle', 'text-red-500');
+   showStatus('Disconnected!', 'fa-times-circle', 'text-red-500');
    
    // Reset protocol buttons
    connectionBtns.forEach(btn => {
        btn.disabled = false;
        btn.classList.remove('bg-cyan-600', 'dark:bg-cyan-700', 'cursor-default');
        btn.classList.add('hover:bg-cyan-500', 'hover:text-white');
    });
    
    // Handle recording state
    if (isRecording) {
        isRecording = false;
        recordBtn.innerHTML = 'Start Recording';
        recordBtn.classList.remove('bg-gray-500');
        recordBtn.classList.add('bg-red-500', 'hover:bg-red-600');
        recordingStatus.classList.add('hidden');
        filenameInput.disabled = false;
-       showStatus('Recording stopped (stream lost)', 'fa-stop-circle', 'text-red-500');
+       showStatus('Recording stopped (connection lost)', 'fa-stop-circle', 'text-red-500');
    }
    
    if (eventSource) {
        eventSource.close();
        eventSource = null;
    }
}
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 409ea22 and 44fb655.

📒 Files selected for processing (4)
  • app.py (1 hunks)
  • connection.py (8 hunks)
  • static/script.js (15 hunks)
  • templates/index.html (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • templates/index.html
🔇 Additional comments (4)
app.py (1)

66-67: ⚠️ Potential issue

Potential null reference exception in connection_manager check

The new implementation uses a streamlined approach to check if the connection is active. However, if connection_manager is None, the hasattr() call will raise an AttributeError before it can return False.

-    is_connected = connection_manager.stream_active if hasattr(connection_manager, 'stream_active') else False
+    is_connected = False
+    if connection_manager is not None and hasattr(connection_manager, 'stream_active'):
+        is_connected = connection_manager.stream_active

Likely an incorrect or invalid review comment.

static/script.js (3)

294-312: Good implementation of timestamp updater

The timestamp updater nicely handles updating the filename dynamically with two smart features:

  1. Only updates when not recording
  2. Preserves custom filenames that don't match the default pattern

95-98: Good refactoring to use dedicated app handlers

Moving from inline click handlers to separate function calls improves code organization and maintainability, especially with the added app status checking.


548-549: Good UX improvement with increased connection timeout

Increasing the polling timeout from 5 to 15 seconds provides more time for connections to establish, improving reliability especially for slower or intermittent connections.

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

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 44fb655 and 5e98400.

📒 Files selected for processing (3)
  • .gitignore (1 hunks)
  • app.py (3 hunks)
  • static/script.js (23 hunks)
✅ Files skipped from review due to trivial changes (1)
  • .gitignore
🧰 Additional context used
🪛 Ruff (0.8.2)
app.py

37-37: Local variable e is assigned to but never used

Remove assignment to unused variable e

(F841)

🔇 Additional comments (19)
app.py (2)

11-11: Appropriate import for directory operations.

The os module is required for the new directory operations in the /log_error endpoint.


83-84: Clean refactoring of stream status check.

The refactored code elegantly uses hasattr() to check for the existence of the stream_active attribute before accessing it, reducing code complexity while maintaining functionality.

static/script.js (17)

1-23: Well-implemented error logging mechanism with recursion protection.

The new centralized error logging function correctly implements:

  1. Protection against recursive logging with the isLogging flag
  2. Proper error formatting with timestamps
  3. Server-side logging with fallback to console
  4. Comprehensive error object handling (stack, message, or toString)

This provides a robust foundation for client-side error reporting.


48-48: Consistent error logging replacement.

All console error calls are replaced with the new centralized logError() function, providing consistent error handling throughout the application.

Also applies to: 77-77, 160-160, 192-192, 459-459, 600-600, 713-713, 734-734, 781-781, 807-807, 885-885


128-164: New app click handler with proper status verification.

The handleAppClick function properly:

  1. Checks if the app is already running before attempting to launch
  2. Verifies connection status
  3. Adds visual loading state during launch
  4. Handles errors and restores the original button state

166-194: Comprehensive app status update function.

The updateAppStatus function effectively:

  1. Fetches the running status of the app
  2. Updates visual indicators (showing/hiding status elements)
  3. Adjusts CSS classes to visually disable running apps
  4. Properly handles the cursor styles and interactivity

197-212: Efficient periodic app status checking.

The status checker implementation:

  1. Runs checks at appropriate intervals (200ms)
  2. Uses element selectors efficiently to find all app cards
  3. Extracts app names correctly from element IDs

317-335: Useful timestamp updater for filename input.

The timestamp updater:

  1. Updates the filename at appropriate intervals (1 second)
  2. Only updates when not recording
  3. Intelligently handles both placeholder and value to maintain consistent naming
  4. Preserves custom filenames entered by users

379-382: Improved initialization of filename input.

The updated initialization properly:

  1. Ensures the input is enabled initially
  2. Sets appropriate CSS classes for light/dark mode
  3. Starts the timestamp updater

572-572: Connection polling timeout increased for better reliability.

Increasing the connection polling timeout from 5 to 15 seconds provides more time for the connection to establish, which can be particularly important for slower connections or when there are network issues.


771-777: Improved recording stop handling with proper input state management.

The code now properly:

  1. Re-enables the filename input after stopping recording
  2. Restores appropriate CSS classes for light/dark mode
  3. Updates the timestamp for the filename
  4. Shows a status message to the user

798-803: Appropriate input state management when recording starts.

The code properly:

  1. Disables the filename input during recording
  2. Applies appropriate CSS classes for visual feedback
  3. Shows a status message to indicate recording has started

820-820: Improved status indicator.

The changes:

  1. Standardize the icon color to white for better visibility
  2. Extend the status display duration from 2 to 3 seconds, giving users more time to notice

Also applies to: 824-824


838-838: Connection status synchronization.

The code now correctly calls handleConnectionSuccess() when a connection is detected, ensuring UI state is properly synchronized with the backend connection status.


850-850: Improved disconnection handling with proper user feedback.

The code now:

  1. Centralizes disconnection handling by calling handleDisconnection()
  2. Shows a status message to inform the user of the disconnection

Also applies to: 856-856


868-874: Comprehensive handling of disconnection during recording.

The code properly:

  1. Re-enables the filename input if recording was stopped due to disconnection
  2. Restores appropriate CSS classes
  3. Shows a clear status message explaining why recording stopped

889-919: Centralized disconnection handler function.

The new handleDisconnection() function properly:

  1. Resets connection state variables
  2. Updates UI button states
  3. Shows status message
  4. Resets protocol buttons
  5. Handles recording state cleanup
  6. Closes event sources

This centralization improves code maintainability and ensures consistent behavior.


930-932: Global error handler for improved error reporting.

The window.onerror handler captures uncaught errors and forwards them to the centralized logging system. This is an excellent defensive programming practice that helps catch errors that might otherwise go unreported.


676-676: Consistent disconnection status message.

The disconnect button handler now shows a "Disconnected!" status message on successful disconnect, consistent with other disconnection messaging in the application.

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 (6)
README.md (6)

10-15: Consistent bullet formatting in Features

  • Add a space after the colon in **LSL Data Streaming**:Once**LSL Data Streaming**: Once
  • Ensure consistent trailing punctuation (either end all bullets with periods or none).
  • Consider hyphenating "multi-channel live plotting" for clarity.

17-19: Minor grammar in Python requirement

Change to:

-1. **Python**: Ensure Latest version of Python is installed.
+1. **Python**: Ensure the latest version of Python is installed.

21-23: Indent code block under list properly

The Virtual Environment code fence is indented by 3 spaces. Markdown nested lists conventionally use 4 spaces per level. For example:

-   ```bash
+    ```bash

This ensures the code block renders correctly under the "2. Virtual Environment" item.


52-58: Normalize Connection Guide headings

Lower the subheading level one level (from #### to ###) under ## Connection Guide, and use consistent hyphenation:

-#### WIFI Connection
+### Wi-Fi Connection

-#### Bluetooth Connection
+### Bluetooth Connection

-#### Serial Connection
+### Serial Connection

This aligns nesting and spelling across connection protocols.

Also applies to: 58-63

🧰 Tools
🪛 markdownlint-cli2 (0.17.2)

52-52: Heading levels should only increment by one level at a time
Expected: h3; Actual: h4

(MD001, heading-increment)


80-93: Improve Applications table formatting

  • Surround the table with a blank line above and below to satisfy Markdown lint rules.
  • In descriptions, hyphenate compound modifiers:
    • 2 Player EEG Based Game2-player EEG-based game
    • Real-time EEG focus based gameReal-time EEG focus-based game
🧰 Tools
🪛 LanguageTool

[uncategorized] ~87-~87: This expression is usually spelled with a hyphen.
Context: ...| EEG Tug of War Game | 2 Player EEG Based Game ...

(BASED_HYPHEN)


[uncategorized] ~88-~88: This expression is usually spelled with a hyphen.
Context: ...EG Beetle game** | Real-time EEG focus based game. ...

(BASED_HYPHEN)

🪛 markdownlint-cli2 (0.17.2)

81-81: Tables should be surrounded by blank lines
null

(MD058, blanks-around-tables)


100-107: Wrap file names in backticks

In the Contributing section, wrap apps.yaml and script file references in backticks for clarity:

-- Add a button in apps.yaml to link your application.
+- Add a button in `apps.yaml` to link your application.
-- Include your script as a .py file with LSL Data Reception code.
+- Include your script as a `.py` file with LSL data reception code.
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 5e98400 and 090a55c.

⛔ Files ignored due to path filters (2)
  • media/Interface.png is excluded by !**/*.png
  • media/Webinterface.png is excluded by !**/*.png
📒 Files selected for processing (1)
  • README.md (1 hunks)
🧰 Additional context used
🪛 LanguageTool
README.md

[duplication] ~59-~59: Possible typo: you repeated a word.
Context: ...ction 1. Ensure Bluetooth is turned ON on your system. 2. Upload the Bluetoot...

(ENGLISH_WORD_REPEAT_RULE)


[uncategorized] ~87-~87: This expression is usually spelled with a hyphen.
Context: ...| EEG Tug of War Game | 2 Player EEG Based Game ...

(BASED_HYPHEN)


[uncategorized] ~88-~88: This expression is usually spelled with a hyphen.
Context: ...EG Beetle game** | Real-time EEG focus based game. ...

(BASED_HYPHEN)

🪛 markdownlint-cli2 (0.17.2)
README.md

44-44: Trailing punctuation in heading
Punctuation: ':'

(MD026, no-trailing-punctuation)


52-52: Heading levels should only increment by one level at a time
Expected: h3; Actual: h4

(MD001, heading-increment)


81-81: Tables should be surrounded by blank lines
null

(MD058, blanks-around-tables)

🔇 Additional comments (3)
README.md (3)

5-9: Verify supported admonition syntax

The [!NOTE] fence is used for an admonition block. Please confirm that your Markdown renderer (e.g., GitHub Pages with an admonitions plugin or your docs framework) recognizes this syntax. If not, switch to a supported format (e.g., <details> tags or :::note).


25-28: Dependencies instruction is clear

The command pip install -r requirements.txt covers installation of dependencies. No changes needed here.


108-115: Contributors list is clear

The contributors section is alphabetically sorted and properly formatted.

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)
README.md (2)

3-4: Project description needs expansion
The current opening line still frames the tool as a “Python script,” but this PR delivers a full web-based dashboard with multi-protocol support and multi-channel EEG/FFT visualization. Please update this to highlight the web interface entry point, Wi-Fi/Bluetooth/Serial connectivity, and multi-channel FFT plots.


41-42: Fix image paths
Still using absolute Windows backslashes—these won’t render on CI or GitHub Pages. Update to relative forward-slash paths, e.g.:

![Web Interface Screenshot](media/Interface.png)  
![Web Interface](media/Webinterface.png)  
🧹 Nitpick comments (8)
README.md (8)

11-15: Refine feature list and add new FFT visualization bullet

  • Line 12: add a space after the colon (**LSL Data Streaming**: Once…).
  • Line 13: clarify “Counter” (e.g., “with a frame counter”).
  • Introduce a new bullet for the multi-channel FFT visualization settings dialog.

Example diff:

- - **LSL Data Streaming**:Once the LSL stream starts…
+ - **LSL Data Streaming**: Once the LSL stream starts…

- - **CSV Logging**: Save raw data with Counter 
+ - **CSV Logging**: Save raw data with a frame counter.

+ - **Multi-Channel FFT Visualization**: Select EEG channels dynamically and view real-time FFT plots.

18-18: Add missing article and lowercase “latest”
Change to “Ensure the latest version of Python is installed.”

🧰 Tools
🪛 LanguageTool

[uncategorized] ~18-~18: You might be missing the article “the” here.
Context: ...## Installation 1. Python: Ensure Latest version of Python is installed. 2. **...

(AI_EN_LECTOR_MISSING_DETERMINER_THE)


47-49: Correct grammar and formatting

  • Change “Multiple Applications can be Launch” to “Multiple applications can be launched.”
  • Add a space before the parenthesis: “…simultaneously (e.g., EEG Tug of War).”

53-57: Unify protocol naming
The heading and description use “WIFI” here but elsewhere we use “Wi-Fi.” Consider standardizing on “Wi-Fi” for consistency.


59-64: Clarify firmware vs. code terminology
Step 2 says “Upload the Bluetooth code.” It may be clearer to refer to “Upload the Bluetooth firmware/sketch to your device.”

🧰 Tools
🪛 LanguageTool

[duplication] ~59-~59: Possible typo: you repeated a word.
Context: ...ction 1. Ensure Bluetooth is turned ON on your system. 2. Upload the Bluetoot...

(ENGLISH_WORD_REPEAT_RULE)


73-78: Standardize heading punctuation
Use a colon in the subheading: change “Visualizing CSV Data - You can plot…” to “Visualizing CSV Data: You can plot…”.


82-88: Improve table formatting and hyphenation

  • Add a blank line before and after the table for readability.
  • In “2 Player EEG Based Game,” hyphenate as “2-Player EEG-Based Game.”
  • Capitalize “Game” in “EEG Beetle Game” and hyphenate “focus-based.”

Example diff:

+ 
  | Application                | Description                                                      |
+ 
  |----------------------------|------------------------------------------------------------------|
  | **ECG with Heart Rate**    | Real-time ECG with BPM calculation.                              |
  | **EMG with Envelope**      | Real-time EMG Visualization with Envelope.                       |
  | **EOG with Blinks**        | Real-time EOG Signal visualization with Blinks marked as Red Dot.|
  | **EEG with FFT**           | Real-time EEG Signal visualization with FFT and Brainpower bands.|
- | **EEG Tug of War Game**    | 2 Player EEG Based Game                                          |
- | **EEG Beetle game**        | Real-time EEG focus based game.                                  |
+ | **EEG Tug of War Game**    | 2-Player EEG-Based Game                                          |
+ | **EEG Beetle Game**        | Real-time EEG focus-based game.                                  |
+ 
🧰 Tools
🪛 LanguageTool

[uncategorized] ~86-~86: This expression is usually spelled with a hyphen.
Context: ...| EEG Tug of War Game | 2 Player EEG Based Game ...

(BASED_HYPHEN)


[uncategorized] ~87-~87: This expression is usually spelled with a hyphen.
Context: ...EG Beetle game** | Real-time EEG focus based game. ...

(BASED_HYPHEN)


98-103: Enhance contribution guidelines
Consider linking to a CONTRIBUTING.md, specifying code style rules, testing requirements, and the PR workflow to guide new contributors effectively.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 090a55c and 15dc4c2.

📒 Files selected for processing (1)
  • README.md (1 hunks)
🧰 Additional context used
🪛 LanguageTool
README.md

[uncategorized] ~18-~18: You might be missing the article “the” here.
Context: ...## Installation 1. Python: Ensure Latest version of Python is installed. 2. **...

(AI_EN_LECTOR_MISSING_DETERMINER_THE)


[duplication] ~59-~59: Possible typo: you repeated a word.
Context: ...ction 1. Ensure Bluetooth is turned ON on your system. 2. Upload the Bluetoot...

(ENGLISH_WORD_REPEAT_RULE)


[uncategorized] ~86-~86: This expression is usually spelled with a hyphen.
Context: ...| EEG Tug of War Game | 2 Player EEG Based Game ...

(BASED_HYPHEN)


[uncategorized] ~87-~87: This expression is usually spelled with a hyphen.
Context: ...EG Beetle game** | Real-time EEG focus based game. ...

(BASED_HYPHEN)

🪛 markdownlint-cli2 (0.17.2)
README.md

44-44: Trailing punctuation in heading
Punctuation: ':'

(MD026, no-trailing-punctuation)


52-52: Heading levels should only increment by one level at a time
Expected: h3; Actual: h4

(MD001, heading-increment)


80-80: Tables should be surrounded by blank lines
null

(MD058, blanks-around-tables)

🔇 Additional comments (5)
README.md (5)

21-24: The virtual environment creation and activation commands are clear and accurate.


27-28: Installing from requirements.txt is correct. Ensure this file is up to date with new dependencies.


30-34: The Windows execution policy note is helpful and properly formatted.


38-39: Running python app.py correctly reflects the new web interface entry point.


67-70: Serial connection steps are clear and consistent with the other protocols.

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)
README.md (1)

3-3: 🛠️ Refactor suggestion

Update project description to reflect web-based dashboard and multi-protocol support

The top description still refers to a Python script. Consider rephrasing to highlight the new web-based interface, multi-protocol (Wi-Fi, Bluetooth, Serial) support, and multi-channel FFT visualization.

🧹 Nitpick comments (4)
README.md (4)

10-16: Fix spacing and improve feature bullet consistency

  • Add a space after the colon in line 12: change **LSL Data Streaming**:Once to **LSL Data Streaming**: Once.
  • Clarify “Save raw data with Counter” on line 13—what is “Counter”?
  • Ensure uniform punctuation across bullets (either all end with periods or none do).

17-20: Add missing determiner in installation step

Change line 18 from:

1. **Python**: Ensure Latest version of Python is installed.

to:

1. **Python**: Ensure the latest version of Python is installed.

Also lowercase “latest” for consistency.

🧰 Tools
🪛 LanguageTool

[uncategorized] ~18-~18: You might be missing the article “the” here.
Context: ...## Installation 1. Python: Ensure Latest version of Python is installed. 2. **...

(AI_EN_LECTOR_MISSING_DETERMINER_THE)


46-51: Fix heading punctuation and grammar in Key Options

  • Remove trailing colon in the heading: change ### Key Options: to ### Key Options.
  • On line 50, revise grammar and spacing:
- Multiple Applications can be Launch from the Interface simultaneously(e.g., `EEG Tug of War`).
+ Multiple applications can be launched from the interface simultaneously (e.g., `EEG Tug of War`).
  • Optional: align “LSL Streaming” naming here with “LSL Data Streaming” used in Features.
🧰 Tools
🪛 markdownlint-cli2 (0.17.2)

46-46: Trailing punctuation in heading
Punctuation: ':'

(MD026, no-trailing-punctuation)


81-92: Surround table with blank lines and correct hyphenation

  • Add a blank line before and after the table for markdownlint compliance.
  • Fix hyphenation and capitalization in game names on lines 88–89:
- | **EEG Tug of War Game**    | 2 Player EEG Based Game                                          |
+ | **EEG Tug of War Game**    | 2-Player EEG-Based Game                                          |
- | **EEG Beetle game**        | Real-time EEG focus based game.                                  |
+ | **EEG Beetle Game**        | Real-time EEG-focus-based game.                                  |

Ensure consistent naming (“Beetle Game”).

🧰 Tools
🪛 LanguageTool

[uncategorized] ~88-~88: This expression is usually spelled with a hyphen.
Context: ...| EEG Tug of War Game | 2 Player EEG Based Game ...

(BASED_HYPHEN)


[uncategorized] ~89-~89: This expression is usually spelled with a hyphen.
Context: ...EG Beetle game** | Real-time EEG focus based game. ...

(BASED_HYPHEN)

🪛 markdownlint-cli2 (0.17.2)

82-82: Tables should be surrounded by blank lines
null

(MD058, blanks-around-tables)

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 15dc4c2 and f4e16c1.

📒 Files selected for processing (1)
  • README.md (1 hunks)
🧰 Additional context used
🪛 LanguageTool
README.md

[uncategorized] ~18-~18: You might be missing the article “the” here.
Context: ...## Installation 1. Python: Ensure Latest version of Python is installed. 2. **...

(AI_EN_LECTOR_MISSING_DETERMINER_THE)


[duplication] ~61-~61: Possible typo: you repeated a word.
Context: ...ction 1. Ensure Bluetooth is turned ON on your system. 2. Upload the Bluetoot...

(ENGLISH_WORD_REPEAT_RULE)


[uncategorized] ~88-~88: This expression is usually spelled with a hyphen.
Context: ...| EEG Tug of War Game | 2 Player EEG Based Game ...

(BASED_HYPHEN)


[uncategorized] ~89-~89: This expression is usually spelled with a hyphen.
Context: ...EG Beetle game** | Real-time EEG focus based game. ...

(BASED_HYPHEN)

🪛 markdownlint-cli2 (0.17.2)
README.md

46-46: Trailing punctuation in heading
Punctuation: ':'

(MD026, no-trailing-punctuation)


54-54: Heading levels should only increment by one level at a time
Expected: h3; Actual: h4

(MD001, heading-increment)


82-82: Tables should be surrounded by blank lines
null

(MD058, blanks-around-tables)

🔇 Additional comments (7)
README.md (7)

5-9: Note block looks good

The firmware note clearly lists required firmwares for Arduino and NPG-Lite. No changes needed.


28-32: Important Windows execution note is clear

The [!IMPORTANT] callout and PowerShell command are helpful and correctly formatted.


34-45: Usage section and screenshots formatting are correct

The usage instructions and image paths use forward slashes and display the web UI previews correctly.


73-80: CSV Logging section is well-structured

The steps and description are clear. No changes needed.


94-99: Troubleshooting section is clear

Troubleshooting tips are concise and well-formatted. No changes needed.


100-107: How to Contribute section is informative

Contribution instructions are clear and actionable.


108-115: Contributors and attribution are correctly listed

The contributors list and audio file attribution are properly formatted.

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)
testing.py (3)

3-3: Remove unused import to keep namespace clean

QGridLayout is imported but never used, triggering Ruff F401.
Delete it to avoid clutter and slightly faster start-up.

-from PyQt5.QtWidgets import (QApplication, QVBoxLayout, QHBoxLayout, QMainWindow, QWidget, QGridLayout, QScrollArea, QPushButton, QDialog, QCheckBox, QLabel, QComboBox, QFrame, QSizePolicy)
+from PyQt5.QtWidgets import (QApplication, QVBoxLayout, QHBoxLayout, QMainWindow, QWidget, QScrollArea, QPushButton, QDialog, QCheckBox, QLabel, QComboBox, QFrame, QSizePolicy)
🧰 Tools
🪛 Ruff (0.11.9)

3-3: PyQt5.QtWidgets.QGridLayout imported but unused

Remove unused import: PyQt5.QtWidgets.QGridLayout

(F401)


95-99: Throttle or remove per-frame console spam

print(f"[FFT] Channel …") executes every redraw (~50 Hz), rapidly flooding stdout and slowing the UI.
Wrap it behind a debug flag or emit only every few seconds.

-if channel == 0:
-    peak_idx = np.argmax(fft_magnitude[1:]) + 1
-    peak_freq = self.freqs[peak_idx]
-    print(f"[FFT] Channel {channel+1}: Peak at {peak_freq:.2f} Hz")
+if self.verbose and channel == 0 and (time.time() - self._last_peak_log) > 2:
+    peak_idx = np.argmax(fft_magnitude[1:]) + 1
+    peak_freq = self.freqs[peak_idx]
+    print(f"[FFT] Channel {channel+1}: Peak at {peak_freq:.2f} Hz")
+    self._last_peak_log = time.time()

(Needs two new attributes: self.verbose and self._last_peak_log.)


362-366: Drop unused loop variable to silence Ruff B007

curve is never referenced inside the loop.

-for idx, (ch, curve) in enumerate(self.eeg_curves):
+for idx, (ch, _) in enumerate(self.eeg_curves):

Minor cleanup, no behaviour change.

🧰 Tools
🪛 Ruff (0.11.9)

363-363: Loop control variable curve not used within loop body

(B007)

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between f4e16c1 and 8ae4832.

📒 Files selected for processing (1)
  • testing.py (1 hunks)
🧰 Additional context used
🪛 Ruff (0.11.9)
testing.py

3-3: PyQt5.QtWidgets.QGridLayout imported but unused

Remove unused import: PyQt5.QtWidgets.QGridLayout

(F401)


363-363: Loop control variable curve not used within loop body

(B007)

🪛 Pylint (3.3.7)
testing.py

[refactor] 18-18: Too many instance attributes (11/7)

(R0902)


[refactor] 144-144: Too few public methods (1/2)

(R0903)


[refactor] 184-184: Too many instance attributes (31/7)

(R0902)

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: 4

♻️ Duplicate comments (2)
connection.py (2)

202-235: Same critical issues as BLE handler - missing methods and flag management.

The WiFi data handler has identical issues with non-existent data_available and get_latest_sample methods.


370-372: Address previous review feedback on thread shutdown order.

A previous reviewer suggested sending the STOP command before joining threads for cleaner shutdown.

Apply the suggested improvement:

        if self.usb_connection:
            try:
+               if hasattr(self.usb_connection, 'ser') and self.usb_connection.ser.is_open:
+                   self.usb_connection.send_command('STOP')
                if hasattr(self.usb_connection, 'ser') and self.usb_connection.ser.is_open:
-                   self.usb_connection.send_command('STOP')
                    self.usb_connection.ser.close()
🧹 Nitpick comments (3)
chords_wifi.py (1)

8-8: Parameter updates align with multi-channel streaming requirements.

The increased channels (3→6), sampling rate (250→500 Hz), and block size (13→15) properly support the unified multi-channel data acquisition model described in the AI summary.

Consider using a configuration dictionary or dataclass to reduce the argument count:

-def __init__(self, stream_name='NPG', channels=6, sampling_rate=500, block_size=15, timeout_sec=1):
+def __init__(self, config=None):
+    config = config or {'stream_name': 'NPG', 'channels': 6, 'sampling_rate': 500, 'block_size': 15, 'timeout_sec': 1}
+    self.stream_name = config['stream_name']
+    self.channels = config['channels']
+    # ... etc
stream.py (1)

11-61: LGTM! Well-designed thread-safe circular buffer.

The CircularBuffer class properly implements:

  • Thread-safe operations with locks
  • Proper wraparound logic for circular indexing
  • Robust data handling for both single values and arrays
  • Comprehensive buffer state management

The static analysis suggests removing the unnecessary else after return in lines 42-47:

            if read_start + block_size <= self.size:
                return self.buffer[read_start:read_start + block_size].copy()
-            else:
-                first_part = self.buffer[read_start:].copy()
-                second_part = self.buffer[:block_size - len(first_part)].copy()
-                return np.concatenate([first_part, second_part])
+            first_part = self.buffer[read_start:].copy()
+            second_part = self.buffer[:block_size - len(first_part)].copy()
+            return np.concatenate([first_part, second_part])
ffteeg.py (1)

3-3: Remove unused import.

The static analysis correctly identifies that QGridLayout is imported but never used.

-from PyQt5.QtWidgets import (QApplication, QVBoxLayout, QHBoxLayout, QMainWindow, QWidget, QGridLayout, QScrollArea, QPushButton, QDialog, QCheckBox, QLabel, QComboBox, QFrame, QSizePolicy)
+from PyQt5.QtWidgets import (QApplication, QVBoxLayout, QHBoxLayout, QMainWindow, QWidget, QScrollArea, QPushButton, QDialog, QCheckBox, QLabel, QComboBox, QFrame, QSizePolicy)
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 8ae4832 and 0aaf8db.

📒 Files selected for processing (5)
  • chords_ble.py (2 hunks)
  • chords_wifi.py (1 hunks)
  • connection.py (6 hunks)
  • ffteeg.py (1 hunks)
  • stream.py (1 hunks)
🧰 Additional context used
🪛 Pylint (3.3.7)
stream.py

[refactor] 42-47: Unnecessary "else" after "return", remove the "else" and de-indent the code inside it

(R1705)


[refactor] 62-62: Too many instance attributes (12/7)

(R0902)


[refactor] 213-213: Too many instance attributes (8/7)

(R0902)


[refactor] 277-277: Too many instance attributes (13/7)

(R0902)

ffteeg.py

[refactor] 18-18: Too many instance attributes (11/7)

(R0902)


[refactor] 128-128: Too few public methods (1/2)

(R0903)


[refactor] 173-173: Too many instance attributes (30/7)

(R0902)

connection.py

[error] 175-175: Instance of 'Chords_BLE' has no 'data_available' member

(E1101)


[error] 179-179: Instance of 'Chords_BLE' has no 'get_latest_sample' member

(E1101)


[refactor] 173-200: Too many nested blocks (6/5)

(R1702)


[error] 209-209: Instance of 'Chords_WIFI' has no 'data_available' member

(E1101)


[error] 213-213: Instance of 'Chords_WIFI' has no 'get_latest_sample' member

(E1101)


[refactor] 207-234: Too many nested blocks (6/5)

(R1702)


[refactor] 241-269: Too many nested blocks (6/5)

(R1702)


[refactor] 347-347: Too many branches (15/12)

(R0912)

chords_wifi.py

[refactor] 8-8: Too many arguments (6/5)

(R0913)


[refactor] 8-8: Too many positional arguments (6/5)

(R0917)

🪛 Ruff (0.11.9)
ffteeg.py

3-3: PyQt5.QtWidgets.QGridLayout imported but unused

Remove unused import: PyQt5.QtWidgets.QGridLayout

(F401)


356-356: Loop control variable curve not used within loop body

(B007)

connection.py

160-160: Local variable estimated_rate is assigned to but never used

Remove assignment to unused variable estimated_rate

(F841)


420-421: Use a single if statement instead of nested if statements

Combine if statements using and

(SIM102)

chords_ble.py

76-76: Local variable channels is assigned to but never used

Remove assignment to unused variable channels

(F841)

🔇 Additional comments (9)
chords_ble.py (1)

16-17: LGTM! Dynamic channel configuration improves maintainability.

The introduction of NUM_CHANNELS constant and computed SINGLE_SAMPLE_LEN makes the code more flexible and eliminates magic numbers.

stream.py (3)

100-126: Verify timeout handling in hardware detection loop.

The retry logic correctly attempts multiple baudrates and handles connection failures. However, ensure that the timeout parameter in connect_hardware is sufficient for slow devices.

Consider testing with actual hardware to verify the timeout values are appropriate for all supported boards, especially slower ones like Arduino Uno variants.


213-276: LGTM! Robust FFT processing with proper threading.

The FFTProcessor class implements:

  • Proper windowing with Hanning window
  • Appropriate overlap for smooth FFT updates
  • Thread-safe data access with locks
  • Peak frequency detection and debugging output

277-334: GUI implementation looks solid with proper PyQt patterns.

The EEGMonitor class correctly implements:

  • Proper PyQt inheritance and initialization
  • Dual plot layout for raw EEG and FFT data
  • Timer-based updates with appropriate interval (20ms)
  • Proper data buffer management for visualization
connection.py (1)

124-139: LGTM! Well-implemented sample rate monitoring.

The update_sample_rate method provides real-time feedback on actual sampling rates using a sliding window average, which is valuable for debugging and monitoring system performance.

ffteeg.py (4)

18-57: Excellent modular design for per-channel data processing.

The DataProcessor class properly implements:

  • Independent filter states per channel with correct initialization
  • Circular buffering for display data
  • Moving windows for FFT processing
  • Thread-safe data management

58-127: LGTM! Robust FFT analysis with proper windowing and smoothing.

The FFTAnalyzer class correctly implements:

  • Hanning windowing with proper amplitude correction
  • Frequency resolution calculation and display
  • Smoothing buffers for stable FFT output
  • Comprehensive band power calculation with relative normalization

128-172: Well-designed settings dialog with proper validation.

The SettingBox class provides intuitive channel selection with appropriate validation ensuring at least one EEG channel is always selected.


378-394: LGTM! Proper multi-channel data processing and UI updates.

The main update loop correctly:

  • Pulls chunks of samples from LSL
  • Processes each sample through the DataProcessor
  • Updates all relevant plots based on channel selection
  • Handles stream disconnection gracefully

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 (5)
connection.py (5)

169-221: Critical: Reference to non-existent connection attribute.

The method references self.connection which doesn't exist in the Connection class. This will cause AttributeError at runtime.

-        while self.running and self.connection:
+        while self.running and self.usb_connection:
             try:
-                raw_sample = self.connection.get_latest_sample()
+                raw_sample = self.usb_connection.get_latest_sample()

223-285: Critical: Reference to non-existent connection attribute.

Same issue as the counter-based handler - references self.connection instead of the actual connection object.

-        while self.running and self.connection:
+        while self.running and self.usb_connection:
             try:
-                raw_sample = self.connection.get_latest_sample()
+                raw_sample = self.usb_connection.get_latest_sample()

287-319: Critical: Missing methods in BLE connection class.

The handler assumes data_available and get_latest_sample methods exist on the BLE connection object, but these are not present in chords_ble.py.

Add proper error handling and verify the BLE connection interface:

-                if hasattr(self.ble_connection, 'data_available') and self.ble_connection.data_available:
+                # TODO: Implement proper data availability check for BLE
+                # The current implementation references non-existent methods
+                if hasattr(self.ble_connection, 'data_available') and self.ble_connection.data_available:

321-353: Critical: Missing methods in WiFi connection class.

Similar to BLE, this handler assumes methods that don't exist in the WiFi connection class.

The WiFi connection class doesn't have data_available or get_latest_sample methods. Consider implementing these methods or using a different approach for WiFi data handling.


355-388: Critical: Assumptions about USB data structure.

The handler assumes self.usb_connection.data is a 2D array without verification, which may not align with the actual implementation.

Add proper data structure verification:

                    if hasattr(self.usb_connection, 'data'):
+                       # Verify data structure exists and has expected format
+                       if not hasattr(self.usb_connection.data, 'shape') or len(self.usb_connection.data.shape) != 2:
+                           continue
                        current_time = local_clock()
🧹 Nitpick comments (2)
chords_wifi.py (1)

8-8: LGTM - Sampling rate alignment with unified protocols.

The change from 250 to 500 Hz aligns with the unified data acquisition rate across connection protocols mentioned in the summary.

Consider refactoring the constructor to reduce the number of parameters (currently 6) by using a configuration object or builder pattern to improve maintainability.

connection.py (1)

141-167: Fix unused variable and improve error handling.

                if filtered_diffs:
                    estimated_rate = 1 / np.mean(filtered_diffs)
+                   print(f"\nEstimated LSL rate: {estimated_rate:.2f} Hz")
                else:
                    print("\nAll timestamps had zero difference.")
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 0aaf8db and f92d4e2.

📒 Files selected for processing (2)
  • chords_wifi.py (1 hunks)
  • connection.py (9 hunks)
🧰 Additional context used
🪛 Ruff (0.11.9)
connection.py

161-161: Local variable estimated_rate is assigned to but never used

Remove assignment to unused variable estimated_rate

(F841)


584-585: Use a single if statement instead of nested if statements

Combine if statements using and

(SIM102)

🪛 Pylint (3.3.7)
connection.py

[error] 175-175: Instance of 'Connection' has no 'connection' member

(E1101)


[error] 177-177: Instance of 'Connection' has no 'connection' member

(E1101)


[refactor] 223-223: Too many local variables (17/15)

(R0914)


[error] 231-231: Instance of 'Connection' has no 'connection' member

(E1101)


[error] 233-233: Instance of 'Connection' has no 'connection' member

(E1101)


[error] 294-294: Instance of 'Chords_BLE' has no 'data_available' member

(E1101)


[error] 298-298: Instance of 'Chords_BLE' has no 'get_latest_sample' member

(E1101)


[refactor] 292-319: Too many nested blocks (6/5)

(R1702)


[error] 328-328: Instance of 'Chords_WIFI' has no 'data_available' member

(E1101)


[error] 332-332: Instance of 'Chords_WIFI' has no 'get_latest_sample' member

(E1101)


[refactor] 326-353: Too many nested blocks (6/5)

(R1702)


[refactor] 360-388: Too many nested blocks (6/5)

(R1702)


[refactor] 511-511: Too many branches (15/12)

(R0912)

chords_wifi.py

[refactor] 8-8: Too many arguments (6/5)

(R0913)


[refactor] 8-8: Too many positional arguments (6/5)

(R0917)

🔇 Additional comments (7)
connection.py (7)

11-14: LGTM - Enhanced imports for threading and LSL functionality.

The new imports support the threading architecture and LSL rate monitoring functionality.


27-44: LGTM - Added attributes for threading and rate monitoring.

The new attributes properly support the threading architecture and sample rate monitoring functionality.


125-139: LGTM - Sample rate monitoring implementation.

The rate monitoring logic correctly calculates and displays average sampling rates using a sliding window approach.


422-423: LGTM - Improved channel data extraction.

The list comprehension simplifies the channel data extraction logic and makes it more readable.


489-509: LGTM - Enhanced USB connection with threading.

The updated USB connection method correctly sets up threading and LSL rate monitoring.


577-587: LGTM - Unified main function waiting logic.

The consistent waiting pattern for all protocols ensures the program stays alive during streaming.


511-563: Improved cleanup with proper thread management.

The enhanced cleanup method properly handles thread termination and connection cleanup.

Consider adding longer timeouts for thread joins to ensure graceful shutdown:

#!/bin/bash
# Verify that the timeout of 1 second is sufficient for thread cleanup
rg -A 5 -B 5 "join.*timeout.*1" --type py

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

🧹 Nitpick comments (2)
ffteeg.py (2)

3-3: Remove unused import.

The QGridLayout import is not used in the code and should be removed to keep imports clean.

-from PyQt5.QtWidgets import (QApplication, QVBoxLayout, QHBoxLayout, QMainWindow, QWidget, QGridLayout, QScrollArea, QPushButton, QDialog, QCheckBox, QLabel, QComboBox, QFrame, QSizePolicy)
+from PyQt5.QtWidgets import (QApplication, QVBoxLayout, QHBoxLayout, QMainWindow, QWidget, QScrollArea, QPushButton, QDialog, QCheckBox, QLabel, QComboBox, QFrame, QSizePolicy)

129-173: LGTM! Clear channel selection dialog.

The SettingBox provides a clean interface for channel selection with proper validation ensuring at least one EEG channel is always selected.

Consider adding user feedback when automatically selecting Channel 1:

     def validate_and_accept(self):
         # Ensure at least one EEG channel is selected
         eeg_selected = any(cb.isChecked() for cb in self.eeg_checkboxes)
         
         if not eeg_selected:
+            # Could add a message box here to inform user
             self.eeg_checkboxes[0].setChecked(True)
         
         self.accept()
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between f92d4e2 and 75e7c8d.

📒 Files selected for processing (1)
  • ffteeg.py (1 hunks)
🧰 Additional context used
🪛 Ruff (0.11.9)
ffteeg.py

3-3: PyQt5.QtWidgets.QGridLayout imported but unused

Remove unused import: PyQt5.QtWidgets.QGridLayout

(F401)


357-357: Loop control variable curve not used within loop body

(B007)

🪛 Pylint (3.3.7)
ffteeg.py

[refactor] 23-23: Too many instance attributes (11/7)

(R0902)


[refactor] 129-129: Too few public methods (1/2)

(R0903)


[refactor] 174-174: Too many instance attributes (30/7)

(R0902)

🔇 Additional comments (10)
ffteeg.py (10)

23-62: LGTM! Well-designed data processing class.

The DataProcessor class implements proper EEG signal filtering and buffering:

  • Appropriate filter design (50Hz notch + 0.5-45Hz bandpass)
  • Efficient circular buffer implementation for real-time processing
  • Per-channel independent processing

63-128: LGTM! Robust FFT analysis implementation.

The FFTAnalyzer class implements proper FFT computation with:

  • Correct Hanning windowing and amplitude scaling
  • Standard EEG frequency band definitions
  • Smoothing for stable visualization
  • Relative power calculation for brainwave analysis

The debug output limited to channel 0 is a good design choice to prevent console spam.


174-207: LGTM! Well-structured initialization.

The initialization properly:

  • Establishes LSL connection with graceful failure handling
  • Initializes processing components with correct parameters
  • Sets up default channel selections
  • Configures UI and update timer

208-231: LGTM! Robust LSL connection handling.

The method properly handles LSL stream discovery and connection with appropriate error handling and user feedback.


232-312: LGTM! Well-designed UI layout.

The horizontal split layout with scrollable EEG plots and dedicated analysis panels provides an intuitive and scalable interface for multi-channel EEG monitoring.


313-355: LGTM! Comprehensive plot initialization.

The method efficiently handles multi-channel plot creation with proper cleanup, color coding, and visibility management.


356-366: LGTM! Proper visibility management.

The method correctly updates plot visibility based on user selection. The static analysis warning about unused curve variable is a false positive - it's properly used as part of tuple unpacking.


367-378: LGTM! Efficient settings dialog handling.

The method properly manages the settings dialog with change detection to avoid unnecessary updates.


379-396: LGTM! Robust real-time data processing loop.

The main update method properly handles LSL data streaming with appropriate timeout handling and disconnection detection.


397-420: LGTM! Clean separation of plot update logic.

The individual plot update methods are well-designed with proper channel selection filtering and efficient data flow from processing components to visualization.

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 (4)
README.md (4)

3-4: Refine project introduction
Hyphenate “Upside-Down Labs”, add the missing article before it, and update the blurb to highlight the new web-based dashboard, network streaming, and multi-protocol support.


53-55: Adjust heading level and casing for Wi-Fi Connection
Change #### WIFI Connection to ### Wi-Fi Connection to correct heading hierarchy and use consistent hyphenation.


59-65: Adjust Bluetooth heading level
Change #### Bluetooth Connection to ### Bluetooth Connection for consistent section structure.


66-71: Adjust Serial heading level
Change #### Serial Connection to ### Serial Connection to maintain proper heading hierarchy.

🧹 Nitpick comments (7)
README.md (7)

11-11: Add space after colon in Data Streaming description
Insert a space after the colon (**LSL Data Streaming**: Once...) for readability.


17-17: Add missing article
Change to “Ensure the latest version of Python is installed.” for grammatical correctness.


45-45: Remove trailing punctuation from heading
Drop the colon in ### Key Options: to conform with markdown style (### Key Options).


49-49: Refine grammar and casing
Rephrase to “Multiple applications can be launched from the interface simultaneously (e.g., EEG Tug of War).”


60-60: Fix duplicate word in Bluetooth guide
Replace “ON on” with “on” (e.g., “Ensure Bluetooth is turned on.”) or rephrase to “Ensure that Bluetooth is turned on.”


80-82: Surround table with blank lines
Add an empty line before and after the applications table to satisfy markdown rules (MD058).


87-88: Hyphenate “based” in game descriptions
Update to “2-Player EEG-Based Game” and “Real-time EEG focus-based game.” for correct hyphenation.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 75e7c8d and 8a0fbd6.

📒 Files selected for processing (1)
  • README.md (1 hunks)
🧰 Additional context used
🪛 LanguageTool
README.md

[uncategorized] ~3-~3: The adjective “Upside-Down” is spelled with a hyphen.
Context: ...dedownlabs/Chords-Arduino-Firmware).Use Upside Down Labs bio-potential amplifiers to read d...

(UPSIDE_DOWN)


[uncategorized] ~3-~3: You might be missing the article “the” here.
Context: ...rd data in CSV Files, and stream it via Lab Streaming Layer. > [!NOTE] > **Fir...

(AI_EN_LECTOR_MISSING_DETERMINER_THE)


[uncategorized] ~17-~17: You might be missing the article “the” here.
Context: ...## Installation 1. Python: Ensure Latest version of Python is installed. 2. **...

(AI_EN_LECTOR_MISSING_DETERMINER_THE)


[duplication] ~60-~60: Possible typo: you repeated a word.
Context: ...ction 1. Ensure Bluetooth is turned ON on your system. 2. Upload the Bluetoot...

(ENGLISH_WORD_REPEAT_RULE)


[uncategorized] ~87-~87: This expression is usually spelled with a hyphen.
Context: ...| EEG Tug of War Game | 2 Player EEG Based Game ...

(BASED_HYPHEN)


[uncategorized] ~88-~88: This expression is usually spelled with a hyphen.
Context: ...EG Beetle game** | Real-time EEG focus based game. ...

(BASED_HYPHEN)

🪛 markdownlint-cli2 (0.17.2)
README.md

45-45: Trailing punctuation in heading
Punctuation: ':'

(MD026, no-trailing-punctuation)


53-53: Heading levels should only increment by one level at a time
Expected: h3; Actual: h4

(MD001, heading-increment)


81-81: Tables should be surrounded by blank lines
null

(MD058, blanks-around-tables)

@lorforlinux
Copy link
Member

@PayalLakra Thank you so much for your contributions. I am merging this now.

@lorforlinux lorforlinux merged commit c18ad73 into upsidedownlabs:main Jun 20, 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