Skip to content

Testing the "Device hosts name" feature on the latest beta version of Bruce - #6

Merged
hackspb0 merged 29 commits into
WiFi-New-featurefrom
Test
Aug 28, 2026
Merged

Testing the "Device hosts name" feature on the latest beta version of Bruce #6
hackspb0 merged 29 commits into
WiFi-New-featurefrom
Test

Conversation

@hackspb0

Copy link
Copy Markdown
Owner

Testing

loratrak and others added 29 commits August 2, 2026 18:31
Turn the ble_api module into a standard Nordic UART Service (NUS) so any
BLE central (nRF Connect, a companion app, ...) can drive Bruce's serial
command parser over BLE.

Changes:
- BLESerialService: standard NUS with separate RX (write) and TX (notify)
  characteristics instead of a single custom characteristic; RX bytes are
  buffered under a mutex and delivered to the parser as complete lines; TX
  output is chunked to the negotiated MTU; fixed vprintf (was passing the
  va_list straight to sprintf).
- ble_api: advertise as "Bruce" with a scan response so the 128-bit NUS
  UUID stays discoverable alongside the battery service.
- settings: show "BLE API ON/OFF" feedback when toggling, so it is clear
  the service actually started.

How to use the BLE API:
1. On the device, enable it in Config > Advanced > "Toggle BLE API"
   (off by default). It then advertises as "Bruce".
2. Connect and talk to the NUS service:
   - Service:      6E400001-B5A3-F393-E0A9-E50E24DCCA9E
   - RX  (write)   6E400002-...: write a command line, e.g. "info\n".
                    Every command must end with a newline.
   - TX  (notify)  6E400003-...: subscribe to receive the parser output.
   - Battery:      standard 0x180F / 0x2A19 is also exposed.
3. Any command accepted by the USB serial CLI works over BLE
   (info, free, uptime, ir, subghz, storage, ...).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The NUS bridge felt slow not because GATT is slow but because of how the
bridge emitted data and how the link was set up. This reworks both without
changing the RX contract (newline-terminated command lines) or the
SerialDevice interface seen by the command handlers.

Coalesced TX notifications:
- print/println/printf/write used to emit one notification per call and
  sleep a fixed 5 ms after every chunk. A command that prints 40 lines
  therefore cost 40 notifications, each waiting for its own connection
  event. Output is now appended to a mutex-guarded buffer and flushed as
  full MTU-3 byte notifications; a trailing partial chunk is flushed by
  flush() or automatically ~12 ms later, piggybacking on the poll the
  command task already does in available(). flush() is no longer a no-op.
- Dropped the fixed 5 ms per-chunk delay. Backpressure now comes from
  bleNotifyRetry() returning false when the controller queues are full,
  which is the real signal; the fixed sleep was pure latency.

Don't stall the USB serial when nobody is listening:
- With no central subscribed to the TX characteristic every notify()
  failed and burned the whole retry budget, so enabling the BLE API slowed
  down ALL serial output even with no app connected. An onSubscribe
  callback on the TX characteristic now tracks subscription state and
  queueTx() simply drops output when there is no subscriber.

Link setup:
- Request ATT MTU 517 in setup(); previously we advertised the NimBLE
  default and capped every notification far below what the peer allowed.
  The central still picks the final value (iOS settles around 185).
- Connection params changed from 6..24 to 12..24 (15..30 ms). iOS rejects
  the entire request when the minimum interval is under 15 ms, which left
  us on whatever interval iOS chose on its own.
- Prefer LE 2M PHY (roughly 2x raw throughput) on chips that have it,
  guarded out for the classic ESP32 (BLE 4.2, no 2M radio). The peer keeps
  1M if it does not support 2M.
- Clear the TX buffer and reset MTU/subscription on disconnect.

Command loop:
- serialcmds polls one command per pass; a burst queued over BLE used to
  pay the full 10 ms idle tick between each one. Tick drops to 1 ms while
  input is still buffered, and still always yields.

To benefit, a central must subscribe to the TX characteristic (the
firmware now only sends to subscribers) and should write to RX with
write-without-response.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The `led` command was listed in `help` but never registered with the CLI,
so typing it in the terminal returned "Command not found". Its help entry
also described it as changing the UI main color, which is what the existing
`screen color` command does.

Register a real `led` composite command that drives the physical RGB LED:

  led r|g|b <0-255>       change a single channel, keeping the other two
  led rgb <r> <g> <b>     set the full color
  led hex <RRGGBB>        set the full color
  led brightness <0-100>  aliases: br, bright
  led effect <0-9>        0 selects the solid color
  led off

The callbacks write through bruceConfig and call ledSetup() instead of
touching leds[] directly. When an effect is active its task reads the color,
effect, speed and direction straight from bruceConfig, so a direct write
would be overwritten on the next frame. As a side effect the change persists
across reboots, matching the behaviour of the LED settings menu.

The whole translation unit is guarded by HAS_RGB_LED: led_control.h only
declares its API under that define, and PlatformIO compiles every source
file regardless of the target board.

Also fix the help text. The UI section now documents the real `screen`
commands, and a new "RGB LED Commands" section is printed only on boards
that actually have an RGB LED.

Built for m5stack-cardputer (HAS_RGB_LED) and Marauder-Mini (without it).
Add Nordic UART Service BLE bridge for serial commands
…mmand

Add led serial command for RGB LED control
fix(rf): route KeeLoq sub files to loopEmulate
)

