Skip to main content

Mock ESP32 ROM bootloader (SLIP) for CI and local upload testing without hardware

Project description

esp32-mock-bootloader

A mock Espressif ROM bootloader for testing firmware uploads without a board.

Run it on your machine or in CI. Point esptool, arduino-cli, or any ROM-compatible flasher at a TCP port or serial path, and flash as if a chip were connected.

CI PyPI Python License

Stability: version 0.x is alpha. CLI flags and protocol details may change before 1.0.0.

Table of contents

Overview

Real Espressif chips expose a ROM bootloader over serial. Upload tools speak a SLIP-framed binary protocol: sync, detect the SoC, write flash blocks, verify MD5, and so on.

esp32-mock-bootloader implements enough of that protocol for upload clients to complete a full flash cycle. It does not run your firmware or emulate peripherals — it only answers the bootloader conversation.

  esptool / arduino-cli / …
           │
           ▼
  socket://127.0.0.1:PORT   or   /dev/tty* / COM*
           │
           ▼
  esp32-mock-bootloader  (SLIP server)
           │
           ▼
  ACK + chip metadata + in-memory flash image

Chip metadata comes from the installed esptool package. When Espressif adds a new SoC to esptool, this mock can support it without a code change here.

For protocol details and authoritative behavior, see Protocol references.

Features

  • No hardware — run upload tests locally and in CI.
  • All esptool SoCs — profiles are built from esptool.targets.CHIP_DEFS.
  • Auto chip detection--chip auto learns the SoC from client traffic.
  • TCP daemon — background start / stop with stable socket:// URLs.
  • PTY and COM paths — Unix PTY, Windows com0com pairs, or socket fallback.
  • GitHub Action — one step to start the mock; teardown runs automatically.
  • CI-ready — tested on Ubuntu, Windows, and macOS with esptool integration tests.

Requirements

Component Version
Python 3.9 or newer
esptool Installed automatically with this package (runtime dependency)
Upload client e.g. pip esptool, or arduino-cli with a client that supports your transport

Optional:

  • com0com on Windows — for real COMx ports in local testing (setup guide).

Installation

From PyPI:

pip install esp32-mock-bootloader

Pin a release:

pip install esp32-mock-bootloader==0.1.0

From source (development):

git clone https://github.com/lucasssvaz/esp32-mock-bootloader.git
cd esp32-mock-bootloader
pip install -e ".[dev]"

Quick start

# 1. Start the mock (background daemon on port 9876)
esp32-mock-bootloader start

# 2. Flash through it
esptool --chip esp32 \
  --port "$(esp32-mock-bootloader url)" \
  write-flash 0x10000 firmware.bin

# 3. Stop when done
esp32-mock-bootloader stop

The daemon keeps running between steps 1 and 3. Use status to inspect it, url for the full socket:// address, or port for the port number alone.

Usage

CLI commands

Command Description
start Start the background daemon and exit once the port is ready
stop Stops the only running instance by default; --port PORT for one; --port all for every instance
status Auto-picks the only running instance; lists all when several are running; --port PORT or --port all
url Prints one socket:// URL (auto-pick); tab-separated list when several; --port PORT or --port all
port Prints one TCP port (auto-pick); one per line when several; --port PORT or --port all
erase-flash Erases the only running instance by default; --port PORT or --port all
chips List SoCs supported by the installed esptool
run Run the server in the foreground (used internally; prefer start)

Common flags for start and run:

Flag Default Description
--chip auto Chip profile, or auto to detect from client traffic
--port 9876 TCP listen port (run / start); with run --pty in null-modem mode, the upload client serial port (e.g. COM19) — same port you pass to esptool. Falls back to OS-assigned port if taken.
--serial-bind run --pty only: mock-side port in a null-modem pair; auto-detected from com0com when only --port is set
--pty off run only: serial path mode (Unix PTY, null-modem COM, or Windows socket fallback)
--bind 127.0.0.1 Bind address
--startup-timeout 30 Seconds start waits for the port (start only)
--force off Stop an existing daemon on the same port first (start only)
--exit-on-disconnect off Exit after the first client disconnects on any transport (run; test TCP helpers enable this)
--timeout none Exit after N seconds (run only)

