Skip to content

pibot_pendant_v1_0_factory_app_tools

The pibot_pendant_v1_0_factory_app_tools module is a collection of host-side Python utilities that support the factory programming, testing, and development workflow for the PiBot CNC Pendant v1.0. These tools run on a development PC, not on the ESP32 target itself.

The module contains four independent scripts located in boards/pibot_pendant_v1_0/Factory/tools/:

ScriptPurpose
flash_all.pyMulti-mode flashing: recovery, full, or firmware-only
flash_factory.pyTargeted factory partition flash
generate_font/generate_font.pyGenerate bitmap font C/H files for the factory app display
raw2png/snap2png.pyConvert raw RGB565 display snapshots to PNG images

These tools are the operational counterpart to the embedded factory application documented in pibot_pendant_v1_0_factory_app_main.md. Similar tool sets exist for other supported boards (see sibling modules esp32s3_8048s070c_factory_app, esp32s3_bzm_tft35_gt911_factory_app, etc.).


pibot_pendant_v1_0_factory_app (parent)

flashes

generates fonts for

decodes snapshots from

pibot_pendant_v1_0_factory_app_main

(main.c - menu, OTA, SD update)

pibot_pendant_v1_0_factory_app_display

(gfx.c, ili9341.c)

pibot_pendant_v1_0_factory_app_input

(buttons.c, encoder.c, touch.c, buzzer.c)

pibot_pendant_v1_0_factory_app_storage

(sdcard.c)

pibot_pendant_v1_0_factory_app_tools

(host-side Python tools)

ESP32 Target

(PiBot Pendant v1.0)

The tools module is decoupled from the embedded code at runtime — it operates only at build-time and during factory/development workflows on the host machine.


Generated Outputs

Build Artifacts / Inputs

Python Dependencies

Host PC (Python 3)

flash_all.py

─────────────

recovery / full / firmware

flash_factory.py

─────────────

factory partition only

generate_font.py

─────────────

bitmap font generator

snap2png.py

─────────────

RGB565 → PNG converter

esptool

(pip install esptool)

Pillow / PIL

(pip install pillow)

pyserial

(serial.tools.list_ports)

partitions*.csv

installer/bootloader.bin

installer/partitions.bin

installer/factory.bin

firmware.bin (app0)

*.ttf (system or custom font)

.raw snapshot files (from SD card)

.png image files

fontWxH.h

fontWxH.c


Tool 1: flash_all.py — Multi-Mode Flash Script

Section titled “Tool 1: flash_all.py — Multi-Mode Flash Script”

Provides a single entry point for all flashing scenarios during factory setup and firmware development. It wraps esptool with mode-aware logic, automatic port detection, and partition-table-aware offset resolution.

--recovery

--full --fw-file fw.bin

--fw fw.bin

Start

Parse Arguments

Mode?

Flash:

• bootloader @ 0x1000

• partitions @ 0xC000

• factory @ auto-offset

Flash:

• bootloader @ 0x1000

• partitions @ 0xC000

• factory @ auto-offset

• firmware @ 0x20000

Flash:

• firmware @ 0x20000 only

Auto-detect or use --port

Validate all files exist

esptool write_flash

Done

RegionOffsetFile
Bootloader0x1000installer/bootloader.bin
Partition table0xC000installer/partitions.bin
OTA data0x10000(not directly flashed by this tool)
app0 (main firmware)0x20000user-supplied fw.bin
Factory appauto-detectedinstaller/factory.bin (typically 0x780000 for the 8 MB layout since a14)

The factory partition offset is not hardcoded. find_factory_offset() reads the first matching partitions*.csv in the project tree and extracts the offset from the row where the subtype column equals factory. The default fallback is 0x780000 (8 MB layout; the scripts said 0x7A0000 before a14). The reference comments in flash_all.py give FLASHFS_OFFSET 0x660000 (8 MB) / 0x2E0000 (4 MB) and FACTORY_OFFSET 0x780000 (8 MB).

