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
20 changes: 20 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
- [x] Verify that the copilot-instructions.md file in the .github directory is created.

- [x] Clarify Project Requirements

- [x] Scaffold the Project

- [x] Customize the Project

- [x] Install Required Extensions

- [x] Compile the Project

- [x] Create and Run Task

- [x] Launch the Project

- [x] Ensure Documentation is Complete
- Work through each checklist item systematically.
- Keep communication concise and focused.
- Follow development best practices.
41 changes: 40 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# :shark: Bruce

Bruce is a versatile ESP32 firmware that supports a ton of offensive features focusing on facilitating Red Team operations.
It also supports M5stack and Lilygo products and works great with Cardputer, Sticks, M5Cores, T-Decks and T-Embeds.
It also supports M5stack and Lilygo products and works great with Cardputer, Sticks, M5Cores, CoreInk, T-Decks and T-Embeds.

**Check our fully open-source hardware too:** https://bruce.computer/boards

Expand Down Expand Up @@ -221,6 +221,7 @@ Also, [read our FAQ](https://github.com/pr3y/Bruce/wiki/FAQ)
| [M5Stack M5Core BASIC](https://shop.m5stack.com/products/basic-core-iot-development-kit) | :ok: | :ok: | :ok: | :ok: | :ok: | :ok:¹ | :x: | Tone | :x: | :x: |
| [M5Stack M5Core2](https://shop.m5stack.com/products/m5stack-core2-esp32-iot-development-kit-v1-1) | :ok: | :ok: | :ok: | :ok: | :ok: | :ok:¹ | :x: | :x: | :x: | :x: |
| [M5Stack M5CoreS3](https://shop.m5stack.com/products/m5stack-cores3-esp32s3-lotdevelopment-kit)/[SE](https://shop.m5stack.com/products/m5stack-cores3-se-iot-controller-w-o-battery-bottom) | :ok: | :ok: | :ok: | :ok: | :x: | :ok: | :x: | :x: | :x: | :x: |
| [M5Stack CoreInk](https://shop.m5stack.com/products/m5stack-esp32-core-ink-development-kit1-54-elnk-display) | :ok: | :ok: | :ok: | :ok: | :x: | :ok:¹ | :x: | :x: | :ok: | :x: |
| [JCZN CYD‑2432S028](https://www.aliexpress.us/item/3256804774970998.html) | :ok: | :ok: | :ok: | :ok: | :x: | :ok:¹ | :x: | :x: | :x: | :x:² |
| [Lilygo T‑Embed CC1101](https://lilygo.cc/products/t-embed-cc1101) | :ok: | :ok: | :ok: | :ok: | :ok: | :ok: | :ok: | :ok: | :ok: | :x: |
| [Lilygo T‑Embed](https://lilygo.cc/products/t-embed) | :ok: | :ok: | :ok: | :ok: | :ok: | :ok: | :ok: | :ok: | :x: | :x: |
Expand All @@ -238,6 +239,44 @@ Also, [read our FAQ](https://github.com/pr3y/Bruce/wiki/FAQ)
*LITE_VERSION*: TelNet, SSH, WireGuard, ScanHosts, RawSniffer, Brucegotchi, BLEBacon, BLEScan and Interpreter are NOT available for M5Launcher Compatibility


## :desktop_computer: E-Ink Display Optimizations (M5Stack CoreInk)

The M5Stack CoreInk features a 200x200 pixel e-ink display that requires special optimizations for an optimal user experience. This fork includes several key enhancements specifically designed for e-ink displays:

### Display & Power Management
- **No Backlight Control**: E-ink displays don't have backlights, so brightness controls are disabled
- **Screen Timeout Disabled**: Screen dimming and auto-off features are bypassed as they don't apply to e-ink
- **Optimized Sleep Mode**: Power saving mode is adapted to work without display backlight control
- **LED Feedback**: Input button presses trigger brief LED pulses to provide immediate feedback (since display updates are slower)

### Refresh Rate Control
- **Configurable Refresh Intervals**: Choose from manual, 15s, 30s, 60s, or 5-minute automatic refresh rates
- Access via: Config → Display & UI → Refresh
- Manual mode (0ms): Display only updates when explicitly flushed by the application
- Timed modes: Display automatically flushes at specified intervals to reduce ghosting
- **Intelligent Flush System**: Menu navigation uses a 300ms minimum flush interval to balance responsiveness with display longevity

### Visual Optimizations
- **Black & White Theme**: Colors are automatically forced to pure black (0x0000) on white (0xFFFF) background for maximum contrast
- **Simplified Time Display**: Clock shows HH:MM format instead of HH:MM:SS to reduce unnecessary refreshes
- **Disabled Scrolling Text**: Text scrolling animations are disabled as they consume excessive refresh cycles
- **Inverted Color Default**: Color inversion is disabled by default (compared to stock Bruce) for natural e-ink appearance

### Input Handling
- **Rocker Navigation**: Uses the 3-button rocker switch (left/right for navigation, center for select)
- Short press center button: Select
- Long press center button (800ms+): Back/Escape
- Configurable inversion via Config → Display & UI → Rocker Invert

### Hardware Support
- **Battery Monitoring**: Full battery level reporting and charging status detection
- **I2C Grove Connector**: Pin 21 (SDA) and Pin 22 (SCL) for external modules
- **BadUSB Support**: Via Grove connector pins
- **No Onboard SD Card**: External SD card adapters can be connected via I2C or SPI

These optimizations ensure Bruce runs efficiently on the CoreInk while preserving battery life and extending the lifespan of the e-ink display.


## :sparkles: Why and how does it look?

Bruce stems from a keen observation within the community focused on devices like Flipper Zero. While these devices offered a glimpse into the world of offensive security, there was a palpable sense that something more could be achieved without being that overpriced, particularly with the robust and modular hardware ecosystem provided by ESP32 Devices, Lilygo and M5Stack products.
Expand Down
38 changes: 38 additions & 0 deletions boards/_boards_json/m5stack-coreink.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"build": {
"arduino": {
"ldscript": "esp32_out.ld",
"partitions": "default_4MB.csv"
},
"core": "esp32",
"extra_flags": [
"-DARDUINO_M5STACK_COREINK",
"-DM5STACK"
],
"f_cpu": "240000000L",
"f_flash": "40000000L",
"flash_mode": "dio",
"mcu": "esp32",
"variant": "pinouts"
},
"connectivity": [
"wifi",
"bluetooth",
"ethernet",
"can"
],
"frameworks": [
"arduino",
"espidf"
],
"name": "M5Stack CoreInk",
"upload": {
"flash_size": "4MB",
"maximum_ram_size": 327680,
"maximum_size": 4194304,
"require_upload_port": true,
"speed": 460800
},
"url": "http://www.m5stack.com",
"vendor": "M5Stack"
}
119 changes: 119 additions & 0 deletions boards/m5stack-coreink/interface.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
#include "core/powerSave.h"
#include <M5Unified.h>
#include <interface.h>

namespace {
constexpr uint8_t ROCKER_LEFT_PIN = 39;
constexpr uint8_t ROCKER_CENTER_PIN = 38;
constexpr uint8_t ROCKER_RIGHT_PIN = 37;
constexpr bool ROCKER_ACTIVE_LOW = true;

bool readRockerPin(uint8_t pin) {
int level = digitalRead(pin);
return ROCKER_ACTIVE_LOW ? (level == LOW) : (level == HIGH);
}
} // namespace

/***************************************************************************************
** Function name: _setup_gpio()
** Location: main.cpp
** Description: initial setup for the device
***************************************************************************************/
void _setup_gpio() {
M5.begin();
pinMode(ROCKER_LEFT_PIN, INPUT);
pinMode(ROCKER_CENTER_PIN, INPUT);
pinMode(ROCKER_RIGHT_PIN, INPUT);
}

/***************************************************************************************
** Function name: getBattery()
** location: display.cpp
** Description: Delivers the battery value from 1-100
***************************************************************************************/
int getBattery() {
int percent = M5.Power.getBatteryLevel();
return (percent < 0) ? 1 : (percent >= 100) ? 100 : percent;
}

/*********************************************************************
** Function: setBrightness
** location: settings.cpp
** set brightness value
**********************************************************************/
void _setBrightness(uint8_t brightval) { (void)brightval; }

/*********************************************************************
** Function: InputHandler
** Handles the variables PrevPress, NextPress, SelPress, AnyKeyPress and EscPress
**********************************************************************/
void InputHandler(void) {
static unsigned long tm = 0;
if (millis() - tm < 200 && !LongPress) return;

M5.update();

// Rocker button: left/right = next/prev (inverted for intuitive nav), center = select/back
static bool lastLeft = false;
static bool lastRight = false;
static bool lastCenter = false;
static unsigned long centerPressTime = 0;

bool leftNow = readRockerPin(ROCKER_LEFT_PIN);
bool rightNow = readRockerPin(ROCKER_RIGHT_PIN);
bool centerNow = readRockerPin(ROCKER_CENTER_PIN);

bool leftPressed = leftNow && !lastLeft;
bool rightPressed = rightNow && !lastRight;
bool centerPressed = centerNow && !lastCenter;

// Track center button hold time for long press detection
if (centerNow && !lastCenter) { centerPressTime = millis(); }
bool centerLongPress = centerNow && (millis() - centerPressTime > 800);

lastLeft = leftNow;
lastRight = rightNow;
lastCenter = centerNow;

if (centerPressed) {
leftPressed = false;
rightPressed = false;
}

bool any = leftPressed || rightPressed || centerPressed;
if (any) tm = millis();
if (any && wakeUpScreen()) return;

AnyKeyPress = any;
UpPress = false;
DownPress = false;
SelPress = centerPressed && !centerLongPress; // Select on short press
NextPress = leftPressed; // Inverted: left = next (right direction)
PrevPress = rightPressed; // Inverted: right = prev (left direction)
EscPress = centerLongPress; // Back on long press (800ms+)
LongPress = false;
}

/*********************************************************************
** Function: powerOff
** location: mykeyboard.cpp
** Turns off the device (or try to)
**********************************************************************/
void powerOff() { M5.Power.powerOff(); }
void goToDeepSleep() { M5.Power.deepSleep(); }

/*********************************************************************
** Function: checkReboot
** location: mykeyboard.cpp
** Btn logic to turn off the device (name is odd btw)
**********************************************************************/
void checkReboot() {}

/***************************************************************************************
** Function name: isCharging()
** Description: Determines if the device is charging
***************************************************************************************/
bool isCharging() {
if (M5.Power.getBatteryCurrent() > 0 || M5.Power.getBatteryCurrent()) return true;
else return false;
}
45 changes: 45 additions & 0 deletions boards/m5stack-coreink/m5stack-coreink.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
; PlatformIO Project Configuration File
;
; Build options: build flags, source filter
; Upload options: custom upload port, speed and extra flags
; Library options: dependencies, extra library storages
; Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html

[env:m5stack-coreink]
board = m5stack-coreink
monitor_speed = 115200
board_build.partitions = custom_4Mb_full.csv
build_src_filter =${env.build_src_filter} +<../boards/m5stack-coreink>
build_flags =
${env.build_flags}
-Iboards/m5stack-coreink
-DCORE_DEBUG_LEVEL=0
-DUSE_M5GFX=1
-DHAS_SCREEN=1
-DHAS_EINK=1
-DROTATION=1
-DFP=1
-DFM=2
-DFG=3
-DTFT_WIDTH=200
-DTFT_HEIGHT=200
-DDEVICE_NAME='"M5Stack CoreInk"'
-DBAD_TX=GROVE_SDA
-DBAD_RX=GROVE_SCL

; Default I2C port
-DGROVE_SDA=21
-DGROVE_SCL=22

; SD Card pins (CoreInk has no onboard SD)
-DSDCARD_CS=-1
-DSDCARD_SCK=-1
-DSDCARD_MISO=-1
-DSDCARD_MOSI=-1

lib_deps =
${env.lib_deps}
m5stack/M5Unified @ ^0.2.11
55 changes: 55 additions & 0 deletions boards/m5stack-coreink/pins_arduino.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#ifndef Pins_Arduino_h
#define Pins_Arduino_h

#include <stdint.h>

static const uint8_t TX = 1;
static const uint8_t RX = 3;

static const uint8_t SDA = 21;
static const uint8_t SCL = 22;

static const uint8_t SS = 5;
static const uint8_t MOSI = 23;
static const uint8_t MISO = 19;
static const uint8_t SCK = 18;

static const uint8_t G0 = 0;
static const uint8_t G1 = 1;
static const uint8_t G2 = 2;
static const uint8_t G3 = 3;
static const uint8_t G4 = 4;
static const uint8_t G5 = 5;
static const uint8_t G12 = 12;
static const uint8_t G13 = 13;
static const uint8_t G14 = 14;
static const uint8_t G15 = 15;
static const uint8_t G16 = 16;
static const uint8_t G17 = 17;
static const uint8_t G18 = 18;
static const uint8_t G19 = 19;
static const uint8_t G21 = 21;
static const uint8_t G22 = 22;
static const uint8_t G23 = 23;
static const uint8_t G25 = 25;
static const uint8_t G26 = 26;
static const uint8_t G27 = 27;
static const uint8_t G32 = 32;
static const uint8_t G33 = 33;
static const uint8_t G34 = 34;
static const uint8_t G35 = 35;
static const uint8_t G36 = 36;
static const uint8_t G39 = 39;

static const uint8_t DAC1 = 25;
static const uint8_t DAC2 = 26;

static const uint8_t ADC1 = 35;
static const uint8_t ADC2 = 36;

#define SPI_SCK_PIN SCK
#define SPI_MOSI_PIN MOSI
#define SPI_MISO_PIN MISO
#define SPI_SS_PIN SS

#endif /* Pins_Arduino_h */
Loading