> ## Content Index
> Fetch the complete content index at: https://new-blog.dougals.me/llms.txt
> Use this file to discover other available public pages before exploring further.

# Flashing XTEink v3 with esptool on Fedora
- URL: https://new-blog.dougals.me/flashing-xteink-v3-with-esptool-on-fedora/
- Published: 2026-08-10T19:22:00.000Z
- Updated: 2026-08-10T19:22:00.000Z
- Author: Dougie Richardson
- Tags: Linux, Fedora, Howto, Xteink, #Migrated-1789219936886, #wp, #wp-post, #Import 2026-09-12 13:32

I picked up an XTEink v3 dev edition to play with CrossPoint firmware. Step one was getting `crosspointreader.com/debug` to actually see the device over Web Serial. That took longer than it should have, mostly because I chased the wrong causes first.

![](https://blog.dougals.me/wp-content/uploads/2026/08/PXL_20260810_185954528-1.jpg)

**Quick summary:** skip the browser flasher entirely and use `esptool` from the command line. The debug page's Web Serial flasher consistently times out at the baud rate it uses. It's not a permissions or connection issue, it just can't win that race. `esptool` with an explicit `--baud` avoids the whole problem and lets you back up the existing firmware first, which the browser can't do at all.

## The setup that actually works

Three things, and all three have to be true at once.

First, your user needs to be in the `dialout` group:

```
sudo usermod -aG dialout $USER
```

Log out and back in, or reboot. Group membership only applies to new sessions, so running the command and immediately retrying gets you nowhere.

Second, the device has to be awake and sitting in its main menu when you connect. Not asleep, not mid-boot.

Third, go into the device's own Settings menu and set both **Time to sleep** and **Time to power off** to **Never** before you start. Otherwise it drops off the USB bus mid-transfer on its own timer, which shows up as a stall partway through a flash.

Web Serial only works in Chromium-family browsers (Chrome, Chromium, Edge, Brave, Opera). Firefox and Safari don't implement the API at all.

## Confirming it enumerated

```
lsusb | grep Espressif
ls -l /dev/ttyACM0
```

It should show as `Espressif USB JTAG/serial debug unit`, VID `303a`, PID `1001`, backed by `/dev/ttyACM0` owned `root:dialout`. The chip is an ESP32-C3 with a native USB Serial/JTAG controller built into the silicon, no separate USB-UART bridge chip involved.

A connect/disconnect/reconnect cycle in the kernel log right after plugging in is normal. That's the controller's CDC stack initialising.

## The red herring

The `dialout` membership was already correct, so that wasn't actually the problem. The actual blocker was simpler: the device wasn't awake at the moment I was testing. Once I confirmed it was connected and sitting in the main menu via `lsusb` and `dmesg`, Chromium picked it up on the first try.

## Two ways to flash it, one of which works

The debug page offers a browser-based flasher. I'd skip it and go straight to `esptool` on the command line instead.

### The browser approach

The debug page flashes over Web Serial using esptool-js. In testing this stalled at inconsistent points, 50% one run, 44% the next, throwing `Error: Read timeout exceeded` in DevTools, even with the device correctly connected and permissioned.

The cause is the baud rate. A full flash erase and write at the rate esptool-js drives just takes longer than its own read timeout allows for. It isn't a flaky connection or a permissions issue, the transfer is racing a clock it can't win, so it fails the same way every time. Don't bother chasing this one, go to `esptool` instead.

### esptool: backup, then write

Install it as a native Fedora package:

```
sudo dnf install -y esptool
```

Close any Chromium tab holding the port open first (see "If it happens again" below), then back up the existing flash before touching anything:

```
esptool --port /dev/ttyACM0 --baud 921600 read-flash 0x0 0x1000000 \
  ~/Nextcloud/xteinkv3_firmware_backups/xteinkv3_backup_$(date +%Y%m%d_%H%M%S).bin
sha256sum ~/Nextcloud/xteinkv3_firmware_backups/xteinkv3_backup_*.bin
```

`0x1000000` is 16MB, the flash size on this particular unit. Check yours first with `esptool --port /dev/ttyACM0 flash-id` if you're doing this on a different device.

The `--baud` flag matters more than it looks, and it's the same flag that saves you from the browser's problem. Without it, esptool defaulted to reading at roughly 2KB/s, which would have taken about two hours for the full 16MB. At 921600 baud the same read takes about four minutes, well inside any sane timeout.

Keep the SHA-256 alongside the backup file. It's the only way to confirm integrity later.

With the backup safely stored, download the new firmware from the debug page as `update.bin` and write it the same way:

```
esptool --port /dev/ttyACM0 --baud 921600 write-flash 0x0 update.bin
```

The device does a hard reset via RTS at the end of the operation. It briefly vanishes from the USB bus for a few seconds before it comes back. Normal behaviour. If you ever need to roll back, the same command with your backup file in place of `update.bin` restores it:

```
esptool --port /dev/ttyACM0 --baud 921600 write-flash 0x0 <backup>.bin
```

## If it happens again

If a transfer stalls, check `journalctl -k --since "5 min ago"` for a USB disconnect around that time. Nine times out of ten that means **Time to sleep** or **Time to power off** wasn't set to Never after all.

Re-check `lsusb` and `/dev/ttyACM0` before assuming it's a browser problem. If the OS sees the device fine but the browser's port picker shows nothing, the page's `requestPort()` filter might not be matching the ESP32-C3's actual VID/PID (`303a:1001`), worth reporting upstream if so.

And if `esptool` reports the port as busy, check `lsof /dev/ttyACM0` first. An open Chromium tab on the debug page is probably still holding the connection.