status --json adds machine-readable output. With several running instances (or --port all), JSON is {"instances": [...]}; otherwise a single status object.

status, url, port, erase-flash, and stop share --port semantics: omit it to auto-pick (one instance behaves like before; several are listed or all stopped/erased), pass a number for one instance, or pass all to always target every running instance.

When no daemon is running and you omit --port, query commands fall back to port 9876 (the start default) so $(esp32-mock-bootloader url) keeps working in single-daemon CI scripts.

Daemon lifecycle

Runtime files live under the OS temp directory ($TMPDIR/esp32-mock-bootloader/ on macOS/Linux, %TEMP%\esp32-mock-bootloader\ on Windows). A single registry.json tracks every running daemon and foreground run process:

{
  "version": 1,
  "instances": {
    "9876": {
      "pid": 12345,
      "port": 9876,
      "chip": "esp32",
      "bind": "127.0.0.1",
      "url": "socket://127.0.0.1:9876",
      "log_file": "/tmp/esp32-mock-bootloader/port-9876.log",
      "detected_chip": "esp32",
      "mode": "daemon"
    }
  }
}

Per-port logs are written alongside the registry. Stale entries are pruned automatically when a process is no longer running. detected_chip is filled after a client identifies the SoC (in auto mode).

Chip selection

esptool’s --chip flag is client-side only — it is never sent over the serial link. The mock cannot read it. Choose the mock profile to match how your upload client selects the SoC:

Mock --chip Client Connect fidelity
esp32, esp32c3, … esptool --chip <same> Full ROM profile (MAC, crystal, security-info) — recommended for CI
esp32, esp32c3, … esptool --chip auto esptool autodetects from ROM probes against the fixed mock profile
auto esptool --chip auto Mock learns the SoC from esptool’s standard detection probes
auto esptool --chip <explicit> Upload usually works; connect may warn until chip-specific registers are read

Recommended CI pattern — one virtual board per job, matching chips:

esp32-mock-bootloader start --chip esp32c3
esptool --chip esp32c3 --port "$(esp32-mock-bootloader url)" flash-id

Or let esptool autodetect against a fixed mock profile:

esp32-mock-bootloader start --chip esp32c3
esptool --chip auto --port "$(esp32-mock-bootloader url)" flash-id

Use mock --chip auto when the client also uses --chip auto, or when you need the mock to learn the SoC from chip-specific register traffic (for example multi-SoC protocol tests).

In auto mode the mock:

  1. Returns a ROM-style error on GET_SECURITY_INFO until a SoC is known.
  2. Returns 0 for the legacy probe at 0x40001000 until chip-specific registers identify the SoC.
  3. Sets detected_chip from unique detect registers or efuse windows (addresses from esptool ROM classes).

ROM profile: For explicit chip modes (and after auto detection), READ_REG returns a sparse set of register values derived from esptool ROM classes — synthetic MAC, crystal calibration (UART_CLKDIV, ESP32 RTCCALICFG1), and security-off defaults. This is not a full efuse block emulator; see espefuse.

Synthetic MAC

esptool's flash-id prints a MAC decoded from efuse/OTP registers. The mock fills those registers with a synthetic BASE_MAC so the line is non-zero and passes each chip family's read_mac() logic. No particular address is required for protocol correctness.

Property Value
OUI 24:0A:C4 (Espressif; not a real burned address on the mock)
Host suffix First 3 bytes of SHA256("<chip>"), e.g. esp32e2:95:26
Stability Same --chip always yields the same MAC across runs
Uniqueness Different SoC names get different suffixes (helps multi-chip CI logs)

Examples: esp3224:0a:c4:e2:95:26, esp32c324:0a:c4:1f:67:7d, esp826624:0a:c4:ce:2b:c1.

To compute the expected MAC in a test: registers.mac_bytes_for_chip("esp32c3") or the formula above. A single fixed MAC for all chips would also work with esptool; the per-chip suffix is a readability choice, not a hardware requirement.

Supported chips

Every target in the installed esptool CHIP_DEFS is available:

esp32-mock-bootloader chips
esp32-mock-bootloader chips --json   # detect registers and chip_id metadata

New esptool releases can add SoCs without updating this package.

Transports

TCP (default)