find_esp32_port() iterates serial.tools.list_ports.comports() and matches description strings against known USB-UART chip keywords: CP210, CH340, CH910, FTDI, USB Serial, USB-SERIAL. If no keyword matches, it falls back to the first available port.

Terminal window
# First-time factory setup (bootloader + partitions + factory only)
python flash_all.py --recovery
# Complete flash: all partitions + main firmware
python flash_all.py --full --fw-file build/pibot_pendant_v1_0.bin
# Development iteration: update firmware only
python flash_all.py --fw build/pibot_pendant_v1_0.bin
# Specify port explicitly
python flash_all.py --recovery --port COM3
python flash_all.py --recovery --port /dev/ttyUSB0
# Custom baud rate
python flash_all.py --fw build/app.bin --port /dev/ttyUSB0 --baud 115200
FunctionDescription
main()Argument parsing, mode dispatch, file validation, calls flash()
find_factory_offset()Parses partitions*.csv to locate the factory partition offset
find_esp32_port()Auto-detects the ESP32 USB serial port
check_file(path, name)Validates that a required binary file exists, prints its size
flash(port, baud, files)Constructs and executes the esptool write_flash command via subprocess

Tool 2: flash_factory.py — Factory Partition Flash

Section titled “Tool 2: flash_factory.py — Factory Partition Flash”

A focused, single-task variant of flash_all.py that flashes only the factory/recovery partition. Useful when the bootloader and partition table are already present and only the factory firmware needs to be refreshed (e.g., after a factory app update without changing the partition layout).

No

Yes

Yes

No

No

Yes

Start

Parse --port, --bin, --offset, --baud

installer/factory.bin

exists?

Exit: file not found

--offset

specified?

Use manual offset

find_factory_offset()

search partitions*.csv

Found?

Exit: cannot determine offset

Hint: use --offset

Use offset from CSV

find_esp32_port() if no --port

esptool write_flash

factory.bin

Done

flash_factory.py searches for the partition CSV in a broader set of patterns than flash_all.py:

PARTITION_CSV_PATTERNS = [
"partitions*.csv",
"boards/*/partitions*.csv",
"boards/*/*/partitions*.csv",
]
Terminal window
# Auto-detect everything (port and offset)
python flash_factory.py
# Specify port only
python flash_factory.py --port /dev/ttyUSB0
# Override offset (e.g., for a 4 MB flash layout)
python flash_factory.py --offset 0x3A0000
# Custom binary path and explicit port
python flash_factory.py --bin path/to/my_factory.bin --port COM4
FunctionDescription
main()Entry point: resolves offset, port, calls esptool subprocess
find_factory_offset()Searches PARTITION_CSV_PATTERNS for the factory subtype row
find_esp32_port()USB-UART chip auto-detection (same logic as flash_all.py)

Tool 3: generate_font/generate_font.py — Bitmap Font Generator

Section titled “Tool 3: generate_font/generate_font.py — Bitmap Font Generator”

Generates C source and header files containing a rasterized bitmap font for use in the factory app’s software framebuffer renderer (gfx.c / ili9341.c — see pibot_pendant_v1_0_factory_app_display.md). The factory app does not use LVGL; it drives the ILI9341 display directly with a custom pixel renderer that consumes these bitmap glyphs.

Each character occupies a fixed cell of W × H pixels, packed as 1 bit per pixel, MSB = leftmost pixel. The glyph data is stored in row-major order:

bytes_per_row = ceil(W / 8)
bytes_per_glyph = bytes_per_row × H

Coverage: ASCII printable range 0x20 (space) through 0x7E (tilde) — 95 characters total.

Output Files

TTF font file

(system or --font)

find_best_font_size()

largest pt that fits in W×H

render_font()

Pillow ImageDraw per glyph

ASCII 0x20-0x7E

Global vertical alignment:

center all glyphs in cell

using bounding-box scan

Threshold: pixel > 127 → bit 1

Pack rows MSB-first

1 bit / pixel → bytes

generate_c_files()

Write fontWxH.c

Write fontWxH.h

fontWxH.h

──────────────

