From 1502c0b38ff524db2e5447b7b73373cf18c44d8c Mon Sep 17 00:00:00 2001 From: RaffaelW <146560011+RaffaelW@users.noreply.github.com> Date: Sun, 15 Mar 2026 21:04:02 +0100 Subject: [PATCH 01/22] fix(tasks): remove SVG optimization from format task documentation --- lib/ArtNet | 1 + lib/AsyncWebServer_ESP32_W5500 | 1 + tasks.py | 8 ++------ 3 files changed, 4 insertions(+), 6 deletions(-) create mode 160000 lib/ArtNet create mode 160000 lib/AsyncWebServer_ESP32_W5500 diff --git a/lib/ArtNet b/lib/ArtNet new file mode 160000 index 0000000..5d9c42b --- /dev/null +++ b/lib/ArtNet @@ -0,0 +1 @@ +Subproject commit 5d9c42b531404ccfbcb14106d6312b03a166868a diff --git a/lib/AsyncWebServer_ESP32_W5500 b/lib/AsyncWebServer_ESP32_W5500 new file mode 160000 index 0000000..38de6ac --- /dev/null +++ b/lib/AsyncWebServer_ESP32_W5500 @@ -0,0 +1 @@ +Subproject commit 38de6ac248c7f270ca3b77ba38512ba39919aed8 diff --git a/tasks.py b/tasks.py index e97fe48..bc5e81f 100644 --- a/tasks.py +++ b/tasks.py @@ -85,12 +85,8 @@ def reset(c): @task def format(c): - """Format all source files using pre-commit hooks and optimize SVGs""" - print("\nOptimizing SVG files...") - c.run( - "find . -name '*.svg' -not -path './build/*' -not -path './managed_components/*' | xargs -r svgo --quiet --final-newline", - warn=True, - ) + """Format all source files using pre-commit hooks""" + is_windows = os.name == "nt" if is_windows: # Windows doesn't provide a POSIX pty From 523b285905d59e3aeeabc31714d4f1087afe1e1a Mon Sep 17 00:00:00 2001 From: RaffaelW <146560011+RaffaelW@users.noreply.github.com> Date: Sun, 15 Mar 2026 21:07:21 +0100 Subject: [PATCH 02/22] fix(tasks): import Exit exception for error handling in tasks --- tasks.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tasks.py b/tasks.py index bc5e81f..c5106d0 100644 --- a/tasks.py +++ b/tasks.py @@ -1,4 +1,5 @@ from invoke import task +from invoke.exceptions import Exit import os import shutil import subprocess From 2f8948954c6144fe6bdabb7c5c37b9f19d7df209 Mon Sep 17 00:00:00 2001 From: RaffaelW <146560011+RaffaelW@users.noreply.github.com> Date: Sun, 15 Mar 2026 21:53:48 +0100 Subject: [PATCH 03/22] remove outdated git submodules in lib --- lib/ArtNet | 1 - lib/AsyncWebServer_ESP32_W5500 | 1 - 2 files changed, 2 deletions(-) delete mode 160000 lib/ArtNet delete mode 160000 lib/AsyncWebServer_ESP32_W5500 diff --git a/lib/ArtNet b/lib/ArtNet deleted file mode 160000 index 5d9c42b..0000000 --- a/lib/ArtNet +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 5d9c42b531404ccfbcb14106d6312b03a166868a diff --git a/lib/AsyncWebServer_ESP32_W5500 b/lib/AsyncWebServer_ESP32_W5500 deleted file mode 160000 index 38de6ac..0000000 --- a/lib/AsyncWebServer_ESP32_W5500 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 38de6ac248c7f270ca3b77ba38512ba39919aed8 From a42a0cafc6f070241d694ee2563bbc7014c0e233 Mon Sep 17 00:00:00 2001 From: HendrikRauh <114620133+HendrikRauh@users.noreply.github.com> Date: Mon, 16 Mar 2026 18:48:31 +0100 Subject: [PATCH 04/22] refactor(tasks): remove format_check task --- tasks.py | 54 ------------------------------------------------------ 1 file changed, 54 deletions(-) diff --git a/tasks.py b/tasks.py index c5106d0..0c5fe52 100644 --- a/tasks.py +++ b/tasks.py @@ -96,60 +96,6 @@ def format(c): c.run("pre-commit run --all-files", pty=True) -@task -def format_check(c): - """Check if all files are formatted correctly""" - missing_tools = [] - format_errors = [] - - print("Checking C file formatting...") - result = c.run( - "find main components -name '*.c' -o -name '*.h' | xargs clang-format --dry-run --Werror", - warn=True, - ) - if result: - if result.return_code == 127: # Command not found - missing_tools.append("clang-format") - elif not result.ok: - format_errors.append("C files") - - print("Checking Python file formatting...") - result = c.run("black --check tasks.py", warn=True) - if result: - if result.return_code == 127: - missing_tools.append("black") - elif not result.ok: - format_errors.append("Python files") - - print("Checking Nix file formatting...") - result = c.run("nixfmt --check flake.nix", warn=True) - if result: - if result.return_code == 127: - missing_tools.append("nixfmt") - elif not result.ok: - format_errors.append("Nix files") - - print("Checking other file formatting...") - result = c.run("prettier --check '**/*.{js,json,yaml,yml,md,html,css}'", warn=True) - if result: - if result.return_code == 127: - missing_tools.append("prettier") - elif not result.ok: - format_errors.append("JS/JSON/YAML/HTML/CSS files") - - if missing_tools: - print(f"\n❌ ERROR: Missing formatting tools: {', '.join(missing_tools)}") - print("Please install them or reload the nix-shell.") - sys.exit(1) - - if format_errors: - print(f"\n❌ ERROR: Formatting issues in: {', '.join(format_errors)}") - print("Run 'invoke format' to fix them.") - sys.exit(1) - - print("\n✅ All files are correctly formatted!") - - @task(help={"o": "Open documentation in the default browser after generation."}) def docs(c, o=False): """Generate Doxygen documentation.""" From b13c7e6eb7d06e0faabc4e9e56ed19f61e3febd3 Mon Sep 17 00:00:00 2001 From: RaffaelW <146560011+RaffaelW@users.noreply.github.com> Date: Mon, 16 Mar 2026 18:49:18 +0100 Subject: [PATCH 05/22] fix(prettier): add directories to ignore for generated code and git submodule --- .prettierignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.prettierignore b/.prettierignore index ad65d50..84199a1 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,6 +1,8 @@ # Ignore build artifacts and generated files build/ managed_components/ +docs/doxygen/ +docs/external/ .direnv/ *.lock From fb12c8c3876faa5e06a74cd96d533f4fce84c7ea Mon Sep 17 00:00:00 2001 From: HendrikRauh <114620133+HendrikRauh@users.noreply.github.com> Date: Mon, 16 Mar 2026 18:58:11 +0100 Subject: [PATCH 06/22] fix(pre-commit): remove deprecated check-docstring-first hook --- .pre-commit-config.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b413fa2..3048387 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -33,7 +33,6 @@ repos: args: [--maxkb=1000] - id: check-ast - id: check-case-conflict - - id: check-docstring-first - id: check-json - id: check-merge-conflict - id: detect-private-key From 5517a49e14659d2be3abc6459e8ed04cc578b86a Mon Sep 17 00:00:00 2001 From: RaffaelW <146560011+RaffaelW@users.noreply.github.com> Date: Mon, 16 Mar 2026 19:15:18 +0100 Subject: [PATCH 07/22] fix(tasks): remove unused sys import from tasks.py --- tasks.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tasks.py b/tasks.py index 0c5fe52..1d8d4e9 100644 --- a/tasks.py +++ b/tasks.py @@ -3,7 +3,6 @@ from invoke.exceptions import Exit import os import shutil import subprocess -import sys import webbrowser From cebe56541a62c913b0d75d124addc31fffe31393 Mon Sep 17 00:00:00 2001 From: HendrikRauh <114620133+HendrikRauh@users.noreply.github.com> Date: Mon, 16 Mar 2026 21:59:46 +0100 Subject: [PATCH 08/22] feat(pre-commit): add cmake-format and cmake-lint hooks --- .pre-commit-config.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3048387..24c3359 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -98,3 +98,8 @@ repos: entry: nixfmt language: system types: [nix] + - repo: https://github.com/cheshirekow/cmake-format-precommit + rev: v0.6.10 + hooks: + - id: cmake-format + - id: cmake-lint From bab5725e921ec25dbfec5e48fdace7339aa9eb68 Mon Sep 17 00:00:00 2001 From: HendrikRauh <114620133+HendrikRauh@users.noreply.github.com> Date: Mon, 16 Mar 2026 21:59:55 +0100 Subject: [PATCH 09/22] style(CMake): reformat CMakeLists.txt for improved readability --- CMakeLists.txt | 10 ++++------ components/storage/CMakeLists.txt | 14 +++++++++----- components/web_server/CMakeLists.txt | 21 +++++++++++++++------ main/CMakeLists.txt | 5 ++--- 4 files changed, 30 insertions(+), 20 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0ec000b..d632be3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,10 +1,7 @@ -# For more information about build system see -# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html -# The following five lines of boilerplate have to be in your project's -# CMakeLists in this exact order for cmake to work correctly cmake_minimum_required(VERSION 3.16) -# Clear any stale EXTRA_COMPONENT_DIRS entries (e.g. leftover from previous runs or PlatformIO) +# Clear any stale EXTRA_COMPONENT_DIRS entries (e.g. leftover from previous runs +# or PlatformIO) set(EXTRA_COMPONENT_DIRS "") include($ENV{IDF_PATH}/tools/cmake/project.cmake) @@ -13,5 +10,6 @@ project(dmx-interface) # Enable LittleFS filesystem image creation for web assets if(COMMAND littlefs_create_partition_image) - littlefs_create_partition_image(storage ${CMAKE_SOURCE_DIR}/data FLASH_IN_PROJECT) + littlefs_create_partition_image(storage ${CMAKE_SOURCE_DIR}/data + FLASH_IN_PROJECT) endif() diff --git a/components/storage/CMakeLists.txt b/components/storage/CMakeLists.txt index 2cb9d94..03f4b47 100644 --- a/components/storage/CMakeLists.txt +++ b/components/storage/CMakeLists.txt @@ -1,6 +1,10 @@ idf_component_register( - SRCS "src/storage.c" - INCLUDE_DIRS "include" - REQUIRES joltwallet__littlefs - PRIV_REQUIRES vfs logger -) + SRCS + "src/storage.c" + INCLUDE_DIRS + "include" + REQUIRES + joltwallet__littlefs + PRIV_REQUIRES + vfs + logger) diff --git a/components/web_server/CMakeLists.txt b/components/web_server/CMakeLists.txt index 080d042..e390e13 100644 --- a/components/web_server/CMakeLists.txt +++ b/components/web_server/CMakeLists.txt @@ -1,7 +1,16 @@ idf_component_register( - SRCS "src/web_server.c" - "src/wifi.c" - INCLUDE_DIRS "include" - REQUIRES esp_http_server storage - PRIV_REQUIRES freertos esp_wifi esp_event esp_netif nvs_flash logger -) + SRCS + "src/web_server.c" + "src/wifi.c" + INCLUDE_DIRS + "include" + REQUIRES + esp_http_server + storage + PRIV_REQUIRES + freertos + esp_wifi + esp_event + esp_netif + nvs_flash + logger) diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 836026c..12392cc 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -1,3 +1,2 @@ -idf_component_register(SRCS "dmx-interface.c" - INCLUDE_DIRS "." - REQUIRES web_server) +idf_component_register(SRCS "dmx-interface.c" INCLUDE_DIRS "." REQUIRES + web_server) From 6c3dba7b5540a22e2aac26ade5f2c221815397ca Mon Sep 17 00:00:00 2001 From: HendrikRauh <114620133+HendrikRauh@users.noreply.github.com> Date: Mon, 16 Mar 2026 22:31:38 +0100 Subject: [PATCH 10/22] fix(.gitignore): add *.uf2 to ignore list for build artifacts --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 9c096f7..af425ae 100755 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ /build/ *.elf *.bin +*.uf2 *.hex *.map *.img From 8f5b6327bc3efdadb8542ecc7c4439da381f184c Mon Sep 17 00:00:00 2001 From: RaffaelW <146560011+RaffaelW@users.noreply.github.com> Date: Mon, 16 Mar 2026 23:26:26 +0100 Subject: [PATCH 11/22] feat(doxygen): add Doxygen code coverage script and pre-commit hook --- .pre-commit-config.yaml | 11 ++ Doxyfile | 1 + tools/doxy-coverage.py | 221 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 233 insertions(+) create mode 100755 tools/doxy-coverage.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3048387..3e87ebf 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -98,3 +98,14 @@ repos: entry: nixfmt language: system types: [nix] + + # Doxygen code coverage + - repo: local + hooks: + - id: doxygen-coverage + name: Doxygen code coverage + language: system + entry: tools/doxy-coverage.py + args: [docs/doxygen/xml, --no-error, --summary-only] + types_or: [c, c++, header] + verbose: true diff --git a/Doxyfile b/Doxyfile index d6d5211..b875352 100644 --- a/Doxyfile +++ b/Doxyfile @@ -17,6 +17,7 @@ USE_MDFILE_AS_MAINPAGE = README.md # Documentation settings GENERATE_LATEX = NO GENERATE_HTML = YES +GENERATE_XML=YES # doxygen-awesome-css settings HTML_EXTRA_STYLESHEET = docs/external/doxygen-awesome-css/doxygen-awesome.css \ diff --git a/tools/doxy-coverage.py b/tools/doxy-coverage.py new file mode 100755 index 0000000..92cae26 --- /dev/null +++ b/tools/doxy-coverage.py @@ -0,0 +1,221 @@ +#!/usr/bin/env python + +# -*- mode: python; coding: utf-8 -*- + +# Based on https://github.com/davatorium/doxy-coverage +# which was forked from https://github.com/alobbs/doxy-coverage +# and modified for use in this project. + +# All files in doxy-coverage are Copyright 2014 Alvaro Lopez Ortega. +# +# Authors: +# * Alvaro Lopez Ortega +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided +# with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +__author__ = "Alvaro Lopez Ortega" +__email__ = "alvaro@alobbs.com" +__copyright__ = "Copyright (C) 2014 Alvaro Lopez Ortega" + +from filecmp import cmp +import os +import sys +import argparse +import xml.etree.ElementTree as ET + +# Defaults +ACCEPTABLE_COVERAGE = 80 + +# Global +ns = None + + +def ERROR(*objs): + print("ERROR: ", *objs, end="\n", file=sys.stderr) + + +def FATAL(*objs): + ERROR(*objs) + sys.exit(0 if ns.no_error else 1) + + +def parse_file(fullpath): + tree = ET.parse(fullpath) + + sourcefile = None + definitions = {} + + for definition in tree.findall("./compounddef//memberdef"): + # Should it be documented + if definition.get("kind") == "function" and definition.get("static") == "yes": + continue + + # Is the definition documented? + documented = False + for k in ("briefdescription", "detaileddescription", "inbodydescription"): + if definition.findall(f"./{k}/"): + documented = True + break + + # Name + d_def = definition.find("./definition") + d_nam = definition.find("./name") + + if not sourcefile: + l = definition.find("./location") + if l is not None: + sourcefile = l.get("file") + + if d_def is not None: + name = d_def.text + elif d_nam is not None: + name = d_nam.text + else: + name = definition.get("id") + + # Aggregate + definitions[name] = documented + + if not sourcefile: + sourcefile = fullpath + + return (sourcefile, definitions) + + +def parse(path): + index_fp = os.path.join(path, "index.xml") + if not os.path.exists(index_fp): + FATAL("Documentation not present. Exiting.", index_fp) + + tree = ET.parse(index_fp) + + files = {} + for entry in tree.findall("compound"): + if entry.get("kind") == "dir": + continue + + file_fp = os.path.join(path, f"{entry.get('refid')}.xml") + sourcefile, definitions = parse_file(file_fp) + + if definitions != {}: + files[sourcefile] = definitions + + return files + + +def report(files, include_files, summary_only): + files_sorted = sorted(files.keys()) + + if len(include_files) != 0: + files_sorted = list(filter(lambda f: f in include_files, files_sorted)) + + if len(files_sorted) == 0: + FATAL("No files to report on. Exiting.") + + files_sorted.reverse() + + total_yes = 0 + total_no = 0 + + for f in files_sorted: + defs = files[f] + + doc_yes = len([d for d in defs.values() if d]) + doc_no = len([d for d in defs.values() if not d]) + doc_per = doc_yes * 100.0 / (doc_yes + doc_no) + + total_yes += doc_yes + total_no += doc_no + + if not summary_only: + print(f"{int(doc_per):3d}% - {f} - ({doc_yes} of {doc_yes + doc_no})") + + if None in defs: + del defs[None] + if not summary_only: + defs_sorted = sorted(defs.keys()) + for d in defs_sorted: + if not defs[d]: + print("\t", d) + + total_all = total_yes + total_no + total_percentage = total_yes * 100 / total_all + print( + f"{"" if summary_only else "\n"}{int(total_percentage)}% API documentation coverage" + ) + return (ns.threshold - total_percentage, 0)[total_percentage > ns.threshold] + + +def main(): + # Arguments + parser = argparse.ArgumentParser() + parser.add_argument( + "dir", action="store", help="Path to Doxygen's XML doc directory" + ) + parser.add_argument( + "include_files", + action="extend", + nargs="*", + help="List of files to check coverage for (Default: all files)", + type=str, + default=[], + ) + parser.add_argument( + "--no-error", + action="store_true", + help="Do not return error code after execution", + ) + parser.add_argument( + "--summary-only", + action="store_true", + help="Only print the summary of the coverage report, without listing the coverage of each file", + ) + parser.add_argument( + "--threshold", + action="store", + help=f"Min acceptable coverage percentage (Default: {ACCEPTABLE_COVERAGE})", + default=ACCEPTABLE_COVERAGE, + type=int, + ) + + global ns + ns = parser.parse_args() + if not ns: + FATAL("ERROR: Couldn't parse parameters") + + # Parse + files = parse(ns.dir) + + # Print report + err = report(files, ns.include_files, ns.summary_only) + if ns.no_error: + return + + sys.exit(round(err)) + + +if __name__ == "__main__": + main() From b8e76f4a9f565638a4b959de3242f5e346bcecb3 Mon Sep 17 00:00:00 2001 From: RaffaelW <146560011+RaffaelW@users.noreply.github.com> Date: Tue, 17 Mar 2026 23:37:41 +0100 Subject: [PATCH 12/22] feat(docs): add task to list Doxygen coverage of documentation --- tasks.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tasks.py b/tasks.py index 1d8d4e9..df0e41b 100644 --- a/tasks.py +++ b/tasks.py @@ -106,3 +106,11 @@ def docs(c, o=False): webbrowser.open(f"file://{os.path.abspath(path)}") return raise Exit(code=1) + + +@task +def docs_coverage(c): + """List doxygen coverage of documentation.""" + subprocess.run( + "python tools/doxy-coverage.py docs/doxygen/xml --no-error", shell=True + ) From 1588a25653181a5337ba8f3aca9156af0e6d272e Mon Sep 17 00:00:00 2001 From: psxde <12482765+psxde@users.noreply.github.com> Date: Wed, 18 Mar 2026 00:03:52 +0100 Subject: [PATCH 13/22] Update README.md Add screws --- README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 187faab..6df196d 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ | 1x | ♂️-DMX-socket | | 1x | ♀️-DMX-socket | -> Additionally you need: `some wires`, `soldering equipment`, `3D-printer`, `small screws`, `shrink tubing`, `hot glue gun` +> Additionally you need: `some wires`, `soldering equipment`, `3D-printer`, `small screws` (see case), `heat shrink tubing`, `hot glue gun` --- @@ -71,6 +71,12 @@ All print files (STL, STEP, X_T) can be found in [assets/case](/assets/case/). A ![Prusa Slicer with case loaded](/assets/case/Screenshot.png) +| Part | Screw | Count | +| ----------- | ------- | ----- | +| Case lid | M2x5 | 4x | +| ESP32 | M2x5 | 2x | +| W5500 | M2,5x5 | 2x | +| XLR sockets | M3+Nuts | 4x | --- ## 💡 Status LED From b512f9d3db30eb3dcaa74021ded635ef2a82c944 Mon Sep 17 00:00:00 2001 From: RaffaelW <146560011+RaffaelW@users.noreply.github.com> Date: Fri, 20 Mar 2026 20:57:25 +0100 Subject: [PATCH 14/22] fix(pre-commit): require serial execution for Doxygen coverage hook --- .pre-commit-config.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7fa8d84..4bb3308 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -116,3 +116,4 @@ repos: args: [docs/doxygen/xml, --no-error, --summary-only] types_or: [c, c++, header] verbose: true + require_serial: true From ec045f861018b99210718b32ae067cedf9235cd8 Mon Sep 17 00:00:00 2001 From: RaffaelW <146560011+RaffaelW@users.noreply.github.com> Date: Fri, 20 Mar 2026 21:25:07 +0100 Subject: [PATCH 15/22] feat(doxygen): set threshold for doxygen coverage and fail if coverage is below threshold --- .pre-commit-config.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4bb3308..c7b7034 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -110,10 +110,10 @@ repos: - repo: local hooks: - id: doxygen-coverage - name: Doxygen code coverage + name: doxygen code coverage language: system entry: tools/doxy-coverage.py - args: [docs/doxygen/xml, --no-error, --summary-only] + args: [docs/doxygen/xml, --threshold=40, --summary-only] types_or: [c, c++, header] - verbose: true + verbose: false require_serial: true From 94628b85233993bb084ba85bd6520f7310f368e0 Mon Sep 17 00:00:00 2001 From: RaffaelW <146560011+RaffaelW@users.noreply.github.com> Date: Fri, 20 Mar 2026 21:37:12 +0100 Subject: [PATCH 16/22] feat(doxygen): add option to generate Doxygen documentation before coverage check --- .pre-commit-config.yaml | 6 ++++-- bootloader | 1 + tools/doxy-coverage.py | 23 +++++++++++++++++++++++ 3 files changed, 28 insertions(+), 2 deletions(-) create mode 160000 bootloader diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c7b7034..bd66da0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,7 +11,8 @@ exclude: | .*\.o| flake.lock| dependencies.lock| - assets/case/ + assets/case/| + docs/doxygen/ ) repos: @@ -113,7 +114,8 @@ repos: name: doxygen code coverage language: system entry: tools/doxy-coverage.py - args: [docs/doxygen/xml, --threshold=40, --summary-only] + args: + [docs/doxygen/xml, --threshold=40, --summary-only, --generate-docs] types_or: [c, c++, header] verbose: false require_serial: true diff --git a/bootloader b/bootloader new file mode 160000 index 0000000..0bf633b --- /dev/null +++ b/bootloader @@ -0,0 +1 @@ +Subproject commit 0bf633be000a2b62796e071a18b93c1018d3e369 diff --git a/tools/doxy-coverage.py b/tools/doxy-coverage.py index 92cae26..ea42100 100755 --- a/tools/doxy-coverage.py +++ b/tools/doxy-coverage.py @@ -42,6 +42,7 @@ __copyright__ = "Copyright (C) 2014 Alvaro Lopez Ortega" from filecmp import cmp import os +import subprocess import sys import argparse import xml.etree.ElementTree as ET @@ -62,6 +63,20 @@ def FATAL(*objs): sys.exit(0 if ns.no_error else 1) +def generate_docs(): + print("Generating Doxygen documentation...") + proc = subprocess.run( + "doxygen Doxyfile", + shell=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + if proc.returncode == 0: + print("Documentation generated") + else: + FATAL("Failed to generate documentation. Exiting.") + + def parse_file(fullpath): tree = ET.parse(fullpath) @@ -200,12 +215,20 @@ def main(): default=ACCEPTABLE_COVERAGE, type=int, ) + parser.add_argument( + "--generate-docs", + action="store_true", + help="Generate Doxygen documentation before checking coverage", + ) global ns ns = parser.parse_args() if not ns: FATAL("ERROR: Couldn't parse parameters") + if ns.generate_docs: + generate_docs() + # Parse files = parse(ns.dir) From e878b7168e12e6266d83cab0f82e139995fbd7f7 Mon Sep 17 00:00:00 2001 From: Hendrik Rauh <114620133+HendrikRauh@users.noreply.github.com> Date: Fri, 20 Mar 2026 21:44:56 +0100 Subject: [PATCH 17/22] Add link to case section for the screws --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6df196d..c8227eb 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ | 1x | ♂️-DMX-socket | | 1x | ♀️-DMX-socket | -> Additionally you need: `some wires`, `soldering equipment`, `3D-printer`, `small screws` (see case), `heat shrink tubing`, `hot glue gun` +> Additionally you need: `some wires`, `soldering equipment`, `3D-printer`, `small screws` (see [case](##-case)), `heat shrink tubing`, `hot glue gun` --- From dc693088d00ab2ae3c391df8fbe9deb2d85baacb Mon Sep 17 00:00:00 2001 From: Hendrik Rauh <114620133+HendrikRauh@users.noreply.github.com> Date: Fri, 20 Mar 2026 21:45:27 +0100 Subject: [PATCH 18/22] Fix markdown syntax in README for links --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c8227eb..b3c8ce8 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ | 1x | ♂️-DMX-socket | | 1x | ♀️-DMX-socket | -> Additionally you need: `some wires`, `soldering equipment`, `3D-printer`, `small screws` (see [case](##-case)), `heat shrink tubing`, `hot glue gun` +> Additionally you need: `some wires`, `soldering equipment`, `3D-printer`, `small screws` (see [case](#-case)), `heat shrink tubing`, `hot glue gun` --- From 89a3f3e742c396cb50be2a0c01eb69b9373ac55e Mon Sep 17 00:00:00 2001 From: Hendrik Rauh <114620133+HendrikRauh@users.noreply.github.com> Date: Fri, 20 Mar 2026 21:58:57 +0100 Subject: [PATCH 19/22] Fix formatting in README.md table --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index b3c8ce8..0a664d6 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,7 @@ All print files (STL, STEP, X_T) can be found in [assets/case](/assets/case/). A | ESP32 | M2x5 | 2x | | W5500 | M2,5x5 | 2x | | XLR sockets | M3+Nuts | 4x | + --- ## 💡 Status LED From 0f72a8a2cab2393f18ba81084296b02867451886 Mon Sep 17 00:00:00 2001 From: RaffaelW <146560011+RaffaelW@users.noreply.github.com> Date: Fri, 20 Mar 2026 22:05:46 +0100 Subject: [PATCH 20/22] fix(doxygen): show files with missing documentation if pre-commit hook fails --- .pre-commit-config.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index bd66da0..9ad2571 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -114,8 +114,7 @@ repos: name: doxygen code coverage language: system entry: tools/doxy-coverage.py - args: - [docs/doxygen/xml, --threshold=40, --summary-only, --generate-docs] + args: [docs/doxygen/xml, --threshold=40, --generate-docs] types_or: [c, c++, header] verbose: false require_serial: true From f30fa4f13045407bdc596f82287c0e4079a95d63 Mon Sep 17 00:00:00 2001 From: HendrikRauh <114620133+HendrikRauh@users.noreply.github.com> Date: Fri, 20 Mar 2026 22:37:36 +0100 Subject: [PATCH 21/22] remove bootloader submodule --- bootloader | 1 - 1 file changed, 1 deletion(-) delete mode 160000 bootloader diff --git a/bootloader b/bootloader deleted file mode 160000 index 0bf633b..0000000 --- a/bootloader +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 0bf633be000a2b62796e071a18b93c1018d3e369 From 472b478afe74bef13273e31d3cd2933c75c03b2a Mon Sep 17 00:00:00 2001 From: HendrikRauh <114620133+HendrikRauh@users.noreply.github.com> Date: Fri, 20 Mar 2026 22:44:33 +0100 Subject: [PATCH 22/22] add doxygen and header guards --- components/logger/include/logger.h | 11 +++++- components/storage/include/storage.h | 5 +-- components/storage/src/storage.c | 5 ++- components/web_server/include/wifi.h | 18 +++++++++ components/web_server/src/web_server.c | 51 ++++++++++++++++++++++++++ components/web_server/src/wifi.c | 25 +++++++++++-- main/CMakeLists.txt | 10 ++++- main/dmx-interface.c | 22 +++++++---- 8 files changed, 126 insertions(+), 21 deletions(-) diff --git a/components/logger/include/logger.h b/components/logger/include/logger.h index 8090718..ecb88ea 100644 --- a/components/logger/include/logger.h +++ b/components/logger/include/logger.h @@ -1,4 +1,3 @@ - /** * @file logger.h * @brief Project-wide logging macros based on ESP-IDF's logging library. @@ -27,8 +26,12 @@ #include "esp_log.h" +#ifdef __cplusplus +extern "C" { +#endif + #ifndef LOG_TAG -#define LOG_TAG "CHAOS" +#define LOG_TAG "CHAOS" ///< Default log tag #endif /** @brief Log a message at Error level. */ @@ -41,3 +44,7 @@ #define LOGD(...) ESP_LOGD(LOG_TAG, __VA_ARGS__) /** @brief Log a message at Verbose level. */ #define LOGV(...) ESP_LOGV(LOG_TAG, __VA_ARGS__) + +#ifdef __cplusplus +} +#endif diff --git a/components/storage/include/storage.h b/components/storage/include/storage.h index 3910ada..84b826e 100644 --- a/components/storage/include/storage.h +++ b/components/storage/include/storage.h @@ -1,5 +1,4 @@ -#ifndef STORAGE_H -#define STORAGE_H +#pragma once #include "esp_err.h" @@ -24,5 +23,3 @@ const char *storage_get_mount_point(void); #ifdef __cplusplus } #endif - -#endif /* STORAGE_H */ diff --git a/components/storage/src/storage.c b/components/storage/src/storage.c index 5e40fc3..c7fae3c 100644 --- a/components/storage/src/storage.c +++ b/components/storage/src/storage.c @@ -1,11 +1,12 @@ -#define LOG_TAG "STORE" +#define LOG_TAG "STORE" ///< "STORE" log tag for this file #include "storage.h" #include "esp_littlefs.h" #include "esp_vfs.h" #include "logger.h" -static const char *LITTLEFS_MOUNT_POINT = "/data"; +static const char *LITTLEFS_MOUNT_POINT = + "/data"; ///< Mount point for LittleFS filesystem esp_err_t storage_init(void) { esp_vfs_littlefs_conf_t conf = { diff --git a/components/web_server/include/wifi.h b/components/web_server/include/wifi.h index 4876de7..6c348f5 100644 --- a/components/web_server/include/wifi.h +++ b/components/web_server/include/wifi.h @@ -6,8 +6,26 @@ extern "C" { #endif +/** + * @brief Start WiFi Access Point (AP) mode. + * + * Initializes and starts the WiFi AP with the given SSID and password. + * + * @param ssid SSID for the AP (1-32 characters) + * @param password Password for the AP (min. 8 characters, optional) + * @param channel WiFi channel to use + * @param max_connections Maximum number of client connections + * @return ESP_OK on success, ESP_ERR_INVALID_ARG or other error codes on + * failure + */ esp_err_t wifi_start_ap(const char *ssid, const char *password, uint8_t channel, uint8_t max_connections); + +/** + * @brief Stop WiFi Access Point (AP) mode. + * + * Deinitializes and stops the WiFi AP. + */ void wifi_stop_ap(void); #ifdef __cplusplus diff --git a/components/web_server/src/web_server.c b/components/web_server/src/web_server.c index 9d25638..6e2f3dc 100644 --- a/components/web_server/src/web_server.c +++ b/components/web_server/src/web_server.c @@ -1,5 +1,10 @@ #define LOG_TAG "WEBSRV" +/** + * @def LOG_TAG + * @brief Tag used for web server logging. + */ + #include "web_server.h" #include @@ -14,12 +19,34 @@ #include "storage.h" // Default configuration values +/** + * @brief Default port for the web server. + */ #define WEBSERVER_DEFAULT_PORT 80 +/** + * @brief Default port for the web server. + */ #define WEBSERVER_DEFAULT_MAX_HANDLERS 32 +/** + * @brief Default maximum number of URI handlers. + */ #define WEBSERVER_DEFAULT_STACK_SIZE (8 * 1024) +/** + * @brief Default stack size for the web server task. + */ #define WEBSERVER_DEFAULT_TASK_PRIORITY 5 +/** + * @brief Default task priority for the web server task. + */ +/** + * @brief Handle for the HTTP server instance. + */ static httpd_handle_t s_server_handle = NULL; + +/** + * @brief Handle for the FreeRTOS web server task. + */ static TaskHandle_t s_server_task_handle = NULL; /** @@ -127,6 +154,15 @@ static void webserver_task(void *arg) { vTaskDelete(NULL); } +/** + * @brief Start the web server with the given configuration. + * + * Initializes storage, configures the HTTP server, registers default handlers, + * and starts the FreeRTOS task for async operation. + * + * @param config Pointer to webserver configuration struct (optional) + * @return Handle to the running HTTP server, or NULL on failure + */ httpd_handle_t webserver_start(const webserver_config_t *config) { if (s_server_handle != NULL) { LOGW("Web server already running"); @@ -207,6 +243,13 @@ httpd_handle_t webserver_start(const webserver_config_t *config) { return s_server_handle; } +/** + * @brief Stop the web server and clean up resources. + * + * Stops the HTTP server and deletes the FreeRTOS task. + * + * @param server Handle to the HTTP server instance + */ void webserver_stop(httpd_handle_t server) { if (server == NULL) { return; @@ -224,6 +267,14 @@ void webserver_stop(httpd_handle_t server) { LOGI("Web server stopped"); } +/** + * @brief Register a URI handler with the web server. + * + * @param server Handle to the HTTP server instance + * @param uri_handler Pointer to the URI handler struct + * @return ESP_OK on success, ESP_ERR_INVALID_ARG or other error codes on + * failure + */ esp_err_t webserver_register_handler(httpd_handle_t server, const httpd_uri_t *uri_handler) { if (server == NULL || uri_handler == NULL) { diff --git a/components/web_server/src/wifi.c b/components/web_server/src/wifi.c index 222592f..d6ea2f5 100644 --- a/components/web_server/src/wifi.c +++ b/components/web_server/src/wifi.c @@ -1,6 +1,4 @@ -#define LOG_TAG "WIFI" - -#include "wifi.h" +#define LOG_TAG "WIFI" ///< "WIFI" log tag for this file #include @@ -9,9 +7,25 @@ #include "esp_wifi.h" #include "logger.h" #include "nvs_flash.h" +#include "wifi.h" +/** + * @brief Indicates whether the WiFi AP is started. + */ static bool s_wifi_started = false; +/** + * @brief Start WiFi Access Point (AP) mode. + * + * Initializes and starts the WiFi AP with the given SSID and password. + * + * @param ssid SSID for the AP (1-32 characters) + * @param password Password for the AP (min. 8 characters, optional) + * @param channel WiFi channel to use + * @param max_connections Maximum number of client connections + * @return ESP_OK on success, ESP_ERR_INVALID_ARG or other error codes on + * failure + */ esp_err_t wifi_start_ap(const char *ssid, const char *password, uint8_t channel, uint8_t max_connections) { if (s_wifi_started) { @@ -74,6 +88,11 @@ esp_err_t wifi_start_ap(const char *ssid, const char *password, uint8_t channel, return ESP_OK; } +/** + * @brief Stop WiFi Access Point (AP) mode. + * + * Deinitializes and stops the WiFi AP. + */ void wifi_stop_ap(void) { if (!s_wifi_started) { return; diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 12392cc..07d74e6 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -1,2 +1,8 @@ -idf_component_register(SRCS "dmx-interface.c" INCLUDE_DIRS "." REQUIRES - web_server) +idf_component_register( + SRCS + "dmx-interface.c" + INCLUDE_DIRS + "." + REQUIRES + web_server + logger) diff --git a/main/dmx-interface.c b/main/dmx-interface.c index 4ed7ccd..fb94c1b 100644 --- a/main/dmx-interface.c +++ b/main/dmx-interface.c @@ -1,32 +1,38 @@ +#define LOG_TAG "MAIN" ///< "MAIN" log tag for this file + #include #include "esp_err.h" -#include "esp_log.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" +#include "logger.h" #include "web_server.h" #include "wifi.h" -static const char *TAG = "MAIN"; - +/** + * @brief Main entry point for the DMX Interface application. + * + * Initializes WiFi Access Point and starts the web server. + * Keeps the application running indefinitely. + */ void app_main(void) { - ESP_LOGI(TAG, "DMX Interface starting..."); + LOGI("DMX Interface starting..."); esp_err_t wifi_err = wifi_start_ap("DMX", "mbgmbgmbg", 1, 4); if (wifi_err != ESP_OK) { - ESP_LOGE(TAG, "Failed to start WiFi AP: %s", esp_err_to_name(wifi_err)); + LOGE("Failed to start WiFi AP: %s", esp_err_to_name(wifi_err)); return; } // Start HTTP web server httpd_handle_t server = webserver_start(NULL); if (server == NULL) { - ESP_LOGE(TAG, "Failed to start web server!"); + LOGE("Failed to start web server!"); return; } - ESP_LOGI(TAG, "Web server started successfully"); - ESP_LOGI(TAG, "Open http://192.168.4.1 in your browser"); + LOGI("Web server started successfully"); + LOGI("Open http://192.168.4.1 in your browser"); // Keep the app running while (1) {