Scan/copy showed a single signal at a time: each new capture overwrote the
previous one on screen, and [NEXT] opened one global menu that acted on
whatever happened to be the last decode. Capturing a second remote meant
losing the first.

Captures now accumulate in an on-screen list (up to 10 slots) that can be
scrolled with PREV/NEXT (or UP/DOWN); SEL opens a per-signal menu with
Signal Info, Replay, Replay as RAW, Save, Save as RAW and Delete. The global
settings (Range, Threshold, Mode, Filter, auto-save, Clear List, Main Menu)
moved to a Scan Options menu, reachable from that menu or directly with SEL
while the list is empty.

Also:

- Deduplicate captures by protocol/key/preset/bits, so holding a button no
  longer fills the list with copies of the same frame. KeeLoq is unaffected
  since its key changes on every press.
- Replay/Save act on the selected entry instead of a single shared buffer,
  and keeloq_step(1) advances the counter of that entry.
- Screen-only actions (Info, Delete, Clear, closing a menu) no longer pay the
  deinitRfModule() + 1s settle; they just re-arm the receiver.
- Wait for a genuinely new key press in the Signal Info view. Some input
  drivers (the Cardputer ADV's TCA8418 among them) raise AnyKeyPress on key
  release too, so the leftover release event from the menu selection
  dismissed the screen the instant it was drawn.
- Replace the setup()/loop() mutual recursion and the options-menu recursion
  with plain loops.

Co-authored-by: Vinicius <discoestudio5@gmail.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…2749)

The main menu only offered the carousel, where reaching a module means
stepping sideways through every other one. This adds a second layout that
lays all modules out as selectable cells, so any of them is one or two
presses away.

The choice lives in Config > Display & UI > Menu Layout and is stored in
bruce.conf as mainMenuStyle. Carousel stays the default, so existing
setups are untouched.

The grid sizes itself from the screen: columns from the available width,
visible rows from the height, scrolling by rows with a thin indicator when
they don't all fit at a readable size. On a Cardputer that lands on 4
columns and 2 visible rows of 4; on a 320x240 board all 15 modules fit at
once. Up/Down jump a whole row while Prev/Next keep stepping one cell at a
time, and boards without separate Up/Down keys fall back to linear
stepping.

Rendering reuses each module's existing drawIcon() through a new
drawIconInBox() helper, which remaps the icon coordinates and theme colors
onto an arbitrary box and restores them afterwards. Only the two cells that
changed are repainted on navigation.

Files' icon had a hardcoded 10px offset that did not follow the scale and
overflowed its box when drawn small; it now scales, with identical output
at scale 1.

Co-authored-by: Vinicius <discoestudio5@gmail.com>
#### Proposed Changes ####

A module that needs a dimmed or highlighted shade of the active theme has no
way to ask for one. The only helper available, getColorVariation, steps
brightness up or down and cannot mix toward another colour, so modules fall
back to a fixed constant such as TFT_DARKGREY or TFT_WHITE. That is the root
cause of most of the screens in Bruce that stop following the user's theme as
soon as they need more than the primary colour.

This adds two helpers next to it in core/display:

  blendColors(a, b, t)      linear RGB565 mix; t = 0 keeps a, t = 255 keeps b
  buildHeatPalette(lut, n)  fills a lookup table with a theme ramp running
                            from the background up to a brightened primary,
                            for waterfalls and other intensity plots

Both are pure additions. No existing function is modified, and nothing calls
them in this PR - the modules that consume them follow separately.

#### Types of Changes ####

New Feature (internal helper API). No behaviour change and no breaking change.

#### Verification ####

Nothing on screen changes from this PR alone, so verification is limited to
confirming the build is clean and that only insertions were made:

    pio run -e m5stack-cardputer
    git show --stat   # 2 files changed, 31 insertions(+), 0 deletions(-)

#### Testing ####

Bruce has no unit test harness covering the display layer, so this was
verified by compiling for m5stack-cardputer on this branch. The helpers get
exercised on hardware through the follow-up PRs that use them.

#### Linked Issues ####

None. This is the base commit of a series that standardises the
spectrum-style screens; the module PRs listed below depend on it.

#### User-Facing Change ####
```release-note
NONE
```

#### Further Comments ####

Prerequisite for ui/spectrum-view, ui/rf-waterfall-theme and
ui/rf-spectrum-theme.

Co-authored-by: Vinicius <discoestudio5@gmail.com>
…eDevices#2756)

* Add theme-aware colour helpers to the display core

#### Proposed Changes ####

A module that needs a dimmed or highlighted shade of the active theme has no
way to ask for one. The only helper available, getColorVariation, steps
brightness up or down and cannot mix toward another colour, so modules fall
back to a fixed constant such as TFT_DARKGREY or TFT_WHITE. That is the root
cause of most of the screens in Bruce that stop following the user's theme as
soon as they need more than the primary colour.

This adds two helpers next to it in core/display:

  blendColors(a, b, t)      linear RGB565 mix; t = 0 keeps a, t = 255 keeps b
  buildHeatPalette(lut, n)  fills a lookup table with a theme ramp running
                            from the background up to a brightened primary,
                            for waterfalls and other intensity plots

Both are pure additions. No existing function is modified, and nothing calls
them in this PR - the modules that consume them follow separately.

#### Types of Changes ####

New Feature (internal helper API). No behaviour change and no breaking change.

