Professionelle KiCad Automatisierung mit KiBot

Durch die Nutzung von CI/CD bei KiCad-Projekten können Projekte automatisiert dokumentiert und die notwendigen Produktionsdaten automatisiert erstellt werden. Die Möglichkeiten sind nahezu endlos und es lassen sich beliebige und unterschiedlich Komplexe Arbeitsabläufe realisieren. Ich möchte mit diesem Artikel meinen eigenen Arbeitsablauf, den ich in den letzten Monaten stetig weiterentwickelt habe, vorstellen.

Beispiel

Bevor ich mit dem Aufbau meines Ablaufs beginne, möchte ich die praktische Nutzung an einem Beispiel vorstellen, Hierfür nehme ich meinen Zigbee Raumsensor. Dieses Projekt beinhaltet verschiedene CI/CD-Pipelines, wobei eine davon genutzt wird um die angehängten Produktionsdaten für das PCB zu erstellen.

Der Ablauf unterstützt vier verschiedene Varianten:

VarianteBeschreibung
DRAFTWird für die ersten Schaltplanentwürfe, ohne Layout genutzt um die entsprechenden Felder in den Dokumenten auszufüllen und die notwendige XML-Datei für das Inhaltsverzeichnis zu generieren. Üblicherweise wird dieser Build nur ein einiges mal ausgeführt.
PRELIMINARY Wird für den Schaltplan und Layout ohne DRC/ERC genutzt. Kann mehrmals genutzt werden um zum fertigen Design zu iterieren.
CHECKEDWird für den Schaltplan und Layout mit einem DRC/ERC genutzt. Dient als Releasevorbereitung um zu prüfen ob das Projekt vollständig ist.
RELEASEDWird nicht manuell, sondern nur automatisch bei Push auf ein Tag ausgewählt und indiziert einen Release-Build.

Die Variante wird über den CI/CD Job festgelegt und kann so z. B. bei einem Versionsrelease entsprechend geändert werden. Abhängig von der ausgeführten Variante beinhaltet der erzeugte Datensatz:

  • Ein komplettes 3D-Model der Leiterkarte als Step-Datei
  • Eine statische Webseite zu dem Projekt mit allen erzeugten Daten
  • Bilder von der unbestückten und der bestückten Leiterkarte, jeweils Ober- und Unterseite
  • Eine KiRI-Konfiguration um die Änderungen am PCB nachvollziehen zu können
  • Fertigungsdaten in Form von Gerber- und Bestückdaten
  • Eine Netzliste und den Schaltplan
  • Dokumentation zu Testpunkten
  • Eine Projekt-README

Der Schaltplan selber ist hierarchisch aufgebaut und beinhaltet Informationen wie

  • Ein Titelblatt mit Inhaltsverzeichnis
  • Ein Blockdiagramm
  • Die Schaltung
  • Eine automatisch generierte Versionshistorie

KiBot

Ausgangsbasis des Arbeitsablaufs ist KiBot, ein Programm welches genutzt werden kann um diverse Datensätze aus einem KiCad-Design zu generieren. KiBot arbeitet mit Konfigurationsdateien im YAML-Format und kann über ERC/DRC, über Produktionsdaten, wie z. B. Gerberdaten, bis hin zu einer Dokumentation mit Titelseite, Schaltplan, Kostenkalkulation, etc. alles an Daten erzeugen, was man für ein ordentliches Release benötigt. Besonders mächtig wird KiBot in Kombination mit einem CI/CD basierten Ablauf, wie er z. B. bei GitHub oder GitLab genutzt werden kann. Hierfür bietet es sich an KiBot als Docker-Container zu integrieren.

KiCad-Bibliothek

Seitdem ich KiCad nutze arbeite ich an einer einzelnen Bibliothek, die alle Symbole, Bauteile, Modelle, etc. meiner KiCad-Projekte enthält. Diese Bibliothek ist der Ausgangspunkt eines jeden Designs und immer wenn ich ein Symbol oder einen Footprint permanent ändern möchte, lege ich ihn als Kopie in dieser Bibliothek ab.

Einblick in die KiCad-Vorlage

Die KiCad-Vorlage ist allgemein gehalten und benutzt verschiedene Variablen um die hinterlegten Dateien projektspezifisch anzupassen. Der Ansatz ist so gewählt worden, damit in Zukunft weitere Dateien als generische Vorlagen hinzugefügt werden können. Die genutzten Variablen können dem Markdown-Dokument der Projektvorlage entnommen werden.

# Template Variables

The `init-project.sh` script replaces the following placeholders automatically when a new project is initialized from this template.

## File Variables (`${...}`)

Replaced via `sed` in all text files (`.md`, `.yaml`, `.adoc`, shell scripts, etc.).

| Variable | Description | Example |
| --- | --- | --- |
| `${PROJECT_NAME}` | Human-readable project name | `My Sensor Board` |
| `${BOARD_NAME}` | KiCad board name (used for file naming) | `MySensorBoard` |
| `${DESIGNER}` | Full name of the hardware designer | `Jane Doe` |
| `${EMAIL}` | Designer e-mail address | `jane@example.com` |
| `${COMPANY}` | Company / organisation name (optional) | `ACME Corp` |
| `${REVISION}` | Initial revision string | `1.0.0` |
| `${RELEASE_DATE}` | Human-readable release date | `02-Mar-2026` |
| `${RELEASE_DATE_NUM}` | ISO release date | `2026-03-02` |
| `${GIT_URL}` | Full GitHub repository URL | `https://github.com/user/repo` |
| `${GIT_USER}` | GitHub username, parsed from `${GIT_URL}` | `user` |
| `${GIT_REPO}` | GitHub repository name, parsed from `${GIT_URL}` | `repo` |
| `${MASTER_BRANCH}` | Default Git branch name | `main` |
| `${PROJECT_NAME_ANCHOR}` | Lowercase, hyphenated project name for Markdown anchors | `my-sensor-board` |
| `${BOARD_NAME_ANCHOR}` | Lowercase, hyphenated board name — also used as hardware directory name | `mysensorboard` |

## KiCad Text Variables

Written into the `.kicad_pro` JSON under `text_variables`. Available inside KiCad schematics and PCB layouts via `${VARIABLE}` in text fields and title blocks.

| Variable | Description |
| --- | --- |
| `PROJECT_NAME` | Human-readable project name |
| `BOARD_NAME` | KiCad board name |
| `DESIGNER` | Full name of the hardware designer |
| `COMPANY` | Company / organisation name |
| `RELEASE_DATE` | Human-readable release date (`DD-Mon-YYYY`) |
| `RELEASE_DATE_NUM` | ISO release date (`YYYY-MM-DD`) |
| `REVISION` | Initial revision string |

## KiBot Variables (`kibot_yaml/kibot_main.yaml`)

| Variable | Set from |
| --- | --- |
| `PROJECT_NAME` | `${PROJECT_NAME}` |
| `BOARD_NAME` | `${BOARD_NAME}` |
| `COMPANY` | `${COMPANY}` |
| `DESIGNER` | `${DESIGNER}` |
| `GIT_URL` | `${GIT_URL}` |

Aufbau der CI/CD Pipeline

Die KiCad-Vorlage beinhaltet auch die CI/CD-Pipeline für die Erstellung der Fertigungsdaten. Diese Pipeline muss bei der Erstellung eines neuen Projektes ebenfalls entsprechend angepasst werden, damit die Daten korrekt erzeugt werden.

name: PCB Data

on:
  workflow_dispatch:
  push:
    tags:
      - '[0-9]+.[0-9]+.[0-9]+' # Match semantic versioning tags
    branches:
      - main
      - master
      - dev
      - '[0-9]*.[0-9]*.[0-9]*_Dev'
      - '[0-9]*.[0-9]*_Dev'
    paths-ignore:
      - '*.md'

env:
  # Name of the KiCad PCB file
  kicad_board: ${BOARD_NAME}

  # KiBot init point
  kibot_config: kibot_yaml/kibot_main.yaml

  # Input directory with the KiCad project and the KiBot files
  kibot_input_dir: ${BOARD_NAME_ANCHOR}

  # Output directory for the run results from KiBot
  kibot_output_dir: ${BOARD_NAME_ANCHOR}

  # Output path for the run results from KiBot
  kibot_output_path: ../production

  # Used variant. We assume:
  # DRAFT: Only schematic in progress, will only generate schematic PDF, netlist and BoM
  # PRELIMINARY: Will generate both schematic and PCB documents, but no ERC/DRC
  # CHECKED: Will generate both schematic and PCB documents, with ERC/DRC
  # RELEASED: Similar to CHECKED, automatically selected when pushing a tag to main
  kibot_variant: PRELIMINARY

  # Master branch. Can be changed for testing
  master_branch: ${MASTER_BRANCH}

  # API keys used by KiCost
  MOUSER_KEY: ${{ secrets.MOUSER_KEY }}
  DIGIKEY_KEY: ${{ secrets.DIGIKEY_KEY }}
  TME_KEY: ${{ secrets.TME_KEY }}
  RS_KEY: ${{ secrets.RS_KEY }}
  FARNELL_KEY: ${{ secrets.FARNELL_KEY }}

permissions:
  contents: write
  pages: write
  id-token: write

concurrency:
  group: pages
  cancel-in-progress: false

jobs:
  release:
    needs: generate_outputs
    runs-on: ubuntu-latest
    container: ghcr.io/inti-cmnb/kicad9_auto_full:latest

    if: github.ref_type == 'tag'
    steps:
      - name: Checkout
        uses: actions/checkout@v5
        with:
          ref: ${{ github.ref }}

      - name: Set variant to RELEASED for tag
        shell: bash
        run: |
          echo "kibot_variant=RELEASED" >> ${GITHUB_ENV}

      - name: Download updated CHANGELOG
        uses: actions/download-artifact@v4
        with:
          name: CHANGELOG.md
          path: .

      - name: Download build artifacts
        uses: actions/download-artifact@v4
        with:
          name: ${{ env.kibot_variant }}.zip
          path: .

      - name: ZIP artifact
        shell: bash
        run: |
          apt-get update
          apt-get install -y zip
          zip -r ${{ env.kicad_board }}.zip ${{ env.kibot_output_dir }}-${{ env.kibot_variant }}

      - name: Release
        uses: docker://antonyurchenko/git-release:v6
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          CHANGELOG_FILE: ./CHANGELOG.md
          RELEASE_NAME_PREFIX: "Release"
        with:
          args: |
            ${{ env.kicad_board }}.zip

  generate_outputs:
    runs-on: ubuntu-latest
    container: ghcr.io/inti-cmnb/kicad9_auto_full:latest

    if: "!contains(github.event.head_commit.message, 'Merge pull request') || github.ref_type == 'tag'"
    steps:
      - name: Checkout Repository
        uses: actions/checkout@v5
        with:
          fetch-depth: 0

      - name: Initialize
        run: |
          mkdir -p log

      # Run these changelog update steps only on tag pushes
      - name: Pull latest changes for changelog update
        if: ${{ github.ref_type == 'tag' }}
        run: |
          git config pull.rebase true
          git config --global user.email "github-actions[bot]@users.noreply.github.com"
          git config --global user.name "GitHub Actions"
          git fetch
          git pull origin ${{ env.master_branch }}

      - name: Getting the dependencies
        shell: bash
        run: |
          git clone https://github.com/Kampi/KiCad.git kicad-library
          echo "KICAD_LIBRARY=$(pwd)/kicad-library" >> ${GITHUB_ENV}

      - name: Extract release notes
        if: ${{ github.ref_type == 'tag' }}
        uses: ffurrer2/extract-release-notes@v2
        with:
          changelog_file: ${{ env.kibot_input_dir }}/CHANGELOG.md
          prerelease: true

      - name: Update CHANGELOG
        if: ${{ github.ref_type == 'tag' }}
        uses: thomaseizinger/keep-a-changelog-new-release@v3
        with:
          changelogPath: ${{ env.kibot_input_dir }}/CHANGELOG.md
          tag: ${{ github.ref_name }}

      - name: Add Full Changelog link to CHANGELOG
        if: ${{ github.ref_type == 'tag' }}
        shell: bash
        run: |
          current_tag="${{ github.ref_name }}"
          changelog_file="${{ env.kibot_input_dir }}/CHANGELOG.md"
          repo_url="https://github.com/${{ github.repository }}"

          # Get the previous tag (all SemVer tags, excluding the current one)
          prev_tag=$(git tag --sort=-version:refname | grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' | grep -v "^${current_tag}$" | head -1)

          python3 ./scripts/update_changelog_links.py \
            --file "${changelog_file}" \
            --tag "${current_tag}" \
            --prev-tag "${prev_tag}" \
            --repo-url "${repo_url}"

      - name: Commit updated CHANGELOG
        if: ${{ github.ref_type == 'tag' }}
        uses: stefanzweifel/git-auto-commit-action@v6
        with:
          branch: ${{ env.master_branch }}
          commit_message: Update CHANGELOG
          file_pattern: ${{ env.kibot_input_dir }}/CHANGELOG.md
          push_options: '--force'

      - name: Cache 3D models data
        uses: set-soft/cache@main
        with:
          path: ~/cache_3d
          key: cache_3d

      - name: Replace .wrl with .step in PCB file
        shell: bash
        run: |
          sed -i 's/\.wrl/.step/g' ${{ env.kibot_input_dir }}/${{ env.kicad_board }}.kicad_pcb

      - name: Determine VERSION argument and override variant if tag
        shell: bash
        run: |
          last_tag=$(git describe --tags --abbrev=0 || echo "")
          if [[ "${{ github.ref_type }}" == "tag" ]]; then
            version_arg="-E REVISION='${last_tag}'"
            kibot_variant="RELEASED"
            echo "kibot_variant=RELEASED" >> ${GITHUB_ENV}
          else
            version_arg="-E REVISION='${last_tag}+ (Unreleased)'"
            kibot_variant="${{ env.kibot_variant }}"
            echo "kibot_variant=${{ env.kibot_variant }}" >> ${GITHUB_ENV}
          fi

          # Determine additional_args based on the variant
          case "$kibot_variant" in
            "DRAFT")
              additional_args="--skip-pre draw_fancy_stackup,erc,drc ${version_arg} draft_group"
              ;;
            "PRELIMINARY")
              additional_args="--skip-pre erc,drc ${version_arg} all_group"
              ;;
            "CHECKED"|"RELEASED")
              additional_args="${version_arg} all_group"
              ;;
            *)
              echo "Unknown variant: $kibot_variant"
              exit 1
              ;;
          esac

          echo "version_arg=${version_arg}" >> ${GITHUB_ENV}
          echo "additional_args=${additional_args}" >> ${GITHUB_ENV}

      # Generate notes (skipped for DRAFT variant)
      - name: Generate notes
        if: ${{ env.kibot_variant != 'DRAFT' }}
        shell: bash
        run: |
          cd ${{ env.kibot_input_dir }}
          kibot -vvvv -c ${{ env.kibot_config }} -d ${{ env.kibot_output_path }}/${{ env.kibot_output_dir }}-${{ env.kibot_variant }} -b ${{ env.kicad_board }}.kicad_pcb -s all -g variant=${{ env.kibot_variant }} notes 2> ../log/kibot_notes.log

      - name: Generate README only
        if: ${{ env.kibot_variant == 'DRAFT' }}
        shell: bash
        run: |
          cd ${{ env.kibot_input_dir }}
          kibot -vvvv -c ${{ env.kibot_config }} -d ${{ env.kibot_output_path }}/${{ env.kibot_output_dir }}-${{ env.kibot_variant }} -b ${{ env.kicad_board }}.kicad_pcb -s draw_fancy_stackup,set_text_variables,erc,drc -g variant=${{ env.kibot_variant }} md_readme 2> ../log/kibot_readme.log

      - name: Generate outputs
        shell: bash
        run: |
          cd ${{ env.kibot_input_dir }}
          kibot -vvvv -c ${{ env.kibot_config }} -d ${{ env.kibot_output_path }}/${{ env.kibot_output_dir }}-${{ env.kibot_variant }} -b ${{ env.kicad_board }}.kicad_pcb -g variant=${{ env.kibot_variant }} -E PDF_REPORT_ROOT_DIR=${{ env.kibot_output_path }}/${{ env.kibot_output_dir }}-${{ env.kibot_variant }} ${{ env.additional_args }} 2> ../log/kibot_output.log

      - name: Collect PCB costs
        shell: bash
        run: |
          cd ${{ env.kibot_input_dir }}
          revision=$(python3 kibot_resources/scripts/get_changelog_version.py -f CHANGELOG.md)
          if [[ $? -ne 0 ]]; then
              echo -e "${YELLOW}Warning: Unable to determine version from CHANGELOG.md. Defaulting to empty revision.${NC}"
              revision=""
          fi
          kibot -vvvv --skip-pre erc,drc,draw_fancy_stackup -c kibot_yaml/kibot_main.yaml -d ${{ env.kibot_output_path }}/${{ env.kibot_output_dir }}-${{ env.kibot_variant }} -g variant=${{ env.kibot_variant }} -E KICOST_CONFIG=kibot_yaml/kicost_config_local.yaml xlsx_bom 2> ../log/kicost_output.log

      - name: Upload artifacts (logs)
        uses: actions/upload-artifact@v4
        if: ${{ always() }}
        with:
          name: Log-${{ env.kibot_variant }}.zip
          path: log

      - name: Clean up
        shell: bash
        run: |
          rm -rf ${KICAD_LIBRARY}
          find . -type f -iname \*.log -delete
          find . -type f -iname \*.ogv -delete
          find . -type f -iname \*.kicad_pro-bak -delete
          rm -f ${{ env.kibot_input_dir }}/kibot_*.kicad_pro
          rm -f ${{ env.kibot_input_dir }}/kibot_*.kicad_pcb

      - name: Upload artifacts (results)
        uses: actions/upload-artifact@v4
        with:
          name: ${{ env.kibot_variant }}.zip
          path: production
          if-no-files-found: ignore

      - name: Upload updated CHANGELOG
        if: ${{ github.ref_type == 'tag' }}
        uses: actions/upload-artifact@v4
        with:
          name: CHANGELOG.md
          path: ${{ env.kibot_input_dir }}/CHANGELOG.md

      - name: Pull latest changes
        shell: bash
        run: |
          git config --global user.email "github-actions[bot]@users.noreply.github.com"
          git config --global user.name "GitHub Actions"

          if [[ "${{ github.ref_type }}" == "tag" ]]; then
            echo "Triggered by a tag, committing changes in detached HEAD state"
            git add -A
            git commit -m "Update outputs (release)"

            DETACHED_COMMIT=$(git rev-parse HEAD)
            echo "Checking out the ${{ env.master_branch }} branch"
            git fetch origin ${{ env.master_branch }}
            git checkout ${{ env.master_branch }}

            echo "Merging detached HEAD commit into ${{ env.master_branch }}"
            git merge --no-ff $DETACHED_COMMIT -m "Merge outputs from tag-triggered workflow" -X theirs
            echo "Pushing to ${{ env.master_branch }} branch"
            git push origin ${{ env.master_branch }}
          else
            echo "Triggered by a branch, using the current branch"
            git pull origin ${{ github.ref_name }} --tags --force
          fi

      - name: Discard changes to .kicad_pcb files and remove temp files
        shell: bash
        run: |
          git checkout HEAD -- *.kicad_pcb

      - name: Push outputs
        uses: stefanzweifel/git-auto-commit-action@v5
        with:
          branch: ${{ github.ref_name }}
          commit_message: Push outputs from CI/CD ($(date +'%Y-%m-%d'))

  deploy:
    needs: generate_outputs
    runs-on: ubuntu-latest

    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}

    if: github.ref_type == 'tag' || github.ref_name == 'main' || github.ref_name == 'master'
    steps:
      - name: Determine variant
        shell: bash
        run: |
          if [[ "${{ github.ref_type }}" == "tag" ]]; then
            echo "kibot_variant=RELEASED" >> "${GITHUB_ENV}"
          else
            echo "kibot_variant=${{ env.kibot_variant }}" >> "${GITHUB_ENV}"
          fi

      - name: Download build artifacts
        uses: actions/download-artifact@v4
        with:
          name: ${{ env.kibot_variant }}.zip
          path: production

      - name: Upload pages artifact
        uses: actions/upload-pages-artifact@v3
        with:
          path: production/${{ env.kibot_output_dir }}-${{ env.kibot_variant }}

      - name: Deploy to GitHub Pages
        id: deployment
        uses: actions/deploy-pages@v4

