← index #18331PR #15991
Off-topic · high · value 1.222
QUERY · ISSUE

ESP32: Ctrl-C from examples/bluetooth/ble_uart_repl.py does not interrupt user code; only the BLE REPL loop stops

openby mispacekopened 2025-10-26updated 2025-10-26
bug

Port, board and/or hardware

esp32 port, tested on ESP32C3 ESP32C6 and ESP32

MicroPython version

MicroPython v1.25.0 on 2025-04-15; ESP32C6 module with ESP32C6 but problem persist in older versions

Summary
When using the official examples/bluetooth/ble_uart_repl.py on ESP32, sending Ctrl-C (0x03) over BLE does not raise KeyboardInterrupt in a running user program. Instead, the BLE REPL itself stops or returns to the prompt while the user code keeps running. The same Ctrl-C works as expected over USB serial REPL.

Environment

  • Firmware: MicroPython v1.25.0 (build date 2025-04-15)
  • Boards tested: ESP32-C3 SuperMini, ESP32-C6 SuperMini and ESP32 Dev Board
  • Host: Windows 10 Chrome (Web Bluetooth), Android Adafruit Bluefruit LE Connect

Reproduction

Copy the three example files to the ESP32.
change:
self._payload = advertising_payload(name=name, appearance=_ADV_APPEARANCE_GENERIC_COMPUTER)
to
self._payload = advertising_payload(name=name, services=[_UART_UUID])
in ble_uart_peripheral.py because online terminals not working without NUS characteristics

Start BLE REPL:

import ble_uart_repl
ble_uart_repl.start()

Now you can connect to device. For example using https://repl.siliconwitchery.com/

Paste simple code into terminal

import utime

i = 0

for i in range(1, 1000):
  utime.sleep_ms(0)  
  print(i)
  utime.sleep_ms(10)

And try to stop code by sending CTRL+C, sometimes code stop correctly, but sometimes BLE Code stop working and numbers still counting.

Expected behaviour

KeyboardInterrupt is raised in the running user loop and execution stops, matching USB serial REPL behavior.

Observed behaviour

The BLE REPL appears to stop working but the user loop continues printing. Ctrl-C does not interrupt the user program. A hard reset is required to stop it.

Additional Information

What we tried

  • Verified that USB serial REPL on the same firmware raises KeyboardInterrupt as expected.
  • Verified the central does send a single 0x03 byte to the RX characteristic.

Technicaly same code without one line of code works much better, why ?

import utime

i = 0

for i in range(1, 1000):    
  print(i)
  utime.sleep_ms(10)

utime.sleep_ms(0) before print making a huge difference but code is still not 100% reliable

Code of Conduct

Yes, I agree

CANDIDATE · PULL REQUEST

tools: Only issue a single Ctrl-C when entering raw REPL.

mergedby dpgeorgeopened 2024-10-10updated 2024-10-15
tools

Summary

A long time ago when there was only the stm port, Ctrl-C would trigger a preemptive NLR jump to break out of running code. Then in commit 124df6f8d07b53542b6960dbeea9b63bff469a67 a more general approach to asynchronous KeyboardInterrupt exceptions was implemented, and stmhal supported both approaches, with the general (soft) interrupt taking priority.

Then in commit bc1488a05f509cd5be8bfab9574babfcb993806f pyboard.py was updated with a corresponding change to make it issue a double Ctrl-C to break out of any existing code when entering the raw REPL (two Ctrl-C characters were sent in order to more reliably trigger the preemptive NLR jump).

No other port has preemptive NLR jumps and so a double Ctrl-C doesn't really behave any differently to a single Ctrl-C: with USB CDC the double Ctrl-C would most likely be in the same USB packet and so processed in the same low-level USB callback, so it's just setting the keyboard interrupt flag twice in a row. The VM/runtime then just sees one keyboard interrupt and acts as though only one Ctrl-C was sent.

This commit changes the double Ctrl-C to a single Ctrl-C in pyboard.py and mpremote. That keeps things as simple as they need to be.

Testing

Ran the test suite on a PYBD-SF2 and ESP32_GENERIC board. It passes.

Also ran the mpremote tests on the same hardware. They also pass.

Trade-offs and Alternatives

There's a slight risk this may change behaviour of some code which expects two Ctrl-C's, but that's unlikely because in the vast majority of cases two back-to-back Ctrl-C's will just end up being one KeyboardInterrupt, ie equivalent to a single Ctrl-C (especially due to #15988 that preemptive NLR jump is removed).

Keyboard

j / / n
next pair
k / / p
previous pair
1 / / h
show query pane
2 / / l
show candidate pane
c
copy suggested comment
r
toggle reasoning
g i
go to index
?
show this help
esc
close overlays

press ? or esc to close

copied