Template
A repository with .gitea/scripts/jlcpcb-files.py (harNES) gets <board>-<tag>-jlcpcb-bom.csv and -jlcpcb-cpl.csv (rotations corrected per LCSC part) in release/: the script runs with --force on a copy of hardware/ without the committed jlcpcb/ folders, so nothing stale is taken. When it refuses a board (DRC, missing correction), release/README.md and a workflow warning say why; the generic -cpl.csv then is the only placement file.
271 lines
12 KiB
Markdown
271 lines
12 KiB
Markdown
# B4L product repository template
|
|
|
|
Every B4L product repository has the same layout, so people and proplan find
|
|
the product image, the boards, the firmware and the libraries in the same
|
|
place. This file explains the layout, the rules and `proplan.toml`. It is
|
|
copied into every repository created from the template; keep it as the
|
|
reference.
|
|
|
|
## Create a product repository
|
|
|
|
In Gitea: **New Repository**, choose the template **B4L/product-template**,
|
|
tick **Git Content**, give the repository the product's name and a
|
|
description. Gitea puts the name into `README.md`, `CHANGELOG.md`,
|
|
`proplan.toml` and the board folder under `hardware/` (the files listed in
|
|
`.gitea/template`). Then:
|
|
|
|
1. Replace `assets/product.png` with a photo or render of the product (PNG,
|
|
JPEG or WebP, at most 5 MB).
|
|
2. Create the KiCad project in the board folder (KiCad: File, New Project; the
|
|
project file is `hardware/<name>/<name>.kicad_pro`).
|
|
3. Commit and push. The KiBot workflow checks the board on every push.
|
|
4. Tag the first prototype `v0.1` when its boards are ordered, and link the
|
|
tag to the stage on the project's Prototypes tab in proplan. CI then puts
|
|
the stage's manufacturing files and documentation into `release/`.
|
|
|
|
## Layout
|
|
|
|
| Path | What goes there |
|
|
|---|---|
|
|
| `proplan.toml` | the manifest proplan reads (below) |
|
|
| `README.md`, `CHANGELOG.md` | what the product is; one changelog section per stage (`## v0.2`) |
|
|
| `release/` | the newest stage's manufacturing files and documentation, written by CI (below) |
|
|
| `assets/product.png` | the product image |
|
|
| `hardware/<board>/` | one KiCad project per board |
|
|
| `hardware/lib/` | symbols, footprints and 3D models shared by the boards |
|
|
| `firmware/<target>/` | MCU code (STM32CubeIDE or CMake), `firmware/lib/` for shared drivers |
|
|
| `external/` | third-party code as git submodules (e.g. ST's USB-PD middleware) |
|
|
| `software/<tool>/` | host tools and apps |
|
|
| `mech/` | FreeCAD sources and STEP exports |
|
|
| `sim/` | LTspice/ngspice, calculation scripts and notebooks |
|
|
| `test/<stage>/` | bench results per stage (`test/v0.1/`) |
|
|
| `docs/`, `docs/datasheets/`, `docs/tex/` | documentation, vendor PDFs, LaTeX sources |
|
|
| `.gitea/workflows/` | KiBot, release outputs, fabrication document |
|
|
|
|
## Rules
|
|
|
|
- A board revision is a tag (`v0.1`, `v0.2`), never a copied folder
|
|
(`kicad_v1_5/` next to `kicad_v1_6/`). A real variant built side by side
|
|
(48 V and 24 V, 2-board and 3-board) is its own folder under `hardware/` and
|
|
its own `[[boards]]` entry.
|
|
- Generated output (gerbers, BOMs, production zips, PDFs, plots) is built by CI
|
|
and attached to the release; it is not committed. The one exception is
|
|
`release/`, which CI fills at a stage tag: nothing goes there by hand, and
|
|
generated files go nowhere else.
|
|
- Vendor datasheets go to `docs/datasheets/`, not the repository root.
|
|
- Third-party code is a git submodule in `external/` and is not edited here.
|
|
- IDE build output and tool caches (`Debug/`, `__pycache__/`, `.claude/`,
|
|
KiCad backups) stay out; `.gitignore` covers them.
|
|
|
|
## Versioning
|
|
|
|
One repository, three kinds of tags. The prefix of a firmware or software
|
|
tag is the folder it versions, so every target and every tool has its own
|
|
releases, and the board release workflow (`v*`) never reacts to them.
|
|
|
|
| What | Tag | Example | Scheme |
|
|
|---|---|---|---|
|
|
| Prototype stage (the whole product) | `vMAJOR.MINOR` | `v0.1`, `v0.2` | the next stage takes the next number; the tag proplan links to a stage and CI attaches the board outputs to |
|
|
| Firmware target `firmware/<target>/` | `firmware/<target>/vMAJOR.MINOR.PATCH` | `firmware/main-mcu/v1.2.0`, `firmware/main-mcu/v1.3.0-rc.1` | SemVer 2.0 (semver.org) |
|
|
| Software tool `software/<tool>/` | `software/<tool>/YYYY.MM.MICRO` | `software/diag/2026.10.0`, `software/diag/2026.10.1` | CalVer (calver.org) |
|
|
|
|
Firmware (SemVer):
|
|
|
|
- MAJOR when something outside the firmware has to change with it: CAN IDs
|
|
or message layout, the protocol the host tools speak, the format of
|
|
settings stored in flash, the bootloader interface. MINOR for new features
|
|
that keep all of that, PATCH for fixes.
|
|
- `0.y.z` while prototyping (anything may change), `1.0.0` with the first
|
|
production firmware. Bench builds before a release are pre-releases
|
|
(`v1.3.0-rc.1`, `-rc.2`).
|
|
- The version is also where the build reads it (CMake
|
|
`project(<name> VERSION 1.2.0)` or a `version.h`), and the firmware reports
|
|
it at run time (CAN, USB or UART), so a board on the bench says what it runs.
|
|
|
|
Software (CalVer `YYYY.MM.MICRO`):
|
|
|
|
- Year, month without a leading zero (calver.org's `MM`), and MICRO counting
|
|
the releases of that month from 0: `2026.10.0`, `2026.10.1`, `2026.11.0`.
|
|
Without the zero the number is also a valid version for pip
|
|
(`pyproject.toml`) and Cargo.
|
|
- A date, not a compatibility promise: a tool's README says which firmware
|
|
MAJOR versions it talks to.
|
|
|
|
For all three:
|
|
|
|
- The tag is the source of truth. A version written in a file
|
|
(`pyproject.toml`, `Cargo.toml`, CMake, `version.h`) matches the tag and
|
|
changes in the commit that gets tagged.
|
|
- A tag is never moved or reused; a bad release gets the next version.
|
|
proplan flags a moved tag on its stage card.
|
|
- Each firmware target and each tool keeps its own `CHANGELOG.md` in its
|
|
folder. The root `CHANGELOG.md` has one section per prototype stage and
|
|
lists the firmware and software versions that stage was built with.
|
|
- Firmware and software releases are made by hand in Gitea for now (push the
|
|
tag, then New Release); `release.yml` builds the release files for `v*` tags
|
|
only.
|
|
|
|
## Release files (`release/`)
|
|
|
|
`release/` always holds the newest prototype stage, ready to send to a
|
|
manufacturer or a customer: whoever needs the current boards or documents
|
|
takes them from there, without KiCad and without knowing the tags. CI writes
|
|
it (`.gitea/workflows/release.yml`, `.gitea/scripts/release-files.py`) when a
|
|
stage tag is pushed; nobody edits it.
|
|
|
|
| Path | What |
|
|
|---|---|
|
|
| `release/README.md` | tag, commit, date, KiCad version, ERC and DRC result per board, the file list with sizes and SHA-256, the stage's CHANGELOG section |
|
|
| `release/hardware/<board>/<board>-<tag>-gerbers.zip` | Gerbers (every copper layer, paste, silkscreen, mask, outline; Protel names, no X2) and Excellon drill files (mm, PTH and NPTH apart): what a PCB fab such as JLCPCB takes |
|
|
| `release/hardware/<board>/<board>-<tag>-bom.csv` | bill of materials grouped by value, footprint, manufacturer, MPN and LCSC; DNP parts left out |
|
|
| `release/hardware/<board>/<board>-<tag>-cpl.csv` | pick and place (KiCad position file, mm, both sides, DNP left out) |
|
|
| `release/hardware/<board>/<board>-<tag>-schematic.pdf`, `-pcb.pdf` | the schematic; the PCB with one page per copper, silkscreen and fab layer |
|
|
| `release/hardware/<board>/<board>-<tag>-top.png`, `-bottom.png` | 3D renders |
|
|
| `release/hardware/<board>/<board>-<tag>-jlcpcb-bom.csv`, `-jlcpcb-cpl.csv` | only in a repository with `.gitea/scripts/jlcpcb-files.py` (harNES): JLCPCB's BOM and a CPL with rotations corrected per LCSC part, made only from a clean DRC; use these, not `-cpl.csv`, for JLCPCB assembly |
|
|
| `release/docs/<name>-<tag>.pdf` | every `docs/*.md` except `README.md` and `input.md` (pandoc), and every `docs/tex/*.tex` with a `\documentclass` (pdflatex); the footer carries product, tag, commit and date |
|
|
|
|
- Boards are the `[[boards]]` of `proplan.toml` (or `hardware/*/*.kicad_pro`
|
|
when it names none), so a repository that adopted the layout without moving
|
|
folders gets its release files too.
|
|
- The tag is in every file name: a file sent to a fab or forwarded by mail
|
|
still says which stage it is.
|
|
- The same files, plus one zip of the whole folder, are attached to the tag's
|
|
release, so older stages stay one click away. `release/` follows the newest
|
|
stage tag only: a tag of an older stage or a pre-release (`v0.3-rc.1`) gets
|
|
its release attachments but leaves `release/` as it is.
|
|
- ERC and DRC errors do not stop the release (the tag records what was
|
|
ordered), but they are printed in `release/README.md` and as warnings on the
|
|
workflow run. Check the KiBot run before tagging.
|
|
- A document that does not build is listed in `release/README.md` as not
|
|
built; characters pdflatex cannot print (emoji) come out as `?` and are
|
|
named there.
|
|
- The commit says `[skip ci]` and `release/` is outside every workflow's
|
|
paths, so it starts no other workflow. `.gitattributes` keeps the files
|
|
byte for byte (`release/** -text`), so the SHA-256 match on Windows too.
|
|
- Locally, from the repository root, in the CI image (Docker):
|
|
`docker run --rm -v "$PWD:/w" -w /w git.b4l.co.th/b4l/kicad10_auto_node:latest python3 .gitea/scripts/release-files.py v0.1 --out build/release`
|
|
writes the same files to `build/` (ignored by git).
|
|
|
|
Not in it (yet): firmware binaries (firmware has its own tags), STEP models
|
|
(`mech/`). JLCPCB order files need `jlcpcb-files.py` and a
|
|
`jlcpcb-corrections.csv` per board (harNES has both); without them the generic
|
|
`-cpl.csv` carries KiCad's rotations, which JLCPCB's footprints may not share.
|
|
|
|
## proplan.toml
|
|
|
|
proplan reads this file and the image it names, nothing else of the
|
|
repository's content: at the default branch for the Code tab, and at a stage's
|
|
tagged commit for the stage's card (so the card shows the product as it was at
|
|
that tag). Every key is optional.
|
|
|
|
```toml
|
|
schema = 1 # 1; a newer schema is refused
|
|
|
|
[product]
|
|
name = "papir" # default: the repository's name
|
|
image = "assets/product.png" # PNG, JPEG or WebP, at most 5 MB; none when left out
|
|
|
|
[[boards]] # one per KiCad project, as many as needed
|
|
name = "papir" # default: the folder's last part
|
|
path = "hardware/papir" # required
|
|
|
|
[paths] # each default: the folder of the same name
|
|
firmware = "firmware"
|
|
software = "software"
|
|
mech = "mech"
|
|
sim = "sim"
|
|
test = "test"
|
|
docs = "docs"
|
|
external = "external"
|
|
```
|
|
|
|
Paths are relative to the repository root (no leading `/`, no `..`). A board,
|
|
a path written under `[paths]` or the image that does not exist is reported on
|
|
the Code tab ("proplan.toml: missing ..."); a path
|
|
left out that does not exist is simply not shown. Unknown keys are ignored.
|
|
|
|
## Adopting the layout in an existing repository
|
|
|
|
An existing repository does not have to move anything: a `proplan.toml` that
|
|
names the current folders is enough, and history and open work stay as they
|
|
are. Moving folders to the template's names can follow later.
|
|
|
|
papir (KiCad project in `papir/`, firmware in `code/`):
|
|
|
|
```toml
|
|
[[boards]]
|
|
name = "papir"
|
|
path = "papir"
|
|
|
|
[paths]
|
|
firmware = "code"
|
|
software = "pirnet"
|
|
mech = "mech"
|
|
docs = "docs"
|
|
```
|
|
|
|
LVSolarBuck (the current board in `kicad48V-R/LVSolarBuck`, firmware in `code64/`):
|
|
|
|
```toml
|
|
[product]
|
|
image = "housing.png"
|
|
|
|
[[boards]]
|
|
name = "LVSolarBuck"
|
|
path = "kicad48V-R/LVSolarBuck"
|
|
|
|
[paths]
|
|
firmware = "code64"
|
|
sim = "ltspice"
|
|
test = "testing"
|
|
docs = "docs"
|
|
```
|
|
|
|
PowerModule (three board variants):
|
|
|
|
```toml
|
|
[product]
|
|
image = "images/render-display.png"
|
|
|
|
[[boards]]
|
|
name = "PowerModule"
|
|
path = "kicad/PowerModule"
|
|
|
|
[[boards]]
|
|
name = "PowerModule48"
|
|
path = "kicad/PowerModule48"
|
|
|
|
[[boards]]
|
|
name = "PowerModule48PD"
|
|
path = "kicad/PowerModule48PD"
|
|
|
|
[paths]
|
|
firmware = "code"
|
|
software = "tools"
|
|
sim = "ltspice"
|
|
test = "testing"
|
|
docs = "tex"
|
|
external = "external"
|
|
```
|
|
|
|
## CI
|
|
|
|
- `.gitea/workflows/kibot.yml`: on a push that touches `hardware/`, KiBot runs
|
|
ERC and DRC and builds schematic PDF, gerbers, drill files, pick and place
|
|
and BOM for every board (`hardware/*/*.kicad_pro`); the outputs are kept as a
|
|
workflow artifact.
|
|
- `.gitea/workflows/release.yml`: on a tag `v*`, builds the release files
|
|
(above) with kicad-cli, pandoc and pdflatex, attaches them to the tag's
|
|
release (created when missing, with the stage's CHANGELOG section) and, for
|
|
the newest stage, commits them to `release/` on main. The Actions token must
|
|
be allowed to write releases and to push to main (if main is protected, its
|
|
push rules must let the Actions bot through). Firmware and software tags
|
|
(`firmware/...`, `software/...`) do not start it.
|
|
- `.gitea/workflows/docgen.yml`: with a `docs/input.md` (typecad-docgen front
|
|
matter, as in PowerModule), builds the fabrication document of the first
|
|
board and pushes it to the `docu` branch.
|
|
|
|
All three use the runner label `kicad10_auto` (as LVSolarBuck). They were
|
|
adapted from LVSolarBuck and PowerModule and run for the first time when the
|
|
first board is committed and tagged; check that first run.
|