Skip to content

Latest commit

 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

enter-bot

An ESP32-S3 that pretends to be a USB keyboard and holds down Enter until your server finishes booting, then stops on its own.

Built for one specific and very annoying problem: an HP laptop used as a headless server, with the battery removed, that refuses to boot unattended after a power cut.

Tested on an HP EliteBook 1030 x360 G3.


The problem

Business laptops make excellent little servers. Low power, quiet, built-in UPS. Until you take the battery out — because it is swollen, or dead, or you simply don't want a lithium pouch cooking in a closet 24/7.

Once the battery is gone, HP firmware notices. After any complete power loss it stops mid-POST with something like:

The system has detected that a battery is not present ...
Press the ENTER key to continue startup

Sometimes it is a real-time-clock complaint, sometimes a docking or configuration notice. The exact wording varies. What does not vary is the outcome: the machine sits at that prompt forever, waiting for a human to press a key.

For a headless box in a closet this is fatal. Power comes back, everything else on the UPS returns, and your server is stuck on a message nobody is there to read. Wake-on-LAN cannot help — the machine never got far enough to have a network stack. Neither can a BIOS "power on after AC loss" setting; the board does power on, it just stops at the prompt.

The trick

There is a window between the moment power returns and the moment the OS is up. The prompt lives inside that window, and inside that window the firmware is listening to exactly one thing: a USB boot-protocol keyboard.

So put one there permanently.

The ESP32-S3 is powered by the laptop's own USB port, which means it boots at the same instant the laptop does. From its very first millisecond it types Enter, over and over, into a machine that may or may not be asking for it. If the prompt appears, it gets answered within milliseconds. If it never appears, the extra keystrokes land on a POST screen and are harmlessly discarded.

Then the important half: the bot watches your network and stops as soon as the server answers. No timer to guess at, no fixed delay to tune.

The consequence worth spelling out:

No display is ever required. Not during setup, not during recovery, not ever. The bot cannot see the screen and never needs to. It types blind through the boot window and uses the network — not a monitor — to learn that the boot succeeded. Fully unattended, in a closet, with no monitor, no KVM, no IPMI, and no iLO.

That is the whole idea. Everything below is detail.

What it actually does

power returns
    │
    ├── laptop starts POST ────────────────► "Press ENTER to continue"
    │
    └── ESP32-S3 powers up from laptop USB
            │
            ├── core 1: enumerate as a USB keyboard, start typing Enter immediately
            │
            └── core 0: join WiFi, then ping the server every 2 s
                            │
                            └── server answers ──► stop typing, go dim green

The two halves run on separate cores and never block each other. Networking is slow and unreliable; typing is not allowed to wait for it.

If the server later stops answering — a reboot, a crash, another power cut — the bot re-arms after a few consecutive failed checks and starts typing again. It also re-arms instantly when USB re-enumerates, because that means the host just came back from the dead.

Hardware

Item Notes
ESP32-S3 board with a native USB port Developed on an ESP32-S3-DevKitC-1 (16 MB flash)
An addressable RGB LED On-board on most S3 dev boards. GPIO 48 on the DevKitC-1, 38 on some clones
A USB-C or USB-A cable Into any port on the target machine that stays powered during POST

That is the entire bill of materials. No soldering, no extra components.

Which USB port: most HP laptops keep every port powered through POST. If in doubt, use one on the left/right side rather than a dock or hub — hubs sometimes enumerate too late to catch the prompt.

Use the board's native USB port, not the UART/serial one. The DevKitC-1 has two USB-C sockets; the one labelled USB is the one that can act as a keyboard. The one labelled UART goes through a serial bridge chip and cannot.

Quick start

git clone https://github.com/LucaCraft89/enter-bot.git
cd enter-bot
pio run -e esp32s3 -t upload

Then unplug it from your computer, plug it into the server, and you are done.

The first boot brings up its own WiFi access point. Join it, open the web UI, and fill in your WiFi and the address of the machine you are babysitting.

Default credentials

Value
Access point SSID enter-bot
Access point password enterbot123
Web UI address http://192.168.4.1 (on the AP)
Web UI username admin
Web UI password enterbot123

The AP password and the UI password are two separate settings that happen to share a default. Both are editable from the Network section of the web UI.

Change both before you deploy this. They are printed in this README, which means they are public. This device can type into your server's console and accept new firmware over the air.

Once WiFi is configured, the same UI is also reachable on your LAN — at the IP your router hands out, or usually at enter-bot.local. The access point stays on regardless, so you can always get back in.

If those passwords do not work, someone has saved different ones from the UI: the values above are only compiled-in defaults, and anything saved to the board's NVS flash wins over them. Wipe it back to defaults with:

pio run -t erase
pio run -e esp32s3 -t upload

That is genuinely all that is required. The rest of this file is for people who want to bake settings in, flash over the air, or understand what went wrong.

Baking in your settings (optional)

If you would rather not configure through the web UI, put your defaults in an untracked header before the first flash:

cp src/secrets.h.example src/secrets.h
$EDITOR src/secrets.h
#define WIFI1_SSID  "MyNetwork"
#define WIFI1_PASS  "my-wifi-password"
#define WIFI2_SSID  ""              // optional second network, "" disables
#define WIFI2_PASS  ""
#define TARGET_HOST "192.168.1.50" // the server this bot watches
#define AP_PASSWORD "enterbot123"
#define UI_PASSWORD "enterbot123"   // change this

src/secrets.h is gitignored. The firmware builds fine without it — you just get placeholder defaults and configure through the UI instead.

These are only factory defaults: they apply on a board that has never had anything saved from the web UI. Once you press Save, the values live in the ESP32's NVS flash and the compiled-in ones are never read again.

Serial port pinning (optional but recommended)

/dev/ttyACM0 is not a stable name. It changes when you replug, and if you have more than one ESP board attached, pio run -t upload can cheerfully overwrite the wrong one. Pin it by serial instead:

ls -l /dev/serial/by-id/
cp platformio_local.ini.example platformio_local.ini
$EDITOR platformio_local.ini

platformio_local.ini is gitignored and merged into the build automatically.

The web UI

Reachable two ways, always:

  • On your LAN, at whatever IP your router hands the bot (shown in the UI, and usually at enter-bot.local)
  • On its own access point, SSID enter-bot / enterbot123, at 192.168.4.1

The access point stays on permanently, even when WiFi is connected. It is there so that a wrong password or a changed network can never lock you out of a device sealed inside a server closet.

Every page and endpoint requires the admin password.

The top of the page shows live state: which network it joined, signal strength, whether the host is answering and how, whether USB has enumerated, and what the bot is doing right now.

Settings worth knowing about

Setting What it does
Key to send Enter, F1, F10, Esc, Space, Tab, Y, Down. Match your firmware's prompt
Gap between keypresses Default 120 ms. Lower is more aggressive; too low can overrun a small firmware key buffer
Key hold time Default 25 ms. Raise it if presses seem to be ignored
Host to watch IPv4 address of the server. When it answers, typing stops
TCP port to try 0 = ping only. Set this if your host is Windows — see below
Failed checks before resuming Default 3. Stops one dropped packet from typing into a live login prompt
Typing enabled Master switch, for when you want the bot present but quiet

If your server is Windows

Windows Firewall drops ICMP echo by default, so ping never succeeds and the bot never learns the host is up — it types forever. Set TCP port to try to something that is actually listening:

  • 3389 — Remote Desktop
  • 445 — file sharing
  • 22 — OpenSSH

The bot tries ping first and falls back to a TCP connect. Either one succeeding counts as "host is up". Point it at a port that is genuinely open: a refused connection also proves the host is alive, but connect() cannot tell refusal apart from a timeout.

LED status

The board's RGB LED is the entire diagnostic channel — there is no serial console, because the USB port is busy being a keyboard.

Green and blue mean two different things and never overwrite each other. Green is the server. Blue is the WiFi.

Colour Meaning
🟢 bright green flash A key was just sent
🟢 dim green Server is reachable. Idle, nothing to do. This is the happy state
🔵 blinking blue Still joining WiFi, server unknown, typing
🔵 steady blue WiFi is up, server is not answering, typing
🟣 purple Firmware update in progress
🟡 dim yellow USB has not enumerated — the host is off, or the port is dead

Reading it at a glance: blinking blue means fix your WiFi, steady blue means fix your host address.

Updating the firmware

Once this firmware runs, TinyUSB owns the USB port. esptool's automatic reset stops working, and reflashing over the cable requires holding BOOT while tapping RESET — which means physically reaching the board. That is exactly what you cannot do when it is taped inside a server.

So there are two wireless paths, and both need only the admin password.

From a terminal:

pio run -e esp32s3_ota -t upload

# if mDNS does not resolve on your network:
pio run -e esp32s3_ota -t upload --upload-port 192.168.1.42

From the web UI: the Firmware update box at the bottom of the page takes .pio/build/esp32s3/firmware.bin directly. Typing pauses during the write, and the board reboots when it finishes. A failed upload leaves the old firmware running.

The 16 MB partition layout gives two 6.5 MB app slots, so a bad flash is always recoverable.

Troubleshooting

The board enumerates as "USB JTAG/serial debug unit" instead of a keyboard, and the enter-bot access point never appears. It is stuck in ROM download mode, which means GPIO0 is being held low — a BOOT button that is pressed, stuck, or jumpered. Release it and tap RESET. You can confirm by watching the boot banner: boot:0x0 (DOWNLOAD(USB/UART0)) means download mode, and the firmware is not running at all.

The target machine will not boot at all with the bot attached. Some firmware is fussy about USB descriptors. Flash the bisect build, which is a boot-protocol keyboard and nothing else — no WiFi, no NVS, no web server:

pio run -e hidtest -t upload

If the machine boots with that and not with the full firmware, the problem is not the HID descriptor.

It types forever and never stops. Open the UI and read the status line. ping fail with a Windows host means you need the TCP port setting. WiFi: down means it never joined — check the SSID and password, and remember that ESP32 radios are 2.4 GHz only, so a 5 GHz-only SSID will never appear.

It stops too early, before the machine is really up. Raise Failed checks before resuming, or point Host to watch at something that only answers once the OS has fully started, rather than at a router or switch.

Presses seem to be ignored by the firmware. Raise Key hold time to 50 ms and Gap between keypresses to 250 ms. Some firmware polls the keyboard slowly and a fast burst just fills a buffer it then discards.

Design notes

A few decisions that are less obvious than they look:

The HID descriptor is hand-rolled. Arduino's USBHIDKeyboard advertises HID_ITF_PROTOCOL_NONE and puts a Report ID byte in front of every report. An operating system driver handles that fine. A BIOS/UEFI keyboard driver usually ignores it. What firmware expects is boot protocol: 8-byte reports, no Report ID, and a device that does not look like a composite IAD device. That is what this sends.

Typing is not gated on the network. fails starts at 255, above any possible threshold, so a cold boot starts typing at the first HID-ready moment and the network checks catch up on their own schedule. The bot never waits to find out whether it should be working.

Config changes reboot the board. Hot-reloading settings into two running tasks means locking. A reboot takes a few seconds and eliminates every race for free.

The access point is never turned off. A device you cannot physically reach must not be able to lock you out of its own configuration.

Security

This device types into your server's console and accepts firmware over the air. Treat it as trusted hardware on a trusted network.

  • Change UI_PASSWORD and AP_PASSWORD before deploying. Both default to enterbot123, which is published in this README and therefore public.
  • The web UI is plain HTTP with basic auth. Fine on a home LAN; do not expose it to the internet, and do not port-forward to it.
  • Saved passwords are rendered back into the settings form, so anyone who can reach the UI can read your WiFi credentials.
  • src/secrets.h and *_local.ini are gitignored. Keep it that way — check before you commit.

Repository layout

platformio.ini              build configuration
platformio_local.ini.example  template for your serial port
src/main.cpp                the entire firmware
src/secrets.h.example       template for your credentials
src/hid_test.cpp            minimal HID-only build, for bisecting boot problems

License

MIT. See LICENSE.

About

ESP32-S3 USB keyboard that answers the 'Press ENTER to continue' boot prompt on a battery-less HP laptop server, then stops once the host answers on the network. No display, KVM or IPMI required.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages