Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 134 additions & 0 deletions .github/workflows/validate.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
name: validate

on:
pull_request:
push:
branches: [main]
workflow_dispatch:

permissions:
contents: read

concurrency:
group: validate-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
cad:
name: CAD artifacts (build123d)
runs-on: ubuntu-24.04
timeout-minutes: 30
steps:
- uses: actions/checkout@v4

- uses: astral-sh/setup-uv@v6
with:
enable-cache: true

- name: Install code-CAD toolchain
run: scripts/cad-setup.sh

- name: Rebuild and validate CAD artifacts
run: scripts/validate.sh --cad

- name: Upload validation report
if: always()
uses: actions/upload-artifact@v4
with:
name: cad-validation
path: build/cad-validation.json
if-no-files-found: ignore

- name: Upload printable artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: wedge-enclosure
path: |
cad/exports/wedge_enclosure.stl
cad/exports/wedge_enclosure.step
cad/exports/wedge_enclosure.png
if-no-files-found: ignore

quality:
name: CAD quality (ruff, mypy, pytest)
runs-on: ubuntu-24.04
timeout-minutes: 30
steps:
- uses: actions/checkout@v4

- uses: astral-sh/setup-uv@v6
with:
enable-cache: true

- name: Install code-CAD toolchain + dev tools
run: scripts/cad-setup.sh --dev

- name: Static analysis
run: scripts/cad-lint.sh

- name: Geometry tests
run: scripts/cad-test.sh

hardware:
name: Hardware ERC/DRC (KiCad 10)
runs-on: ubuntu-24.04
timeout-minutes: 30
steps:
- uses: actions/checkout@v4

- name: Install KiCad 10
run: |
sudo add-apt-repository --yes ppa:kicad/kicad-10.0-releases
sudo apt-get update
sudo apt-get install -y --no-install-recommends kicad
kicad-cli --version

- name: Seed the global footprint table
run: |
KICAD_VER="$(kicad-cli --version | cut -d. -f1,2)"
mkdir -p "$HOME/.config/kicad/$KICAD_VER"
if [ -f /usr/share/kicad/template/fp-lib-table ]; then
cp /usr/share/kicad/template/fp-lib-table "$HOME/.config/kicad/$KICAD_VER/"
fi

- name: Validate schematic and board
run: scripts/validate.sh --hardware

- name: Build the JLCPCB fabrication package (no order)
run: scripts/fab_export.sh

- name: Upload fabrication package
if: always()
uses: actions/upload-artifact@v4
with:
name: jlcpcb-package
path: |
build/fab/TEG-gerbers.zip
build/fab/TEG-BOM-jlcpcb.csv
build/fab/TEG-CPL-jlcpcb.csv
if-no-files-found: ignore

