commit 421b4202ccae819a256f053588c8e1d6a730f27f Author: grabowski Date: Tue Oct 13 16:32:25 2026 +0700 Product repository template Layout, proplan.toml, README/CHANGELOG, placeholder product image, .gitignore, KiBot/release/docgen workflows and TEMPLATE.md. Source of truth: contrib/product-template in B4L/proplan (cargo test --test product_template checks it as Gitea 1.27 creates a repository from it). diff --git a/.gitea/template b/.gitea/template new file mode 100644 index 0000000..9169111 --- /dev/null +++ b/.gitea/template @@ -0,0 +1,9 @@ +# Files Gitea fills in when a repository is created from this template: the +# new repository's name and description go into their text and their path +# (the board folder under hardware/ is renamed to the repository's name). +# Gitea 1.27: plain globs only. Everything else (workflows, TEMPLATE.md) is +# copied as it is. +proplan.toml +README.md +CHANGELOG.md +hardware/*/README.md diff --git a/.gitea/workflows/docgen.yml b/.gitea/workflows/docgen.yml new file mode 100644 index 0000000..5263f26 --- /dev/null +++ b/.gitea/workflows/docgen.yml @@ -0,0 +1,54 @@ +name: Fabrication document + +# 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, so main stays free of generated files. Adapted from +# LVSolarBuck and PowerModule. +on: + push: + branches: [main] + paths: + - 'docs/input.md' + - 'hardware/**' + - '.gitea/workflows/docgen.yml' + +jobs: + docgen: + runs-on: kicad10_auto + steps: + - uses: actions/checkout@v4 + + - name: Inputs + id: inputs + run: | + pcb=$(find hardware -mindepth 2 -maxdepth 2 -name '*.kicad_pcb' | sort | head -1) + if [ -f docs/input.md ] && [ -n "$pcb" ]; then + echo "pcb=$pcb" >> "$GITHUB_OUTPUT" + else + echo "No docs/input.md or no board yet: nothing to build." + fi + + - name: Build + if: steps.inputs.outputs.pcb != '' + run: | + mkdir -p output-docs + typecad-docgen docs/input.md "${{ steps.inputs.outputs.pcb }}" -o output-docs/index.html + + - name: Push to the docu branch + if: steps.inputs.outputs.pcb != '' + env: + GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} + run: | + git config --global user.email "${{ gitea.actor }}@noreply.localhost" + git config --global user.name "${{ gitea.actor }}" + REPO_URL=$(git config --get remote.origin.url) + AUTHED_URL=${REPO_URL/https:\/\//https:\/\/${GITEA_TOKEN}@} + git clone --depth 1 --branch docu "$AUTHED_URL" docu || git clone --depth 1 "$AUTHED_URL" docu + cd docu + git checkout -B docu + git rm -rq . || true + cp -r ../output-docs/* . + git add -A + if git diff --cached --quiet; then echo "No changes"; exit 0; fi + git commit -m "chore: fabrication document" + git push --force "$AUTHED_URL" docu diff --git a/.gitea/workflows/kibot.yml b/.gitea/workflows/kibot.yml new file mode 100644 index 0000000..2dd9401 --- /dev/null +++ b/.gitea/workflows/kibot.yml @@ -0,0 +1,48 @@ +name: KiBot + +# Checks every board (hardware/*/*.kicad_pro): ERC, DRC, schematic PDF, +# gerbers, drill, pick and place, BOM; the outputs are kept as an artifact. +# Adapted from LVSolarBuck (runner label kicad10_auto). Without a board yet +# the job ends right away. +on: + push: + branches: [main] + paths: + - 'hardware/**' + - 'kibot.yaml' + - '.gitea/workflows/kibot.yml' + pull_request: + paths: + - 'hardware/**' + - 'kibot.yaml' + workflow_dispatch: + +jobs: + boards: + runs-on: kicad10_auto + steps: + - uses: actions/checkout@v4 + + - name: Find boards + id: boards + run: | + found=$(find hardware -mindepth 2 -maxdepth 2 -name '*.kicad_pro' | sort | tr '\n' ' ') + echo "projects=$found" >> "$GITHUB_OUTPUT" + if [ -z "$found" ]; then echo "No KiCad project under hardware/ yet."; fi + + - name: KiBot + if: steps.boards.outputs.projects != '' + run: | + for pro in ${{ steps.boards.outputs.projects }}; do + dir=$(dirname "$pro"); board=$(basename "$dir") + echo "== $board" + (cd "$dir" && kibot -c "$GITHUB_WORKSPACE/kibot.yaml" -d "$GITHUB_WORKSPACE/Generated/$board" -v) \ + || echo "::warning::KiBot reported problems for $board" + done + + - name: Keep the outputs + if: steps.boards.outputs.projects != '' + uses: https://github.com/christopherHX/gitea-upload-artifact@v4 + with: + name: kibot-outputs + path: Generated diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml new file mode 100644 index 0000000..b4b4df3 --- /dev/null +++ b/.gitea/workflows/release.yml @@ -0,0 +1,57 @@ +name: Release outputs + +# On a tag v* (a prototype stage): the KiBot outputs of every board, zipped per +# board, attached to the tag's release (created when missing). The job token +# must be allowed to write releases. Adapted from LVSolarBuck; runs for the +# first time at the first tag. +on: + push: + tags: ['v*'] + +jobs: + release: + runs-on: kicad10_auto + steps: + - uses: actions/checkout@v4 + + - name: KiBot outputs per board + run: | + mkdir -p dist + for pro in $(find hardware -mindepth 2 -maxdepth 2 -name '*.kicad_pro' | sort); do + dir=$(dirname "$pro"); board=$(basename "$dir") + (cd "$dir" && kibot -c "$GITHUB_WORKSPACE/kibot.yaml" -d "$GITHUB_WORKSPACE/out/$board") \ + || echo "::warning::KiBot reported problems for $board" + python3 -c 'import shutil, sys; shutil.make_archive(sys.argv[1], "zip", sys.argv[2])' \ + "dist/$board-${GITHUB_REF_NAME}" "out/$board" + done + ls -la dist + + - name: Attach to the release + env: + TOKEN: ${{ secrets.GITEA_TOKEN }} + run: | + python3 - <<'EOF' + import json, os, pathlib, urllib.error, urllib.request, uuid + api = f"{os.environ['GITHUB_SERVER_URL']}/api/v1/repos/{os.environ['GITHUB_REPOSITORY']}" + tag = os.environ["GITHUB_REF_NAME"] + auth = {"Authorization": f"token {os.environ['TOKEN']}"} + + def call(method, url, body=None, ctype="application/json"): + req = urllib.request.Request(url, data=body, method=method, headers={**auth, "Content-Type": ctype}) + with urllib.request.urlopen(req) as r: + return json.load(r) + + try: + rel = call("GET", f"{api}/releases/tags/{tag}") + except urllib.error.HTTPError as e: + if e.code != 404: + raise + rel = call("POST", f"{api}/releases", json.dumps({"tag_name": tag, "name": tag}).encode()) + for f in sorted(pathlib.Path("dist").glob("*.zip")): + b = uuid.uuid4().hex + head = (f"--{b}\r\nContent-Disposition: form-data; name=\"attachment\"; filename=\"{f.name}\"\r\n" + "Content-Type: application/zip\r\n\r\n").encode() + body = head + f.read_bytes() + f"\r\n--{b}--\r\n".encode() + call("POST", f"{api}/releases/{rel['id']}/assets?name={f.name}", body, f"multipart/form-data; boundary={b}") + print("attached", f.name) + EOF diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..dfae03e --- /dev/null +++ b/.gitignore @@ -0,0 +1,37 @@ +# KiCad: backups, locks, local settings, caches +*-backups/ +*.kicad_prl +*.lck +*.bak +*.bck +*.kicad_pcb-bak +*.kicad_sch-bak +_autosave-* +fp-info-cache +\#auto_saved_files\# + +# FreeCAD backups +*.FCBak +*.FCStd1 + +# IDE build output and tool caches +Debug/ +Release/ +build/ +__pycache__/ +*.pyc +.venv/ +node_modules/ +.history/ +.claude/ +.vscode/ +.idea/ + +# Generated output: CI builds it and attaches it to the release +Generated/ +Fabrication/ +kibot_output_*/ + +# OS +.DS_Store +Thumbs.db diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..cef4f91 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,7 @@ +# Changelog: $REPO_NAME + +One section per prototype stage, newest first; the heading is the tag. + +## v0.1 (planned) + +- First boards. diff --git a/README.md b/README.md new file mode 100644 index 0000000..c5b40d9 --- /dev/null +++ b/README.md @@ -0,0 +1,25 @@ +# $REPO_NAME + +$REPO_DESCRIPTION + +Created from [B4L/product-template](https://git.b4l.co.th/B4L/product-template). +TEMPLATE.md explains the layout, the rules and `proplan.toml`. + +| Folder | What goes there | +|---|---| +| `assets/` | the product image (`assets/product.png`, shown by proplan), renders | +| `hardware/$REPO_NAME/` | the KiCad project of the board | +| `hardware/lib/` | symbols, footprints and 3D models shared by the boards | +| `firmware/` | MCU code, one folder per target; `firmware/lib/` for shared drivers | +| `external/` | third-party code as git submodules | +| `software/` | host tools and apps | +| `mech/` | FreeCAD sources and STEP exports | +| `sim/` | LTspice/ngspice models, calculation scripts and notebooks | +| `test/` | bench results per prototype stage (`test/v0.1/`) | +| `docs/` | documentation; `docs/datasheets/` for vendor PDFs, `docs/tex/` for LaTeX | + +## Prototype stages + +Each prototype is a git tag (`v0.1`, `v0.2`, ...). CI builds gerbers, BOM and +schematic PDFs for the tag and attaches them to its release; CHANGELOG.md says +what each stage changes. In proplan the prototype stage links the tag. diff --git a/TEMPLATE.md b/TEMPLATE.md new file mode 100644 index 0000000..86dc5e8 --- /dev/null +++ b/TEMPLATE.md @@ -0,0 +1,168 @@ +# 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//.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. + +## 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`) | +| `assets/product.png` | the product image | +| `hardware//` | one KiCad project per board | +| `hardware/lib/` | symbols, footprints and 3D models shared by the boards | +| `firmware//` | 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//` | host tools and apps | +| `mech/` | FreeCAD sources and STEP exports | +| `sim/` | LTspice/ngspice, calculation scripts and notebooks | +| `test//` | 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. +- 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. + +## 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*`, the same outputs are zipped + per board and attached to the tag's release (created when missing). The + Actions token must be allowed to write releases. +- `.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. diff --git a/assets/README.md b/assets/README.md new file mode 100644 index 0000000..70cbbae --- /dev/null +++ b/assets/README.md @@ -0,0 +1,4 @@ +# Assets + +`product.png` is the product image proplan shows (PNG, JPEG or WebP, at most +5 MB). Renders and photos for documents go here too. diff --git a/assets/product.png b/assets/product.png new file mode 100644 index 0000000..3d8934d Binary files /dev/null and b/assets/product.png differ diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..951fb52 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,5 @@ +# Documentation + +Datasheets of the parts in `docs/datasheets/`, LaTeX sources in `docs/tex/`. +A `docs/input.md` (typecad-docgen front matter) turns on the fabrication +document workflow. diff --git a/docs/datasheets/README.md b/docs/datasheets/README.md new file mode 100644 index 0000000..80d1205 --- /dev/null +++ b/docs/datasheets/README.md @@ -0,0 +1,4 @@ +# Datasheets + +Vendor datasheets and application notes (PDF), named after the part, for +example `stm32c542-datasheet.pdf`. diff --git a/docs/tex/README.md b/docs/tex/README.md new file mode 100644 index 0000000..b0dfbad --- /dev/null +++ b/docs/tex/README.md @@ -0,0 +1,3 @@ +# LaTeX sources + +Sources of the developer documentation. diff --git a/external/README.md b/external/README.md new file mode 100644 index 0000000..e0b2801 --- /dev/null +++ b/external/README.md @@ -0,0 +1,5 @@ +# External code + +Third-party code as git submodules, never edited here, for example: + + git submodule add https://github.com/STMicroelectronics/stm32-mw-usbpd-core external/stm32-mw-usbpd-core diff --git a/firmware/README.md b/firmware/README.md new file mode 100644 index 0000000..ac3e798 --- /dev/null +++ b/firmware/README.md @@ -0,0 +1,7 @@ +# Firmware + +MCU code, one folder per target (an STM32CubeIDE or CMake project each), for +example `firmware/main-mcu/`. Drivers shared by the targets go to +`firmware/lib/`; third-party stacks (HAL, USB-PD middleware) are submodules in +`external/`. IDE build folders (`Debug/`, `Release/`, `build/`) are not +committed. diff --git a/firmware/lib/README.md b/firmware/lib/README.md new file mode 100644 index 0000000..cc6d0f9 --- /dev/null +++ b/firmware/lib/README.md @@ -0,0 +1,3 @@ +# Shared firmware code + +Drivers and modules used by more than one firmware target. diff --git a/hardware/$REPO_NAME/README.md b/hardware/$REPO_NAME/README.md new file mode 100644 index 0000000..9ddbd4f --- /dev/null +++ b/hardware/$REPO_NAME/README.md @@ -0,0 +1,11 @@ +# $REPO_NAME board + +The KiCad project of $REPO_NAME lives here: create it with KiCad (File, New +Project) as `hardware/$REPO_NAME/$REPO_NAME.kicad_pro`. + +- A revision of this board is a git tag (`v0.1`), not a copy of this folder. +- A real variant built next to it (48 V, 2-board) gets its own folder under + `hardware/` and its own `[[boards]]` entry in `proplan.toml`. +- Shared symbols, footprints and 3D models go to `hardware/lib/`. +- Gerbers, BOMs and PDFs are built by CI (`.gitea/workflows/kibot.yml`); do not + commit them. diff --git a/hardware/lib/README.md b/hardware/lib/README.md new file mode 100644 index 0000000..8a5ac15 --- /dev/null +++ b/hardware/lib/README.md @@ -0,0 +1,6 @@ +# Shared KiCad libraries + +Symbols (`*.kicad_sym`), footprints (`*.pretty/`) and 3D models (`3dmodels/`) +used by more than one board. Add them to the board projects as project +libraries with a path relative to the project (KiCad's KIPRJMOD variable), so +the repository works on every computer. diff --git a/kibot.yaml b/kibot.yaml new file mode 100644 index 0000000..824b43b --- /dev/null +++ b/kibot.yaml @@ -0,0 +1,78 @@ +# KiBot outputs for every board; the workflows run KiBot inside each board +# folder (hardware//). Adapted from LVSolarBuck's kibot.yaml. +kibot: + version: 1 + +preflight: + erc: + warnings_as_errors: false + dont_stop: true + drc: + warnings_as_errors: false + dont_stop: true + check_zone_fills: true + +outputs: + - name: schematic_pdf + comment: Schematic PDF + type: pdf_sch_print + dir: Schematic + + - name: gerbers + comment: Gerber files + type: gerber + dir: Gerbers + options: + exclude_edge_layer: true + exclude_pads_from_silkscreen: true + plot_footprint_refs: true + plot_footprint_values: false + create_gerber_job_file: true + subtract_mask_from_silk: true + layers: + - copper + - F.Paste + - B.Paste + - F.Silkscreen + - B.Silkscreen + - F.Mask + - B.Mask + - Edge.Cuts + + - name: drill + comment: Drill files + type: excellon + dir: Gerbers + options: + metric_units: true + pth_and_npth_single_file: false + + - name: position + comment: Pick and place + type: position + dir: Assembly + options: + format: CSV + units: millimeters + separate_files_for_front_and_back: true + only_smd: false + + - name: bom + comment: Bill of materials + type: bom + dir: BoM + options: + format: CSV + + - name: fabrication + comment: Fabrication package + type: compress + dir: . + options: + output: '%f-fabrication.%x' + format: ZIP + files: + - from_output: gerbers + dest: / + - from_output: drill + dest: / diff --git a/mech/README.md b/mech/README.md new file mode 100644 index 0000000..2fe45f3 --- /dev/null +++ b/mech/README.md @@ -0,0 +1,4 @@ +# Mechanics + +FreeCAD sources (`*.FCStd`) and STEP exports of enclosures, holders and +heatsinks. FreeCAD backups (`*.FCBak`, `*.FCStd1`) are not committed. diff --git a/proplan.toml b/proplan.toml new file mode 100644 index 0000000..3221895 --- /dev/null +++ b/proplan.toml @@ -0,0 +1,24 @@ +# proplan.toml: what proplan reads from this repository. The Code tab shows +# whether the folders named here exist, the product image appears on the +# project and on each prototype stage. Every key is optional; an omitted path +# means the folder of the same name. TEMPLATE.md has the full reference. +schema = 1 + +[product] +name = "$REPO_NAME" +image = "assets/product.png" + +# One entry per KiCad project. A real variant (48 V, 2-board) is another +# entry with its own folder; a revision is a git tag, never a folder copy. +[[boards]] +name = "$REPO_NAME" +path = "hardware/$REPO_NAME" + +[paths] +firmware = "firmware" +software = "software" +mech = "mech" +sim = "sim" +test = "test" +docs = "docs" +external = "external" diff --git a/sim/README.md b/sim/README.md new file mode 100644 index 0000000..f93381d --- /dev/null +++ b/sim/README.md @@ -0,0 +1,4 @@ +# Simulation and calculations + +Circuit simulations (LTspice, ngspice) and calculation scripts or notebooks +(compensator, heatsink, inductor). Large generated results are not committed. diff --git a/software/README.md b/software/README.md new file mode 100644 index 0000000..8a71fb4 --- /dev/null +++ b/software/README.md @@ -0,0 +1,5 @@ +# Software + +Host tools and apps (diagnostics, configuration, test rigs), one folder per +tool, for example `software/diag/` with its own README and pyproject.toml or +Cargo.toml. diff --git a/test/README.md b/test/README.md new file mode 100644 index 0000000..d8e822a --- /dev/null +++ b/test/README.md @@ -0,0 +1,5 @@ +# Bench results + +One folder per prototype stage: `test/v0.1/`, `test/v0.2/`, with photos, +scope captures, measurements and short notes. Attach the key results to the +stage in proplan too.