# PolterHID ![Square PolterHID ghost mascot](assets/polterhid-ghost-square.svg) 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: ```python 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.