- name: Upload ERC/DRC reports
if: always()
uses: actions/upload-artifact@v4
with:
name: kicad-reports
path: build/*.rpt
if-no-files-found: ignore

scripts:
name: Scripts (syntax)
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: "3.13"

- name: Syntax-check shell and Python
run: |
for f in scripts/*.sh; do bash -n "$f"; done
python3 -m compileall -q cad scripts tests
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,8 @@ _autosave-*
__pycache__/
*.pyc

# CAD toolchain virtualenvs (scripts/cad-setup*.sh recreate them)
.venv/

# Generated validation artifacts (regenerate with scripts/validate.sh)
build/
22 changes: 22 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Local quality gates for the code-CAD sources. The hooks use the repo-local
# .venv (create it once with `scripts/cad-setup.sh --dev`) so ruff/mypy versions
# are pinned by cad/requirements-dev.txt rather than re-downloaded by pre-commit.
#
# uv pip install --python .venv/bin/python pre-commit
# .venv/bin/pre-commit install
repos:
- repo: local
hooks:
- id: cad-lint
name: CAD lint (ruff + mypy)
entry: scripts/cad-lint.sh
language: system
pass_filenames: false
files: ^(cad/.*\.py|tests/.*\.py|pyproject\.toml)$
- id: cad-test
name: CAD geometry tests (pytest)
entry: scripts/cad-test.sh
language: system
pass_filenames: false
files: ^(cad/wedge\.py|cad/parameters\.py|tests/.*\.py)$
stages: [pre-push]
8 changes: 6 additions & 2 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
{
"recommendations": [
"jessejabezarendse.kicanvas-viewer"
"jessejabezarendse.kicanvas-viewer",
"bernhard-42.ocp-cad-viewer",
"ms-python.python",
"charliermarsh.ruff",
"ms-python.mypy-type-checker"
]
}
}
4 changes: 3 additions & 1 deletion BOM.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,4 +117,6 @@ Conservative and datasheet-verified. Usable energy 3.15→2.2 V is limited (≈2
- [x] MT3608 feedback divider computed → 73.2 k / 10 k = 5.0 V
- [x] Storage decided (option D) and added to the netlist/board
- [ ] **Route copper + GND pour** ← the remaining work
- [ ] Export Gerbers/drill + JLCPCB `BOM.csv` / `CPL.csv`
- [x] Export Gerbers/drill + JLCPCB `BOM.csv` / `CPL.csv` — scripted as
[scripts/fab_export.sh](scripts/fab_export.sh); see
[docs/jlcpcb-fabrication.md](docs/jlcpcb-fabrication.md)
10 changes: 8 additions & 2 deletions PLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,8 +179,11 @@ Draft currently uses a **single root sheet** with block annotations; to be split
5. **Route the board** — GND pour first, then power, then signals; re-check DRC.
6. **Source the remaining parts** — supercap (hand-solder), 22 µH inductor, the six odd E96
resistors, R8/R9, R10. See the parts table in [README.md](README.md).
7. **Export Gerbers/drill + JLCPCB `BOM.csv` / `CPL.csv`**; add a fabrication checklist
(stackup, assembly side, part rotations). No script yet.
7. ~~**Export Gerbers/drill + JLCPCB `BOM.csv` / `CPL.csv`**~~ **Scripted** —
`scripts/fab_export.sh` produces the whole JLCPCB package (Gerbers + drill + BOM +
CPL) in one command; `scripts/jlc_bom.py` / `scripts/jlc_cpl.py` do the conversions.
Fabrication checklist and the manual, approval-gated ordering path are documented in
[docs/jlcpcb-fabrication.md](docs/jlcpcb-fabrication.md).
8. **Bench-verify** the board from a bench supply emulating the TEG before running the thermal tests.
9. **Thermal tests** — one-module baseline, two-module comparison, five cold starts, 0.5 W load.
10. Optional follow-ups: make the MPP ratio selectable (R13/R14) for measurement; recompute
Expand All @@ -190,6 +193,9 @@ Draft currently uses a **single root sheet** with block annotations; to be split

| Date | Change |
|------|--------|
| 2026-10-20 | **CAD toolchain finalized on build123d.** The bake-off (CadQuery, SolidCraft, FreeCAD) was a comparison exercise; its sources, exports and runner scripts are removed so the PR contains only the toolchain we will maintain: `cad/wedge.py` + `cad/parameters.py`, the committed STEP/STL, the VS Code viewer workflow, and the ruff/mypy/pytest + artifact-validation gates. `scripts/validate_cad.py` now validates only the canonical build123d artifacts. |
| 2026-10-20 | **Validation pipeline added for the engineering artifacts.** `scripts/validate_cad.py` checks the committed STEP/STL (closed single-body mesh, bed fit, STEP↔STL agreement) and rebuilds the canonical wedge to catch stale exports; `scripts/validate.sh` now runs both CAD and KiCad ERC/DRC; `.github/workflows/validate.yml` runs the CAD, hardware (ERC/DRC + JLCPCB package build) and script-syntax jobs on every PR. Details in [docs/validation.md](docs/validation.md) |
| 2026-10-14 | **JLCPCB fabrication flow scripted end-to-end.** Added `scripts/fab_export.sh` (one command → Gerbers + Excellon drill + BOM + CPL + upload zip in `build/fab/`), `scripts/jlc_bom.py` (schematic → JLCPCB `Comment,Designator,Footprint,LCSC Part #`) and `scripts/jlc_cpl.py` (KiCad `pos` CSV → JLCPCB CPL). Default is SMT-only assembly, with the BOM restricted to the CPL designators so the two files always agree (JLCPCB rejects a BOM part missing from the CPL); `--tht` includes through-hole parts. Verified the BOM generator against the reference board (23 lines / 33 designators). Ordering stays **manual and approval-gated** — JLCPCB's official Open Platform API (`api.jlcpcb.com`) is application-gated and not wired; documented in [docs/jlcpcb-fabrication.md](docs/jlcpcb-fabrication.md) |
| 2026-10-10 | **TEG module datasheet obtained and extracted** (Coherent TG12-4, 3 pages) — closes the "datasheet verification pending" item open since day one. Verified: **200 °C continuous** rating (230 °C intermittent), AC resistance 2.76–3.41 Ω, **clamp at 1.4 MPa (200 psi)**, thermal resistance ~2.2 °C/W, and power/voltage/current tables at cold side 50 °C. **The earlier estimate was ~3× pessimistic**: at hot 170 °C a module makes **2.12 W** (not ~0.7 W), so **2 modules ≈ 3.2 W and 3 modules ≈ 4.8 W** at a 150 °C hot side — E1 is met with 2–3 modules. Also established: V_mp/V_oc ≈ 0.57 (fractional-V_oc MPPT confirmed), the **cold side is the binding constraint** (~150 W to reject for ~4.8 W electrical, so expect 3–4 W with a practical fan-cooled heatsink), and at low ΔT the array voltage falls below a 5 V buck's input so a buck-boost may be needed for cold start. Sourced via alldatasheet → Bing → Coherent's own PDF (the direct search engines were blocked from this environment; the PDF came from coherent.com) |
| 2026-10-10 | **Project re-scoped by [REQUIREMENTS.md](REQUIREMENTS.md)**: fireplace-mounted wedge harvester, **~3–4 W**, 1 × USB-A + 1 × USB-C sharing the output, **fan-cooled** (no water), total BOM ≤ $200. The BQ25504 board (≤300 mW) becomes a validated sub-watt **reference**, not the deliverable. Corrected an earlier error: the first 5–10 W analysis assumed passive cooling at ΔT ≈ 50 °C; with fan-forced cooling at ΔT ≈ 70 °C per-module output roughly doubles, so a few watts needs 2–3 modules, not 10–20. Key new parts identified and verified in the JLCPCB catalogue: buck front end **TPS54331DR (C9865)**, USB-C receptacle **TYPE-C-31-M-12 (C165948)**, SMD USB-A **AF 10.0 QT-ZB6.8 (C456016)**; fan driver reuses AO3400A (C20917) |
|------|--------|
Expand Down
43 changes: 33 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,14 +69,17 @@ The classes still exist so wider widths can be set later, after a manual fanout

## Fabrication outputs

Generated into `build/fab/` (gitignored — regenerate with the commands in
[Opening the project](#opening-the-project)):
Generated into `build/fab/` (gitignored) by **[scripts/fab_export.sh](scripts/fab_export.sh)**
— one command for the whole JLCPCB package (Gerbers + drill + BOM + CPL). Full flow and
the ordering/approval path: **[docs/jlcpcb-fabrication.md](docs/jlcpcb-fabrication.md)**.

| File | What |
|------|------|
| `TEG-gerbers.zip` | JLCPCB PCB upload — Gerbers + drill + `.gbrjob` |
| `TEG-*.gbl/.gtl/.gbs/.gts/.gbo/.gto/.gm1` + `TEG-job.gbrjob` | Gerbers (2 copper, 2 mask, 2 silk, edge) |
| `TEG.drl` | Excellon drill |
| `TEG-pos.csv` | Pick-and-place (`kicad-cli pcb export pos`) |
| `TEG-pos.csv` | Raw pick-and-place (`kicad-cli pcb export pos`) |
| `TEG-CPL-jlcpcb.csv` | JLCPCB **CPL** — `Designator,Val,Package,Mid X,Mid Y,Rotation,Layer` |
| `TEG-BOM-jlcpcb.csv` | JLCPCB BOM — `Comment,Designator,Footprint,LCSC Part #` |
| `TEG-routed.pdf`, `TEG-schematic.pdf` | 2D renders for review (copper visible here) |
| `build/render/TEG-*.png` | **3D renders** (top / isometric / bottom / harvester close-up) — see [scripts/render.sh](scripts/render.sh) |
Expand Down Expand Up @@ -154,6 +157,21 @@ There is **no MCU on the board**, so `FAN_PWM` (J2 pin 4) must be driven by an e
a bench supply, signal generator, or your instrumentation. `FAN_TACH` (pin 3) has a 10 kΩ
pull-up to `+5V` (R11) so you can read fan speed. Feed the fan from `+5V` (pin 2) and `GND` (pin 1).

## Continuous validation

Every pull request runs these checks (see **[docs/validation.md](docs/validation.md)**):

| Check | What it proves |
|-------|----------------|
| **CAD artifacts** | Rebuilds the build123d wedge and requires the committed STEP/STL to match. Every STL is a closed, single-body, bed-fitting mesh. |
| **ERC / DRC** | `kicad-cli` (KiCad 10) finds no error-level schematic or board violations. |
| **Fab package** | Gerbers, drill, CPL, BOM and the JLCPCB zip build; nothing is ordered. |
| **Scripts** | All shell scripts parse and all Python sources compile. |

Run the same checks locally with `scripts/validate.sh` (add `--cad` if KiCad is
not installed). A failing CAD check usually means the committed exports are
stale: run `scripts/cad-run.sh` and commit the updated artifacts.

## What to expect when you test

**Two TG12-4 modules produce only a few hundred mW at useful ΔT, while the fan draws
Expand All @@ -168,6 +186,9 @@ the expected behaviour of this build, not a fault.
| [REQUIREMENTS.md](REQUIREMENTS.md) | **Whole-device requirements** — electrical, mechanical, thermal/safety, manufacturing, and the system BOM split into *from JLCPCB* vs *buy separately* |
| [BOM.md](BOM.md) | Board bill of materials (the sub-watt reference design) |
| [PLAN.md](PLAN.md) | Living plan: findings, decisions, open questions, changelog |
| [docs/validation.md](docs/validation.md) | **CI + local validation flow** — what every push checks, how to fix a failing artifact |
| [.github/workflows/validate.yml](.github/workflows/validate.yml) | CI: rebuild/validate CAD, lint/test the model code, run KiCad ERC/DRC, syntax-check scripts |
| [docs/jlcpcb-fabrication.md](docs/jlcpcb-fabrication.md) | **End-to-end JLCPCB flow** — fab package command, verification checklist, manual approval-gated ordering |
| `hardware/TEG.kicad_pro` | KiCad project |
| `hardware/TEG.kicad_sch` | Root schematic (auto-generated — nets defined by pin labels) |
| `hardware/TEG.kicad_pcb` | Board (auto-generated — 33 footprints placed and netted, **no copper routed yet**) |
Expand All @@ -176,8 +197,12 @@ the expected behaviour of this build, not a fault.
| `hardware/easyeda2kicad.3dshapes/` | 3D WRL models, named as the footprints reference them |
| `scripts/gen_schematic.py` | Regenerates `TEG.kicad_sch` from the part/net table inside it |
| `scripts/gen_board.py` | Regenerates `TEG.kicad_pcb` (shares the netlist with `gen_schematic.py`) |
| `scripts/validate.sh` | Runs KiCad ERC (schematic) and DRC (board); needs `kicad-cli` |
| `scripts/validate.sh` | **All checks**: rebuild/validate the code-CAD exports, then KiCad ERC + DRC (`--cad` / `--hardware` for one half) |
| `scripts/validate_cad.py` | Validates committed CAD STEP/STL artifacts: manifold mesh, bed fit, STEP↔STL agreement, stale-export check |
| `scripts/jlc_lookup.py` | Queries JLCPCB's own parts API — id, library type, stock |
| `scripts/fab_export.sh` | **One command** → JLCPCB package (Gerbers + drill + BOM + CPL) in `build/fab/` |
| `scripts/jlc_bom.py` | Schematic → JLCPCB BOM CSV (`Comment,Designator,Footprint,LCSC Part #`) |
| `scripts/jlc_cpl.py` | KiCad `pos` CSV → JLCPCB CPL CSV |
| `scripts/freeroute.sh` | Routes the board via the SPECCTRA DSN→FreeRouting→SES round-trip, then DRCs |
| `scripts/render.sh` | Renders 3D views of the board (with component bodies) into `build/render/` |
| `scripts/route_board.py` | *Abandoned* hand-written router (kept for reference; not used) |
Expand All @@ -194,14 +219,12 @@ The schematic and board are **generated** — edit the part/net table in
python3 scripts/gen_schematic.py # -> hardware/TEG.kicad_sch
python3 scripts/gen_board.py # -> hardware/TEG.kicad_pcb (unrouted, wipes routing)
./scripts/freeroute.sh # route it (DSN -> FreeRouting -> SES -> DRC)
./scripts/validate.sh # ERC + DRC (needs kicad-cli)
./scripts/validate.sh # all checks (CAD artifacts + ERC/DRC)
./scripts/validate.sh --cad # CAD artifacts only, no KiCad needed
python3 scripts/jlc_lookup.py "22uH inductor" # search the JLCPCB catalogue

# fabrication outputs
KC=~/Applications/KiCad/KiCad.app/Contents/MacOS/kicad-cli
$KC pcb export gerbers -o build/fab/ hardware/TEG.kicad_pcb
$KC pcb export drill --excellon-units mm -o build/fab/ hardware/TEG.kicad_pcb
$KC pcb export pos --format csv --units mm -o build/fab/TEG-pos.csv hardware/TEG.kicad_pcb
# one-command JLCPCB package: gerbers + drill + BOM + CPL into build/fab/
scripts/fab_export.sh # see docs/jlcpcb-fabrication.md (ordering is manual + approval-gated)
```

**Known cosmetic warnings** (safe to ignore): ERC reports 3 × `lib_symbol_mismatch` on the two
Expand Down
25 changes: 25 additions & 0 deletions cad/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# cad/ — code-CAD for the printed parts

The wedge enclosure and other printed mechanical parts are authored **in code**
with [build123d](https://build123d.readthedocs.io/), not in a GUI. Source is a
program, so a change is a reviewable diff and an agent can drive it end to end.

- `parameters.py` — every dimension (the single source of truth).
- `wedge.py` — the wedge model; exports `exports/wedge_enclosure.{step,stl,png}`.
- `exports/` — committed build output. The **STL feeds the Bambu print task**.
- `requirements.txt` — pinned model toolchain; `requirements-dev.txt` adds
ruff, mypy and pytest for the quality gate.

```bash
scripts/cad-setup.sh --dev # once: create .venv/ and install deps (no sudo)
scripts/cad-run.sh # build + export
scripts/cad-lint.sh # ruff + mypy
scripts/cad-test.sh # geometry tests
```

For live review, open the model in VS Code with the **OCP CAD Viewer** extension
(pinned in `.vscode/extensions.json`); see
[docs/cad-toolchain.md §10](../docs/cad-toolchain.md#10-editing-and-review-in-a-gui).

Full contract, conventions, and the requirements mapping:
**[docs/cad-toolchain.md](../docs/cad-toolchain.md)**.
Binary file added cad/exports/wedge_enclosure.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading