Files
PolterHID/README.md
T
Commander1024 9e49484354 Add maintenance mode and board status indicators
Provide BOOT-triggered USB CDC maintenance mode for file updates, plus
onboard LED activity flashes and enhanced display status information.
2026-08-27 20:33:24 +02:00

6.0 KiB
Raw Blame History

PolterHID

Square PolterHID ghost mascot

A deliberately limited MicroPython awareness-training firmware for an ESP32 USB stick. On boot, it presents as a USB keyboard/mouse and, at independently random intervals, sends only:

  • one Return key press and release; and
  • a configurable relative mouse movement, from 1 to 127 pixels per event.

The authenticated web page also provides manual Volume up and Volume down controls for a clearly observable, non-destructive HID demonstration.

A password-protected local web page changes the enabled event types, mouse movement distance, and timing. The device connects to one preconfigured Wi-Fi network; it does not create an access point. On the T-Dongle-S3, its onboard RGB LED briefly flashes red for a Return press, green for mouse jitter, and blue for a volume action.

Hardware and firmware requirement

This needs an ESP32-S2 or ESP32-S3 board/stick whose USB connector is wired to the chip's native USB peripheral. A classic ESP32 with a CP210x/CH340 USB-to-serial adapter cannot act as a USB HID device.

Flash a current MicroPython build for that exact board which provides machine.USBDevice. Check at the REPL:

from machine import USBDevice

If that import fails, use an up-to-date native-USB MicroPython build for the board. This project uses the custom USB-device API and does not work with CircuitPython's usb_hid API.

Install

  1. Flash a current ESP32-S3 MicroPython build, then verify that from machine import USBDevice succeeds at the REPL. A separate mdns module is not required on the standard ESP32 port.
  2. Copy settings.example.json to the device filesystem as settings.json and replace all three credentials. Set display_enabled to false when validating on a board without the T-Dongle-S3 display; leave it true for the T-Dongle-S3. Do this before first boot: the firmware cannot join Wi-Fi with the placeholder defaults.
  3. Copy main.py, hid.py, led.py, maintenance.py, settings.py, web.py, and display.py to the device root filesystem, then copy boot.py last. This ordering ensures the module imported by boot.py is already present.
  4. Reset the board and plug its native USB connector into a dedicated test host. It should enumerate as a keyboard, mouse, and media-control HID device.
  5. Once it joins Wi-Fi, visit http://polterhid.local/. If the client/network does not support mDNS, find the device IP address in the training Wi-Fi DHCP leases and visit http://DEVICE_IP/ instead.
  6. Sign in with username admin and the web_password from settings.json.

boot.py configures the native USB interface before USB initialisation, while main.py runs the application when executed by the runtime. Importing main from the REPL only loads its functions; call main.main() explicitly if needed. The device is intentionally HID-only: its built-in USB serial/CDC REPL is replaced by the HID device after boot.py runs. On a LILYGO T-Dongle-S3, its built-in 160×80 ST7735 screen shows a short sparkling starfield, a side-profile ghost crossing the screen, and the POLTERHID title before showing the configured Wi-Fi SSID, IP address, station MAC address, and a top-right Wi-Fi signal indicator for 15 seconds. It then clears the panel and turns its backlight off; press BOOT once to show the information again for 10 seconds. The screen driver is skipped when display_enabled is false, which is useful on a display-less DevKit. The firmware sets the ESP32 network hostname to polterhid before joining Wi-Fi; standard ESP32 MicroPython builds use their built-in mDNS responder to announce polterhid.local. Configuration saved in the web page is retained in settings.json and takes effect immediately.

Maintenance mode and file uploads

Use maintenance mode to regain the normal MicroPython USB serial/CDC REPL and update files without reflashing the board.

  1. Disconnect power or press reset without holding the T-Dongle-S3 BOOT button.
  2. During the first 1.5 seconds after reset, press BOOT once.
  3. The firmware leaves USB CDC enabled and exits before starting HID injection, Wi-Fi, the web server, or display animations.
  4. Wait for the host to enumerate the device as a MicroPython serial port, then connect with mpremote, Thonny, or another serial REPL tool.
  5. Upload the changed files and reset normally without pressing BOOT to return to HID mode.

For example, with mpremote:

mpremote connect auto fs cp main.py :
mpremote connect auto fs cp display.py :
mpremote connect auto reset

Important: Do not hold BOOT while resetting or powering on. BOOT is connected to GPIO0, an ESP ROM boot strap; holding it at reset enters the ESP download bootloader rather than PolterHID maintenance mode. If updating boot.py, upload maintenance.py first because boot.py imports it.

Operational safeguards

  • Change the example credentials before deployment. HTTP Basic authentication and all form data, including the password, are unencrypted because this intentionally has no TLS. Use an isolated/trusted training Wi-Fi network.
  • The page provides an immediate Enable all injection switch. Turn it off before handing the device to anyone or ending a session.
  • This firmware intentionally has no shell commands, keystroke payloads, text injection, storage emulation, Wi-Fi scanning, AP mode, or remote firmware-update endpoint.
  • Test first on a dedicated demonstration machine. The configured defaults are intentionally infrequent but still cause a Return and minimal mouse movement.

USB implementation note

hid.py exposes one composite HID interface with keyboard report ID 1, mouse report ID 2, and Consumer Control/media report ID 3 (Volume up/down). boot.py selects USBDevice.BUILTIN_NONE, supplies explicit USB device/configuration descriptors, and activates the interface. It queues reports while the endpoint is busy and discards a transfer if the host disconnects, avoiding stale events being replayed after a reconnect.