#define FONT_WIDTH W

#define FONT_HEIGHT H

#define FONT_BYTES_PER_ROW

const uint8_t *fontWxH_get_glyph(char c)

fontWxH.c

──────────────

static const uint8_t font_data[][B]

fontWxH_get_glyph() implementation

Rather than using hard-coded baseline constants, the tool scans all 95 glyphs to determine the global bounding box, then centers the entire glyph set vertically within the target cell:

max_top = min(bbox.top) across all glyphs (most negative y)
max_bottom = max(bbox.bottom) across all glyphs
total_height = max_bottom - max_top
y_offset = max(0, (target_h - total_height) / 2) - max_top

This produces consistent baselines across different TTF files and point sizes.

// fontWxH.h
#define FONT_WIDTH W
#define FONT_HEIGHT H
#define FONT_BYTES_PER_ROW ceil(W/8)
// Returns pointer to (FONT_BYTES_PER_ROW × FONT_HEIGHT) bytes of glyph bitmap.
// Characters outside [0x20, 0x7E] map to the space glyph (font_data[0]).
const uint8_t *fontWxH_get_glyph(char c);

Yes

No

None found

--font path.ttf

specified?

Validate path exists

find_monospace_font()

Search system paths

Linux:

DejaVuSansMono.ttf

LiberationMono-Regular.ttf

macOS:

Menlo.ttc, Monaco.dfont

Courier New.ttf

Windows:

consola.ttf, cour.ttf

lucon.ttf

Render with chosen font

Exit with install hint

and download URL

Terminal window
# 8×16 cell, current directory, system monospace font
python generate_font.py 8x16
# Output to factory main source directory
python generate_font.py 8x16 boards/pibot_pendant_v1_0/Factory/main
# Use a specific DOS-style font (recommended for readability)
python generate_font.py 8x16 . --font "Perfect DOS VGA 437.ttf"
# Smaller font for dense info display
python generate_font.py 6x12 ./main
# Larger font with explicit path
python generate_font.py 10x20 ./main --font /path/to/MyFont.ttf
Cell sizeBytes/glyphTotal (95 chars)
6×1212~1.1 KB
8×1616~1.5 KB
10×2030~2.8 KB
16×3264~6.1 KB
FunctionDescription
main()CLI parsing, orchestrates the pipeline
find_monospace_font()OS-aware search for a suitable TTF font
find_best_font_size(font_path, w, h)Finds the largest point size whose rendered glyphs fit within W×H
render_font(font_path, w, h)Renders all 95 glyphs with consistent vertical alignment using Pillow
generate_c_files(glyphs, w, h, output_dir)Writes .c and .h output files with the bitmap data

Tool 4: raw2png/snap2png.py — Snapshot Converter

Section titled “Tool 4: raw2png/snap2png.py — Snapshot Converter”

Converts raw display snapshot files (.raw) captured by the factory app into viewable .png images. The factory app’s gfx_snapshot_begin() / gfx_snapshot_end() / snap_write() functions (see pibot_pendant_v1_0_factory_app_display.md) write framebuffer contents to the SD card in a compact binary format. This tool decodes those files on the host for inspection and debugging.

Offset Size Type Field
────────────────────────────────────────────
0x00 4 bytes uint32_le Image width (pixels)
0x04 4 bytes uint32_le Image height (pixels)
0x08 W×H×2 uint16_le[] RGB565 pixels, row-major order

Total file size: 8 + width × height × 2 bytes.

Each 16-bit RGB565 pixel is expanded to 24-bit RGB888 using bit-replication (not zero-padding) to preserve luminance accuracy across the full value range:

r = (r5 << 3) | (r5 >> 2) # 5-bit → 8-bit (0x1F → 0xFF)
g = (g6 << 2) | (g6 >> 4) # 6-bit → 8-bit (0x3F → 0xFF)
b = (b5 << 3) | (b5 >> 2) # 5-bit → 8-bit (0x1F → 0xFF)

.raw file

(SD card output)

Read uint32_le width

Read uint32_le height

Read W × H × 2 bytes

(RGB565 pixel data)

For each pixel:

rgb565_to_rgb888()

Build PIL Image RGB

W × H pixels

.png output

(same stem or -o override)

Terminal window
# Single file — output: snap000.png
python snap2png.py snap000.raw
# Batch conversion of all snapshots from the SD card
python snap2png.py snap*.raw
# Custom output filename (single input only)
python snap2png.py snap000.raw -o before_update.png
# Convert from a mounted SD card path
python snap2png.py /media/sdcard/snap*.raw
FunctionDescription
main()Argument parsing, iterates files, dispatches convert_raw_to_png()
convert_raw_to_png(raw_path, output_path)Reads header, decodes pixel data, saves PNG via Pillow
rgb565_to_rgb888(pixel)Bit-replication color depth expansion for a single RGB565 pixel

Tool Relationships and Workflow Integration

Section titled “Tool Relationships and Workflow Integration”
SD CardESP32 Targetsnap2png.pygenerate_font.pyflash_factory.pyflash_all.pyDeveloper / Factory LineSD CardESP32 Targetsnap2png.pygenerate_font.pyflash_factory.pyflash_all.pyDeveloper / Factory LineInitial Board Setup (factory line)Firmware Development CycleFactory App Update OnlyDisplay Font Preparation (build-time)Rebuild factory app with new font → factory.binDebug / Screenshot Workflowpython flash_all.py --recoveryesptool: bootloader (0x1000) + partitions (0xC000) + factory (auto-offset)python flash_all.py --fw firmware.binesptool: firmware → app0 @ 0x20000python flash_factory.pyesptool: factory.bin → CSV-detected offsetpython generate_font.py 8x16 ./mainfont8x16.c + font8x16.hFactory app writes snap000.raw, snap001.raw...python snap2png.py snap*.rawsnap000.png, snap001.png (viewable screenshots)
DependencyRequired ByInstall
Python ≥ 3.8All toolsSystem Python or virtual environment
esptoolflash_all.py, flash_factory.pypip install esptool
pyserialflash_all.py, flash_factory.pypip install pyserial
Pillowgenerate_font.py, snap2png.pypip install pillow
A TTF monospace fontgenerate_font.pySystem-provided or manual download

Recommended font for generate_font.py: Perfect DOS VGA 437 by Zeh Fernando (free, full CP437 coverage).
Download: https://www.dafont.com/perfect-dos-vga-437.font
Best results at multiples of 8 px height: 8x16, 16x32.


The flash scripts expect pre-built binaries in an installer/ subdirectory relative to the script location:

boards/pibot_pendant_v1_0/Factory/tools/
├── flash_all.py
├── flash_factory.py
├── installer/
│ ├── bootloader.bin ← custom bootloader (see pibot_pendant_v1_0_bootloader.md)
│ ├── partitions.bin ← compiled partition table binary
│ └── factory.bin ← compiled factory app binary
├── generate_font/
│ └── generate_font.py
└── raw2png/
└── snap2png.py

The installer/ binaries are build outputs:

  • bootloader.bin and factory.bin — produced by idf.py build inside boards/pibot_pendant_v1_0/Factory/
  • partitions.bin — produced by gen_esp32part.py from the project’s partitions*.csv
  • Main firmware binary — built separately from the root project via idf.py build

DocumentRelationship
pibot_pendant_v1_0_factory_app_main.mdEmbedded factory app that runs on the target; these tools flash and support it
pibot_pendant_v1_0_factory_app_display.mdgfx.c / ili9341.c — consumes fonts from generate_font.py; produces .raw snapshots decoded by snap2png.py
pibot_pendant_v1_0_bootloader.mdCustom bootloader binary flashed by flash_all.py --recovery
Factory/factory_app_technical_doc.mdIn-depth technical reference for the entire factory app system
guides/tools.mdProject-wide host tools guide (WebSocket test, bridge, simulator, build scripts)
guides/board_build_guidelines.mdBuild system context: partition table layout, binary paths, variant system