Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions FLASHER_GUIDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Bruce Flasher Guide

This guide explains how to use the **Bruce Flasher** tool, a simple way to build and flash Bruce Firmware to your ESP32 device.

## Prerequisites

1. **Python 3.7+**: Ensure Python is installed and added to your system PATH.
2. **USB Drivers**: Make sure you have the correct USB drivers for your device (CH34x, CP210x, etc.).
3. **Dependencies**: Install the required Python packages:
```bash
pip install -r requirements.txt
```

## How to Use

1. **Open a Terminal**: Navigate to the `firmware` folder where the script is located.

```bash
cd firmware
```

2. **Run the Flasher**:

```bash
python bruce_flasher.py
```

3. **Select Your Device**:

- You will see a list of supported devices (M5Stack, Lilygo, CYD, etc.).
- Type the **number** corresponding to your device and press `Enter`.

4. **Select Action**:

- Choose **1. Build & Flash** to compile the code and upload it to your device.
- _Note: The first run may take a few minutes as it downloads necessary frameworks._

5. **Select Port** (if prompted):
- Select your device's COM port from the list, or choose **0** for auto-detection.

## Troubleshooting

- **"Device not in bootloader mode"**:
- Hold the **BOOT** button (sometimes labeled as '0') on your device.
- Press the **RESET** button once.
- Release the **BOOT** button.
- Try flashing again.
- **"MissingPackageManifestError"**:
- Run the tool again and select option **4. Clean / Fix** (if available) or simply retry "Build & Flash", as the tool now includes auto-cleanup steps.
- **Black Screen after flashing**:
- Press the **RESET** button on your device.

---

_Happy Hacking!_
Binary file added __pycache__/bruce_flasher.cpython-311.pyc
Binary file not shown.
12 changes: 12 additions & 0 deletions build.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from pathlib import Path
import csv
import shutil
from SCons.Script import Import

# Import PlatformIO's SCons environment
Expand Down Expand Up @@ -120,6 +121,17 @@ def q(p): return f"\"{p}\""
except FileNotFoundError:
size = 0
print(f"[merge_bin] Success -> {out_bin} ({size} bytes)")

# Copy to build_output directory
try:
target_dir = proj_dir / "build_output" / pioenv
target_dir.mkdir(parents=True, exist_ok=True)
target_file = target_dir / out_bin.name
print(f"[merge_bin] Copying to {target_file}")
shutil.copy2(out_bin, target_file)
except Exception as e:
print(f"[merge_bin] Warning: Failed to copy output: {e}")

if ota0_offset:
if size < (ota0_offset + ota_size):
print("[Final bin] Valid bin to upload")
Expand Down
2 changes: 2 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
platformio>=6.0.0
requests
1 change: 1 addition & 0 deletions src/core/menu_items/BleMenu.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ void BleMenu::optionsMenu() {
}

options.push_back({"Media Cmds", [=]() { MediaCommands(hid_ble, true); }});
options.push_back({"Presenter", [=]() { PresenterMode(hid_ble, true); }});
#if !defined(LITE_VERSION)
options.push_back({"BLE Scan", ble_scan});
options.push_back({"iBeacon", [=]() { ibeacon(); }});
Expand Down
183 changes: 183 additions & 0 deletions src/modules/badusb_ble/ducky_typer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -590,3 +590,186 @@ void MediaCommands(HIDInterface *hid, bool ble) {
}
returnToMenu = true;
}