#### Verification ####

Nothing on screen changes from this PR alone, so verification is limited to
confirming the build is clean and that only insertions were made:

    pio run -e m5stack-cardputer
    git show --stat   # 2 files changed, 31 insertions(+), 0 deletions(-)

#### Testing ####

Bruce has no unit test harness covering the display layer, so this was
verified by compiling for m5stack-cardputer on this branch. The helpers get
exercised on hardware through the follow-up PRs that use them.

#### Linked Issues ####

None. This is the base commit of a series that standardises the
spectrum-style screens; the module PRs listed below depend on it.

#### User-Facing Change ####
```release-note
NONE
```

#### Further Comments ####

Prerequisite for ui/spectrum-view, ui/rf-waterfall-theme and
ui/rf-spectrum-theme.

* Add a shared spectrum-analyzer plot component

#### Proposed Changes ####

Every "signal strength across a band" screen in Bruce draws its own bars,
picks its own colours and hardcodes its own geometry. No two look alike, and
none of them scale past the panel the original author happened to own.

This adds SpectrumPlot (core/spectrum_plot), which owns the whole
presentation for that shape of data:

  - layout derived from tftWidth/tftHeight, degrading on short screens by
    dropping the ruler first, then the status line, then the waterfall
  - filled trace with a peak-hold line, an animated noise floor and a dashed
    reference grid visible only through the empty sky
  - scrolling waterfall backed by a ring of rendered rows
  - labelled ruler and a status line
  - every colour derived from the active theme through blendColors

Callers keep their own radio and fill an envelope of width() values in 0-100;
the component owns every pixel. Each pixel of the band is written exactly once
per frame, so the animation is flicker free without needing a full-screen
sprite.

WifiSpectrumView (modules/wifi/wifi_spectrum) sits on top and maps the 2.4GHz
channels onto a real frequency axis as overlapping spectral lobes, so the
22MHz overlap between neighbours becomes visible and a sweep reads as one
continuous trace. It also eases the drawn levels toward the measured ones, so
the trace glides rather than snapping channel by channel.

Nothing uses either component in this PR; the modules move over separately.

#### Types of Changes ####

New Feature (internal UI component). No behaviour change and no breaking
change on its own.

#### Verification ####

Nothing on screen changes from this PR alone. The component becomes visible
through ui/channel-analyzer-spectrum, ui/jam-detect-spectrum and
ui/nrf-spectrum, which is where the rendering should be judged.

    pio run -e m5stack-cardputer
    git show --stat   # 4 new files, 445 insertions(+), 0 deletions(-)

#### Testing ####

No unit test harness exists for this layer. Both files were compiled for
m5stack-cardputer as part of ui/channel-analyzer-spectrum, which stacks
directly on this branch and builds clean; this branch was not built in
isolation. Rendering has not yet been checked on hardware by me - a reviewer
with a device should confirm the layout on a non-240x135 panel in particular,
since the degradation path for short screens is the least exercised part.

#### Linked Issues ####

None. Depends on ui/color-helpers for blendColors and buildHeatPalette.

#### User-Facing Change ####
```release-note
NONE
```

#### Further Comments ####

Memory cost is one calloc of wfRows * plotW bytes for the waterfall history
(about 5KB on a 240px panel) plus two envelope buffers of plotW bytes each,
all freed on exit. If the waterfall allocation fails the component degrades to
a plot without history rather than refusing to start.

* Channel Analyzer: draw a swept spectrum instead of stacked bars

The analyzer drew eleven horizontal bars and repainted one of them per dwell.
The display jumped from channel to channel, showed nothing about how adjacent
channels overlap, and sat still between measurements.

It now renders through WifiSpectrumView: the eleven channels become
overlapping spectral lobes on a real frequency axis, with a peak-hold trace, a
scrolling waterfall, a channel ruler and a status line. The view is also
animated inside the dwell window, so levels glide toward the last measurement
instead of snapping when a channel commits.

The measurement path is untouched. The promiscuous RX callback, the WiFi
bring-up, the channel hop and the airtime estimate are byte-identical; the
whole presentation layer simply left this file. Up/Down still adjust the dwell
between 150 and 1000ms and ESC still exits.

New Feature / UI rework. No breaking change. Airtime measurement behaviour is
unchanged.

Flash and open WiFi -> Channel Analyzer. Expect:

  - a continuous skyline across channels 1-11 rather than eleven bars, with
    visibly overlapping lobes around 1/6/11
  - the lobe of the channel being measured filled brighter, sweeping across
  - a red-tinted peak-hold line above the live trace
  - a waterfall under the trace, scrolling one row per measurement
  - the channel ruler highlighting the current channel
  - Up/Down changing the dwell shown in the status line

Sitting next to a busy AP on a known channel should raise that lobe; the
airtime percentages in the status line should match what the previous build
reported for the same environment.

No unit test harness exists for this layer. Compiled for m5stack-cardputer on
this branch. Not yet verified on hardware by me - the numbers should be
sanity-checked against the previous build in the same RF environment, since
this PR is only supposed to change how they are drawn.

None. Depends on ui/spectrum-view.

```release-note
Channel Analyzer now shows a swept spectrum with overlapping channel lobes, peak hold and a waterfall instead of one bar per channel. Controls are unchanged.
```

Net -43 lines: the drawing code moved into the shared component, which
ui/jam-detect-spectrum then reuses unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Vinicius <discoestudio5@gmail.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* Add theme-aware colour helpers to the display core

#### Proposed Changes ####

A module that needs a dimmed or highlighted shade of the active theme has no
way to ask for one. The only helper available, getColorVariation, steps
brightness up or down and cannot mix toward another colour, so modules fall
back to a fixed constant such as TFT_DARKGREY or TFT_WHITE. That is the root
cause of most of the screens in Bruce that stop following the user's theme as
soon as they need more than the primary colour.

This adds two helpers next to it in core/display:

  blendColors(a, b, t)      linear RGB565 mix; t = 0 keeps a, t = 255 keeps b
  buildHeatPalette(lut, n)  fills a lookup table with a theme ramp running
                            from the background up to a brightened primary,
                            for waterfalls and other intensity plots

Both are pure additions. No existing function is modified, and nothing calls
them in this PR - the modules that consume them follow separately.

#### Types of Changes ####

New Feature (internal helper API). No behaviour change and no breaking change.

#### Verification ####

Nothing on screen changes from this PR alone, so verification is limited to
confirming the build is clean and that only insertions were made:

    pio run -e m5stack-cardputer
    git show --stat   # 2 files changed, 31 insertions(+), 0 deletions(-)

#### Testing ####

Bruce has no unit test harness covering the display layer, so this was
verified by compiling for m5stack-cardputer on this branch. The helpers get
exercised on hardware through the follow-up PRs that use them.

#### Linked Issues ####

None. This is the base commit of a series that standardises the
spectrum-style screens; the module PRs listed below depend on it.

#### User-Facing Change ####
```release-note
NONE
```

#### Further Comments ####

Prerequisite for ui/spectrum-view, ui/rf-waterfall-theme and
ui/rf-spectrum-theme.

* Add a shared spectrum-analyzer plot component

#### Proposed Changes ####

Every "signal strength across a band" screen in Bruce draws its own bars,
picks its own colours and hardcodes its own geometry. No two look alike, and
none of them scale past the panel the original author happened to own.

This adds SpectrumPlot (core/spectrum_plot), which owns the whole
presentation for that shape of data:

  - layout derived from tftWidth/tftHeight, degrading on short screens by
    dropping the ruler first, then the status line, then the waterfall
  - filled trace with a peak-hold line, an animated noise floor and a dashed
    reference grid visible only through the empty sky
  - scrolling waterfall backed by a ring of rendered rows
  - labelled ruler and a status line
  - every colour derived from the active theme through blendColors

Callers keep their own radio and fill an envelope of width() values in 0-100;
the component owns every pixel. Each pixel of the band is written exactly once
per frame, so the animation is flicker free without needing a full-screen
sprite.

WifiSpectrumView (modules/wifi/wifi_spectrum) sits on top and maps the 2.4GHz
channels onto a real frequency axis as overlapping spectral lobes, so the
22MHz overlap between neighbours becomes visible and a sweep reads as one
continuous trace. It also eases the drawn levels toward the measured ones, so
the trace glides rather than snapping channel by channel.

Nothing uses either component in this PR; the modules move over separately.

#### Types of Changes ####

New Feature (internal UI component). No behaviour change and no breaking
change on its own.

#### Verification ####

Nothing on screen changes from this PR alone. The component becomes visible
through ui/channel-analyzer-spectrum, ui/jam-detect-spectrum and
ui/nrf-spectrum, which is where the rendering should be judged.

    pio run -e m5stack-cardputer
    git show --stat   # 4 new files, 445 insertions(+), 0 deletions(-)

#### Testing ####

No unit test harness exists for this layer. Both files were compiled for
m5stack-cardputer as part of ui/channel-analyzer-spectrum, which stacks
directly on this branch and builds clean; this branch was not built in
isolation. Rendering has not yet been checked on hardware by me - a reviewer
with a device should confirm the layout on a non-240x135 panel in particular,
since the degradation path for short screens is the least exercised part.

#### Linked Issues ####

None. Depends on ui/color-helpers for blendColors and buildHeatPalette.

#### User-Facing Change ####
```release-note
NONE
```

#### Further Comments ####

Memory cost is one calloc of wfRows * plotW bytes for the waterfall history
(about 5KB on a 240px panel) plus two envelope buffers of plotW bytes each,
all freed on exit. If the waterfall allocation fails the component degrades to
a plot without history rather than refusing to start.

* Jam Detect: share the Channel Analyzer spectrum view

#### Proposed Changes ####

Jam Detect and Channel Analyzer sweep the same eleven channels and carried two
separate implementations of the same stacked-bar drawing, differing mainly in
which fixed TFT_ constants each had picked. Jam Detect also flagged attacks
with a TFT_RED/TFT_GREEN banner that ignored the theme entirely.

The ~55 line jd_draw is replaced by a 12 line jd_normalize that scales
deauth/s against twice the alert threshold - a channel sitting exactly on the
threshold fills half its lobe, anything past double saturates - and hands the
result to WifiSpectrumView. Both modules now look identical.

The alert recolours the trace and the status line instead of painting a
banner, using a colour chosen to stay visible even when the theme itself is
reddish.

Detection is untouched: the deauth/disassoc frame test, the channel hop, the
rate extrapolation and the immediate in-dwell trip all behave as before.

#### Types of Changes ####

New Feature / UI rework. No breaking change. Detection behaviour is unchanged.

#### Verification ####

Flash and open WiFi -> Jam Detect. With no attack running, expect a quiet
trace and a "clear" status line. Then run a deauth flood from a second device
on a known channel and expect:

  - that channel's lobe to rise and the trace to switch to the alert colour
  - the status line to read "! ATTACK CHn <rate>/s" in the alert colour
  - the alert to latch while the sweep moves on, as it did before
  - Up/Down to still move the threshold in steps of 5, between 5 and 250

Setting the threshold below the ambient deauth rate should trip immediately,
which is the quickest way to check the alert path without generating traffic.

#### Testing ####

No unit test harness exists for this layer. jam_detect.cpp compiles clean for
m5stack-cardputer, verified in the combined tree; this branch was not built in
isolation. Not yet verified on hardware by me - the alert path in particular
should be confirmed against a real deauth source.

#### Linked Issues ####

None. Depends on ui/spectrum-view.

#### User-Facing Change ####
```release-note
Jam Detect now shares the Channel Analyzer spectrum display. Attacks are flagged by recolouring the trace and status line instead of a coloured banner, and the alert colour follows the active theme.
```

#### Further Comments ####

The rate is unbounded while the shared view wants 0-100, so the normalisation
picks twice the threshold as full scale. That choice is what makes the plot
readable at any threshold setting, but it does mean the lobe height is
relative to the threshold rather than absolute - the exact rate stays in the
status line.

---------

Co-authored-by: Vinicius <discoestudio5@gmail.com>
…ruceDevices#2758)

* Add theme-aware colour helpers to the display core

#### Proposed Changes ####

A module that needs a dimmed or highlighted shade of the active theme has no
way to ask for one. The only helper available, getColorVariation, steps
brightness up or down and cannot mix toward another colour, so modules fall
back to a fixed constant such as TFT_DARKGREY or TFT_WHITE. That is the root
cause of most of the screens in Bruce that stop following the user's theme as
soon as they need more than the primary colour.

This adds two helpers next to it in core/display:

  blendColors(a, b, t)      linear RGB565 mix; t = 0 keeps a, t = 255 keeps b
  buildHeatPalette(lut, n)  fills a lookup table with a theme ramp running
                            from the background up to a brightened primary,
                            for waterfalls and other intensity plots

Both are pure additions. No existing function is modified, and nothing calls
them in this PR - the modules that consume them follow separately.

#### Types of Changes ####

New Feature (internal helper API). No behaviour change and no breaking change.

#### Verification ####

Nothing on screen changes from this PR alone, so verification is limited to
confirming the build is clean and that only insertions were made:

    pio run -e m5stack-cardputer
    git show --stat   # 2 files changed, 31 insertions(+), 0 deletions(-)

#### Testing ####

Bruce has no unit test harness covering the display layer, so this was
verified by compiling for m5stack-cardputer on this branch. The helpers get
exercised on hardware through the follow-up PRs that use them.

#### Linked Issues ####

None. This is the base commit of a series that standardises the
spectrum-style screens; the module PRs listed below depend on it.

#### User-Facing Change ####
```release-note
NONE
```

#### Further Comments ####

Prerequisite for ui/spectrum-view, ui/rf-waterfall-theme and
ui/rf-spectrum-theme.

* Add a shared spectrum-analyzer plot component

#### Proposed Changes ####

Every "signal strength across a band" screen in Bruce draws its own bars,
picks its own colours and hardcodes its own geometry. No two look alike, and
none of them scale past the panel the original author happened to own.

This adds SpectrumPlot (core/spectrum_plot), which owns the whole
presentation for that shape of data:

  - layout derived from tftWidth/tftHeight, degrading on short screens by
    dropping the ruler first, then the status line, then the waterfall
  - filled trace with a peak-hold line, an animated noise floor and a dashed
    reference grid visible only through the empty sky
  - scrolling waterfall backed by a ring of rendered rows
  - labelled ruler and a status line
  - every colour derived from the active theme through blendColors

Callers keep their own radio and fill an envelope of width() values in 0-100;
the component owns every pixel. Each pixel of the band is written exactly once
per frame, so the animation is flicker free without needing a full-screen
sprite.

WifiSpectrumView (modules/wifi/wifi_spectrum) sits on top and maps the 2.4GHz
channels onto a real frequency axis as overlapping spectral lobes, so the
22MHz overlap between neighbours becomes visible and a sweep reads as one
continuous trace. It also eases the drawn levels toward the measured ones, so
the trace glides rather than snapping channel by channel.

Nothing uses either component in this PR; the modules move over separately.

#### Types of Changes ####

New Feature (internal UI component). No behaviour change and no breaking
change on its own.

#### Verification ####

Nothing on screen changes from this PR alone. The component becomes visible
through ui/channel-analyzer-spectrum, ui/jam-detect-spectrum and
ui/nrf-spectrum, which is where the rendering should be judged.

    pio run -e m5stack-cardputer
    git show --stat   # 4 new files, 445 insertions(+), 0 deletions(-)

#### Testing ####

No unit test harness exists for this layer. Both files were compiled for
m5stack-cardputer as part of ui/channel-analyzer-spectrum, which stacks
directly on this branch and builds clean; this branch was not built in
isolation. Rendering has not yet been checked on hardware by me - a reviewer
with a device should confirm the layout on a non-240x135 panel in particular,
since the degradation path for short screens is the least exercised part.

#### Linked Issues ####

None. Depends on ui/color-helpers for blendColors and buildHeatPalette.