Die Pipeline enthält drei Jobs:

JobBeschreibung
generate_outputsStandardjob, der manuell oder bei jedem Push auf einen Entwicklungsbranch mit dem Namen x.y(.z)_Dev ausgeführt wird. Während der Ausführung werden Fertigungsdaten entsprechend der eingestellten Variante erzeugt und als Artefakt hochgeladen. Je nach Variante wird auch das CHANGELOG und die Revision History im Projekt entsprechend aktualisiert.
releaseDieser Job wird während der Erstellung eines Releases ausgeführt, wenn das entsprechende Release-Tag auf den main– oder master-Branch gepusht wird. Am Ende des Jobs wird ein Release mit den entsprechenden Informationen aus dem CHANGELOG auf GitHub veröffentlicht.
deployDieser Job wird ausgeführt, wenn auf den main– oder master-Branch oder auf ein Tag gepusht wird und dient dazu die erzeugte Dokumentation als Deployment zu veröffentlichen. Ein Beispiel kann hier eingesehen werden.

Am umfangreichsten ist der generate_outputs Job, da dieser Job die verschiedenen Varianten behandeln und die Daten korrekt im Projekt hinterlegen muss. Hierbei werden, auf Grund von Problemen mit dem 3D-Export, vorhandene 3D-Modelle im .wrl Format durch .stp ersetzt, das CHANGELOG entsprechend bearbeitet um die [Unreleased]-Sektion in die entsprechende Release-Sektion mit Datum umzuwandeln, KiBot mit den entsprechenden Argumenten aufzurufen und die Artefakte und die Logs hochzuladen.

Projektvorlage + KiCad-Plugin

Den beschriebenen Arbeitsablauf habe ich nach und nach in eine Projektvorlage integriert und dokumentiert. Dieses Projekt gibt es auf GitHub zum Download und es ist die Basis für ein Initialisierungsskript und ein KiCad-Plugin zur Projekterstellung. Beide Tools nutzen die Dateien aus der Projektvorlage um das Projekt entsprechend der Eingaben zu initialisieren. Als Zusatz können verschiedene Vorlagen für den PCB Stackup und Lizenzen hinterlegt und ausgewählt werden, sodass die Vorlage beliebig an die eigenen Bedürfnisse angepasst werden kann.

Das Plugin nutzt, anders als die Skripte, eine grafische Oberfläche und fragt alle notwendigen Informationen über eine Eingabemaske ab.

Unabhängig vom verwendeten Tool kann das erstellte Projekt direkt bearbeitet, auf eine beliebige Git-Instanz gepusht und der Workflow für KiBot ausgeführt werden.

Disclaimer

Der hier beschriebene Ablauf mag nicht für alle User gleichermaßen gültig oder praktikabel sein, kann aber eine gute Inspiration dafür sein, was alles mit KiCad möglich ist. Nehmt diesen Post daher gerne als Ideensammlung / Vorschlag meinerseits. Ich bin auch immer offen für neue Ideen und Anmerkungen. Meine verwendete KiBot-Vorlage basiert zudem auf der Hierarchischen KiBot Vorlage von nguyen-v.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

Nach oben scrollen