The daemon listens on 127.0.0.1:PORT. Pip-installed esptool accepts socket:// URLs via pyserial.

esp32-mock-bootloader start --port 9876
esptool --chip esp32c6 --port socket://127.0.0.1:9876 write-flash 0x10000 app.bin

VID/PID messages

esptool reads USB vendor/product IDs only from real USB serial devices (Espressif VID 0x303A). Virtual transports cannot provide those descriptors:

Transport Typical esptool message Fixable by mock?
socket:// Device VID/PID identification is only supported on COM and /dev/ serial ports. No — document only
Unix PTY (/dev/ttys…) Failed to get VID/PID of a device on /dev/ttys… No — PTY is not a USB device
Windows com0com (COMx) Same Failed to get VID/PID — virtual pairs have no USB descriptors No

These messages are harmless for upload tests. Only a physical Espressif USB-Serial/JTAG adapter silences them.

PTY / serial path

Use --pty with run when a tool expects a device path instead of a URL (common with arduino-cli):

esp32-mock-bootloader run --pty --chip esp32
# The PTY path is printed to stdout; use --port-file to write it to a file:
esp32-mock-bootloader run --pty --port-file /tmp/mock-pty --chip esp32
esptool --chip esp32 --port "$(cat /tmp/mock-pty)" write-flash 0x10000 firmware.bin
Platform --pty provides
Linux / macOS Real PTY device (e.g. /dev/ttys003)
Windows (local) com0com virtual COM pair
Windows (CI) socket://127.0.0.1:PORT fallback when no COM pair is configured

PTY vs COM

Both modes expose a serial device path to the client, but they create that path differently:

PTY (Unix default) Null-modem serial (--port / --serial-bind)
What it is Kernel pseudo-terminal pair created by the mock Two ends of an existing serial device (virtual or physical)
Server I/O Master side of the PTY (PtyMasterTransport) pyserial on --serial-bind
Client path Slave device (e.g. /dev/ttys003) --port (e.g. COM19); written to --port-file
Typical platform Linux / macOS Windows (com0com); Linux with socat loopback
Needs extra software No Yes on Windows (com0com); paired ports on Linux

Null-modem mode is not Windows-only. Any path pyserial can open works on any OS. Unix CI defaults to PTY because the kernel provides a free pair with no setup.

On Windows with com0com you usually pass only --port COM19 (the upload port). The mock looks up the paired port via setupc and binds there automatically. Pass --serial-bind COM18 as well when you want to name both ends explicitly.

Legacy env vars ESP32_MOCK_COM_PORT / ESP32_MOCK_COM_PEER still work; prefer ESP32_MOCK_SERIAL_BIND and ESP32_MOCK_PORT.

Windows with com0com

Install com0com. The mock binds the paired port; --port is what esptool uses (written to the path file):

esp32-mock-bootloader run --pty --port-file mock.port ^
  --port COM19 --chip esp32
esptool --chip esp32 --port COM19 write-flash 0x10000 firmware.bin

To name both ends explicitly:

esp32-mock-bootloader run --pty --port-file mock.port ^
  --serial-bind COM18 --port COM19 --chip esp32

Expect Failed to get VID/PID on com0com ports (see VID/PID messages).

Environment variables ESP32_MOCK_SERIAL_BIND and ESP32_MOCK_PORT work the same as --serial-bind / --port. Legacy ESP32_MOCK_COM_PORT / ESP32_MOCK_COM_PEER are still read.

Helper script (elevated prompt; creates a pair, runs esptool, removes the pair):

python scripts\test_windows_com.py

Set ESP32_MOCK_KEEP_COM_PAIR=1 to leave the pair installed after the script exits.

GitHub Actions

The action starts the daemon in the main step and stops it in a post step when the job ends — including on failure. No manual stop required.

jobs:
  upload-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Mock bootloader
        uses: lucasssvaz/esp32-mock-bootloader@v0.1.0
        id: mock

      - name: Flash test firmware
        run: |
          pip install esptool
          esptool --chip esp32 \
            --port "${{ steps.mock.outputs.url }}" \
            --no-stub --before no-reset --after no-reset \
            write-flash 0x10000 firmware.bin

Outputs: url (socket://…), port.

Inputs (all optional):

Input Default Description
chip auto Chip profile
port 9876 TCP port
startup-timeout 30 Startup wait in seconds
python-version 3.x Python for setup-python
version (empty) PyPI pin (e.g. 0.1.0); omit to install from the action checkout

Manual CLI in a workflow (without the action):

- run: pip install esp32-mock-bootloader esptool
- run: esp32-mock-bootloader start
- run: |
    esptool --chip esp32 --port "$(esp32-mock-bootloader url)" \
      write-flash 0x10000 firmware.bin
- run: esp32-mock-bootloader stop
  if: always()

Client examples

esptool (TCP, no stub — typical for CI):

esptool --chip esp32c6 \
  --port socket://127.0.0.1:9876 \
  --no-stub --before no-reset --after no-reset \
  write-flash 0x10000 app.bin

arduino-cli:

arduino-cli compile -b esp32:esp32:esp32 \
  -p socket://127.0.0.1:9876 --upload sketch.ino

Shell CI script:

esp32-mock-bootloader start
PORT="$(esp32-mock-bootloader url)"
# run your upload tests against "$PORT"
esp32-mock-bootloader stop

Python API

The public surface is intentionally small: start a mock, read its endpoint, optionally query or stop running instances. Upload clients (esptool, arduino-cli) connect to handle.url() — the mock never runs uploads for you.

Exports: mock_bootloader, MockHandle, instances, __version__

Style A — context manager (single instance, auto cleanup)

import subprocess
from esp32_mock_bootloader import mock_bootloader

with mock_bootloader(chip="esp32") as mock:
    subprocess.run(["esptool", "--chip", "esp32", "--port", mock.url(), "write-flash", ...])

Style B — imperative (multiple instances)

from esp32_mock_bootloader import mock_bootloader

server_a = mock_bootloader(chip="esp32")
server_b = mock_bootloader(chip="esp32c3")
try:
    # point esptool / arduino-cli at server_a.url() and server_b.url()
    ...
finally:
    server_b.stop()
    server_a.stop()

instances — CLI-parity operations

Same verbs as esp32-mock-bootloader status|url|port|stop|erase-flash:

from esp32_mock_bootloader import mock_bootloader, instances

server_a = mock_bootloader(chip="esp32")
server_b = mock_bootloader(chip="esp32c3")

print(instances.status(format="text"))   # table of all running mocks
rows = instances.status()              # list[dict] when multiple
urls = instances.url(port="all")
instances.erase_flash(port=server_a.port())
instances.stop(port="all")

Each MockHandle exposes the same verbs scoped to that server: server_a.url(), server_a.status(), server_a.erase_flash(), server_a.stop(), etc.

Default mode for mock_bootloader() is foreground (subprocess server; stops when the handle is destroyed or the with block ends). Use mode="daemon" for a background daemon like esp32-mock-bootloader start.

Advanced (opt-in)

Protocol testing and raw transports live under esp32_mock_bootloader.advanced:

from esp32_mock_bootloader import mock_bootloader
from esp32_mock_bootloader.advanced import protocol

server = mock_bootloader(chip="esp32")
try:
    client = protocol.connect(server)
    client.send_command(cmd, data)
finally:
    server.stop()

Also exported: transport, process, protocol_client, constants. Chip metadata: from esp32_mock_bootloader import chips.

Runnable scripts with comments live under examples/ (basic upload patterns and esp32_mock_bootloader.advanced protocol examples).

Reference constants (FLASH_APP_OFFSET, SYNC_PAYLOAD, …) live in esp32_mock_bootloader.constants.

How it works

The mock speaks SLIP-framed ROM commands. Implemented handlers include:

SYNC, FLASH_BEGIN / DATA / END, FLASH_DEFL_*, MEM_* (+ OHAI after MEM_END), READ_REG, WRITE_REG (with mask, readable via READ_REG), GET_SECURITY_INFO, SPI_SET_PARAMS, SPI_ATTACH, CHANGE_BAUDRATE, SPI_FLASH_MD5, READ_FLASH_SLOW (ROM), and stub-only ERASE_FLASH, ERASE_REGION, READ_FLASH (streaming + MD5), RUN_USER_CODE. FLASH_DATA, MEM_DATA, and FLASH_DEFL_DATA validate the 0xEF XOR checksum (ROM error 0x07, stub error 0xC1). Unknown commands return ROM error 0x05 or stub error 0xFF.

Flash data is stored in an in-memory image (erased bytes default to 0xFF). After a stub upload (MEM_END with entrypoint + OHAI), SPI_FLASH_MD5 returns a 16-byte binary digest; in ROM mode it returns 32-byte lowercase hex ASCII — matching esptool’s flash_md5sum() expectations. Unknown commands receive a generic ACK.

The mock validates protocol behavior, not silicon accuracy. It does not model Wi-Fi, sleep, brownout, or real flash timing.

Protocol references

Implementation follows Espressif’s published bootloader protocol and the esptool reference client. Primary sources:

Topic Reference
Serial protocol overview esptool serial protocol (ESP32) — same command set is documented per chip under esptool/en/latest/<chip>/advanced-topics/serial-protocol.html
Flash upload & MD5 verify Verifying uploaded data — ROM returns 32 hex ASCII bytes; stub returns 16 raw MD5 bytes before the 2 status bytes
SPI_FLASH_MD5 (0x13) Commands table
Stub upload & OHAI Functional description — initializationMEM_END entrypoint, unsolicited OHAI SLIP packet
Client MD5 handling esptool loader.pyflash_md5sumRESP_DATA_LEN 32 (ROM) vs RESP_DATA_LEN_STUB 16 (stub); status bytes follow the digest
Stub lifecycle esptool loader.pyrun_stub — RAM download via MEM_*, then mem_finish(entry)
Chip profiles & detection esptool targets / CHIP_DEFS — register addresses, magic values, security info

Integration tests in downstream projects (e.g. arduino-esp32 upload tests) exercise the default stub path (flasher.py / esptool without --no-stub). CI examples in this repo that pass --no-stub target the ROM MD5 format explicitly.

Limitations

  • Protocol emulator only — no application code runs on the mock.
  • Client packaging matters — some bundled esptool builds lack socket://; use PTY/COM or pip esptool.
  • com0com is local — GitHub-hosted Windows runners use the socket fallback automatically.
  • VID/PID noise on virtual ports — socket, PTY, and com0com cannot expose Espressif USB descriptors; esptool prints informational VID/PID messages (see Transports).
  • Alpha API — expect changes before 1.0.0.

espefuse

This mock targets esptool ROM upload clients (write-flash, flash-id, stub upload). It is not an efuse programmer.

Tool / mode Use with mock?
espefuse --virt Yes — in-process efuse emulation for host-side tests (no serial port)
espefuse --port … read/burn commands No — requires on-chip efuse controller WRITE_REG sequences and persistent burned state

After connect, the mock exposes a sparse ROM profile (MAC, crystal registers, security-off defaults) so esptool connect paths behave plausibly. That is not espefuse field parity:

espefuse command Mock support
summary Partial / best-effort only — most named fields stay at defaults
dump, adc-info, check-error No — unmapped addresses read as 0
burn-*, read-protect-efuse, write-protect-efuse No — permanently out of scope unless a full efuse controller emulator is added

Supported chips follow installed esptool CHIP_DEFS. espefuse supported chips are a subset (no ESP8266).

Development

See CONTRIBUTING.md for pull request guidelines and AI disclosure expectations.

git clone https://github.com/lucasssvaz/esp32-mock-bootloader.git
cd esp32-mock-bootloader
pip install -e ".[dev]"

Run tests:

pytest                                      # parallel by default (pytest-xdist)
pytest -n0                                  # single process (debugging)
pytest -m "not esptool"                     # protocol unit tests only
pytest -m "not transport"                     # skip TCP/PTY integration
pytest tests/test_protocol.py -m transport  # TCP transport smoke only
pytest tests/test_process.py                # process.py + transport.py (advanced)
pytest tests/test_com0com.py                # com0com unit tests

CI runs the full suite on Ubuntu, Windows, and macOS (parallel via pytest-xdist), enforces coverage baselines on Ubuntu, and verifies parallel subprocess coverage with scripts/verify_parallel_coverage.py.

com0com testing tiers:

  1. CI (all OS)tests/test_com0com.py uses tests/fixtures/fake_setupc.py (no driver install).
  2. Windows CItest_windows_com0com_esptool skips when setupc is missing or not elevated.
  3. Local Windows — install com0com, run elevated: pytest -m com0com or scripts/test_windows_com.py.
pytest -m com0com    # optional real com0com integration (Windows + admin)

Coverage (parallel + subprocess children; see reports/README.md):

pytest -n auto --cov-config=pyproject.toml \
  --cov=esp32_mock_bootloader \
  --cov-report=term-missing \
  --cov-report=html:reports/htmlcov \
  --cov-report=xml:reports/coverage.xml
python scripts/verify_parallel_coverage.py
python scripts/check_coverage.py

Build a release wheel:

hatch build

Project layout

esp32-mock-bootloader/
├── CONTRIBUTING.md              # PR guidelines and AI policy
├── src/esp32_mock_bootloader/   # Python package (api, CLI, daemon, SLIP server)
│   ├── registry.py              # Registry (multi-instance coordination)
│   ├── session.py               # Internal Session / SessionGroup lifecycle
│   ├── client.py                # Protocol Client (bound to Session)
│   ├── api.py                   # mock_bootloader() and MockHandle
│   ├── instances.py             # CLI-parity status/url/port/stop/erase_flash
│   ├── advanced/                # Opt-in protocol.connect, transport, process, constants
│   ├── constants.py             # Protocol/layout reference values
│   ├── transport.py             # TCP / PTY / serial client connections
│   ├── process.py               # Subprocess spawn and teardown helpers
│   ├── protocol_client.py       # SLIP client helpers
│   ├── chips.py                 # Chip profiles from esptool
│   ├── registers.py             # Sparse ROM register profile for esptool fidelity
│   ├── protocol.py              # Protocol constants
│   └── server.py                # SLIP server (advanced)
├── examples/                    # Runnable basic + advanced usage examples
├── action/                      # Node.js steps for the GitHub Action
├── action.yml                   # Composite action entry point
├── tests/                       # pytest suite (protocol, esptool, transports)
├── scripts/                     # Coverage checker, Windows COM helper
├── reports/                     # Coverage config and baselines
└── .github/workflows/           # CI and release pipelines

AI disclosure

This repository was developed with help from AI coding assistants. Every change is reviewed and tested by a human maintainer before merge or release.

Contributor expectations (disclosure, review, commit trailers) are in CONTRIBUTING.md.

License

Copyright 2026 Lucas Saavedra Vaz. Released under the Apache-2.0 license.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

esp32_mock_bootloader-0.3.0.tar.gz (55.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

esp32_mock_bootloader-0.3.0-py3-none-any.whl (57.4 kB view details)

Uploaded Python 3

File details

Details for the file esp32_mock_bootloader-0.3.0.tar.gz.

File metadata

  • Download URL: esp32_mock_bootloader-0.3.0.tar.gz
  • Upload date:
  • Size: 55.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for esp32_mock_bootloader-0.3.0.tar.gz
Algorithm Hash digest
SHA256 bdc343366fb2fe8173423efea53176e3d4868146730b8ada7a28c78837ad82df
MD5 d106d0e8ada73df3ee0ce2a2764b997d
BLAKE2b-256 fcd92a9b1c4976c44a85b46ccdd25adf0d3693b9310f532d26d6df338d8ab6aa

See more details on using hashes here.

Provenance

The following attestation bundles were made for esp32_mock_bootloader-0.3.0.tar.gz:

Publisher: release.yml on lucasssvaz/esp32-mock-bootloader

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file esp32_mock_bootloader-0.3.0-py3-none-any.whl.

File metadata

File hashes

Hashes for esp32_mock_bootloader-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 856e01bbc0c3ecd009cb08bb1ef5b19e6f656e3eede82f320dcc120600822270
MD5 66e8cd67f05fcc4ac39b3ba7f73c3efe
BLAKE2b-256 29ad28c90308842445fd209297a91dbd8aa3027ec52e541710ec30a5ed5234da

See more details on using hashes here.

Provenance

The following attestation bundles were made for esp32_mock_bootloader-0.3.0-py3-none-any.whl:

Publisher: release.yml on lucasssvaz/esp32-mock-bootloader

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page