#### User-Facing Change ####
```release-note
NONE
```

#### Further Comments ####

Memory cost is one calloc of wfRows * plotW bytes for the waterfall history
(about 5KB on a 240px panel) plus two envelope buffers of plotW bytes each,
all freed on exit. If the waterfall allocation fails the component degrades to
a plot without history rather than refusing to start.

* NRF Spectrum: continuous trace, theme colours and a frequency ruler

#### Proposed Changes ####

The sweeper drew one bar per channel with the colour alternating on index
parity, which is texture rather than information. It cleared the screen with a
literal TFT_BLACK, drew its grid in a magic RGB565(25, 25, 25), and had no
border, no title and no status bar. Its only axis labels sat in the middle of
the plot.

It now renders through SpectrumPlot: the 80 carriers are interpolated into a
continuous trace, peak hold decays one level per sweep, and the module gets
the standard frame plus a 2.40-2.48GHz ruler and a status line naming the
strongest carrier and its level.

Scanning is also split from drawing. scanChannels() now only measures and
returns its result string, which is what the WebUI already advertised in the
header will need; the loop drives the plot separately. Redraws are capped at
40ms so repainting does not starve the radio sweep.

The RPD sampling itself is unchanged: same channel order, same 128us listen
window, same smoothing accumulator.

#### Types of Changes ####

New Feature / UI rework, plus a small refactor separating scan from render.
No breaking change - scanChannels keeps its signature and its return format.

#### Verification ####

Flash and open NRF -> Spectrum with an NRF24 attached over SPI. Expect:

  - a continuous trace across the band instead of 80 alternating bars
  - a peak-hold line that decays slowly rather than sticking
  - a title, border and status bar consistent with the rest of Bruce
  - a 2.40 / 2.42 / 2.44 / 2.46 / 2.48 ruler along the bottom
  - the status line naming the busiest carrier

Holding a 2.4GHz transmitter near the radio should raise the trace at the
matching frequency, and the status line should name that channel.

With no NRF24 present the module should still show its frame and then the
"NRF24 not found" error, rather than a blank screen.

#### Testing ####

No unit test harness exists for this layer. nrf_spectrum.cpp compiles clean
for m5stack-cardputer, verified in the combined tree; this branch was not
built in isolation. Not yet verified on hardware by me, and I have no NRF24 to
test against - a reviewer with the radio should confirm the trace tracks a
known transmitter.

#### Linked Issues ####

None. Depends on ui/spectrum-view.

#### User-Facing Change ####
```release-note
NRF Spectrum now draws a continuous trace with peak hold, a frequency ruler and a waterfall, follows the active theme, and shows the standard title and status bar.
```

#### Further Comments ####

Splitting scanChannels from the rendering is the part worth a second look: the
function previously drew as a side effect, so anything calling it for the
WebUI string would also have painted the screen. It now does what its name and
return type suggest.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Vinicius <discoestudio5@gmail.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…reen (BruceDevices#2760)

* Add theme-aware colour helpers to the display core

#### Proposed Changes ####

A module that needs a dimmed or highlighted shade of the active theme has no
way to ask for one. The only helper available, getColorVariation, steps
brightness up or down and cannot mix toward another colour, so modules fall
back to a fixed constant such as TFT_DARKGREY or TFT_WHITE. That is the root
cause of most of the screens in Bruce that stop following the user's theme as
soon as they need more than the primary colour.

This adds two helpers next to it in core/display:

  blendColors(a, b, t)      linear RGB565 mix; t = 0 keeps a, t = 255 keeps b
  buildHeatPalette(lut, n)  fills a lookup table with a theme ramp running
                            from the background up to a brightened primary,
                            for waterfalls and other intensity plots

Both are pure additions. No existing function is modified, and nothing calls
them in this PR - the modules that consume them follow separately.

#### Types of Changes ####

New Feature (internal helper API). No behaviour change and no breaking change.

#### Verification ####

Nothing on screen changes from this PR alone, so verification is limited to
confirming the build is clean and that only insertions were made:

    pio run -e m5stack-cardputer
    git show --stat   # 2 files changed, 31 insertions(+), 0 deletions(-)

#### Testing ####

Bruce has no unit test harness covering the display layer, so this was
verified by compiling for m5stack-cardputer on this branch. The helpers get
exercised on hardware through the follow-up PRs that use them.

#### Linked Issues ####

None. This is the base commit of a series that standardises the
spectrum-style screens; the module PRs listed below depend on it.

#### User-Facing Change ####
```release-note
NONE
```

#### Further Comments ####

Prerequisite for ui/spectrum-view, ui/rf-waterfall-theme and
ui/rf-spectrum-theme.

* RF Spectrum: derive the plot area from the panel, not from a 120px screen

#### Proposed Changes ####

The three views in this file each assumed a fixed layout. The plot started at
y = 20 and ran to tftHeight, spanned x = 0 to tftWidth, and the dBm scale was
positioned with the literal `(tftHeight - 120) + 95`, which only lands
correctly on a screen with 120px of usable height. The grid was TFT_DARKGREY
and the font size a literal 1.

All three now share one plot rectangle from rf_plot_top(), rf_plot_bot(),
rf_plot_left() and rf_plot_width(), derived from the title bar, the status
line and the rounded theme border at x = 5, so the trace no longer paints over
the frame on either side. The dBm arithmetic is replaced by an rssiY() that
maps -95..-20 onto that band, and the left gutter is sized from the label
width instead of a hardcoded 20px.

The header moved to drawMainBorderWithTitle with the tuning on a status line,
drawn once and repainted only when the frequency changes, so the live graph no
longer flickers. Grid and labels use theme-derived shades.

Behaviour of the RMT capture, the M5 receiver and the CC1101 sweep is
unchanged.

#### Types of Changes ####

Bugfix (layout only correct on one screen size; graph overran the frame) plus
UI rework.

#### Verification ####

Flash and open each of the three views - RF -> Spectrum, RF -> SquareWave and
RF -> RSSI, the last in both fixed-frequency and range modes. In every one:

  - the graph must stay inside the rounded border, with clear margins on the
    left and right edges
  - the title bar and the status line must not be overdrawn
  - the dBm labels in the RSSI view must line up with the trace, and -95 must
    sit at the bottom of the plot rather than off the band

The frequency-range view should still show the band edges bottom-left and
bottom-right and the Max= readout centred.

Worth checking on a panel that is not 240x135, since the old scale arithmetic
was only correct at one height and that is what this PR fixes.

#### Testing ####

No unit test harness exists for this layer. Compiled for m5stack-cardputer on
this branch. Not yet verified on hardware by me - the three views need a CC1101
or an M5 RF module to exercise fully.

#### Linked Issues ####

None. Depends on ui/color-helpers for blendColors. Does not need the
SpectrumPlot component, so it can be reviewed and merged independently of the
rest of the series.

#### User-Facing Change ####
```release-note
RF Spectrum, SquareWave and RSSI views now size their plot area from the display instead of assuming a 120px tall screen, no longer draw over the window border, and follow the active theme.
```

#### Further Comments ####

The RSSI range view still draws its own bargraph rather than going through the
shared SpectrumPlot component, which it is a natural fit for - same shape of
data as the NRF sweeper. That conversion was left out to keep this PR to
layout and theming; it would be a good follow-up.

---------

Co-authored-by: Vinicius <discoestudio5@gmail.com>
…ices#2765)

#### Proposed Changes ####

The suite drew every screen itself, on a pixel grid that assumed a tall
panel. Rows sat at a fixed y = 60 with 25 or 30px pitch under a two-line
footer, so on a 135px Cardputer the menus fitted two rows and the device
list exactly one, which is why a long list left no sense of place. Colours
were roughly 279 fixed TFT_ constants, so nothing followed the theme.

Add a small set of shared helpers and move every list onto them:

  - geometry derived from tftWidth/tftHeight; a Cardputer goes from 1-2
    visible rows to 5
  - the standard Bruce frame through drawMainBorderWithTitle, so the screens
    finally carry the border, title and status bar
  - colours from the theme via getColorVariation instead of fixed white,
    green and cyan
  - truncation measured with textWidth rather than counted in characters
  - only the list body repaints between key presses, so moving the cursor no
    longer flashes the whole screen
  - navigation without the blocking delay(150) that made input feel stuck

Device rows carry what the list was missing: an ordinal to refer to a device
by, the last two MAC octets to tell apart identical names, and a four-step
signal meter in place of a raw RSSI number. Capability tags reserve their
width before the name is measured, so a long name can no longer push the
vulnerability markers off screen.

Submenus show the selected target on the hint line, and every list carries a
position readout.

Feedback screens stop flooding the panel with solid yellow, green and red
and use Bruce's own displayWarning / displayError / displaySuccess stripes.
The two second splash carrying its own hardcoded version number is gone.

#### Types of Changes ####

UI rework, plus three bugfixes:

  - showAdaptiveMessage drew its hint with TFT_BLACK on the theme background,
    so "Press any key to continue..." was invisible on every dark theme and
    the screen looked hung. Pre-existing.
  - showDeviceInfoScreen silently dropped any line past the bottom of the
    panel. It now wraps everything up front and pages with PREV/NEXT.
  - the multi-target picker advertised "NEXT: Confirm" but no key confirmed,
    so ESC - which clears the selection - was the only way out and the
    success path was unreachable. A trailing Confirm row completes it.

#### Verification ####

Flash and open BLE Suite. Expect five rows per screen on a Cardputer instead
of one or two, a themed border with title and battery, and an "n/total"
readout at the foot of every list. Device rows should show number, name, MAC
tail, tags and signal bars, and changing the theme colour should recolour the
whole suite.

Run a Quick Vulnerability Scan against any target and confirm the TEST
RESULTS screen shows its text with a coloured severity bar down the left
edge: green for safe, orange for vulnerable, red for failure.

The DuckyScript picker and the multi-select flow deserve a real run: their
payload strings and target lists are unchanged but the selection paths were
rewritten.

#### Testing ####

No unit test harness exists for this layer. Compiled clean for
m5stack-cardputer.

Partially exercised on hardware: an earlier revision of this branch rendered
the results screens blank, because showDeviceInfoScreen kept using the
caller's textColor after losing the solid background it was meant to contrast
with - six call sites pass TFT_BLACK, which is invisible on a black theme.
Body text now always uses the theme foreground and the caller's colour only
picks the severity marker, which is why that class of failure cannot recur.
The attack paths themselves were not exercised; this change only touches
presentation.

#### Linked Issues ####

None. Independent of the spectrum PRs: it shares no file with them and uses
only core APIs already present in dev, so it can merge in any order.

#### User-Facing Change ####
```release-note
BLE Suite now uses the standard Bruce frame, follows the active theme and sizes its lists to the display, showing five rows on a Cardputer instead of one. Device entries gained an index, MAC tail and signal bars, and result screens scroll instead of truncating. Fixed an invisible "press any key" hint on dark themes and a multi-target picker that could not confirm a selection.
```

#### Further Comments ####

Around 165 fixed colours remain, in sniffer and attack-result screens not
covered here. No scrollbar was added: Bruce has no such idiom anywhere, so it
belongs in the core rather than in this module.

Co-authored-by: Vinicius <discoestudio5@gmail.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…ces#2780)

Co-authored-by: pr3y <67231282+pr3y@users.noreply.github.com>
* Add theme-aware colour helpers to the display core

#### Proposed Changes ####

A module that needs a dimmed or highlighted shade of the active theme has no
way to ask for one. The only helper available, getColorVariation, steps
brightness up or down and cannot mix toward another colour, so modules fall
back to a fixed constant such as TFT_DARKGREY or TFT_WHITE. That is the root
cause of most of the screens in Bruce that stop following the user's theme as
soon as they need more than the primary colour.

This adds two helpers next to it in core/display:

  blendColors(a, b, t)      linear RGB565 mix; t = 0 keeps a, t = 255 keeps b
  buildHeatPalette(lut, n)  fills a lookup table with a theme ramp running
                            from the background up to a brightened primary,
                            for waterfalls and other intensity plots

Both are pure additions. No existing function is modified, and nothing calls
them in this PR - the modules that consume them follow separately.

#### Types of Changes ####

New Feature (internal helper API). No behaviour change and no breaking change.

#### Verification ####

Nothing on screen changes from this PR alone, so verification is limited to
confirming the build is clean and that only insertions were made:

    pio run -e m5stack-cardputer
    git show --stat   # 2 files changed, 31 insertions(+), 0 deletions(-)

#### Testing ####

Bruce has no unit test harness covering the display layer, so this was
verified by compiling for m5stack-cardputer on this branch. The helpers get
exercised on hardware through the follow-up PRs that use them.

#### Linked Issues ####

None. This is the base commit of a series that standardises the
spectrum-style screens; the module PRs listed below depend on it.

#### User-Facing Change ####
```release-note
NONE
```

#### Further Comments ####

Prerequisite for ui/spectrum-view, ui/rf-waterfall-theme and
ui/rf-spectrum-theme.

* Add a shared spectrum-analyzer plot component

#### Proposed Changes ####

Every "signal strength across a band" screen in Bruce draws its own bars,
picks its own colours and hardcodes its own geometry. No two look alike, and
none of them scale past the panel the original author happened to own.

This adds SpectrumPlot (core/spectrum_plot), which owns the whole
presentation for that shape of data:

  - layout derived from tftWidth/tftHeight, degrading on short screens by
    dropping the ruler first, then the status line, then the waterfall
  - filled trace with a peak-hold line, an animated noise floor and a dashed
    reference grid visible only through the empty sky
  - scrolling waterfall backed by a ring of rendered rows
  - labelled ruler and a status line
  - every colour derived from the active theme through blendColors

Callers keep their own radio and fill an envelope of width() values in 0-100;
the component owns every pixel. Each pixel of the band is written exactly once
per frame, so the animation is flicker free without needing a full-screen
sprite.

WifiSpectrumView (modules/wifi/wifi_spectrum) sits on top and maps the 2.4GHz
channels onto a real frequency axis as overlapping spectral lobes, so the
22MHz overlap between neighbours becomes visible and a sweep reads as one
continuous trace. It also eases the drawn levels toward the measured ones, so
the trace glides rather than snapping channel by channel.

Nothing uses either component in this PR; the modules move over separately.

#### Types of Changes ####

New Feature (internal UI component). No behaviour change and no breaking
change on its own.

#### Verification ####

Nothing on screen changes from this PR alone. The component becomes visible
through ui/channel-analyzer-spectrum, ui/jam-detect-spectrum and
ui/nrf-spectrum, which is where the rendering should be judged.

    pio run -e m5stack-cardputer
    git show --stat   # 4 new files, 445 insertions(+), 0 deletions(-)

#### Testing ####

No unit test harness exists for this layer. Both files were compiled for
m5stack-cardputer as part of ui/channel-analyzer-spectrum, which stacks
directly on this branch and builds clean; this branch was not built in
isolation. Rendering has not yet been checked on hardware by me - a reviewer
with a device should confirm the layout on a non-240x135 panel in particular,
since the degradation path for short screens is the least exercised part.

#### Linked Issues ####

None. Depends on ui/color-helpers for blendColors and buildHeatPalette.

#### User-Facing Change ####
```release-note
NONE
```

#### Further Comments ####

Memory cost is one calloc of wfRows * plotW bytes for the waterfall history
(about 5KB on a 240px panel) plus two envelope buffers of plotW bytes each,
all freed on exit. If the waterfall allocation fails the component degrades to
a plot without history rather than refusing to start.

---------

Co-authored-by: Vinicius <discoestudio5@gmail.com>
Refactor HMAC-SHA1 implementation to use precomputed states directly, improving performance and reducing memory overhead. Update PTK derivation to derive KCK instead.
@hackspb0
hackspb0 merged commit fe7b63f into WiFi-New-feature Aug 28, 2026
8 checks 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.

9 participants