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.

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 boot.py, main.py, hid.py, settings.py, web.py, and display.py to the device root filesystem.
  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. Use the BOOT button to enter download mode for recovery, and verify the network configuration before deployment. On a LILYGO T-Dongle-S3, its built-in 160×80 ST7735 screen plays a boot animation, then shows the configured Wi-Fi SSID and either connecting... or the DHCP-assigned IPv4 address for 15 seconds. It subsequently enters an idle mode with occasional short PolterHID animations. 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.

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.

S
Description
PolterHID is a harmless ESP32-S3 HID awareness trainer that demonstrates the risks of untrusted USB devices using randomized Return/mouse events, Wi‑Fi controls, mDNS, display animations, and RGB feedback.
Readme
66 KiB
Languages
Python 100%