// Presenter mode - simple button press to advance slides
void PresenterMode(HIDInterface *&hid, bool ble) {
if (_Ask_for_restart == 2) {
displayError("Restart your Device");
delay(1000);
return;
}

ducky_startKb(hid, ble);

displayTextLine("Pairing...");

while (!hid->isConnected() && !check(EscPress));

if (!hid->isConnected()) {
displayWarning("Canceled", true);
returnToMenu = true;
return;
}

BLEConnected = true;

// Initialize presenter state
int currentSlide = 1;
int lastDisplayedSlide = 0;
unsigned long startTime = 0; // Will be set on first interaction
unsigned long lastDisplayedSeconds = 0;
bool timerStarted = false;
bool firstDraw = true;

// Helper function to draw static UI elements (only once)
auto drawStaticUI = [&]() {
tft.fillScreen(bruceConfig.bgColor);

// Draw title "PRESENTER" at the top
tft.setTextSize(FM);
tft.setTextColor(bruceConfig.priColor, bruceConfig.bgColor);
tft.drawCentreString("PRESENTER", tftWidth / 2, 10, 1);

// Draw a separator line
tft.drawFastHLine(10, 35, tftWidth - 20, bruceConfig.priColor);

// Draw time label
tft.setTextSize(FM);
tft.setTextColor(bruceConfig.priColor, bruceConfig.bgColor);
tft.drawCentreString("Time", tftWidth / 2, tftHeight / 2 + 15, 1);

// Draw controls hint at bottom
tft.setTextSize(1);
tft.setTextColor(bruceConfig.priColor, bruceConfig.bgColor);
tft.drawCentreString("<< PREV | SEL | NEXT >>", tftWidth / 2, tftHeight - 15, 1);
};

// Helper function to update slide number (only when changed)
auto updateSlideDisplay = [&]() {
// Clear previous slide area
tft.fillRect(0, tftHeight / 2 - 35, tftWidth, 40, bruceConfig.bgColor);

// Draw current slide number - large and centered
tft.setTextSize(4);
tft.setTextColor(TFT_WHITE, bruceConfig.bgColor);
String slideStr = "Slide " + String(currentSlide);
tft.drawCentreString(slideStr, tftWidth / 2, tftHeight / 2 - 30, 1);
lastDisplayedSlide = currentSlide;
};

// Helper function to update timer (only when seconds change)
auto updateTimerDisplay = [&]() {
unsigned long elapsed = 0;
if (timerStarted) { elapsed = (millis() - startTime) / 1000; }

int hours = elapsed / 3600;
int minutes = (elapsed % 3600) / 60;
int seconds = elapsed % 60;

// Format time string
char timeBuffer[16];
if (hours > 0) {
snprintf(timeBuffer, sizeof(timeBuffer), "%d:%02d:%02d", hours, minutes, seconds);
} else {
snprintf(timeBuffer, sizeof(timeBuffer), "%02d:%02d", minutes, seconds);
}

// Clear timer area and redraw
tft.fillRect(0, tftHeight / 2 + 30, tftWidth, 30, bruceConfig.bgColor);
tft.setTextSize(3);
tft.setTextColor(timerStarted ? TFT_GREEN : TFT_DARKGREY, bruceConfig.bgColor);
tft.drawCentreString(timeBuffer, tftWidth / 2, tftHeight / 2 + 35, 1);

lastDisplayedSeconds = elapsed;
};

// Initial UI draw
drawStaticUI();
updateSlideDisplay();
updateTimerDisplay();

while (1) {
bool slideChanged = false;

// Middle button = Next slide (Right Arrow for presentations)
if (check(SelPress)) {
delay(50); // Allow system to stabilize after check()
// First press only starts timer, doesn't send key
if (!timerStarted) {
startTime = millis();
timerStarted = true;
updateTimerDisplay();
// Prime the HID connection with an empty report
hid->releaseAll();
delay(50);
} else {
hid->press(KEY_RIGHT_ARROW);
delay(80);
hid->releaseAll();
currentSlide++;
slideChanged = true;
}
delay(150); // debounce
}
// Wheel right = Next slide (Right arrow)
else if (check(NextPress)) {
delay(50); // Allow system to stabilize after check()
// First press only starts timer, doesn't send key
if (!timerStarted) {
startTime = millis();
timerStarted = true;
updateTimerDisplay();
// Prime the HID connection with an empty report
hid->releaseAll();
delay(50);
} else {
hid->press(KEY_RIGHT_ARROW);
delay(80);
hid->releaseAll();
currentSlide++;
slideChanged = true;
}
delay(150); // debounce
}
// Wheel left = Previous slide (Left arrow)
else if (check(PrevPress)) {
delay(50); // Allow system to stabilize after check()
// First press only starts timer, doesn't send key
if (!timerStarted) {
startTime = millis();
timerStarted = true;
updateTimerDisplay();
// Prime the HID connection with an empty report
hid->releaseAll();
delay(50);
} else {
hid->press(KEY_LEFT_ARROW);
delay(80);
hid->releaseAll();
if (currentSlide > 1) currentSlide--;
slideChanged = true;
}
delay(150); // debounce
}

// Update slide display only if changed
if (slideChanged) {
updateSlideDisplay();
updateTimerDisplay();
}

// Update timer display every second (only if timer is running)
if (timerStarted) {
unsigned long currentSeconds = (millis() - startTime) / 1000;
if (currentSeconds != lastDisplayedSeconds) { updateTimerDisplay(); }
}

// Escape to exit
if (check(EscPress)) break;

delay(10);
}

hid->releaseAll();
returnToMenu = true;
}
3 changes: 3 additions & 0 deletions src/modules/badusb_ble/ducky_typer.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,7 @@ void ducky_keyboard(HIDInterface *&hid, bool ble = false);
// Send media commands through BLE or USB HID
void MediaCommands(HIDInterface *hid, bool ble = false);

// Presenter mode - press button to advance slides
void PresenterMode(HIDInterface *&hid, bool ble = true);

#endif