diff --git a/software/zig_main/.devcontainer/Dockerfile b/software/zig_main/.devcontainer/Dockerfile
new file mode 100644
index 0000000..56e1ae0
--- /dev/null
+++ b/software/zig_main/.devcontainer/Dockerfile
@@ -0,0 +1,71 @@
+ARG DOCKER_TAG=latest
+FROM espressif/idf:${DOCKER_TAG}
+
+ARG DEBIAN_FRONTEND=nointeractive
+ARG CONTAINER_USER=esp
+ARG USER_UID=1050
+ARG USER_GID=$USER_UID
+
+RUN apt-get update \
+ && apt install -y -q \
+ cmake \
+ git \
+ libglib2.0-0 \
+ libnuma1 \
+ libpixman-1-0 \
+ && rm -rf /var/lib/apt/lists/*
+
+# QEMU
+ENV QEMU_REL=esp_develop_9.2.2_20250817
+ENV QEMU_SHA256=588bfaccd0f929650655d10a580f020c6ba9c131712d8fa519280081b8d126eb
+ENV QEMU_DIST=qemu-xtensa-softmmu-${QEMU_REL}-x86_64-linux-gnu.tar.xz
+ENV QEMU_URL=https://github.com/espressif/qemu/releases/download/esp-develop-9.2.2-20250817/${QEMU_DIST}
+
+# Zig v0.16.0 - xtensa
+ENV ZIG_REL=zig-relsafe-x86_64-linux-musl-baseline
+ENV ZIG_SHA256=411f1858a9610803af28cd271acf4548873545ccc866f4b9060903ff0d4b6e8e
+ENV ZIG_DIST=${ZIG_REL}.tar.xz
+ENV ZIG_URL=https://github.com/kassane/zig-espressif-bootstrap/releases/download/0.16.0-xtensa/${ZIG_DIST}
+
+# ZLS v0.16.0
+ENV ZLS_REL=zls-x86_64-linux-musl-baseline
+ENV ZLS_SHA256=993192ee0e212334df0cc5ae4fc5e5bdfded395054414a4a7b0202e4dd3c74dd
+ENV ZLS_DIST=${ZLS_REL}.tar.xz
+ENV ZLS_URL=https://github.com/kassane/zig-espressif-bootstrap/releases/download/0.16.0-xtensa-dev/${ZLS_DIST}
+
+ENV LC_ALL=C.UTF-8
+ENV LANG=C.UTF-8
+
+RUN wget --no-verbose ${QEMU_URL} \
+ && echo "${QEMU_SHA256} *${QEMU_DIST}" | sha256sum --check --strict - \
+ && tar -xf $QEMU_DIST -C /opt \
+ && rm ${QEMU_DIST}
+
+RUN wget --no-verbose ${ZIG_URL} \
+ && echo "${ZIG_SHA256} *${ZIG_DIST}" | sha256sum --check --strict - \
+ && tar -xf $ZIG_DIST -C /opt \
+ && rm ${ZIG_DIST}
+
+RUN wget --no-verbose ${ZLS_URL} \
+ && echo "${ZLS_SHA256} *${ZLS_DIST}" | sha256sum --check --strict - \
+ && mkdir -p /opt/zls \
+ && tar -xf $ZLS_DIST -C /opt/zls \
+ && rm ${ZLS_DIST}
+
+ENV PATH=/opt/qemu/bin:/opt/${ZIG_REL}:/opt/zls/bin:${PATH}
+
+RUN groupadd --gid $USER_GID $CONTAINER_USER \
+ && adduser --uid $USER_UID --gid $USER_GID --disabled-password --gecos "" ${CONTAINER_USER} \
+ && usermod -a -G root $CONTAINER_USER && usermod -a -G dialout $CONTAINER_USER
+
+RUN chmod -R 775 /opt/esp/python_env/
+
+USER ${CONTAINER_USER}
+ENV USER=${CONTAINER_USER}
+WORKDIR /home/${CONTAINER_USER}
+
+RUN echo "source /opt/esp/idf/export.sh > /dev/null 2>&1" >> ~/.bashrc
+
+ENTRYPOINT [ "/opt/esp/entrypoint.sh" ]
+
+CMD ["/bin/bash", "-c"]
diff --git a/software/zig_main/.devcontainer/devcontainer.json b/software/zig_main/.devcontainer/devcontainer.json
new file mode 100644
index 0000000..b5035d0
--- /dev/null
+++ b/software/zig_main/.devcontainer/devcontainer.json
@@ -0,0 +1,51 @@
+{
+ "name": "ESP-IDF QEMU",
+ "build": {
+ "dockerfile": "Dockerfile",
+ "args": {
+ "DOCKER_TAG": "release-v6.0"
+ }
+ },
+ "mounts": [
+ // Unfortunately this mount only works for linux users:
+ "source=/dev/bus/usb,target=/dev/bus/usb,type=bind,consistency=cached"
+ ],
+ "customizations": {
+ "vscode": {
+ "settings": {
+ "terminal.integrated.defaultProfile.linux": "bash",
+ "idf.espIdfPath": "/opt/esp/idf",
+ "idf.customExtraPaths": "",
+ "idf.pythonBinPath": "/opt/esp/python_env/idf6.0_py3.14_env/bin/python",
+ "idf.toolsPath": "/opt/esp",
+ "zig.zls.path": "/opt/zls/bin/zls",
+ "zig.path": "/opt/zig-relsafe-x86_64-linux-musl-baseline/zig",
+ "idf.gitPath": "/usr/bin/git"
+ },
+ "extensions": [
+ "espressif.esp-idf-extension",
+ "ziglang.vscode-zig"
+ ]
+ },
+ "codespaces": {
+ "settings": {
+ "terminal.integrated.defaultProfile.linux": "bash",
+ "idf.espIdfPath": "/opt/esp/idf",
+ "idf.customExtraPaths": "",
+ "idf.pythonBinPath": "/opt/esp/python_env/idf6.0_py3.14_env/bin/python",
+ "idf.toolsPath": "/opt/esp",
+ "zig.zls.path": "/opt/zls/bin/zls",
+ "zig.path": "/opt/zig-relsafe-x86_64-linux-musl-baseline/zig",
+ "idf.gitPath": "/usr/bin/git"
+ },
+ "extensions": [
+ "espressif.esp-idf-extension",
+ "ziglang.vscode-zig",
+ "espressif.esp-idf-web"
+ ]
+ }
+ },
+ "runArgs": [
+ "--privileged"
+ ]
+}
\ No newline at end of file
diff --git a/software/zig_main/.gitignore b/software/zig_main/.gitignore
new file mode 100644
index 0000000..73b9802
--- /dev/null
+++ b/software/zig_main/.gitignore
@@ -0,0 +1,13 @@
+build/
+*zig-*/
+.vscode/
+.cache/
+sdkconfig
+*.old
+*.lock
+managed_components/
+imports/idf-sys.zig
+imports/helpers.zig
+imports/c_builtins.zig
+include/bindings.h
+!flake.lock
diff --git a/software/zig_main/CMakeLists.txt b/software/zig_main/CMakeLists.txt
new file mode 100644
index 0000000..f35304f
--- /dev/null
+++ b/software/zig_main/CMakeLists.txt
@@ -0,0 +1,10 @@
+# The following 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)
+
+include($ENV{IDF_PATH}/tools/cmake/project.cmake)
+
+if(NOT CMAKE_BUILD_TYPE)
+ set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Choose default type of build (Debug)" FORCE)
+endif()
+project(zig-sample-idf)
diff --git a/software/zig_main/LICENSE-APACHE b/software/zig_main/LICENSE-APACHE
new file mode 100644
index 0000000..f8e5e5e
--- /dev/null
+++ b/software/zig_main/LICENSE-APACHE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+Copyright [yyyy] [name of copyright owner]
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
\ No newline at end of file
diff --git a/software/zig_main/LICENSE-MIT b/software/zig_main/LICENSE-MIT
new file mode 100644
index 0000000..6e71434
--- /dev/null
+++ b/software/zig_main/LICENSE-MIT
@@ -0,0 +1,16 @@
+MIT No Attribution
+
+Copyright 2024 all Contributors
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this
+software and associated documentation files (the "Software"), to deal in the Software
+without restriction, including without limitation the rights to use, copy, modify,
+merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file
diff --git a/software/zig_main/README.md b/software/zig_main/README.md
new file mode 100644
index 0000000..a97e8ec
--- /dev/null
+++ b/software/zig_main/README.md
@@ -0,0 +1,150 @@
+[](https://deepwiki.com/kassane/zig-esp-idf-sample)
+
+# Using Zig Language & Toolchain with ESP-IDF
+
+| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C6 | ESP32-H2 | ESP32-P4 | ESP32-H4 | ESP32-S2 | ESP32-S3 |
+| ----------------- | ----- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
+
+## STATUS: Experimental
+
+## Description
+
+This project aims to integrate Zig language and toolchain with the [Espressif IoT Development Framework](https://github.com/espressif/esp-idf) for enhanced development capabilities on ESP32 and its variants.
+
+More information about building and using Zig with ESP-IDF can be found in the [documentation](docs/getting-started.md).
+
+## Prerequisites
+
+- [Zig](https://ziglang.org/download) toolchain - v0.16.0 or master
+- [ESP-IDF](https://github.com/espressif/esp-idf) - v5.x or v6.x or master
+
+### Targets Allowed
+
+
+
+
+ | Target |
+ Architecture |
+ Features |
+ Zig Build Configuration |
+
+
+
+
+ | ESP32 |
+ Xtensa LX6 |
+ Dual-core, WiFi, BT Classic, BLE |
+ -Dtarget=xtensa-freestanding-none -Dcpu=esp32 |
+
+
+ | ESP32-S2 |
+ Xtensa LX7 |
+ Single-core, WiFi, USB OTG |
+ -Dtarget=xtensa-freestanding-none -Dcpu=esp32s2 |
+
+
+ | ESP32-S3 |
+ Xtensa LX7 |
+ Dual-core, WiFi, BLE 5.0, USB OTG, AI |
+ -Dtarget=xtensa-freestanding-none -Dcpu=esp32s3 |
+
+
+ | ESP32-C2 |
+ RISC-V |
+ Single-core, WiFi, BLE 5.0, Low-cost |
+ -Dtarget=riscv32-freestanding-none -Dcpu=generic_rv32+m+c+zicsr+zifencei |
+
+
+ | ESP32-C3 |
+ RISC-V |
+ Single-core, WiFi, BLE 5.0, Low-power |
+
+
+ | ESP32-C5 |
+ RISC-V |
+ Single-core, WiFi 6, BLE 5.0 |
+ -Dtarget=riscv32-freestanding-none -Dcpu=generic_rv32+m+a+c+zicsr+zifencei |
+
+
+ | ESP32-C6 |
+ RISC-V |
+ Single-core, WiFi 6, BLE 5.0, Zigbee, Thread |
+
+
+ | ESP32-C61 |
+ RISC-V |
+ Single-core, WiFi 6, BLE 5.0, Low-cost |
+
+
+ | ESP32-H2 |
+ RISC-V |
+ BLE 5.0, Zigbee 3.0, Thread, No WiFi |
+
+
+ | ESP32-H21 |
+ RISC-V |
+ BLE 5.0, Zigbee 3.0, Thread, No WiFi |
+
+
+ | ESP32-H4 |
+ RISC-V |
+ BLE 5.2, Zigbee, Thread, FPU, No WiFi |
+ -Dtarget=riscv32-freestanding-eabihf -Dcpu=esp32h4 (Espressif fork) / generic_rv32+m+a+c+f+zicsr+zifencei (upstream) |
+
+
+ | ESP32-P4 |
+ RISC-V |
+ Dual-core, AI, DSP, FPU, No WiFi/BT |
+ -Dtarget=riscv32-freestanding-eabihf -Dcpu=esp32p4 (Espressif fork) / generic_rv32+m+a+c+f+zicsr+zifencei (upstream) |
+
+
+
+
+> [!WARNING]
+> **Xtensa Architecture Support**
+>
+> The upstream [Zig compiler](https://ziglang.org/download) (LLVM backend) does not support Xtensa architecture. For ESP32, ESP32-S2, and ESP32-S3 targets, you must use the [Espressif Zig fork](https://github.com/kassane/zig-espressif-bootstrap/releases).
+>
+> - **RISC-V targets (all variants):** Works with upstream Zig ✅ (uses generic_rv32 fallback); named CPU models available with Espressif fork
+> - **Xtensa targets (ESP32/S2/S3):** Requires [zig-xtensa](https://github.com/kassane/zig-espressif-bootstrap/releases) (auto-downloaded)
+>
+> The build system automatically downloads the correct toolchain for your target.
+
+
+### Key Features:
+
+- **Zig Language Integration**: Use the Zig programming language to write firmware code. It provides modern language features such as comptime, meta-programming, and error handling.
+
+- **Zig Toolchain Integration**: The Zig toolchain can be used to build zig libraries and executables, and can also be integrated with the ESP-IDF build system. Also, system compiler and linker can be replaced to `zig cc`/`zig c++`.
+ - **Note:** For C++ support, zig toolchain uses `llvm-libc++` ABI by default.
+
+- **ESP-IDF Compatibility**: Seamlessly integrate Zig with the ESP-IDF framework, allowing developers to leverage the rich set of APIs and functionalities provided by ESP-IDF for IoT development.
+
+- **Build System Configuration**: Using CMake to build Zig libraries allows easy integration with existing ESP-IDF projects while providing efficient dependency management and build configuration.
+
+- **Cross-Platform Development**: Facilitate development across various ESP32 variants including ESP32-C2, ESP32-C3, ESP32-C5, ESP32-C6, ESP32-H2, ESP32-P4, ESP32-S2, and ESP32-S3, ensuring broad compatibility and versatility.
+
+
+### About Allocators
+
+> [!NOTE]
+>
+> Asserts allocations are within `@alignOf(std.c.max_align_t)` and directly calls
+> `malloc`/`free`. Does not attempt to utilize `malloc_usable_size`.
+>
+> - `std.heap.raw_c_allocator` allocator is safe to use as the backing allocator with `std.heap.ArenaAllocator` for example and is more optimal in such a case than `std.heap.c_allocator`. - ref.: [std-doc](https://ziglang.org/documentation/0.15.2/std/#std.heap.raw_c_allocator)
+>
+> - `std.heap.ArenaAllocator` takes an existing allocator, wraps it, and provides an interface where you can allocate without freeing, and then free it all together. - ref.: [std-doc](https://ziglang.org/documentation/master/std/#std.heap.ArenaAllocator)
+>
+> **Custom Allocators** (based on `std.heap.raw_c_allocator`)
+>
+> - `idf.heap.HeapCapsAllocator` - ref.: [espressif-doc](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/mem_alloc.html)
+> - `idf.heap.MultiHeapAllocator` - ref.: [espressif-doc](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/mem_alloc.html)
+> - `idf.heap.VPortAllocator` - ref.: [FreeRTOS-doc](https://www.freertos.org/a00111.html)
+
+
+### License
+
+This project is licensed twice:
+- [Apache](LICENSE-APACHE)
+- [MIT-0](LICENSE-MIT)
\ No newline at end of file
diff --git a/software/zig_main/build.zig b/software/zig_main/build.zig
new file mode 100644
index 0000000..52818aa
--- /dev/null
+++ b/software/zig_main/build.zig
@@ -0,0 +1,258 @@
+const std = @import("std");
+
+pub fn build(b: *std.Build) !void {
+ const target = b.standardTargetOptions(.{
+ .whitelist = espressif_targets,
+ .default_target = espressif_targets[0],
+ });
+ const optimize = b.standardOptimizeOption(.{});
+
+ const example = b.option([]const u8, "example", "Relative path (from project root) to the Zig source file") orelse "main/app.zig";
+
+ const obj = b.addObject(.{
+ .name = "app_zig",
+ .root_module = b.createModule(.{
+ .root_source_file = b.path(example),
+ .target = target,
+ .optimize = optimize,
+ .link_libc = true,
+ }),
+ });
+ obj.root_module.addImport("esp_idf", idf_wrapped_modules(b));
+
+ const obj_install = b.addInstallArtifact(obj, .{
+ .dest_dir = .{
+ .override = .{
+ .custom = "obj",
+ },
+ },
+ });
+ b.getInstallStep().dependOn(&obj_install.step);
+}
+
+// ---------------------------------------------------------------------------
+// Module descriptor table
+//
+// To add a new module:
+// 1. Add a row here with the .zig source file and any deps by name.
+// 2. Add the name to the `esp_idf` entry's deps list if it should be
+// re-exported through the top-level "esp_idf" namespace module.
+//
+// To add a new dependency inside an existing .zig source file:
+// 1. Add the dep name to the matching row's `deps` field here.
+// That is the *only* place you need to edit — no other blocks to touch.
+// ---------------------------------------------------------------------------
+const ModuleSpec = struct {
+ /// Name used for @import("…") and as the key in the resolver map.
+ name: []const u8,
+ /// Path relative to the `imports/` directory.
+ file: []const u8,
+ /// Names of other modules (must appear earlier in this table).
+ deps: []const []const u8 = &.{},
+};
+
+/// Ordered list — a module may only reference deps that appear before it.
+const module_specs = [_]ModuleSpec{
+ // ── leaf (no deps) ──────────────────────────────────────────────────
+ .{ .name = "sys", .file = "idf-sys.zig" },
+ // ── depend on sys only ──────────────────────────────────────────────
+ .{ .name = "error", .file = "error.zig", .deps = &.{"sys"} },
+ .{ .name = "log", .file = "logger.zig", .deps = &.{"sys"} },
+ .{ .name = "ver", .file = "version.zig", .deps = &.{"sys"} },
+ .{ .name = "heap", .file = "heap.zig", .deps = &.{"sys"} },
+ .{ .name = "bootloader", .file = "bootloader.zig", .deps = &.{"sys"} },
+ .{ .name = "lwip", .file = "lwip.zig", .deps = &.{"sys"} },
+ .{ .name = "mqtt", .file = "mqtt.zig", .deps = &.{"sys"} },
+ .{ .name = "phy", .file = "phy.zig", .deps = &.{"sys"} },
+ .{ .name = "segger", .file = "segger.zig", .deps = &.{"sys"} },
+ .{ .name = "crc", .file = "crc.zig", .deps = &.{"sys"} },
+ // ── depend on sys + error ───────────────────────────────────────────
+ .{ .name = "bluetooth", .file = "bluetooth.zig", .deps = &.{ "sys", "error" } },
+ .{ .name = "led", .file = "led-strip.zig", .deps = &.{ "sys", "error" } },
+ .{ .name = "wifi", .file = "wifi.zig", .deps = &.{ "sys", "error" } },
+ .{ .name = "gpio", .file = "gpio.zig", .deps = &.{ "sys", "error" } },
+ .{ .name = "uart", .file = "uart.zig", .deps = &.{ "sys", "error" } },
+ .{ .name = "i2c", .file = "i2c.zig", .deps = &.{ "sys", "error" } },
+ .{ .name = "i2s", .file = "i2s.zig", .deps = &.{ "sys", "error" } },
+ .{ .name = "spi", .file = "spi.zig", .deps = &.{ "sys", "error" } },
+ .{ .name = "now", .file = "now.zig", .deps = &.{ "sys", "error" } },
+ .{ .name = "pulse", .file = "pcnt.zig", .deps = &.{ "sys", "error" } },
+ .{ .name = "http", .file = "http.zig", .deps = &.{ "sys", "error" } },
+ .{ .name = "dsp", .file = "dsp.zig", .deps = &.{ "sys", "error" } },
+ .{ .name = "hosted", .file = "hosted.zig", .deps = &.{ "sys", "error" } },
+ .{ .name = "wifi_remote", .file = "wifi_remote.zig", .deps = &.{ "sys", "error" } },
+ .{ .name = "pthread", .file = "pthread.zig", .deps = &.{ "sys", "error" } },
+ .{ .name = "timer", .file = "timer.zig", .deps = &.{ "sys", "error" } },
+ .{ .name = "ledc", .file = "ledc.zig", .deps = &.{ "sys", "error" } },
+ .{ .name = "twai", .file = "twai.zig", .deps = &.{ "sys", "error" } },
+ .{ .name = "pm", .file = "pm.zig", .deps = &.{ "sys", "error" } },
+ .{ .name = "matter", .file = "matter.zig", .deps = &.{ "sys", "error" } },
+ .{ .name = "rtos", .file = "rtos.zig", .deps = &.{ "sys", "error" } },
+ .{ .name = "nvs", .file = "nvs.zig", .deps = &.{ "sys", "error" } },
+ .{ .name = "partition", .file = "partition.zig", .deps = &.{ "sys", "error" } },
+ .{ .name = "sleep", .file = "sleep.zig", .deps = &.{ "sys", "error" } },
+ .{ .name = "event", .file = "event.zig", .deps = &.{ "sys", "error" } },
+ .{ .name = "wdt", .file = "wdt.zig", .deps = &.{ "sys", "error" } },
+ .{ .name = "nimble", .file = "nimble.zig", .deps = &.{ "sys", "error" } },
+ // ── depend on sys + log ────────────────────────
+ .{ .name = "panic", .file = "panic.zig", .deps = &.{ "sys", "log" } },
+};
+
+/// Names re-exported by the top-level "esp_idf" umbrella module (idf.zig).
+const esp_idf_exports = [_][]const u8{
+ "sys", "error", "log", "ver", "heap", "bootloader", "lwip", "mqtt",
+ "phy", "segger", "crc", "bluetooth", "led", "wifi", "gpio", "uart",
+ "i2c", "i2s", "spi", "now", "pulse", "http", "dsp", "panic",
+ "rtos", "nvs", "partition", "sleep", "event", "wdt", "nimble", "hosted",
+ "wifi_remote", "timer", "ledc", "twai", "pm", "pthread", "matter",
+};
+
+pub fn idf_wrapped_modules(b: *std.Build) *std.Build.Module {
+ const src_path = std.fs.path.dirname(@src().file) orelse b.pathResolve(&.{"."});
+ const imports_dir = b.pathJoin(&.{ src_path, "imports" });
+
+ // Build a name → *Module map so deps can be looked up by name.
+ var map = std.StringHashMap(*std.Build.Module).init(b.allocator);
+ defer map.deinit();
+
+ inline for (module_specs) |spec| {
+ // Collect this module's imports from the already-resolved map.
+ var imports: std.ArrayList(std.Build.Module.Import) = .empty;
+ defer imports.deinit(b.allocator);
+
+ inline for (spec.deps) |dep_name| {
+ const dep_mod = map.get(dep_name) orelse
+ @panic("dep '" ++ dep_name ++ "' not yet resolved — check ordering in module_specs");
+ imports.append(b.allocator, .{ .name = dep_name, .module = dep_mod }) catch @panic("OOM");
+ }
+
+ const mod = b.addModule(spec.name, .{
+ .root_source_file = b.path(b.pathJoin(&.{ imports_dir, spec.file })),
+ .imports = imports.items,
+ });
+ map.put(spec.name, mod) catch @panic("OOM");
+ }
+
+ // Build the esp_idf umbrella module's import list.
+ var top_imports: std.ArrayList(std.Build.Module.Import) = .empty;
+ defer top_imports.deinit(b.allocator);
+
+ inline for (esp_idf_exports) |name| {
+ const mod = map.get(name) orelse
+ @panic("export '" ++ name ++ "' not found in module_specs");
+ top_imports.append(b.allocator, .{ .name = name, .module = mod }) catch @panic("OOM");
+ }
+
+ return b.addModule("esp_idf", .{
+ .root_source_file = b.path(b.pathJoin(&.{ imports_dir, "idf.zig" })),
+ .imports = top_imports.items,
+ });
+}
+
+pub const espressif_targets: []const std.Target.Query =
+ if (hasEspXtensaSupport()) riscv_targets ++ xtensa_targets else riscv_targets;
+
+const riscv_targets: []const std.Target.Query = blk: {
+ var result: []const std.Target.Query = &.{};
+
+ // Named ESP32 RISC-V CPU models — available when built with the Espressif Zig fork.
+ // MC group (c2, c3): m+c+zicsr+zifencei abi=none
+ // MAC group (c5, c6, c61, h2, h21): m+a+c+zicsr+zifencei abi=none
+ // MACF group (h4, s31, p4, p4eco4): m+a+c+f+zicsr+zifencei abi=eabihf
+ const plain_models = .{
+ "esp32c2", "esp32c3",
+ "esp32c5", "esp32c6",
+ "esp32c61", "esp32c61eco0",
+ "esp32h2", "esp32h21",
+ };
+ for (plain_models) |name| {
+ if (@hasDecl(std.Target.riscv.cpu, name)) {
+ result = result ++ &[_]std.Target.Query{.{
+ .cpu_arch = .riscv32,
+ .cpu_model = .{ .explicit = &@field(std.Target.riscv.cpu, name) },
+ .os_tag = .freestanding,
+ .abi = .none,
+ }};
+ }
+ }
+
+ // MACF group: has FPU — use eabihf ABI.
+ const macf_models = .{
+ "esp32h4", "esp32p4",
+ "esp32p4eco4", "esp32s31",
+ };
+ for (macf_models) |name| {
+ if (@hasDecl(std.Target.riscv.cpu, name)) {
+ result = result ++ &[_]std.Target.Query{.{
+ .cpu_arch = .riscv32,
+ .cpu_model = .{ .explicit = &@field(std.Target.riscv.cpu, name) },
+ .os_tag = .freestanding,
+ .abi = .eabihf,
+ }};
+ }
+ }
+
+ // Generic fallback targets for standard Zig builds without named ESP models.
+ if (result.len == 0) {
+ result = &[_]std.Target.Query{
+ // MC: c2, c3
+ .{
+ .cpu_arch = .riscv32,
+ .cpu_model = .{ .explicit = &std.Target.riscv.cpu.generic_rv32 },
+ .os_tag = .freestanding,
+ .abi = .none,
+ .cpu_features_add = std.Target.riscv.featureSet(&.{ .m, .c, .zifencei, .zicsr }),
+ },
+ // MAC: c5, c6, c61, h2, h21
+ .{
+ .cpu_arch = .riscv32,
+ .cpu_model = .{ .explicit = &std.Target.riscv.cpu.generic_rv32 },
+ .os_tag = .freestanding,
+ .abi = .none,
+ .cpu_features_add = std.Target.riscv.featureSet(&.{ .m, .a, .c, .zifencei, .zicsr }),
+ },
+ // MACF: h4, s31, p4, p4eco4
+ .{
+ .cpu_arch = .riscv32,
+ .cpu_model = .{ .explicit = &std.Target.riscv.cpu.generic_rv32 },
+ .os_tag = .freestanding,
+ .abi = .eabihf,
+ .cpu_features_add = std.Target.riscv.featureSet(&.{ .m, .a, .c, .f, .zifencei, .zicsr }),
+ },
+ };
+ }
+
+ break :blk result;
+};
+
+const xtensa_targets: []const std.Target.Query = &.{
+ // ESP32: Requires Espressif LLVM fork
+ .{
+ .cpu_arch = .xtensa,
+ .cpu_model = .{ .explicit = &std.Target.xtensa.cpu.esp32 },
+ .os_tag = .freestanding,
+ .abi = .none,
+ },
+ // ESP32-S2: Requires Espressif LLVM fork
+ .{
+ .cpu_arch = .xtensa,
+ .cpu_model = .{ .explicit = &std.Target.xtensa.cpu.esp32s2 },
+ .os_tag = .freestanding,
+ .abi = .none,
+ },
+ // ESP32-S3: Requires Espressif LLVM fork
+ .{
+ .cpu_arch = .xtensa,
+ .cpu_model = .{ .explicit = &std.Target.xtensa.cpu.esp32s3 },
+ .os_tag = .freestanding,
+ .abi = .none,
+ },
+};
+
+/// Checks if the Zig compiler has Espressif Xtensa support enabled
+fn hasEspXtensaSupport() bool {
+ for (std.Target.Cpu.Arch.xtensa.allCpuModels()) |model| {
+ if (std.mem.startsWith(u8, model.name, "esp")) return true;
+ }
+ return false;
+}
diff --git a/software/zig_main/build.zig.zon b/software/zig_main/build.zig.zon
new file mode 100644
index 0000000..1f90d41
--- /dev/null
+++ b/software/zig_main/build.zig.zon
@@ -0,0 +1,54 @@
+.{
+ .name = .esp_idf_sample,
+ .version = "0.1.0",
+ .fingerprint = 0xbe5a38cf3bc380ae,
+ // This field is optional.
+ // Each dependency must either provide a `url` and `hash`, or a `path`.
+ // `zig build --fetch` can be used to fetch all dependencies of a package, recursively.
+ // Once all dependencies are fetched, `zig build` no longer requires
+ // internet connectivity.
+ .dependencies = .{
+ // See `zig fetch --save ` for a command-line interface for adding dependencies.
+ //.example = .{
+ // // When updating this field to a new URL, be sure to delete the corresponding
+ // // `hash`, otherwise you are communicating that you expect to find the old hash at
+ // // the new URL.
+ // .url = "https://example.com/foo.tar.gz",
+ //
+ // // This is computed from the file contents of the directory of files that is
+ // // obtained after fetching `url` and applying the inclusion rules given by
+ // // `paths`.
+ // //
+ // // This field is the source of truth; packages do not come from a `url`; they
+ // // come from a `hash`. `url` is just one of many possible mirrors for how to
+ // // obtain a package matching this `hash`.
+ // //
+ // // Uses the [multihash](https://multiformats.io/multihash/) format.
+ // .hash = "...",
+ //
+ // // When this is provided, the package is found in a directory relative to the
+ // // build root. In this case the package's hash is irrelevant and therefore not
+ // // computed. This field and `url` are mutually exclusive.
+ // .path = "foo",
+ //},
+ },
+ // Specifies the set of files and directories that are included in this package.
+ // Only files and directories listed here are included in the `hash` that
+ // is computed for this package.
+ // Paths are relative to the build root. Use the empty string (`""`) to refer to
+ // the build root itself.
+ // A directory listed here means that all files within, recursively, are included.
+ .paths = .{
+ // This makes *all* files, recursively, included in this package. It is generally
+ // better to explicitly list the files and directories instead, to insure that
+ // fetching from tarballs, file system paths, and version control all result
+ // in the same contents hash.
+ "main",
+ "imports",
+ "build.zig",
+ "build.zig.zon",
+ "LICENSE-APACHE",
+ "LICENSE-MIT",
+ "README.md",
+ },
+}
diff --git a/software/zig_main/cmake/bindgen-standalone.cmake b/software/zig_main/cmake/bindgen-standalone.cmake
new file mode 100644
index 0000000..8b3e27e
--- /dev/null
+++ b/software/zig_main/cmake/bindgen-standalone.cmake
@@ -0,0 +1,158 @@
+# ──────────────────────────────────────────────────────────────────────────────
+# Download translate_c-standalone
+# ──────────────────────────────────────────────────────────────────────────────
+
+# Determine platform triple for translate_c-standalone naming
+if(ZIG_PLATFORM STREQUAL "linux-musl")
+ if(ZIG_ARCH STREQUAL "aarch64")
+ set(TRANSLATE_C_TRIPLE "aarch64-linux-musl")
+ set(HASH_SUM "7d5968a41a02064d90035db95ef102fc2ab5e71a2eb48ba6cf028eb79b4ae43d")
+ elseif(ZIG_ARCH STREQUAL "x86_64")
+ set(TRANSLATE_C_TRIPLE "x86_64-linux-musl")
+ set(HASH_SUM "d098e2675fc6706914b6e53dd72f6536895420460f287e73e19772c979fabb84")
+ else()
+ message(FATAL_ERROR "Unsupported architecture for translate_c-standalone: ${ZIG_ARCH}")
+ endif()
+ set(ARCHIVE_EXT "tar.xz")
+elseif(ZIG_PLATFORM STREQUAL "windows")
+ if(ZIG_ARCH STREQUAL "x86_64")
+ set(TRANSLATE_C_TRIPLE "x86_64-windows-gnu")
+ set(HASH_SUM "8e53a03ee44983eea2786c2ba028e83a816b9a993a768b3f4ca8c6e3525923e2")
+ else()
+ message(FATAL_ERROR "Unsupported architecture for translate_c-standalone: ${ZIG_ARCH}")
+ endif()
+ set(ARCHIVE_EXT "zip")
+elseif(ZIG_PLATFORM STREQUAL "macos")
+ if(ZIG_ARCH STREQUAL "aarch64")
+ set(TRANSLATE_C_TRIPLE "aarch64-macos")
+ set(HASH_SUM "8b3d2360d09add35f764ac138e0ec497503dc3de788a01e93fd7b9923a44369c")
+ else()
+ message(FATAL_ERROR "Unsupported architecture for translate_c-standalone: ${ZIG_ARCH}")
+ endif()
+ set(ARCHIVE_EXT "tar.xz")
+else()
+ message(FATAL_ERROR "Unsupported platform for translate_c-standalone: ${ZIG_PLATFORM}")
+endif()
+
+set(TRANSLATE_C_DIR "${CMAKE_BINARY_DIR}/translate_c-standalone")
+set(TRANSLATE_C_ARCHIVE "${CMAKE_BINARY_DIR}/translate_c-standalone-${TRANSLATE_C_TRIPLE}-baseline.${ARCHIVE_EXT}")
+set(TRANSLATE_C_BIN "${TRANSLATE_C_DIR}/bin")
+
+# Download if not already extracted
+if(NOT EXISTS "${TRANSLATE_C_BIN}")
+ set(TRANSLATE_C_URL "https://github.com/kassane/zig-espressif-bootstrap/releases/download/0.16.0-xtensa-dev/translate_c-standalone-${TRANSLATE_C_TRIPLE}-baseline.${ARCHIVE_EXT}")
+
+ message(STATUS "Downloading translate_c-standalone (${TRANSLATE_C_TRIPLE}):")
+ message(STATUS " => ${TRANSLATE_C_ARCHIVE}")
+
+ file(DOWNLOAD "${TRANSLATE_C_URL}" "${TRANSLATE_C_ARCHIVE}"
+ TLS_VERIFY ON
+ EXPECTED_HASH SHA256=${HASH_SUM}
+ STATUS download_status
+ LOG download_log
+ )
+
+ list(GET download_status 0 dl_code)
+ if(NOT dl_code EQUAL 0)
+ message(FATAL_ERROR "Download failed:\n${download_log}")
+ endif()
+
+ message(STATUS "Extracting ${ARCHIVE_EXT} ...")
+ file(MAKE_DIRECTORY "${TRANSLATE_C_DIR}")
+
+ if(HOST_OS_LOWER MATCHES "windows|win")
+ execute_process(
+ COMMAND powershell -NoProfile -ExecutionPolicy Bypass
+ -Command "Expand-Archive -Path '${TRANSLATE_C_ARCHIVE}' -DestinationPath '${TRANSLATE_C_DIR}' -Force"
+ RESULT_VARIABLE extract_result
+ )
+ else()
+ execute_process(
+ COMMAND ${CMAKE_COMMAND} -E tar xf "${TRANSLATE_C_ARCHIVE}"
+ WORKING_DIRECTORY "${TRANSLATE_C_DIR}"
+ RESULT_VARIABLE extract_result
+ )
+ endif()
+
+ if(NOT extract_result EQUAL 0)
+ message(FATAL_ERROR "Extraction failed (code ${extract_result})")
+ endif()
+
+ file(REMOVE "${TRANSLATE_C_ARCHIVE}")
+
+ # Make executable on Unix
+ if(NOT HOST_OS_LOWER MATCHES "windows|win")
+ execute_process(
+ COMMAND chmod +x "${TRANSLATE_C_BIN}"
+ RESULT_VARIABLE chmod_result
+ )
+ if(NOT chmod_result EQUAL 0)
+ message(WARNING "Failed to set executable bit on translate_c-standalone")
+ endif()
+ endif()
+
+ message(STATUS "translate_c-standalone ready at: ${TRANSLATE_C_BIN}")
+else()
+ message(STATUS "Using cached translate_c-standalone: ${TRANSLATE_C_BIN}")
+endif()
+set(BINDGEN "${TRANSLATE_C_BIN}/translate-c")
+
+# ──────────────────────────────────────────────────────────────────────────────
+# Helper to invoke translate-c (mirrors zig_run from zig-runner.cmake)
+# ──────────────────────────────────────────────────────────────────────────────
+function(bindgen_run)
+ cmake_parse_arguments(PARSE_ARGV 0 ARG
+ "VERBATIM;ALLOW_FAIL"
+ "WORKING_DIRECTORY;RESULT_VARIABLE;OUTPUT_VARIABLE;ERROR_VARIABLE;OUTPUT_FILE;TIMEOUT"
+ "COMMAND"
+ )
+ if(NOT ARG_COMMAND)
+ message(FATAL_ERROR "bindgen_run: COMMAND list is required")
+ endif()
+ if(NOT DEFINED ARG_WORKING_DIRECTORY)
+ set(ARG_WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}")
+ endif()
+ if(NOT DEFINED ARG_TIMEOUT)
+ set(ARG_TIMEOUT 300)
+ endif()
+ set(extra_args)
+ if(ARG_VERBATIM)
+ list(APPEND extra_args VERBATIM)
+ endif()
+ if(ARG_OUTPUT_FILE)
+ list(APPEND extra_args OUTPUT_FILE "${ARG_OUTPUT_FILE}")
+ endif()
+ execute_process(
+ COMMAND "${BINDGEN}" ${ARG_COMMAND}
+ WORKING_DIRECTORY "${ARG_WORKING_DIRECTORY}"
+ RESULT_VARIABLE result
+ OUTPUT_VARIABLE output
+ ERROR_VARIABLE error
+ TIMEOUT ${ARG_TIMEOUT}
+ OUTPUT_STRIP_TRAILING_WHITESPACE
+ ERROR_STRIP_TRAILING_WHITESPACE
+ ${extra_args}
+ )
+ if(DEFINED ARG_RESULT_VARIABLE)
+ set(${ARG_RESULT_VARIABLE} "${result}" PARENT_SCOPE)
+ endif()
+ if(DEFINED ARG_OUTPUT_VARIABLE)
+ set(${ARG_OUTPUT_VARIABLE} "${output}" PARENT_SCOPE)
+ endif()
+ if(DEFINED ARG_ERROR_VARIABLE)
+ set(${ARG_ERROR_VARIABLE} "${error}" PARENT_SCOPE)
+ endif()
+ if(NOT ARG_ALLOW_FAIL)
+ if(result MATCHES "timeout")
+ message(FATAL_ERROR
+ "translate-c timed out after ${ARG_TIMEOUT}s — Aro deadlock on stub headers (issue #61)\n"
+ " Command: ${BINDGEN} ${ARG_COMMAND}\n"
+ "--- stderr ---\n${error}")
+ elseif(NOT result EQUAL 0)
+ message(FATAL_ERROR
+ "translate-c failed (code ${result}):\n"
+ " ${BINDGEN} ${ARG_COMMAND}\n"
+ "--- stderr ---\n${error}")
+ endif()
+ endif()
+endfunction()
diff --git a/software/zig_main/cmake/bindings.cmake b/software/zig_main/cmake/bindings.cmake
new file mode 100644
index 0000000..801ce1b
--- /dev/null
+++ b/software/zig_main/cmake/bindings.cmake
@@ -0,0 +1,32 @@
+# ────────────────────────────────────────────────────────────────────────────────
+# Helper for downloading / referencing esp-rs/esp-idf-sys bindings.h
+#
+# This file is part of a Zig + ESP-IDF project using translate-c.
+# It downloads the official bindings.h mega-header from esp-rs/esp-idf-sys
+# as a reference (helps when improving stubs.h or comparing generated bindings).
+#
+# Huge thanks to the esp-rs community for maintaining esp-idf-sys
+# Repository: https://github.com/esp-rs/esp-idf-sys
+# bindings.h: https://github.com/esp-rs/esp-idf-sys/blob/master/src/include/esp-idf/bindings.h
+# ────────────────────────────────────────────────────────────────────────────────
+
+set(BINDINGS_URL
+ "https://raw.githubusercontent.com/esp-rs/esp-idf-sys/refs/heads/master/src/include/esp-idf/bindings.h"
+)
+set(BINDINGS_ESP_RS "${CMAKE_SOURCE_DIR}/include/bindings.h")
+if(NOT EXISTS "${BINDINGS_ESP_RS}")
+ file(DOWNLOAD ${BINDINGS_URL} ${BINDINGS_ESP_RS}
+ TLS_VERIFY ON
+ STATUS download_status
+ LOG download_log
+ TIMEOUT 15
+ )
+ list(GET download_status 0 status_code)
+ list(GET download_status 1 error_msg)
+ if(NOT status_code EQUAL 0)
+ message(FATAL_ERROR "Failed to download esp-idf/bindings.h\nStatus: ${status_code}\nError: ${error_msg}\nLog:\n${download_log}")
+ endif()
+ message(STATUS "Downloaded esp-idf/bindings.h => ${BINDINGS_ESP_RS}")
+else()
+ message(STATUS "esp-idf/bindings.h already exists => ${BINDINGS_ESP_RS}")
+endif()
diff --git a/software/zig_main/cmake/extra-components.cmake b/software/zig_main/cmake/extra-components.cmake
new file mode 100644
index 0000000..1cc32dd
--- /dev/null
+++ b/software/zig_main/cmake/extra-components.cmake
@@ -0,0 +1,124 @@
+# ─── Optional/Managed Components Detection ───────────────────────────────────
+idf_build_get_property(BUILD_COMPS BUILD_COMPONENTS)
+
+# Get IDF_PATH from environment
+set(IDF_PATH $ENV{IDF_PATH})
+
+# Include version.cmake to get IDF_VERSION_MAJOR, IDF_VERSION_MINOR, etc.
+if(EXISTS "${IDF_PATH}/tools/cmake/version.cmake")
+ include("${IDF_PATH}/tools/cmake/version.cmake")
+else()
+ message(FATAL_ERROR "Failed to find version.cmake in ${IDF_PATH}/tools/cmake")
+endif()
+
+
+# Helper function to check and add component
+macro(check_managed_component COMPONENT_NAME VENDOR PACKAGE DEFINE_NAME)
+ set(COMP_PATHS "")
+ set(COMP_BASE "${CMAKE_SOURCE_DIR}/managed_components/${VENDOR}__${PACKAGE}")
+
+ if("${PACKAGE}" STREQUAL "esp-dsp")
+ list(APPEND COMP_PATHS
+ "${COMP_BASE}/modules/common/include"
+ "${COMP_BASE}/modules/dotprod/include"
+ "${COMP_BASE}/modules/support/include"
+ "${COMP_BASE}/modules/support/mem/include"
+ "${COMP_BASE}/modules/windows/include"
+ "${COMP_BASE}/modules/windows/hann/include"
+ "${COMP_BASE}/modules/windows/blackman/include"
+ "${COMP_BASE}/modules/windows/blackman_harris/include"
+ "${COMP_BASE}/modules/windows/blackman_nuttall/include"
+ "${COMP_BASE}/modules/windows/nuttall/include"
+ "${COMP_BASE}/modules/windows/flat_top/include"
+ "${COMP_BASE}/modules/iir/include"
+ "${COMP_BASE}/modules/fir/include"
+ "${COMP_BASE}/modules/math/include"
+ "${COMP_BASE}/modules/math/add/include"
+ "${COMP_BASE}/modules/math/sub/include"
+ "${COMP_BASE}/modules/math/mul/include"
+ "${COMP_BASE}/modules/math/addc/include"
+ "${COMP_BASE}/modules/math/mulc/include"
+ "${COMP_BASE}/modules/math/sqrt/include"
+ "${COMP_BASE}/modules/matrix/include"
+ "${COMP_BASE}/modules/matrix/mul/include"
+ "${COMP_BASE}/modules/matrix/add/include"
+ "${COMP_BASE}/modules/matrix/addc/include"
+ "${COMP_BASE}/modules/matrix/mulc/include"
+ "${COMP_BASE}/modules/matrix/sub/include"
+ "${COMP_BASE}/modules/fft/include"
+ "${COMP_BASE}/modules/dct/include"
+ "${COMP_BASE}/modules/conv/include"
+ "${COMP_BASE}/modules/kalman/ekf/include"
+ "${COMP_BASE}/modules/kalman/ekf_imu13states/include"
+ )
+ elseif("${PACKAGE}" STREQUAL "esp_wifi_remote")
+ # Add base include if exists
+ set(BASE_INCLUDE "${COMP_BASE}/include")
+ if(EXISTS "${BASE_INCLUDE}")
+ list(APPEND COMP_PATHS "${BASE_INCLUDE}")
+ endif()
+
+ # Add version-specific include using MAJOR.MINOR
+ set(VERSION_SUBDIR "idf_v${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}")
+ set(VERSION_PATH "${COMP_BASE}/${VERSION_SUBDIR}/include")
+ if(EXISTS "${VERSION_PATH}")
+ list(APPEND COMP_PATHS "${VERSION_PATH}")
+ endif()
+ elseif("${PACKAGE}" STREQUAL "esp_hosted")
+ # Add host and api include paths for esp_hosted
+ set(HOST_PATH "${COMP_BASE}/host")
+ if(EXISTS "${HOST_PATH}")
+ list(APPEND COMP_PATHS "${HOST_PATH}")
+ endif()
+ set(API_PATH "${HOST_PATH}/api/include")
+ if(EXISTS "${API_PATH}")
+ list(APPEND COMP_PATHS "${API_PATH}")
+ endif()
+ elseif("${PACKAGE}" STREQUAL "esp_matter")
+ # esp_matter is a C++ (CHIP SDK) library.
+ # matter_stubs.h provides the C interface for translate-c instead.
+ list(APPEND COMP_PATHS "${COMP_BASE}/components/esp_matter")
+ else()
+ list(APPEND COMP_PATHS "${COMP_BASE}/include")
+ endif()
+
+ # Check if component is in build
+ if("${VENDOR}__${PACKAGE}" IN_LIST BUILD_COMPS OR "${PACKAGE}" IN_LIST BUILD_COMPS)
+ # Verify at least one path exists (check the base directory)
+ if(EXISTS "${COMP_BASE}")
+ message(STATUS "${COMPONENT_NAME} component found")
+ set(${DEFINE_NAME} 1)
+
+ # Add all include paths that actually exist
+ set(VALID_PATHS 0)
+ foreach(COMP_PATH IN LISTS COMP_PATHS)
+ if(EXISTS "${COMP_PATH}")
+ list(APPEND INCLUDE_DIRS "${COMP_PATH}")
+ math(EXPR VALID_PATHS "${VALID_PATHS} + 1")
+ endif()
+ endforeach()
+
+ if(VALID_PATHS GREATER 0)
+ message(STATUS " Added ${VALID_PATHS} include paths for ${COMPONENT_NAME}")
+ else()
+ message(WARNING "${COMPONENT_NAME} base exists but no include paths found")
+ set(${DEFINE_NAME} 0)
+ endif()
+ else()
+ message(WARNING "${COMPONENT_NAME} in components but not found: ${COMP_BASE}")
+ set(${DEFINE_NAME} 0)
+ endif()
+ else()
+ message(STATUS "${COMPONENT_NAME} not in build. To add: idf.py add-dependency ${VENDOR}/${PACKAGE}")
+ set(${DEFINE_NAME} 0)
+ endif()
+
+ list(APPEND EXTRA_DEFINE_FLAGS "-D${DEFINE_NAME}=${${DEFINE_NAME}}")
+endmacro()
+
+# Add your components here
+check_managed_component("LED Strip" "espressif" "led_strip" "HAS_LED_STRIP")
+check_managed_component("ESP-DSP" "espressif" "esp-dsp" "HAS_ESP_DSP")
+check_managed_component("ESP Wifi Remote" "espressif" "esp_wifi_remote" "HAS_ESP_WIFI_REMOTE")
+check_managed_component("ESP Hosted" "espressif" "esp_hosted" "HAS_ESP_HOSTED")
+check_managed_component("ESP Matter" "espressif" "esp_matter" "HAS_ESP_MATTER")
diff --git a/software/zig_main/cmake/patch.cmake b/software/zig_main/cmake/patch.cmake
new file mode 100644
index 0000000..15b1a6a
--- /dev/null
+++ b/software/zig_main/cmake/patch.cmake
@@ -0,0 +1,162 @@
+# ============================================================================
+# Zig Bindings Patcher
+# Removes and replaces problematic structs/functions from ESP-IDF bindings
+# ============================================================================
+
+message(STATUS "Patching Zig bindings: ${TARGET_FILE}")
+
+# ============================================================================
+# Read file content
+# ============================================================================
+file(READ "${TARGET_FILE}" FILE_CONTENT)
+
+# ============================================================================
+# Determine WiFi support based on target
+# ============================================================================
+if(CONFIG_IDF_TARGET_ESP32P4 OR CONFIG_IDF_TARGET_ESP32H2 OR CONFIG_IDF_TARGET_ESP32H21 OR CONFIG_IDF_TARGET_ESP32H4)
+ set(WIFI_SUPPORTED FALSE)
+else()
+ set(WIFI_SUPPORTED TRUE)
+endif()
+
+# ============================================================================
+# Component status (passed from zig-config.cmake)
+# ============================================================================
+if(NOT DEFINED HAS_LED_STRIP)
+ set(HAS_LED_STRIP 0)
+endif()
+if(NOT DEFINED HAS_ESP_DSP)
+ set(HAS_ESP_DSP 0)
+endif()
+
+# message(STATUS "Component detection:")
+# message(STATUS " HAS_LED_STRIP: ${HAS_LED_STRIP}")
+# message(STATUS " HAS_ESP_DSP: ${HAS_ESP_DSP}")
+
+# ============================================================================
+# Remove problematic definitions
+# ============================================================================
+
+# WiFi patches (only for WiFi-enabled targets)
+if(WIFI_SUPPORTED)
+ string(REGEX REPLACE "pub const wifi_sta_config_t[^;]*;" "" FILE_CONTENT "${FILE_CONTENT}")
+ string(REGEX REPLACE "pub const wifi_ap_config_t[^;]*;" "" FILE_CONTENT "${FILE_CONTENT}")
+endif()
+
+# Remove portTICK_PERIOD_MS (will be replaced with custom version)
+string(REGEX REPLACE "pub const portTICK_PERIOD_MS[^;]*;" "" FILE_CONTENT "${FILE_CONTENT}")
+
+# ============================================================================
+# Handle bitfield-related opaque types and unions
+# ============================================================================
+string(REGEX REPLACE "(//[^\n]*\n?)*const struct_unnamed_[0-9]+ = opaque {};( //[^\n]*\n?)*" "" FILE_CONTENT "${FILE_CONTENT}")
+string(REGEX REPLACE "(//[^\n]*\n?)*const union_unnamed_[0-9]+ = extern union \\{\n unnamed_0: struct_unnamed_[0-9]+,\n val: u32,\n\\};( //[^\n]*\n?)*" "" FILE_CONTENT "${FILE_CONTENT}")
+string(REGEX REPLACE "unnamed_0: struct_unnamed_[0-9]+,\n?" "" FILE_CONTENT "${FILE_CONTENT}")
+string(REGEX REPLACE "([a-zA-Z0-9_]+): ((\[[0-9]+\])?)(struct|union)_unnamed_[0-9]+" "\\1: \\2u32" FILE_CONTENT "${FILE_CONTENT}")
+string(REGEX REPLACE "zeroes\\((struct|union)_unnamed_[0-9]+\\)" "zeroes(u32)" FILE_CONTENT "${FILE_CONTENT}")
+string(REGEX REPLACE "zeroes\\((\\[[0-9]+\\])(struct|union)_unnamed_[0-9]+\\)" "zeroes(\\1u32)" FILE_CONTENT "${FILE_CONTENT}")
+
+# ============================================================================
+# Fix peripheral register structs (lp_*, gpio_dev_t, i2c_dev_t, etc.)
+# ============================================================================
+# 1. Aggressively remove ALL gpio_dev_t and lp_* declarations (including opaque and aliases)
+string(REGEX REPLACE "pub const (struct_)?gpio_dev_t = [^;]+;" "" FILE_CONTENT "${FILE_CONTENT}")
+string(REGEX REPLACE "pub const (struct_)?lp_[a-z0-9_]*_dev_t = [^;]+;" "" FILE_CONTENT "${FILE_CONTENT}")
+string(REGEX REPLACE "pub const (struct_)?i2c_dev_t = [^;]+;" "" FILE_CONTENT "${FILE_CONTENT}")
+# 2. Also remove any remaining aliases that point to struct_ versions
+string(REGEX REPLACE "pub const (lp_[a-z0-9_]+_dev_t|gpio_dev_t|i2c_dev_t) = struct_[^;]+;" "" FILE_CONTENT "${FILE_CONTENT}")
+# 3. Append clean definitions with correct sizes
+string(APPEND FILE_CONTENT "
+pub const lp_io_dev_t = extern struct { reserved: [1024]u8 };
+pub const lp_clkrst_dev_t = extern struct { reserved: [1024]u8 };
+pub const lp_i2c_dev_t = extern struct { reserved: [0x184]u8 };
+pub const lp_timer_dev_t = extern struct { reserved: [1024]u8 };
+pub const lp_uart_dev_t = extern struct { reserved: [0xa0]u8 };
+pub const lp_wdt_dev_t = extern struct { reserved: [1024]u8 };
+pub const lp_io_mux_dev_t = extern struct { reserved: [512]u8 };
+pub const lp_iomux_dev_t = extern struct { reserved: [84]u8 };
+pub const lp_aonclkrst_dev_t = extern struct { reserved: [1024]u8 };
+pub const i2c_dev_t = extern struct { reserved: [388]u8 };
+")
+# 4. Add gpio_dev_t with target-specific size (this must come AFTER the general remove)
+if(CONFIG_IDF_TARGET_ESP32C61 OR CONFIG_IDF_TARGET_ESP32C5 OR CONFIG_IDF_TARGET_ESP32H4)
+ string(APPEND FILE_CONTENT "\npub const gpio_dev_t = extern struct { reserved: [3584]u8 };")
+elseif(CONFIG_IDF_TARGET_ESP32P4)
+ string(APPEND FILE_CONTENT "\npub const gpio_dev_t = extern struct { reserved: [2048]u8 };")
+elseif(CONFIG_IDF_TARGET_ESP32C2 OR CONFIG_IDF_TARGET_ESP32C6 OR CONFIG_IDF_TARGET_ESP32H2)
+ string(APPEND FILE_CONTENT "\npub const gpio_dev_t = extern struct { reserved: [1792]u8 };")
+else()
+ # Fallback
+endif()
+if(CONFIG_IDF_TARGET_ESP32P4)
+ string(APPEND FILE_CONTENT "\npub const lp_gpio_dev_t = extern struct { reserved: [308]u8 };")
+else()
+ string(APPEND FILE_CONTENT "\npub const lp_gpio_dev_t = extern struct { reserved: [1024]u8 };")
+endif()
+
+# ESP32-P4 specific: Remove xPortCanYield function
+if(CONFIG_IDF_TARGET_ESP32P4)
+ string(REGEX REPLACE "pub fn xPortCanYield\\([^)]*\\) callconv\\(\\.c\\) bool \\{([^{}]|\\{[^{}]*\\})*\\}" "" FILE_CONTENT "${FILE_CONTENT}")
+endif()
+
+# LED Strip component patches (if enabled)
+if(HAS_LED_STRIP EQUAL 1)
+ message(STATUS " Applying LED Strip patches")
+ string(REGEX REPLACE "pub const struct_led_strip_rmt_extra_config_20[^;]*;" "" FILE_CONTENT "${FILE_CONTENT}")
+ string(REGEX REPLACE "pub const struct_format_layout_15[^;]*;" "" FILE_CONTENT "${FILE_CONTENT}")
+ string(REGEX REPLACE "pub const led_strip_rmt_config_t[^;]*;" "" FILE_CONTENT "${FILE_CONTENT}")
+ string(REGEX REPLACE "pub const led_color_component_format_t[^;]*;" "" FILE_CONTENT "${FILE_CONTENT}")
+ string(REGEX REPLACE "pub const led_strip_config_t = extern struct \\{[^}]*\\};" "" FILE_CONTENT "${FILE_CONTENT}")
+endif()
+
+# ============================================================================
+# Append custom patch files
+# ============================================================================
+set(PATCH_DIR "${CMAKE_SOURCE_DIR}/../../../patches")
+
+# Define patches to apply
+set(PATCH_FILES
+ "porttick_period_ms.zig"
+)
+
+# Add target-specific patches
+if(CONFIG_IDF_TARGET_ESP32P4)
+ list(APPEND PATCH_FILES "xport_can_yield.zig")
+endif()
+
+# Add WiFi patches
+if(WIFI_SUPPORTED)
+ list(APPEND PATCH_FILES
+ "wifi/wifi_sta_config_t.zig"
+ "wifi/wifi_ap_config_t.zig"
+ )
+endif()
+
+# Add LED Strip patches
+if(HAS_LED_STRIP EQUAL 1)
+ list(APPEND PATCH_FILES
+ "led_strip/led_strip_rmt_extra_config_20.zig"
+ "led_strip/led_strip_struct_format_layout_15.zig"
+ "led_strip/led_color_component_format_t.zig"
+ "led_strip/led_strip_rmt_config_t.zig"
+ "led_strip/led_strip_config_t.zig"
+ )
+endif()
+
+# Apply each patch file
+foreach(PATCH_FILE IN LISTS PATCH_FILES)
+ set(PATCH_PATH "${PATCH_DIR}/${PATCH_FILE}")
+ if(EXISTS "${PATCH_PATH}")
+ message(STATUS " Applying patch: ${PATCH_FILE}")
+ file(READ "${PATCH_PATH}" PATCH_CONTENT)
+ string(APPEND FILE_CONTENT "\n${PATCH_CONTENT}")
+ else()
+ message(WARNING " Patch file not found: ${PATCH_FILE}")
+ endif()
+endforeach()
+
+# ============================================================================
+# Write patched content
+# ============================================================================
+file(WRITE "${TARGET_FILE}" "${FILE_CONTENT}")
+message(STATUS "Patching completed: ${TARGET_FILE}")
diff --git a/software/zig_main/cmake/zig-config.cmake b/software/zig_main/cmake/zig-config.cmake
new file mode 100644
index 0000000..8d0e021
--- /dev/null
+++ b/software/zig_main/cmake/zig-config.cmake
@@ -0,0 +1,488 @@
+# ──────────────────────────────────────────────────────────────────────────────
+# Zig configuration for ESP-IDF (esp32 xtensa/riscv targets)
+# ──────────────────────────────────────────────────────────────────────────────
+
+set(ZIG_MIN_VERSION "0.16.0")
+
+# ─── Host platform & architecture detection ──────────────────────────────────
+cmake_host_system_information(RESULT HOST_OS QUERY OS_NAME)
+string(TOLOWER "${HOST_OS}" HOST_OS_LOWER)
+
+set(HOST_ARCH "${CMAKE_HOST_SYSTEM_PROCESSOR}")
+
+# Normalize to Zig-style triple names
+if(HOST_ARCH MATCHES "^(AMD64|amd64|x86_64|X64)$")
+ set(ZIG_ARCH "x86_64")
+elseif(HOST_ARCH MATCHES "^(aarch64|arm64|ARM64)$")
+ set(ZIG_ARCH "aarch64")
+elseif(HOST_ARCH MATCHES "^(x86|i686|i386|i[345]86)$")
+ set(ZIG_ARCH "x86")
+else()
+ message(FATAL_ERROR "Unsupported host architecture '${HOST_ARCH}'.\n"
+ "Please report:\n"
+ " CMAKE_HOST_SYSTEM_PROCESSOR = ${CMAKE_HOST_SYSTEM_PROCESSOR}\n"
+ " cmake --system-information | findstr PROCESSOR")
+endif()
+
+# Platform + extension
+if(HOST_OS_LOWER MATCHES "(linux|unix)")
+ set(ZIG_PLATFORM "linux-musl")
+ set(ARCHIVE_EXT "tar.xz")
+elseif(HOST_OS_LOWER MATCHES "windows|win")
+ set(ZIG_PLATFORM "windows")
+ set(ARCHIVE_EXT "zip")
+elseif(HOST_OS_LOWER MATCHES "darwin|mac|osx")
+ set(ZIG_PLATFORM "macos")
+ set(ARCHIVE_EXT "tar.xz")
+else()
+ message(FATAL_ERROR "Unsupported host OS: ${HOST_OS}")
+endif()
+
+set(ZIG_TRIPLET "${ZIG_ARCH}-${ZIG_PLATFORM}-baseline")
+set(ZIG_DIR "${CMAKE_BINARY_DIR}/zig-relsafe-${ZIG_TRIPLET}")
+set(ZIG_ARCHIVE "${ZIG_DIR}.${ARCHIVE_EXT}")
+
+# ─── Decide which zig to use ─────────────────────────────────────────────────
+find_program(ZIG_FOUND zig)
+
+set(USE_ZIG_ESPRESSIF_BOOTSTRAP TRUE)
+if(ZIG_FOUND AND CONFIG_IDF_TARGET_ARCH_RISCV)
+ # For most RISC-V prefer system zig (except P4 & H4)
+ if(NOT (CONFIG_IDF_TARGET_ESP32P4 OR CONFIG_IDF_TARGET_ESP32H4))
+ set(USE_ZIG_ESPRESSIF_BOOTSTRAP FALSE)
+ endif()
+endif()
+
+# ─── Download & extract espressif zig if needed ──────────────────────────────
+if(USE_ZIG_ESPRESSIF_BOOTSTRAP)
+ include(${CMAKE_SOURCE_DIR}/cmake/zig-download.cmake)
+else()
+ if(NOT ZIG_FOUND)
+ message(FATAL_ERROR "System 'zig' not found and espressif bootstrap is disabled.")
+ endif()
+ set(ZIG_BIN "${ZIG_FOUND}")
+endif()
+message(STATUS "Using Zig executable: ${ZIG_BIN}")
+
+# ────────────────────────────────────────────────────────────────────────────────────
+include(${CMAKE_SOURCE_DIR}/cmake/zig-runner.cmake)
+# ─────── get Zig version ────────────────────────────────────────────────────────────
+zig_run(COMMAND version OUTPUT_VARIABLE ZIG_VERSION)
+if("${ZIG_VERSION}" VERSION_LESS "${ZIG_MIN_VERSION}")
+ message(FATAL_ERROR "Zig version too old: ${ZIG_VERSION} < ${ZIG_MIN_VERSION}")
+endif()
+message(STATUS "Zig version: ${ZIG_VERSION}")
+
+# Detect whether this Zig build is the Espressif fork (has named ESP32 RISC-V CPU models).
+# Upstream Zig only ships generic RISC-V models; the fork adds esp32c3, esp32c6, etc.
+zig_run(
+ COMMAND build-obj --show-builtin -target riscv32-freestanding -mcpu=esp32c3
+ RESULT_VARIABLE _zig_esp_riscv_probe
+ OUTPUT_VARIABLE _zig_esp_riscv_probe_out
+ ERROR_VARIABLE _zig_esp_riscv_probe_err
+ ALLOW_FAIL
+)
+if(_zig_esp_riscv_probe EQUAL 0)
+ set(ZIG_HAS_ESP_RISCV_MODELS ON)
+ message(STATUS "Zig RISC-V: Espressif fork detected — using named ESP CPU models")
+else()
+ set(ZIG_HAS_ESP_RISCV_MODELS OFF)
+ message(STATUS "Zig RISC-V: upstream Zig detected — falling back to generic_rv32 models")
+endif()
+
+# ====================================================================================
+
+# Determine target model from CONFIG_IDF_TARGET
+string(TOLOWER "${CONFIG_IDF_TARGET}" TARGET_IDF_MODEL)
+
+# Target architecture configuration lookup table
+set(RISCV_TARGETS
+ "esp32c2" "esp32c3" "esp32c5" "esp32c6" "esp32c61" "esp32c61eco0"
+ "esp32h2" "esp32h21" "esp32h4" "esp32p4" "esp32p4eco4" "esp32s31")
+set(XTENSA_TARGETS
+ "esp32" "esp32s2" "esp32s3")
+
+if(TARGET_IDF_MODEL IN_LIST RISCV_TARGETS)
+ set(TARGET_IDF_ARCH "riscv")
+
+ # MACF group (FPU): use eabihf ABI. All others use none.
+ set(_rv_macf_targets "esp32h4" "esp32s31" "esp32p4" "esp32p4eco4")
+ if(TARGET_IDF_MODEL IN_LIST _rv_macf_targets)
+ set(ZIG_TARGET "riscv32-freestanding-eabihf")
+ else()
+ set(ZIG_TARGET "riscv32-freestanding-none")
+ endif()
+
+ if(ZIG_HAS_ESP_RISCV_MODELS)
+ # Espressif Zig fork: use named CPU model (feature set is already encoded in model).
+ set(TARGET_CPU_MODEL "${TARGET_IDF_MODEL}")
+ else()
+ # Upstream Zig: fall back to generic_rv32 with explicit feature sets.
+ # MC (c2, c3): m+c+zicsr+zifencei abi=none
+ # MAC (c5, c6, c61, h2, h21): m+a+c+zicsr+zifencei abi=none
+ # MACF (h4, s31, p4, p4eco4): m+a+c+f+zicsr+zifencei abi=eabihf
+ set(_rv_mc_targets "esp32c2" "esp32c3")
+ set(_rv_mac_targets "esp32c5" "esp32c6" "esp32c61" "esp32c61eco0" "esp32h2" "esp32h21")
+ if(TARGET_IDF_MODEL IN_LIST _rv_mc_targets)
+ set(TARGET_CPU_MODEL "generic_rv32+m+c+zicsr+zifencei")
+ elseif(TARGET_IDF_MODEL IN_LIST _rv_mac_targets)
+ set(TARGET_CPU_MODEL "generic_rv32+m+a+c+zicsr+zifencei")
+ elseif(TARGET_IDF_MODEL IN_LIST _rv_macf_targets)
+ set(TARGET_CPU_MODEL "generic_rv32+m+a+c+f+zicsr+zifencei")
+ else()
+ set(TARGET_CPU_MODEL "generic_rv32+m+c+zicsr+zifencei")
+ endif()
+ endif()
+
+elseif(TARGET_IDF_MODEL IN_LIST XTENSA_TARGETS)
+ set(TARGET_IDF_ARCH "xtensa")
+ set(ZIG_TARGET "xtensa-freestanding-none")
+ set(TARGET_CPU_MODEL "${TARGET_IDF_MODEL}")
+
+else()
+ message(FATAL_ERROR "Unsupported IDF target: ${CONFIG_IDF_TARGET}")
+endif()
+
+message(STATUS "ESP-IDF Target: ${TARGET_IDF_MODEL}")
+message(STATUS "Architecture: ${TARGET_IDF_ARCH}")
+message(STATUS "Zig Target: ${ZIG_TARGET}")
+message(STATUS "CPU Model: ${TARGET_CPU_MODEL}")
+
+# Check Toolchain version
+get_filename_component(TOOLCHAIN_BIN_DIR "${CMAKE_C_COMPILER}" DIRECTORY)
+get_filename_component(TOOLCHAIN_VERSION_DIR "${TOOLCHAIN_BIN_DIR}" DIRECTORY)
+if("${TOOLCHAIN_VERSION_DIR}" MATCHES "esp-([0-9]+\\.[0-9]+\\.[0-9]+_[0-9]+)")
+ set(TOOLCHAIN_VERSION "${CMAKE_MATCH_1}")
+ message(STATUS "Detected ESP toolchain version: ${TOOLCHAIN_VERSION}")
+else()
+ message(WARNING "Standard ESP version pattern not found in: ${TOOLCHAIN_VERSION_DIR}")
+endif()
+
+if(CONFIG_IDF_TARGET_ARCH_RISCV)
+ set(ARCH "riscv")
+ set(TRIPLE "riscv32-esp-elf")
+ set(ARCH_DEFINE "__riscv")
+elseif(CONFIG_IDF_TARGET_ARCH_XTENSA)
+ set(ARCH "xtensa")
+ set(TRIPLE "xtensa-esp-elf")
+ set(ARCH_DEFINE "__XTENSA__")
+endif()
+# Get toolchain includes with fallback paths
+set(POSSIBLE_INCLUDE_PATHS
+ "${TOOLCHAIN_VERSION_DIR}/${TRIPLE}/include"
+ "${TOOLCHAIN_BIN_DIR}/../${TRIPLE}/include"
+ "${TOOLCHAIN_VERSION_DIR}/${TRIPLE}/${TRIPLE}/include"
+)
+# Find the toolchain include directory
+set(TOOLCHAIN_ELF_INCLUDE "")
+foreach(PATH ${POSSIBLE_INCLUDE_PATHS})
+ if(IS_DIRECTORY "${PATH}")
+ set(TOOLCHAIN_ELF_INCLUDE "${PATH}")
+ message(STATUS "Found toolchain include at: ${TOOLCHAIN_ELF_INCLUDE}")
+ break()
+ endif()
+endforeach()
+# sys-include should be sys-include directory OR the same as regular include
+# (since sys headers are typically under include/sys/)
+set(POSSIBLE_SYS_INCLUDE_PATHS
+ "${TOOLCHAIN_VERSION_DIR}/${TRIPLE}/sys-include"
+ "${TOOLCHAIN_BIN_DIR}/../${TRIPLE}/sys-include"
+ "${TOOLCHAIN_VERSION_DIR}/${TRIPLE}/${TRIPLE}/sys-include"
+ "${TOOLCHAIN_VERSION_DIR}/${TRIPLE}/${TRIPLE}/include"
+ "${TOOLCHAIN_ELF_INCLUDE}"
+)
+# Find the first existing sys-include directory
+set(TOOLCHAIN_SYS_INCLUDE "")
+foreach(PATH ${POSSIBLE_SYS_INCLUDE_PATHS})
+ if(IS_DIRECTORY "${PATH}")
+ set(TOOLCHAIN_SYS_INCLUDE "${PATH}")
+ message(STATUS "Found sys-include at: ${TOOLCHAIN_SYS_INCLUDE}")
+ break()
+ endif()
+endforeach()
+if(NOT IS_DIRECTORY "${TOOLCHAIN_ELF_INCLUDE}")
+ message(WARNING "Toolchain include directory not found: ${TOOLCHAIN_ELF_INCLUDE}")
+endif()
+if(NOT IS_DIRECTORY "${TOOLCHAIN_SYS_INCLUDE}")
+ message(WARNING "Toolchain sys-include directory not found: ${TOOLCHAIN_SYS_INCLUDE}")
+endif()
+
+# components list
+set(INCLUDE_DIRS
+ # Wrapper dir MUST come first — redirects "freertos/portmacro.h" through a
+ # single canonical path to fix Windows mixed-separator #pragma once failure.
+ # See include/freertos-compat/freertos/portmacro.h for details (issue #50).
+ "${CMAKE_SOURCE_DIR}/include/freertos-compat"
+ "${IDF_PATH}/components/freertos/FreeRTOS-Kernel-SMP/portable/${ARCH}/include/freertos"
+ "${IDF_PATH}/components/freertos/FreeRTOS-Kernel/include"
+ "${IDF_PATH}/components/freertos/config/include"
+ "${IDF_PATH}/components/freertos/config/include/freertos"
+ "${IDF_PATH}/components/freertos/esp_additions/include"
+ "${IDF_PATH}/components/freertos/config/${ARCH}/include"
+ "${IDF_PATH}/components/esp_hw_support/include"
+ "${IDF_PATH}/components/soc/include"
+ "${IDF_PATH}/components/soc/${TARGET_IDF_MODEL}/include"
+ "${IDF_PATH}/components/esp_common/include"
+ "${IDF_PATH}/components/hal/include"
+ "${IDF_PATH}/components/bootloader_support/include"
+ "${IDF_PATH}/components/${ARCH}/include"
+ "${IDF_PATH}/components/bt/include/${TARGET_IDF_MODEL}/include"
+ "${IDF_PATH}/components/bt/host/nimble/nimble/nimble/include"
+ "${IDF_PATH}/components/bt/host/bluedroid/api/include"
+ "${IDF_PATH}/components/bt/host/bluedroid/api/include/api"
+ "${IDF_PATH}/components/${ARCH}/${TARGET_IDF_MODEL}/include"
+ "${IDF_PATH}/components/${ARCH}/${TARGET_IDF_MODEL}/include/${ARCH}"
+ "${IDF_PATH}/components/${ARCH}/${TARGET_IDF_MODEL}/include/${ARCH}/config"
+ "${IDF_PATH}/components/${ARCH}/${TARGET_IDF_MODEL}/include/${ARCH}/core"
+ "${IDF_PATH}/components/soc/${TARGET_IDF_MODEL}/register"
+ "${IDF_PATH}/components/esp_system/include"
+ "${IDF_PATH}/components/esp_hw_support/etm/include"
+ "${IDF_PATH}/components/esp_hw_support/ldo/include"
+ "${IDF_PATH}/components/esp_hal_ana_cmpr/include"
+ "${IDF_PATH}/components/esp_hal_ana_conv/include"
+ "${IDF_PATH}/components/esp_hal_cam/include"
+ "${IDF_PATH}/components/esp_hal_dma/include"
+ "${IDF_PATH}/components/esp_hal_gpio/include"
+ "${IDF_PATH}/components/esp_hal_gpio/${TARGET_IDF_MODEL}/include"
+ "${IDF_PATH}/components/esp_hal_gpspi/include"
+ "${IDF_PATH}/components/esp_hal_i2c/include"
+ "${IDF_PATH}/components/esp_hal_i2s/include"
+ "${IDF_PATH}/components/esp_hal_lcd/include"
+ "${IDF_PATH}/components/esp_hal_ledc/include"
+ "${IDF_PATH}/components/esp_hal_mcpwm/include"
+ "${IDF_PATH}/components/esp_hal_mspi/include"
+ "${IDF_PATH}/components/esp_hal_parlio/include"
+ "${IDF_PATH}/components/esp_hal_pcnt/include"
+ "${IDF_PATH}/components/esp_hal_ppa/include"
+ "${IDF_PATH}/components/esp_hal_rmt/include"
+ "${IDF_PATH}/components/esp_hal_timg/include"
+ "${IDF_PATH}/components/esp_hal_touch_sens/include"
+ "${IDF_PATH}/components/esp_hal_twai/include"
+ "${IDF_PATH}/components/esp_hal_uart/include"
+ "${IDF_PATH}/components/esp_hal_usb/include"
+ "${IDF_PATH}/components/esp_hal_wdt/include"
+ "${IDF_PATH}/components/esp_driver_ana_cmpr/include"
+ "${IDF_PATH}/components/esp_driver_bitscrambler/include"
+ "${IDF_PATH}/components/esp_driver_cam/include"
+ "${IDF_PATH}/components/esp_driver_dac/include"
+ "${IDF_PATH}/components/esp_driver_gpio/include"
+ "${IDF_PATH}/components/esp_driver_gptimer/include"
+ "${IDF_PATH}/components/esp_driver_i2c/include"
+ "${IDF_PATH}/components/esp_driver_i2s/include"
+ "${IDF_PATH}/components/esp_driver_i3c/include"
+ "${IDF_PATH}/components/esp_driver_isp/include"
+ "${IDF_PATH}/components/esp_driver_jpeg/include"
+ "${IDF_PATH}/components/esp_driver_ledc/include"
+ "${IDF_PATH}/components/esp_driver_mcpwm/include"
+ "${IDF_PATH}/components/esp_driver_parlio/include"
+ "${IDF_PATH}/components/esp_driver_pcnt/include"
+ "${IDF_PATH}/components/esp_driver_ppa/include"
+ "${IDF_PATH}/components/esp_driver_rmt/include"
+ "${IDF_PATH}/components/esp_driver_sd_intf/include"
+ "${IDF_PATH}/components/esp_driver_sdio/include"
+ "${IDF_PATH}/components/esp_driver_sdm/include"
+ "${IDF_PATH}/components/esp_driver_sdmmc/include"
+ "${IDF_PATH}/components/esp_driver_sdspi/include"
+ "${IDF_PATH}/components/esp_driver_spi/include"
+ "${IDF_PATH}/components/esp_driver_touch_sens/include"
+ "${IDF_PATH}/components/esp_driver_tsens/include"
+ "${IDF_PATH}/components/esp_driver_twai/include"
+ "${IDF_PATH}/components/esp_driver_uart/include"
+ "${IDF_PATH}/components/esp_driver_usb_serial_jtag/include"
+ "${IDF_PATH}/components/esp_phy/include"
+ "${IDF_PATH}/components/esp_tee/include"
+ "${IDF_PATH}/components/esp_timer/include"
+ "${IDF_PATH}/components/esp_coex/include"
+ "${IDF_PATH}/components/esp_psram/include"
+ "${IDF_PATH}/components/esp_security/include"
+ "${IDF_PATH}/components/esp_trace/include"
+ "${IDF_PATH}/components/esp_blockdev/include"
+ "${IDF_PATH}/components/pthread/include"
+ "${IDF_PATH}/components/hal/platform_port/include"
+ "${IDF_PATH}/components/heap/include"
+ "${IDF_PATH}/components/ieee802154/include"
+ "${IDF_PATH}/components/openthread/include"
+ "${IDF_PATH}/components/openthread/openthread/third_party/mbedtls/repo/include"
+ "${IDF_PATH}/components/esp_rom/${TARGET_IDF_MODEL}/include/${TARGET_IDF_MODEL}"
+ "${IDF_PATH}/components/esp_rom/include"
+ "${IDF_PATH}/components/esp_wifi/include"
+ "${IDF_PATH}/components/esp_bootloader_format/include"
+ "${IDF_PATH}/components/esp_app_format/include"
+ "${IDF_PATH}/components/esp_pm/include"
+ "${IDF_PATH}/components/esp_lcd/include"
+ "${IDF_PATH}/components/esp_lcd/interface"
+ "${IDF_PATH}/components/esp_lcd/dsi/include"
+ "${IDF_PATH}/components/esp_lcd/rgb/include"
+ "${IDF_PATH}/components/mbedtls/mbedtls/tf-psa-crypto/drivers/builtin/include"
+ "${IDF_PATH}/components/mbedtls/mbedtls/tf-psa-crypto/include"
+ "${IDF_PATH}/components/mbedtls/esp_crt_bundle/include"
+ "${IDF_PATH}/components/mbedtls/port/include"
+ "${IDF_PATH}/components/mbedtls/mbedtls/include"
+ "${IDF_PATH}/components/http_parser"
+ "${IDF_PATH}/components/esp-tls"
+ "${IDF_PATH}/components/esp_https_ota/include"
+ "${IDF_PATH}/components/esp_http_server/include"
+ "${IDF_PATH}/components/esp_https_server/include"
+ "${IDF_PATH}/components/esp_http_client/include"
+ "${IDF_PATH}/components/log/include"
+ "${IDF_PATH}/components/vfs/include"
+ "${IDF_PATH}/components/wpa_supplicant/esp_supplicant/include"
+ "${IDF_PATH}/components/nvs_flash/include"
+ "${IDF_PATH}/components/esp_partition/include"
+ "${IDF_PATH}/components/esp_netif/include"
+ "${IDF_PATH}/components/esp_event/include"
+ "${IDF_PATH}/components/driver/i2c/include"
+ "${IDF_PATH}/components/driver/deprecated"
+ "${IDF_PATH}/components/driver/touch_sensor/${TARGET_IDF_MODEL}/include"
+ "${IDF_PATH}/components/driver/touch_sensor/include"
+ "${IDF_PATH}/components/driver/twai/include"
+ "${IDF_PATH}/components/spi_flash/include"
+ "${IDF_PATH}/components/esp_vfs_console/include"
+ "${IDF_PATH}/components/esp_ringbuf/include"
+ "${IDF_PATH}/components/esp_usb_cdc_rom_console/include"
+ "${CMAKE_SOURCE_DIR}/build/config"
+)
+# Toolchain system includes (separate from regular includes)
+set(SYSTEM_INCLUDE_DIRS
+ "${TOOLCHAIN_SYS_INCLUDE}"
+ "${TOOLCHAIN_ELF_INCLUDE}"
+ "${IDF_PATH}/components/newlib"
+ "${IDF_PATH}/components/newlib/platform_include"
+ "${IDF_PATH}/components/esp_libc/platform_include"
+ "${IDF_PATH}/components/lwip/lwip/src/include"
+ "${IDF_PATH}/components/lwip/port/esp32xx/include"
+ "${IDF_PATH}/components/lwip/port/freertos/include"
+ "${IDF_PATH}/components/lwip/port/include"
+ "${IDF_PATH}/components/lwip/include"
+ "${IDF_PATH}/components/lwip/include/apps"
+)
+if(CONFIG_IDF_TARGET_ESP32P4)
+ list(APPEND INCLUDE_DIRS "${IDF_PATH}/components/soc/${TARGET_IDF_MODEL}/register/hw_ver3")
+elseif(CONFIG_IDF_TARGET_ESP32H4)
+ list(APPEND INCLUDE_DIRS "${IDF_PATH}/components/soc/${TARGET_IDF_MODEL}/register/hw_ver_mp")
+endif()
+
+# get esp-idf C Macros
+idf_build_get_property(all_defines COMPILE_DEFINITIONS)
+
+set(EXTRA_DEFINE_FLAGS "")
+foreach(def ${all_defines})
+ string(STRIP "${def}" def_clean)
+ if(NOT def_clean STREQUAL "")
+ if(NOT def_clean MATCHES "^-D")
+ list(APPEND EXTRA_DEFINE_FLAGS "-D${def_clean}")
+ else()
+ list(APPEND EXTRA_DEFINE_FLAGS "${def_clean}")
+ endif()
+ endif()
+endforeach()
+
+string(TOUPPER "${TARGET_IDF_ARCH}" TARGET_IDF_ARCH_UPPER)
+string(TOUPPER "${TARGET_IDF_MODEL}" TARGET_IDF_MODEL_UPPER)
+set(DEFINE_FLAGS
+ "-D__${TARGET_IDF_ARCH}"
+ "-Dcpu_${TARGET_CPU_MODEL}"
+ "-D${ARCH_DEFINE}"
+ "-D__${TARGET_IDF_ARCH_UPPER}_EL__"
+ "-DCONFIG_IDF_TARGET_${TARGET_IDF_MODEL_UPPER}"
+ "-D__COUNTER__=0"
+ "-DIRAM_ATTR="
+ "-D_SECTION_ATTR_IMPL\\(x,y\\)="
+ "-DSOC_MMU_PAGE_SIZE=0x8000"
+ "-DLWIP_NO_UNISTD_H="
+)
+string(JOIN " " DEFINE_FLAGS_STR ${DEFINE_FLAGS})
+
+if(ARCH_DEFINE)
+ set(DEFINE_FLAGS "${DEFINE_FLAGS} -D${ARCH_DEFINE}")
+endif()
+set(IDF_SYS_ZIG "${CMAKE_SOURCE_DIR}/imports/idf-sys.zig")
+set(IDF_SYS_C "${CMAKE_SOURCE_DIR}/include/stubs.h")
+
+# get esp-rs bindings.h
+include(${CMAKE_SOURCE_DIR}/cmake/bindings.cmake)
+
+# add extra-components
+include(${CMAKE_SOURCE_DIR}/cmake/extra-components.cmake)
+
+set(INCLUDE_FLAGS "")
+foreach(dir ${INCLUDE_DIRS})
+ # Normalize to forward slashes (prevents Windows mixed-separator double-include in translate-c)
+ file(TO_CMAKE_PATH "${dir}" dir)
+ set(INCLUDE_FLAGS "${INCLUDE_FLAGS} -I\"${dir}\"")
+endforeach()
+
+# Build system include flags (for toolchain)
+foreach(dir ${SYSTEM_INCLUDE_DIRS})
+ file(TO_CMAKE_PATH "${dir}" dir)
+ set(INCLUDE_FLAGS "${INCLUDE_FLAGS} -isystem \"${dir}\"")
+endforeach()
+if(NOT WIN32)
+ separate_arguments(INCLUDE_FLAGS UNIX_COMMAND "${INCLUDE_FLAGS}")
+else()
+ separate_arguments(INCLUDE_FLAGS WINDOWS_COMMAND "${INCLUDE_FLAGS}")
+endif()
+
+# Run `translate-c` to generate `idf-sys.zig`
+include(${CMAKE_SOURCE_DIR}/cmake/bindgen-standalone.cmake)
+bindgen_run(
+ COMMAND
+ ${IDF_SYS_C} -target ${ZIG_TARGET} -mcpu ${TARGET_CPU_MODEL}
+ ${DEFINE_FLAGS} ${EXTRA_DEFINE_FLAGS} ${INCLUDE_FLAGS}
+ OUTPUT_FILE ${IDF_SYS_ZIG}
+ WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/imports
+ DEPENDS ${IDF_SYS_C}
+)
+
+set(PATCHES_DONE "${CMAKE_BINARY_DIR}/patches_applied.done")
+add_custom_command(
+ OUTPUT "${PATCHES_DONE}"
+ COMMAND ${CMAKE_COMMAND}
+ -D TARGET_FILE=${IDF_SYS_ZIG}
+ -D CONFIG_IDF_TARGET_ESP32H2=${CONFIG_IDF_TARGET_ESP32H2}
+ -D CONFIG_IDF_TARGET_ESP32H21=${CONFIG_IDF_TARGET_ESP32H21}
+ -D CONFIG_IDF_TARGET_ESP32H4=${CONFIG_IDF_TARGET_ESP32H4}
+ -D CONFIG_IDF_TARGET_ESP32P4=${CONFIG_IDF_TARGET_ESP32P4}
+ -D CONFIG_IDF_TARGET_ESP32C2=${CONFIG_IDF_TARGET_ESP32C2}
+ -D CONFIG_IDF_TARGET_ESP32C5=${CONFIG_IDF_TARGET_ESP32C5}
+ -D CONFIG_IDF_TARGET_ESP32C6=${CONFIG_IDF_TARGET_ESP32C6}
+ -D CONFIG_IDF_TARGET_ESP32C61=${CONFIG_IDF_TARGET_ESP32C61}
+ -D HAS_LED_STRIP=${HAS_LED_STRIP}
+ -D HAS_ESP_DSP=${HAS_ESP_DSP}
+ -P ${CMAKE_SOURCE_DIR}/cmake/patch.cmake
+ COMMAND ${CMAKE_COMMAND} -E touch "${PATCHES_DONE}"
+ DEPENDS "${IDF_SYS_ZIG}"
+ COMMENT "Patching idf-sys.zig"
+ VERBATIM
+)
+add_custom_target(translate_c ALL DEPENDS "${PATCHES_DONE}")
+message(STATUS "IDF_SYS_ZIG is set to: ${IDF_SYS_ZIG}")
+
+
+if(CMAKE_BUILD_TYPE STREQUAL "Debug")
+ set(ZIG_BUILD_TYPE "Debug")
+else()
+ set(ZIG_BUILD_TYPE "ReleaseSafe")
+endif()
+
+add_custom_target(zig_build
+ ${ZIG_BIN} build
+ --build-file ${CMAKE_SOURCE_DIR}/build.zig
+ -Doptimize=${ZIG_BUILD_TYPE}
+ -Dtarget=${ZIG_TARGET}
+ -Dcpu=${TARGET_CPU_MODEL}
+ ${ZIG_EXAMPLE_ARG}
+ -freference-trace
+ --cache-dir ${CMAKE_BINARY_DIR}/../.zig-cache
+ --prefix ${CMAKE_BINARY_DIR}
+ WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
+ BYPRODUCTS ${CMAKE_BINARY_DIR}/obj/app_zig.o
+ VERBATIM)
+
+add_dependencies(zig_build translate_c)
+add_dependencies(${COMPONENT_LIB} zig_build)
+
+target_sources(${COMPONENT_LIB}
+ PRIVATE
+ ${CMAKE_BINARY_DIR}/obj/app_zig.o
+)
diff --git a/software/zig_main/cmake/zig-download.cmake b/software/zig_main/cmake/zig-download.cmake
new file mode 100644
index 0000000..21d4cb8
--- /dev/null
+++ b/software/zig_main/cmake/zig-download.cmake
@@ -0,0 +1,58 @@
+if(NOT EXISTS "${ZIG_DIR}/zig")
+ if(ZIG_PLATFORM STREQUAL "linux-musl")
+ if(ZIG_ARCH STREQUAL "aarch64")
+ set(HASH_SUM "d3e4930bbac053b40860290cdec2ad1e052418172aa452e590b242c081a01f94")
+ elseif(ZIG_ARCH STREQUAL "x86_64")
+ set(HASH_SUM "411f1858a9610803af28cd271acf4548873545ccc866f4b9060903ff0d4b6e8e")
+ else()
+ message(FATAL_ERROR "Unsupported architecture: ${ZIG_ARCH}")
+ endif()
+ elseif(ZIG_PLATFORM STREQUAL "windows")
+ if(ZIG_ARCH STREQUAL "x86_64")
+ set(HASH_SUM "4d5b66d857e790d068e408e6bdd0054f9e78ca2d0ab96b5ce25f63a125f1701e")
+ else()
+ message(FATAL_ERROR "Unsupported architecture: ${ZIG_ARCH}")
+ endif()
+ elseif(ZIG_PLATFORM STREQUAL "macos")
+ if(ZIG_ARCH STREQUAL "aarch64")
+ set(HASH_SUM "f47ac7927ae44f3b8290aa818d3158b0f9adc12742e03b7496ad440b6d89d38e")
+ else()
+ message(FATAL_ERROR "Unsupported architecture: ${ZIG_ARCH}")
+ endif()
+ endif()
+ set(ZIG_URL "https://github.com/kassane/zig-espressif-bootstrap/releases/download/0.16.0-xtensa/zig-relsafe-${ZIG_TRIPLET}.${ARCHIVE_EXT}")
+ message(STATUS "Downloading Zig (espressif variant):")
+ message(STATUS " => ${ZIG_ARCHIVE}")
+ file(DOWNLOAD "${ZIG_URL}" "${ZIG_ARCHIVE}"
+ TLS_VERIFY ON
+ EXPECTED_HASH SHA256=${HASH_SUM}
+ STATUS download_status
+ LOG download_log
+ # SHOW_PROGRESS
+ )
+ list(GET download_status 0 dl_code)
+ if(NOT dl_code EQUAL 0)
+ message(FATAL_ERROR "Download failed:\n${download_log}")
+ endif()
+ message(STATUS "Extracting ${ARCHIVE_EXT} ...")
+ if(HOST_OS_LOWER MATCHES "windows|win")
+ execute_process(
+ COMMAND powershell -NoProfile -ExecutionPolicy Bypass
+ -Command "Expand-Archive -Path '${ZIG_ARCHIVE}' -DestinationPath '${CMAKE_BINARY_DIR}' -Force"
+ RESULT_VARIABLE extract_result
+ )
+ else()
+ execute_process(
+ COMMAND ${CMAKE_COMMAND} -E tar xf "${ZIG_ARCHIVE}"
+ WORKING_DIRECTORY "${CMAKE_BINARY_DIR}"
+ RESULT_VARIABLE extract_result
+ )
+ endif()
+ if(NOT extract_result EQUAL 0)
+ message(FATAL_ERROR "Extraction failed (code ${extract_result})")
+ endif()
+ file(REMOVE "${ZIG_ARCHIVE}")
+else()
+ message(STATUS "Using cached espressif zig: ${ZIG_DIR}/zig")
+endif()
+set(ZIG_BIN "${ZIG_DIR}/zig")
diff --git a/software/zig_main/cmake/zig-runner.cmake b/software/zig_main/cmake/zig-runner.cmake
new file mode 100644
index 0000000..41ee14d
--- /dev/null
+++ b/software/zig_main/cmake/zig-runner.cmake
@@ -0,0 +1,63 @@
+# ──────────────────────────────────────────────────────────────────────────────
+# HELPER to run zig cli (build - translate-c - fmt - ...)
+# ──────────────────────────────────────────────────────────────────────────────
+function(zig_run)
+ cmake_parse_arguments(PARSE_ARGV 0 ARG
+ "VERBATIM;ALLOW_FAIL"
+ "WORKING_DIRECTORY;RESULT_VARIABLE;OUTPUT_VARIABLE;ERROR_VARIABLE;OUTPUT_FILE;TIMEOUT"
+ "COMMAND"
+ )
+ if(NOT ARG_COMMAND)
+ message(FATAL_ERROR "zig_run: COMMAND list is required")
+ endif()
+ if(NOT DEFINED ARG_WORKING_DIRECTORY)
+ set(ARG_WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}")
+ endif()
+ # Default timeout: 300 s. translate-c normally finishes in seconds;
+ if(NOT DEFINED ARG_TIMEOUT)
+ set(ARG_TIMEOUT 300)
+ endif()
+ set(extra_args)
+ if(ARG_VERBATIM)
+ list(APPEND extra_args VERBATIM)
+ endif()
+ if(ARG_OUTPUT_FILE)
+ list(APPEND extra_args OUTPUT_FILE "${ARG_OUTPUT_FILE}")
+ endif()
+ execute_process(
+ COMMAND "${ZIG_BIN}" ${ARG_COMMAND}
+ WORKING_DIRECTORY "${ARG_WORKING_DIRECTORY}"
+ RESULT_VARIABLE result
+ OUTPUT_VARIABLE output
+ ERROR_VARIABLE error
+ TIMEOUT ${ARG_TIMEOUT}
+ OUTPUT_STRIP_TRAILING_WHITESPACE
+ ERROR_STRIP_TRAILING_WHITESPACE
+ ${extra_args}
+ )
+ # Propagate results to parent scope if requested
+ if(DEFINED ARG_RESULT_VARIABLE)
+ set(${ARG_RESULT_VARIABLE} ${result} PARENT_SCOPE)
+ endif()
+ if(DEFINED ARG_OUTPUT_VARIABLE)
+ set(${ARG_OUTPUT_VARIABLE} "${output}" PARENT_SCOPE)
+ endif()
+ if(DEFINED ARG_ERROR_VARIABLE)
+ set(${ARG_ERROR_VARIABLE} "${error}" PARENT_SCOPE)
+ endif()
+ if(NOT ARG_ALLOW_FAIL)
+ if(result MATCHES "timeout")
+ message(FATAL_ERROR
+ "Zig command timed out after ${ARG_TIMEOUT}s\n"
+ " Command: ${ZIG_BIN} ${ARG_COMMAND}\n"
+ "--- stdout ---\n${output}\n"
+ "--- stderr ---\n${error}")
+ elseif(NOT result EQUAL 0)
+ message(FATAL_ERROR
+ "Zig command failed (code ${result}):\n"
+ " ${ZIG_BIN} ${ARG_COMMAND}\n"
+ "--- stdout ---\n${output}\n"
+ "--- stderr ---\n${error}")
+ endif()
+ endif()
+endfunction()
diff --git a/software/zig_main/docs/build-internals.md b/software/zig_main/docs/build-internals.md
new file mode 100644
index 0000000..aef514f
--- /dev/null
+++ b/software/zig_main/docs/build-internals.md
@@ -0,0 +1,341 @@
+## How does the mixin build-system work?
+
+#### Intro
+
+ESP-IDF uses the `idf.py` script as a wrapper around CMake. It's responsible for creating the build environment, running CMake to generate build files, and using Ninja to build the project.
+
+The Zig build system (`build.zig`) is a wrapper around the Zig compiler and integrates with ESP-IDF's CMake infrastructure.
+
+For more details about Zig commands, see [doc/zig-xtensa](zig-xtensa.md)
+
+#### Building this project
+
+After cloning this project, you need to install ESP-IDF and set up the environment:
+
+1. **Install ESP-IDF** by following the official guide:
+ - Clone ESP-IDF repository:
+ ```bash
+ git clone --recursive https://github.com/espressif/esp-idf.git
+ ```
+ - Run the installation script:
+ - Windows: `install.bat` or `install.ps1`
+ - POSIX: `./install.sh`
+
+2. **Set up the ESP-IDF environment variables:**
+ - Windows: run `export.bat` or `./export.ps1`
+ - POSIX: `. ./export.sh`
+
+ Once the environment is set up, you can build the project using this scheme:
+
+ 
+
+3. **Set the target ESP device** (if not already set):
+
+ ```bash
+ idf.py set-target
+ ```
+
+ **Supported targets:**
+ - RISC-V: `esp32c2`, `esp32c3`, `esp32c5`, `esp32c6`, `esp32c61`, `esp32c61eco0`, `esp32h2`, `esp32h21`, `esp32h4`, `esp32s31`, `esp32p4`, `esp32p4eco4`
+ - Xtensa: `esp32`, `esp32s2`, `esp32s3`
+
+4. **Add managed components** (optional):
+ ```bash
+ idf.py add-dependency espressif/led_strip
+ idf.py add-dependency espressif/esp-dsp
+ ```
+
+ Managed components are automatically detected during build and their headers are included in the Zig bindings.
+
+5. **Build the project:**
+ ```bash
+ idf.py build
+ ```
+
+ This will:
+ - Configure CMake and detect managed components
+ - Download/use appropriate Zig toolchain (espressif or upstream)
+ - Generate `idf-sys.zig` bindings via `translate-c`
+ - Apply target-specific patches
+ - Build Zig code into object files
+ - Link everything with ESP-IDF components
+
+6. **Flash the firmware to your device:**
+ ```bash
+ idf.py -p PORT flash
+ ```
+ Replace `PORT` with your device's serial port (e.g., `COM3` on Windows or `/dev/ttyUSB0` on Linux)
+
+7. **Monitor the device output:**
+ ```bash
+ idf.py monitor
+ ```
+
+**Additional useful commands:**
+- Clean the project: `idf.py clean`
+- Full clean and rebuild: `idf.py fullclean`
+- Build and flash in one command: `idf.py -p PORT flash monitor`
+- Show all targets: `idf.py --list-targets`
+- Configure project: `idf.py menuconfig`
+- Reconfigure (refresh component detection): `idf.py reconfigure`
+
+---
+
+### Current role of `build.zig`
+
+Since the latest refactors (post [#37](https://github.com/kassane/zig-esp-idf-sample/pull/37) and related CMake changes), `build.zig` is **focused exclusively on Zig code compilation**:
+
+**What `build.zig` handles:**
+- Defines Zig modules:
+ - `esp_idf` → High-level facade module that re-exports safe wrappers from `imports/*.zig` (gpio, wifi, heap, rtos, led, etc.)
+ - `sys` → Low-level module that imports the generated `idf-sys.zig` (raw C bindings)
+- Collects and compiles Zig source files
+- Uses pre-generated includes and dependencies from CMake
+- Generates object files (`app_zig.o`) that CMake links with ESP-IDF
+
+**What has moved to CMake:**
+- Searching and linking IDF object files and `.a` libraries → handled by ESP-IDF's CMake system
+- Collecting include paths from IDF components → automatic via `zig-config.cmake`
+- Running `translate-c` on `stubs.h` to generate `idf-sys.zig` → fully automated in `cmake/` scripts
+- Applying target-specific patches → handled by `cmake/patch.cmake`
+- Detecting and including managed components → automatic detection in `zig-config.cmake`
+- Toolchain selection (espressif vs upstream Zig) → automatic based on target architecture
+
+**This separation makes the project cleaner:**
+- **CMake** handles the complex C/ESP-IDF world (toolchain, bindings generation, component management)
+- **Zig** handles only modern, safe, comptime-friendly code compilation
+
+---
+
+### Managed Components Integration
+
+The build system automatically detects managed components installed via `idf.py add-dependency`:
+
+**Add extra-components like:**
+- `espressif/led_strip` → `HAS_LED_STRIP` define
+- `espressif/esp-dsp` → `HAS_ESP_DSP` define
+- `espressif/esp_bsp_devkit` → `HAS_ESP_BSP_DEVKIT` define
+
+**How it works:**
+1. CMake detects components in `managed_components/` directory
+2. Adds component include paths to `INCLUDE_DIRS`
+3. Generates preprocessor defines (e.g., `HAS_LED_STRIP=1`)
+4. Passes defines to `translate-c` for binding generation
+5. Headers are conditionally included in `include/stubs.h`:
+ ```c
+ #if HAS_LED_STRIP
+ #include "led_strip.h"
+ #endif
+ ```
+
+**To add a new managed component:**
+1. Run: `idf.py add-dependency vendor/component`
+2. Add detection in `extra-components.cmake`:
+ ```cmake
+ check_managed_component("Component Name" "vendor" "component" "HAS_COMPONENT")
+ ```
+3. Add conditional include in `include/stubs.h`
+4. Use in Zig via the wrapped API in `imports/`
+
+---
+
+### Toolchain Selection
+
+The build system intelligently selects the appropriate Zig toolchain:
+
+**RISC-V targets (all variants including H4/P4):**
+- Works with both **upstream Zig** and **Espressif's Zig fork**
+- Upstream Zig: uses generic `riscv32-freestanding-none` with feature flags (e.g. `+m+a+c+zicsr+zifencei`)
+- Espressif fork: uses named CPU models (e.g. `esp32c6`, `esp32p4`) with exact feature sets
+- Build system auto-detects which toolchain is in use and selects the right CPU model
+
+**Xtensa targets (ESP32, ESP32-S2, ESP32-S3):**
+- Always uses **Espressif's Zig fork** (Xtensa support not in upstream)
+
+The toolchain is automatically downloaded and cached in `build/zig-relsafe-*` if not found.
+
+---
+
+### Advanced: Build System Internals
+
+For advanced users who want to understand or modify the build system:
+
+**Key CMake files:**
+- `cmake/zig-config.cmake` → Main configuration, target detection, component discovery
+- `cmake/zig-download.cmake` → Automatic Zig toolchain download
+- `cmake/zig-runner.cmake` → Helper functions for running Zig commands
+- `cmake/bindings.cmake` → get esp-rs/esp-idf-sys bindings header
+- `cmake/extra-components.cmake` → helper to add more components
+- `cmake/patch.cmake` → Post-processing patches for generated bindings
+
+**Bindings generation flow:**
+1. CMake collects all IDF component include paths
+2. Detects managed components and adds their paths
+3. Runs `zig translate-c` on `include/stubs.h` with all includes
+4. Generates `imports/idf-sys.zig` with raw C bindings
+5. Applies target-specific patches (ESP32-H2, H4, P4)
+6. Zig code imports via `@import("sys")` or high-level `@import("esp_idf")`
+
+**Zig module structure:**
+```
+imports/
+├── idf-sys.zig # Generated C bindings (don't edit manually)
+├── esp_idf.zig # Main facade module
+├── gpio.zig # GPIO wrapper
+├── wifi.zig # WiFi wrapper
+├── heap.zig # Heap allocators
+├── rtos.zig # FreeRTOS wrappers
+├── led-strip.zig # LED strip wrapper (requires HAS_LED_STRIP)
+└── ... # Other high-level wrappers
+```
+
+**In your Zig code:**
+```zig
+const idf = @import("esp_idf");
+const sys = idf.sys; // Access raw C bindings if needed
+const led = idf.led; // Use wrapped APIs (recommended)
+```
+
+This architecture allows safe, idiomatic Zig code while maintaining full access to ESP-IDF's C APIs when necessary.
+
+### Build Scheme Graph
+
+```
+┌─────────────────────────────────────────────────────────────────────────┐
+│ ESP-IDF Build System │
+└─────────────────────────────────────────────────────────────────────────┘
+ │
+ ▼
+ ┌───────────────┐
+ │ idf.py │
+ │ (Python) │
+ └───────┬───────┘
+ │
+ ┌───────────────┴───────────────┐
+ ▼ ▼
+ ┌───────────────┐ ┌──────────────────┐
+ │ set-target │ │ add-dependency │
+ │ (esp32c6) │ │ (managed_comps) │
+ └───────┬───────┘ └────────┬─────────┘
+ │ │
+ └──────────────┬──────────────┘
+ ▼
+ ┌─────────────────┐
+ │ CMake │
+ │ Configure │
+ └────────┬────────┘
+ │
+ ┌─────────────────────────┼─────────────────────────┐
+ │ │ │
+ ▼ ▼ ▼
+┌──────────────┐ ┌─────────────────────┐ ┌──────────────────┐
+│ ESP-IDF │ │ zig-config.cmake │ │ Component │
+│ Components │ │ • Detect target │ │ Detection │
+│ (.a libs) │ │ • Find toolchain │ │ (managed_comps) │
+└──────┬───────┘ │ • Collect includes │ └────────┬─────────┘
+ │ │ • Check components │ │
+ │ └──────────┬──────────┘ │
+ │ │ │
+ │ ┌──────────▼──────────┐ │
+ │ │ bindings.cmake │ │
+ │ │ • Build INCLUDE │◄────────────┘
+ │ │ list with │
+ │ │ managed_comps │
+ │ │ • Generate defines │
+ │ │ (HAS_COMP_NAME) │
+ │ └──────────┬──────────┘
+ │ │
+ │ ┌──────────▼──────────┐
+ │ │ zig translate-c │
+ │ │ stubs.h → │
+ │ │ idf-sys.zig │
+ │ └──────────┬──────────┘
+ │ │
+ │ ┌──────────▼──────────┐
+ │ │ patch.cmake │
+ │ │ • Fix bitfields │
+ │ │ • Target patches │
+ │ │ (H2, H4, P4) │
+ │ └──────────┬──────────┘
+ │ │
+ │ ▼
+ │ ┌─────────────────────┐
+ │ │ build.zig │◄─────────┐
+ │ │ • Import idf-sys │ │
+ │ │ • Define modules: │ │
+ │ │ - esp_idf │ │
+ │ │ - sys │ │
+ │ │ • Compile Zig │ │
+ │ │ sources │ │
+ │ └──────────┬──────────┘ │
+ │ │ │
+ │ ▼ │
+ │ ┌─────────────────────┐ │
+ │ │ Zig Compiler │ │
+ │ │ (upstream or │ │
+ │ │ espressif fork) │ │
+ │ └──────────┬──────────┘ │
+ │ │ │
+ │ ▼ │
+ │ ┌─────────────────────┐ │
+ │ │ app_zig.o │ │
+ │ │ (Zig object) │ │
+ │ └──────────┬──────────┘ │
+ │ │ │
+ └───────────────────────┼─────────────────────┘
+ │
+ ▼
+ ┌─────────────────────┐
+ │ Ninja / Make │
+ │ Link all objects │
+ │ + ESP-IDF libs │
+ └──────────┬──────────┘
+ │
+ ▼
+ ┌─────────────────────┐
+ │ ELF Binary │
+ │ ├─ bootloader.bin │
+ │ ├─ partition.bin │
+ │ └─ app.bin │
+ └──────────┬──────────┘
+ │
+ ┌──────────▼──────────┐
+ │ idf.py flash │
+ │ (esptool.py) │
+ └──────────┬──────────┘
+ │
+ ▼
+ ┌──────────┐
+ │ ESP32 │
+ │ Device │
+ └──────────┘
+
+┌─────────────────────────────────────────────────────────────────────────┐
+│ Key Data Flows │
+├─────────────────────────────────────────────────────────────────────────┤
+│ 1. Component Discovery: CMake → managed_components/ → HAS_* defines │
+│ 2. Binding Generation: stubs.h + includes → translate-c → idf-sys.zig │
+│ 3. Zig Compilation: build.zig → zig build-obj → app_zig.o │
+│ 4. Final Link: ESP-IDF .a libs + app_zig.o → firmware.elf │
+└─────────────────────────────────────────────────────────────────────────┘
+```
+### Led-dtrip component e.g:
+```
+┌─────────────────────────────────────────────────────────────────────────┐
+│ Managed Components Flow │
+├─────────────────────────────────────────────────────────────────────────┤
+│ idf.py add-dependency espressif/led_strip │
+│ ↓ │
+│ managed_components/espressif__led_strip/ │
+│ ↓ │
+│ zig-config.cmake detects component │
+│ ↓ │
+│ Adds: -DHAS_LED_STRIP=1 -I.../espressif__led_strip/include │
+│ ↓ │
+│ stubs.h: #if HAS_LED_STRIP → #include "led_strip.h" │
+│ ↓ │
+│ translate-c generates bindings │
+│ ↓ │
+│ Zig code: const led = @import("esp_idf").led; │
+└─────────────────────────────────────────────────────────────────────────┘
+```
diff --git a/software/zig_main/docs/build-scheme.png b/software/zig_main/docs/build-scheme.png
new file mode 100644
index 0000000..82c83cb
Binary files /dev/null and b/software/zig_main/docs/build-scheme.png differ
diff --git a/software/zig_main/docs/getting-started.md b/software/zig_main/docs/getting-started.md
new file mode 100644
index 0000000..76f731c
--- /dev/null
+++ b/software/zig_main/docs/getting-started.md
@@ -0,0 +1,820 @@
+# Getting Started with Zig ESP-IDF Sample
+
+A complete guide to building ESP32 firmware using Zig and ESP-IDF.
+
+## Table of Contents
+
+- [Prerequisites](#prerequisites)
+- [Installation](#installation)
+- [Quick Start](#quick-start)
+- [Project Structure](#project-structure)
+- [Building Your First Project](#building-your-first-project)
+- [Working with Components](#working-with-components)
+- [Examples](#examples)
+- [Troubleshooting](#troubleshooting)
+- [Next Steps](#next-steps)
+
+---
+
+## Prerequisites
+
+### Required Software
+
+- **Python 3.12+** (for ESP-IDF tools)
+- **Git** (for cloning repositories)
+- **CMake 3.16+** (bundled with ESP-IDF)
+- **Ninja** or **Make** (bundled with ESP-IDF)
+- **Zig compiler** (optional - will be auto-downloaded if not found)
+
+### Supported Operating Systems
+
+- **Linux** (Ubuntu 20.04+, Debian, Fedora, Arch)
+- **macOS** (10.15+)
+- **Windows** (10/11 with PowerShell or WSL2)
+- **Nix/NixOS** (via `flake.nix`)
+
+### Supported ESP32 Targets
+
+| Architecture | Targets |
+|--------------|---------|
+| **RISC-V** | ESP32-C2, C3, C5, C6, C61, H2, H21, H4, P4 |
+| **Xtensa** | ESP32, ESP32-S2, ESP32-S3 |
+
+---
+
+## Installation
+
+### Step 1: Install ESP-IDF
+
+**Option A: Standard Installation**
+
+```bash
+# Clone ESP-IDF
+git clone --recursive https://github.com/espressif/esp-idf.git
+cd esp-idf
+
+# Linux/macOS:
+./install.sh
+
+# Windows (PowerShell):
+.\install.ps1
+
+# Windows (Command Prompt):
+install.bat
+```
+
+**Option B: Using Nix Flakes** (Linux/macOS)
+
+```bash
+# Enter development environment with all dependencies
+nix develop
+
+# Or use direnv for automatic activation
+echo "use flake" > .envrc
+direnv allow
+```
+
+**Option C: ESP-IDF installer** (Windows/macOS)
+
+Download from: https://dl.espressif.com/dl/esp-idf/
+
+### Step 2: Set up ESP-IDF Environment
+
+Every time you open a new terminal, activate ESP-IDF:
+
+```bash
+# Linux/macOS:
+. $HOME/esp/esp-idf/export.sh
+
+# Windows (PowerShell):
+. $HOME/esp/esp-idf/export.ps1
+
+# Windows (Command Prompt):
+%USERPROFILE%\esp\esp-idf\export.bat
+```
+
+### Step 3: Clone This Project
+
+```bash
+git clone https://github.com/kassane/zig-esp-idf-sample.git
+cd zig-esp-idf-sample
+```
+
+### Step 4: Verify Installation
+
+```bash
+idf.py --version
+# Expected output: ESP-IDF v5.x.x or v6.x.x
+```
+
+---
+
+## Quick Start
+
+### 1. Set Your Target Device
+
+```bash
+# For ESP32-C6 (RISC-V)
+idf.py set-target esp32c6
+
+# For ESP32-S3 (Xtensa)
+idf.py set-target esp32s3
+
+# For ESP32 (original, Xtensa)
+idf.py set-target esp32
+```
+
+### 2. Build the Project
+
+```bash
+idf.py build
+```
+
+**What happens during build:**
+- ✅ CMake detects your target and configures the build
+- ✅ Zig toolchain is automatically downloaded (if needed)
+- ✅ C bindings are generated from ESP-IDF headers
+- ✅ Target-specific patches are applied
+- ✅ Zig code is compiled to object files
+- ✅ Everything is linked with ESP-IDF libraries
+- ✅ Firmware binaries are generated
+
+### 3. Flash to Device
+
+```bash
+# Connect your ESP32 via USB, then:
+idf.py -p PORT flash
+
+# Find your port:
+# Linux: /dev/ttyUSB0 or /dev/ttyACM0
+# macOS: /dev/cu.usbserial-*
+# Windows: COM3, COM4, etc.
+
+# Example:
+idf.py -p /dev/ttyUSB0 flash
+```
+
+### 4. Monitor Output
+
+```bash
+idf.py -p PORT monitor
+
+# Or combine flash + monitor:
+idf.py -p PORT flash monitor
+
+# Exit monitor: Ctrl+]
+```
+
+---
+
+## Project Structure
+
+```
+zig-esp-idf-sample/
+├── build.zig # Zig build script (compiles Zig code)
+├── build.zig.zon # Zig package manifest
+├── CMakeLists.txt # Root CMake config
+├── sdkconfig # ESP-IDF configuration (generated)
+├── sdkconfig.defaults # Default SDK config
+├── sdkconfig.defaults.esp32 # ESP32-specific defaults
+├── partitions_matter.csv # Custom partition table for Matter (3 MB app, 4 MB flash)
+├── dependencies.lock # Component version lock
+├── wokwi.toml # Wokwi simulator config
+│
+├── main/
+│ ├── CMakeLists.txt # Main component config
+│ ├── placeholder.c # Minimal C entry point (required by CMake)
+│ ├── matter_wrappers.cpp # C++ shims for esp_matter C++ API (activated when component present)
+│ ├── app.zig # Main Zig application entry
+│ ├── idf_component.yml # Component dependencies
+│ ├── Kconfig.projbuild # Project configuration options
+│ └── examples/
+│ ├── gpio-blink.zig # Toggle LED on GPIO2
+│ ├── uart-echo.zig # UART echo
+│ ├── i2c-scan.zig # I2C bus scan
+│ ├── wifi-station.zig # WiFi station
+│ ├── http-server.zig # HTTP server
+│ ├── ble-gatt-server.zig # BLE GATT server
+│ ├── smartled-rgb.zig # WS2812B LED strip
+│ ├── dsp-math.zig # DSP/FFT operations
+│ └── matter-light.zig # Matter On/Off Light
+│
+├── imports/ # Zig API wrappers and bindings
+│ ├── idf.zig # Main ESP-IDF facade module
+│ ├── idf-sys.zig # Generated C bindings (auto-generated)
+│ ├── sys.zig # Re-exports idf-sys
+│ ├── error.zig # esp_err_t → Zig error mapping
+│ ├── logger.zig # std.log integration (espLogFn)
+│ ├── version.zig # ESP-IDF version info (ver)
+│ ├── heap.zig # HeapCapsAllocator, MultiHeapAllocator
+│ ├── bootloader.zig # Partition/bootloader control
+│ ├── gpio.zig # GPIO wrapper
+│ ├── wifi.zig # WiFi station/AP/scan
+│ ├── uart.zig # UART driver
+│ ├── i2c.zig # I2C master
+│ ├── spi.zig # SPI master (+ SDSPI)
+│ ├── i2s.zig # I2S audio (STD, PDM, TDM)
+│ ├── http.zig # HTTP server + client
+│ ├── mqtt.zig # MQTT client
+│ ├── lwip.zig # lwIP sockets, DNS, SNTP
+│ ├── crc.zig # ESP-ROM CRC-8/16/32
+│ ├── bluetooth.zig # Bluedroid BLE
+│ ├── nimble.zig # NimBLE BLE (compile-time guarded)
+│ ├── now.zig # ESP-NOW protocol
+│ ├── nvs.zig # NVS flash key-value storage
+│ ├── partition.zig # Partition table operations
+│ ├── sleep.zig # Deep/light sleep + wakeup
+│ ├── event.zig # ESP event loop
+│ ├── wdt.zig # Task watchdog timer
+│ ├── rtos.zig # FreeRTOS tasks/queues/semaphores/timers
+│ ├── pcnt.zig # Pulse counter (pulse)
+│ ├── phy.zig # Wireless PHY / RF calibration
+│ ├── segger.zig # Segger SystemView profiling
+│ ├── led-strip.zig # LED strip — requires espressif/led_strip
+│ ├── dsp.zig # DSP/FFT — requires espressif/esp-dsp
+│ ├── hosted.zig # ESP-Hosted coexistence — requires espressif/esp_hosted
+│ ├── wifi_remote.zig # WiFi remote — requires espressif/esp_wifi_remote
+│ ├── timer.zig # High-resolution esp_timer (one-shot + periodic)
+│ ├── ledc.zig # LED PWM controller (duty, fade)
+│ ├── twai.zig # TWAI/CAN bus driver
+│ ├── pm.zig # Power management locks
+│ ├── pthread.zig # POSIX threads (FreeRTOS-backed)
+│ └── panic.zig # Zig panic handler
+│
+├── include/ # C headers for binding generation
+│ ├── stubs.h # Core ESP-IDF headers (input to zig translate-c)
+│ ├── wifi_stubs.h # WiFi macro wrappers
+│ ├── bt_stubs.h # Bluetooth macro wrappers
+│ ├── matter_stubs.h # C wrapper interface for esp_matter (C++ component)
+│ ├── matter_closure_patch.h # GCC 14 C++23 fix for closure-control cluster
+│ └── bindings.h # Additional bindings
+│
+├── cmake/ # CMake build system scripts
+│ ├── zig-config.cmake # Main Zig configuration
+│ ├── zig-download.cmake # Auto-download Zig toolchain
+│ ├── zig-runner.cmake # Helper functions for Zig
+│ ├── bindings.cmake # Binding generation
+│ ├── extra-components.cmake # Managed component detection
+│ └── patch.cmake # Post-processing patches
+│
+├── patches/ # Binding fixes for translate-c issues
+│ ├── *.zig
+│
+├── docs/ # Documentation
+│ ├── build-internals.md # Build system details
+│ ├── build-scheme.png # Build flow diagram
+│ └── zig-xtensa.md # Xtensa toolchain info
+│
+└── flake.nix # Nix development environment
+```
+
+---
+
+## Building Your First Project
+
+### Example 1: Hello World
+
+```zig
+const std = @import("std");
+const idf = @import("esp_idf");
+
+comptime {
+ @export(&main, .{ .name = "app_main" });
+}
+
+fn main() callconv(.c) void {
+ log.info("Hello from Zig on ESP32!", .{});
+ log.info("Zig version: {s}", .{@import("builtin").zig_version_string});
+
+ // Show memory info
+ var heap = idf.heap.HeapCapsAllocator.init(null); // default: MALLOC_CAP_DEFAULT
+ log.info("Free heap: {} bytes", .{heap.freeSize()});
+
+ // Sleep forever
+ while (true) {
+ idf.rtos.Task.delayMs(1000);
+ }
+}
+
+// overwrite zig std_options config
+pub const std_options: std.Options = .{
+ .logFn = idf.log.espLogFn,
+};
+// rename log instance
+const log = std.log.scoped(.@"esp-idf");
+// overwrite std panic-handler
+pub const panic = idf.esp_panic.panic;
+```
+
+**Build and run:**
+```bash
+idf.py build flash monitor
+```
+
+### Example 2: Blinking LED
+
+**Create `main/examples/blink.zig`:**
+
+```zig
+const std = @import("std");
+const idf = @import("esp_idf");
+
+const LED_PIN = .@"18"; // Change to your LED pin
+
+comptime {
+ @export(&main, .{ .name = "app_main" });
+}
+
+fn main() callconv(.c) void {
+ // Configure GPIO as output
+ idf.gpio.Direction.set(LED_PIN, .output) catch {
+ log.err("Failed to configure GPIO", .{});
+ return;
+ };
+
+ log.info("Blinking LED on GPIO {d}", .{LED_PIN});
+
+ while (true) {
+ // LED ON
+ idf.gpio.Level.set(LED_PIN, 1) catch {};
+ log.info("LED ON", .{});
+ idf.rtos.Task.delayMs(1000);
+
+ // LED OFF
+ idf.gpio.Level.set(LED_PIN, 0) catch {};
+ log.info("LED OFF", .{});
+ idf.rtos.Task.delayMs(1000);
+ }
+}
+
+// overwrite zig std_options config
+pub const std_options: std.Options = .{
+ .logFn = idf.log.espLogFn,
+};
+// rename log instance
+const log = std.log.scoped(.blink);
+// overwrite std panic-handler
+pub const panic = idf.esp_panic.panic;
+```
+
+---
+
+## Working with Components
+
+### Adding Managed Components
+
+Components are specified in `main/idf_component.yml`:
+
+**Add a component:**
+
+```bash
+# Use idf.py command:
+idf.py add-dependency espressif/led_strip
+# then
+idf.py reconfigure
+```
+
+### Using LED Strip Component
+
+**The build system automatically:**
+1. ✅ Detects components in `dependencies.lock`
+2. ✅ Adds include paths to binding generation
+3. ✅ Generates `HAS_*` defines (e.g., `HAS_LED_STRIP=1`)
+4. ✅ Includes headers in `include/stubs.h` conditionally
+5. ✅ Makes APIs available in Zig via `idf.*` modules
+
+**1. Ensure LED strip is in `main/idf_component.yml`:**
+```yaml
+dependencies:
+ espressif/led_strip: "*"
+```
+
+**2. Reconfigure:**
+```bash
+idf.py reconfigure
+```
+
+**3. Check provided example:**
+
+The example in [examples/smartled-rgb.zig](../main/examples/smartled-rgb.zig) demonstrates:
+- Configuring WS2812B LED strip on GPIO 2
+- Setting individual pixel colors
+- Refreshing the display
+- Blinking pattern
+
+### DSP basics (FFT + math)
+
+[examples/dsp-math.zig](../main/examples/dsp-math.zig)
+
+- Generates a sine tone (freq = 0.2 normalized)
+- Applies Hann window
+- Does FFT (1024 points, float32, radix-2)
+- Computes power spectrum in dB
+- Prints ASCII plot of the spectrum (64×10 chars, -120 to +40 dB)
+
+### Using WiFi
+
+**Use the WiFi station example:**
+[examples/wifi-station.zig](../main/examples/wifi-station.zig)
+
+**Configure WiFi credentials:**
+
+```bash
+idf.py menuconfig
+# Navigate to: Example Configuration
+# Set WiFi SSID and Password
+```
+
+Or edit [main/Kconfig.projbuild](../main/Kconfig.projbuild) to change default values.
+
+### Available Wrapper APIs
+
+The project provides comprehensive Zig wrappers in `imports/`:
+
+| Module | Description | Notes |
+|--------|-------------|-------|
+| `idf.gpio` | GPIO control | Any target |
+| `idf.wifi` | WiFi station/AP/scan | Not on H2/H4/P4 |
+| `idf.bt` | Bluedroid BLE | Requires `CONFIG_BT_ENABLED` |
+| `idf.nimble` | NimBLE BLE | Requires `CONFIG_BT_NIMBLE_ENABLED` |
+| `idf.heap` | HeapCapsAllocator, MultiHeapAllocator | Any target |
+| `idf.rtos` | FreeRTOS tasks/queues/semaphores/timers | Any target |
+| `idf.nvs` | NVS flash key-value storage | Any target |
+| `idf.partition` | Partition table operations | Any target |
+| `idf.sleep` | Deep/light sleep + wakeup sources | Any target |
+| `idf.event` | ESP event loop | Any target |
+| `idf.wdt` | Task watchdog timer | Any target |
+| `idf.bl` | Bootloader/partition control | Any target |
+| `idf.i2c` | I2C master | Any target |
+| `idf.spi` | SPI master + SDSPI | Any target |
+| `idf.uart` | UART driver | Any target |
+| `idf.i2s` | I2S audio (STD, PDM, TDM) | Any target |
+| `idf.http` | HTTP server (httpd) + client | Any target |
+| `idf.mqtt` | MQTT client | Any target |
+| `idf.lwip` | lwIP sockets, DNS, SNTP | Any target |
+| `idf.crc` | ESP-ROM CRC-8/16/32 | Any target |
+| `idf.esp_now` | ESP-NOW protocol | Any target |
+| `idf.pulse` | Pulse counter (PCNT) | Any target |
+| `idf.phy` | Wireless PHY / RF calibration | Any target |
+| `idf.segger` | Segger SystemView profiling | Any target |
+| `idf.ver` | ESP-IDF version info | Any target |
+| `idf.led` | LED strip WS2812B | Requires `espressif/led_strip` |
+| `idf.dsp` | DSP/FFT operations | Requires `espressif/esp-dsp` |
+| `idf.esp_hosted` | ESP-Hosted coexistence | Requires `espressif/esp_hosted` |
+| `idf.wifi_remote` | WiFi via slave MCU | Requires `espressif/esp_wifi_remote` |
+| `idf.timer` | High-resolution esp_timer | Any target |
+| `idf.ledc` | LED PWM controller | Any target |
+| `idf.twai` | TWAI/CAN bus driver | Any target |
+| `idf.pm` | Power management locks | Any target |
+| `idf.pthread` | POSIX threads (FreeRTOS-backed) | Any target |
+| `idf.matter` | Matter/CHIP protocol | Requires `espressif/esp_matter` |
+| `idf.log` | std.log integration (`espLogFn`) | Any target |
+| `idf.err` | esp_err_t → Zig error mapping | Any target |
+| `idf.sys` | Raw C bindings | Direct ESP-IDF API access |
+
+---
+
+## Examples
+
+### FreeRTOS Tasks
+
+```zig
+const idf = @import("esp_idf");
+const std = @import("std");
+
+// overwrite std panic-handler
+pub const panic = idf.esp_panic.panic;
+// overwrite std.log
+pub const std_options: std.Options = .{
+ .logFn = idf.log.espLogFn,
+};
+
+export fn myTask(_: ?*anyopaque) callconv(.c) void {
+ const log = std.log.scoped(.task);
+ while (true) {
+ log.info("Task running!", .{});
+ idf.rtos.Task.delayMs(1000);
+ }
+}
+
+export fn app_main() callconv(.c) void {
+ _ = idf.rtos.Task.create(myTask, "my_task", 2048, null, 5) catch @panic("Failed to create task");
+
+ // Main task continues...
+ while (true) {
+ idf.rtos.Task.delayMs(1000);
+ }
+}
+```
+
+### Using Custom Allocators
+
+```zig
+const idf = @import("esp_idf");
+const std = @import("std");
+
+export fn app_main() callconv(.c) void {
+ var arena = std.heap.ArenaAllocator.init(std.heap.c_allocator);
+ defer arena.deinit();
+
+ const allocator = arena.allocator();
+
+ var list: std.ArrayList(u32) = .empty;
+ defer list.deinit(allocator);
+
+ list.append(allocator, 10) catch {};
+ list.append(allocator, 20) catch {};
+ list.append(allocator, 30) catch {};
+
+ // ...
+}
+```
+
+### Accessing Raw C APIs
+
+When wrappers aren't available, use raw bindings:
+
+```zig
+const idf = @import("esp_idf");
+const std = @import("std");
+const sys = idf.sys;
+const log = std.log.scoped(.@"esp-idf");
+
+export fn app_main() callconv(.c) void {
+ var mac_addr: [6]u8 = undefined;
+
+ // Direct ESP-IDF C API call
+ const result = sys.esp_efuse_mac_get_default(&mac_addr);
+ if (result != sys.ESP_OK) {
+ std.log.err("Failed to get MAC address", .{});
+ return;
+ }
+
+ log.info("MAC: {X:0>2}:{X:0>2}:{X:0>2}:{X:0>2}:{X:0>2}:{X:0>2}", .{
+ mac_addr[0], mac_addr[1], mac_addr[2],
+ mac_addr[3], mac_addr[4], mac_addr[5],
+ });
+}
+
+pub const std_options: std.Options = .{
+ .logFn = idf.log.espLogFn,
+};
+```
+
+---
+
+## Troubleshooting
+
+### Build Errors
+
+**"zig: command not found"**
+- The Zig toolchain should auto-download. Check `build/zig-relsafe-*` directory
+- Or install manually: https://ziglang.org/download/
+- For Nix users: `nix develop`
+
+**"Component not found: espressif__led_strip"**
+```bash
+# Check idf_component.yml
+cat main/idf_component.yml
+
+# Reconfigure to download components
+idf.py reconfigure
+```
+
+**"translate-c failed"**
+- Check `include/stubs.h` and `include/wifi_stubs.h` for syntax errors
+- Ensure managed components exist in `managed_components/`
+- Check that `HAS_*` defines match components in `cmake/extra-components.cmake`
+
+**Binding generation errors**
+- The `patches/` directory contains fixes for common `translate-c` issues
+- If you see struct layout errors, a patch likely needs updating
+- Check `cmake/patch.cmake` for how patches are applied
+
+### Flash Errors
+
+**"Serial port not found"**
+```bash
+# Linux: Add user to dialout group
+sudo usermod -a -G dialout $USER
+# Log out and back in
+
+# Check available ports
+ls /dev/tty*
+
+# Windows: Check Device Manager for COM port
+```
+
+**"Failed to connect to ESP32"**
+- Hold BOOT button while connecting
+- Try different USB cable/port (must be data cable, not power-only)
+- Verify target matches your device: `idf.py set-target esp32c6`
+- Check USB drivers are installed (CP210x or CH340)
+
+### Runtime Errors
+
+**"Guru Meditation Error: Core panic"**
+- Check stack size (increase in `xTaskCreate`, default 2048 often too small)
+- Verify GPIO pins match your hardware
+- Enable debug build: `idf.py menuconfig` → Compiler options → Debug (-Og)
+- Check `sdkconfig` for panic handler settings
+
+**"Out of memory" / Heap errors**
+- Use arena allocators: `std.heap.ArenaAllocator`
+- Check available heap: `heap.freeSize()`
+- Reduce allocations in hot paths
+- Consider enabling PSRAM if available
+- Check `idf.py size-components` for memory usage
+
+**WiFi not connecting**
+- Verify SSID and password in menuconfig
+- Check WiFi country code matches your region
+- Ensure 2.4GHz WiFi (5GHz not supported)
+- Check WiFi credentials don't contain special characters
+
+**Matter example: "app partition is too small"**
+
+The Matter binary is ~2.2 MB and requires a 4 MB flash chip and custom partition table:
+```bash
+# Ensure sdkconfig has 4 MB flash and custom partition:
+# CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
+# CONFIG_PARTITION_TABLE_CUSTOM=y
+# CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions_matter.csv"
+
+# If sdkconfig doesn't reflect sdkconfig.defaults changes, update it:
+idf.py reconfigure
+idf.py build -DCONFIG_ZIG_EXAMPLE_MATTER_LIGHT=y
+```
+
+**Matter example: IDF version incompatibility**
+- `espressif/esp_matter` 1.4.x requires IDF **v5.x** (tested with v5.5)
+- Not compatible with IDF v6.x (depends on `json`/`mqtt` components removed in v6)
+- Check your IDF version: `idf.py --version`
+
+---
+
+## Next Steps
+
+### Learn More
+
+- **Zig Language:** https://ziglang.org/documentation/master/
+- **ESP-IDF Docs:** https://docs.espressif.com/projects/esp-idf/
+- **Project Wiki:**
+ - [Build Internals](./build-internals.md)
+ - [Xtensa Toolchain](./zig-xtensa.md)
+
+### Explore Examples
+
+All examples are in `main/examples/`:
+- `gpio-blink.zig` - Toggle an LED on GPIO2 (any target)
+- `uart-echo.zig` - Read UART1 and echo back
+- `i2c-scan.zig` - Scan I2C bus for devices
+- `wifi-station.zig` - Connect to a WiFi AP
+- `http-server.zig` - Serve a web page over WiFi
+- `ble-gatt-server.zig` - BLE peripheral with GATT notifications (requires `CONFIG_BT_ENABLED`)
+- `smartled-rgb.zig` - WS2812B LED strip control (requires `espressif/led_strip`)
+- `dsp-math.zig` - FFT + power spectrum via DSP (requires `espressif/esp-dsp`)
+- `matter-light.zig` - Matter On/Off Light device (requires `espressif/esp_matter`, IDF v5.x, 4 MB flash)
+
+### Configuration
+
+```bash
+# Interactive configuration menu
+idf.py menuconfig
+
+# Key settings to explore:
+# - Component config → FreeRTOS → Tick rate (Hz)
+# - Component config → ESP System Settings → Panic handler behavior
+# - Partition Table → Choose partition scheme
+# - Example Configuration → WiFi credentials
+# - Compiler options → Optimization level
+```
+
+### Testing with Wokwi Simulator
+
+The project includes `wokwi.toml` for simulation:
+
+1. Install Wokwi CLI: https://docs.wokwi.com/wokwi-ci/idf-wokwi-usage
+2. Edit `wokwi.toml` to match your project
+3. Run: `idf.py wokwi --timeout 30000`
+
+or use VSCode Extension
+
+* More info: https://docs.wokwi.com/vscode/getting-started
+
+### Advanced Topics
+
+- **Custom Components:** Create reusable Zig modules in `imports/`
+- **WiFi & Networking:** HTTP servers, WebSockets, mDNS
+- **OTA Updates:** Over-the-air firmware updates
+- **Bluetooth:** BLE advertising, GATT services
+- **Deep Sleep:** Ultra-low power modes
+- **File Systems:** SPIFFS, FAT, LittleFS
+- **Cryptography:** Hardware-accelerated crypto
+
+### Development Tips
+
+**Use `sdkconfig.defaults` for version control:**
+- Base config: `sdkconfig.defaults`
+- Target-specific: `sdkconfig.defaults.esp32`
+- Don't commit `sdkconfig` (it's generated)
+
+**Debugging:**
+```bash
+# Monitor with timestamps
+idf.py monitor --timestamps
+
+# Filter logs by tag
+idf.py monitor --print-filter "tag:app"
+
+# Save logs to file
+idf.py monitor | tee output.log
+```
+
+**Clean builds when needed:**
+```bash
+# Clean Zig cache
+rm -rf .zig-cache
+
+# Full CMake clean
+idf.py fullclean
+
+# Regenerate bindings
+idf.py reconfigure
+```
+
+### Contributing
+
+Found a bug or want to contribute?
+- **GitHub:** https://github.com/kassane/zig-esp-idf-sample
+- **Issues:** https://github.com/kassane/zig-esp-idf-sample/issues
+- **Pull Requests:** See [CONTRIBUTING.md](../CONTRIBUTING.md)
+
+---
+
+## Quick Reference
+
+### Common Commands
+
+```bash
+# Setup
+idf.py set-target esp32c6 # Set target device
+idf.py reconfigure # Regenerate build config / update deps
+
+# Build
+idf.py build # Build project
+idf.py clean # Clean build files
+idf.py fullclean # Full clean (including config)
+
+# Flash
+idf.py -p PORT flash # Flash firmware
+idf.py -p PORT monitor # Monitor serial output
+idf.py -p PORT flash monitor # Flash and monitor
+idf.py -p PORT app-flash # Flash app only (faster)
+
+# Config
+idf.py menuconfig # Interactive configuration
+idf.py size # Show binary sizes
+idf.py size-components # Component size breakdown
+
+# Dependencies
+idf.py add-dependency PKG # Add managed component
+
+# Help
+idf.py --help # Show all commands
+idf.py --list-targets # List supported targets
+```
+
+### File Locations
+
+- **Main app:** `main/app.zig`
+- **Examples:** `main/examples/*.zig`
+- **Dependencies:** `main/idf_component.yml`
+- **Configuration:** `sdkconfig`, `sdkconfig.defaults*`
+- **Bindings:** `imports/idf-sys.zig` (auto-generated)
+- **Wrappers:** `imports/*.zig`
+- **Build scripts:** `cmake/*.cmake`
+
+### Pin Configuration
+
+Check your board's pinout:
+- **GPIO Pins:** Varies by model (ESP32: 0-39, ESP32-C6: 0-30)
+- **Built-in LED:** Often GPIO 2, 8, or 18
+- **UART:** Usually GPIO 1 (TX), GPIO 3 (RX)
+- **I2C:** SDA/SCL pins vary by board
+- **SPI:** MOSI/MISO/CLK pins vary by board
+
+### Hardware Requirements
+
+- **ESP32 Development Board** (any supported variant)
+- **USB Cable** (must be data cable, not power-only)
+- **LEDs/Components** (optional, for examples)
+- **LED Strip WS2812B** (optional, for smartled-rgb example)
+
+---
+
+## Support
+
+- **Documentation:** [docs/](.)
+- **Issues:** https://github.com/kassane/zig-esp-idf-sample/issues
+- **Discussions:** https://github.com/kassane/zig-esp-idf-sample/discussions
diff --git a/software/zig_main/docs/zig-xtensa.md b/software/zig_main/docs/zig-xtensa.md
new file mode 100644
index 0000000..8d8c63a
--- /dev/null
+++ b/software/zig_main/docs/zig-xtensa.md
@@ -0,0 +1,184 @@
+# Zig for Xtensa (Esp32, Esp8266 and Esp32-S series)
+
+Like [esp-rs](https://github.com/espressif/rust-esp32-example/blob/main/docs/rust-on-xtensa.md), forked zig toolchain uses **LLVM codegen** for xtensa target.
+
+**Current version:**
+
+- **Zig**: v0.16.0 ([bootstrap fork](https://github.com/kassane/zig-espressif-bootstrap))
+- **LLVM**: v21.1.0 ([espressif-fork](https://github.com/espressif/llvm-project))
+
+
+### Commands
+
+**Zig command line interface:**
+
+- `build-lib`: build static-lib or shared-lib (add `-dynamic` flag);
+- `build-obj`: build object file, like `clang/gcc -c`.
+- `build-exe`: build executable
+- `build`: build-system mode, need `build.zig`.
+
+**Clang command line interface:**
+
+- `zig cc`: clang CLI
+- `zig c++`: clang++ CLI (uses `llvm-libc++` + `llvm-libunwind` by default)
+
+**Note:** Zig toolchain does not change `libclang` codegen. However, the default config uses `-fsanitize=undefined`.
+
+### Targets available
+
+```bash
+$> zig build-lib --show-builtin -target xtensa-freestanding-none -mcpu=(empty or any text)
+
+info: available CPUs for architecture 'xtensa':
+ cnl
+ esp32
+ esp32s2
+ esp32s3
+ esp8266
+ generic
+
+error: unknown CPU: ''
+```
+
+**Note:** Freestanding targets are not listed on `zig targets | jq .libc` (triple-targets)
+
+#### CPU Features
+
+Similar to [Targets available](#targets-available) command, add `-mcpu` or `-Dcpu=` (need `build.zig`).
+
+- `+` add feature
+- `-` remove feature
+
+**Note:** For show feature list add `+`/`-` without feature name
+
+**e.g.:**
+```bash
+$> zig build-lib --show-builtin -target xtensa-freestanding-none -mcpu=esp32+
+
+info: available CPU features for architecture 'xtensa':
+ bool: Enable Xtensa Boolean extension
+ clamps: Enable Xtensa CLAMPS option
+ coprocessor: Enable Xtensa Coprocessor option
+ dcache: Enable Xtensa Data Cache option
+ debug: Enable Xtensa Debug option
+ density: Enable Density instructions
+ dfpaccel: Enable Xtensa Double Precision FP acceleration
+ div32: Enable Xtensa Div32 option
+ esp32s2ops: Support Xtensa esp32-s2 ISA extension
+ esp32s3ops: Support Xtensa esp32-s3 ISA extension
+ exception: Enable Xtensa Exception option
+ extendedl32r: Enable Xtensa Extended L32R option
+ forced_atomics: Assume that lock-free native-width atomics are available
+ fp: Enable Xtensa Single FP instructions
+ hifi3: Enable Xtensa HIFI3 instructions
+ highpriinterrupts: Enable Xtensa HighPriInterrupts option
+ highpriinterrupts_level3: Enable Xtensa HighPriInterrupts Level3
+ highpriinterrupts_level4: Enable Xtensa HighPriInterrupts Level4
+ highpriinterrupts_level5: Enable Xtensa HighPriInterrupts Level5
+ highpriinterrupts_level6: Enable Xtensa HighPriInterrupts Level6
+ highpriinterrupts_level7: Enable Xtensa HighPriInterrupts Level7
+ interrupt: Enable Xtensa Interrupt option
+ loop: Enable Xtensa Loop extension
+ mac16: Enable Xtensa MAC16 instructions
+ minmax: Enable Xtensa MINMAX option
+ miscsr: Enable Xtensa Miscellaneous SR option
+ mul16: Enable Xtensa Mul16 option
+ mul32: Enable Xtensa Mul32 option
+ mul32high: Enable Xtensa Mul32High option
+ nsa: Enable Xtensa NSA option
+ prid: Enable Xtensa Processor ID option
+ regprotect: Enable Xtensa Region Protection option
+ rvector: Enable Xtensa Relocatable Vector option
+ s32c1i: Enable Xtensa S32C1I option
+ sext: Enable Xtensa Sign Extend option
+ threadptr: Enable Xtensa THREADPTR option
+ timers1: Enable Xtensa Timers 1
+ timers2: Enable Xtensa Timers 2
+ timers3: Enable Xtensa Timers 3
+ windowed: Enable Xtensa Windowed Register option
+
+error: unknown CPU feature: ''
+```
+
+
+#### Target info (builtin)
+
+**Note:** If like syntax-highlighting use `| bat -p -l zig` pipeline command or save this output as `builtin.zig` and open on your code editor.
+
+```bash
+$> zig build-lib --show-builtin -target xtensa-freestanding-none -mcpu=esp32s3
+```
+```zig
+const std = @import("std");
+/// Zig version. When writing code that supports multiple versions of Zig, prefer
+/// feature detection (i.e. with `@hasDecl` or `@hasField`) over version checks.
+pub const zig_version = std.SemanticVersion.parse(zig_version_string) catch unreachable;
+pub const zig_version_string = "0.16.0-xtensa-dev.2287+eb3f16db5";
+pub const zig_backend = std.builtin.CompilerBackend.stage2_llvm;
+
+pub const output_mode: std.builtin.OutputMode = .Lib;
+pub const link_mode: std.builtin.LinkMode = .static;
+pub const unwind_tables: std.builtin.UnwindTables = .async;
+pub const is_test = false;
+pub const single_threaded = false;
+pub const abi: std.Target.Abi = .none;
+pub const cpu: std.Target.Cpu = .{
+ .arch = .xtensa,
+ .model = &std.Target.xtensa.cpu.esp32s3,
+ .features = std.Target.xtensa.featureSet(&.{
+ .bool,
+ .clamps,
+ .coprocessor,
+ .dcache,
+ .debug,
+ .density,
+ .div32,
+ .esp32s3ops,
+ .exception,
+ .fp,
+ .highpriinterrupts,
+ .highpriinterrupts_level7,
+ .interrupt,
+ .loop,
+ .mac16,
+ .minmax,
+ .miscsr,
+ .mul16,
+ .mul32,
+ .mul32high,
+ .nsa,
+ .prid,
+ .regprotect,
+ .rvector,
+ .s32c1i,
+ .sext,
+ .threadptr,
+ .timers3,
+ .windowed,
+ }),
+};
+pub const os: std.Target.Os = .{
+ .tag = .freestanding,
+ .version_range = .{ .none = {} },
+};
+pub const target: std.Target = .{
+ .cpu = cpu,
+ .os = os,
+ .abi = abi,
+ .ofmt = object_format,
+ .dynamic_linker = .none,
+};
+pub const object_format: std.Target.ObjectFormat = .elf;
+pub const mode: std.builtin.OptimizeMode = .Debug;
+pub const link_libc = false;
+pub const link_libcpp = false;
+pub const have_error_return_tracing = true;
+pub const valgrind_support = false;
+pub const sanitize_thread = false;
+pub const fuzz = false;
+pub const position_independent_code = false;
+pub const position_independent_executable = false;
+pub const strip_debug_info = false;
+pub const code_model: std.builtin.CodeModel = .default;
+pub const omit_frame_pointer = false;
+```
diff --git a/software/zig_main/imports/bluetooth.zig b/software/zig_main/imports/bluetooth.zig
new file mode 100644
index 0000000..b891b93
--- /dev/null
+++ b/software/zig_main/imports/bluetooth.zig
@@ -0,0 +1,520 @@
+const sys = @import("sys");
+const errors = @import("error");
+
+// ---------------------------------------------------------------------------
+// Shim — BT_CONTROLLER_INIT_CONFIG_DEFAULT() macro cannot be translated by
+// zig translate-c; placeholder.c exposes it as a regular C function.
+// ---------------------------------------------------------------------------
+extern fn zig_bt_controller_default_cfg() sys.esp_bt_controller_config_t;
+
+// ---------------------------------------------------------------------------
+// BT mode
+// ---------------------------------------------------------------------------
+
+pub const Mode = enum(sys.esp_bt_mode_t) {
+ idle = sys.ESP_BT_MODE_IDLE,
+ ble = sys.ESP_BT_MODE_BLE,
+ classic = sys.ESP_BT_MODE_CLASSIC_BT,
+ dual = sys.ESP_BT_MODE_BTDM,
+};
+
+// ---------------------------------------------------------------------------
+// BLE power
+// ---------------------------------------------------------------------------
+
+pub const PowerType = sys.esp_ble_power_type_t;
+pub const PowerLevel = sys.esp_power_level_t;
+
+pub fn txPowerSet(power_type: PowerType, level: PowerLevel) !void {
+ try errors.espCheckError(sys.esp_ble_tx_power_set(power_type, level));
+}
+
+pub fn txPowerGet(power_type: PowerType) PowerLevel {
+ return sys.esp_ble_tx_power_get(power_type);
+}
+
+// ---------------------------------------------------------------------------
+// Controller lifecycle
+// ---------------------------------------------------------------------------
+
+pub const Controller = struct {
+ /// Return the default controller config.
+ /// Wraps the BT_CONTROLLER_INIT_CONFIG_DEFAULT() macro via a static
+ /// inline C function in include/bt_stubs.h (translated by translate-c).
+ pub fn defaultConfig() sys.esp_bt_controller_config_t {
+ return sys.zig_bt_controller_default_cfg();
+ }
+
+ pub fn init(cfg: *sys.esp_bt_controller_config_t) !void {
+ try errors.espCheckError(sys.esp_bt_controller_init(cfg));
+ }
+
+ pub fn deinit() !void {
+ try errors.espCheckError(sys.esp_bt_controller_deinit());
+ }
+
+ pub fn enable(mode: Mode) !void {
+ try errors.espCheckError(sys.esp_bt_controller_enable(@intFromEnum(mode)));
+ }
+
+ pub fn disable() !void {
+ try errors.espCheckError(sys.esp_bt_controller_disable());
+ }
+
+ pub fn getStatus() sys.esp_bt_controller_status_t {
+ return sys.esp_bt_controller_get_status();
+ }
+
+ /// Release memory for a BT mode that won't be used (saves heap).
+ /// Call before `init` when only BLE or only Classic BT is needed.
+ pub fn memRelease(mode: Mode) !void {
+ try errors.espCheckError(sys.esp_bt_controller_mem_release(@intFromEnum(mode)));
+ }
+
+ /// Release both controller and host memory for unused BT mode.
+ pub fn memReleaseAll(mode: Mode) !void {
+ try errors.espCheckError(sys.esp_bt_mem_release(@intFromEnum(mode)));
+ }
+
+ pub fn sleepEnable() !void {
+ try errors.espCheckError(sys.esp_bt_sleep_enable());
+ }
+
+ pub fn sleepDisable() !void {
+ try errors.espCheckError(sys.esp_bt_sleep_disable());
+ }
+};
+
+// ---------------------------------------------------------------------------
+// Bluedroid host stack lifecycle
+// ---------------------------------------------------------------------------
+
+pub const Bluedroid = struct {
+ pub fn init() !void {
+ try errors.espCheckError(sys.esp_bluedroid_init());
+ }
+
+ pub fn initWithCfg(cfg: *sys.esp_bluedroid_config_t) !void {
+ try errors.espCheckError(sys.esp_bluedroid_init_with_cfg(cfg));
+ }
+
+ pub fn deinit() !void {
+ try errors.espCheckError(sys.esp_bluedroid_deinit());
+ }
+
+ pub fn enable() !void {
+ try errors.espCheckError(sys.esp_bluedroid_enable());
+ }
+
+ pub fn disable() !void {
+ try errors.espCheckError(sys.esp_bluedroid_disable());
+ }
+
+ pub fn getStatus() sys.esp_bluedroid_status_t {
+ return sys.esp_bluedroid_get_status();
+ }
+};
+
+// ---------------------------------------------------------------------------
+// Convenience: full BLE init / deinit sequence
+// ---------------------------------------------------------------------------
+
+/// Initialise BT controller (BLE-only mode) + Bluedroid stack.
+/// Typical call sequence for BLE applications:
+/// 1. `idf.nvs.flashInitOrErase()`
+/// 2. `bluetooth.bleInit()`
+/// 3. Register GAP + GATT callbacks
+/// 4. `bluetooth.Gap.startAdvertising(¶ms)`
+pub fn bleInit() !void {
+ var cfg = Controller.defaultConfig();
+ // Release Classic BT memory — we only need BLE.
+ try Controller.memRelease(.classic);
+ try Controller.init(&cfg);
+ try Controller.enable(.ble);
+ try Bluedroid.init();
+ try Bluedroid.enable();
+}
+
+/// Graceful BLE teardown (reverse of `bleInit`).
+pub fn bleDeinit() !void {
+ try Bluedroid.disable();
+ try Bluedroid.deinit();
+ try Controller.disable();
+ try Controller.deinit();
+}
+
+// ---------------------------------------------------------------------------
+// Device
+// ---------------------------------------------------------------------------
+
+pub const Device = struct {
+ /// Register a callback for generic BT device events.
+ pub fn registerCallback(cb: sys.esp_bt_dev_cb_t) !void {
+ try errors.espCheckError(sys.esp_bt_dev_register_callback(cb));
+ }
+
+ /// Get the local BT/BLE MAC address (6 bytes, big-endian).
+ pub fn getAddress() [*]const u8 {
+ return sys.esp_bt_dev_get_address();
+ }
+};
+
+// ---------------------------------------------------------------------------
+// GAP BLE
+// ---------------------------------------------------------------------------
+
+pub const Gap = struct {
+ pub const CbT = sys.esp_gap_ble_cb_t;
+ pub const AdvData = sys.esp_ble_adv_data_t;
+ pub const AdvParams = sys.esp_ble_adv_params_t;
+ pub const ScanParams = sys.esp_ble_scan_params_t;
+ pub const ConnUpdateParams = sys.esp_ble_conn_update_params_t;
+ pub const AddrType = sys.esp_ble_addr_type_t;
+ pub const SmParam = sys.esp_ble_sm_param_t;
+ pub const BondDev = sys.esp_ble_bond_dev_t;
+
+ pub fn registerCallback(cb: CbT) !void {
+ try errors.espCheckError(sys.esp_ble_gap_register_callback(cb));
+ }
+
+ // -- Advertising ---------------------------------------------------------
+
+ pub fn configAdvData(adv_data: *AdvData) !void {
+ try errors.espCheckError(sys.esp_ble_gap_config_adv_data(adv_data));
+ }
+
+ pub fn configAdvDataRaw(raw_data: []u8) !void {
+ try errors.espCheckError(sys.esp_ble_gap_config_adv_data_raw(raw_data.ptr, @intCast(raw_data.len)));
+ }
+
+ pub fn configScanRspDataRaw(raw_data: []u8) !void {
+ try errors.espCheckError(sys.esp_ble_gap_config_scan_rsp_data_raw(raw_data.ptr, @intCast(raw_data.len)));
+ }
+
+ pub fn startAdvertising(params: *AdvParams) !void {
+ try errors.espCheckError(sys.esp_ble_gap_start_advertising(params));
+ }
+
+ pub fn stopAdvertising() !void {
+ try errors.espCheckError(sys.esp_ble_gap_stop_advertising());
+ }
+
+ // -- Scanning ------------------------------------------------------------
+
+ pub fn setScanParams(scan_params: *ScanParams) !void {
+ try errors.espCheckError(sys.esp_ble_gap_set_scan_params(scan_params));
+ }
+
+ /// Start scanning for `duration` seconds (0 = indefinite).
+ pub fn startScanning(duration: u32) !void {
+ try errors.espCheckError(sys.esp_ble_gap_start_scanning(duration));
+ }
+
+ pub fn stopScanning() !void {
+ try errors.espCheckError(sys.esp_ble_gap_stop_scanning());
+ }
+
+ // -- Connection ----------------------------------------------------------
+
+ pub fn updateConnParams(params: *ConnUpdateParams) !void {
+ try errors.espCheckError(sys.esp_ble_gap_update_conn_params(params));
+ }
+
+ pub fn disconnect(remote_bda: []u8) !void {
+ try errors.espCheckError(sys.esp_ble_gap_disconnect(remote_bda.ptr));
+ }
+
+ pub fn readRssi(remote_addr: []u8) !void {
+ try errors.espCheckError(sys.esp_ble_gap_read_rssi(remote_addr.ptr));
+ }
+
+ // -- Local identity ------------------------------------------------------
+
+ pub fn setDeviceName(name: [*:0]const u8) !void {
+ try errors.espCheckError(sys.esp_ble_gap_set_device_name(name));
+ }
+
+ pub fn configLocalPrivacy(enable: bool) !void {
+ try errors.espCheckError(sys.esp_ble_gap_config_local_privacy(enable));
+ }
+
+ // -- Security ------------------------------------------------------------
+
+ pub fn setSecurityParam(param: SmParam, value: *anyopaque, len: u8) !void {
+ try errors.espCheckError(sys.esp_ble_gap_set_security_param(param, value, len));
+ }
+
+ pub fn securityReply(bd_addr: []u8, accept: bool) !void {
+ try errors.espCheckError(sys.esp_ble_gap_security_rsp(bd_addr.ptr, accept));
+ }
+
+ pub fn passkeyReply(bd_addr: []u8, accept: bool, passkey: u32) !void {
+ try errors.espCheckError(sys.esp_ble_passkey_reply(bd_addr.ptr, accept, passkey));
+ }
+
+ pub fn confirmReply(bd_addr: []u8, accept: bool) !void {
+ try errors.espCheckError(sys.esp_ble_confirm_reply(bd_addr.ptr, accept));
+ }
+
+ // -- Bond management -----------------------------------------------------
+
+ pub fn removeBondDevice(bd_addr: []u8) !void {
+ try errors.espCheckError(sys.esp_ble_remove_bond_device(bd_addr.ptr));
+ }
+
+ pub fn getBondDeviceNum() c_int {
+ return sys.esp_ble_get_bond_device_num();
+ }
+
+ pub fn getBondDeviceList(dev_num: *c_int, dev_list: [*]BondDev) !void {
+ try errors.espCheckError(sys.esp_ble_get_bond_device_list(dev_num, dev_list));
+ }
+
+ // -- Whitelist -----------------------------------------------------------
+
+ pub fn updateWhitelist(add: bool, remote_bda: []u8, addr_type: sys.esp_ble_wl_addr_type_t) !void {
+ try errors.espCheckError(sys.esp_ble_gap_update_whitelist(add, remote_bda.ptr, addr_type));
+ }
+
+ pub fn clearWhitelist() !void {
+ try errors.espCheckError(sys.esp_ble_gap_clear_whitelist());
+ }
+};
+
+// ---------------------------------------------------------------------------
+// GATT common
+// ---------------------------------------------------------------------------
+
+pub const GattIf = sys.esp_gatt_if_t;
+pub const Uuid = sys.esp_bt_uuid_t;
+pub const GattStatus = sys.esp_gatt_status_t;
+pub const GattPerm = sys.esp_gatt_perm_t;
+pub const GattProp = sys.esp_gatt_char_prop_t;
+pub const AttrValue = sys.esp_attr_value_t;
+pub const AttrControl = sys.esp_attr_control_t;
+
+// ---------------------------------------------------------------------------
+// GATT Server (GATTS)
+// ---------------------------------------------------------------------------
+
+pub const GattServer = struct {
+ pub const CbT = sys.esp_gatts_cb_t;
+ pub const SrvcId = sys.esp_gatt_srvc_id_t;
+ pub const AttrDb = sys.esp_gatts_attr_db_t;
+ pub const Rsp = sys.esp_gatt_rsp_t;
+
+ pub fn registerCallback(cb: CbT) !void {
+ try errors.espCheckError(sys.esp_ble_gatts_register_callback(cb));
+ }
+
+ pub fn appRegister(app_id: u16) !void {
+ try errors.espCheckError(sys.esp_ble_gatts_app_register(app_id));
+ }
+
+ pub fn appUnregister(gatts_if: GattIf) !void {
+ try errors.espCheckError(sys.esp_ble_gatts_app_unregister(gatts_if));
+ }
+
+ pub fn createService(gatts_if: GattIf, service_id: *SrvcId, num_handle: u16) !void {
+ try errors.espCheckError(sys.esp_ble_gatts_create_service(gatts_if, service_id, num_handle));
+ }
+
+ /// Create all attributes in one call using an attribute table.
+ pub fn createAttrTab(attr_db: []const AttrDb, gatts_if: GattIf, srvc_inst_id: u8) !void {
+ try errors.espCheckError(sys.esp_ble_gatts_create_attr_tab(attr_db.ptr, gatts_if, @intCast(attr_db.len), srvc_inst_id));
+ }
+
+ pub fn addChar(
+ service_handle: u16,
+ char_uuid: *Uuid,
+ perm: GattPerm,
+ property: GattProp,
+ char_val: ?*AttrValue,
+ control: ?*AttrControl,
+ ) !void {
+ try errors.espCheckError(sys.esp_ble_gatts_add_char(service_handle, char_uuid, perm, property, char_val, control));
+ }
+
+ pub fn addCharDescr(
+ service_handle: u16,
+ descr_uuid: *Uuid,
+ perm: GattPerm,
+ val: ?*AttrValue,
+ control: ?*AttrControl,
+ ) !void {
+ try errors.espCheckError(sys.esp_ble_gatts_add_char_descr(service_handle, descr_uuid, perm, val, control));
+ }
+
+ pub fn deleteService(service_handle: u16) !void {
+ try errors.espCheckError(sys.esp_ble_gatts_delete_service(service_handle));
+ }
+
+ pub fn startService(service_handle: u16) !void {
+ try errors.espCheckError(sys.esp_ble_gatts_start_service(service_handle));
+ }
+
+ pub fn stopService(service_handle: u16) !void {
+ try errors.espCheckError(sys.esp_ble_gatts_stop_service(service_handle));
+ }
+
+ /// Send a notification or indication to a connected client.
+ pub fn sendIndicate(
+ gatts_if: GattIf,
+ conn_id: u16,
+ attr_handle: u16,
+ value: []u8,
+ need_confirm: bool,
+ ) !void {
+ try errors.espCheckError(sys.esp_ble_gatts_send_indicate(
+ gatts_if,
+ conn_id,
+ attr_handle,
+ @intCast(value.len),
+ value.ptr,
+ need_confirm,
+ ));
+ }
+
+ pub fn sendResponse(
+ gatts_if: GattIf,
+ conn_id: u16,
+ trans_id: u32,
+ status: GattStatus,
+ rsp: *Rsp,
+ ) !void {
+ try errors.espCheckError(sys.esp_ble_gatts_send_response(gatts_if, conn_id, trans_id, status, rsp));
+ }
+
+ pub fn setAttrValue(attr_handle: u16, value: []const u8) !void {
+ try errors.espCheckError(sys.esp_ble_gatts_set_attr_value(attr_handle, @intCast(value.len), value.ptr));
+ }
+
+ pub fn getAttrValue(attr_handle: u16, length: *u16, value: *[*]const u8) GattStatus {
+ return sys.esp_ble_gatts_get_attr_value(attr_handle, length, value);
+ }
+
+ pub fn open(gatts_if: GattIf, remote_bda: []u8, is_direct: bool) !void {
+ try errors.espCheckError(sys.esp_ble_gatts_open(gatts_if, remote_bda.ptr, is_direct));
+ }
+
+ pub fn close(gatts_if: GattIf, conn_id: u16) !void {
+ try errors.espCheckError(sys.esp_ble_gatts_close(gatts_if, conn_id));
+ }
+
+ pub fn showLocalDatabase() !void {
+ try errors.espCheckError(sys.esp_ble_gatts_show_local_database());
+ }
+};
+
+// ---------------------------------------------------------------------------
+// GATT Client (GATTC)
+// ---------------------------------------------------------------------------
+
+pub const GattClient = struct {
+ pub const CbT = sys.esp_gattc_cb_t;
+ pub const SvcElem = sys.esp_gattc_service_elem_t;
+ pub const CharElem = sys.esp_gattc_char_elem_t;
+ pub const DescrElem = sys.esp_gattc_descr_elem_t;
+ pub const WriteType = sys.esp_gatt_write_type_t;
+ pub const AuthReq = sys.esp_gatt_auth_req_t;
+
+ pub fn registerCallback(cb: CbT) !void {
+ try errors.espCheckError(sys.esp_ble_gattc_register_callback(cb));
+ }
+
+ pub fn appRegister(app_id: u16) !void {
+ try errors.espCheckError(sys.esp_ble_gattc_app_register(app_id));
+ }
+
+ pub fn appUnregister(gattc_if: GattIf) !void {
+ try errors.espCheckError(sys.esp_ble_gattc_app_unregister(gattc_if));
+ }
+
+ pub fn open(
+ gattc_if: GattIf,
+ remote_bda: []u8,
+ addr_type: sys.esp_ble_addr_type_t,
+ is_direct: bool,
+ ) !void {
+ try errors.espCheckError(sys.esp_ble_gattc_open(gattc_if, remote_bda.ptr, addr_type, is_direct));
+ }
+
+ pub fn close(gattc_if: GattIf, conn_id: u16) !void {
+ try errors.espCheckError(sys.esp_ble_gattc_close(gattc_if, conn_id));
+ }
+
+ pub fn sendMtuReq(gattc_if: GattIf, conn_id: u16) !void {
+ try errors.espCheckError(sys.esp_ble_gattc_send_mtu_req(gattc_if, conn_id));
+ }
+
+ pub fn searchService(gattc_if: GattIf, conn_id: u16, filter_uuid: ?*Uuid) !void {
+ try errors.espCheckError(sys.esp_ble_gattc_search_service(gattc_if, conn_id, filter_uuid));
+ }
+
+ pub fn readChar(gattc_if: GattIf, conn_id: u16, handle: u16, auth_req: AuthReq) !void {
+ try errors.espCheckError(sys.esp_ble_gattc_read_char(gattc_if, conn_id, handle, auth_req));
+ }
+
+ pub fn writeChar(
+ gattc_if: GattIf,
+ conn_id: u16,
+ handle: u16,
+ value: []u8,
+ write_type: WriteType,
+ auth_req: AuthReq,
+ ) !void {
+ try errors.espCheckError(sys.esp_ble_gattc_write_char(
+ gattc_if,
+ conn_id,
+ handle,
+ @intCast(value.len),
+ value.ptr,
+ write_type,
+ auth_req,
+ ));
+ }
+
+ pub fn writeCharDescr(
+ gattc_if: GattIf,
+ conn_id: u16,
+ handle: u16,
+ value: []u8,
+ write_type: WriteType,
+ auth_req: AuthReq,
+ ) !void {
+ try errors.espCheckError(sys.esp_ble_gattc_write_char_descr(
+ gattc_if,
+ conn_id,
+ handle,
+ @intCast(value.len),
+ value.ptr,
+ write_type,
+ auth_req,
+ ));
+ }
+
+ pub fn registerForNotify(gattc_if: GattIf, server_bda: []u8, handle: u16) !void {
+ try errors.espCheckError(sys.esp_ble_gattc_register_for_notify(gattc_if, server_bda.ptr, handle));
+ }
+
+ pub fn unregisterForNotify(gattc_if: GattIf, server_bda: []u8, handle: u16) !void {
+ try errors.espCheckError(sys.esp_ble_gattc_unregister_for_notify(gattc_if, server_bda.ptr, handle));
+ }
+
+ pub fn cacheRefresh(remote_bda: []u8) !void {
+ try errors.espCheckError(sys.esp_ble_gattc_cache_refresh(remote_bda.ptr));
+ }
+};
+
+// ---------------------------------------------------------------------------
+// WiFi/BT power domain (shared with wifi.zig)
+// ---------------------------------------------------------------------------
+
+pub const PowerDomain = struct {
+ pub fn on() void {
+ sys.esp_wifi_bt_power_domain_on();
+ }
+ pub fn off() void {
+ sys.esp_wifi_bt_power_domain_off();
+ }
+};
diff --git a/software/zig_main/imports/bootloader.zig b/software/zig_main/imports/bootloader.zig
new file mode 100644
index 0000000..5dc3035
--- /dev/null
+++ b/software/zig_main/imports/bootloader.zig
@@ -0,0 +1,631 @@
+const sys = @import("sys");
+const std = @import("std");
+const errors = @import("error");
+
+// ---------------------------------------------------------------------------
+// Re-export register/device types directly from sys — they are opaque bitfield
+// unions that zig translate-c demoted; there is no benefit in redeclaring them.
+// ---------------------------------------------------------------------------
+
+pub const EfuseDev = sys.efuse_dev_t;
+pub const EfuseBlock = sys.ets_efuse_block_t;
+pub const EfusePurpose = sys.ets_efuse_purpose_t;
+pub const EtsStatus = sys.ets_status_t;
+pub const SecureBootStatus = sys.ets_secure_boot_status_t;
+pub const SecureBootSigBlock = sys.ets_secure_boot_sig_block_t;
+pub const SecureBootSignature = sys.ets_secure_boot_signature_t;
+pub const SecureBootKeyDigests = sys.ets_secure_boot_key_digests_t;
+pub const SecureBootSigBlockV1 = sys.esp_secure_boot_sig_block_t;
+pub const SecureBootIvDigest = sys.esp_secure_boot_iv_digest_t;
+pub const ImageSigPubKeyDigests = sys.esp_image_sig_public_key_digests_t;
+pub const RsaPubkey = sys.ets_rsa_pubkey_t;
+
+pub const OtaSelectEntry = sys.esp_ota_select_entry_t;
+pub const PartitionPos = sys.esp_partition_pos_t;
+pub const PartitionInfo = sys.esp_partition_info_t;
+pub const BootloaderState = sys.bootloader_state_t;
+
+pub const ImageHeader = sys.esp_image_header_t; // opaque
+pub const ImageSegmentHeader = sys.esp_image_segment_header_t;
+pub const ImageMetadata = sys.esp_image_metadata_t;
+pub const ImageFlashMapping = sys.esp_image_flash_mapping_t;
+pub const RtcRetainMem = sys.rtc_retain_mem_t;
+
+pub const ChipId = sys.esp_chip_id_t;
+pub const SpiMode = sys.esp_image_spi_mode_t;
+pub const SpiFreq = sys.esp_image_spi_freq_t;
+pub const FlashSize = sys.esp_image_flash_size_t;
+pub const ImageLoadMode = sys.esp_image_load_mode_t;
+pub const ImageType = sys.esp_image_type;
+pub const GpioHold = sys.esp_comm_gpio_hold_t;
+
+pub const EtsEvent = sys.ETSEvent;
+pub const EtsTask = sys.ETSTask;
+pub const EtsTimer = sys.ETSTimer;
+pub const EtsTimerFunc = sys.ETSTimerFunc;
+pub const EtsIsrFn = sys.ets_isr_t;
+pub const EtsIdleCb = sys.ets_idle_cb_t;
+
+// The EFUSE peripheral register bank — memory-mapped, volatile.
+pub extern var EFUSE: EfuseDev;
+
+// ---------------------------------------------------------------------------
+// Efuse — high-level ESP-IDF API
+// ---------------------------------------------------------------------------
+
+pub const Efuse = struct {
+ pub fn setTiming(clock: u32) !void {
+ if (sys.ets_efuse_set_timing(clock) != 0) return error.EfuseTimingFailed;
+ }
+
+ pub fn read() !void {
+ if (sys.ets_efuse_read() != 0) return error.EfuseReadFailed;
+ }
+
+ pub fn program(block: EfuseBlock) !void {
+ if (sys.ets_efuse_program(block) != 0) return error.EfuseProgramFailed;
+ }
+
+ pub fn clearProgramRegisters() void {
+ sys.ets_efuse_clear_program_registers();
+ }
+
+ pub fn writeKey(
+ key_block: EfuseBlock,
+ purpose: EfusePurpose,
+ data: []const u8,
+ ) !void {
+ if (sys.ets_efuse_write_key(key_block, purpose, data.ptr, data.len) != 0)
+ return error.EfuseWriteKeyFailed;
+ }
+
+ pub fn getReadRegisterAddress(block: EfuseBlock) u32 {
+ return sys.ets_efuse_get_read_register_address(block);
+ }
+
+ pub fn getKeyPurpose(block: EfuseBlock) EfusePurpose {
+ return sys.ets_efuse_get_key_purpose(block);
+ }
+
+ pub fn findPurpose(purpose: EfusePurpose, out_block: *EfuseBlock) bool {
+ return sys.ets_efuse_find_purpose(purpose, out_block);
+ }
+
+ pub fn keyBlockUnused(block: EfuseBlock) bool {
+ return sys.ets_efuse_key_block_unused(block);
+ }
+
+ pub fn findUnusedKeyBlock() EfuseBlock {
+ return sys.ets_efuse_find_unused_key_block();
+ }
+
+ pub fn countUnusedKeyBlocks() c_uint {
+ return sys.ets_efuse_count_unused_key_blocks();
+ }
+
+ pub fn rsCalculate(data: []const u8, rs_values: []u8) void {
+ sys.ets_efuse_rs_calculate(data.ptr, rs_values.ptr);
+ }
+
+ pub fn getSpiConfig() u32 {
+ return sys.ets_efuse_get_spiconfig();
+ }
+ pub fn getWpPad() u32 {
+ return sys.ets_efuse_get_wp_pad();
+ }
+
+ pub fn downloadModesDisabled() bool {
+ return sys.ets_efuse_download_modes_disabled();
+ }
+ pub fn legacySpiBootModeDisabled() bool {
+ return sys.ets_efuse_legacy_spi_boot_mode_disabled();
+ }
+ pub fn getUartPrintControl() u32 {
+ return sys.ets_efuse_get_uart_print_control();
+ }
+ pub fn usbSerialJtagPrintDisabled() u32 {
+ return sys.ets_efuse_usb_serial_jtag_print_is_disabled();
+ }
+ pub fn usbDownloadModeDisabled() bool {
+ return sys.ets_efuse_usb_download_mode_disabled();
+ }
+ pub fn usbModuleDisabled() bool {
+ return sys.ets_efuse_usb_module_disabled();
+ }
+ pub fn securityDownloadModesEnabled() bool {
+ return sys.ets_efuse_security_download_modes_enabled();
+ }
+ pub fn secureBootEnabled() bool {
+ return sys.ets_efuse_secure_boot_enabled();
+ }
+ pub fn secureBootAggressiveRevokeEnabled() bool {
+ return sys.ets_efuse_secure_boot_aggressive_revoke_enabled();
+ }
+ pub fn cacheEncryptionEnabled() bool {
+ return sys.ets_efuse_cache_encryption_enabled();
+ }
+ pub fn flashOpi5padsPowerSelVddspi() bool {
+ return sys.ets_efuse_flash_opi_5pads_power_sel_vddspi();
+ }
+ pub fn forceSendResume() bool {
+ return sys.ets_efuse_force_send_resume();
+ }
+ pub fn getFlashDelayUs() u32 {
+ return sys.ets_efuse_get_flash_delay_us();
+ }
+
+ pub fn enableJtagTemporarily(hmac_key: []const u8, block: EfuseBlock) !void {
+ if (sys.ets_jtag_enable_temporarily(hmac_key.ptr, block) != 0)
+ return error.JtagEnableFailed;
+ }
+
+ pub fn romMacAddressCrc8(data: []const u8) u8 {
+ return sys.esp_rom_efuse_mac_address_crc8(data.ptr, @intCast(data.len));
+ }
+ pub fn romGetFlashGpioInfo() u32 {
+ return sys.esp_rom_efuse_get_flash_gpio_info();
+ }
+ pub fn romGetFlashWpGpio() u32 {
+ return sys.esp_rom_efuse_get_flash_wp_gpio();
+ }
+ pub fn romIsSecureBootEnabled() bool {
+ return sys.esp_rom_efuse_is_secure_boot_enabled();
+ }
+
+ /// CRC-8 over arbitrary data (ESP ROM implementation).
+ pub fn crc8(data: []const u8) u8 {
+ return sys.esp_crc8(data.ptr, @intCast(data.len));
+ }
+};
+
+// ---------------------------------------------------------------------------
+// EFUSE register-level inline accessors (mirror efuse_ll_* from esp-idf).
+// These read directly from the memory-mapped EFUSE peripheral.
+// ---------------------------------------------------------------------------
+
+pub const EfuseLl = struct {
+ pub fn getFlashCryptCnt() u32 {
+ return sys.efuse_ll_get_flash_crypt_cnt();
+ }
+ pub fn getWdtDelaySel() u32 {
+ return sys.efuse_ll_get_wdt_delay_sel();
+ }
+ pub fn getMac0() u32 {
+ return sys.efuse_ll_get_mac0();
+ }
+ pub fn getMac1() u32 {
+ return sys.efuse_ll_get_mac1();
+ }
+ pub fn getSecureBootV2En() bool {
+ return sys.efuse_ll_get_secure_boot_v2_en();
+ }
+ pub fn getErrRstEnable() bool {
+ return sys.efuse_ll_get_err_rst_enable();
+ }
+ pub fn getWaferVersionMajor() u32 {
+ return sys.efuse_ll_get_chip_wafer_version_major();
+ }
+ pub fn getWaferVersionMinor() u32 {
+ return sys.efuse_ll_get_chip_wafer_version_minor();
+ }
+ pub fn getDisableWaferVersionMajor() bool {
+ return sys.efuse_ll_get_disable_wafer_version_major();
+ }
+ pub fn getBlkVersionMajor() u32 {
+ return sys.efuse_ll_get_blk_version_major();
+ }
+ pub fn getBlkVersionMinor() u32 {
+ return sys.efuse_ll_get_blk_version_minor();
+ }
+ pub fn getDisableBlkVersionMajor() bool {
+ return sys.efuse_ll_get_disable_blk_version_major();
+ }
+ pub fn getChipVerPkg() u32 {
+ return sys.efuse_ll_get_chip_ver_pkg();
+ }
+ pub fn getOcode() u32 {
+ return sys.efuse_ll_get_ocode();
+ }
+ pub fn getKRtcLdo() u32 {
+ return sys.efuse_ll_get_k_rtc_ldo();
+ }
+ pub fn getKDigLdo() u32 {
+ return sys.efuse_ll_get_k_dig_ldo();
+ }
+ pub fn getVRtcDbias20() u32 {
+ return sys.efuse_ll_get_v_rtc_dbias20();
+ }
+ pub fn getVDigDbias20() u32 {
+ return sys.efuse_ll_get_v_dig_dbias20();
+ }
+ pub fn getDigDbias_hvt() u32 {
+ return sys.efuse_ll_get_dig_dbias_hvt();
+ }
+
+ pub fn isReadCmdPending() bool {
+ return sys.efuse_ll_get_read_cmd();
+ }
+ pub fn isPgmCmdPending() bool {
+ return sys.efuse_ll_get_pgm_cmd();
+ }
+ pub fn triggerReadCmd() void {
+ sys.efuse_ll_set_read_cmd();
+ }
+ pub fn triggerPgmCmd(block: u32) void {
+ sys.efuse_ll_set_pgm_cmd(block);
+ }
+ pub fn setConfReadOpCode() void {
+ sys.efuse_ll_set_conf_read_op_code();
+ }
+ pub fn setConfWriteOpCode() void {
+ sys.efuse_ll_set_conf_write_op_code();
+ }
+ pub fn setDacNum(val: u8) void {
+ sys.efuse_ll_set_dac_num(val);
+ }
+ pub fn setDacClkDiv(val: u8) void {
+ sys.efuse_ll_set_dac_clk_div(val);
+ }
+ pub fn setPwrOnNum(val: u16) void {
+ sys.efuse_ll_set_pwr_on_num(val);
+ }
+ pub fn setPwrOffNum(val: u16) void {
+ sys.efuse_ll_set_pwr_off_num(val);
+ }
+};
+
+// ---------------------------------------------------------------------------
+// Partition table
+// ---------------------------------------------------------------------------
+
+pub const Partition = struct {
+ pub fn tableVerify(
+ table: [*]const PartitionInfo,
+ log_errors: bool,
+ num_partitions: *c_int,
+ ) !void {
+ try errors.espCheckError(sys.esp_partition_table_verify(table, log_errors, num_partitions));
+ }
+
+ pub fn isFlashRegionWritable(addr: usize, size: usize) bool {
+ return sys.esp_partition_is_flash_region_writable(addr, size);
+ }
+
+ pub fn mainFlashRegionSafe(addr: usize, size: usize) bool {
+ return sys.esp_partition_main_flash_region_safe(addr, size);
+ }
+};
+
+// ---------------------------------------------------------------------------
+// Image verification / loading
+// ---------------------------------------------------------------------------
+
+pub const Image = struct {
+ pub fn verify(
+ mode: ImageLoadMode,
+ part: *const PartitionPos,
+ data: ?*ImageMetadata,
+ ) !void {
+ try errors.espCheckError(sys.esp_image_verify(mode, part, data));
+ }
+
+ pub fn getMetadata(part: *const PartitionPos, metadata: *ImageMetadata) !void {
+ try errors.espCheckError(sys.esp_image_get_metadata(part, metadata));
+ }
+
+ pub fn getFlashSize(app_flash_size: FlashSize) c_int {
+ return sys.esp_image_get_flash_size(app_flash_size);
+ }
+
+ pub fn verifyBootloader(out_length: *u32) !void {
+ try errors.espCheckError(sys.esp_image_verify_bootloader(out_length));
+ }
+
+ pub fn verifyBootloaderData(data: *ImageMetadata) !void {
+ try errors.espCheckError(sys.esp_image_verify_bootloader_data(data));
+ }
+};
+
+// ---------------------------------------------------------------------------
+// Bootloader
+// ---------------------------------------------------------------------------
+
+pub const Bootloader = struct {
+ pub fn loadImage(part: *const PartitionPos, data: *ImageMetadata) !void {
+ try errors.espCheckError(sys.bootloader_load_image(part, data));
+ }
+
+ pub fn loadImageNoVerify(part: *const PartitionPos, data: *ImageMetadata) !void {
+ try errors.espCheckError(sys.bootloader_load_image_no_verify(part, data));
+ }
+
+ pub fn readOtadata(
+ ota_info: *const PartitionPos,
+ two_otadata: *[2]OtaSelectEntry,
+ ) !void {
+ try errors.espCheckError(sys.bootloader_common_read_otadata(ota_info, two_otadata));
+ }
+
+ pub fn otaSelectCrc(s: *const OtaSelectEntry) u32 {
+ return sys.bootloader_common_ota_select_crc(s);
+ }
+
+ pub fn otaSelectValid(s: *const OtaSelectEntry) bool {
+ return sys.bootloader_common_ota_select_valid(s);
+ }
+
+ pub fn otaSelectInvalid(s: *const OtaSelectEntry) bool {
+ return sys.bootloader_common_ota_select_invalid(s);
+ }
+
+ pub fn checkLongHoldGpio(pin: u32, delay_sec: u32) GpioHold {
+ return sys.bootloader_common_check_long_hold_gpio(pin, delay_sec);
+ }
+
+ pub fn checkLongHoldGpioLevel(pin: u32, delay_sec: u32, level: bool) GpioHold {
+ return sys.bootloader_common_check_long_hold_gpio_level(pin, delay_sec, level);
+ }
+
+ pub fn erasePartTypeData(list_erase: [*:0]const u8, ota_data_erase: bool) bool {
+ return sys.bootloader_common_erase_part_type_data(list_erase, ota_data_erase);
+ }
+
+ pub fn labelSearch(list: [*:0]const u8, label: [*:0]u8) bool {
+ return sys.bootloader_common_label_search(list, label);
+ }
+
+ pub fn configureSpiPins(drv: c_int) void {
+ sys.bootloader_configure_spi_pins(drv);
+ }
+
+ pub fn getSha256OfPartition(
+ address: u32,
+ size: u32,
+ part_type: c_int,
+ out_sha_256: *[32]u8,
+ ) !void {
+ try errors.espCheckError(sys.bootloader_common_get_sha256_of_partition(address, size, part_type, out_sha_256));
+ }
+
+ pub fn getActiveOtadata(two_otadata: *[2]OtaSelectEntry) c_int {
+ return sys.bootloader_common_get_active_otadata(two_otadata);
+ }
+
+ pub fn selectOtadata(
+ two_otadata: *const [2]OtaSelectEntry,
+ valid_two_otadata: *[2]bool,
+ max: bool,
+ ) c_int {
+ return sys.bootloader_common_select_otadata(two_otadata, valid_two_otadata, max);
+ }
+
+ pub fn getChipVerPkg() u32 {
+ return sys.bootloader_common_get_chip_ver_pkg();
+ }
+
+ pub fn checkChipValidity(img_hdr: *const ImageHeader, kind: ImageType) !void {
+ try errors.espCheckError(sys.bootloader_common_check_chip_validity(img_hdr, kind));
+ }
+
+ pub fn vddsdioConf() void {
+ sys.bootloader_common_vddsdio_configure();
+ }
+
+ pub fn randomEnable() void {
+ sys.bootloader_random_enable();
+ }
+ pub fn randomDisable() void {
+ sys.bootloader_random_disable();
+ }
+ pub fn fillRandom(buf: []u8) void {
+ sys.bootloader_fill_random(buf.ptr, buf.len);
+ }
+
+ pub fn readBootloaderHeader() !void {
+ try errors.espCheckError(sys.bootloader_read_bootloader_header());
+ }
+
+ pub fn checkBootloaderValidity() !void {
+ try errors.espCheckError(sys.bootloader_check_bootloader_validity());
+ }
+
+ pub fn clearBssSection() void {
+ sys.bootloader_clear_bss_section();
+ }
+ pub fn configWdt() void {
+ sys.bootloader_config_wdt();
+ }
+ pub fn enableRandom() void {
+ sys.bootloader_enable_random();
+ }
+ pub fn printBanner() void {
+ sys.bootloader_print_banner();
+ }
+
+ pub fn init() !void {
+ try errors.espCheckError(sys.bootloader_init());
+ }
+
+ pub fn flashEncrypt(bs: *BootloaderState) bool {
+ return sys.flash_encrypt(bs);
+ }
+
+ /// True if two flash regions [start1,end1) and [start2,end2) overlap.
+ pub fn regionsOverlap(start1: isize, end1: isize, start2: isize, end2: isize) bool {
+ return (end1 > start2) and (end2 > start1);
+ }
+};
+
+// ---------------------------------------------------------------------------
+// Secure boot
+// ---------------------------------------------------------------------------
+
+pub const SecureBoot = struct {
+ pub fn enabled() bool {
+ // Reads directly from the efuse secure_boot_v2 bit — no ESP-IDF call needed.
+ return EfuseLl.getSecureBootV2En();
+ }
+
+ pub fn generateDigest() !void {
+ try errors.espCheckError(sys.esp_secure_boot_generate_digest());
+ }
+
+ pub fn permanentlyEnable() !void {
+ try errors.espCheckError(sys.esp_secure_boot_permanently_enable());
+ }
+
+ pub fn v2PermanentlyEnable(image_data: *const ImageMetadata) !void {
+ try errors.espCheckError(sys.esp_secure_boot_v2_permanently_enable(image_data));
+ }
+
+ pub fn verifySignature(src_addr: u32, length: u32) !void {
+ try errors.espCheckError(sys.esp_secure_boot_verify_signature(src_addr, length));
+ }
+
+ pub fn verifyEcdsaSignatureBlock(
+ sig_block: *const SecureBootSigBlockV1,
+ image_digest: [*:0]const u8,
+ verified_digest: *u8,
+ ) !void {
+ try errors.espCheckError(sys.esp_secure_boot_verify_ecdsa_signature_block(sig_block, image_digest, verified_digest));
+ }
+
+ pub fn initChecks() void {
+ sys.esp_secure_boot_init_checks();
+ }
+
+ pub fn enableSecureFeatures() !void {
+ try errors.espCheckError(sys.esp_secure_boot_enable_secure_features());
+ }
+
+ pub fn cfgVerifyReleaseMode() bool {
+ return sys.esp_secure_boot_cfg_verify_release_mode();
+ }
+
+ // ROM-level secure boot ---------------------------------------------------
+
+ pub fn romVerifyBootloaderWithKeys(
+ verified_hash: *u8,
+ trusted_keys: *const SecureBootKeyDigests,
+ stage_load: bool,
+ ) SecureBootStatus {
+ return sys.ets_secure_boot_verify_bootloader_with_keys(verified_hash, trusted_keys, stage_load);
+ }
+
+ pub fn romReadKeyDigests(trusted_keys: *SecureBootKeyDigests) EtsStatus {
+ return sys.ets_secure_boot_read_key_digests(trusted_keys);
+ }
+
+ pub fn romVerifySignature(
+ sig: *const SecureBootSignature,
+ image_digest: [*:0]const u8,
+ trusted_keys: *const SecureBootKeyDigests,
+ verified_digest: *u8,
+ ) SecureBootStatus {
+ return sys.ets_secure_boot_verify_signature(sig, image_digest, trusted_keys, verified_digest);
+ }
+
+ pub fn romRevokePublicKeyDigest(index: c_int) void {
+ sys.ets_secure_boot_revoke_public_key_digest(index);
+ }
+
+ pub fn romRsaPssVerify(
+ key: *const RsaPubkey,
+ sig: [*:0]const u8,
+ digest: [*:0]const u8,
+ verified_digest: *u8,
+ ) bool {
+ return sys.ets_rsa_pss_verify(key, sig, digest, verified_digest);
+ }
+};
+
+// ---------------------------------------------------------------------------
+// ETS (ROM system services)
+// ---------------------------------------------------------------------------
+
+pub const Ets = struct {
+ pub fn printf(fmt: [*:0]const u8, args: anytype) c_int {
+ return @call(.auto, sys.ets_printf, .{fmt} ++ args);
+ }
+
+ pub fn delayUs(us: u32) void {
+ sys.ets_delay_us(us);
+ }
+ pub fn getCpuFrequency() u32 {
+ return sys.ets_get_cpu_frequency();
+ }
+ pub fn updateCpuFrequency(ticks_per_us: u32) void {
+ sys.ets_update_cpu_frequency(ticks_per_us);
+ }
+ pub fn getXtalFreq() u32 {
+ return sys.ets_get_xtal_freq();
+ }
+ pub fn getXtalDiv() u32 {
+ return sys.ets_get_xtal_div();
+ }
+ pub fn getApbFreq() u32 {
+ return sys.ets_get_apb_freq();
+ }
+
+ pub fn isrAttach(irq: c_int, func: EtsIsrFn, arg: ?*anyopaque) void {
+ sys.ets_isr_attach(irq, func, arg);
+ }
+ pub fn isrMask(mask: u32) void {
+ sys.ets_isr_mask(mask);
+ }
+ pub fn isrUnmask(mask: u32) void {
+ sys.ets_isr_unmask(mask);
+ }
+ pub fn intrLock() void {
+ sys.ets_intr_lock();
+ }
+ pub fn intrUnlock() void {
+ sys.ets_intr_unlock();
+ }
+
+ pub fn setUserStart(start: u32) void {
+ sys.ets_set_user_start(start);
+ }
+ pub fn installPutc1(p: ?*const fn (u8) callconv(.c) void) void {
+ sys.ets_install_putc1(p);
+ }
+ pub fn installPutc2(p: ?*const fn (u8) callconv(.c) void) void {
+ sys.ets_install_putc2(p);
+ }
+ pub fn installUartPrintf() void {
+ sys.ets_install_uart_printf();
+ }
+
+ pub fn intr_matrix_set(cpu_no: c_int, model_num: u32, intr_num: u32) void {
+ sys.intr_matrix_set(cpu_no, model_num, intr_num);
+ }
+};
+
+// ---------------------------------------------------------------------------
+// ETS Timer
+// ---------------------------------------------------------------------------
+
+pub const EtsTimerApi = struct {
+ pub fn init() void {
+ sys.ets_timer_init();
+ }
+ pub fn deinit() void {
+ sys.ets_timer_deinit();
+ }
+
+ pub fn arm(timer: *EtsTimer, timeout_ms: u32, repeat: bool) void {
+ sys.ets_timer_arm(timer, timeout_ms, repeat);
+ }
+ pub fn armUs(timer: *EtsTimer, us: u32, repeat: bool) void {
+ sys.ets_timer_arm_us(timer, us, repeat);
+ }
+ pub fn disarm(timer: *EtsTimer) void {
+ sys.ets_timer_disarm(timer);
+ }
+ pub fn setFn(timer: *EtsTimer, func: ?*const EtsTimerFunc, arg: ?*anyopaque) void {
+ sys.ets_timer_setfn(timer, func, arg);
+ }
+ pub fn done(timer: *EtsTimer) void {
+ sys.ets_timer_done(timer);
+ }
+};
diff --git a/software/zig_main/imports/crc.zig b/software/zig_main/imports/crc.zig
new file mode 100644
index 0000000..9f3ca6b
--- /dev/null
+++ b/software/zig_main/imports/crc.zig
@@ -0,0 +1,51 @@
+const sys = @import("sys");
+
+// ---------------------------------------------------------------------------
+// CRC helpers — thin wrappers around the ESP-ROM implementations.
+//
+// All functions take a running `crc` (pass 0 for the first block) and a
+// data slice, and return the updated CRC value.
+//
+// Le = reflected polynomial (little-endian / standard CRC convention).
+// Be = non-reflected polynomial (big-endian convention).
+// ---------------------------------------------------------------------------
+
+// ── CRC-8 ─────────────────────────────────────────────────────────────────
+
+/// CRC-8 with reflected (little-endian) polynomial.
+pub fn crc8Le(crc: u8, buf: []const u8) u8 {
+ return sys.esp_rom_crc8_le(crc, buf.ptr, buf.len);
+}
+
+/// CRC-8 with non-reflected (big-endian) polynomial.
+pub fn crc8Be(crc: u8, buf: []const u8) u8 {
+ return sys.esp_rom_crc8_be(crc, buf.ptr, buf.len);
+}
+
+// ── CRC-16 ────────────────────────────────────────────────────────────────
+
+/// CRC-16 with reflected (little-endian) polynomial.
+pub fn crc16Le(crc: u16, buf: []const u8) u16 {
+ return sys.esp_rom_crc16_le(crc, buf.ptr, buf.len);
+}
+
+/// CRC-16 with non-reflected (big-endian) polynomial.
+pub fn crc16Be(crc: u16, buf: []const u8) u16 {
+ return sys.esp_rom_crc16_be(crc, buf.ptr, buf.len);
+}
+
+// ── CRC-32 ────────────────────────────────────────────────────────────────
+
+/// CRC-32 with reflected (little-endian) polynomial — compatible with
+/// zlib / Ethernet / standard CRC-32.
+pub fn crc32Le(crc: u32, buf: []const u8) u32 {
+ return sys.esp_rom_crc32_le(crc, buf.ptr, buf.len);
+}
+
+/// CRC-32 with non-reflected (big-endian) polynomial.
+pub fn crc32Be(crc: u32, buf: []const u8) u32 {
+ return sys.esp_rom_crc32_be(crc, buf.ptr, buf.len);
+}
+
+// ── Zig stdlib CRC (kept for pure-Zig use without ROM calls) ──────────────
+pub const zigCRC32 = @import("std").hash.crc;
diff --git a/software/zig_main/imports/dsp.zig b/software/zig_main/imports/dsp.zig
new file mode 100644
index 0000000..d22f881
--- /dev/null
+++ b/software/zig_main/imports/dsp.zig
@@ -0,0 +1,440 @@
+// ESP-DSP (Digital Signal Processing) Library Wrapper
+// requires: idf.py add-dependency espressif/esp-dsp
+const sys = @import("sys");
+const errors = @import("error");
+const std = @import("std");
+
+// ═══════════════════════════════════════════════════════════════════════════
+// Complex number types
+// ═══════════════════════════════════════════════════════════════════════════
+
+/// Complex number with 16-bit integer components (short complex)
+pub const Complex16 = extern struct {
+ re: i16,
+ im: i16,
+
+ pub fn init(re: i16, im: i16) Complex16 {
+ return .{ .re = re, .im = im };
+ }
+
+ pub fn fromUnion(u: sys.sc16_t) Complex16 {
+ return .{ .re = u.unnamed_0.re, .im = u.unnamed_0.im };
+ }
+
+ pub fn toUnion(self: Complex16) sys.sc16_t {
+ return .{ .unnamed_0 = .{ .re = self.re, .im = self.im } };
+ }
+};
+
+/// Complex number with 32-bit float components (float complex)
+pub const Complex32 = extern struct {
+ re: f32,
+ im: f32,
+
+ pub fn init(re: f32, im: f32) Complex32 {
+ return .{ .re = re, .im = im };
+ }
+
+ pub fn fromUnion(u: sys.fc32_t) Complex32 {
+ return .{ .re = u.unnamed_0.re, .im = u.unnamed_0.im };
+ }
+
+ pub fn toUnion(self: Complex32) sys.fc32_t {
+ return .{ .unnamed_0 = .{ .re = self.re, .im = self.im } };
+ }
+
+ pub fn magnitude(self: Complex32) f32 {
+ return @sqrt(self.re * self.re + self.im * self.im);
+ }
+
+ pub fn phase(self: Complex32) f32 {
+ return std.math.atan2(self.im, self.re);
+ }
+};
+
+// ═══════════════════════════════════════════════════════════════════════════
+// Image/2D data structure
+// ═══════════════════════════════════════════════════════════════════════════
+
+pub const Image2D = extern struct {
+ data: ?*anyopaque = null,
+ step_x: c_int = 0,
+ step_y: c_int = 0,
+ stride_x: c_int = 0,
+ stride_y: c_int = 0,
+
+ pub fn init(data: *anyopaque, step_x: i32, step_y: i32, stride_x: i32, stride_y: i32) Image2D {
+ return .{ .data = data, .step_x = step_x, .step_y = step_y, .stride_x = stride_x, .stride_y = stride_y };
+ }
+};
+
+// ═══════════════════════════════════════════════════════════════════════════
+// Filter structures
+// ═══════════════════════════════════════════════════════════════════════════
+
+/// FIR filter with float32 coefficients
+pub const FirF32 = struct {
+ inner: sys.fir_f32_t,
+
+ pub fn init(coeffs: []f32, delay: []f32) !FirF32 {
+ if (coeffs.len != delay.len) return error.LengthMismatch;
+
+ var fir: sys.fir_f32_t = undefined;
+ try errors.espCheckError(sys.dsps_fir_init_f32(&fir, coeffs.ptr, delay.ptr, @intCast(coeffs.len)));
+
+ return .{ .inner = fir };
+ }
+
+ pub fn initDecimated(coeffs: []f32, delay: []f32, decimation: u32) !FirF32 {
+ if (coeffs.len != delay.len) return error.LengthMismatch;
+
+ var fir: sys.fir_f32_t = undefined;
+ try errors.espCheckError(sys.dsps_fird_init_f32(&fir, coeffs.ptr, delay.ptr, @intCast(coeffs.len), @intCast(decimation)));
+
+ return .{ .inner = fir };
+ }
+
+ pub fn process(self: *FirF32, input: []const f32, output: []f32) !void {
+ if (input.len != output.len) return error.LengthMismatch;
+ try errors.espCheckError(sys.dsps_fir_f32_ae32(&self.inner, input.ptr, output.ptr, @intCast(input.len)));
+ }
+
+ pub fn deinit(self: *FirF32) !void {
+ try errors.espCheckError(sys.dsps_fir_f32_free(&self.inner));
+ }
+};
+
+/// FIR filter with int16 coefficients
+pub const FirS16 = struct {
+ inner: sys.fir_s16_t,
+
+ pub fn initDecimated(coeffs: []i16, delay: []i16, decimation: u16, start_pos: u16, shift: u16) !FirS16 {
+ if (coeffs.len != delay.len) return error.LengthMismatch;
+
+ var fir: sys.fir_s16_t = undefined;
+ try errors.espCheckError(sys.dsps_fird_init_s16(&fir, coeffs.ptr, delay.ptr, @intCast(coeffs.len), @intCast(decimation), @intCast(start_pos), @intCast(shift)));
+
+ return .{ .inner = fir };
+ }
+
+ pub fn process(self: *FirS16, input: []const i16, output: []i16) !u32 {
+ if (input.len != output.len) return error.LengthMismatch;
+ const result = sys.dsps_fird_s16_aes3(&self.inner, input.ptr, output.ptr, @intCast(input.len));
+ return @intCast(result);
+ }
+
+ pub fn deinit(self: *FirS16) !void {
+ try errors.espCheckError(sys.dsps_fird_s16_aexx_free(&self.inner));
+ }
+};
+
+// ═══════════════════════════════════════════════════════════════════════════
+// FFT operations
+// ═══════════════════════════════════════════════════════════════════════════
+
+pub const FFT = struct {
+ /// Initialize FFT with radix-2 (power of 2 sizes)
+ pub fn initRadix2F32(table_buffer: []f32, table_size: u32) !void {
+ try errors.espCheckError(sys.dsps_fft2r_init_fc32(table_buffer.ptr, @intCast(table_size)));
+ }
+
+ pub fn deinitRadix2F32() void {
+ sys.dsps_fft2r_deinit_fc32();
+ }
+
+ /// Perform radix-2 FFT on complex data (in-place)
+ /// data must be [re0, im0, re1, im1, ...] format
+ pub fn fft2rF32(data: []f32, fft_size: u32, w_table: []f32) !void {
+ try errors.espCheckError(sys.dsps_fft2r_fc32_aes3_(data.ptr, @intCast(fft_size), w_table.ptr));
+ }
+
+ /// Bit-reverse reordering for FFT
+ pub fn bitReverseF32(data: []f32, fft_size: u32) !void {
+ try errors.espCheckError(sys.dsps_bit_rev_fc32_ansi(data.ptr, @intCast(fft_size)));
+ }
+
+ /// Initialize FFT with radix-4 (optimized for power of 4 sizes)
+ pub fn initRadix4F32(table_buffer: []f32, max_fft_size: u32) !void {
+ try errors.espCheckError(sys.dsps_fft4r_init_fc32(table_buffer.ptr, @intCast(max_fft_size)));
+ }
+
+ pub fn deinitRadix4F32() void {
+ sys.dsps_fft4r_deinit_fc32();
+ }
+
+ /// Perform radix-4 FFT
+ pub fn fft4rF32(data: []f32, fft_size: u32, w_table: []f32) !void {
+ try errors.espCheckError(sys.dsps_fft4r_fc32_ae32_(data.ptr, @intCast(fft_size), w_table.ptr, @intCast(w_table.len)));
+ }
+
+ /// Convert complex FFT output to real signal
+ pub fn complex2RealF32(data: []f32, fft_size: u32, w_table: []f32) !void {
+ try errors.espCheckError(sys.dsps_cplx2real_fc32_ae32_(data.ptr, @intCast(fft_size), w_table.ptr, @intCast(w_table.len)));
+ }
+};
+
+// ═══════════════════════════════════════════════════════════════════════════
+// Basic math operations
+// ═══════════════════════════════════════════════════════════════════════════
+
+pub const Math = struct {
+ /// Vector addition: output = input1 + input2
+ pub fn addF32(input1: []const f32, input2: []const f32, output: []f32) !void {
+ if (input1.len != input2.len or input1.len != output.len) {
+ return error.LengthMismatch;
+ }
+ try errors.espCheckError(sys.dsps_add_f32_ae32(input1.ptr, input2.ptr, output.ptr, @intCast(input1.len), 1, 1, 1));
+ }
+
+ /// Vector addition (16-bit): output = input1 + input2
+ pub fn addS16(input1: []const i16, input2: []const i16, output: []i16, shift: i32) !void {
+ if (input1.len != input2.len or input1.len != output.len) {
+ return error.LengthMismatch;
+ }
+ try errors.espCheckError(sys.dsps_add_s16_aes3(input1.ptr, input2.ptr, output.ptr, @intCast(input1.len), 1, 1, 1, shift));
+ }
+
+ /// Vector subtraction: output = input1 - input2
+ pub fn subF32(input1: []const f32, input2: []const f32, output: []f32) !void {
+ if (input1.len != input2.len or input1.len != output.len) {
+ return error.LengthMismatch;
+ }
+ try errors.espCheckError(sys.dsps_sub_f32_ae32(input1.ptr, input2.ptr, output.ptr, @intCast(input1.len), 1, 1, 1));
+ }
+
+ /// Vector multiplication: output = input1 * input2
+ pub fn mulF32(input1: []const f32, input2: []const f32, output: []f32) !void {
+ if (input1.len != input2.len or input1.len != output.len) {
+ return error.LengthMismatch;
+ }
+ try errors.espCheckError(sys.dsps_mul_f32_ae32(input1.ptr, input2.ptr, output.ptr, @intCast(input1.len), 1, 1, 1));
+ }
+
+ /// Scalar addition: output = input + C
+ pub fn addConstF32(input: []const f32, output: []f32, constant: f32) !void {
+ if (input.len != output.len) return error.LengthMismatch;
+ try errors.espCheckError(sys.dsps_addc_f32_ae32(input.ptr, output.ptr, @intCast(input.len), constant, 1, 1));
+ }
+
+ /// Scalar multiplication: output = input * C
+ pub fn mulConstF32(input: []const f32, output: []f32, constant: f32) !void {
+ if (input.len != output.len) return error.LengthMismatch;
+ try errors.espCheckError(sys.dsps_mulc_f32_ae32(input.ptr, output.ptr, @intCast(input.len), constant, 1, 1));
+ }
+
+ /// Element-wise square root
+ pub fn sqrtF32(input: []const f32, output: []f32) !void {
+ if (input.len != output.len) return error.LengthMismatch;
+ try errors.espCheckError(sys.dsps_sqrt_f32_ansi(input.ptr, output.ptr, @intCast(input.len)));
+ }
+
+ /// Dot product: result = sum(src1[i] * src2[i])
+ pub fn dotProductF32(src1: []const f32, src2: []const f32) !f32 {
+ if (src1.len != src2.len) return error.LengthMismatch;
+ var result: f32 = 0;
+ try errors.espCheckError(sys.dsps_dotprod_f32_aes3(src1.ptr, src2.ptr, &result, @intCast(src1.len)));
+ return result;
+ }
+
+ /// Dot product (16-bit)
+ pub fn dotProductS16(src1: []const i16, src2: []const i16, shift: i8) !i16 {
+ if (src1.len != src2.len) return error.LengthMismatch;
+ var result: i16 = 0;
+ try errors.espCheckError(sys.dsps_dotprod_s16_ae32(src1.ptr, src2.ptr, &result, @intCast(src1.len), shift));
+ return result;
+ }
+};
+
+// ═══════════════════════════════════════════════════════════════════════════
+// Matrix operations
+// ═══════════════════════════════════════════════════════════════════════════
+
+pub const Matrix = struct {
+ /// Matrix multiplication: C = A * B
+ /// A: m x k, B: k x n, C: m x n
+ pub fn multiplyF32(A: []const f32, B: []const f32, C: []f32, m: u32, n: u32, k: u32) !void {
+ try errors.espCheckError(sys.dspm_mult_f32_aes3(A.ptr, B.ptr, C.ptr, @intCast(m), @intCast(n), @intCast(k)));
+ }
+
+ /// Matrix addition: output = input1 + input2
+ pub fn addF32(input1: []const f32, input2: []const f32, output: []f32, rows: u32, cols: u32) !void {
+ try errors.espCheckError(sys.dspm_add_f32_ae32(input1.ptr, input2.ptr, output.ptr, @intCast(rows), @intCast(cols), 0, 0, 0, 1, 1, 1));
+ }
+
+ /// Matrix subtraction: output = input1 - input2
+ pub fn subF32(input1: []const f32, input2: []const f32, output: []f32, rows: u32, cols: u32) !void {
+ try errors.espCheckError(sys.dspm_sub_f32_ae32(input1.ptr, input2.ptr, output.ptr, @intCast(rows), @intCast(cols), 0, 0, 0, 1, 1, 1));
+ }
+
+ /// Scalar matrix multiplication: output = input * C
+ pub fn mulConstF32(input: []const f32, output: []f32, constant: f32, rows: u32, cols: u32) !void {
+ try errors.espCheckError(sys.dspm_mulc_f32_ae32(input.ptr, output.ptr, constant, @intCast(rows), @intCast(cols), 0, 0, 1, 1));
+ }
+
+ /// Optimized 3x3 matrix multiplication
+ pub fn multiply3x3F32(A: []const f32, B: []const f32, C: []f32) !void {
+ if (A.len < 9 or B.len < 9 or C.len < 9) return error.InvalidSize;
+ try errors.espCheckError(sys.dspm_mult_3x3x3_f32_ae32(A.ptr, B.ptr, C.ptr));
+ }
+
+ /// Optimized 4x4 matrix multiplication
+ pub fn multiply4x4F32(A: []const f32, B: []const f32, C: []f32) !void {
+ if (A.len < 16 or B.len < 16 or C.len < 16) return error.InvalidSize;
+ try errors.espCheckError(sys.dspm_mult_4x4x4_f32_ae32(A.ptr, B.ptr, C.ptr));
+ }
+};
+
+// ═══════════════════════════════════════════════════════════════════════════
+// Window functions
+// ═══════════════════════════════════════════════════════════════════════════
+
+pub const Window = struct {
+ pub fn hann(window: []f32) void {
+ sys.dsps_wind_hann_f32(window.ptr, @intCast(window.len));
+ }
+
+ pub fn blackman(window: []f32) void {
+ sys.dsps_wind_blackman_f32(window.ptr, @intCast(window.len));
+ }
+
+ pub fn blackmanHarris(window: []f32) void {
+ sys.dsps_wind_blackman_harris_f32(window.ptr, @intCast(window.len));
+ }
+
+ pub fn blackmanNuttall(window: []f32) void {
+ sys.dsps_wind_blackman_nuttall_f32(window.ptr, @intCast(window.len));
+ }
+
+ pub fn nuttall(window: []f32) void {
+ sys.dsps_wind_nuttall_f32(window.ptr, @intCast(window.len));
+ }
+
+ pub fn flatTop(window: []f32) void {
+ sys.dsps_wind_flat_top_f32(window.ptr, @intCast(window.len));
+ }
+};
+
+// ═══════════════════════════════════════════════════════════════════════════
+// Biquad IIR filter
+// ═══════════════════════════════════════════════════════════════════════════
+
+pub const BiquadFilterType = enum {
+ lowpass,
+ highpass,
+ bandpass,
+ bandpass_0db,
+ notch,
+ allpass_360,
+ allpass_180,
+ peaking_eq,
+ low_shelf,
+ high_shelf,
+};
+
+pub const BiquadFilter = struct {
+ coeffs: [5]f32,
+ state: [2]f32,
+
+ pub fn init(filter_type: BiquadFilterType, freq: f32, q_factor: f32, gain: f32) !BiquadFilter {
+ var coeffs: [5]f32 = undefined;
+
+ const result = switch (filter_type) {
+ .lowpass => sys.dsps_biquad_gen_lpf_f32(&coeffs, freq, q_factor),
+ .highpass => sys.dsps_biquad_gen_hpf_f32(&coeffs, freq, q_factor),
+ .bandpass => sys.dsps_biquad_gen_bpf_f32(&coeffs, freq, q_factor),
+ .bandpass_0db => sys.dsps_biquad_gen_bpf0db_f32(&coeffs, freq, q_factor),
+ .notch => sys.dsps_biquad_gen_notch_f32(&coeffs, freq, gain, q_factor),
+ .allpass_360 => sys.dsps_biquad_gen_allpass360_f32(&coeffs, freq, q_factor),
+ .allpass_180 => sys.dsps_biquad_gen_allpass180_f32(&coeffs, freq, q_factor),
+ .peaking_eq => sys.dsps_biquad_gen_peakingEQ_f32(&coeffs, freq, q_factor),
+ .low_shelf => sys.dsps_biquad_gen_lowShelf_f32(&coeffs, freq, gain, q_factor),
+ .high_shelf => sys.dsps_biquad_gen_highShelf_f32(&coeffs, freq, gain, q_factor),
+ };
+
+ try errors.espCheckError(result);
+
+ return .{ .coeffs = coeffs, .state = std.mem.zeroes([2]f32) };
+ }
+
+ pub fn process(self: *BiquadFilter, input: []const f32, output: []f32) !void {
+ if (input.len != output.len) return error.LengthMismatch;
+ try errors.espCheckError(sys.dsps_biquad_f32_aes3(input.ptr, output.ptr, @intCast(input.len), &self.coeffs, &self.state));
+ }
+
+ pub fn reset(self: *BiquadFilter) void {
+ self.state = std.mem.zeroes([2]f32);
+ }
+};
+
+// ═══════════════════════════════════════════════════════════════════════════
+// Convolution and correlation
+// ═══════════════════════════════════════════════════════════════════════════
+
+pub const Signal = struct {
+ /// Convolution: convout = Signal ⊛ Kernel
+ pub fn convolveF32(signal: []const f32, kernel: []const f32, output: []f32) !void {
+ try errors.espCheckError(sys.dsps_conv_f32_ae32(signal.ptr, @intCast(signal.len), kernel.ptr, @intCast(kernel.len), output.ptr));
+ }
+
+ /// Cross-correlation: dest = Signal ⋆ Pattern
+ pub fn correlateF32(signal: []const f32, pattern: []const f32, output: []f32) !void {
+ try errors.espCheckError(sys.dsps_corr_f32_ae32(signal.ptr, @intCast(signal.len), pattern.ptr, @intCast(pattern.len), output.ptr));
+ }
+
+ /// Generate tone: output = Ampl * sin(2π * freq * t + phase)
+ pub fn generateToneF32(output: []f32, amplitude: f32, frequency: f32, phase: f32) !void {
+ try errors.espCheckError(sys.dsps_tone_gen_f32(output.ptr, @intCast(output.len), amplitude, frequency, phase));
+ }
+
+ /// Calculate Signal-to-Noise Ratio
+ pub fn snrF32(input: []const f32, use_dc: bool) f32 {
+ return sys.dsps_snr_f32(input.ptr, @intCast(input.len), if (use_dc) 1 else 0);
+ }
+
+ /// Calculate Spurious-Free Dynamic Range
+ pub fn sfdrF32(input: []const f32, use_dc: bool) f32 {
+ return sys.dsps_sfdr_f32(input.ptr, @intCast(input.len), if (use_dc) 1 else 0);
+ }
+};
+
+// ═══════════════════════════════════════════════════════════════════════════
+// DCT (Discrete Cosine Transform)
+// ═══════════════════════════════════════════════════════════════════════════
+
+pub const DCT = struct {
+ /// Forward DCT (in-place)
+ pub fn forwardF32(data: []f32) !void {
+ try errors.espCheckError(sys.dsps_dct_f32(data.ptr, @intCast(data.len)));
+ }
+
+ /// Inverse DCT (in-place)
+ pub fn inverseF32(data: []f32) !void {
+ try errors.espCheckError(sys.dsps_dct_inv_f32(data.ptr, @intCast(data.len)));
+ }
+};
+
+// ═══════════════════════════════════════════════════════════════════════════
+// Utility functions
+// ═══════════════════════════════════════════════════════════════════════════
+
+pub const Utils = struct {
+ /// Check if a number is a power of two
+ pub fn isPowerOfTwo(x: i32) bool {
+ return sys.dsp_is_power_of_two(x);
+ }
+
+ /// Get the power of two value (e.g., 3 → 8, 4 → 16)
+ pub fn powerOfTwo(x: i32) i32 {
+ return sys.dsp_power_of_two(x);
+ }
+
+ /// Visualize signal data (for debugging)
+ pub fn viewSignalF32(data: []const f32, width: u32, height: u32, min: f32, max: f32) void {
+ sys.dsps_view(data.ptr, @intCast(data.len), @intCast(width), @intCast(height), min, max, '*');
+ }
+
+ /// Visualize spectrum data (for debugging)
+ pub fn viewSpectrumF32(data: []const f32, min: f32, max: f32) void {
+ sys.dsps_view_spectrum(data.ptr, @intCast(data.len), min, max);
+ }
+};
diff --git a/software/zig_main/imports/error.zig b/software/zig_main/imports/error.zig
new file mode 100644
index 0000000..5715b21
--- /dev/null
+++ b/software/zig_main/imports/error.zig
@@ -0,0 +1,58 @@
+const sys = @import("sys");
+const std = @import("std");
+
+// Zig error
+const esp_error = error{
+ Fail,
+ ErrorNoMem,
+ ErrorInvalidArg,
+ ErrorInvalidState,
+ ErrorInvalidSize,
+ ErrorNotFound,
+ ErrorNotSupported,
+ ErrorTimeout,
+ ErrorInvalidResponse,
+ ErrorInvalidCRC,
+ ErrorInvalidVersion,
+ ErrorInvalidMAC,
+ ErrorNotFinished,
+ ErrorNotAllowed,
+ ErrorWiFiBase,
+ ErrorMeshBase,
+ ErrorFlashBase,
+ ErrorHWCryptoBase,
+ ErrorMemProtectBase,
+};
+
+// C to Zig error
+pub fn espError(err: sys.esp_err_t) esp_error!sys.esp_err_t {
+ return switch (@as(sys.esp_err_t, err)) {
+ @as(sys.esp_err_t, sys.ESP_FAIL) => esp_error.Fail,
+ @as(sys.esp_err_t, sys.ESP_ERR_NO_MEM) => esp_error.ErrorNoMem,
+ @as(sys.esp_err_t, sys.ESP_ERR_INVALID_ARG) => esp_error.ErrorInvalidArg,
+ @as(sys.esp_err_t, sys.ESP_ERR_INVALID_STATE) => esp_error.ErrorInvalidState,
+ @as(sys.esp_err_t, sys.ESP_ERR_INVALID_SIZE) => esp_error.ErrorInvalidSize,
+ @as(sys.esp_err_t, sys.ESP_ERR_NOT_FOUND) => esp_error.ErrorNotFound,
+ @as(sys.esp_err_t, sys.ESP_ERR_NOT_SUPPORTED) => esp_error.ErrorNotSupported,
+ @as(sys.esp_err_t, sys.ESP_ERR_TIMEOUT) => esp_error.ErrorTimeout,
+ @as(sys.esp_err_t, sys.ESP_ERR_INVALID_RESPONSE) => esp_error.ErrorInvalidResponse,
+ @as(sys.esp_err_t, sys.ESP_ERR_INVALID_CRC) => esp_error.ErrorInvalidCRC,
+ @as(sys.esp_err_t, sys.ESP_ERR_INVALID_VERSION) => esp_error.ErrorInvalidVersion,
+ @as(sys.esp_err_t, sys.ESP_ERR_INVALID_MAC) => esp_error.ErrorInvalidMAC,
+ @as(sys.esp_err_t, sys.ESP_ERR_NOT_FINISHED) => esp_error.ErrorNotFinished,
+ @as(sys.esp_err_t, sys.ESP_ERR_NOT_ALLOWED) => esp_error.ErrorNotAllowed,
+ @as(sys.esp_err_t, sys.ESP_ERR_WIFI_BASE) => esp_error.ErrorWiFiBase,
+ @as(sys.esp_err_t, sys.ESP_ERR_MESH_BASE) => esp_error.ErrorMeshBase,
+ @as(sys.esp_err_t, sys.ESP_ERR_FLASH_BASE) => esp_error.ErrorFlashBase,
+ @as(sys.esp_err_t, sys.ESP_ERR_HW_CRYPTO_BASE) => esp_error.ErrorHWCryptoBase,
+ @as(sys.esp_err_t, sys.ESP_ERR_MEMPROT_BASE) => esp_error.ErrorMemProtectBase,
+ else => err, // Return the original `sys.esp_err_t` if it's not mapped
+ };
+}
+
+pub fn espCheckError(errc: sys.esp_err_t) esp_error!void {
+ if (errc == @as(sys.esp_err_t, sys.ESP_OK)) return;
+ // Try to surface a specific error variant; unknown non-zero codes become Fail.
+ _ = try espError(errc);
+ return error.Fail;
+}
diff --git a/software/zig_main/imports/event.zig b/software/zig_main/imports/event.zig
new file mode 100644
index 0000000..e31165f
--- /dev/null
+++ b/software/zig_main/imports/event.zig
@@ -0,0 +1,202 @@
+const sys = @import("sys");
+const errors = @import("error");
+
+// ---------------------------------------------------------------------------
+// Type aliases
+// ---------------------------------------------------------------------------
+
+pub const Base = sys.esp_event_base_t;
+pub const LoopHandle = sys.esp_event_loop_handle_t;
+pub const HandlerT = sys.esp_event_handler_t;
+pub const HandlerInstance = sys.esp_event_handler_instance_t;
+pub const LoopArgs = sys.esp_event_loop_args_t;
+
+/// Match any event base when registering handlers.
+pub const ANY_BASE: Base = sys.ESP_EVENT_ANY_BASE;
+/// Match any event ID when registering handlers.
+pub const ANY_ID: i32 = sys.ESP_EVENT_ANY_ID;
+
+// ---------------------------------------------------------------------------
+// Default event loop
+// ---------------------------------------------------------------------------
+
+/// Create the default event loop. Must be called once before posting events
+/// or registering handlers on the default loop.
+pub fn loopCreateDefault() !void {
+ try errors.espCheckError(sys.esp_event_loop_create_default());
+}
+
+/// Delete the default event loop.
+pub fn loopDeleteDefault() !void {
+ try errors.espCheckError(sys.esp_event_loop_delete_default());
+}
+
+// ---------------------------------------------------------------------------
+// Custom event loop
+// ---------------------------------------------------------------------------
+
+/// Create a custom event loop with the given configuration.
+pub fn loopCreate(args: *const LoopArgs) !LoopHandle {
+ var loop: LoopHandle = null;
+ try errors.espCheckError(sys.esp_event_loop_create(args, &loop));
+ return loop;
+}
+
+/// Delete a custom event loop.
+pub fn loopDelete(loop: LoopHandle) !void {
+ try errors.espCheckError(sys.esp_event_loop_delete(loop));
+}
+
+/// Run a custom event loop for up to `ticks_to_run` ticks.
+pub fn loopRun(loop: LoopHandle, ticks_to_run: sys.TickType_t) !void {
+ try errors.espCheckError(sys.esp_event_loop_run(loop, ticks_to_run));
+}
+
+// ---------------------------------------------------------------------------
+// Handler registration — default loop
+// ---------------------------------------------------------------------------
+
+/// Register an event handler on the default event loop.
+///
+/// Pass `ANY_BASE` / `ANY_ID` as wildcards.
+pub fn handlerRegister(
+ event_base: Base,
+ event_id: i32,
+ handler: HandlerT,
+ arg: ?*anyopaque,
+) !void {
+ try errors.espCheckError(sys.esp_event_handler_register(event_base, event_id, handler, arg));
+}
+
+/// Unregister a previously registered handler from the default loop.
+pub fn handlerUnregister(
+ event_base: Base,
+ event_id: i32,
+ handler: HandlerT,
+) !void {
+ try errors.espCheckError(sys.esp_event_handler_unregister(event_base, event_id, handler));
+}
+
+/// Register an instance-based handler on the default loop.
+/// Returns an `HandlerInstance` token usable with `handlerInstanceUnregister`.
+pub fn handlerInstanceRegister(
+ event_base: Base,
+ event_id: i32,
+ handler: HandlerT,
+ arg: ?*anyopaque,
+) !HandlerInstance {
+ var instance: HandlerInstance = null;
+ try errors.espCheckError(sys.esp_event_handler_instance_register(event_base, event_id, handler, arg, &instance));
+ return instance;
+}
+
+/// Unregister an instance-based handler from the default loop.
+pub fn handlerInstanceUnregister(
+ event_base: Base,
+ event_id: i32,
+ instance: HandlerInstance,
+) !void {
+ try errors.espCheckError(sys.esp_event_handler_instance_unregister(event_base, event_id, instance));
+}
+
+// ---------------------------------------------------------------------------
+// Handler registration — custom loop
+// ---------------------------------------------------------------------------
+
+/// Register a handler on a custom event loop.
+pub fn handlerRegisterWith(
+ loop: LoopHandle,
+ event_base: Base,
+ event_id: i32,
+ handler: HandlerT,
+ arg: ?*anyopaque,
+) !void {
+ try errors.espCheckError(sys.esp_event_handler_register_with(loop, event_base, event_id, handler, arg));
+}
+
+/// Unregister a handler from a custom event loop.
+pub fn handlerUnregisterWith(
+ loop: LoopHandle,
+ event_base: Base,
+ event_id: i32,
+ handler: HandlerT,
+) !void {
+ try errors.espCheckError(sys.esp_event_handler_unregister_with(loop, event_base, event_id, handler));
+}
+
+/// Register an instance-based handler on a custom event loop.
+pub fn handlerInstanceRegisterWith(
+ loop: LoopHandle,
+ event_base: Base,
+ event_id: i32,
+ handler: HandlerT,
+ arg: ?*anyopaque,
+) !HandlerInstance {
+ var instance: HandlerInstance = null;
+ try errors.espCheckError(sys.esp_event_handler_instance_register_with(loop, event_base, event_id, handler, arg, &instance));
+ return instance;
+}
+
+/// Unregister an instance-based handler from a custom event loop.
+pub fn handlerInstanceUnregisterWith(
+ loop: LoopHandle,
+ event_base: Base,
+ event_id: i32,
+ instance: HandlerInstance,
+) !void {
+ try errors.espCheckError(sys.esp_event_handler_instance_unregister_with(loop, event_base, event_id, instance));
+}
+
+// ---------------------------------------------------------------------------
+// Event posting
+// ---------------------------------------------------------------------------
+
+/// Post an event to the default event loop.
+///
+/// `event_data` may be null when `event_data_size` is 0.
+/// `ticks_to_wait` is how long to block if the queue is full.
+pub fn post(
+ event_base: Base,
+ event_id: i32,
+ event_data: ?*const anyopaque,
+ event_data_size: usize,
+ ticks_to_wait: sys.TickType_t,
+) !void {
+ try errors.espCheckError(sys.esp_event_post(event_base, event_id, event_data, event_data_size, ticks_to_wait));
+}
+
+/// Post an event to a custom event loop.
+pub fn postTo(
+ loop: LoopHandle,
+ event_base: Base,
+ event_id: i32,
+ event_data: ?*const anyopaque,
+ event_data_size: usize,
+ ticks_to_wait: sys.TickType_t,
+) !void {
+ try errors.espCheckError(sys.esp_event_post_to(loop, event_base, event_id, event_data, event_data_size, ticks_to_wait));
+}
+
+/// Post an event from an ISR to the default event loop.
+/// Sets `*task_unblocked` to true if a higher-priority task was unblocked.
+pub fn isrPost(
+ event_base: Base,
+ event_id: i32,
+ event_data: ?*const anyopaque,
+ event_data_size: usize,
+ task_unblocked: *sys.BaseType_t,
+) !void {
+ try errors.espCheckError(sys.esp_event_isr_post(event_base, event_id, event_data, event_data_size, task_unblocked));
+}
+
+/// Post an event from an ISR to a custom event loop.
+pub fn isrPostTo(
+ loop: LoopHandle,
+ event_base: Base,
+ event_id: i32,
+ event_data: ?*const anyopaque,
+ event_data_size: usize,
+ task_unblocked: *sys.BaseType_t,
+) !void {
+ try errors.espCheckError(sys.esp_event_isr_post_to(loop, event_base, event_id, event_data, event_data_size, task_unblocked));
+}
diff --git a/software/zig_main/imports/gpio.zig b/software/zig_main/imports/gpio.zig
new file mode 100644
index 0000000..9eea443
--- /dev/null
+++ b/software/zig_main/imports/gpio.zig
@@ -0,0 +1,383 @@
+const sys = @import("sys");
+const std = @import("std");
+const errors = @import("error");
+
+// ---------------------------------------------------------------------------
+// Num — generated enum containing only pins that exist on the current target.
+// ---------------------------------------------------------------------------
+
+pub fn Num() type {
+ comptime var names: []const []const u8 = &.{};
+ comptime var values: []const sys.gpio_num_t = &.{};
+
+ // Always-present sentinels.
+ names = names ++ &[_][]const u8{ "NC", "MAX" };
+ values = values ++ &[_]sys.gpio_num_t{
+ @intCast(sys.GPIO_NUM_NC),
+ @intCast(sys.GPIO_NUM_MAX),
+ };
+
+ // Add only the pins the current target actually declares in sys.
+ inline for (0..49) |n| {
+ @setEvalBranchQuota(200000);
+ const decl = std.fmt.comptimePrint("GPIO_NUM_{d}", .{n});
+ if (@hasDecl(sys, decl)) {
+ names = names ++ &[_][]const u8{std.fmt.comptimePrint("{d}", .{n})};
+ values = values ++ &[_]sys.gpio_num_t{@intCast(@field(sys, decl))};
+ }
+ }
+
+ // @Enum(TagInt, mode, field_names, field_values)
+ return @Enum(
+ sys.gpio_num_t,
+ .exhaustive,
+ names,
+ values[0..],
+ );
+}
+
+/// The GPIO pin enum for the current target.
+/// Only pins declared by the BSP/sys module are present as fields.
+/// Referencing a missing pin (e.g. `.@"22"` on ESP32-C3) is a compile error.
+pub const GpioNum = Num();
+
+/// Convert a GpioNum to the raw C type expected by esp-idf APIs.
+pub inline fn numToC(gpio_num: GpioNum) sys.gpio_num_t {
+ return @intFromEnum(gpio_num);
+}
+
+// ---------------------------------------------------------------------------
+// Other enumerations
+// ---------------------------------------------------------------------------
+
+pub const Port = enum(sys.gpio_port_t) {
+ GPIO_PORT_0 = sys.GPIO_PORT_0,
+ GPIO_PORT_MAX = sys.GPIO_PORT_MAX,
+};
+
+pub const IntType = enum(sys.gpio_int_type_t) {
+ disable = sys.GPIO_INTR_DISABLE,
+ posedge = sys.GPIO_INTR_POSEDGE,
+ negedge = sys.GPIO_INTR_NEGEDGE,
+ anyedge = sys.GPIO_INTR_ANYEDGE,
+ low_level = sys.GPIO_INTR_LOW_LEVEL,
+ high_level = sys.GPIO_INTR_HIGH_LEVEL,
+ max = sys.GPIO_INTR_MAX,
+};
+
+pub const Mode = enum(sys.gpio_mode_t) {
+ disable = sys.GPIO_MODE_DISABLE,
+ input = sys.GPIO_MODE_INPUT,
+ output = sys.GPIO_MODE_OUTPUT,
+ output_od = sys.GPIO_MODE_OUTPUT_OD,
+ input_output_od = sys.GPIO_MODE_INPUT_OUTPUT_OD,
+ input_output = sys.GPIO_MODE_INPUT_OUTPUT,
+};
+
+pub const Pullup = enum(sys.gpio_pullup_t) {
+ disable = sys.GPIO_PULLUP_DISABLE,
+ enable = sys.GPIO_PULLUP_ENABLE,
+};
+
+pub const Pulldown = enum(sys.gpio_pulldown_t) {
+ disable = sys.GPIO_PULLDOWN_DISABLE,
+ enable = sys.GPIO_PULLDOWN_ENABLE,
+};
+
+pub const PullMode = enum(sys.gpio_pull_mode_t) {
+ pullup_only = sys.GPIO_PULLUP_ONLY,
+ pulldown_only = sys.GPIO_PULLDOWN_ONLY,
+ pullup_pulldown = sys.GPIO_PULLUP_PULLDOWN,
+ floating = sys.GPIO_FLOATING,
+};
+
+pub const DriveCap = enum(sys.gpio_drive_cap_t) {
+ cap_0 = sys.GPIO_DRIVE_CAP_0,
+ cap_1 = sys.GPIO_DRIVE_CAP_1,
+ cap_2 = sys.GPIO_DRIVE_CAP_2,
+ default = sys.GPIO_DRIVE_CAP_DEFAULT,
+ cap_3 = sys.GPIO_DRIVE_CAP_3,
+ max = sys.GPIO_DRIVE_CAP_MAX,
+};
+
+// ---------------------------------------------------------------------------
+// Configuration
+// ---------------------------------------------------------------------------
+
+pub fn config(cfg: [*c]const sys.gpio_config_t) !void {
+ try errors.espCheckError(sys.gpio_config(cfg));
+}
+
+pub fn resetPin(gpio_num: GpioNum) !void {
+ try errors.espCheckError(sys.gpio_reset_pin(numToC(gpio_num)));
+}
+
+// ---------------------------------------------------------------------------
+// Interrupts
+// ---------------------------------------------------------------------------
+
+pub fn setIntrType(gpio_num: GpioNum, intr_type: IntType) !void {
+ try errors.espCheckError(sys.gpio_set_intr_type(numToC(gpio_num), @intFromEnum(intr_type)));
+}
+
+pub fn intrEnable(gpio_num: GpioNum) !void {
+ try errors.espCheckError(sys.gpio_intr_enable(numToC(gpio_num)));
+}
+
+pub fn intrDisable(gpio_num: GpioNum) !void {
+ try errors.espCheckError(sys.gpio_intr_disable(numToC(gpio_num)));
+}
+
+pub fn installISRService(intr_alloc_flags: c_int) !void {
+ try errors.espCheckError(sys.gpio_install_isr_service(intr_alloc_flags));
+}
+
+pub fn uninstallISRService() void {
+ sys.gpio_uninstall_isr_service();
+}
+
+pub fn isrRegister(
+ handler: ?*const fn (?*anyopaque) callconv(.c) void,
+ arg: ?*anyopaque,
+ intr_alloc_flags: c_int,
+ handle: [*c]sys.gpio_isr_handle_t,
+) !void {
+ try errors.espCheckError(sys.gpio_isr_register(handler, arg, intr_alloc_flags, handle));
+}
+
+pub fn isrHandlerAdd(gpio_num: GpioNum, isr_handler: sys.gpio_isr_t, args: ?*anyopaque) !void {
+ try errors.espCheckError(sys.gpio_isr_handler_add(numToC(gpio_num), isr_handler, args));
+}
+
+pub fn isrHandlerRemove(gpio_num: GpioNum) !void {
+ try errors.espCheckError(sys.gpio_isr_handler_remove(numToC(gpio_num)));
+}
+
+// ---------------------------------------------------------------------------
+// Level / Direction
+// ---------------------------------------------------------------------------
+
+pub const Level = struct {
+ pub fn set(gpio_num: GpioNum, level: u32) !void {
+ try errors.espCheckError(sys.gpio_set_level(numToC(gpio_num), level));
+ }
+ /// Returns true if the pin is high, false if low.
+ pub fn get(gpio_num: GpioNum) bool {
+ return sys.gpio_get_level(numToC(gpio_num)) != 0;
+ }
+};
+
+pub const Direction = struct {
+ pub fn set(gpio_num: GpioNum, mode: Mode) !void {
+ try errors.espCheckError(sys.gpio_set_direction(numToC(gpio_num), @intFromEnum(mode)));
+ }
+ pub fn sleepSet(gpio_num: GpioNum, mode: Mode) !void {
+ try errors.espCheckError(sys.gpio_sleep_set_direction(numToC(gpio_num), @intFromEnum(mode)));
+ }
+};
+
+// ---------------------------------------------------------------------------
+// Pull resistors
+// ---------------------------------------------------------------------------
+
+pub fn setPullMode(gpio_num: GpioNum, pull: PullMode) !void {
+ try errors.espCheckError(sys.gpio_set_pull_mode(numToC(gpio_num), @intFromEnum(pull)));
+}
+
+pub fn sleepSetPullMode(gpio_num: GpioNum, pull: PullMode) !void {
+ try errors.espCheckError(sys.gpio_sleep_set_pull_mode(numToC(gpio_num), @intFromEnum(pull)));
+}
+
+pub const PULL = struct {
+ pub fn upEn(gpio_num: GpioNum) !void {
+ try errors.espCheckError(sys.gpio_pullup_en(numToC(gpio_num)));
+ }
+ pub fn upDis(gpio_num: GpioNum) !void {
+ try errors.espCheckError(sys.gpio_pullup_dis(numToC(gpio_num)));
+ }
+ pub fn downEn(gpio_num: GpioNum) !void {
+ try errors.espCheckError(sys.gpio_pulldown_en(numToC(gpio_num)));
+ }
+ pub fn downDis(gpio_num: GpioNum) !void {
+ try errors.espCheckError(sys.gpio_pulldown_dis(numToC(gpio_num)));
+ }
+};
+
+// ---------------------------------------------------------------------------
+// Drive strength
+// ---------------------------------------------------------------------------
+
+pub fn setDriveCapability(gpio_num: GpioNum, strength: DriveCap) !void {
+ try errors.espCheckError(sys.gpio_set_drive_capability(numToC(gpio_num), @intFromEnum(strength)));
+}
+
+/// Returns the drive capability of the given pin.
+pub fn getDriveCapability(gpio_num: GpioNum) !DriveCap {
+ var raw: sys.gpio_drive_cap_t = undefined;
+ try errors.espCheckError(sys.gpio_get_drive_capability(numToC(gpio_num), &raw));
+ return @enumFromInt(raw);
+}
+
+// ---------------------------------------------------------------------------
+// Hold / deep-sleep
+// ---------------------------------------------------------------------------
+
+pub fn holdEn(gpio_num: GpioNum) !void {
+ try errors.espCheckError(sys.gpio_hold_en(numToC(gpio_num)));
+}
+
+pub fn holdDis(gpio_num: GpioNum) !void {
+ try errors.espCheckError(sys.gpio_hold_dis(numToC(gpio_num)));
+}
+
+pub fn forceHoldAll() !void {
+ try errors.espCheckError(sys.gpio_force_hold_all());
+}
+
+pub fn forceUnholdAll() !void {
+ try errors.espCheckError(sys.gpio_force_unhold_all());
+}
+
+pub fn deepSleepHoldEn() void {
+ sys.gpio_deep_sleep_hold_en();
+}
+
+pub fn deepSleepHoldDis() void {
+ sys.gpio_deep_sleep_hold_dis();
+}
+
+pub fn deepSleepWakeupEnable(gpio_num: GpioNum, intr_type: IntType) !void {
+ try errors.espCheckError(sys.gpio_deep_sleep_wakeup_enable(numToC(gpio_num), @intFromEnum(intr_type)));
+}
+
+pub fn deepSleepWakeupDisable(gpio_num: GpioNum) !void {
+ try errors.espCheckError(sys.gpio_deep_sleep_wakeup_disable(numToC(gpio_num)));
+}
+
+// ---------------------------------------------------------------------------
+// Sleep select
+// ---------------------------------------------------------------------------
+
+pub fn sleepSelEn(gpio_num: GpioNum) !void {
+ try errors.espCheckError(sys.gpio_sleep_sel_en(numToC(gpio_num)));
+}
+
+pub fn sleepSelDis(gpio_num: GpioNum) !void {
+ try errors.espCheckError(sys.gpio_sleep_sel_dis(numToC(gpio_num)));
+}
+
+// ---------------------------------------------------------------------------
+// Wakeup
+// ---------------------------------------------------------------------------
+
+pub fn wakeupEnable(gpio_num: GpioNum, intr_type: IntType) !void {
+ try errors.espCheckError(sys.gpio_wakeup_enable(numToC(gpio_num), @intFromEnum(intr_type)));
+}
+
+pub fn wakeupDisable(gpio_num: GpioNum) !void {
+ try errors.espCheckError(sys.gpio_wakeup_disable(numToC(gpio_num)));
+}
+
+// ---------------------------------------------------------------------------
+// IOMUX
+// ---------------------------------------------------------------------------
+
+pub fn iomuxIn(gpio_num: GpioNum, signal_idx: u32) void {
+ sys.gpio_iomux_in(numToC(gpio_num), signal_idx);
+}
+
+pub fn iomuxOut(gpio_num: GpioNum, func: c_int, oen_inv: bool) void {
+ sys.gpio_iomux_out(numToC(gpio_num), func, oen_inv);
+}
+
+// ---------------------------------------------------------------------------
+// Debug
+// ---------------------------------------------------------------------------
+
+pub fn dumpIOConfiguration(out_stream: ?*std.c.FILE, io_bit_mask: u64) !void {
+ try errors.espCheckError(sys.gpio_dump_io_configuration(out_stream, io_bit_mask));
+}
+
+// ---------------------------------------------------------------------------
+// ROM helpers
+// ---------------------------------------------------------------------------
+
+pub const ROM = struct {
+ pub fn padSelectGPIO(iopad_num: u32) void {
+ sys.esp_rom_gpio_pad_select_gpio(iopad_num);
+ }
+ pub fn padPullupOnly(iopad_num: u32) void {
+ sys.esp_rom_gpio_pad_pullup_only(iopad_num);
+ }
+ pub fn padUnhold(gpio_num: GpioNum) void {
+ sys.esp_rom_gpio_pad_unhold(numToC(gpio_num));
+ }
+ pub fn padSetDrive(iopad_num: u32, drv: u32) void {
+ sys.esp_rom_gpio_pad_set_drv(iopad_num, drv);
+ }
+ pub fn connectInSignal(gpio_num: GpioNum, signal_idx: u32, inv: bool) void {
+ sys.esp_rom_gpio_connect_in_signal(numToC(gpio_num), signal_idx, inv);
+ }
+ pub fn connectOutSignal(gpio_num: GpioNum, signal_idx: u32, out_inv: bool, oen_inv: bool) void {
+ sys.esp_rom_gpio_connect_out_signal(numToC(gpio_num), signal_idx, out_inv, oen_inv);
+ }
+};
+
+// ---------------------------------------------------------------------------
+// ETM (Event Task Matrix)
+// ---------------------------------------------------------------------------
+
+pub const ETM = struct {
+ pub fn newChannel(cfg: [*c]const sys.esp_etm_channel_config_t, ret_chan: [*c]sys.esp_etm_channel_handle_t) !void {
+ try errors.espCheckError(sys.esp_etm_new_channel(cfg, ret_chan));
+ }
+ pub fn delChannel(chan: sys.esp_etm_channel_handle_t) !void {
+ try errors.espCheckError(sys.esp_etm_del_channel(chan));
+ }
+ pub fn channelEnable(chan: sys.esp_etm_channel_handle_t) !void {
+ try errors.espCheckError(sys.esp_etm_channel_enable(chan));
+ }
+ pub fn channelDisable(chan: sys.esp_etm_channel_handle_t) !void {
+ try errors.espCheckError(sys.esp_etm_channel_disable(chan));
+ }
+ pub fn channelConnect(
+ chan: sys.esp_etm_channel_handle_t,
+ event: sys.esp_etm_event_handle_t,
+ task: sys.esp_etm_task_handle_t,
+ ) !void {
+ try errors.espCheckError(sys.esp_etm_channel_connect(chan, event, task));
+ }
+ pub fn delEvent(event: sys.esp_etm_event_handle_t) !void {
+ try errors.espCheckError(sys.esp_etm_del_event(event));
+ }
+ pub fn delTask(task: sys.esp_etm_task_handle_t) !void {
+ try errors.espCheckError(sys.esp_etm_del_task(task));
+ }
+ pub fn dump(out_stream: ?*std.c.FILE) !void {
+ try errors.espCheckError(sys.esp_etm_dump(out_stream));
+ }
+};
+
+// ---------------------------------------------------------------------------
+// GPIO ETM event/task binding
+// ---------------------------------------------------------------------------
+
+pub fn newEtmEvent(cfg: [*c]const sys.gpio_etm_event_config_t, ret_event: [*c]sys.esp_etm_event_handle_t) !void {
+ try errors.espCheckError(sys.gpio_new_etm_event(cfg, ret_event));
+}
+
+pub fn etmEventBindGPIO(event: sys.esp_etm_event_handle_t, gpio_num: GpioNum) !void {
+ try errors.espCheckError(sys.gpio_etm_event_bind_gpio(event, numToC(gpio_num)));
+}
+
+pub fn newEtmTask(cfg: [*c]const sys.gpio_etm_task_config_t, ret_task: [*c]sys.esp_etm_task_handle_t) !void {
+ try errors.espCheckError(sys.gpio_new_etm_task(cfg, ret_task));
+}
+
+pub fn etmTaskAddGPIO(task: sys.esp_etm_task_handle_t, gpio_num: GpioNum) !void {
+ try errors.espCheckError(sys.gpio_etm_task_add_gpio(task, numToC(gpio_num)));
+}
+
+pub fn etmTaskRemoveGPIO(task: sys.esp_etm_task_handle_t, gpio_num: GpioNum) !void {
+ try errors.espCheckError(sys.gpio_etm_task_rm_gpio(task, numToC(gpio_num)));
+}
diff --git a/software/zig_main/imports/heap.zig b/software/zig_main/imports/heap.zig
new file mode 100644
index 0000000..672d7bd
--- /dev/null
+++ b/software/zig_main/imports/heap.zig
@@ -0,0 +1,326 @@
+const sys = @import("sys");
+const std = @import("std");
+const errors = @import("error");
+const builtin = @import("builtin");
+
+// read: https://github.com/espressif/esp-idf/blob/97d95853572ab74f4769597496af9d5fe8b6bdea/components/heap/include/esp_heap_caps.h#L29-L53
+// ---------------------------------------------------------------------------
+// Caps — packed struct matching esp_heap_caps.h bit positions exactly.
+//
+// Bit layout (matches MALLOC_CAP_* defines):
+// 0 exec (only when CONFIG_HEAP_HAS_EXEC_HEAP)
+// 1 32bit
+// 2 8bit
+// 3 dma
+// 4- 9 pid2..pid7
+// 10 spiram
+// 11 internal
+// 12 default
+// 13 iram_8bit
+// 14 retention
+// 15 rtcram
+// 16 tcm
+// 17 dma_desc_ahb
+// 18 dma_desc_axi
+// 19 cache_aligned
+// 20 simd
+// 21-30 (reserved)
+// 31 invalid
+// ---------------------------------------------------------------------------
+pub const Caps = packed struct(u32) {
+ exec: bool = false, // bit 0 — requires CONFIG_HEAP_HAS_EXEC_HEAP
+ @"32bit": bool = false, // bit 1
+ @"8bit": bool = false, // bit 2
+ dma: bool = false, // bit 3
+ pid2: bool = false, // bit 4
+ pid3: bool = false, // bit 5
+ pid4: bool = false, // bit 6
+ pid5: bool = false, // bit 7
+ pid6: bool = false, // bit 8
+ pid7: bool = false, // bit 9
+ spiram: bool = false, // bit 10
+ internal: bool = false, // bit 11
+ default: bool = false, // bit 12
+ iram_8bit: bool = false, // bit 13
+ retention: bool = false, // bit 14
+ rtcram: bool = false, // bit 15
+ tcm: bool = false, // bit 16
+ dma_desc_ahb: bool = false, // bit 17
+ dma_desc_axi: bool = false, // bit 18
+ cache_aligned: bool = false, // bit 19
+ simd: bool = false, // bit 20
+ _reserved: u10 = 0, // bits 21-30
+ invalid: bool = false, // bit 31
+
+ /// Cast to the raw u32 value the heap_caps_* C functions expect.
+ pub fn toRaw(self: Caps) u32 {
+ return @bitCast(self);
+ }
+
+ /// Re-hydrate from a raw C bitmask (e.g. value returned by a C API).
+ pub fn fromRaw(raw: u32) Caps {
+ return @bitCast(raw);
+ }
+
+ // -- Named presets matching common ESP-IDF usage patterns ---------------
+
+ /// General-purpose heap (equivalent to malloc/calloc).
+ pub const default_caps: Caps = .{ .default = true };
+ /// Internal RAM, byte-addressable.
+ pub const internal_caps: Caps = .{ .internal = true, .@"8bit" = true };
+ /// DMA-capable internal RAM.
+ pub const dma_caps: Caps = .{ .dma = true, .@"8bit" = true, .internal = true };
+ /// External SPI RAM, byte-addressable.
+ pub const spiram_caps: Caps = .{ .spiram = true, .@"8bit" = true };
+ /// RTC fast memory (survives deep sleep).
+ pub const rtcram_caps: Caps = .{ .rtcram = true };
+ /// Tightly-coupled memory.
+ pub const tcm_caps: Caps = .{ .tcm = true };
+ /// Executable memory (requires CONFIG_HEAP_HAS_EXEC_HEAP).
+ pub const exec_caps: Caps = .{ .exec = true };
+ /// Cache-line aligned memory.
+ pub const cache_aligned_caps: Caps = .{ .cache_aligned = true, .default = true };
+};
+
+// Verify the bit layout matches the C header at compile time.
+comptime {
+ std.debug.assert(@as(u32, @bitCast(Caps{ .exec = true })) == (1 << 0));
+ std.debug.assert(@as(u32, @bitCast(Caps{ .@"32bit" = true })) == (1 << 1));
+ std.debug.assert(@as(u32, @bitCast(Caps{ .@"8bit" = true })) == (1 << 2));
+ std.debug.assert(@as(u32, @bitCast(Caps{ .dma = true })) == (1 << 3));
+ std.debug.assert(@as(u32, @bitCast(Caps{ .spiram = true })) == (1 << 10));
+ std.debug.assert(@as(u32, @bitCast(Caps{ .internal = true })) == (1 << 11));
+ std.debug.assert(@as(u32, @bitCast(Caps{ .default = true })) == (1 << 12));
+ std.debug.assert(@as(u32, @bitCast(Caps{ .simd = true })) == (1 << 20));
+ std.debug.assert(@as(u32, @bitCast(Caps{ .invalid = true })) == (1 << 31));
+}
+
+// ---------------------------------------------------------------------------
+// HeapCapsAllocator
+// ---------------------------------------------------------------------------
+
+pub const HeapCapsAllocator = struct {
+ caps: Caps = Caps.default_caps,
+
+ const Self = @This();
+
+ pub fn init(caps: ?Caps) Self {
+ return .{ .caps = caps orelse Caps.default_caps };
+ }
+
+ pub fn allocator(self: *Self) std.mem.Allocator {
+ return .{
+ .ptr = self,
+ .vtable = &.{
+ .alloc = alloc,
+ .resize = resize,
+ .remap = remap,
+ .free = free,
+ },
+ };
+ }
+
+ pub fn dump(self: Self) void {
+ sys.heap_caps_dump(self.caps.toRaw());
+ }
+ pub fn allocatedSize(_: Self, ptr: ?*anyopaque) usize {
+ return sys.heap_caps_get_allocated_size(ptr);
+ }
+ pub fn largestFreeBlock(self: Self) usize {
+ return sys.heap_caps_get_largest_free_block(self.caps.toRaw());
+ }
+ pub fn totalSize(self: Self) usize {
+ return sys.heap_caps_get_total_size(self.caps.toRaw());
+ }
+ pub fn freeSize(self: Self) usize {
+ return sys.heap_caps_get_free_size(self.caps.toRaw());
+ }
+ pub fn minimumFreeSize(self: Self) usize {
+ return sys.heap_caps_get_minimum_free_size(self.caps.toRaw());
+ }
+ pub fn internalFreeSize(_: Self) usize {
+ return sys.esp_get_free_internal_heap_size();
+ }
+
+ fn alloc(ctx: *anyopaque, len: usize, alignment: std.mem.Alignment, _: usize) ?[*]u8 {
+ const self: *Self = @ptrCast(@alignCast(ctx));
+ return @ptrCast(sys.heap_caps_aligned_alloc(
+ alignment.toByteUnits(),
+ len,
+ self.caps.toRaw(),
+ ));
+ }
+
+ fn resize(_: *anyopaque, buf: []u8, _: std.mem.Alignment, new_len: usize, _: usize) bool {
+ if (new_len <= buf.len) return true;
+ if (@TypeOf(sys.heap_caps_get_allocated_size) != void) {
+ if (new_len <= sys.heap_caps_get_allocated_size(buf.ptr)) return true;
+ }
+ return false;
+ }
+
+ fn remap(ctx: *anyopaque, memory: []u8, _: std.mem.Alignment, new_len: usize, _: usize) ?[*]u8 {
+ const self: *Self = @ptrCast(@alignCast(ctx));
+ return @ptrCast(sys.heap_caps_realloc(memory.ptr, new_len, self.caps.toRaw()));
+ }
+
+ fn free(_: *anyopaque, buf: []u8, _: std.mem.Alignment, _: usize) void {
+ sys.heap_caps_free(buf.ptr);
+ if (builtin.mode == .Debug) {
+ if (!sys.heap_caps_check_integrity_all(true))
+ @panic("heap_caps: integrity check failed after free");
+ }
+ }
+};
+
+// ---------------------------------------------------------------------------
+// MultiHeapAllocator
+// ---------------------------------------------------------------------------
+
+pub const MultiHeapAllocator = struct {
+ handle: sys.multi_heap_handle_t = null,
+
+ const Self = @This();
+
+ pub fn init(handle: sys.multi_heap_handle_t) Self {
+ return .{ .handle = handle };
+ }
+
+ pub fn allocator(self: *Self) std.mem.Allocator {
+ return .{
+ .ptr = self,
+ .vtable = &.{
+ .alloc = alloc,
+ .resize = resize,
+ .remap = remap,
+ .free = free,
+ },
+ };
+ }
+
+ pub fn allocatedSize(self: Self, p: ?*anyopaque) usize {
+ return sys.multi_heap_get_allocated_size(self.handle, p);
+ }
+ pub fn freeSize(self: Self) usize {
+ return sys.multi_heap_free_size(self.handle);
+ }
+ pub fn minimumFreeSize(self: Self) usize {
+ return sys.multi_heap_minimum_free_size(self.handle);
+ }
+
+ fn alloc(ctx: *anyopaque, len: usize, _: std.mem.Alignment, _: usize) ?[*]u8 {
+ const self: *Self = @ptrCast(@alignCast(ctx));
+ return @ptrCast(sys.multi_heap_malloc(self.handle, len));
+ }
+
+ fn resize(ctx: *anyopaque, buf: []u8, _: std.mem.Alignment, new_len: usize, _: usize) bool {
+ const self: *Self = @ptrCast(@alignCast(ctx));
+ if (new_len <= buf.len) return true;
+ if (@TypeOf(sys.multi_heap_get_allocated_size) != void) {
+ if (new_len <= sys.multi_heap_get_allocated_size(self.handle, buf.ptr))
+ return true;
+ }
+ return false;
+ }
+
+ fn remap(ctx: *anyopaque, memory: []u8, _: std.mem.Alignment, new_len: usize, _: usize) ?[*]u8 {
+ const self: *Self = @ptrCast(@alignCast(ctx));
+ return @ptrCast(sys.multi_heap_realloc(self.handle, memory.ptr, new_len));
+ }
+
+ fn free(ctx: *anyopaque, buf: []u8, _: std.mem.Alignment, _: usize) void {
+ const self: *Self = @ptrCast(@alignCast(ctx));
+ sys.multi_heap_free(self.handle, buf.ptr);
+ if (builtin.mode == .Debug) {
+ if (!sys.multi_heap_check(self.handle, true))
+ @panic("multi_heap: integrity check failed after free");
+ }
+ }
+};
+
+// ---------------------------------------------------------------------------
+// VPortAllocator
+// ---------------------------------------------------------------------------
+
+pub const VPortAllocator = struct {
+ const Self = @This();
+
+ pub fn init() Self {
+ return .{};
+ }
+
+ pub fn allocator(self: *Self) std.mem.Allocator {
+ return .{
+ .ptr = self,
+ .vtable = &.{
+ .alloc = alloc,
+ .resize = resize,
+ .remap = remap,
+ .free = free,
+ },
+ };
+ }
+
+ pub fn freeSize(_: Self) usize {
+ return sys.xPortGetFreeHeapSize();
+ }
+ pub fn minimumFreeSize(_: Self) usize {
+ return sys.xPortGetMinimumEverFreeHeapSize();
+ }
+
+ fn alloc(_: *anyopaque, len: usize, _: std.mem.Alignment, _: usize) ?[*]u8 {
+ return @ptrCast(sys.pvPortMalloc(len));
+ }
+
+ fn resize(_: *anyopaque, buf: []u8, _: std.mem.Alignment, new_len: usize, _: usize) bool {
+ return new_len <= buf.len;
+ }
+
+ fn remap(_: *anyopaque, memory: []u8, _: std.mem.Alignment, new_len: usize, _: usize) ?[*]u8 {
+ const new_ptr = sys.pvPortMalloc(new_len) orelse return null;
+ @memcpy(@as([*]u8, @ptrCast(new_ptr))[0..@min(memory.len, new_len)], memory[0..@min(memory.len, new_len)]);
+ sys.vPortFree(memory.ptr);
+ return @ptrCast(new_ptr);
+ }
+
+ fn free(_: *anyopaque, buf: []u8, _: std.mem.Alignment, _: usize) void {
+ sys.vPortFree(buf.ptr);
+ }
+};
+
+// ---------------------------------------------------------------------------
+// TRACE
+// ---------------------------------------------------------------------------
+
+pub const TRACE = struct {
+ pub fn initStandalone(record_buffer: [*c]sys.heap_trace_record_t, num_records: usize) !void {
+ try errors.espCheckError(sys.heap_trace_init_standalone(record_buffer, num_records));
+ }
+ pub fn initTohost() !void {
+ try errors.espCheckError(sys.heap_trace_init_tohost());
+ }
+ pub fn start(mode: sys.heap_trace_mode_t) !void {
+ try errors.espCheckError(sys.heap_trace_start(mode));
+ }
+ pub fn stop() !void {
+ try errors.espCheckError(sys.heap_trace_stop());
+ }
+ pub fn @"resume"() !void {
+ try errors.espCheckError(sys.heap_trace_resume());
+ }
+ pub fn getCount() usize {
+ return sys.heap_trace_get_count();
+ }
+ pub fn get(index: usize, record: [*c]sys.heap_trace_record_t) !void {
+ try errors.espCheckError(sys.heap_trace_get(index, record));
+ }
+ pub fn dump() void {
+ sys.heap_trace_dump();
+ }
+ pub fn dumpCaps(caps: Caps) void {
+ sys.heap_trace_dump_caps(caps.toRaw());
+ }
+ pub fn summary(sum: [*c]sys.heap_trace_summary_t) !void {
+ try errors.espCheckError(sys.heap_trace_summary(sum));
+ }
+};
diff --git a/software/zig_main/imports/hosted.zig b/software/zig_main/imports/hosted.zig
new file mode 100644
index 0000000..bd9e47b
--- /dev/null
+++ b/software/zig_main/imports/hosted.zig
@@ -0,0 +1,191 @@
+const sys = @import("sys");
+const errors = @import("error");
+
+pub const MAC_LEN = 6;
+
+// ─────────────────────────────────────────────────────────────────────────────
+// Re-exported / renamed types from sys
+// ─────────────────────────────────────────────────────────────────────────────
+
+pub const EspMacType = sys.esp_mac_type_t;
+pub const EspResetReason = sys.esp_reset_reason_t;
+pub const EspEventBase = sys.esp_event_base_t;
+pub const EspHostedAppDesc = sys.esp_hosted_app_desc_t;
+pub const EspHostedMemInfo = sys.esp_hosted_mem_info_t;
+pub const EspHostedCapInfo = sys.esp_hosted_cap_info_t;
+pub const EspHostedCurrMemInfo = sys.esp_hosted_curr_mem_info_t;
+
+// ─────────────────────────────────────────────────────────────────────────────
+// Constants & enums
+// ─────────────────────────────────────────────────────────────────────────────
+
+pub const EVENT_BASE = sys.ESP_HOSTED_EVENT;
+
+pub const EventId = enum(c_int) {
+ cp_init = sys.ESP_HOSTED_EVENT_CP_INIT,
+ cp_heartbeat = sys.ESP_HOSTED_EVENT_CP_HEARTBEAT,
+ transport_failure = sys.ESP_HOSTED_EVENT_TRANSPORT_FAILURE,
+ transport_up = sys.ESP_HOSTED_EVENT_TRANSPORT_UP,
+ transport_down = sys.ESP_HOSTED_EVENT_TRANSPORT_DOWN,
+ mem_monitor = sys.ESP_HOSTED_EVENT_MEM_MONITOR,
+ _,
+};
+
+pub const SlaveOtaStatus = enum(c_int) {
+ activated = sys.ESP_HOSTED_SLAVE_OTA_ACTIVATED,
+ completed = sys.ESP_HOSTED_SLAVE_OTA_COMPLETED,
+ not_required = sys.ESP_HOSTED_SLAVE_OTA_NOT_REQUIRED,
+ not_started = sys.ESP_HOSTED_SLAVE_OTA_NOT_STARTED,
+ in_progress = sys.ESP_HOSTED_SLAVE_OTA_IN_PROGRESS,
+ failed = sys.ESP_HOSTED_SLAVE_OTA_FAILED,
+ _,
+};
+
+pub const MemMonitorConfig = enum(c_uint) {
+ no_change = sys.ESP_HOSTED_MEMMONITOR_NO_CHANGE,
+ disable = sys.ESP_HOSTED_MEMMONITOR_DISABLE,
+ enable = sys.ESP_HOSTED_MEMMONITOR_ENABLE,
+ _,
+};
+
+// ─────────────────────────────────────────────────────────────────────────────
+// Callback types (Zig-friendly)
+// ─────────────────────────────────────────────────────────────────────────────
+
+pub const CustomDataCallback = *const fn (
+ msg_id: u32,
+ data: []const u8,
+) void;
+
+// ─────────────────────────────────────────────────────────────────────────────
+// Internal adapter state
+// ─────────────────────────────────────────────────────────────────────────────
+
+var custom_data_cb: ?CustomDataCallback = null;
+
+fn customDataCbAdapter(
+ msg_id: u32,
+ data: [*c]const u8,
+ data_len: usize,
+) callconv(.C) void {
+ if (custom_data_cb) |cb| {
+ cb(msg_id, data[0..data_len]);
+ }
+}
+
+// ─────────────────────────────────────────────────────────────────────────────
+// Hosted API
+// ─────────────────────────────────────────────────────────────────────────────
+
+pub const Hosted = struct {
+
+ // ─── Lifecycle ──────────────────────────────────────────────────────────────
+
+ pub fn btControllerInit() !void {
+ try errors.espCheckError(sys.esp_hosted_bt_controller_init());
+ }
+
+ pub fn btControllerDeinit(release_mem: bool) !void {
+ try errors.espCheckError(sys.esp_hosted_bt_controller_deinit(release_mem));
+ }
+
+ pub fn btControllerEnable() !void {
+ try errors.espCheckError(sys.esp_hosted_bt_controller_enable());
+ }
+
+ pub fn btControllerDisable() !void {
+ try errors.espCheckError(sys.esp_hosted_bt_controller_disable());
+ }
+
+ // ─── MAC address management ─────────────────────────────────────────────────
+
+ pub fn setInterfaceMac(typ: EspMacType, mac: []const u8) !void {
+ if (mac.len != MAC_LEN) return error.InvalidMacLength;
+ try errors.espCheckError(sys.esp_hosted_iface_mac_addr_set(@constCast(mac.ptr), mac.len, typ));
+ }
+
+ pub fn getInterfaceMac(typ: EspMacType) ![MAC_LEN]u8 {
+ var mac: [MAC_LEN]u8 = undefined;
+ try errors.espCheckError(sys.esp_hosted_iface_mac_addr_get(&mac, mac.len, typ));
+ return mac;
+ }
+
+ pub fn getInterfaceMacLen(typ: EspMacType) usize {
+ return sys.esp_hosted_iface_mac_addr_len_get(typ);
+ }
+
+ // ─── Coprocessor information ────────────────────────────────────────────────
+
+ pub fn getCoprocessorAppDesc() !EspHostedAppDesc {
+ var desc: EspHostedAppDesc = undefined;
+ try errors.espCheckError(sys.esp_hosted_get_coprocessor_app_desc(&desc));
+ return desc;
+ }
+
+ pub fn getCoprocessorFwVersion() ![3]u32 {
+ var ver: [3]u32 = undefined;
+ try errors.espCheckError(sys.esp_hosted_get_coprocessor_fwversion(&ver));
+ return ver;
+ }
+
+ // ─── Custom data channel ────────────────────────────────────────────────────
+
+ pub fn registerCustomDataCallback(cb: CustomDataCallback) !void {
+ custom_data_cb = cb;
+ try errors.espCheckError(sys.esp_hosted_register_custom_callback(0, customDataCbAdapter)); // msg_id currently ignored by most impls
+ }
+
+ pub fn sendCustomData(msg_id: u32, data: []const u8) !void {
+ try errors.espCheckError(sys.esp_hosted_send_custom_data(msg_id, data.ptr, data.len));
+ }
+
+ // ─── Heartbeat ──────────────────────────────────────────────────────────────
+
+ pub fn configureHeartbeat(enable: bool, duration_seconds: i32) !void {
+ try errors.espCheckError(sys.esp_hosted_configure_heartbeat(enable, duration_seconds));
+ }
+
+ // ─── Memory monitor ─────────────────────────────────────────────────────────
+
+ pub fn setMemoryMonitor(
+ cfg: MemMonitorConfig,
+ report_always: bool,
+ interval_sec: u32,
+ internal_thresholds: sys.esp_hosted_mem_monitor_threshold_t,
+ external_thresholds: sys.esp_hosted_mem_monitor_threshold_t,
+ ) !EspHostedCurrMemInfo {
+ const config = sys.esp_hosted_config_mem_monitor_t{
+ .config = @intFromEnum(cfg),
+ .report_always = report_always,
+ .interval_sec = interval_sec,
+ .internal_mem = internal_thresholds,
+ .external_mem = external_thresholds,
+ };
+
+ var curr: EspHostedCurrMemInfo = undefined;
+ try errors.espCheckError(sys.esp_hosted_set_mem_monitor(&config, &curr));
+ return curr;
+ }
+
+ // ─── Slave OTA (very basic blocking style) ──────────────────────────────────
+
+ pub fn slaveOtaBegin() !void {
+ try errors.espCheckError(sys.esp_hosted_slave_ota_begin());
+ }
+
+ pub fn slaveOtaWrite(data: []const u8) !void {
+ try errors.espCheckError(sys.esp_hosted_slave_ota_write(@constCast(data.ptr), @intCast(data.len)));
+ }
+
+ pub fn slaveOtaEnd() !void {
+ try errors.espCheckError(sys.esp_hosted_slave_ota_end());
+ }
+
+ pub fn slaveOtaActivate() !void {
+ try errors.espCheckError(sys.esp_hosted_slave_ota_activate());
+ }
+
+ pub fn slaveOtaFromUrl(url: []const u8) !void {
+ try errors.espCheckError(sys.esp_hosted_slave_ota(url.ptr));
+ }
+};
diff --git a/software/zig_main/imports/http.zig b/software/zig_main/imports/http.zig
new file mode 100644
index 0000000..449a75e
--- /dev/null
+++ b/software/zig_main/imports/http.zig
@@ -0,0 +1,302 @@
+const sys = @import("sys");
+const errors = @import("error");
+
+// ---------------------------------------------------------------------------
+// http_parser (third-party parser bundled with ESP-IDF)
+// ---------------------------------------------------------------------------
+
+pub const Parser = struct {
+ pub const version = sys.http_parser_version;
+ pub const init = sys.http_parser_init;
+ pub const settingsInit = sys.http_parser_settings_init;
+ pub const execute = sys.http_parser_execute;
+ pub const urlInit = sys.http_parser_url_init;
+ pub const parseUrl = sys.http_parser_parse_url;
+ pub const pause = sys.http_parser_pause;
+};
+pub const shouldKeepAlive = sys.http_should_keep_alive;
+pub const methodStr = sys.http_method_str;
+pub const errnoName = sys.http_errno_name;
+pub const errnoDescription = sys.http_errno_description;
+pub const bodyIsFinal = sys.http_body_is_final;
+
+// ---------------------------------------------------------------------------
+// HTTP server (httpd)
+// ---------------------------------------------------------------------------
+
+pub const Server = struct {
+ /// Start the HTTP server. Returns a handle on success.
+ pub fn start(config: *const sys.httpd_config_t) !sys.httpd_handle_t {
+ var handle: sys.httpd_handle_t = null;
+ try errors.espCheckError(sys.httpd_start(&handle, config));
+ return handle;
+ }
+
+ /// Stop and clean up the HTTP server.
+ pub fn stop(handle: sys.httpd_handle_t) !void {
+ return errors.espCheckError(sys.httpd_stop(handle));
+ }
+
+ /// Register a URI handler.
+ pub fn registerUri(handle: sys.httpd_handle_t, uri_handler: *const sys.httpd_uri_t) !void {
+ return errors.espCheckError(sys.httpd_register_uri_handler(handle, uri_handler));
+ }
+
+ /// Unregister a URI handler by URI string and method.
+ pub fn unregisterUri(handle: sys.httpd_handle_t, uri: [*:0]const u8, method: sys.httpd_method_t) !void {
+ return errors.espCheckError(sys.httpd_unregister_uri_handler(handle, uri, method));
+ }
+
+ /// Unregister all URI handlers for a given URI string.
+ pub fn unregisterAllUris(handle: sys.httpd_handle_t, uri: [*:0]const u8) !void {
+ return errors.espCheckError(sys.httpd_unregister_uri(handle, uri));
+ }
+
+ pub const Session = struct {
+ pub fn setReceiveOverride(hd: sys.httpd_handle_t, sockfd: c_int, recv_func: sys.httpd_recv_func_t) !void {
+ return errors.espCheckError(sys.httpd_sess_set_recv_override(hd, sockfd, recv_func));
+ }
+ pub fn setSendOverride(hd: sys.httpd_handle_t, sockfd: c_int, send_func: sys.httpd_send_func_t) !void {
+ return errors.espCheckError(sys.httpd_sess_set_send_override(hd, sockfd, send_func));
+ }
+ pub fn setPendingOverride(hd: sys.httpd_handle_t, sockfd: c_int, pending_func: sys.httpd_pending_func_t) !void {
+ return errors.espCheckError(sys.httpd_sess_set_pending_override(hd, sockfd, pending_func));
+ }
+ pub fn triggerClose(handle: sys.httpd_handle_t, sockfd: c_int) !void {
+ return errors.espCheckError(sys.httpd_sess_trigger_close(handle, sockfd));
+ }
+ pub fn updateLRUCounter(handle: sys.httpd_handle_t, sockfd: c_int) !void {
+ return errors.espCheckError(sys.httpd_sess_update_lru_counter(handle, sockfd));
+ }
+ pub const getContext = sys.httpd_sess_get_ctx;
+ pub const setContext = sys.httpd_sess_set_ctx;
+ pub const getTransportContext = sys.httpd_sess_get_transport_ctx;
+ pub const setTransportContext = sys.httpd_sess_set_transport_ctx;
+ };
+
+ pub const Request = struct {
+ pub fn asyncHandlerBegin(r: [*c]sys.httpd_req_t, out: [*c][*c]sys.httpd_req_t) !void {
+ return errors.espCheckError(sys.httpd_req_async_handler_begin(r, out));
+ }
+ pub fn asyncHandlerComplete(r: [*c]sys.httpd_req_t) !void {
+ return errors.espCheckError(sys.httpd_req_async_handler_complete(r));
+ }
+ pub const toSockfd = sys.httpd_req_to_sockfd;
+ pub const receiver = sys.httpd_req_recv;
+ pub const getHDRValueLen = sys.httpd_req_get_hdr_value_len;
+ pub fn getHDRValueStr(r: [*c]sys.httpd_req_t, field: [*:0]const u8, val: [*:0]u8, val_size: usize) !void {
+ return errors.espCheckError(sys.httpd_req_get_hdr_value_str(r, field, val, val_size));
+ }
+ pub const getUrlQueryLen = sys.httpd_req_get_url_query_len;
+ pub fn getUrlQueryStr(r: [*c]sys.httpd_req_t, buf: [*:0]u8, buf_len: usize) !void {
+ return errors.espCheckError(sys.httpd_req_get_url_query_str(r, buf, buf_len));
+ }
+ pub fn getCookieValue(req: [*c]sys.httpd_req_t, cookie_name: [*:0]const u8, val: [*:0]u8, val_size: [*c]usize) !void {
+ return errors.espCheckError(sys.httpd_req_get_cookie_val(req, cookie_name, val, val_size));
+ }
+ };
+
+ pub fn queryKeyValue(qry: [*:0]const u8, key: [*:0]const u8, val: [*:0]u8, val_size: usize) !void {
+ return errors.espCheckError(sys.httpd_query_key_value(qry, key, val, val_size));
+ }
+
+ pub const uri_MatchWildcard = sys.httpd_uri_match_wildcard;
+
+ pub const Response = struct {
+ /// Send a complete response with a byte-slice body.
+ pub fn send(r: [*c]sys.httpd_req_t, buf: []const u8) !void {
+ return errors.espCheckError(sys.httpd_resp_send(r, buf.ptr, @intCast(buf.len)));
+ }
+ /// Send a response chunk with a byte-slice body.
+ pub fn sendChunk(r: [*c]sys.httpd_req_t, buf: []const u8) !void {
+ return errors.espCheckError(sys.httpd_resp_send_chunk(r, buf.ptr, @intCast(buf.len)));
+ }
+ /// Send a null-terminated string as the complete response.
+ /// Passes -1 (HTTPD_RESP_USE_STRLEN) so ESP-IDF calls strlen.
+ pub fn sendStr(r: [*c]sys.httpd_req_t, str: [*:0]const u8) !void {
+ return errors.espCheckError(sys.httpd_resp_send(r, str, -1));
+ }
+ /// Send a null-terminated string as a response chunk.
+ pub fn sendStrChunk(r: [*c]sys.httpd_req_t, str: [*:0]const u8) !void {
+ return errors.espCheckError(sys.httpd_resp_send_chunk(r, str, -1));
+ }
+ pub fn setStatus(r: [*c]sys.httpd_req_t, status: [*:0]const u8) !void {
+ return errors.espCheckError(sys.httpd_resp_set_status(r, status));
+ }
+ pub fn setType(r: [*c]sys.httpd_req_t, @"type": [*:0]const u8) !void {
+ return errors.espCheckError(sys.httpd_resp_set_type(r, @"type"));
+ }
+ pub fn setHDR(r: [*c]sys.httpd_req_t, field: [*:0]const u8, value: [*:0]const u8) !void {
+ return errors.espCheckError(sys.httpd_resp_set_hdr(r, field, value));
+ }
+ pub fn sendError(req: [*c]sys.httpd_req_t, @"error": sys.httpd_err_code_t, msg: [*:0]const u8) !void {
+ return errors.espCheckError(sys.httpd_resp_send_err(req, @"error", msg));
+ }
+ pub fn send404(r: [*c]sys.httpd_req_t) !void {
+ return errors.espCheckError(sys.httpd_resp_send_err(r, .HTTPD_404_NOT_FOUND, null));
+ }
+ pub fn send408(r: [*c]sys.httpd_req_t) !void {
+ return errors.espCheckError(sys.httpd_resp_send_err(r, .HTTPD_408_REQ_TIMEOUT, null));
+ }
+ pub fn send500(r: [*c]sys.httpd_req_t) !void {
+ return errors.espCheckError(sys.httpd_resp_send_err(r, .HTTPD_500_INTERNAL_SERVER_ERROR, null));
+ }
+ };
+
+ pub const rawSend = sys.httpd_send;
+
+ pub const Socket = struct {
+ pub const send = sys.httpd_socket_send;
+ pub const receive = sys.httpd_socket_recv;
+ };
+
+ pub const getGlobalUserContext = sys.httpd_get_global_user_ctx;
+ pub const getGlobalTransportContext = sys.httpd_get_global_transport_ctx;
+ pub fn getClientList(handle: sys.httpd_handle_t, fds: [*c]usize, client_fds: [*c]c_int) !void {
+ return errors.espCheckError(sys.httpd_get_client_list(handle, fds, client_fds));
+ }
+ pub const httpd_work_fn_t = sys.httpd_work_fn_t;
+ pub fn queueWork(handle: sys.httpd_handle_t, work: httpd_work_fn_t, arg: ?*anyopaque) !void {
+ return errors.espCheckError(sys.httpd_queue_work(handle, work, arg));
+ }
+};
+
+// ---------------------------------------------------------------------------
+// HTTP client (esp_http_client)
+// ---------------------------------------------------------------------------
+
+pub const Client = struct {
+ handle: sys.esp_http_client_handle_t = null,
+
+ /// Initialise the HTTP client from a config struct. Call `deinit()` when done.
+ pub fn init(config: *const sys.esp_http_client_config_t) Client {
+ return .{ .handle = sys.esp_http_client_init(config) };
+ }
+
+ /// Clean up the client handle. Must be called after use.
+ pub fn deinit(self: Client) !void {
+ return errors.espCheckError(sys.esp_http_client_cleanup(self.handle));
+ }
+
+ /// Perform a complete blocking request/response cycle.
+ pub fn perform(self: Client) !void {
+ return errors.espCheckError(sys.esp_http_client_perform(self.handle));
+ }
+
+ /// Open the connection (for streaming writes).
+ /// `write_len` is Content-Length; pass 0 for requests without a body.
+ pub fn open(self: Client, write_len: c_int) !void {
+ return errors.espCheckError(sys.esp_http_client_open(self.handle, write_len));
+ }
+
+ /// Close the connection (without cleaning up the handle).
+ pub fn close(self: Client) !void {
+ return errors.espCheckError(sys.esp_http_client_close(self.handle));
+ }
+
+ /// Write request body data (call after `open()`).
+ pub fn write(self: Client, data: []const u8) !c_int {
+ const n = sys.esp_http_client_write(self.handle, data.ptr, @intCast(data.len));
+ if (n < 0) return error.HttpWriteFailed;
+ return n;
+ }
+
+ /// Fetch (read) response headers after `open()`.
+ /// Returns Content-Length, or negative on error / chunked encoding.
+ pub fn fetchHeaders(self: Client) i64 {
+ return sys.esp_http_client_fetch_headers(self.handle);
+ }
+
+ /// Read response body into `buf`. Returns number of bytes read (0 = done).
+ pub fn read(self: Client, buf: []u8) !c_int {
+ const n = sys.esp_http_client_read(self.handle, buf.ptr, @intCast(buf.len));
+ if (n < 0) return error.HttpReadFailed;
+ return n;
+ }
+
+ /// Read the entire response into `buf` (convenience wrapper).
+ pub fn readResponse(self: Client, buf: []u8) !c_int {
+ const n = sys.esp_http_client_read_response(self.handle, buf.ptr, @intCast(buf.len));
+ if (n < 0) return error.HttpReadFailed;
+ return n;
+ }
+
+ /// HTTP status code of the last response.
+ pub fn getStatusCode(self: Client) c_int {
+ return sys.esp_http_client_get_status_code(self.handle);
+ }
+
+ /// Content-Length of the last response (-1 if unknown / chunked).
+ pub fn getContentLength(self: Client) i64 {
+ return sys.esp_http_client_get_content_length(self.handle);
+ }
+
+ /// Cancel the current in-progress request.
+ pub fn cancelRequest(self: Client) !void {
+ return errors.espCheckError(sys.esp_http_client_cancel_request(self.handle));
+ }
+
+ /// Setters — configure the client before calling `perform()` or `open()`.
+ pub const Set = struct {
+ pub fn url(client: sys.esp_http_client_handle_t, _url: [*:0]const u8) !void {
+ return errors.espCheckError(sys.esp_http_client_set_url(client, _url));
+ }
+ pub fn postField(client: sys.esp_http_client_handle_t, data: []const u8) !void {
+ return errors.espCheckError(sys.esp_http_client_set_post_field(client, data.ptr, @intCast(data.len)));
+ }
+ pub fn header(client: sys.esp_http_client_handle_t, field: [*:0]const u8, value: [*:0]const u8) !void {
+ return errors.espCheckError(sys.esp_http_client_set_header(client, field, value));
+ }
+ pub fn username(client: sys.esp_http_client_handle_t, _username: [*:0]const u8) !void {
+ return errors.espCheckError(sys.esp_http_client_set_username(client, _username));
+ }
+ pub fn password(client: sys.esp_http_client_handle_t, _password: [*:0]const u8) !void {
+ return errors.espCheckError(sys.esp_http_client_set_password(client, _password));
+ }
+ pub fn method(client: sys.esp_http_client_handle_t, _method: sys.esp_http_client_method_t) !void {
+ return errors.espCheckError(sys.esp_http_client_set_method(client, _method));
+ }
+ pub fn timeout(client: sys.esp_http_client_handle_t, timeout_ms: u32) !void {
+ return errors.espCheckError(sys.esp_http_client_set_timeout_ms(client, timeout_ms));
+ }
+ pub fn userData(client: sys.esp_http_client_handle_t, user_data: ?*anyopaque) !void {
+ return errors.espCheckError(sys.esp_http_client_set_user_data(client, user_data));
+ }
+ pub fn authData(client: sys.esp_http_client_handle_t, auth_data: []const u8) !void {
+ return errors.espCheckError(sys.esp_http_client_set_auth_data(client, auth_data.ptr, @intCast(auth_data.len)));
+ }
+ pub fn authType(client: sys.esp_http_client_handle_t, auth_type: sys.esp_http_client_auth_type_t) !void {
+ return errors.espCheckError(sys.esp_http_client_set_authtype(client, auth_type));
+ }
+ pub fn redirection(client: sys.esp_http_client_handle_t) !void {
+ return errors.espCheckError(sys.esp_http_client_set_redirection(client));
+ }
+ };
+
+ /// Getters — retrieve configuration or response state.
+ pub const Get = struct {
+ /// Copy the current URL into a buffer.
+ pub fn url(client: sys.esp_http_client_handle_t, buf: []u8) !void {
+ return errors.espCheckError(sys.esp_http_client_get_url(client, buf.ptr, @intCast(buf.len)));
+ }
+ /// Get a pointer to the POST field data (no copy).
+ pub fn postField(client: sys.esp_http_client_handle_t, data: *[*c]const u8, len: *c_int) !void {
+ return errors.espCheckError(sys.esp_http_client_get_post_field(client, data, len));
+ }
+ /// Get the value of a request header by key (returns pointer via `value`).
+ pub fn header(client: sys.esp_http_client_handle_t, key: [*:0]const u8, value: *[*c]const u8) !void {
+ return errors.espCheckError(sys.esp_http_client_get_header(client, key, value));
+ }
+ /// Get the configured username (returns pointer via `value`).
+ pub fn username(client: sys.esp_http_client_handle_t, value: *[*c]const u8) !void {
+ return errors.espCheckError(sys.esp_http_client_get_username(client, value));
+ }
+ /// Get the configured password (returns pointer via `value`).
+ pub fn password(client: sys.esp_http_client_handle_t, value: *[*c]const u8) !void {
+ return errors.espCheckError(sys.esp_http_client_get_password(client, value));
+ }
+ pub fn userData(client: sys.esp_http_client_handle_t, user_data: *?*anyopaque) !void {
+ return errors.espCheckError(sys.esp_http_client_get_user_data(client, user_data));
+ }
+ };
+};
diff --git a/software/zig_main/imports/i2c.zig b/software/zig_main/imports/i2c.zig
new file mode 100644
index 0000000..23aa90e
--- /dev/null
+++ b/software/zig_main/imports/i2c.zig
@@ -0,0 +1,44 @@
+const sys = @import("sys");
+const errors = @import("error");
+
+pub const BUS = struct {
+ pub fn add(bus_config: ?*const sys.i2c_master_bus_config_t, ret_bus_handle: [*c]sys.i2c_master_bus_handle_t) !void {
+ return try errors.espCheckError(sys.i2c_new_master_bus(bus_config, ret_bus_handle));
+ }
+ pub fn addDevice(bus_handle: sys.i2c_master_bus_handle_t, dev_config: [*c]const sys.i2c_device_config_t, ret_handle: [*c]sys.i2c_master_dev_handle_t) !void {
+ return try errors.espCheckError(sys.i2c_master_bus_add_device(bus_handle, dev_config, ret_handle));
+ }
+ pub fn del(bus_handle: sys.i2c_master_bus_handle_t) !void {
+ return try errors.espCheckError(sys.i2c_del_master_bus(bus_handle));
+ }
+ pub fn removeDevice(handle: sys.i2c_master_dev_handle_t) !void {
+ return try errors.espCheckError(sys.i2c_master_bus_rm_device(handle));
+ }
+ pub fn reset(handle: sys.i2c_master_bus_handle_t) !void {
+ return try errors.espCheckError(sys.i2c_master_bus_reset(handle));
+ }
+};
+
+/// Write bytes to an I2C device. Buffer length is taken from the slice.
+pub fn transmit(i2c_dev: sys.i2c_master_dev_handle_t, write_buffer: []const u8, xfer_timeout_ms: c_int) !void {
+ return errors.espCheckError(sys.i2c_master_transmit(i2c_dev, write_buffer.ptr, write_buffer.len, xfer_timeout_ms));
+}
+
+/// Write then read in a single I2C transaction (repeated-start).
+pub fn transmitReceive(i2c_dev: sys.i2c_master_dev_handle_t, write_buffer: []const u8, read_buffer: []u8, xfer_timeout_ms: c_int) !void {
+ return errors.espCheckError(sys.i2c_master_transmit_receive(i2c_dev, write_buffer.ptr, write_buffer.len, read_buffer.ptr, read_buffer.len, xfer_timeout_ms));
+}
+
+/// Read bytes from an I2C device. Buffer length is taken from the slice.
+pub fn receive(i2c_dev: sys.i2c_master_dev_handle_t, read_buffer: []u8, xfer_timeout_ms: c_int) !void {
+ return errors.espCheckError(sys.i2c_master_receive(i2c_dev, read_buffer.ptr, read_buffer.len, xfer_timeout_ms));
+}
+pub fn probe(i2c_master: sys.i2c_master_bus_handle_t, address: u16, xfer_timeout_ms: c_int) !void {
+ return try errors.espCheckError(sys.i2c_master_probe(i2c_master, address, xfer_timeout_ms));
+}
+pub fn registerEventCallbacks(i2c_dev: sys.i2c_master_dev_handle_t, cbs: [*c]const sys.i2c_master_event_callbacks_t, user_data: ?*anyopaque) !void {
+ return try errors.espCheckError(sys.i2c_master_register_event_callbacks(i2c_dev, cbs, user_data));
+}
+pub fn waitAllDone(i2c_master: sys.i2c_master_bus_handle_t, timeout_ms: c_int) !void {
+ return try errors.espCheckError(sys.i2c_master_wait_all_done(i2c_master, timeout_ms));
+}
diff --git a/software/zig_main/imports/i2s.zig b/software/zig_main/imports/i2s.zig
new file mode 100644
index 0000000..89cb5f8
--- /dev/null
+++ b/software/zig_main/imports/i2s.zig
@@ -0,0 +1,102 @@
+const sys = @import("sys");
+const errors = @import("error");
+
+pub fn newChannel(chan_cfg: [*c]const sys.i2s_chan_config_t, ret_tx_handle: [*c]sys.i2s_chan_handle_t, ret_rx_handle: [*c]sys.i2s_chan_handle_t) !void {
+ return try errors.espCheckError(sys.i2s_new_channel(chan_cfg, ret_tx_handle, ret_rx_handle));
+}
+pub fn delChannel(handle: sys.i2s_chan_handle_t) !void {
+ return try errors.espCheckError(sys.i2s_del_channel(handle));
+}
+pub fn channelGetInfo(handle: sys.i2s_chan_handle_t, chan_info: [*c]sys.i2s_chan_info_t) !void {
+ return try errors.espCheckError(sys.i2s_channel_get_info(handle, chan_info));
+}
+pub fn channelEnable(handle: sys.i2s_chan_handle_t) !void {
+ return try errors.espCheckError(sys.i2s_channel_enable(handle));
+}
+pub fn channelDisable(handle: sys.i2s_chan_handle_t) !void {
+ return try errors.espCheckError(sys.i2s_channel_disable(handle));
+}
+pub fn channelPreloadData(tx_handle: sys.i2s_chan_handle_t, src: ?*const anyopaque, size: usize, bytes_loaded: [*c]usize) !void {
+ return try errors.espCheckError(sys.i2s_channel_preload_data(tx_handle, src, size, bytes_loaded));
+}
+pub fn channelWrite(handle: sys.i2s_chan_handle_t, src: ?*const anyopaque, size: usize, bytes_written: [*c]usize, timeout_ms: u32) !void {
+ return try errors.espCheckError(sys.i2s_channel_write(handle, src, size, bytes_written, timeout_ms));
+}
+pub fn channelRead(handle: sys.i2s_chan_handle_t, dest: ?*anyopaque, size: usize, bytes_read: [*c]usize, timeout_ms: u32) !void {
+ return try errors.espCheckError(sys.i2s_channel_read(handle, dest, size, bytes_read, timeout_ms));
+}
+pub fn channelRegisterEventCallback(handle: sys.i2s_chan_handle_t, callbacks: [*c]const sys.i2s_event_callbacks_t, user_data: ?*anyopaque) !void {
+ return try errors.espCheckError(sys.i2s_channel_register_event_callback(handle, callbacks, user_data));
+}
+pub fn channelInitPdmRXMode(handle: sys.i2s_chan_handle_t, pdm_rx_cfg: ?*const sys.i2s_pdm_rx_config_t) !void {
+ return try errors.espCheckError(sys.i2s_channel_init_pdm_rx_mode(handle, pdm_rx_cfg));
+}
+pub fn channelReconfigPdmRXClock(handle: sys.i2s_chan_handle_t, clk_cfg: [*c]const sys.i2s_pdm_rx_clk_config_t) !void {
+ return try errors.espCheckError(sys.i2s_channel_reconfig_pdm_rx_clock(handle, clk_cfg));
+}
+pub fn channelReconfigPdmRXSlot(handle: sys.i2s_chan_handle_t, slot_cfg: [*c]const sys.i2s_pdm_rx_slot_config_t) !void {
+ return try errors.espCheckError(sys.i2s_channel_reconfig_pdm_rx_slot(handle, slot_cfg));
+}
+pub fn channelReconfigPdmRXGPIO(handle: sys.i2s_chan_handle_t, gpio_cfg: ?*const sys.i2s_pdm_rx_gpio_config_t) !void {
+ return try errors.espCheckError(sys.i2s_channel_reconfig_pdm_rx_gpio(handle, gpio_cfg));
+}
+pub fn channelInitPdmTXMode(handle: sys.i2s_chan_handle_t, pdm_tx_cfg: ?*const sys.i2s_pdm_tx_config_t) !void {
+ return try errors.espCheckError(sys.i2s_channel_init_pdm_tx_mode(handle, pdm_tx_cfg));
+}
+pub fn channelReconfigPdmTXClock(handle: sys.i2s_chan_handle_t, clk_cfg: [*c]const sys.i2s_pdm_tx_clk_config_t) !void {
+ return try errors.espCheckError(sys.i2s_channel_reconfig_pdm_tx_clock(handle, clk_cfg));
+}
+pub fn channelReconfigPdmTXSlot(handle: sys.i2s_chan_handle_t, slot_cfg: [*c]const sys.i2s_pdm_tx_slot_config_t) !void {
+ return try errors.espCheckError(sys.i2s_channel_reconfig_pdm_tx_slot(handle, slot_cfg));
+}
+pub fn channelReconfigPdmTXGPIO(handle: sys.i2s_chan_handle_t, gpio_cfg: ?*const sys.i2s_pdm_tx_gpio_config_t) !void {
+ return try errors.espCheckError(sys.i2s_channel_reconfig_pdm_tx_gpio(handle, gpio_cfg));
+}
+
+// ---------------------------------------------------------------------------
+// STD mode (standard I2S / PCM — most common)
+// ---------------------------------------------------------------------------
+
+/// Initialise a channel in standard I2S mode (I2S, MSB-justified, LSB-justified, PCM).
+pub fn channelInitStdMode(handle: sys.i2s_chan_handle_t, std_cfg: *const sys.i2s_std_config_t) !void {
+ return errors.espCheckError(sys.i2s_channel_init_std_mode(handle, std_cfg));
+}
+
+/// Reconfigure clock parameters of a standard-mode channel (must be disabled first).
+pub fn channelReconfigStdClock(handle: sys.i2s_chan_handle_t, clk_cfg: *const sys.i2s_std_clk_config_t) !void {
+ return errors.espCheckError(sys.i2s_channel_reconfig_std_clock(handle, clk_cfg));
+}
+
+/// Reconfigure slot parameters of a standard-mode channel (must be disabled first).
+pub fn channelReconfigStdSlot(handle: sys.i2s_chan_handle_t, slot_cfg: *const sys.i2s_std_slot_config_t) !void {
+ return errors.espCheckError(sys.i2s_channel_reconfig_std_slot(handle, slot_cfg));
+}
+
+/// Reconfigure GPIO pins of a standard-mode channel (must be disabled first).
+pub fn channelReconfigStdGPIO(handle: sys.i2s_chan_handle_t, gpio_cfg: *const sys.i2s_std_gpio_config_t) !void {
+ return errors.espCheckError(sys.i2s_channel_reconfig_std_gpio(handle, gpio_cfg));
+}
+
+// ---------------------------------------------------------------------------
+// TDM mode (Time-Division Multiplexed — multiple slots per frame)
+// ---------------------------------------------------------------------------
+
+/// Initialise a channel in TDM mode.
+pub fn channelInitTdmMode(handle: sys.i2s_chan_handle_t, tdm_cfg: *const sys.i2s_tdm_config_t) !void {
+ return errors.espCheckError(sys.i2s_channel_init_tdm_mode(handle, tdm_cfg));
+}
+
+/// Reconfigure clock parameters of a TDM channel (must be disabled first).
+pub fn channelReconfigTdmClock(handle: sys.i2s_chan_handle_t, clk_cfg: *const sys.i2s_tdm_clk_config_t) !void {
+ return errors.espCheckError(sys.i2s_channel_reconfig_tdm_clock(handle, clk_cfg));
+}
+
+/// Reconfigure slot parameters of a TDM channel (must be disabled first).
+pub fn channelReconfigTdmSlot(handle: sys.i2s_chan_handle_t, slot_cfg: *const sys.i2s_tdm_slot_config_t) !void {
+ return errors.espCheckError(sys.i2s_channel_reconfig_tdm_slot(handle, slot_cfg));
+}
+
+/// Reconfigure GPIO pins of a TDM channel (must be disabled first).
+pub fn channelReconfigTdmGPIO(handle: sys.i2s_chan_handle_t, gpio_cfg: *const sys.i2s_tdm_gpio_config_t) !void {
+ return errors.espCheckError(sys.i2s_channel_reconfig_tdm_gpio(handle, gpio_cfg));
+}
diff --git a/software/zig_main/imports/idf.zig b/software/zig_main/imports/idf.zig
new file mode 100644
index 0000000..5585372
--- /dev/null
+++ b/software/zig_main/imports/idf.zig
@@ -0,0 +1,134 @@
+pub const bl = @import("bootloader");
+pub const bt = switch (@hasDecl(sys, "CONFIG_BT_ENABLED")) {
+ true => @import("bluetooth"),
+ false => @compileError("bluetooth requires CONFIG_BT_ENABLED in sdkconfig"),
+};
+pub const nimble = if (@hasDecl(sys, "CONFIG_BT_NIMBLE_ENABLED"))
+ @import("nimble")
+else
+ @compileError(
+ \\NimBLE not enabled. Enable via:
+ \\ idf.py menuconfig → Component config → Bluetooth → Host → NimBLE
+ \\Then run: idf.py reconfigure
+ );
+pub const crc = @import("crc");
+pub const dsp = switch (@hasDecl(sys, "HAS_ESP_DSP")) {
+ true => @import("dsp"),
+ false => @compileError("requires: idf.py add-dependency espressif/esp-dsp"),
+};
+pub const err = @import("error");
+pub const gpio = @import("gpio");
+pub const heap = @import("heap");
+pub const http = @import("http");
+pub const i2c = @import("i2c");
+pub const i2s = @import("i2s");
+pub const led = switch (@hasDecl(sys, "HAS_LED_STRIP")) {
+ true => @import("led"),
+ false => @compileError("requires: idf.py add-dependency espressif/led_strip"),
+};
+pub const log = @import("log");
+pub const lwip = @import("lwip");
+pub const mqtt = @import("mqtt");
+pub const esp_now = @import("now");
+pub const phy = @import("phy");
+pub const pulse = @import("pulse");
+pub const esp_panic = @import("panic");
+pub const rtos = @import("rtos");
+pub const nvs = @import("nvs");
+pub const partition = @import("partition");
+pub const sleep = @import("sleep");
+pub const event = @import("event");
+pub const wdt = @import("wdt");
+pub const segger = @import("segger");
+pub const spi = @import("spi");
+pub const uart = @import("uart");
+pub const ver = @import("ver");
+pub const esp_hosted = switch (@hasDecl(sys, "HAS_ESP_HOSTED")) {
+ true => @import("hosted"),
+ false => @compileError("requires: idf.py add-dependency espressif/esp_hosted"),
+};
+pub const wifi_remote = switch (@hasDecl(sys, "HAS_ESP_WIFI_REMOTE")) {
+ true => @import("wifi_remote"),
+ false => @compileError("requires: idf.py add-dependency espressif/esp_wifi_remote"),
+};
+pub const timer = @import("timer");
+pub const ledc = @import("ledc");
+pub const twai = @import("twai");
+pub const pm = @import("pm");
+pub const pthread = @import("pthread");
+pub const matter = switch (@hasDecl(sys, "HAS_ESP_MATTER")) {
+ true => @import("matter"),
+ false => @compileError("requires: idf.py add-dependency espressif/esp_matter"),
+};
+pub const wifi = switch (currentTarget) {
+ .esp32h2, .esp32h21, .esp32h4, .esp32p4 => @compileError("Wifi requires CONFIG_ESP_WIFI_ENABLED in sdkconfig"),
+ else => @import("wifi"),
+};
+pub const sys = @import("sys");
+
+const Device = enum {
+ esp32,
+ esp32s2,
+ esp32s3,
+ esp32s31,
+ esp32c2,
+ esp32c3,
+ esp32c5,
+ esp32c6,
+ esp32c61,
+ esp32h2,
+ esp32h21,
+ esp32h4,
+ esp32p4,
+};
+
+// Convert compile-time target string to enum
+pub const currentTarget = blk: {
+ const target_str = sys.CONFIG_IDF_TARGET;
+ break :blk @import("std").meta.stringToEnum(Device, target_str) orelse {
+ @compileError("Unknown ESP32 device target: " ++ target_str);
+ };
+};
+
+// Check all imports
+comptime {
+ _ = sys;
+ _ = bl;
+ if (@hasDecl(sys, "CONFIG_BT_ENABLED")) _ = bt;
+ _ = crc;
+ _ = err;
+ _ = gpio;
+ _ = heap;
+ _ = http;
+ _ = i2c;
+ _ = i2s;
+ _ = log;
+ _ = lwip;
+ _ = mqtt;
+ if (@hasDecl(sys, "HAS_ESP_HOSTED")) _ = esp_hosted;
+ _ = esp_now;
+ _ = phy;
+ _ = pulse;
+ _ = esp_panic;
+ _ = rtos;
+ if (@hasDecl(sys, "CONFIG_BT_NIMBLE_ENABLED")) _ = nimble;
+ _ = nvs;
+ _ = partition;
+ _ = sleep;
+ _ = event;
+ _ = wdt;
+ _ = segger;
+ _ = spi;
+ _ = uart;
+ _ = ver;
+ if (@hasDecl(sys, "CONFIG_ESP_WIFI_ENABLED")) _ = wifi;
+ if (@hasDecl(sys, "HAS_ESP_WIFI_REMOTE")) _ = wifi_remote;
+ if (@hasDecl(sys, "HAS_ESP_DSP")) _ = dsp;
+ if (@hasDecl(sys, "HAS_LED_STRIP")) _ = led;
+ _ = timer;
+ _ = ledc;
+ _ = twai;
+ _ = pm;
+ _ = pthread;
+ if (@hasDecl(sys, "HAS_ESP_MATTER")) _ = matter;
+}
diff --git a/software/zig_main/imports/led-strip.zig b/software/zig_main/imports/led-strip.zig
new file mode 100644
index 0000000..5319db1
--- /dev/null
+++ b/software/zig_main/imports/led-strip.zig
@@ -0,0 +1,119 @@
+// requires: idf.py add-dependency espressif/led_strip
+const sys = @import("sys");
+const errors = @import("error");
+
+pub const LedModel = enum(sys.led_model_t) {
+ ws2812 = sys.LED_MODEL_WS2812,
+ sk6812 = sys.LED_MODEL_SK6812,
+ ws2811 = sys.LED_MODEL_WS2811,
+ ws2816 = sys.LED_MODEL_WS2816,
+ invalid = sys.LED_MODEL_INVALID,
+};
+pub const LedStripHandle = sys.led_strip_handle_t;
+
+pub const ColorComponentFormat = extern union {
+ format: packed struct {
+ r_pos: u2, // Position of the red channel in the color order: 0~3
+ g_pos: u2, // Position of the green channel in the color order: 0~3
+ b_pos: u2, // Position of the blue channel in the color order: 0~3
+ w_pos: u2, // Position of the white channel in the color order: 0~3
+ reserved: u19, // Reserved
+ bytes_per_color: u2, // Bytes per color component: 1 or 2. If set to 0, it will fallback to 1
+ num_components: u3, // Number of color components per pixel: 3 or 4. If set to 0, it will fallback to 3
+ },
+ format_id: u32,
+};
+pub const PixelFormat = enum(u32) {
+ grb = 0x00, // default WS2812
+ rgb = 0x01,
+ grbw = 0x10,
+ rgbw = 0x11,
+};
+pub const LedStripConfig = extern struct {
+ strip_gpio_num: i32,
+ max_leds: u32,
+ led_model: LedModel,
+ color_component_format: ColorComponentFormat,
+ // flags: sys.struct_led_strip_extra_flags_29, // opaque
+ flags: u32 = 0, // most users leave = 0; see component for bits
+
+ pub fn init(gpio: u32, count: u32, model: LedModel, format_id: u32) LedStripConfig {
+ return .{
+ .strip_gpio_num = @intCast(gpio),
+ .max_leds = count,
+ .led_model = model,
+ .color_component_format = .{ .format_id = format_id },
+ .flags = 0,
+ };
+ }
+ pub fn ws2812(gpio: u32, count: u32) LedStripConfig {
+ return init(gpio, count, .ws2812, @intFromEnum(PixelFormat.grb));
+ }
+};
+pub const LedStripRmtConfig = struct {
+ clk_src: sys.rmt_clock_source_t,
+ resolution_hz: u32,
+ mem_block_symbols: usize = 0, // 0 = default/auto
+ // flags: sys.struct_led_strip_rmt_extra_config_33, // opaque
+ flags: u32 = 0,
+
+ pub fn init_10mhz() LedStripRmtConfig {
+ return .{
+ .clk_src = sys.RMT_CLK_SRC_DEFAULT,
+ .resolution_hz = 10_000_000,
+ .mem_block_symbols = 0,
+ .flags = 0,
+ };
+ }
+
+ // Very common setting in examples
+ pub const default = init_10mhz();
+};
+pub const LedStripSpiConfig = struct {
+ clk_src: sys.spi_clock_source_t,
+ spi_bus: sys.spi_host_device_t,
+ // flags: sys.struct_unnamed_34, // opaque
+ flags: u32 = 0,
+};
+
+// ───────────────────────────────────────────────
+// API functions
+
+/// Creates RMT-backed LED strip
+pub fn newRmtDevice(led_config: *const LedStripConfig, rmt_config: *const LedStripRmtConfig, handle: *LedStripHandle) !LedStripHandle {
+ const result = sys.led_strip_new_rmt_device(@ptrCast(led_config), @ptrCast(rmt_config), @ptrCast(handle));
+ try errors.espCheckError(result);
+ return handle.*;
+}
+
+/// Creates SPI-backed LED strip
+pub fn newSpiDevice(led_config: *const LedStripConfig, spi_config: *const LedStripSpiConfig, handle: *LedStripHandle) !LedStripHandle {
+ const result = sys.led_strip_new_spi_device(@ptrCast(led_config), @ptrCast(spi_config), @ptrCast(handle));
+ try errors.espCheckError(result);
+ return handle.*;
+}
+/// Set one RGB pixel
+pub fn setPixel(strip: LedStripHandle, index: u32, r: u8, g: u8, b: u8) !void {
+ // The C API takes u32 — we clamp/convert for safety
+ return try errors.espCheckError(sys.led_strip_set_pixel(strip, index, @as(u32, r), @as(u32, g), @as(u32, b)));
+}
+/// Variant that also sets white channel (SK6812 RGBW etc.)
+pub fn setPixelRgbw(strip: LedStripHandle, index: u32, r: u8, g: u8, b: u8, w: u8) !void {
+ return try errors.espCheckError(sys.led_strip_set_pixel_rgbw(strip, index, @as(u32, r), @as(u32, g), @as(u32, b), @as(u32, w)));
+}
+/// HSV variant (convenience — internally converts to RGB)
+pub fn setPixelHsv(strip: LedStripHandle, index: u32, hue: u16, sat: u8, val: u8) !void {
+ return try errors.espCheckError(sys.led_strip_set_pixel_hsv(strip, index, hue, sat, val));
+}
+/// Push all buffered pixel data to the strip
+pub fn refresh(strip: LedStripHandle) !void {
+ return try errors.espCheckError(sys.led_strip_refresh(strip));
+}
+/// Turn all LEDs off
+pub fn clear(strip: LedStripHandle) !void {
+ return try errors.espCheckError(sys.led_strip_clear(strip));
+}
+/// Free resources
+pub fn deinit(strip: LedStripHandle) !void {
+ return try errors.espCheckError(sys.led_strip_del(strip));
+}
diff --git a/software/zig_main/imports/ledc.zig b/software/zig_main/imports/ledc.zig
new file mode 100644
index 0000000..bc79f3e
--- /dev/null
+++ b/software/zig_main/imports/ledc.zig
@@ -0,0 +1,218 @@
+const sys = @import("sys");
+const errors = @import("error");
+
+// ---------------------------------------------------------------------------
+// Type aliases & enums
+// ---------------------------------------------------------------------------
+
+pub const ChannelConfig = sys.ledc_channel_config_t;
+pub const TimerConfig = sys.ledc_timer_config_t;
+pub const IsrHandle = sys.ledc_isr_handle_t;
+pub const CbParam = sys.ledc_cb_param_t;
+pub const Cbs = sys.ledc_cbs_t;
+
+/// Speed mode. Most modern ESP32 variants only support LOW_SPEED_MODE.
+pub const SpeedMode = enum(sys.ledc_mode_t) {
+ low = sys.LEDC_LOW_SPEED_MODE,
+};
+
+pub const IntrType = enum(sys.ledc_intr_type_t) {
+ disable = sys.LEDC_INTR_DISABLE,
+ fade_end = sys.LEDC_INTR_FADE_END,
+};
+
+pub const DutyDir = enum(sys.ledc_duty_direction_t) {
+ decrease = sys.LEDC_DUTY_DIR_DECREASE,
+ increase = sys.LEDC_DUTY_DIR_INCREASE,
+};
+
+pub const Timer = enum(sys.ledc_timer_t) {
+ @"0" = sys.LEDC_TIMER_0,
+ @"1" = sys.LEDC_TIMER_1,
+ @"2" = sys.LEDC_TIMER_2,
+ @"3" = sys.LEDC_TIMER_3,
+};
+
+pub const Channel = enum(sys.ledc_channel_t) {
+ @"0" = sys.LEDC_CHANNEL_0,
+ @"1" = sys.LEDC_CHANNEL_1,
+ @"2" = sys.LEDC_CHANNEL_2,
+ @"3" = sys.LEDC_CHANNEL_3,
+ @"4" = sys.LEDC_CHANNEL_4,
+ @"5" = sys.LEDC_CHANNEL_5,
+};
+
+pub const TimerBit = enum(sys.ledc_timer_bit_t) {
+ @"1" = sys.LEDC_TIMER_1_BIT,
+ @"2" = sys.LEDC_TIMER_2_BIT,
+ @"3" = sys.LEDC_TIMER_3_BIT,
+ @"4" = sys.LEDC_TIMER_4_BIT,
+ @"5" = sys.LEDC_TIMER_5_BIT,
+ @"6" = sys.LEDC_TIMER_6_BIT,
+ @"7" = sys.LEDC_TIMER_7_BIT,
+ @"8" = sys.LEDC_TIMER_8_BIT,
+ @"9" = sys.LEDC_TIMER_9_BIT,
+ @"10" = sys.LEDC_TIMER_10_BIT,
+ @"11" = sys.LEDC_TIMER_11_BIT,
+ @"12" = sys.LEDC_TIMER_12_BIT,
+ @"13" = sys.LEDC_TIMER_13_BIT,
+ @"14" = sys.LEDC_TIMER_14_BIT,
+};
+
+pub const FadeMode = enum(sys.ledc_fade_mode_t) {
+ no_wait = sys.LEDC_FADE_NO_WAIT,
+ wait_done = sys.LEDC_FADE_WAIT_DONE,
+};
+
+// ---------------------------------------------------------------------------
+// Timer configuration and control
+// ---------------------------------------------------------------------------
+
+pub const TimerCtrl = struct {
+ /// Configure a LEDC timer (frequency + duty resolution).
+ pub fn config(cfg: *const TimerConfig) !void {
+ try errors.espCheckError(sys.ledc_timer_config(cfg));
+ }
+
+ /// Reset a LEDC timer.
+ pub fn reset(mode: SpeedMode, timer: Timer) !void {
+ try errors.espCheckError(sys.ledc_timer_rst(@intFromEnum(mode), @intFromEnum(timer)));
+ }
+
+ /// Pause a LEDC timer.
+ pub fn pause(mode: SpeedMode, timer: Timer) !void {
+ try errors.espCheckError(sys.ledc_timer_pause(@intFromEnum(mode), @intFromEnum(timer)));
+ }
+
+ /// Resume a paused LEDC timer.
+ pub fn @"resume"(mode: SpeedMode, timer: Timer) !void {
+ try errors.espCheckError(sys.ledc_timer_resume(@intFromEnum(mode), @intFromEnum(timer)));
+ }
+
+ /// Set the frequency (Hz) of a running timer.
+ pub fn setFreq(mode: SpeedMode, timer: Timer, freq_hz: u32) !void {
+ try errors.espCheckError(sys.ledc_set_freq(@intFromEnum(mode), @intFromEnum(timer), freq_hz));
+ }
+
+ /// Get the current frequency (Hz) of a timer.
+ pub fn getFreq(mode: SpeedMode, timer: Timer) u32 {
+ return sys.ledc_get_freq(@intFromEnum(mode), @intFromEnum(timer));
+ }
+
+ /// Return the best duty resolution for a given source clock and target frequency.
+ pub fn findSuitableDutyResolution(src_clk_freq: u32, timer_freq: u32) u32 {
+ return sys.ledc_find_suitable_duty_resolution(src_clk_freq, timer_freq);
+ }
+};
+
+// ---------------------------------------------------------------------------
+// Channel configuration and control
+// ---------------------------------------------------------------------------
+
+pub const ChannelCtrl = struct {
+ /// Configure a LEDC output channel.
+ pub fn config(cfg: *const ChannelConfig) !void {
+ try errors.espCheckError(sys.ledc_channel_config(cfg));
+ }
+
+ /// Re-assign a GPIO to an existing channel (without full reconfiguration).
+ pub fn setPin(gpio_num: c_int, mode: SpeedMode, ch: Channel) !void {
+ try errors.espCheckError(sys.ledc_set_pin(gpio_num, @intFromEnum(mode), @intFromEnum(ch)));
+ }
+
+ /// Stop the channel output and set the GPIO to `idle_level` (0 or 1).
+ pub fn stop(mode: SpeedMode, ch: Channel, idle_level: u32) !void {
+ try errors.espCheckError(sys.ledc_stop(@intFromEnum(mode), @intFromEnum(ch), idle_level));
+ }
+
+ /// Bind a channel to a different timer.
+ pub fn bindTimer(mode: SpeedMode, ch: Channel, timer: Timer) !void {
+ try errors.espCheckError(sys.ledc_bind_channel_timer(@intFromEnum(mode), @intFromEnum(ch), @intFromEnum(timer)));
+ }
+
+ // ── Duty ──────────────────────────────────────────────────────────────
+
+ /// Set the duty cycle (without updating the hardware — call `updateDuty` after).
+ pub fn setDuty(mode: SpeedMode, ch: Channel, duty: u32) !void {
+ try errors.espCheckError(sys.ledc_set_duty(@intFromEnum(mode), @intFromEnum(ch), duty));
+ }
+
+ /// Set duty + hpoint (without updating hardware).
+ pub fn setDutyWithHpoint(mode: SpeedMode, ch: Channel, duty: u32, hpoint: u32) !void {
+ try errors.espCheckError(sys.ledc_set_duty_with_hpoint(@intFromEnum(mode), @intFromEnum(ch), duty, hpoint));
+ }
+
+ /// Latch the duty value set by `setDuty`/`setDutyWithHpoint` into hardware.
+ pub fn updateDuty(mode: SpeedMode, ch: Channel) !void {
+ try errors.espCheckError(sys.ledc_update_duty(@intFromEnum(mode), @intFromEnum(ch)));
+ }
+
+ /// Set duty and immediately apply it to hardware (combines set + update).
+ pub fn setDutyAndUpdate(mode: SpeedMode, ch: Channel, duty: u32, hpoint: u32) !void {
+ try errors.espCheckError(sys.ledc_set_duty_and_update(@intFromEnum(mode), @intFromEnum(ch), duty, hpoint));
+ }
+
+ /// Get the current duty cycle value.
+ pub fn getDuty(mode: SpeedMode, ch: Channel) u32 {
+ return sys.ledc_get_duty(@intFromEnum(mode), @intFromEnum(ch));
+ }
+
+ /// Get the current hpoint value.
+ pub fn getHpoint(mode: SpeedMode, ch: Channel) c_int {
+ return sys.ledc_get_hpoint(@intFromEnum(mode), @intFromEnum(ch));
+ }
+
+ // ── Callback ──────────────────────────────────────────────────────────
+
+ /// Register a callback for fade-end events on a channel.
+ pub fn registerCallback(mode: SpeedMode, ch: Channel, cbs: *Cbs, user_arg: ?*anyopaque) !void {
+ try errors.espCheckError(sys.ledc_cb_register(@intFromEnum(mode), @intFromEnum(ch), cbs, user_arg));
+ }
+};
+
+// ---------------------------------------------------------------------------
+// Fade functions
+// ---------------------------------------------------------------------------
+
+pub const Fade = struct {
+ /// Install the fade ISR service. Call once before using any fade functions.
+ /// `intr_alloc_flags`: interrupt allocation flags (e.g. ESP_INTR_FLAG_IRAM).
+ pub fn install(intr_alloc_flags: c_int) !void {
+ try errors.espCheckError(sys.ledc_fade_func_install(intr_alloc_flags));
+ }
+
+ /// Uninstall the fade ISR service.
+ pub fn uninstall() void {
+ sys.ledc_fade_func_uninstall();
+ }
+
+ /// Start a fade previously configured with `setFadeStep` or `setFadeTime`.
+ pub fn start(mode: SpeedMode, ch: Channel, fade_mode: FadeMode) !void {
+ try errors.espCheckError(sys.ledc_fade_start(@intFromEnum(mode), @intFromEnum(ch), @intFromEnum(fade_mode)));
+ }
+
+ /// Stop an in-progress fade.
+ pub fn stop(mode: SpeedMode, ch: Channel) !void {
+ try errors.espCheckError(sys.ledc_fade_stop(@intFromEnum(mode), @intFromEnum(ch)));
+ }
+
+ /// Configure a fade by time: ramp from current duty to `target_duty` in `fade_time_ms`.
+ pub fn setFadeTime(mode: SpeedMode, ch: Channel, target_duty: u32, fade_time_ms: c_int) !void {
+ try errors.espCheckError(sys.ledc_set_fade_with_time(@intFromEnum(mode), @intFromEnum(ch), target_duty, fade_time_ms));
+ }
+
+ /// Configure a fade by step: ramp with a fixed `scale` increment every `cycle_num` PWM cycles.
+ pub fn setFadeStep(mode: SpeedMode, ch: Channel, target_duty: u32, scale: u32, cycle_num: u32) !void {
+ try errors.espCheckError(sys.ledc_set_fade_with_step(@intFromEnum(mode), @intFromEnum(ch), target_duty, scale, cycle_num));
+ }
+
+ /// Set fade time and immediately start it.
+ pub fn setFadeTimeAndStart(mode: SpeedMode, ch: Channel, target_duty: u32, fade_time_ms: u32, fade_mode: FadeMode) !void {
+ try errors.espCheckError(sys.ledc_set_fade_time_and_start(@intFromEnum(mode), @intFromEnum(ch), target_duty, fade_time_ms, @intFromEnum(fade_mode)));
+ }
+
+ /// Set fade step and immediately start it.
+ pub fn setFadeStepAndStart(mode: SpeedMode, ch: Channel, target_duty: u32, scale: u32, cycle_num: u32, fade_mode: FadeMode) !void {
+ try errors.espCheckError(sys.ledc_set_fade_step_and_start(@intFromEnum(mode), @intFromEnum(ch), target_duty, scale, cycle_num, @intFromEnum(fade_mode)));
+ }
+};
diff --git a/software/zig_main/imports/logger.zig b/software/zig_main/imports/logger.zig
new file mode 100644
index 0000000..a5c60d5
--- /dev/null
+++ b/software/zig_main/imports/logger.zig
@@ -0,0 +1,102 @@
+const std = @import("std");
+const sys = @import("sys");
+
+pub const default_log_scope = .espressif;
+
+pub fn espLogFn(
+ comptime level: std.log.Level,
+ comptime scope: @TypeOf(.EnumLiteral),
+ comptime format: []const u8,
+ args: anytype,
+) void {
+ const esp_level = comptime levelToEsp(level);
+ const color = comptime levelColor(level);
+ const prefix = color ++ "[" ++ comptime level.asText() ++ "] (" ++ @tagName(scope) ++ "): ";
+
+ var heap = std.heap.ArenaAllocator.init(std.heap.c_allocator);
+ defer heap.deinit();
+
+ ESP_LOG(heap.allocator(), esp_level, "logging", prefix ++ format ++ LOG_RESET_COLOR ++ "\n", args);
+}
+
+// ---------------------------------------------------------------------------
+// Level mapping
+// ---------------------------------------------------------------------------
+
+pub const default_level: sys.esp_log_level_t = switch (@import("builtin").mode) {
+ .Debug => sys.ESP_LOG_DEBUG,
+ .ReleaseSafe => sys.ESP_LOG_INFO,
+ .ReleaseFast, .ReleaseSmall => sys.ESP_LOG_ERROR,
+};
+
+/// Converts a Zig log level to its ESP-IDF equivalent.
+pub fn levelToEsp(comptime level: std.log.Level) sys.esp_log_level_t {
+ return switch (level) {
+ .err => sys.ESP_LOG_ERROR,
+ .warn => sys.ESP_LOG_WARN,
+ .info => sys.ESP_LOG_INFO,
+ .debug => sys.ESP_LOG_DEBUG,
+ };
+}
+
+/// Returns the ANSI color escape for a given Zig log level.
+pub fn levelColor(comptime level: std.log.Level) []const u8 {
+ return switch (level) {
+ .err => LOG_COLOR_E,
+ .warn => LOG_COLOR_W,
+ .info => LOG_COLOR_I,
+ .debug => LOG_COLOR(LOG_COLOR_BLUE),
+ };
+}
+
+// ---------------------------------------------------------------------------
+// Core log primitive
+// ---------------------------------------------------------------------------
+
+pub fn ESP_LOG(
+ allocator: std.mem.Allocator,
+ level: sys.esp_log_level_t,
+ comptime tag: [*:0]const u8,
+ comptime fmt: []const u8,
+ args: anytype,
+) void {
+ if (isComptime(args)) {
+ const buffer: [:0]const u8 = std.fmt.comptimePrint(fmt, args);
+ sys.esp_log_write(level, tag, "%s", buffer.ptr);
+ } else {
+ const buffer: [:0]u8 = std.fmt.allocPrintSentinel(allocator, fmt, args, 0) catch return;
+ sys.esp_log_write(level, tag, "%s", buffer.ptr);
+ }
+}
+
+// ---------------------------------------------------------------------------
+// ANSI color helpers
+// ---------------------------------------------------------------------------
+
+pub const LOG_COLOR_BLACK = "30";
+pub const LOG_COLOR_RED = "31";
+pub const LOG_COLOR_GREEN = "32";
+pub const LOG_COLOR_BROWN = "33";
+pub const LOG_COLOR_BLUE = "34";
+pub const LOG_COLOR_PURPLE = "35";
+pub const LOG_COLOR_CYAN = "36";
+
+pub inline fn LOG_COLOR(comptime COLOR: []const u8) []const u8 {
+ return "\x1b[0;" ++ COLOR ++ "m";
+}
+pub inline fn LOG_BOLD(comptime COLOR: []const u8) []const u8 {
+ return "\x1b[1;" ++ COLOR ++ "m";
+}
+
+pub const LOG_RESET_COLOR = "\x1b[0m";
+pub const LOG_COLOR_E = LOG_COLOR(LOG_COLOR_RED);
+pub const LOG_COLOR_W = LOG_COLOR(LOG_COLOR_BROWN);
+pub const LOG_COLOR_I = LOG_COLOR(LOG_COLOR_GREEN);
+
+// ---------------------------------------------------------------------------
+// Internal helpers
+// ---------------------------------------------------------------------------
+
+inline fn isComptime(val: anytype) bool {
+ return @typeInfo(@TypeOf(.{val})).@"struct".fields[0].is_comptime;
+}
diff --git a/software/zig_main/imports/lwip.zig b/software/zig_main/imports/lwip.zig
new file mode 100644
index 0000000..7faa646
--- /dev/null
+++ b/software/zig_main/imports/lwip.zig
@@ -0,0 +1,771 @@
+const std = @import("std");
+const sys = @import("sys");
+
+// ---------------------------------------------------------------------------
+// lwIP error type — maps err_t (i8) to Zig errors.
+// ---------------------------------------------------------------------------
+
+pub const LwipError = error{
+ OutOfMemory,
+ BufferError,
+ Timeout,
+ RoutingError,
+ InProgress,
+ IllegalValue,
+ WouldBlock,
+ AddressInUse,
+ AlreadyConnecting,
+ AlreadyConnected,
+ NotConnected,
+ InterfaceError,
+ Aborted,
+ Reset,
+ Closed,
+ IllegalArgument,
+};
+
+pub fn errFromC(e: sys.err_t) LwipError!void {
+ return switch (@as(sys.err_enum_t, @enumFromInt(e))) {
+ .ERR_OK => {},
+ .ERR_MEM => LwipError.OutOfMemory,
+ .ERR_BUF => LwipError.BufferError,
+ .ERR_TIMEOUT => LwipError.Timeout,
+ .ERR_RTE => LwipError.RoutingError,
+ .ERR_INPROGRESS => LwipError.InProgress,
+ .ERR_VAL => LwipError.IllegalValue,
+ .ERR_WOULDBLOCK => LwipError.WouldBlock,
+ .ERR_USE => LwipError.AddressInUse,
+ .ERR_ALREADY => LwipError.AlreadyConnecting,
+ .ERR_ISCONN => LwipError.AlreadyConnected,
+ .ERR_CONN => LwipError.NotConnected,
+ .ERR_IF => LwipError.InterfaceError,
+ .ERR_ABRT => LwipError.Aborted,
+ .ERR_RST => LwipError.Reset,
+ .ERR_CLSD => LwipError.Closed,
+ .ERR_ARG => LwipError.IllegalArgument,
+ };
+}
+
+// ---------------------------------------------------------------------------
+// Re-export raw types callers may need.
+// ---------------------------------------------------------------------------
+
+pub const RawNetif = sys.netif;
+pub const RawPbuf = sys.pbuf;
+pub const RawIp4Addr = sys.ip4_addr_t;
+pub const RawIp6Addr = sys.ip6_addr_t;
+pub const RawIpAddr = sys.ip_addr_t;
+pub const RawSockAddr = sys.sockaddr;
+pub const SockLen = sys.socklen_t;
+pub const AddrFamily = sys.sa_family_t;
+pub const MsgHdr = sys.msghdr;
+pub const AddrInfo = sys.addrinfo;
+pub const HostEnt = sys.hostent;
+pub const PollFd = sys.pollfd;
+pub const NFds = sys.nfds_t;
+pub const IoVec = sys.iovec;
+pub const InAddr = sys.in_addr;
+pub const In6Addr = sys.in6_addr;
+pub const SockAddrIn = sys.sockaddr_in;
+pub const SockAddrIn6 = sys.sockaddr_in6;
+pub const SockAddrStore = sys.sockaddr_storage;
+pub const IpMreq = sys.ip_mreq;
+pub const Ipv6Mreq = sys.ipv6_mreq;
+pub const Linger = sys.linger;
+pub const InPktInfo = sys.in_pktinfo;
+pub const IpAddrType = sys.enum_lwip_ip_addr_type;
+pub const NetifMacFilterAction = sys.enum_netif_mac_filter_action;
+pub const PbufLayer = sys.pbuf_layer;
+pub const PbufType = sys.pbuf_type;
+pub const MempType = sys.memp_t;
+pub const DnsCb = sys.dns_found_callback;
+pub const NetifExtCbFn = sys.netif_ext_callback_fn;
+pub const NetifNscReason = sys.netif_nsc_reason_t;
+pub const SysThreadCoreLock = sys.sys_thread_core_lock_t;
+pub const LwipSock = sys.lwip_sock;
+
+// ---------------------------------------------------------------------------
+// IP address helpers
+// ---------------------------------------------------------------------------
+
+pub const Ip4Addr = struct {
+ raw: RawIp4Addr,
+
+ pub fn fromString(s: [*:0]const u8) ?Ip4Addr {
+ var a: RawIp4Addr = undefined;
+ if (sys.ip4addr_aton(s, &a) == 0) return null;
+ return .{ .raw = a };
+ }
+
+ pub fn toString(self: *const Ip4Addr, buf: []u8) ?[]const u8 {
+ const p = sys.ip4addr_ntoa_r(&self.raw, buf.ptr, @intCast(buf.len));
+ if (p == null) return null;
+ return std.mem.sliceTo(p, 0);
+ }
+
+ pub fn isbroadcast(self: Ip4Addr, netif: *const RawNetif) bool {
+ return sys.ip4_addr_isbroadcast_u32(self.raw.addr, netif) != 0;
+ }
+
+ pub fn netmaskValid(self: Ip4Addr) bool {
+ return sys.ip4_addr_netmask_valid(self.raw.addr) != 0;
+ }
+};
+
+pub const Ip6Addr = struct {
+ raw: RawIp6Addr,
+
+ pub fn fromString(s: [*:0]const u8) ?Ip6Addr {
+ var a: RawIp6Addr = undefined;
+ if (sys.ip6addr_aton(s, &a) == 0) return null;
+ return .{ .raw = a };
+ }
+
+ pub fn toString(self: *const Ip6Addr, buf: []u8) ?[]const u8 {
+ const p = sys.ip6addr_ntoa_r(&self.raw, buf.ptr, @intCast(buf.len));
+ if (p == null) return null;
+ return std.mem.sliceTo(p, 0);
+ }
+};
+
+pub const IpAddr = struct {
+ raw: RawIpAddr,
+
+ pub fn fromString(s: [*:0]const u8) ?IpAddr {
+ var a: RawIpAddr = undefined;
+ if (sys.ipaddr_aton(s, &a) == 0) return null;
+ return .{ .raw = a };
+ }
+
+ pub fn toString(self: *const IpAddr, buf: []u8) ?[]const u8 {
+ const p = sys.ipaddr_ntoa_r(&self.raw, buf.ptr, @intCast(buf.len));
+ if (p == null) return null;
+ return std.mem.sliceTo(p, 0);
+ }
+};
+
+// ---------------------------------------------------------------------------
+// Socket API
+// ---------------------------------------------------------------------------
+
+/// A file descriptor returned by socket(). Negative values are invalid.
+pub const Socket = struct {
+ fd: c_int,
+
+ pub fn isValid(self: Socket) bool {
+ return self.fd >= 0;
+ }
+
+ // -- Lifecycle ----------------------------------------------------------
+
+ pub fn create(domain: c_int, kind: c_int, protocol: c_int) !Socket {
+ const fd = sys.lwip_socket(domain, kind, protocol);
+ if (fd < 0) return LwipError.InterfaceError;
+ return .{ .fd = fd };
+ }
+
+ pub fn close(self: Socket) !void {
+ if (sys.lwip_close(self.fd) != 0) return LwipError.InterfaceError;
+ }
+
+ pub fn shutdown(self: Socket, how: c_int) !void {
+ if (sys.lwip_shutdown(self.fd, how) != 0) return LwipError.InterfaceError;
+ }
+
+ // -- Address binding / connection ---------------------------------------
+
+ pub fn bind(self: Socket, addr: *const RawSockAddr, len: SockLen) !void {
+ if (sys.lwip_bind(self.fd, addr, len) != 0) return LwipError.AddressInUse;
+ }
+
+ pub fn listen(self: Socket, backlog: c_int) !void {
+ if (sys.lwip_listen(self.fd, backlog) != 0) return LwipError.InterfaceError;
+ }
+
+ pub fn accept(self: Socket, addr: ?*RawSockAddr, len: ?*SockLen) !Socket {
+ const fd = sys.lwip_accept(self.fd, addr, len);
+ if (fd < 0) return LwipError.NotConnected;
+ return .{ .fd = fd };
+ }
+
+ pub fn connect(self: Socket, addr: *const RawSockAddr, len: SockLen) !void {
+ if (sys.lwip_connect(self.fd, addr, len) != 0) return LwipError.NotConnected;
+ }
+
+ // -- Send / Receive -----------------------------------------------------
+
+ pub fn send(self: Socket, data: []const u8, flags: c_int) !usize {
+ const n = sys.lwip_send(self.fd, data.ptr, data.len, flags);
+ if (n < 0) return LwipError.InterfaceError;
+ return @intCast(n);
+ }
+
+ pub fn sendTo(
+ self: Socket,
+ data: []const u8,
+ flags: c_int,
+ to: *const RawSockAddr,
+ tolen: SockLen,
+ ) !usize {
+ const n = sys.lwip_sendto(self.fd, data.ptr, data.len, flags, to, tolen);
+ if (n < 0) return LwipError.InterfaceError;
+ return @intCast(n);
+ }
+
+ pub fn sendMsg(self: Socket, msg: *const MsgHdr, flags: c_int) !usize {
+ const n = sys.lwip_sendmsg(self.fd, msg, flags);
+ if (n < 0) return LwipError.InterfaceError;
+ return @intCast(n);
+ }
+
+ pub fn recv(self: Socket, buf: []u8, flags: c_int) !usize {
+ const n = sys.lwip_recv(self.fd, buf.ptr, buf.len, flags);
+ if (n < 0) return LwipError.InterfaceError;
+ return @intCast(n);
+ }
+
+ pub fn recvFrom(
+ self: Socket,
+ buf: []u8,
+ flags: c_int,
+ from: ?*RawSockAddr,
+ fromlen: ?*SockLen,
+ ) !usize {
+ const n = sys.lwip_recvfrom(self.fd, buf.ptr, buf.len, flags, from, fromlen);
+ if (n < 0) return LwipError.InterfaceError;
+ return @intCast(n);
+ }
+
+ pub fn recvMsg(self: Socket, msg: *MsgHdr, flags: c_int) !usize {
+ const n = sys.lwip_recvmsg(self.fd, msg, flags);
+ if (n < 0) return LwipError.InterfaceError;
+ return @intCast(n);
+ }
+
+ pub fn read(self: Socket, buf: []u8) !usize {
+ const n = sys.lwip_read(self.fd, buf.ptr, buf.len);
+ if (n < 0) return LwipError.InterfaceError;
+ return @intCast(n);
+ }
+
+ pub fn write(self: Socket, data: []const u8) !usize {
+ const n = sys.lwip_write(self.fd, data.ptr, data.len);
+ if (n < 0) return LwipError.InterfaceError;
+ return @intCast(n);
+ }
+
+ pub fn readv(self: Socket, iov: []const IoVec) !usize {
+ const n = sys.lwip_readv(self.fd, iov.ptr, @intCast(iov.len));
+ if (n < 0) return LwipError.InterfaceError;
+ return @intCast(n);
+ }
+
+ pub fn writev(self: Socket, iov: []const IoVec) !usize {
+ const n = sys.lwip_writev(self.fd, iov.ptr, @intCast(iov.len));
+ if (n < 0) return LwipError.InterfaceError;
+ return @intCast(n);
+ }
+
+ // -- Socket options -----------------------------------------------------
+
+ pub fn setOpt(
+ self: Socket,
+ level: c_int,
+ optname: c_int,
+ optval: *const anyopaque,
+ optlen: SockLen,
+ ) !void {
+ if (sys.lwip_setsockopt(self.fd, level, optname, optval, optlen) != 0)
+ return LwipError.IllegalArgument;
+ }
+
+ pub fn getOpt(
+ self: Socket,
+ level: c_int,
+ optname: c_int,
+ optval: *anyopaque,
+ optlen: *SockLen,
+ ) !void {
+ if (sys.lwip_getsockopt(self.fd, level, optname, optval, optlen) != 0)
+ return LwipError.IllegalArgument;
+ }
+
+ pub fn setOptExt(
+ self: *LwipSock,
+ level: c_int,
+ optname: c_int,
+ optval: *const anyopaque,
+ optlen: u32,
+ ) !void {
+ var err_code: c_int = 0;
+ if (!sys.lwip_setsockopt_impl_ext(self, level, optname, optval, optlen, &err_code))
+ return LwipError.IllegalArgument;
+ }
+
+ pub fn getOptExt(
+ self: *LwipSock,
+ level: c_int,
+ optname: c_int,
+ optval: *anyopaque,
+ optlen: *u32,
+ ) !void {
+ var err_code: c_int = 0;
+ if (!sys.lwip_getsockopt_impl_ext(self, level, optname, optval, optlen, &err_code))
+ return LwipError.IllegalArgument;
+ }
+
+ // -- Name queries -------------------------------------------------------
+
+ pub fn getPeerName(self: Socket, addr: *RawSockAddr, len: *SockLen) !void {
+ if (sys.lwip_getpeername(self.fd, addr, len) != 0) return LwipError.NotConnected;
+ }
+
+ pub fn getSockName(self: Socket, addr: *RawSockAddr, len: *SockLen) !void {
+ if (sys.lwip_getsockname(self.fd, addr, len) != 0) return LwipError.InterfaceError;
+ }
+
+ // -- Misc ---------------------------------------------------------------
+
+ pub fn ioctl(self: Socket, cmd: c_long, argp: ?*anyopaque) !void {
+ if (sys.lwip_ioctl(self.fd, cmd, argp) != 0) return LwipError.IllegalArgument;
+ }
+
+ pub fn fcntl(self: Socket, cmd: c_int, val: c_int) !c_int {
+ const r = sys.lwip_fcntl(self.fd, cmd, val);
+ if (r < 0) return LwipError.IllegalArgument;
+ return r;
+ }
+};
+
+/// poll() over a set of sockets.
+pub fn poll(fds: []PollFd, timeout_ms: c_int) !c_int {
+ const r = sys.lwip_poll(fds.ptr, @intCast(fds.len), timeout_ms);
+ if (r < 0) return LwipError.InterfaceError;
+ return r;
+}
+
+// ---------------------------------------------------------------------------
+// Address conversion
+// ---------------------------------------------------------------------------
+
+pub fn inetNtop(af: c_int, src: *const anyopaque, dst: []u8) ?[*:0]const u8 {
+ return sys.lwip_inet_ntop(af, src, dst.ptr, @intCast(dst.len));
+}
+
+pub fn inetPton(af: c_int, src: [*:0]const u8, dst: *anyopaque) bool {
+ return sys.lwip_inet_pton(af, src, dst) == 1;
+}
+
+// ---------------------------------------------------------------------------
+// DNS
+// ---------------------------------------------------------------------------
+
+pub const Dns = struct {
+ pub fn init() void {
+ sys.dns_init();
+ }
+
+ pub fn setServer(index: u8, server: *const RawIpAddr) void {
+ sys.dns_setserver(index, server);
+ }
+
+ pub fn getServer(index: u8) *const RawIpAddr {
+ return sys.dns_getserver(index);
+ }
+
+ pub fn getHostByName(
+ hostname: [*:0]const u8,
+ addr: *RawIpAddr,
+ callback: DnsCb,
+ callback_arg: ?*anyopaque,
+ ) !void {
+ try errFromC(sys.dns_gethostbyname(hostname, addr, callback, callback_arg));
+ }
+
+ pub fn getHostByNameAddrType(
+ hostname: [*:0]const u8,
+ addr: *RawIpAddr,
+ callback: DnsCb,
+ callback_arg: ?*anyopaque,
+ addr_type: u8,
+ ) !void {
+ try errFromC(sys.dns_gethostbyname_addrtype(hostname, addr, callback, callback_arg, addr_type));
+ }
+
+ pub fn clearCache() void {
+ sys.dns_clear_cache();
+ }
+
+ /// Blocking getaddrinfo (uses lwip_getaddrinfo).
+ pub fn getAddrInfo(
+ nodename: [*:0]const u8,
+ servname: [*:0]const u8,
+ hints: ?*const AddrInfo,
+ res: *[*c]AddrInfo,
+ ) !void {
+ if (sys.lwip_getaddrinfo(nodename, servname, hints, res) != 0)
+ return LwipError.NotConnected;
+ }
+
+ pub fn freeAddrInfo(ai: *AddrInfo) void {
+ sys.lwip_freeaddrinfo(ai);
+ }
+
+ pub fn getHostByNameR(
+ name: [*:0]const u8,
+ ret: *HostEnt,
+ buf: []u8,
+ result: *[*c]HostEnt,
+ h_errnop: *c_int,
+ ) !void {
+ if (sys.lwip_gethostbyname_r(name, ret, buf.ptr, buf.len, result, h_errnop) != 0)
+ return LwipError.NotConnected;
+ }
+};
+
+// ---------------------------------------------------------------------------
+// Network interface (netif)
+// ---------------------------------------------------------------------------
+
+pub const Netif = struct {
+ pub fn init() void {
+ sys.netif_init();
+ }
+
+ pub fn add(
+ netif_ptr: *RawNetif,
+ ipaddr: ?*const RawIp4Addr,
+ netmask: ?*const RawIp4Addr,
+ gw: ?*const RawIp4Addr,
+ state: ?*anyopaque,
+ init_fn: sys.netif_init_fn,
+ input_fn: sys.netif_input_fn,
+ ) ?*RawNetif {
+ return sys.netif_add(netif_ptr, ipaddr, netmask, gw, state, init_fn, input_fn);
+ }
+
+ pub fn addNoAddr(
+ netif_ptr: *RawNetif,
+ state: ?*anyopaque,
+ init_fn: sys.netif_init_fn,
+ input_fn: sys.netif_input_fn,
+ ) ?*RawNetif {
+ return sys.netif_add_noaddr(netif_ptr, state, init_fn, input_fn);
+ }
+
+ pub fn remove(netif_ptr: *RawNetif) void {
+ sys.netif_remove(netif_ptr);
+ }
+
+ pub fn find(name: [*:0]const u8) ?*RawNetif {
+ return sys.netif_find(name);
+ }
+
+ pub fn setDefault(netif_ptr: *RawNetif) void {
+ sys.netif_set_default(netif_ptr);
+ }
+
+ pub fn setAddr(
+ netif_ptr: *RawNetif,
+ ipaddr: *const RawIp4Addr,
+ netmask: *const RawIp4Addr,
+ gw: *const RawIp4Addr,
+ ) void {
+ sys.netif_set_addr(netif_ptr, ipaddr, netmask, gw);
+ }
+
+ pub fn setUp(netif_ptr: *RawNetif) void {
+ sys.netif_set_up(netif_ptr);
+ }
+ pub fn setDown(netif_ptr: *RawNetif) void {
+ sys.netif_set_down(netif_ptr);
+ }
+ pub fn setLinkUp(netif_ptr: *RawNetif) void {
+ sys.netif_set_link_up(netif_ptr);
+ }
+ pub fn setLinkDown(netif_ptr: *RawNetif) void {
+ sys.netif_set_link_down(netif_ptr);
+ }
+
+ pub fn ip6AddrSet(netif_ptr: *RawNetif, idx: i8, addr: *const RawIp6Addr) void {
+ sys.netif_ip6_addr_set(netif_ptr, idx, addr);
+ }
+
+ pub fn ip6AddrSetState(netif_ptr: *RawNetif, idx: i8, state: u8) void {
+ sys.netif_ip6_addr_set_state(netif_ptr, idx, state);
+ }
+
+ pub fn createIp6Linklocal(netif_ptr: *RawNetif, from_mac_48bit: bool) void {
+ sys.netif_create_ip6_linklocal_address(netif_ptr, @intFromBool(from_mac_48bit));
+ }
+
+ pub fn addIp6Address(netif_ptr: *RawNetif, addr: *const RawIp6Addr, chosen_idx: *i8) !void {
+ try errFromC(sys.netif_add_ip6_address(netif_ptr, addr, chosen_idx));
+ }
+
+ pub fn nameToIndex(name: [*:0]const u8) u8 {
+ return sys.netif_name_to_index(name);
+ }
+
+ pub fn getByIndex(idx: u8) ?*RawNetif {
+ return sys.netif_get_by_index(idx);
+ }
+
+ pub fn addExtCallback(cb: *sys.netif_ext_callback_t, func: NetifExtCbFn) void {
+ sys.netif_add_ext_callback(cb, func);
+ }
+
+ pub fn removeExtCallback(cb: *sys.netif_ext_callback_t) void {
+ sys.netif_remove_ext_callback(cb);
+ }
+
+ pub fn input(p: *RawPbuf, inp: *RawNetif) !void {
+ try errFromC(sys.netif_input(p, inp));
+ }
+
+ pub fn loopOutput(netif_ptr: *RawNetif, p: *RawPbuf) !void {
+ try errFromC(sys.netif_loop_output(netif_ptr, p));
+ }
+
+ pub fn poll(netif_ptr: *RawNetif) void {
+ sys.netif_poll(netif_ptr);
+ }
+};
+
+// ---------------------------------------------------------------------------
+// Packet buffer (pbuf)
+// ---------------------------------------------------------------------------
+
+pub const Pbuf = struct {
+ pub fn alloc(layer: PbufLayer, length: u16, kind: PbufType) ?*RawPbuf {
+ return sys.pbuf_alloc(layer, length, kind);
+ }
+
+ pub fn free(p: *RawPbuf) u8 {
+ return sys.pbuf_free(p);
+ }
+
+ pub fn ref(p: *RawPbuf) void {
+ sys.pbuf_ref(p);
+ }
+
+ pub fn chain(head: *RawPbuf, tail: *RawPbuf) void {
+ sys.pbuf_chain(head, tail);
+ }
+
+ pub fn cat(head: *RawPbuf, tail: *RawPbuf) void {
+ sys.pbuf_cat(head, tail);
+ }
+
+ pub fn dechain(p: *RawPbuf) ?*RawPbuf {
+ return sys.pbuf_dechain(p);
+ }
+
+ pub fn copy(dst: *RawPbuf, src: *const RawPbuf) !void {
+ try errFromC(sys.pbuf_copy(dst, src));
+ }
+
+ pub fn copyPartial(p: *const RawPbuf, dst: []u8, offset: u16) u16 {
+ return sys.pbuf_copy_partial(p, dst.ptr, @intCast(dst.len), offset);
+ }
+
+ pub fn take(buf: *RawPbuf, data: []const u8) !void {
+ try errFromC(sys.pbuf_take(buf, data.ptr, @intCast(data.len)));
+ }
+
+ pub fn coalesce(p: *RawPbuf, layer: PbufLayer) ?*RawPbuf {
+ return sys.pbuf_coalesce(p, layer);
+ }
+
+ pub fn getAt(p: *const RawPbuf, offset: u16) u8 {
+ return sys.pbuf_get_at(p, offset);
+ }
+
+ pub fn putAt(p: *RawPbuf, offset: u16, data: u8) void {
+ sys.pbuf_put_at(p, offset, data);
+ }
+
+ pub fn chainLength(p: *const RawPbuf) u16 {
+ return sys.pbuf_clen(p);
+ }
+
+ pub fn realloc(p: *RawPbuf, size: u16) void {
+ sys.pbuf_realloc(p, size);
+ }
+
+ pub fn addHeader(p: *RawPbuf, increment: usize) bool {
+ return sys.pbuf_add_header(p, increment) == 0;
+ }
+
+ pub fn removeHeader(p: *RawPbuf, size: usize) bool {
+ return sys.pbuf_remove_header(p, size) == 0;
+ }
+};
+
+// ---------------------------------------------------------------------------
+// SNTP helpers
+// ---------------------------------------------------------------------------
+
+pub const SNTP = struct {
+ pub fn getSyncInterval() u32 {
+ return sys.sntp_get_sync_interval();
+ }
+ pub fn setSystemTime(sec: u32, us: u32) void {
+ sys.sntp_set_system_time(sec, us);
+ }
+ pub fn getSystemTime(sec: *u32, us: *u32) void {
+ sys.sntp_get_system_time(sec, us);
+ }
+};
+
+// ---------------------------------------------------------------------------
+// lwIP OS abstraction (sys_arch_*)
+// ---------------------------------------------------------------------------
+
+pub const Sys = struct {
+ pub const Sem = sys.sys_sem_t;
+ pub const Mutex = sys.sys_mutex_t;
+ pub const Thread = sys.sys_thread_t;
+ pub const Mbox = sys.sys_mbox_t;
+
+ pub fn init() void {
+ sys.sys_init();
+ }
+ pub fn now() u32 {
+ return sys.sys_now();
+ }
+ pub fn jiffies() u32 {
+ return sys.sys_jiffies();
+ }
+ pub fn delayMs(ms: u32) void {
+ sys.sys_delay_ms(ms);
+ }
+
+ pub fn semNew(sem: *Sem, count: u8) !void {
+ try errFromC(sys.sys_sem_new(sem, count));
+ }
+ pub fn semSignal(sem: *Sem) void {
+ sys.sys_sem_signal(sem);
+ }
+ pub fn semWait(sem: *Sem, timeout_ms: u32) u32 {
+ return sys.sys_arch_sem_wait(sem, timeout_ms);
+ }
+ pub fn semFree(sem: *Sem) void {
+ sys.sys_sem_free(sem);
+ }
+
+ pub fn mutexNew(m: *Mutex) !void {
+ try errFromC(sys.sys_mutex_new(m));
+ }
+ pub fn mutexLock(m: *Mutex) void {
+ sys.sys_mutex_lock(m);
+ }
+ pub fn mutexUnlock(m: *Mutex) void {
+ sys.sys_mutex_unlock(m);
+ }
+ pub fn mutexFree(m: *Mutex) void {
+ sys.sys_mutex_free(m);
+ }
+
+ pub fn mboxNew(mbox: *Mbox, size: c_int) !void {
+ try errFromC(sys.sys_mbox_new(mbox, size));
+ }
+ pub fn mboxPost(mbox: *Mbox, msg: ?*anyopaque) void {
+ sys.sys_mbox_post(mbox, msg);
+ }
+ pub fn mboxTryPost(mbox: *Mbox, msg: ?*anyopaque) !void {
+ try errFromC(sys.sys_mbox_trypost(mbox, msg));
+ }
+ pub fn mboxTryPostFromISR(mbox: *Mbox, msg: ?*anyopaque) !void {
+ try errFromC(sys.sys_mbox_trypost_fromisr(mbox, msg));
+ }
+ pub fn mboxFetch(mbox: *Mbox, msg: *?*anyopaque, timeout_ms: u32) u32 {
+ return sys.sys_arch_mbox_fetch(mbox, msg, timeout_ms);
+ }
+ pub fn mboxTryFetch(mbox: *Mbox, msg: *?*anyopaque) u32 {
+ return sys.sys_arch_mbox_tryfetch(mbox, msg);
+ }
+ pub fn mboxFree(mbox: *Mbox) void {
+ sys.sys_mbox_free(mbox);
+ }
+
+ pub fn threadNew(
+ name: [*:0]const u8,
+ thread_fn: sys.lwip_thread_fn,
+ arg: ?*anyopaque,
+ stacksize: c_int,
+ prio: c_int,
+ ) Thread {
+ return sys.sys_thread_new(name, thread_fn, arg, stacksize, prio);
+ }
+
+ pub fn threadSemInit() [*c]Sem {
+ return sys.sys_thread_sem_init();
+ }
+ pub fn threadSemDeinit() void {
+ sys.sys_thread_sem_deinit();
+ }
+ pub fn threadSemGet() [*c]Sem {
+ return sys.sys_thread_sem_get();
+ }
+ pub fn threadTcpip(kind: SysThreadCoreLock) bool {
+ return sys.sys_thread_tcpip(kind);
+ }
+
+ pub fn archProtect() c_int {
+ return sys.sys_arch_protect();
+ }
+ pub fn archUnprotect(pval: c_int) void {
+ sys.sys_arch_unprotect(pval);
+ }
+};
+
+// ---------------------------------------------------------------------------
+// lwIP memory pool helpers (low-level, rarely used directly)
+// ---------------------------------------------------------------------------
+
+pub const Memp = struct {
+ pub fn init() void {
+ sys.memp_init();
+ }
+ pub fn malloc(kind: MempType) ?*anyopaque {
+ return sys.memp_malloc(kind);
+ }
+ pub fn free(kind: MempType, mem: ?*anyopaque) void {
+ sys.memp_free(kind, mem);
+ }
+};
+
+// ---------------------------------------------------------------------------
+// Byte order helpers (htons/htonl)
+// ---------------------------------------------------------------------------
+
+pub inline fn htons(x: u16) u16 {
+ return sys.lwip_htons(x);
+}
+pub inline fn htonl(x: u32) u32 {
+ return sys.lwip_htonl(x);
+}
+pub inline fn ntohs(x: u16) u16 {
+ return sys.lwip_htons(x);
+} // symmetric on all archs
+pub inline fn ntohl(x: u32) u32 {
+ return sys.lwip_htonl(x);
+}
+
+// ---------------------------------------------------------------------------
+// String helpers (lwip_str*)
+// ---------------------------------------------------------------------------
+
+pub fn strnicmp(a: [*:0]const u8, b: [*:0]const u8, n: usize) c_int {
+ return sys.lwip_strnicmp(a, b, n);
+}
+pub fn stricmp(a: [*:0]const u8, b: [*:0]const u8) c_int {
+ return sys.lwip_stricmp(a, b);
+}
+pub fn strnstr(haystack: [*:0]const u8, needle: [*:0]const u8, n: usize) ?[*:0]u8 {
+ return sys.lwip_strnstr(haystack, needle, n);
+}
+pub fn itoa(buf: []u8, number: c_int) void {
+ sys.lwip_itoa(buf.ptr, buf.len, number);
+}
diff --git a/software/zig_main/imports/matter.zig b/software/zig_main/imports/matter.zig
new file mode 100644
index 0000000..679305d
--- /dev/null
+++ b/software/zig_main/imports/matter.zig
@@ -0,0 +1,252 @@
+//! ESP Matter wrapper — Zig interface to the CHIP/Matter C++ SDK via matter_stubs.h.
+//!
+//! Requires the `espressif/esp_matter` managed component.
+//! To add it: `idf.py add-dependency espressif/esp_matter`
+
+const sys = @import("sys");
+const errors = @import("error");
+
+// ── Re-export core types ─────────────────────────────────────────────────────
+
+pub const Node = sys.esp_matter_node_t;
+pub const Endpoint = sys.esp_matter_endpoint_t;
+pub const Cluster = sys.esp_matter_cluster_t;
+pub const Attribute = sys.esp_matter_attribute_t;
+
+/// Endpoint creation flags.
+pub const EpFlags = enum(u8) {
+ none = sys.ESP_MATTER_EP_FLAG_NONE,
+ destroyable = sys.ESP_MATTER_EP_FLAG_DESTROYABLE,
+ bridge = sys.ESP_MATTER_EP_FLAG_BRIDGE,
+};
+
+/// Attribute value types (Zig enum wrapping the C enum constants).
+pub const ValType = enum(c_uint) {
+ invalid = sys.ESP_MATTER_VAL_TYPE_INVALID,
+ boolean = sys.ESP_MATTER_VAL_TYPE_BOOLEAN,
+ integer = sys.ESP_MATTER_VAL_TYPE_INTEGER,
+ float_ = sys.ESP_MATTER_VAL_TYPE_FLOAT,
+ array = sys.ESP_MATTER_VAL_TYPE_ARRAY,
+ char_string = sys.ESP_MATTER_VAL_TYPE_CHAR_STRING,
+ octet_string = sys.ESP_MATTER_VAL_TYPE_OCTET_STRING,
+ int8 = sys.ESP_MATTER_VAL_TYPE_INT8,
+ uint8 = sys.ESP_MATTER_VAL_TYPE_UINT8,
+ int16 = sys.ESP_MATTER_VAL_TYPE_INT16,
+ uint16 = sys.ESP_MATTER_VAL_TYPE_UINT16,
+ int32 = sys.ESP_MATTER_VAL_TYPE_INT32,
+ uint32 = sys.ESP_MATTER_VAL_TYPE_UINT32,
+ int64 = sys.ESP_MATTER_VAL_TYPE_INT64,
+ uint64 = sys.ESP_MATTER_VAL_TYPE_UINT64,
+ enum8 = sys.ESP_MATTER_VAL_TYPE_ENUM8,
+ bitmap8 = sys.ESP_MATTER_VAL_TYPE_BITMAP8,
+ bitmap16 = sys.ESP_MATTER_VAL_TYPE_BITMAP16,
+ bitmap32 = sys.ESP_MATTER_VAL_TYPE_BITMAP32,
+ enum16 = sys.ESP_MATTER_VAL_TYPE_ENUM16,
+ long_char_string = sys.ESP_MATTER_VAL_TYPE_LONG_CHAR_STRING,
+ long_octet_string = sys.ESP_MATTER_VAL_TYPE_LONG_OCTET_STRING,
+ _,
+};
+
+/// Attribute value (union mirroring esp_matter_val_t).
+pub const Val = sys.esp_matter_val_t;
+
+/// Attribute value with type tag.
+pub const AttrVal = sys.esp_matter_attr_val_t;
+
+/// Attribute update callback type (Zig enum with named members).
+/// Use e.g. `matter.AttrCbType.ESP_MATTER_ATTR_CB_POST_UPDATE`.
+pub const AttrCbType = enum(c_uint) {
+ ESP_MATTER_ATTR_CB_PRE_UPDATE = sys.ESP_MATTER_ATTR_CB_PRE_UPDATE,
+ ESP_MATTER_ATTR_CB_POST_UPDATE = sys.ESP_MATTER_ATTR_CB_POST_UPDATE,
+ ESP_MATTER_ATTR_CB_READ = sys.ESP_MATTER_ATTR_CB_READ,
+ ESP_MATTER_ATTR_CB_WRITE = sys.ESP_MATTER_ATTR_CB_WRITE,
+ _,
+};
+
+/// Attribute update callback function pointer type.
+/// The callback receives a `?*AttrVal` which at the C ABI boundary is `[*c]AttrVal`.
+/// Return 0 (ESP_OK) to allow, non-zero from PRE_UPDATE to block.
+pub const AttrCallback = ?*const fn (
+ AttrCbType,
+ u16,
+ u32,
+ u32,
+ ?*AttrVal,
+ ?*anyopaque,
+) callconv(.c) c_int;
+
+/// Identify cluster callback type.
+pub const IdentifyCbType = enum(c_uint) {
+ ESP_MATTER_IDENTIFY_CB_START = sys.ESP_MATTER_IDENTIFY_CB_START,
+ ESP_MATTER_IDENTIFY_CB_STOP = sys.ESP_MATTER_IDENTIFY_CB_STOP,
+ ESP_MATTER_IDENTIFY_CB_EFFECT = sys.ESP_MATTER_IDENTIFY_CB_EFFECT,
+ _,
+};
+
+/// Identify cluster callback function pointer type.
+pub const IdentifyCallback = ?*const fn (IdentifyCbType, u16, u8, u8, ?*anyopaque) callconv(.c) c_int;
+
+// ── Attribute value helpers ──────────────────────────────────────────────────
+
+/// Convenience wrappers matching esp_matter_val_bool(), esp_matter_uint8(), etc.
+pub const val = struct {
+ pub fn boolean(b: bool) AttrVal {
+ return sys.esp_matter_val_bool(b);
+ }
+ pub fn uint8(v: u8) AttrVal {
+ return sys.esp_matter_val_uint8(v);
+ }
+ pub fn uint16(v: u16) AttrVal {
+ return sys.esp_matter_val_uint16(v);
+ }
+ pub fn uint32(v: u32) AttrVal {
+ return sys.esp_matter_val_uint32(v);
+ }
+ pub fn int16(v: i16) AttrVal {
+ return sys.esp_matter_val_int16(v);
+ }
+ pub fn nullable() AttrVal {
+ return sys.esp_matter_val_nullable();
+ }
+};
+
+// ── Core ─────────────────────────────────────────────────────────────────────
+
+/// Start the Matter stack (non-blocking; spawns Matter OS task).
+/// Call after building the node + endpoint + cluster data model.
+pub fn start(attr_cb: AttrCallback, identify_cb: IdentifyCallback) !void {
+ // @ptrCast converts our typed function pointer to the raw sys type.
+ // The types are ABI-compatible: enum(c_uint)↔c_uint, ?*T↔[*c]T.
+ try errors.espCheckError(sys.esp_matter_wrapper_start(@ptrCast(attr_cb), @ptrCast(identify_cb)));
+}
+
+/// Erase Matter NVS data and reboot. Non-recoverable.
+pub fn factoryReset() !void {
+ try errors.espCheckError(sys.esp_matter_wrapper_factory_reset());
+}
+
+/// Return true if Matter has been started.
+pub fn isStarted() bool {
+ return sys.esp_matter_wrapper_is_started();
+}
+
+// ── Node ─────────────────────────────────────────────────────────────────────
+
+/// Create the root Matter node (endpoint 0, Root Node device type).
+/// attr_cb fires on every attribute read/write; identify_cb fires on Identify requests.
+/// priv_data is forwarded to both callbacks.
+pub fn nodeCreate(attr_cb: AttrCallback, identify_cb: IdentifyCallback, priv_data: ?*anyopaque) !*Node {
+ const n = sys.esp_matter_wrapper_node_create(@ptrCast(attr_cb), @ptrCast(identify_cb), priv_data);
+ if (n == null) return error.MatterNodeCreateFailed;
+ return @ptrCast(n);
+}
+
+// ── Endpoint ─────────────────────────────────────────────────────────────────
+
+/// Endpoint operations.
+pub const endpoint = struct {
+ /// Create a generic endpoint on the node.
+ pub fn create(node: *Node, flags: EpFlags, priv_data: ?*anyopaque) !*Endpoint {
+ const ep = sys.esp_matter_wrapper_endpoint_create(node, @intFromEnum(flags), priv_data);
+ if (ep == null) return error.MatterEndpointCreateFailed;
+ return @ptrCast(ep);
+ }
+
+ /// Destroy a destroyable endpoint.
+ pub fn destroy(node: *Node, ep: *Endpoint) !void {
+ try errors.espCheckError(sys.esp_matter_wrapper_endpoint_destroy(node, ep));
+ }
+
+ /// Return the endpoint's numeric ID.
+ pub fn getId(ep: *Endpoint) u16 {
+ return sys.esp_matter_wrapper_endpoint_get_id(ep);
+ }
+
+ /// Associate a Matter device type with this endpoint.
+ pub fn addDeviceType(ep: *Endpoint, device_type_id: u32, version: u8) !void {
+ try errors.espCheckError(
+ sys.esp_matter_wrapper_endpoint_add_device_type(ep, device_type_id, version),
+ );
+ }
+
+ /// Enable a dynamically-created endpoint (only after Matter is started).
+ pub fn enable(ep: *Endpoint) !void {
+ try errors.espCheckError(sys.esp_matter_wrapper_endpoint_enable(ep));
+ }
+
+ // ── Pre-built device-type helpers ─────────────────────────────────────
+
+ /// Add an On/Off Light endpoint (device type 0x0100).
+ pub fn addOnOffLight(node: *Node, flags: EpFlags, priv_data: ?*anyopaque) !*Endpoint {
+ const ep = sys.esp_matter_wrapper_add_on_off_light(node, @intFromEnum(flags), priv_data);
+ if (ep == null) return error.MatterEndpointCreateFailed;
+ return @ptrCast(ep);
+ }
+
+ /// Add an On/Off Switch endpoint (device type 0x0103).
+ pub fn addOnOffSwitch(node: *Node, flags: EpFlags, priv_data: ?*anyopaque) !*Endpoint {
+ const ep = sys.esp_matter_wrapper_add_on_off_switch(node, @intFromEnum(flags), priv_data);
+ if (ep == null) return error.MatterEndpointCreateFailed;
+ return @ptrCast(ep);
+ }
+
+ /// Add a Dimmable Light endpoint (device type 0x0101).
+ pub fn addDimmableLight(node: *Node, flags: EpFlags, priv_data: ?*anyopaque) !*Endpoint {
+ const ep = sys.esp_matter_wrapper_add_dimmable_light(node, @intFromEnum(flags), priv_data);
+ if (ep == null) return error.MatterEndpointCreateFailed;
+ return @ptrCast(ep);
+ }
+
+ /// Add a Color Temperature Light endpoint (device type 0x010C).
+ pub fn addColorTemperatureLight(node: *Node, flags: EpFlags, priv_data: ?*anyopaque) !*Endpoint {
+ const ep = sys.esp_matter_wrapper_add_color_temperature_light(node, @intFromEnum(flags), priv_data);
+ if (ep == null) return error.MatterEndpointCreateFailed;
+ return @ptrCast(ep);
+ }
+};
+
+// ── Cluster ───────────────────────────────────────────────────────────────────
+
+/// Cluster operations.
+pub const cluster = struct {
+ /// Cluster flag: server cluster.
+ pub const FLAG_SERVER: u8 = 0x40;
+ /// Cluster flag: client cluster.
+ pub const FLAG_CLIENT: u8 = 0x80;
+
+ /// Create a cluster on an endpoint.
+ pub fn create(ep: *Endpoint, cluster_id: u32, flags: u8) !*Cluster {
+ const cl = sys.esp_matter_wrapper_cluster_create(ep, cluster_id, flags);
+ if (cl == null) return error.MatterClusterCreateFailed;
+ return @ptrCast(cl);
+ }
+};
+
+// ── Attribute ─────────────────────────────────────────────────────────────────
+
+/// Attribute operations.
+pub const attribute = struct {
+ /// Create an attribute on a cluster with a default value.
+ pub fn create(cl: *Cluster, attribute_id: u32, flags: u16, default_val: AttrVal) !*Attribute {
+ const attr = sys.esp_matter_wrapper_attribute_create(cl, attribute_id, flags, default_val);
+ if (attr == null) return error.MatterAttributeCreateFailed;
+ return @ptrCast(attr);
+ }
+
+ /// Update an attribute value (use after Matter is started).
+ pub fn update(endpoint_id: u16, cluster_id: u32, attribute_id: u32, v: *AttrVal) !void {
+ try errors.espCheckError(
+ sys.esp_matter_wrapper_attribute_update(endpoint_id, cluster_id, attribute_id, v),
+ );
+ }
+
+ /// Get the current value of an attribute.
+ pub fn getVal(attr: *Attribute, v: *AttrVal) !void {
+ try errors.espCheckError(sys.esp_matter_wrapper_attribute_get_val(attr, v));
+ }
+
+ /// Set an attribute value (use before Matter is started).
+ pub fn setVal(attr: *Attribute, v: *AttrVal) !void {
+ try errors.espCheckError(sys.esp_matter_wrapper_attribute_set_val(attr, v));
+ }
+};
diff --git a/software/zig_main/imports/mqtt.zig b/software/zig_main/imports/mqtt.zig
new file mode 100644
index 0000000..74057da
--- /dev/null
+++ b/software/zig_main/imports/mqtt.zig
@@ -0,0 +1,236 @@
+const sys = @import("sys");
+const errors = @import("error");
+
+// ─────────────────────────────────────────────────────────────────────────────
+// Re-exported types
+// ─────────────────────────────────────────────────────────────────────────────
+pub const Handle = sys.esp_mqtt_client_handle_t;
+pub const EventId = sys.esp_mqtt_event_id_t;
+pub const Event = sys.esp_mqtt_event_t;
+pub const EventHandle = sys.esp_mqtt_event_handle_t;
+pub const EventHandler = sys.esp_event_handler_t;
+pub const ClientConfig = sys.esp_mqtt_client_config_t;
+pub const Topic = sys.esp_mqtt_topic_t;
+pub const ErrorCodes = sys.esp_mqtt_error_codes_t;
+pub const ErrorType = sys.esp_mqtt_error_type_t;
+pub const Transport = sys.esp_mqtt_transport_t;
+pub const ProtocolVersion = sys.esp_mqtt_protocol_ver_t;
+pub const ConnectReturnCode = sys.esp_mqtt_connect_return_code_t;
+
+// MQTT v5
+pub const Handle5 = sys.esp_mqtt5_client_handle_t;
+pub const ErrorReasonCode5 = sys.mqtt5_error_reason_code;
+pub const UserPropertyHandle5 = sys.mqtt5_user_property_handle_t;
+pub const UserPropertyItem5 = sys.esp_mqtt5_user_property_item_t;
+pub const ConnectionProperty5 = sys.esp_mqtt5_connection_property_config_t;
+pub const PublishProperty5 = sys.esp_mqtt5_publish_property_config_t;
+pub const SubscribeProperty5 = sys.esp_mqtt5_subscribe_property_config_t;
+pub const UnsubscribeProperty5 = sys.esp_mqtt5_unsubscribe_property_config_t;
+pub const DisconnectProperty5 = sys.esp_mqtt5_disconnect_property_config_t;
+pub const EventProperty5 = sys.esp_mqtt5_event_property_t;
+
+// ─────────────────────────────────────────────────────────────────────────────
+// Errors
+// ─────────────────────────────────────────────────────────────────────────────
+pub const Error = error{
+ /// publish / subscribe / enqueue returned -1 (queuing failed)
+ Failed,
+ /// publish returned -2 (outbox full)
+ OutboxFull,
+};
+
+/// Converts a message-id return value (≥0 = ok, -1 = failed, -2 = outbox full)
+/// into `!u32`.
+fn msgIdResult(rc: c_int) Error!u32 {
+ return switch (rc) {
+ -2 => error.OutboxFull,
+ -1 => error.Failed,
+ else => @intCast(rc),
+ };
+}
+
+// ─────────────────────────────────────────────────────────────────────────────
+// Client lifecycle
+// ─────────────────────────────────────────────────────────────────────────────
+
+/// Initialise a new MQTT client. Returns `null` if allocation fails.
+pub fn init(cfg: *const ClientConfig) ?Handle {
+ return sys.esp_mqtt_client_init(cfg);
+}
+
+pub fn deinit(client: Handle) !void {
+ return errors.espCheckError(sys.esp_mqtt_client_destroy(client));
+}
+
+pub fn setConfig(client: Handle, cfg: *const ClientConfig) !void {
+ return errors.espCheckError(sys.esp_mqtt_set_config(client, cfg));
+}
+
+pub fn setUri(client: Handle, uri: [:0]const u8) !void {
+ return errors.espCheckError(sys.esp_mqtt_client_set_uri(client, uri.ptr));
+}
+
+// ─────────────────────────────────────────────────────────────────────────────
+// Connection control
+// ─────────────────────────────────────────────────────────────────────────────
+
+pub fn start(client: Handle) !void {
+ return errors.espCheckError(sys.esp_mqtt_client_start(client));
+}
+
+pub fn stop(client: Handle) !void {
+ return errors.espCheckError(sys.esp_mqtt_client_stop(client));
+}
+
+pub fn reconnect(client: Handle) !void {
+ return errors.espCheckError(sys.esp_mqtt_client_reconnect(client));
+}
+
+pub fn disconnect(client: Handle) !void {
+ return errors.espCheckError(sys.esp_mqtt_client_disconnect(client));
+}
+
+// ─────────────────────────────────────────────────────────────────────────────
+// Publish
+// ─────────────────────────────────────────────────────────────────────────────
+
+pub const PublishConfig = struct {
+ qos: u2 = 0,
+ retain: bool = false,
+};
+
+/// Publish `data` to `topic`. Returns the message-id assigned by the broker.
+pub fn publish(client: Handle, topic: [:0]const u8, data: []const u8, cfg: PublishConfig) Error!u32 {
+ return msgIdResult(sys.esp_mqtt_client_publish(
+ client,
+ topic.ptr,
+ data.ptr,
+ @intCast(data.len),
+ @intCast(cfg.qos),
+ @intFromBool(cfg.retain),
+ ));
+}
+
+pub const EnqueueConfig = struct {
+ qos: u2 = 0,
+ retain: bool = false,
+ /// Store in outbox even when disconnected.
+ store: bool = true,
+};
+
+/// Enqueue a publish for delivery (stores in outbox when disconnected).
+pub fn enqueue(client: Handle, topic: [:0]const u8, data: []const u8, cfg: EnqueueConfig) Error!u32 {
+ return msgIdResult(sys.esp_mqtt_client_enqueue(
+ client,
+ topic.ptr,
+ data.ptr,
+ @intCast(data.len),
+ @intCast(cfg.qos),
+ @intFromBool(cfg.retain),
+ cfg.store,
+ ));
+}
+
+// ─────────────────────────────────────────────────────────────────────────────
+// Subscribe / Unsubscribe
+// ─────────────────────────────────────────────────────────────────────────────
+
+/// Subscribe to a single topic filter. Returns the message-id.
+pub fn subscribe(client: Handle, topic: [:0]const u8, qos: u2) Error!u32 {
+ return msgIdResult(sys.esp_mqtt_client_subscribe_single(client, topic.ptr, @intCast(qos)));
+}
+
+/// Subscribe to multiple topic filters in one call.
+pub fn subscribeMultiple(client: Handle, topics: []const Topic) Error!u32 {
+ return msgIdResult(sys.esp_mqtt_client_subscribe_multiple(
+ client,
+ topics.ptr,
+ @intCast(topics.len),
+ ));
+}
+
+pub fn unsubscribe(client: Handle, topic: [:0]const u8) Error!u32 {
+ return msgIdResult(sys.esp_mqtt_client_unsubscribe(client, topic.ptr));
+}
+
+// ─────────────────────────────────────────────────────────────────────────────
+// Event registration
+// ─────────────────────────────────────────────────────────────────────────────
+
+pub fn registerEvent(client: Handle, event: EventId, handler: EventHandler, arg: ?*anyopaque) !void {
+ return errors.espCheckError(sys.esp_mqtt_client_register_event(client, event, handler, arg));
+}
+
+pub fn unregisterEvent(client: Handle, event: EventId, handler: EventHandler) !void {
+ return errors.espCheckError(sys.esp_mqtt_client_unregister_event(client, event, handler));
+}
+
+// ─────────────────────────────────────────────────────────────────────────────
+// Diagnostics
+// ─────────────────────────────────────────────────────────────────────────────
+
+/// Returns the current outbox size in bytes.
+pub fn getOutboxSize(client: Handle) usize {
+ return @intCast(sys.esp_mqtt_client_get_outbox_size(client));
+}
+
+/// Dispatch a custom application event through the MQTT event loop.
+pub fn dispatchCustomEvent(client: Handle, event: *Event) !void {
+ return errors.espCheckError(sys.esp_mqtt_dispatch_custom_event(client, event));
+}
+
+// ─────────────────────────────────────────────────────────────────────────────
+// MQTT v5 extensions
+// ─────────────────────────────────────────────────────────────────────────────
+
+pub const v5 = struct {
+ pub fn setConnectProperty(client: Handle5, prop: *const ConnectionProperty5) !void {
+ return errors.espCheckError(sys.esp_mqtt5_client_set_connect_property(client, prop));
+ }
+
+ pub fn setPublishProperty(client: Handle5, prop: *const PublishProperty5) !void {
+ return errors.espCheckError(sys.esp_mqtt5_client_set_publish_property(client, prop));
+ }
+
+ pub fn setSubscribeProperty(client: Handle5, prop: *const SubscribeProperty5) !void {
+ return errors.espCheckError(sys.esp_mqtt5_client_set_subscribe_property(client, prop));
+ }
+
+ pub fn setUnsubscribeProperty(client: Handle5, prop: *const UnsubscribeProperty5) !void {
+ return errors.espCheckError(sys.esp_mqtt5_client_set_unsubscribe_property(client, prop));
+ }
+
+ pub fn setDisconnectProperty(client: Handle5, prop: *const DisconnectProperty5) !void {
+ return errors.espCheckError(sys.esp_mqtt5_client_set_disconnect_property(client, prop));
+ }
+
+ // ── User properties ───────────────────────────────────────────────────
+
+ pub fn setUserProperty(handle: *UserPropertyHandle5, items: []UserPropertyItem5) !void {
+ return errors.espCheckError(sys.esp_mqtt5_client_set_user_property(
+ handle,
+ items.ptr,
+ @intCast(items.len),
+ ));
+ }
+
+ /// Returns how many user-property items are present.
+ pub fn getUserPropertyCount(handle: UserPropertyHandle5) u8 {
+ return sys.esp_mqtt5_client_get_user_property_count(handle);
+ }
+
+ /// Read user-property items into the caller-supplied slice.
+ /// The slice length must be ≥ `getUserPropertyCount()`.
+ pub fn getUserProperty(handle: UserPropertyHandle5, out: []UserPropertyItem5) !void {
+ var count: u8 = @intCast(out.len);
+ return errors.espCheckError(sys.esp_mqtt5_client_get_user_property(
+ handle,
+ out.ptr,
+ &count,
+ ));
+ }
+
+ pub fn deleteUserProperty(handle: UserPropertyHandle5) void {
+ sys.esp_mqtt5_client_delete_user_property(handle);
+ }
+};
diff --git a/software/zig_main/imports/nimble.zig b/software/zig_main/imports/nimble.zig
new file mode 100644
index 0000000..d74e660
--- /dev/null
+++ b/software/zig_main/imports/nimble.zig
@@ -0,0 +1,158 @@
+// NimBLE wrapper — requires CONFIG_BT_NIMBLE_ENABLED in sdkconfig.
+//
+// Enable via:
+// idf.py menuconfig → Component config → Bluetooth → NimBLE
+//
+// When enabled, re-run `idf.py reconfigure` to regenerate the bindings.
+// NimBLE symbols (ble_hs_*, ble_gap_*, ble_gatt_*) will appear in idf-sys.zig.
+const sys = @import("sys");
+
+comptime {
+ if (!@hasDecl(sys, "CONFIG_BT_NIMBLE_ENABLED"))
+ @compileError(
+ \\NimBLE is not enabled. Enable it via:
+ \\ idf.py menuconfig → Component config → Bluetooth
+ \\ → Host → NimBLE - BLE stack
+ \\Then run: idf.py reconfigure
+ );
+}
+
+// ---------------------------------------------------------------------------
+// NimBLE type aliases (available only when CONFIG_BT_NIMBLE_ENABLED=y)
+// ---------------------------------------------------------------------------
+
+pub const HsCfg = sys.ble_hs_cfg_t;
+pub const GapConnDesc = sys.ble_gap_conn_desc;
+pub const GapAdvParams = sys.ble_gap_adv_params;
+pub const GapConnParams = sys.ble_gap_conn_params;
+pub const GattSvcDef = sys.ble_gatt_svc_def;
+pub const GattCharDef = sys.ble_gatt_chr_def;
+pub const GattDscrDef = sys.ble_gatt_dsc_def;
+pub const Uuid16 = sys.ble_uuid16_t;
+pub const Uuid128 = sys.ble_uuid128_t;
+
+// ---------------------------------------------------------------------------
+// Host stack
+// ---------------------------------------------------------------------------
+
+pub const Host = struct {
+ /// Initialize the NimBLE host stack (call before syncing).
+ pub fn init() void {
+ sys.nimble_port_init();
+ }
+
+ /// Start the NimBLE host task.
+ pub fn run() void {
+ sys.nimble_port_run();
+ }
+
+ /// Free task resources (call from host task on exit).
+ pub fn freeThenResume() void {
+ sys.nimble_port_freertos_deinit();
+ }
+};
+
+// ---------------------------------------------------------------------------
+// GAP (Generic Access Profile)
+// ---------------------------------------------------------------------------
+
+pub const Gap = struct {
+ pub const AdvType = sys.ble_gap_adv_type;
+ pub const EventCb = sys.ble_gap_event_fn;
+
+ pub fn setDeviceName(name: [*:0]const u8) !void {
+ const rc = sys.ble_svc_gap_device_name_set(name);
+ if (rc != 0) return error.NimbleError;
+ }
+
+ pub fn advStart(
+ own_addr_type: u8,
+ direct_addr: ?*const sys.ble_addr_t,
+ duration_ms: i32,
+ params: ?*const GapAdvParams,
+ cb: ?EventCb,
+ cb_arg: ?*anyopaque,
+ ) !void {
+ const rc = sys.ble_gap_adv_start(own_addr_type, direct_addr, duration_ms, params, cb, cb_arg);
+ if (rc != 0) return error.NimbleError;
+ }
+
+ pub fn advStop() !void {
+ const rc = sys.ble_gap_adv_stop();
+ if (rc != 0) return error.NimbleError;
+ }
+
+ pub fn connect(
+ own_addr_type: u8,
+ peer_addr: *const sys.ble_addr_t,
+ duration_ms: i32,
+ params: ?*const GapConnParams,
+ cb: EventCb,
+ cb_arg: ?*anyopaque,
+ ) !void {
+ const rc = sys.ble_gap_connect(own_addr_type, peer_addr, duration_ms, params, cb, cb_arg);
+ if (rc != 0) return error.NimbleError;
+ }
+
+ pub fn terminate(conn_handle: u16, hci_reason: u8) !void {
+ const rc = sys.ble_gap_terminate(conn_handle, hci_reason);
+ if (rc != 0) return error.NimbleError;
+ }
+};
+
+// ---------------------------------------------------------------------------
+// GATT Server
+// ---------------------------------------------------------------------------
+
+pub const GattServer = struct {
+ pub fn registerSvcs(svcs: []const GattSvcDef) !void {
+ const rc = sys.ble_gatts_add_svcs(svcs.ptr);
+ if (rc != 0) return error.NimbleError;
+ }
+
+ pub fn count(svcs: []const GattSvcDef, out: *sys.ble_gatt_svc_def_iter) !void {
+ _ = svcs;
+ _ = out;
+ }
+
+ pub fn notify(conn_handle: u16, attr_handle: u16) !void {
+ const rc = sys.ble_gatts_notify(conn_handle, attr_handle);
+ if (rc != 0) return error.NimbleError;
+ }
+
+ pub fn indicate(conn_handle: u16, attr_handle: u16) !void {
+ const rc = sys.ble_gatts_indicate(conn_handle, attr_handle);
+ if (rc != 0) return error.NimbleError;
+ }
+};
+
+// ---------------------------------------------------------------------------
+// Advertising data builder (OOP API)
+// ---------------------------------------------------------------------------
+
+pub const AdvFields = struct {
+ fields: sys.ble_hs_adv_fields = .{},
+
+ pub fn setFlags(self: *AdvFields, flags: u8) void {
+ self.fields.flags = flags;
+ self.fields.flags_is_present = 1;
+ }
+
+ pub fn setName(self: *AdvFields, name: [*:0]const u8, complete: bool) void {
+ self.fields.name = name;
+ self.fields.name_len = @intCast(std.mem.len(name));
+ self.fields.name_is_complete = @intFromBool(complete);
+ }
+
+ pub fn setTxPwrLvl(self: *AdvFields, level: i8) void {
+ self.fields.tx_pwr_lvl = level;
+ self.fields.tx_pwr_lvl_is_present = 1;
+ }
+
+ pub fn build(self: *AdvFields, buf: []u8, out_len: *u8) !void {
+ const rc = sys.ble_hs_adv_set_fields(&self.fields, buf.ptr, out_len, @intCast(buf.len));
+ if (rc != 0) return error.NimbleError;
+ }
+};
+
+const std = @import("std");
diff --git a/software/zig_main/imports/now.zig b/software/zig_main/imports/now.zig
new file mode 100644
index 0000000..fbec901
--- /dev/null
+++ b/software/zig_main/imports/now.zig
@@ -0,0 +1,315 @@
+const sys = @import("sys");
+const errors = @import("error");
+
+// ---------------------------------------------------------------------------
+// Re-exported types from sys
+// ---------------------------------------------------------------------------
+
+pub const WifiInterface = sys.wifi_interface_t;
+pub const WifiPktRxCtrl = sys.wifi_pkt_rx_ctrl_t;
+pub const WifiTxInfo = sys.wifi_tx_info_t;
+pub const WifiTxRateConfig = sys.wifi_tx_rate_config_t;
+pub const WifiActionTx = sys.wifi_action_tx_t;
+pub const WifiSecondChan = sys.wifi_second_chan_t;
+pub const WifiRoc = sys.wifi_roc_t;
+
+// ---------------------------------------------------------------------------
+// Constants
+// ---------------------------------------------------------------------------
+
+pub const MAC_LEN = 6;
+pub const LMK_LEN = 16;
+pub const PMK_LEN = 16;
+pub const OUI_LEN = 3;
+
+// ---------------------------------------------------------------------------
+// Send status
+// ---------------------------------------------------------------------------
+
+pub const SendStatus = enum(c_uint) {
+ success = 0,
+ fail = 1,
+};
+
+// ---------------------------------------------------------------------------
+// Peer info
+// ---------------------------------------------------------------------------
+
+pub const PeerInfo = extern struct {
+ peer_addr: [MAC_LEN]u8 = .{0} ** MAC_LEN,
+ lmk: [LMK_LEN]u8 = .{0} ** LMK_LEN,
+ channel: u8 = 0,
+ ifidx: WifiInterface = undefined,
+ encrypt: bool = false,
+ priv: ?*anyopaque = null,
+};
+
+pub const PeerNum = extern struct {
+ total_num: c_int = 0,
+ encrypt_num: c_int = 0,
+};
+
+// ---------------------------------------------------------------------------
+// Receive info (passed to the recv callback)
+// ---------------------------------------------------------------------------
+
+pub const RecvInfo = extern struct {
+ src_addr: [*c]u8 = null,
+ des_addr: [*c]u8 = null,
+ rx_ctrl: ?*WifiPktRxCtrl = null,
+};
+
+// ---------------------------------------------------------------------------
+// Switch-channel / remain-on-channel action structs
+// ---------------------------------------------------------------------------
+
+pub const SwitchChannel = extern struct {
+ type: WifiActionTx = undefined,
+ channel: u8 = 0,
+ sec_channel: WifiSecondChan = undefined,
+ wait_time_ms: u32 = 0,
+ op_id: u8 = 0,
+ dest_mac: [MAC_LEN]u8 = .{0} ** MAC_LEN,
+ data_len: u16 = 0,
+ _data: [0]u8 = .{},
+};
+
+pub const RemainOnChannel = extern struct {
+ type: WifiRoc = undefined,
+ channel: u8 = 0,
+ sec_channel: WifiSecondChan = undefined,
+ wait_time_ms: u32 = 0,
+ op_id: u8 = 0,
+};
+
+// ---------------------------------------------------------------------------
+// Callback types — use these in your application; cast to/from the C type
+// at registration time via the EspNow.registerRecvCb / registerSendCb wrappers.
+// ---------------------------------------------------------------------------
+
+pub const RecvCb = *const fn (
+ info: *const RecvInfo,
+ data: [*]const u8,
+ data_len: usize,
+) void;
+
+pub const SendCb = *const fn (
+ tx_info: *const WifiTxInfo,
+ status: SendStatus,
+) void;
+
+// Internal C-ABI adapters stored when a Zig callback is registered.
+var recv_cb_zig: ?RecvCb = null;
+var send_cb_zig: ?SendCb = null;
+
+fn recvCbAdapter(
+ info: [*c]const sys.esp_now_recv_info_t,
+ data: [*c]const u8,
+ data_len: c_int,
+) callconv(.c) void {
+ if (recv_cb_zig) |cb|
+ cb(@ptrCast(info), data, @intCast(data_len));
+}
+
+fn sendCbAdapter(
+ tx_info: [*c]const sys.esp_now_send_info_t,
+ status: sys.esp_now_send_status_t,
+) callconv(.c) void {
+ if (send_cb_zig) |cb|
+ cb(@ptrCast(tx_info), @enumFromInt(status));
+}
+
+// ---------------------------------------------------------------------------
+// EspNow namespace
+// ---------------------------------------------------------------------------
+
+pub const EspNow = struct {
+
+ // -- Lifecycle -----------------------------------------------------------
+
+ pub fn init() !void {
+ try errors.espCheckError(sys.esp_now_init());
+ }
+
+ pub fn deinit() !void {
+ try errors.espCheckError(sys.esp_now_deinit());
+ }
+
+ pub fn getVersion() !u32 {
+ var ver: u32 = 0;
+ try errors.espCheckError(sys.esp_now_get_version(&ver));
+ return ver;
+ }
+
+ // -- Callbacks -----------------------------------------------------------
+
+ /// Register a Zig-typed receive callback.
+ pub fn registerRecvCb(cb: RecvCb) !void {
+ recv_cb_zig = cb;
+ try errors.espCheckError(sys.esp_now_register_recv_cb(recvCbAdapter));
+ }
+
+ pub fn unregisterRecvCb() !void {
+ recv_cb_zig = null;
+ try errors.espCheckError(sys.esp_now_unregister_recv_cb());
+ }
+
+ /// Register a Zig-typed send callback.
+ pub fn registerSendCb(cb: SendCb) !void {
+ send_cb_zig = cb;
+ try errors.espCheckError(sys.esp_now_register_send_cb(sendCbAdapter));
+ }
+
+ pub fn unregisterSendCb() !void {
+ send_cb_zig = null;
+ try errors.espCheckError(sys.esp_now_unregister_send_cb());
+ }
+
+ // -- Sending -------------------------------------------------------------
+
+ /// Send data to a peer.
+ /// `peer_addr` — 6-byte MAC or null for broadcast.
+ /// `data` — payload slice (max 250 bytes).
+ pub fn send(peer_addr: ?*const [MAC_LEN]u8, data: []const u8) !void {
+ const addr_ptr: [*c]const u8 = if (peer_addr) |a| a else null;
+ try errors.espCheckError(sys.esp_now_send(addr_ptr, data.ptr, data.len));
+ }
+
+ // -- Peer management -----------------------------------------------------
+
+ pub fn addPeer(peer: *const PeerInfo) !void {
+ try errors.espCheckError(sys.esp_now_add_peer(@ptrCast(peer)));
+ }
+
+ pub fn delPeer(peer_addr: *const [MAC_LEN]u8) !void {
+ try errors.espCheckError(sys.esp_now_del_peer(peer_addr));
+ }
+
+ pub fn modPeer(peer: *const PeerInfo) !void {
+ try errors.espCheckError(sys.esp_now_mod_peer(@ptrCast(peer)));
+ }
+
+ pub fn setPeerRateConfig(
+ peer_addr: *const [MAC_LEN]u8,
+ config: *WifiTxRateConfig,
+ ) !void {
+ try errors.espCheckError(sys.esp_now_set_peer_rate_config(peer_addr, config));
+ }
+
+ pub fn getPeer(peer_addr: *const [MAC_LEN]u8) !PeerInfo {
+ var info: PeerInfo = undefined;
+ try errors.espCheckError(sys.esp_now_get_peer(peer_addr, @ptrCast(&info)));
+ return info;
+ }
+
+ /// Iterate over the peer list.
+ /// Pass `from_head = true` on the first call, then `false` to continue.
+ pub fn fetchPeer(from_head: bool) !PeerInfo {
+ var info: PeerInfo = undefined;
+ try errors.espCheckError(sys.esp_now_fetch_peer(from_head, @ptrCast(&info)));
+ return info;
+ }
+
+ pub fn isPeerExist(peer_addr: *const [MAC_LEN]u8) bool {
+ return sys.esp_now_is_peer_exist(peer_addr);
+ }
+
+ pub fn getPeerNum() !PeerNum {
+ var num: PeerNum = undefined;
+ try errors.espCheckError(sys.esp_now_get_peer_num(@ptrCast(&num)));
+ return num;
+ }
+
+ // -- Security ------------------------------------------------------------
+
+ /// Set the Primary Master Key (16 bytes).
+ pub fn setPmk(pmk: *const [PMK_LEN]u8) !void {
+ try errors.espCheckError(sys.esp_now_set_pmk(pmk));
+ }
+
+ // -- Power saving --------------------------------------------------------
+
+ pub fn setWakeWindow(window: u16) !void {
+ try errors.espCheckError(sys.esp_now_set_wake_window(window));
+ }
+
+ // -- OUI -----------------------------------------------------------------
+
+ /// Set user OUI (3 bytes).
+ pub fn setUserOui(oui: *[OUI_LEN]u8) !void {
+ try errors.espCheckError(sys.esp_now_set_user_oui(oui));
+ }
+
+ /// Get user OUI (3 bytes).
+ pub fn getUserOui(oui: *[OUI_LEN]u8) !void {
+ try errors.espCheckError(sys.esp_now_get_user_oui(oui));
+ }
+
+ // -- Channel actions -----------------------------------------------------
+
+ pub fn switchChannel(config: *SwitchChannel) !void {
+ try errors.espCheckError(sys.esp_now_switch_channel_tx(@ptrCast(config)));
+ }
+
+ pub fn remainOnChannel(config: *RemainOnChannel) !void {
+ try errors.espCheckError(sys.esp_now_remain_on_channel(@ptrCast(config)));
+ }
+};
+
+// ---------------------------------------------------------------------------
+// SmartConfig — bundled here because the raw bindings file included it.
+// ---------------------------------------------------------------------------
+
+pub const SmartconfigType = enum(c_uint) {
+ esptouch = 0,
+ airkiss = 1,
+ esptouch_airkiss = 2,
+ esptouch_v2 = 3,
+};
+
+pub const SmartconfigEvent = enum(c_uint) {
+ scan_done = 0,
+ found_channel = 1,
+ got_ssid_pswd = 2,
+ send_ack_done = 3,
+};
+
+pub const SmartconfigGotSsidPswd = extern struct {
+ ssid: [32]u8 = .{0} ** 32,
+ password: [64]u8 = .{0} ** 64,
+ bssid_set: bool = false,
+ bssid: [MAC_LEN]u8 = .{0} ** MAC_LEN,
+ type: SmartconfigType = .esptouch,
+ token: u8 = 0,
+ cellphone_ip: [4]u8 = .{0} ** 4,
+};
+
+pub const SmartConfig = struct {
+ pub fn getVersion() [*:0]const u8 {
+ return sys.esp_smartconfig_get_version();
+ }
+
+ pub fn start(config: *const sys.smartconfig_start_config_t) !void {
+ try errors.espCheckError(sys.esp_smartconfig_start(config));
+ }
+
+ pub fn stop() !void {
+ try errors.espCheckError(sys.esp_smartconfig_stop());
+ }
+
+ pub fn setTimeout(time_s: u8) !void {
+ try errors.espCheckError(sys.esp_esptouch_set_timeout(time_s));
+ }
+
+ pub fn setType(kind: SmartconfigType) !void {
+ try errors.espCheckError(sys.esp_smartconfig_set_type(@intFromEnum(kind)));
+ }
+
+ pub fn setFastMode(enable: bool) !void {
+ try errors.espCheckError(sys.esp_smartconfig_fast_mode(enable));
+ }
+
+ pub fn getRvdData(buf: []u8) !void {
+ try errors.espCheckError(sys.esp_smartconfig_get_rvd_data(buf.ptr, @intCast(buf.len)));
+ }
+};
diff --git a/software/zig_main/imports/nvs.zig b/software/zig_main/imports/nvs.zig
new file mode 100644
index 0000000..ea35c11
--- /dev/null
+++ b/software/zig_main/imports/nvs.zig
@@ -0,0 +1,292 @@
+const sys = @import("sys");
+const errors = @import("error");
+
+// ---------------------------------------------------------------------------
+// Type aliases
+// ---------------------------------------------------------------------------
+
+pub const Handle = sys.nvs_handle_t;
+pub const Iterator = sys.nvs_iterator_t;
+pub const EntryInfo = sys.nvs_entry_info_t;
+pub const Stats = sys.nvs_stats_t;
+
+pub const OpenMode = enum(sys.nvs_open_mode_t) {
+ read_only = sys.NVS_READONLY,
+ read_write = sys.NVS_READWRITE,
+};
+
+pub const Type = enum(sys.nvs_type_t) {
+ u8 = sys.NVS_TYPE_U8,
+ i8 = sys.NVS_TYPE_I8,
+ u16 = sys.NVS_TYPE_U16,
+ i16 = sys.NVS_TYPE_I16,
+ u32 = sys.NVS_TYPE_U32,
+ i32 = sys.NVS_TYPE_I32,
+ u64 = sys.NVS_TYPE_U64,
+ i64 = sys.NVS_TYPE_I64,
+ str = sys.NVS_TYPE_STR,
+ blob = sys.NVS_TYPE_BLOB,
+ any = sys.NVS_TYPE_ANY,
+};
+
+// ---------------------------------------------------------------------------
+// Flash initialisation
+// ---------------------------------------------------------------------------
+
+/// Initialise NVS flash with the default "nvs" partition.
+/// Call once at boot before opening any handles.
+pub fn flashInit() !void {
+ try errors.espCheckError(sys.nvs_flash_init());
+}
+
+/// Initialise NVS flash on an explicitly named partition.
+pub fn flashInitPartition(partition_label: [*:0]const u8) !void {
+ try errors.espCheckError(sys.nvs_flash_init_partition(partition_label));
+}
+
+/// Initialise NVS flash, erasing the default partition if it has no free pages
+/// or if an incompatible version was found. This is the pattern recommended by
+/// Espressif for robust boot-time NVS initialisation.
+pub fn flashInitOrErase() !void {
+ const rc = sys.nvs_flash_init();
+ if (rc == sys.ESP_ERR_NVS_NO_FREE_PAGES or rc == sys.ESP_ERR_NVS_NEW_VERSION_FOUND) {
+ try errors.espCheckError(sys.nvs_flash_erase());
+ try errors.espCheckError(sys.nvs_flash_init());
+ } else {
+ try errors.espCheckError(rc);
+ }
+}
+
+/// De-initialise the default NVS partition.
+pub fn flashDeinit() !void {
+ try errors.espCheckError(sys.nvs_flash_deinit());
+}
+
+/// De-initialise a named NVS partition.
+pub fn flashDeinitPartition(partition_label: [*:0]const u8) !void {
+ try errors.espCheckError(sys.nvs_flash_deinit_partition(partition_label));
+}
+
+/// Erase all data in the default NVS partition (loses all stored values).
+pub fn flashErase() !void {
+ try errors.espCheckError(sys.nvs_flash_erase());
+}
+
+/// Erase all data in a named NVS partition.
+pub fn flashErasePartition(part_name: [*:0]const u8) !void {
+ try errors.espCheckError(sys.nvs_flash_erase_partition(part_name));
+}
+
+// ---------------------------------------------------------------------------
+// Handle open / close
+// ---------------------------------------------------------------------------
+
+/// Open an NVS namespace in the default "nvs" partition.
+pub fn open(namespace: [*:0]const u8, mode: OpenMode) !Handle {
+ var handle: Handle = 0;
+ try errors.espCheckError(sys.nvs_open(namespace, @intFromEnum(mode), &handle));
+ return handle;
+}
+
+/// Open an NVS namespace on a specific partition.
+pub fn openFromPartition(part_name: [*:0]const u8, namespace: [*:0]const u8, mode: OpenMode) !Handle {
+ var handle: Handle = 0;
+ try errors.espCheckError(sys.nvs_open_from_partition(part_name, namespace, @intFromEnum(mode), &handle));
+ return handle;
+}
+
+/// Close an NVS handle. Must be called to release resources.
+pub fn close(handle: Handle) void {
+ sys.nvs_close(handle);
+}
+
+/// Commit pending writes. Must be called after set operations to persist data.
+pub fn commit(handle: Handle) !void {
+ try errors.espCheckError(sys.nvs_commit(handle));
+}
+
+// ---------------------------------------------------------------------------
+// Write (set) operations
+// ---------------------------------------------------------------------------
+
+pub fn setI8(handle: Handle, key: [*:0]const u8, value: i8) !void {
+ try errors.espCheckError(sys.nvs_set_i8(handle, key, value));
+}
+
+pub fn setU8(handle: Handle, key: [*:0]const u8, value: u8) !void {
+ try errors.espCheckError(sys.nvs_set_u8(handle, key, value));
+}
+
+pub fn setI16(handle: Handle, key: [*:0]const u8, value: i16) !void {
+ try errors.espCheckError(sys.nvs_set_i16(handle, key, value));
+}
+
+pub fn setU16(handle: Handle, key: [*:0]const u8, value: u16) !void {
+ try errors.espCheckError(sys.nvs_set_u16(handle, key, value));
+}
+
+pub fn setI32(handle: Handle, key: [*:0]const u8, value: i32) !void {
+ try errors.espCheckError(sys.nvs_set_i32(handle, key, value));
+}
+
+pub fn setU32(handle: Handle, key: [*:0]const u8, value: u32) !void {
+ try errors.espCheckError(sys.nvs_set_u32(handle, key, value));
+}
+
+pub fn setI64(handle: Handle, key: [*:0]const u8, value: i64) !void {
+ try errors.espCheckError(sys.nvs_set_i64(handle, key, value));
+}
+
+pub fn setU64(handle: Handle, key: [*:0]const u8, value: u64) !void {
+ try errors.espCheckError(sys.nvs_set_u64(handle, key, value));
+}
+
+/// Store a null-terminated string. The key limit is 15 characters.
+pub fn setStr(handle: Handle, key: [*:0]const u8, value: [*:0]const u8) !void {
+ try errors.espCheckError(sys.nvs_set_str(handle, key, value));
+}
+
+/// Store a binary blob of arbitrary length.
+pub fn setBlob(handle: Handle, key: [*:0]const u8, value: []const u8) !void {
+ try errors.espCheckError(sys.nvs_set_blob(handle, key, value.ptr, value.len));
+}
+
+// ---------------------------------------------------------------------------
+// Read (get) operations
+// ---------------------------------------------------------------------------
+
+pub fn getI8(handle: Handle, key: [*:0]const u8) !i8 {
+ var v: i8 = 0;
+ try errors.espCheckError(sys.nvs_get_i8(handle, key, &v));
+ return v;
+}
+
+pub fn getU8(handle: Handle, key: [*:0]const u8) !u8 {
+ var v: u8 = 0;
+ try errors.espCheckError(sys.nvs_get_u8(handle, key, &v));
+ return v;
+}
+
+pub fn getI16(handle: Handle, key: [*:0]const u8) !i16 {
+ var v: i16 = 0;
+ try errors.espCheckError(sys.nvs_get_i16(handle, key, &v));
+ return v;
+}
+
+pub fn getU16(handle: Handle, key: [*:0]const u8) !u16 {
+ var v: u16 = 0;
+ try errors.espCheckError(sys.nvs_get_u16(handle, key, &v));
+ return v;
+}
+
+pub fn getI32(handle: Handle, key: [*:0]const u8) !i32 {
+ var v: i32 = 0;
+ try errors.espCheckError(sys.nvs_get_i32(handle, key, &v));
+ return v;
+}
+
+pub fn getU32(handle: Handle, key: [*:0]const u8) !u32 {
+ var v: u32 = 0;
+ try errors.espCheckError(sys.nvs_get_u32(handle, key, &v));
+ return v;
+}
+
+pub fn getI64(handle: Handle, key: [*:0]const u8) !i64 {
+ var v: i64 = 0;
+ try errors.espCheckError(sys.nvs_get_i64(handle, key, &v));
+ return v;
+}
+
+pub fn getU64(handle: Handle, key: [*:0]const u8) !u64 {
+ var v: u64 = 0;
+ try errors.espCheckError(sys.nvs_get_u64(handle, key, &v));
+ return v;
+}
+
+/// Read a string value.
+/// Pass `buf = null` with `buf_len` pointing to 0 first to query the required size.
+/// Then allocate `buf_len` bytes and call again.
+pub fn getStr(handle: Handle, key: [*:0]const u8, buf: ?[*]u8, buf_len: *usize) !void {
+ try errors.espCheckError(sys.nvs_get_str(handle, key, buf, buf_len));
+}
+
+/// Read a blob value.
+/// Pass `buf = null` with `buf_len` pointing to 0 first to query the required size.
+pub fn getBlob(handle: Handle, key: [*:0]const u8, buf: ?*anyopaque, buf_len: *usize) !void {
+ try errors.espCheckError(sys.nvs_get_blob(handle, key, buf, buf_len));
+}
+
+// ---------------------------------------------------------------------------
+// Key management
+// ---------------------------------------------------------------------------
+
+/// Returns the type stored for `key`, or an error if the key does not exist.
+pub fn findKey(handle: Handle, key: [*:0]const u8) !Type {
+ var t: sys.nvs_type_t = 0;
+ try errors.espCheckError(sys.nvs_find_key(handle, key, &t));
+ return @enumFromInt(t);
+}
+
+/// Erase a single key from the namespace.
+pub fn eraseKey(handle: Handle, key: [*:0]const u8) !void {
+ try errors.espCheckError(sys.nvs_erase_key(handle, key));
+}
+
+/// Erase all keys in the namespace.
+pub fn eraseAll(handle: Handle) !void {
+ try errors.espCheckError(sys.nvs_erase_all(handle));
+}
+
+// ---------------------------------------------------------------------------
+// Statistics
+// ---------------------------------------------------------------------------
+
+/// Get storage statistics for a partition (pass `null` for the default "nvs" partition).
+pub fn getStats(part_name: ?[*:0]const u8, stats: *Stats) !void {
+ try errors.espCheckError(sys.nvs_get_stats(part_name, stats));
+}
+
+/// Get the number of used entries for an open handle's namespace.
+pub fn getUsedEntryCount(handle: Handle) !usize {
+ var count: usize = 0;
+ try errors.espCheckError(sys.nvs_get_used_entry_count(handle, &count));
+ return count;
+}
+
+// ---------------------------------------------------------------------------
+// Iteration
+// ---------------------------------------------------------------------------
+
+/// Find an iterator over entries matching the given partition, namespace, and type.
+/// Release with `releaseIterator` when done.
+pub fn entryFind(
+ part_name: ?[*:0]const u8,
+ namespace: ?[*:0]const u8,
+ nvs_type: Type,
+) !Iterator {
+ var it: Iterator = null;
+ try errors.espCheckError(sys.nvs_entry_find(part_name, namespace, @intFromEnum(nvs_type), &it));
+ return it;
+}
+
+/// Find an iterator over entries in an open handle matching the given type.
+pub fn entryFindInHandle(handle: Handle, nvs_type: Type) !Iterator {
+ var it: Iterator = null;
+ try errors.espCheckError(sys.nvs_entry_find_in_handle(handle, @intFromEnum(nvs_type), &it));
+ return it;
+}
+
+/// Advance the iterator to the next entry. Returns error when no more entries.
+pub fn entryNext(it: *Iterator) !void {
+ try errors.espCheckError(sys.nvs_entry_next(it));
+}
+
+/// Fill `info` with details about the current iterator position.
+pub fn entryInfo(it: Iterator, info: *EntryInfo) !void {
+ try errors.espCheckError(sys.nvs_entry_info(it, info));
+}
+
+/// Release an iterator obtained with `entryFind`.
+pub fn releaseIterator(it: Iterator) void {
+ sys.nvs_release_iterator(it);
+}
diff --git a/software/zig_main/imports/panic.zig b/software/zig_main/imports/panic.zig
new file mode 100644
index 0000000..646fece
--- /dev/null
+++ b/software/zig_main/imports/panic.zig
@@ -0,0 +1,21 @@
+const sys = @import("sys");
+const log = @import("log");
+
+/// panic handler for esp-idf
+pub fn panic(msg: []const u8, stack_trace: ?*@import("std").builtin.StackTrace, _: ?usize) noreturn {
+ sys.esp_log_write(log.default_level, "PANIC", "[%lu ms] PANIC: %.*s\n", sys.esp_log_timestamp(), msg.len, msg.ptr);
+
+ // try print stack trace if available
+ if (stack_trace) |st| {
+ var i: usize = st.index;
+ if (i > st.instruction_addresses.len) i = st.instruction_addresses.len;
+ var idx: usize = 0;
+ while (idx < i) : (idx += 1) {
+ sys.esp_log_write(log.default_level, "PANIC", " #%u: 0x%08lx\n", idx, st.instruction_addresses[idx]);
+ }
+ }
+
+ while (true) {
+ asm volatile ("" ::: .{ .memory = true });
+ }
+}
diff --git a/software/zig_main/imports/partition.zig b/software/zig_main/imports/partition.zig
new file mode 100644
index 0000000..6a0425a
--- /dev/null
+++ b/software/zig_main/imports/partition.zig
@@ -0,0 +1,171 @@
+const sys = @import("sys");
+const errors = @import("error");
+
+// ---------------------------------------------------------------------------
+// Type aliases
+// ---------------------------------------------------------------------------
+
+pub const Partition = sys.esp_partition_t;
+pub const Iterator = sys.esp_partition_iterator_t;
+pub const MmapHandle = sys.esp_partition_mmap_handle_t;
+
+pub const Type = enum(sys.esp_partition_type_t) {
+ app = sys.ESP_PARTITION_TYPE_APP,
+ data = sys.ESP_PARTITION_TYPE_DATA,
+ bootloader = sys.ESP_PARTITION_TYPE_BOOTLOADER,
+ partition_table = sys.ESP_PARTITION_TYPE_PARTITION_TABLE,
+ any = sys.ESP_PARTITION_TYPE_ANY,
+};
+
+pub const Subtype = enum(sys.esp_partition_subtype_t) {
+ // APP subtypes
+ app_factory = sys.ESP_PARTITION_SUBTYPE_APP_FACTORY,
+ app_ota_0 = sys.ESP_PARTITION_SUBTYPE_APP_OTA_0,
+ app_ota_1 = sys.ESP_PARTITION_SUBTYPE_APP_OTA_1,
+ app_ota_2 = sys.ESP_PARTITION_SUBTYPE_APP_OTA_2,
+ app_ota_3 = sys.ESP_PARTITION_SUBTYPE_APP_OTA_3,
+ app_test = sys.ESP_PARTITION_SUBTYPE_APP_TEST,
+ // DATA subtypes
+ data_ota = sys.ESP_PARTITION_SUBTYPE_DATA_OTA,
+ data_phy = sys.ESP_PARTITION_SUBTYPE_DATA_PHY,
+ data_nvs = sys.ESP_PARTITION_SUBTYPE_DATA_NVS,
+ data_coredump = sys.ESP_PARTITION_SUBTYPE_DATA_COREDUMP,
+ data_nvs_keys = sys.ESP_PARTITION_SUBTYPE_DATA_NVS_KEYS,
+ data_fat = sys.ESP_PARTITION_SUBTYPE_DATA_FAT,
+ data_spiffs = sys.ESP_PARTITION_SUBTYPE_DATA_SPIFFS,
+ // Catch-all
+ any = sys.ESP_PARTITION_SUBTYPE_ANY,
+};
+
+pub const MmapMemory = enum(sys.esp_partition_mmap_memory_t) {
+ data = sys.ESP_PARTITION_MMAP_DATA,
+ inst = sys.ESP_PARTITION_MMAP_INST,
+};
+
+// ---------------------------------------------------------------------------
+// Find
+// ---------------------------------------------------------------------------
+
+/// Return an iterator over all partitions that match `type`, `subtype`, and
+/// optional `label`. Release with `iteratorRelease` when done.
+pub fn find(part_type: Type, subtype: Subtype, label: ?[*:0]const u8) Iterator {
+ return sys.esp_partition_find(
+ @intFromEnum(part_type),
+ @intFromEnum(subtype),
+ label,
+ );
+}
+
+/// Return a pointer to the first matching partition, or null if none found.
+pub fn findFirst(part_type: Type, subtype: Subtype, label: ?[*:0]const u8) ?*const Partition {
+ return sys.esp_partition_find_first(
+ @intFromEnum(part_type),
+ @intFromEnum(subtype),
+ label,
+ );
+}
+
+// ---------------------------------------------------------------------------
+// Iterator
+// ---------------------------------------------------------------------------
+
+/// Return the partition pointed to by the iterator, or null at end.
+pub fn iteratorGet(it: Iterator) ?*const Partition {
+ return sys.esp_partition_get(it);
+}
+
+/// Advance the iterator by one step. Returns null when exhausted.
+pub fn iteratorNext(it: Iterator) Iterator {
+ return sys.esp_partition_next(it);
+}
+
+/// Release an iterator obtained via `find`.
+pub fn iteratorRelease(it: Iterator) void {
+ sys.esp_partition_iterator_release(it);
+}
+
+// ---------------------------------------------------------------------------
+// Read / Write / Erase
+// ---------------------------------------------------------------------------
+
+/// Read `size` bytes from `partition` at `src_offset` into `dst`.
+pub fn read(partition: *const Partition, src_offset: usize, dst: []u8) !void {
+ try errors.espCheckError(sys.esp_partition_read(partition, src_offset, dst.ptr, dst.len));
+}
+
+/// Write `src` bytes to `partition` at `dst_offset`.
+/// The region must have been erased first.
+pub fn write(partition: *const Partition, dst_offset: usize, src: []const u8) !void {
+ try errors.espCheckError(sys.esp_partition_write(partition, dst_offset, src.ptr, src.len));
+}
+
+/// Read without decryption (raw flash content).
+pub fn readRaw(partition: *const Partition, src_offset: usize, dst: []u8) !void {
+ try errors.espCheckError(sys.esp_partition_read_raw(partition, src_offset, dst.ptr, dst.len));
+}
+
+/// Write without encryption (raw flash write).
+pub fn writeRaw(partition: *const Partition, dst_offset: usize, src: []const u8) !void {
+ try errors.espCheckError(sys.esp_partition_write_raw(partition, dst_offset, src.ptr, src.len));
+}
+
+/// Erase `size` bytes starting at `offset`.
+/// Both `offset` and `size` must be aligned to 4096 bytes (the sector size).
+pub fn eraseRange(partition: *const Partition, offset: usize, size: usize) !void {
+ try errors.espCheckError(sys.esp_partition_erase_range(partition, offset, size));
+}
+
+// ---------------------------------------------------------------------------
+// Memory-map
+// ---------------------------------------------------------------------------
+
+/// Memory-map a region of a partition into the CPU's address space.
+/// Returns `(ptr, handle)` — call `munmap(handle)` when done.
+pub fn mmap(
+ partition: *const Partition,
+ offset: usize,
+ size: usize,
+ memory: MmapMemory,
+) !struct { ptr: *const anyopaque, handle: MmapHandle } {
+ var out_ptr: ?*const anyopaque = null;
+ var out_handle: MmapHandle = 0;
+ try errors.espCheckError(sys.esp_partition_mmap(
+ partition,
+ offset,
+ size,
+ @intFromEnum(memory),
+ &out_ptr,
+ &out_handle,
+ ));
+ return .{ .ptr = out_ptr.?, .handle = out_handle };
+}
+
+/// Unmap a previously mapped partition region.
+pub fn munmap(handle: MmapHandle) void {
+ sys.esp_partition_munmap(handle);
+}
+
+// ---------------------------------------------------------------------------
+// Utilities
+// ---------------------------------------------------------------------------
+
+/// Fill `sha_256` (must be 32 bytes) with the SHA-256 of the partition contents.
+pub fn getSha256(partition: *const Partition, sha_256: *[32]u8) !void {
+ try errors.espCheckError(sys.esp_partition_get_sha256(partition, sha_256));
+}
+
+/// Returns true if two partitions have identical content.
+pub fn checkIdentity(p1: *const Partition, p2: *const Partition) bool {
+ return sys.esp_partition_check_identity(p1, p2);
+}
+
+/// Copy `size` bytes from `src_part` (at `src_offset`) to `dest_part` (at `dest_offset`).
+pub fn copy(
+ dest_part: *const Partition,
+ dest_offset: u32,
+ src_part: *const Partition,
+ src_offset: u32,
+ size: usize,
+) !void {
+ try errors.espCheckError(sys.esp_partition_copy(dest_part, dest_offset, src_part, src_offset, size));
+}
diff --git a/software/zig_main/imports/pcnt.zig b/software/zig_main/imports/pcnt.zig
new file mode 100644
index 0000000..82f6911
--- /dev/null
+++ b/software/zig_main/imports/pcnt.zig
@@ -0,0 +1,71 @@
+const sys = @import("sys");
+const errors = @import("error");
+
+pub const PulseCounter = struct {
+ pub const watchEventData_t = sys.pcnt_watch_event_data_t;
+ pub const eventCallbacks_t = sys.pcnt_event_callbacks_t;
+ pub const glitchFilterConfig_t = sys.pcnt_glitch_filter_config_t;
+
+ pub const Unit = struct {
+ pub const config_t = sys.pcnt_unit_config_t;
+ pub const handle_t = sys.pcnt_unit_handle_t;
+
+ pub fn init(config: ?*const sys.pcnt_unit_config_t, unit: ?*sys.pcnt_unit_handle_t) !void {
+ return try errors.espCheckError(sys.pcnt_new_unit(config, unit));
+ }
+ pub fn del(unit: sys.pcnt_unit_handle_t) !void {
+ return try errors.espCheckError(sys.pcnt_del_unit(unit));
+ }
+ pub fn enable(unit: sys.pcnt_unit_handle_t) !void {
+ return try errors.espCheckError(sys.pcnt_unit_enable(unit));
+ }
+ pub fn disable(unit: sys.pcnt_unit_handle_t) !void {
+ return try errors.espCheckError(sys.pcnt_unit_disable(unit));
+ }
+ pub fn start(unit: sys.pcnt_unit_handle_t) !void {
+ return try errors.espCheckError(sys.pcnt_unit_start(unit));
+ }
+ pub fn stop(unit: sys.pcnt_unit_handle_t) !void {
+ return try errors.espCheckError(sys.pcnt_unit_stop(unit));
+ }
+ pub fn clear(unit: sys.pcnt_unit_handle_t) !void {
+ return try errors.espCheckError(sys.pcnt_unit_clear_count(unit));
+ }
+ pub fn setGlitchFilter(unit: sys.pcnt_unit_handle_t, config: ?*const sys.pcnt_glitch_filter_config_t) !void {
+ return try errors.espCheckError(sys.pcnt_unit_set_glitch_filter(unit, config));
+ }
+ pub fn getCount(unit: sys.pcnt_unit_handle_t, value: ?*c_int) !void {
+ return try errors.espCheckError(sys.pcnt_unit_get_count(unit, value));
+ }
+ pub fn registerEventCallbacks(unit: sys.pcnt_unit_handle_t, cbs: ?*const sys.pcnt_event_callbacks_t, user_data: ?*anyopaque) !void {
+ return try errors.espCheckError(sys.pcnt_unit_register_event_callbacks(unit, cbs, user_data));
+ }
+ pub fn addWatchPoint(unit: sys.pcnt_unit_handle_t, watch_point: c_int) !void {
+ return try errors.espCheckError(sys.pcnt_unit_add_watch_point(unit, watch_point));
+ }
+ pub fn removeWatchPoint(unit: sys.pcnt_unit_handle_t, watch_point: c_int) !void {
+ return try errors.espCheckError(sys.pcnt_unit_remove_watch_point(unit, watch_point));
+ }
+ };
+ pub const Channel = struct {
+ pub const handle_t = sys.pcnt_channel_handle_t;
+ pub const config_t = sys.pcnt_chan_config_t;
+ pub const edgeAction_t = sys.pcnt_channel_edge_action_t;
+ pub const level_t = sys.pcnt_channel_level_t;
+
+ pub fn init(unit: sys.pcnt_unit_handle_t, config: ?*const sys.pcnt_chan_config_t, chan: ?*sys.pcnt_channel_handle_t) !void {
+ return try errors.espCheckError(sys.pcnt_new_channel(unit, config, chan));
+ }
+ pub fn del(chan: sys.pcnt_channel_handle_t) !void {
+ return try errors.espCheckError(sys.pcnt_del_channel(chan));
+ }
+ const set = struct {
+ pub fn edgeAction(chan: sys.pcnt_channel_handle_t, pos_act: sys.pcnt_channel_edge_action_t, neg_act: sys.pcnt_channel_edge_action_t) !void {
+ return try errors.espCheckError(sys.pcnt_channel_set_edge_action(chan, pos_act, neg_act));
+ }
+ pub fn levelAction(chan: sys.pcnt_channel_handle_t, high_act: sys.pcnt_channel_level_action_t, low_act: sys.pcnt_channel_level_action_t) !void {
+ return try errors.espCheckError(sys.pcnt_channel_set_edge_action(chan, high_act, low_act));
+ }
+ };
+ };
+};
diff --git a/software/zig_main/imports/phy.zig b/software/zig_main/imports/phy.zig
new file mode 100644
index 0000000..391f868
--- /dev/null
+++ b/software/zig_main/imports/phy.zig
@@ -0,0 +1,46 @@
+const sys = @import("sys");
+
+pub const RF = struct {
+ pub fn config(conf: u8) void {
+ sys.esp_phy_rftest_config(conf);
+ }
+ pub fn init() void {
+ sys.esp_phy_rftest_init();
+ }
+};
+pub fn txContinEn(contin_en: bool) void {
+ sys.esp_phy_tx_contin_en(contin_en);
+}
+pub fn cbw40mEn(en: bool) void {
+ sys.esp_phy_cbw40m_en(en);
+}
+pub fn testStartStop(value: u8) void {
+ sys.esp_phy_test_start_stop(value);
+}
+pub const WIFI = struct {
+ pub fn tx(chan: u32, rate: sys.esp_phy_wifi_rate_t, backoff: i8, length_byte: u32, packet_delay: u32, packet_num: u32) void {
+ sys.esp_phy_wifi_tx(chan, rate, backoff, length_byte, packet_delay, packet_num);
+ }
+ pub fn rx(chan: u32, rate: sys.esp_phy_wifi_rate_t) void {
+ sys.esp_phy_wifi_rx(chan, rate);
+ }
+ pub fn txTone(start: u32, chan: u32, backoff: u32) void {
+ sys.esp_phy_wifi_tx_tone(start, chan, backoff);
+ }
+};
+pub const BLE = struct {
+ pub fn tx(txpwr: u32, chan: u32, len: u32, data_type: sys.esp_phy_ble_type_t, syncw: u32, rate: sys.esp_phy_ble_rate_t, tx_num_in: u32) void {
+ sys.esp_phy_ble_tx(txpwr, chan, len, data_type, syncw, rate, tx_num_in);
+ }
+ pub fn rx(chan: u32, syncw: u32, rate: sys.esp_phy_ble_rate_t) void {
+ sys.esp_phy_ble_rx(chan, syncw, rate);
+ }
+};
+pub const BT = struct {
+ pub fn txTone(start: u32, chan: u32, power: u32) void {
+ sys.esp_phy_bt_tx_tone(start, chan, power);
+ }
+};
+pub fn getRXResult(rx_result: [*c]sys.esp_phy_rx_result_t) void {
+ sys.esp_phy_get_rx_result(rx_result);
+}
diff --git a/software/zig_main/imports/pm.zig b/software/zig_main/imports/pm.zig
new file mode 100644
index 0000000..974f681
--- /dev/null
+++ b/software/zig_main/imports/pm.zig
@@ -0,0 +1,68 @@
+const sys = @import("sys");
+const errors = @import("error");
+
+// ---------------------------------------------------------------------------
+// Type aliases & enums
+// ---------------------------------------------------------------------------
+
+/// Unified power management configuration.
+/// Fields: `max_freq_mhz`, `min_freq_mhz`, `light_sleep_enable`.
+pub const Config = sys.esp_pm_config_t;
+
+pub const LockHandle = sys.esp_pm_lock_handle_t;
+
+/// Type of power management lock.
+/// - `cpu_freq_max`: CPU runs at its maximum configured frequency while held.
+/// - `apb_freq_max`: APB bus runs at maximum frequency while held.
+/// - `no_light_sleep`: Prevents light sleep while held.
+pub const LockType = enum(sys.esp_pm_lock_type_t) {
+ cpu_freq_max = sys.ESP_PM_CPU_FREQ_MAX,
+ apb_freq_max = sys.ESP_PM_APB_FREQ_MAX,
+ no_light_sleep = sys.ESP_PM_NO_LIGHT_SLEEP,
+};
+
+// ---------------------------------------------------------------------------
+// System-wide configuration
+// ---------------------------------------------------------------------------
+
+/// Apply a power management configuration.
+/// `config` is a pointer to an `esp_pm_config_t` struct.
+pub fn configure(config: *const Config) !void {
+ try errors.espCheckError(sys.esp_pm_configure(config));
+}
+
+/// Retrieve the current power management configuration.
+pub fn getConfiguration(config: *Config) !void {
+ try errors.espCheckError(sys.esp_pm_get_configuration(config));
+}
+
+// ---------------------------------------------------------------------------
+// Power management locks
+// ---------------------------------------------------------------------------
+
+pub const Lock = struct {
+ /// Create a new PM lock.
+ /// `lock_type`: the resource to hold.
+ /// `arg`: only used for `cpu_freq_max`/`apb_freq_max` (target MHz); pass 0 otherwise.
+ /// `name`: optional human-readable name for `esp_pm_dump_locks`.
+ pub fn create(lock_type: LockType, arg: c_int, name: [*:0]const u8) !LockHandle {
+ var handle: LockHandle = null;
+ try errors.espCheckError(sys.esp_pm_lock_create(@intFromEnum(lock_type), arg, name, &handle));
+ return handle;
+ }
+
+ /// Acquire a PM lock. Nesting is supported — matched `release` calls are required.
+ pub fn acquire(handle: LockHandle) !void {
+ try errors.espCheckError(sys.esp_pm_lock_acquire(handle));
+ }
+
+ /// Release a previously acquired PM lock.
+ pub fn release(handle: LockHandle) !void {
+ try errors.espCheckError(sys.esp_pm_lock_release(handle));
+ }
+
+ /// Delete a PM lock. The lock must not be held when deleted.
+ pub fn delete(handle: LockHandle) !void {
+ try errors.espCheckError(sys.esp_pm_lock_delete(handle));
+ }
+};
diff --git a/software/zig_main/imports/pthread.zig b/software/zig_main/imports/pthread.zig
new file mode 100644
index 0000000..6d9301d
--- /dev/null
+++ b/software/zig_main/imports/pthread.zig
@@ -0,0 +1,233 @@
+// Zig wrappers for ESP-IDF's POSIX pthread implementation (backed by FreeRTOS).
+//
+// Symbols come from idf-sys.zig (generated by `zig translate-c include/stubs.h`
+// with ESP_IDF_COMP_PTHREAD_ENABLED defined, which pulls in pthread.h and
+// esp_pthread.h).
+//
+// Error handling:
+// POSIX pthread_* functions return 0 on success, errno on failure → `check()`.
+// ESP-IDF esp_pthread_* functions return esp_err_t → `errors.espCheckError()`.
+
+const sys = @import("sys");
+const errors = @import("error");
+
+// ---------------------------------------------------------------------------
+// Error helper for POSIX-style return codes
+// ---------------------------------------------------------------------------
+
+pub const Error = error{PthreadError};
+
+fn check(rc: c_int) Error!void {
+ if (rc != 0) return error.PthreadError;
+}
+
+// ---------------------------------------------------------------------------
+// Type aliases (from idf-sys)
+// ---------------------------------------------------------------------------
+
+pub const Thread_t = sys.pthread_t;
+pub const Attr_t = sys.pthread_attr_t;
+pub const Mutex_t = sys.pthread_mutex_t;
+pub const MutexAttr_t = sys.pthread_mutexattr_t;
+pub const Cond_t = sys.pthread_cond_t;
+pub const CondAttr_t = sys.pthread_condattr_t;
+pub const Key_t = sys.pthread_key_t;
+pub const Once_t = sys.pthread_once_t;
+pub const SchedParam = sys.sched_param;
+pub const Timespec = sys.timespec;
+
+/// ESP-IDF-specific pthread configuration (stack size, priority, core pinning, etc.).
+pub const EspCfg = sys.esp_pthread_cfg_t;
+
+// ---------------------------------------------------------------------------
+// Thread — lifecycle & identity
+// ---------------------------------------------------------------------------
+
+pub const Thread = struct {
+ /// Create a thread with optional attributes. Returns the thread ID.
+ pub fn create(
+ attr: ?*const Attr_t,
+ start_fn: *const fn (?*anyopaque) callconv(.c) ?*anyopaque,
+ arg: ?*anyopaque,
+ ) !Thread_t {
+ var tid: Thread_t = undefined;
+ try check(sys.pthread_create(&tid, attr, start_fn, arg));
+ return tid;
+ }
+
+ /// Wait for a thread to terminate; returns its exit value.
+ pub fn join(tid: Thread_t) !?*anyopaque {
+ var value: ?*anyopaque = null;
+ try check(sys.pthread_join(tid, &value));
+ return value;
+ }
+
+ /// Detach a thread so its resources are freed automatically on exit.
+ pub fn detach(tid: Thread_t) !void {
+ try check(sys.pthread_detach(tid));
+ }
+
+ /// Terminate the calling thread with `retval`.
+ pub fn exit(retval: ?*anyopaque) noreturn {
+ sys.pthread_exit(retval);
+ unreachable;
+ }
+
+ /// Return the thread ID of the calling thread.
+ pub fn self() Thread_t {
+ return sys.pthread_self();
+ }
+
+ /// Return `true` if two thread IDs refer to the same thread.
+ pub fn equal(t1: Thread_t, t2: Thread_t) bool {
+ return sys.pthread_equal(t1, t2) != 0;
+ }
+
+ /// Yield the processor to another ready thread.
+ pub fn yield() void {
+ sys.pthread_yield();
+ }
+};
+
+// ---------------------------------------------------------------------------
+// Thread attributes
+// ---------------------------------------------------------------------------
+
+pub const Attr = struct {
+ pub fn init(attr: *Attr_t) !void {
+ try check(sys.pthread_attr_init(attr));
+ }
+ pub fn deinit(attr: *Attr_t) !void {
+ try check(sys.pthread_attr_destroy(attr));
+ }
+ pub fn setStackSize(attr: *Attr_t, size: usize) !void {
+ try check(sys.pthread_attr_setstacksize(attr, size));
+ }
+ pub fn getStackSize(attr: *const Attr_t) !usize {
+ var sz: usize = 0;
+ try check(sys.pthread_attr_getstacksize(attr, &sz));
+ return sz;
+ }
+ pub fn setDetachState(attr: *Attr_t, state: c_int) !void {
+ try check(sys.pthread_attr_setdetachstate(attr, state));
+ }
+ pub fn getDetachState(attr: *const Attr_t) !c_int {
+ var s: c_int = 0;
+ try check(sys.pthread_attr_getdetachstate(attr, &s));
+ return s;
+ }
+ pub fn setSchedParam(attr: *Attr_t, param: *const SchedParam) !void {
+ try check(sys.pthread_attr_setschedparam(attr, param));
+ }
+ pub fn getSchedParam(attr: *const Attr_t) !SchedParam {
+ var p: SchedParam = .{};
+ try check(sys.pthread_attr_getschedparam(attr, &p));
+ return p;
+ }
+};
+
+// ---------------------------------------------------------------------------
+// Mutex
+// ---------------------------------------------------------------------------
+
+pub const Mutex = struct {
+ pub fn init(mutex: *Mutex_t, attr: ?*const MutexAttr_t) !void {
+ try check(sys.pthread_mutex_init(mutex, attr));
+ }
+ pub fn deinit(mutex: *Mutex_t) !void {
+ try check(sys.pthread_mutex_destroy(mutex));
+ }
+ pub fn lock(mutex: *Mutex_t) !void {
+ try check(sys.pthread_mutex_lock(mutex));
+ }
+ pub fn tryLock(mutex: *Mutex_t) !void {
+ try check(sys.pthread_mutex_trylock(mutex));
+ }
+ pub fn unlock(mutex: *Mutex_t) !void {
+ try check(sys.pthread_mutex_unlock(mutex));
+ }
+ pub fn timedLock(mutex: *Mutex_t, timeout: *const Timespec) !void {
+ try check(sys.pthread_mutex_timedlock(mutex, timeout));
+ }
+};
+
+// ---------------------------------------------------------------------------
+// Condition variable
+// ---------------------------------------------------------------------------
+
+pub const Cond = struct {
+ pub fn init(cond: *Cond_t, attr: ?*const CondAttr_t) !void {
+ try check(sys.pthread_cond_init(cond, attr));
+ }
+ pub fn deinit(cond: *Cond_t) !void {
+ try check(sys.pthread_cond_destroy(cond));
+ }
+ pub fn wait(cond: *Cond_t, mutex: *Mutex_t) !void {
+ try check(sys.pthread_cond_wait(cond, mutex));
+ }
+ pub fn timedWait(cond: *Cond_t, mutex: *Mutex_t, abstime: *const Timespec) !void {
+ try check(sys.pthread_cond_timedwait(cond, mutex, abstime));
+ }
+ pub fn signal(cond: *Cond_t) !void {
+ try check(sys.pthread_cond_signal(cond));
+ }
+ pub fn broadcast(cond: *Cond_t) !void {
+ try check(sys.pthread_cond_broadcast(cond));
+ }
+};
+
+// ---------------------------------------------------------------------------
+// Thread-local storage (TLS) keys
+// ---------------------------------------------------------------------------
+
+pub const Key = struct {
+ pub fn create(destructor: ?*const fn (?*anyopaque) callconv(.c) void) !Key_t {
+ var key: Key_t = undefined;
+ try check(sys.pthread_key_create(&key, destructor));
+ return key;
+ }
+ pub fn delete(key: Key_t) !void {
+ try check(sys.pthread_key_delete(key));
+ }
+ pub fn setSpecific(key: Key_t, value: ?*const anyopaque) !void {
+ try check(sys.pthread_setspecific(key, value));
+ }
+ pub fn getSpecific(key: Key_t) ?*anyopaque {
+ return sys.pthread_getspecific(key);
+ }
+};
+
+// ---------------------------------------------------------------------------
+// Once-only initialisation
+// ---------------------------------------------------------------------------
+
+pub fn once(once_ctrl: *Once_t, init_fn: *const fn () callconv(.c) void) !void {
+ try check(sys.pthread_once(once_ctrl, init_fn));
+}
+
+// ---------------------------------------------------------------------------
+// ESP-IDF pthread extensions (esp_pthread.h)
+// ---------------------------------------------------------------------------
+
+pub const EspThread = struct {
+ /// Return a default ESP pthread configuration populated from menuconfig values.
+ pub fn getDefaultConfig() EspCfg {
+ return sys.esp_pthread_get_default_config();
+ }
+
+ /// Set per-thread creation parameters for the next `pthread_create()` call.
+ /// Overrides stack size, priority, core affinity, and thread name.
+ pub fn setConfig(cfg: *const EspCfg) !void {
+ try errors.espCheckError(sys.esp_pthread_set_cfg(cfg));
+ }
+
+ /// Retrieve the currently active ESP pthread configuration.
+ pub fn getConfig(cfg: *EspCfg) !void {
+ try errors.espCheckError(sys.esp_pthread_get_cfg(cfg));
+ }
+
+ /// Initialise the ESP pthread library (called internally by ESP-IDF startup).
+ pub fn init() !void {
+ try errors.espCheckError(sys.esp_pthread_init());
+ }
+};
diff --git a/software/zig_main/imports/rtos.zig b/software/zig_main/imports/rtos.zig
new file mode 100644
index 0000000..e9060c6
--- /dev/null
+++ b/software/zig_main/imports/rtos.zig
@@ -0,0 +1,734 @@
+const sys = @import("sys");
+const errors = @import("error");
+
+// ---------------------------------------------------------------------------
+// Type aliases — use these instead of raw C types at call sites.
+// ---------------------------------------------------------------------------
+
+pub const TaskHandle = sys.TaskHandle_t;
+pub const QueueHandle = sys.QueueHandle_t;
+pub const SemaphoreHandle = sys.QueueHandle_t;
+pub const TimerHandle = sys.TimerHandle_t;
+pub const EventGroupHandle = sys.EventGroupHandle_t;
+pub const StreamBufferHandle = sys.StreamBufferHandle_t;
+pub const RingbufHandle = sys.RingbufHandle_t;
+pub const TickType = sys.TickType_t;
+pub const BaseType = sys.BaseType_t;
+pub const UBaseType = sys.UBaseType_t;
+
+pub const portMAX_DELAY = sys.portMAX_DELAY;
+pub const portTICK_PERIOD_MS = sys.portTICK_PERIOD_MS;
+
+// ---------------------------------------------------------------------------
+// Tick conversion helpers.
+// ---------------------------------------------------------------------------
+
+/// Convert milliseconds to ticks (truncating, matching FreeRTOS pdMS_TO_TICKS).
+pub inline fn msToTicks(ms: TickType) TickType {
+ return @divTrunc(ms * sys.configTICK_RATE_HZ, 1000);
+}
+
+/// Convert ticks to milliseconds (truncating).
+pub inline fn ticksToMs(ticks: TickType) TickType {
+ return @divTrunc(ticks * 1000, sys.configTICK_RATE_HZ);
+}
+
+/// Returns true if a FreeRTOS BaseType_t return value indicates success.
+inline fn pdOK(ret: BaseType) bool {
+ return ret == sys.pdTRUE;
+}
+
+// ---------------------------------------------------------------------------
+// Task
+// ---------------------------------------------------------------------------
+
+pub const Task = struct {
+ pub const Handle = TaskHandle;
+ pub const Function = sys.TaskFunction_t;
+
+ /// Create a task pinned to a specific core (pass `tskNO_AFFINITY` for any core).
+ pub fn createPinnedToCore(
+ task_fn: Function,
+ name: [*:0]const u8,
+ stack_size: u32,
+ param: ?*anyopaque,
+ priority: UBaseType,
+ core_id: BaseType,
+ ) !Handle {
+ var handle: Handle = null;
+ if (!pdOK(sys.xTaskCreatePinnedToCore(task_fn, name, stack_size, param, priority, &handle, core_id)))
+ return error.TaskCreateFailed;
+ return handle;
+ }
+
+ /// Create a task with no core affinity.
+ pub fn create(
+ task_fn: Function,
+ name: [*:0]const u8,
+ stack_size: u32,
+ param: ?*anyopaque,
+ priority: UBaseType,
+ ) !Handle {
+ var handle: Handle = null;
+ if (!pdOK(sys.xTaskCreate(task_fn, name, stack_size, param, priority, &handle)))
+ return error.TaskCreateFailed;
+ return handle;
+ }
+
+ pub fn createStaticPinnedToCore(
+ task_fn: Function,
+ name: [*:0]const u8,
+ stack_size: u32,
+ param: ?*anyopaque,
+ priority: UBaseType,
+ stack_buf: [*]sys.StackType_t,
+ task_buf: *sys.StaticTask_t,
+ core_id: BaseType,
+ ) Handle {
+ return sys.xTaskCreateStaticPinnedToCore(task_fn, name, stack_size, param, priority, stack_buf, task_buf, core_id);
+ }
+
+ pub fn delete(handle: ?Handle) void {
+ sys.vTaskDelete(handle);
+ }
+
+ pub fn delay(ticks: TickType) void {
+ sys.vTaskDelay(ticks);
+ }
+
+ pub fn delayMs(ms: TickType) void {
+ sys.vTaskDelay(msToTicks(ms));
+ }
+
+ pub fn delayUntil(prev_wake: *TickType, increment: TickType) bool {
+ return pdOK(sys.xTaskDelayUntil(prev_wake, increment));
+ }
+
+ pub fn @"suspend"(handle: ?Handle) void {
+ sys.vTaskSuspend(handle);
+ }
+
+ pub fn @"resume"(handle: Handle) void {
+ sys.vTaskResume(handle);
+ }
+
+ pub fn resumeFromISR(handle: Handle) bool {
+ return pdOK(sys.xTaskResumeFromISR(handle));
+ }
+
+ pub fn priorityGet(handle: ?Handle) UBaseType {
+ return sys.uxTaskPriorityGet(handle);
+ }
+
+ pub fn prioritySet(handle: ?Handle, priority: UBaseType) void {
+ sys.vTaskPrioritySet(handle, priority);
+ }
+
+ pub fn getName(handle: ?Handle) [*:0]const u8 {
+ return sys.pcTaskGetName(handle);
+ }
+
+ pub fn getHandle(name: [*:0]const u8) ?Handle {
+ return sys.xTaskGetHandle(name);
+ }
+
+ pub fn getCurrent() Handle {
+ return sys.xTaskGetCurrentTaskHandle();
+ }
+
+ pub fn getTickCount() TickType {
+ return sys.xTaskGetTickCount();
+ }
+
+ pub fn getTickCountFromISR() TickType {
+ return sys.xTaskGetTickCountFromISR();
+ }
+
+ pub fn getNumberOfTasks() UBaseType {
+ return sys.uxTaskGetNumberOfTasks();
+ }
+
+ pub fn getStackHighWaterMark(handle: ?Handle) UBaseType {
+ return sys.uxTaskGetStackHighWaterMark(handle);
+ }
+
+ pub fn getSchedulerState() BaseType {
+ return sys.xTaskGetSchedulerState();
+ }
+
+ pub fn abortDelay(handle: Handle) bool {
+ return pdOK(sys.xTaskAbortDelay(handle));
+ }
+
+ pub fn startScheduler() void {
+ sys.vTaskStartScheduler();
+ }
+
+ pub fn endScheduler() void {
+ sys.vTaskEndScheduler();
+ }
+
+ pub fn suspendAll() void {
+ sys.vTaskSuspendAll();
+ }
+
+ pub fn resumeAll() bool {
+ return pdOK(sys.xTaskResumeAll());
+ }
+
+ pub fn setThreadLocalStoragePointer(handle: ?Handle, index: BaseType, value: ?*anyopaque) void {
+ sys.vTaskSetThreadLocalStoragePointer(handle, index, value);
+ }
+
+ pub fn getThreadLocalStoragePointer(handle: ?Handle, index: BaseType) ?*anyopaque {
+ return sys.pvTaskGetThreadLocalStoragePointer(handle, index);
+ }
+
+ /// Send a direct-to-task notification.
+ pub fn notify(
+ handle: Handle,
+ value: u32,
+ action: sys.eNotifyAction,
+ index: UBaseType,
+ ) !void {
+ if (!pdOK(sys.xTaskGenericNotify(handle, index, value, action, null)))
+ return error.TaskNotifyFailed;
+ }
+
+ pub fn notifyWait(
+ index: UBaseType,
+ clear_on_entry: u32,
+ clear_on_exit: u32,
+ out_value: ?*u32,
+ ticks_to_wait: TickType,
+ ) bool {
+ return pdOK(sys.xTaskGenericNotifyWait(index, clear_on_entry, clear_on_exit, out_value, ticks_to_wait));
+ }
+};
+
+// ---------------------------------------------------------------------------
+// Queue
+// ---------------------------------------------------------------------------
+
+pub const Queue = struct {
+ pub const Handle = QueueHandle;
+
+ pub fn create(length: UBaseType, item_size: UBaseType) !Handle {
+ const h = sys.xQueueGenericCreate(length, item_size, sys.queueQUEUE_TYPE_BASE);
+ return h orelse error.QueueCreateFailed;
+ }
+
+ pub fn createStatic(
+ length: UBaseType,
+ item_size: UBaseType,
+ storage: [*]u8,
+ buf: *sys.StaticQueue_t,
+ ) Handle {
+ return sys.xQueueGenericCreateStatic(length, item_size, storage, buf, sys.queueQUEUE_TYPE_BASE);
+ }
+
+ pub fn delete(handle: Handle) void {
+ sys.vQueueDelete(handle);
+ }
+
+ pub fn reset(handle: Handle) bool {
+ return pdOK(sys.xQueueGenericReset(handle, 0));
+ }
+
+ pub fn send(handle: Handle, item: *const anyopaque, ticks_to_wait: TickType) bool {
+ return pdOK(sys.xQueueGenericSend(handle, item, ticks_to_wait, sys.queueSEND_TO_BACK));
+ }
+
+ pub fn sendToFront(handle: Handle, item: *const anyopaque, ticks_to_wait: TickType) bool {
+ return pdOK(sys.xQueueGenericSend(handle, item, ticks_to_wait, sys.queueSEND_TO_FRONT));
+ }
+
+ pub fn overwrite(handle: Handle, item: *const anyopaque) void {
+ _ = sys.xQueueGenericSend(handle, item, 0, sys.queueOVERWRITE);
+ }
+
+ pub fn sendFromISR(handle: Handle, item: *const anyopaque, higher_prio_woken: *BaseType) bool {
+ return pdOK(sys.xQueueGenericSendFromISR(handle, item, higher_prio_woken, sys.queueSEND_TO_BACK));
+ }
+
+ pub fn overwriteFromISR(handle: Handle, item: *const anyopaque, higher_prio_woken: *BaseType) void {
+ _ = sys.xQueueGenericSendFromISR(handle, item, higher_prio_woken, sys.queueOVERWRITE);
+ }
+
+ pub fn receive(handle: Handle, out: *anyopaque, ticks_to_wait: TickType) bool {
+ return pdOK(sys.xQueueReceive(handle, out, ticks_to_wait));
+ }
+
+ pub fn receiveFromISR(handle: Handle, out: *anyopaque, higher_prio_woken: *BaseType) bool {
+ return pdOK(sys.xQueueReceiveFromISR(handle, out, higher_prio_woken));
+ }
+
+ pub fn peek(handle: Handle, out: *anyopaque, ticks_to_wait: TickType) bool {
+ return pdOK(sys.xQueuePeek(handle, out, ticks_to_wait));
+ }
+
+ pub fn messagesWaiting(handle: Handle) UBaseType {
+ return sys.uxQueueMessagesWaiting(handle);
+ }
+
+ pub fn spacesAvailable(handle: Handle) UBaseType {
+ return sys.uxQueueSpacesAvailable(handle);
+ }
+
+ pub fn isEmptyFromISR(handle: Handle) bool {
+ return pdOK(sys.xQueueIsQueueEmptyFromISR(handle));
+ }
+
+ pub fn isFullFromISR(handle: Handle) bool {
+ return pdOK(sys.xQueueIsQueueFullFromISR(handle));
+ }
+};
+
+// ---------------------------------------------------------------------------
+// Semaphore
+// ---------------------------------------------------------------------------
+
+pub const Semaphore = struct {
+ pub const Handle = SemaphoreHandle;
+
+ // Queue type constants (from FreeRTOS queue.h).
+ const QUEUE_TYPE_BINARY_SEMAPHORE = sys.queueQUEUE_TYPE_BINARY_SEMAPHORE;
+ const QUEUE_TYPE_MUTEX = sys.queueQUEUE_TYPE_MUTEX;
+ const QUEUE_TYPE_RECURSIVE_MUTEX = sys.queueQUEUE_TYPE_RECURSIVE_MUTEX;
+ const QUEUE_TYPE_COUNTING_SEMAPHORE = sys.queueQUEUE_TYPE_COUNTING_SEMAPHORE;
+
+ pub fn createBinary() !Handle {
+ const h = sys.xQueueGenericCreate(1, 0, QUEUE_TYPE_BINARY_SEMAPHORE);
+ return h orelse error.SemaphoreCreateFailed;
+ }
+
+ pub fn createBinaryStatic(buf: *sys.StaticQueue_t) Handle {
+ return sys.xQueueGenericCreateStatic(1, 0, null, buf, QUEUE_TYPE_BINARY_SEMAPHORE);
+ }
+
+ pub fn createMutex() !Handle {
+ const h = sys.xQueueCreateMutex(QUEUE_TYPE_MUTEX);
+ return h orelse error.SemaphoreCreateFailed;
+ }
+
+ pub fn createMutexStatic(buf: *sys.StaticQueue_t) Handle {
+ return sys.xQueueCreateMutexStatic(QUEUE_TYPE_MUTEX, buf);
+ }
+
+ pub fn createRecursiveMutex() !Handle {
+ const h = sys.xQueueCreateMutex(QUEUE_TYPE_RECURSIVE_MUTEX);
+ return h orelse error.SemaphoreCreateFailed;
+ }
+
+ pub fn createRecursiveMutexStatic(buf: *sys.StaticQueue_t) Handle {
+ return sys.xQueueCreateMutexStatic(QUEUE_TYPE_RECURSIVE_MUTEX, buf);
+ }
+
+ pub fn createCounting(max_count: UBaseType, initial_count: UBaseType) !Handle {
+ const h = sys.xQueueCreateCountingSemaphore(max_count, initial_count);
+ return h orelse error.SemaphoreCreateFailed;
+ }
+
+ pub fn createCountingStatic(max_count: UBaseType, initial_count: UBaseType, buf: *sys.StaticQueue_t) Handle {
+ return sys.xQueueCreateCountingSemaphoreStatic(max_count, initial_count, buf);
+ }
+
+ pub fn delete(handle: Handle) void {
+ sys.vQueueDelete(handle);
+ }
+
+ pub fn take(handle: Handle, ticks_to_wait: TickType) bool {
+ return pdOK(sys.xQueueSemaphoreTake(handle, ticks_to_wait));
+ }
+
+ pub fn takeRecursive(handle: Handle, ticks_to_wait: TickType) bool {
+ return pdOK(sys.xQueueTakeMutexRecursive(handle, ticks_to_wait));
+ }
+
+ pub fn takeFromISR(handle: Handle, higher_prio_woken: *BaseType) bool {
+ return pdOK(sys.xQueueReceiveFromISR(handle, null, higher_prio_woken));
+ }
+
+ pub fn give(handle: Handle) bool {
+ return pdOK(sys.xQueueGenericSend(handle, null, 0, sys.queueSEND_TO_BACK));
+ }
+
+ pub fn giveRecursive(handle: Handle) bool {
+ return pdOK(sys.xQueueGiveMutexRecursive(handle));
+ }
+
+ pub fn giveFromISR(handle: Handle, higher_prio_woken: *BaseType) bool {
+ return pdOK(sys.xQueueGiveFromISR(handle, higher_prio_woken));
+ }
+
+ pub fn getMutexHolder(handle: Handle) ?TaskHandle {
+ return sys.xQueueGetMutexHolder(handle);
+ }
+
+ pub fn getMutexHolderFromISR(handle: Handle) ?TaskHandle {
+ return sys.xQueueGetMutexHolderFromISR(handle);
+ }
+
+ pub fn getCount(handle: Handle) UBaseType {
+ return sys.uxQueueMessagesWaiting(handle);
+ }
+
+ pub fn getCountFromISR(handle: Handle) UBaseType {
+ return sys.uxQueueMessagesWaitingFromISR(handle);
+ }
+};
+
+// ---------------------------------------------------------------------------
+// Timer
+// ---------------------------------------------------------------------------
+
+pub const Timer = struct {
+ pub const Handle = TimerHandle;
+ pub const Callback = sys.TimerCallbackFunction_t;
+
+ pub fn create(
+ name: [*:0]const u8,
+ period: TickType,
+ auto_reload: bool,
+ id: ?*anyopaque,
+ callback: Callback,
+ ) !Handle {
+ const h = sys.xTimerCreate(name, period, @intFromBool(auto_reload), id, callback);
+ return h orelse error.TimerCreateFailed;
+ }
+
+ pub fn createStatic(
+ name: [*:0]const u8,
+ period: TickType,
+ auto_reload: bool,
+ id: ?*anyopaque,
+ callback: Callback,
+ buf: *sys.StaticTimer_t,
+ ) Handle {
+ return sys.xTimerCreateStatic(name, period, @intFromBool(auto_reload), id, callback, buf);
+ }
+
+ pub fn start(handle: Handle, ticks_to_wait: TickType) bool {
+ return pdOK(sys.xTimerGenericCommandFromTask(handle, sys.tmrCOMMAND_START, sys.xTaskGetTickCount(), null, ticks_to_wait));
+ }
+
+ pub fn stop(handle: Handle, ticks_to_wait: TickType) bool {
+ return pdOK(sys.xTimerGenericCommandFromTask(handle, sys.tmrCOMMAND_STOP, 0, null, ticks_to_wait));
+ }
+
+ pub fn reset(handle: Handle, ticks_to_wait: TickType) bool {
+ return pdOK(sys.xTimerGenericCommandFromTask(handle, sys.tmrCOMMAND_RESET, sys.xTaskGetTickCount(), null, ticks_to_wait));
+ }
+
+ pub fn changePeriod(handle: Handle, new_period: TickType, ticks_to_wait: TickType) bool {
+ return pdOK(sys.xTimerGenericCommandFromTask(handle, sys.tmrCOMMAND_CHANGE_PERIOD, new_period, null, ticks_to_wait));
+ }
+
+ pub fn delete(handle: Handle, ticks_to_wait: TickType) bool {
+ return pdOK(sys.xTimerGenericCommandFromTask(handle, sys.tmrCOMMAND_DELETE, 0, null, ticks_to_wait));
+ }
+
+ pub fn isActive(handle: Handle) bool {
+ return pdOK(sys.xTimerIsTimerActive(handle));
+ }
+
+ pub fn getName(handle: Handle) [*:0]const u8 {
+ return sys.pcTimerGetName(handle);
+ }
+
+ pub fn getPeriod(handle: Handle) TickType {
+ return sys.xTimerGetPeriod(handle);
+ }
+
+ pub fn getID(handle: Handle) ?*anyopaque {
+ return sys.pvTimerGetTimerID(handle);
+ }
+
+ pub fn setID(handle: Handle, id: ?*anyopaque) void {
+ sys.vTimerSetTimerID(handle, id);
+ }
+
+ pub fn setReloadMode(handle: Handle, auto_reload: bool) void {
+ sys.vTimerSetReloadMode(handle, @intFromBool(auto_reload));
+ }
+
+ pub fn pendFunctionCall(
+ callback: sys.PendedFunction_t,
+ param1: ?*anyopaque,
+ param2: u32,
+ ticks_to_wait: TickType,
+ ) bool {
+ return pdOK(sys.xTimerPendFunctionCall(callback, param1, param2, ticks_to_wait));
+ }
+
+ pub fn pendFunctionCallFromISR(
+ callback: sys.PendedFunction_t,
+ param1: ?*anyopaque,
+ param2: u32,
+ higher_prio_woken: *BaseType,
+ ) bool {
+ return pdOK(sys.xTimerPendFunctionCallFromISR(callback, param1, param2, higher_prio_woken));
+ }
+};
+
+// ---------------------------------------------------------------------------
+// EventGroup
+// ---------------------------------------------------------------------------
+
+pub const EventGroup = struct {
+ pub const Handle = EventGroupHandle;
+ pub const Bits = sys.EventBits_t;
+
+ pub fn create() !Handle {
+ const h = sys.xEventGroupCreate();
+ return h orelse error.EventGroupCreateFailed;
+ }
+
+ pub fn createStatic(buf: *sys.StaticEventGroup_t) Handle {
+ return sys.xEventGroupCreateStatic(buf);
+ }
+
+ pub fn delete(handle: Handle) void {
+ sys.vEventGroupDelete(handle);
+ }
+
+ pub fn waitBits(
+ handle: Handle,
+ bits_to_wait: Bits,
+ clear_on_exit: bool,
+ wait_for_all: bool,
+ ticks_to_wait: TickType,
+ ) Bits {
+ return sys.xEventGroupWaitBits(
+ handle,
+ bits_to_wait,
+ @intFromBool(clear_on_exit),
+ @intFromBool(wait_for_all),
+ ticks_to_wait,
+ );
+ }
+
+ pub fn setBits(handle: Handle, bits: Bits) Bits {
+ return sys.xEventGroupSetBits(handle, bits);
+ }
+
+ pub fn clearBits(handle: Handle, bits: Bits) Bits {
+ return sys.xEventGroupClearBits(handle, bits);
+ }
+
+ pub fn getBitsFromISR(handle: Handle) Bits {
+ return sys.xEventGroupGetBitsFromISR(handle);
+ }
+
+ pub fn sync(
+ handle: Handle,
+ bits_to_set: Bits,
+ bits_to_wait: Bits,
+ ticks_to_wait: TickType,
+ ) Bits {
+ return sys.xEventGroupSync(handle, bits_to_set, bits_to_wait, ticks_to_wait);
+ }
+};
+
+// ---------------------------------------------------------------------------
+// StreamBuffer
+// ---------------------------------------------------------------------------
+
+pub const StreamBuffer = struct {
+ pub const Handle = StreamBufferHandle;
+
+ pub fn create(size: usize, trigger_level: usize) !Handle {
+ const h = sys.xStreamBufferGenericCreate(size, trigger_level, 0, null, null);
+ return h orelse error.StreamBufferCreateFailed;
+ }
+
+ pub fn createStatic(
+ size: usize,
+ trigger_level: usize,
+ storage: [*]u8,
+ buf: *sys.StaticStreamBuffer_t,
+ ) Handle {
+ return sys.xStreamBufferGenericCreateStatic(size, trigger_level, 0, storage, buf, null, null);
+ }
+
+ pub fn delete(handle: Handle) void {
+ sys.vStreamBufferDelete(handle);
+ }
+
+ pub fn send(handle: Handle, data: []const u8, ticks_to_wait: TickType) usize {
+ return sys.xStreamBufferSend(handle, data.ptr, data.len, ticks_to_wait);
+ }
+
+ pub fn sendFromISR(handle: Handle, data: []const u8, higher_prio_woken: *BaseType) usize {
+ return sys.xStreamBufferSendFromISR(handle, data.ptr, data.len, higher_prio_woken);
+ }
+
+ pub fn receive(handle: Handle, buf: []u8, ticks_to_wait: TickType) usize {
+ return sys.xStreamBufferReceive(handle, buf.ptr, buf.len, ticks_to_wait);
+ }
+
+ pub fn receiveFromISR(handle: Handle, buf: []u8, higher_prio_woken: *BaseType) usize {
+ return sys.xStreamBufferReceiveFromISR(handle, buf.ptr, buf.len, higher_prio_woken);
+ }
+
+ pub fn reset(handle: Handle) bool {
+ return pdOK(sys.xStreamBufferReset(handle));
+ }
+
+ pub fn isFull(handle: Handle) bool {
+ return pdOK(sys.xStreamBufferIsFull(handle));
+ }
+
+ pub fn isEmpty(handle: Handle) bool {
+ return pdOK(sys.xStreamBufferIsEmpty(handle));
+ }
+
+ pub fn spacesAvailable(handle: Handle) usize {
+ return sys.xStreamBufferSpacesAvailable(handle);
+ }
+
+ pub fn bytesAvailable(handle: Handle) usize {
+ return sys.xStreamBufferBytesAvailable(handle);
+ }
+
+ pub fn setTriggerLevel(handle: Handle, trigger_level: usize) bool {
+ return pdOK(sys.xStreamBufferSetTriggerLevel(handle, trigger_level));
+ }
+};
+
+// ---------------------------------------------------------------------------
+// MessageBuffer (thin wrapper over StreamBuffer with a 4-byte length header).
+// ---------------------------------------------------------------------------
+
+pub const MessageBuffer = struct {
+ pub const Handle = StreamBufferHandle;
+
+ pub fn create(size: usize) !Handle {
+ const h = sys.xStreamBufferGenericCreate(size, 0, 1, null, null);
+ return h orelse error.MessageBufferCreateFailed;
+ }
+
+ pub fn delete(handle: Handle) void {
+ sys.vStreamBufferDelete(handle);
+ }
+
+ pub fn send(handle: Handle, data: []const u8, ticks_to_wait: TickType) usize {
+ return sys.xStreamBufferSend(handle, data.ptr, data.len, ticks_to_wait);
+ }
+
+ pub fn receive(handle: Handle, buf: []u8, ticks_to_wait: TickType) usize {
+ return sys.xStreamBufferReceive(handle, buf.ptr, buf.len, ticks_to_wait);
+ }
+
+ pub fn nextMessageLength(handle: Handle) usize {
+ return sys.xStreamBufferNextMessageLengthBytes(handle);
+ }
+};
+
+// ---------------------------------------------------------------------------
+// RingBuffer (ESP-IDF extension)
+// ---------------------------------------------------------------------------
+
+pub const RingBuffer = struct {
+ pub const Handle = RingbufHandle;
+ pub const Type = sys.RingbufferType_t;
+
+ pub fn create(size: usize, kind: Type) !Handle {
+ const h = sys.xRingbufferCreate(size, kind);
+ return h orelse error.RingbufferCreateFailed;
+ }
+
+ pub fn createNoSplit(item_size: usize, item_count: usize) !Handle {
+ const h = sys.xRingbufferCreateNoSplit(item_size, item_count);
+ return h orelse error.RingbufferCreateFailed;
+ }
+
+ pub fn createWithCaps(size: usize, kind: Type, caps: u32) !Handle {
+ const h = sys.xRingbufferCreateWithCaps(size, kind, caps);
+ return h orelse error.RingbufferCreateFailed;
+ }
+
+ pub fn delete(handle: Handle) void {
+ sys.vRingbufferDelete(handle);
+ }
+
+ pub fn deleteWithCaps(handle: Handle) void {
+ sys.vRingbufferDeleteWithCaps(handle);
+ }
+
+ pub fn send(handle: Handle, data: []const u8, ticks_to_wait: TickType) bool {
+ return pdOK(sys.xRingbufferSend(handle, data.ptr, data.len, ticks_to_wait));
+ }
+
+ pub fn sendFromISR(handle: Handle, data: []const u8, higher_prio_woken: *BaseType) bool {
+ return pdOK(sys.xRingbufferSendFromISR(handle, data.ptr, data.len, higher_prio_woken));
+ }
+
+ pub fn receive(handle: Handle, out_size: *usize, ticks_to_wait: TickType) ?[]u8 {
+ var size: usize = 0;
+ const ptr = sys.xRingbufferReceive(handle, &size, ticks_to_wait);
+ if (ptr == null) return null;
+ out_size.* = size;
+ return @as([*]u8, @ptrCast(ptr))[0..size];
+ }
+
+ pub fn receiveFromISR(handle: Handle, out_size: *usize) ?[]u8 {
+ var size: usize = 0;
+ const ptr = sys.xRingbufferReceiveFromISR(handle, &size);
+ if (ptr == null) return null;
+ out_size.* = size;
+ return @as([*]u8, @ptrCast(ptr))[0..size];
+ }
+
+ pub fn returnItem(handle: Handle, item: *anyopaque) void {
+ sys.vRingbufferReturnItem(handle, item);
+ }
+
+ pub fn returnItemFromISR(handle: Handle, item: *anyopaque, higher_prio_woken: *BaseType) void {
+ sys.vRingbufferReturnItemFromISR(handle, item, higher_prio_woken);
+ }
+
+ pub fn getMaxItemSize(handle: Handle) usize {
+ return sys.xRingbufferGetMaxItemSize(handle);
+ }
+
+ pub fn getCurFreeSize(handle: Handle) usize {
+ return sys.xRingbufferGetCurFreeSize(handle);
+ }
+};
+
+// ---------------------------------------------------------------------------
+// Hook (idle/tick callbacks)
+// ---------------------------------------------------------------------------
+
+pub const Hook = struct {
+ pub const Register = struct {
+ pub fn idleForCPU(cb: sys.esp_freertos_idle_cb_t, cpu_id: UBaseType) !void {
+ try errors.espCheckError(sys.esp_register_freertos_idle_hook_for_cpu(cb, cpu_id));
+ }
+ pub fn idle(cb: sys.esp_freertos_idle_cb_t) !void {
+ try errors.espCheckError(sys.esp_register_freertos_idle_hook(cb));
+ }
+ pub fn tickForCPU(cb: sys.esp_freertos_tick_cb_t, cpu_id: UBaseType) !void {
+ try errors.espCheckError(sys.esp_register_freertos_tick_hook_for_cpu(cb, cpu_id));
+ }
+ pub fn tick(cb: sys.esp_freertos_tick_cb_t) !void {
+ try errors.espCheckError(sys.esp_register_freertos_tick_hook(cb));
+ }
+ };
+
+ pub const Deregister = struct {
+ pub fn idleForCPU(cb: sys.esp_freertos_idle_cb_t, cpu_id: UBaseType) void {
+ sys.esp_deregister_freertos_idle_hook_for_cpu(cb, cpu_id);
+ }
+ pub fn idle(cb: sys.esp_freertos_idle_cb_t) void {
+ sys.esp_deregister_freertos_idle_hook(cb);
+ }
+ pub fn tickForCPU(cb: sys.esp_freertos_tick_cb_t, cpu_id: UBaseType) void {
+ sys.esp_deregister_freertos_tick_hook_for_cpu(cb, cpu_id);
+ }
+ pub fn tick(cb: sys.esp_freertos_tick_cb_t) void {
+ sys.esp_deregister_freertos_tick_hook(cb);
+ }
+ };
+};
diff --git a/software/zig_main/imports/segger.zig b/software/zig_main/imports/segger.zig
new file mode 100644
index 0000000..1871d74
--- /dev/null
+++ b/software/zig_main/imports/segger.zig
@@ -0,0 +1,219 @@
+const sys = @import("sys");
+
+/// SEGGER RTT channel flags
+pub const Flags = enum(c_uint) {
+ no_block_skip = 0,
+ no_block_trim = 1,
+ block_if_fifo_full = 2,
+ _,
+};
+
+/// Errors returned by RTT operations
+pub const Error = error{
+ /// Buffer index out of range or channel not configured
+ InvalidBuffer,
+ /// Write was skipped (no-block mode, FIFO full)
+ WriteSkipped,
+};
+
+fn rttResult(rc: c_int) Error!void {
+ if (rc < 0) return error.InvalidBuffer;
+}
+
+pub const RTT = struct {
+ // -------------------------------------------------------------------------
+ // Init
+ // -------------------------------------------------------------------------
+
+ pub fn init() void {
+ sys.SEGGER_RTT_Init();
+ }
+
+ // -------------------------------------------------------------------------
+ // Buffer allocation / configuration
+ // -------------------------------------------------------------------------
+
+ /// Allocate an additional up-buffer (target → host).
+ /// Returns the buffer index or `error.InvalidBuffer`.
+ pub fn allocUpBuffer(name: [:0]const u8, buf: []u8, flags: Flags) Error!c_uint {
+ const rc = sys.SEGGER_RTT_AllocUpBuffer(name.ptr, buf.ptr, @intCast(buf.len), @intFromEnum(flags));
+ if (rc < 0) return error.InvalidBuffer;
+ return @intCast(rc);
+ }
+
+ /// Allocate an additional down-buffer (host → target).
+ pub fn allocDownBuffer(name: [:0]const u8, buf: []u8, flags: Flags) Error!c_uint {
+ const rc = sys.SEGGER_RTT_AllocDownBuffer(name.ptr, buf.ptr, @intCast(buf.len), @intFromEnum(flags));
+ if (rc < 0) return error.InvalidBuffer;
+ return @intCast(rc);
+ }
+
+ /// Reconfigure an existing up-buffer.
+ pub fn configUpBuffer(idx: c_uint, name: [:0]const u8, buf: []u8, flags: Flags) Error!void {
+ return rttResult(sys.SEGGER_RTT_ConfigUpBuffer(idx, name.ptr, buf.ptr, @intCast(buf.len), @intFromEnum(flags)));
+ }
+
+ /// Reconfigure an existing down-buffer.
+ pub fn configDownBuffer(idx: c_uint, name: [:0]const u8, buf: []u8, flags: Flags) Error!void {
+ return rttResult(sys.SEGGER_RTT_ConfigDownBuffer(idx, name.ptr, buf.ptr, @intCast(buf.len), @intFromEnum(flags)));
+ }
+
+ // -------------------------------------------------------------------------
+ // Rename / re-flag existing channels
+ // -------------------------------------------------------------------------
+
+ pub fn setNameUpBuffer(idx: c_uint, name: [:0]const u8) Error!void {
+ return rttResult(sys.SEGGER_RTT_SetNameUpBuffer(idx, name.ptr));
+ }
+
+ pub fn setNameDownBuffer(idx: c_uint, name: [:0]const u8) Error!void {
+ return rttResult(sys.SEGGER_RTT_SetNameDownBuffer(idx, name.ptr));
+ }
+
+ pub fn setFlagsUpBuffer(idx: c_uint, flags: Flags) Error!void {
+ return rttResult(sys.SEGGER_RTT_SetFlagsUpBuffer(idx, @intFromEnum(flags)));
+ }
+
+ pub fn setFlagsDownBuffer(idx: c_uint, flags: Flags) Error!void {
+ return rttResult(sys.SEGGER_RTT_SetFlagsDownBuffer(idx, @intFromEnum(flags)));
+ }
+
+ // -------------------------------------------------------------------------
+ // Write (target → host)
+ // -------------------------------------------------------------------------
+
+ /// Write bytes to an up-buffer. Returns the number of bytes actually written.
+ pub fn write(idx: c_uint, data: []const u8) c_uint {
+ return sys.SEGGER_RTT_Write(idx, data.ptr, @intCast(data.len));
+ }
+
+ /// Write bytes without locking (call only from within a lock / ISR context).
+ pub fn writeNoLock(idx: c_uint, data: []const u8) c_uint {
+ return sys.SEGGER_RTT_WriteNoLock(idx, data.ptr, @intCast(data.len));
+ }
+
+ /// Write bytes, overwriting old data if the FIFO is full (no-lock variant).
+ pub fn writeWithOverwriteNoLock(idx: c_uint, data: []const u8) void {
+ sys.SEGGER_RTT_WriteWithOverwriteNoLock(idx, data.ptr, @intCast(data.len));
+ }
+
+ /// Write a single character to an up-buffer. Returns 1 on success, 0 if skipped.
+ pub fn putChar(idx: c_uint, c: u8) bool {
+ return sys.SEGGER_RTT_PutChar(idx, c) != 0;
+ }
+
+ /// Write a single character; skip (don't wait) if full.
+ pub fn putCharSkip(idx: c_uint, c: u8) bool {
+ return sys.SEGGER_RTT_PutCharSkip(idx, c) != 0;
+ }
+
+ /// Write a null-terminated string to an up-buffer.
+ pub fn writeString(idx: c_uint, s: [:0]const u8) c_uint {
+ return sys.SEGGER_RTT_WriteString(idx, s.ptr);
+ }
+
+ /// Formatted print to an up-buffer (wraps `SEGGER_RTT_printf`).
+ pub const printf = sys.SEGGER_RTT_printf;
+
+ /// Formatted print using a `va_list`.
+ pub fn vprintf(idx: c_uint, fmt: [:0]const u8, args: [*c]sys.va_list) Error!void {
+ return rttResult(sys.SEGGER_RTT_vprintf(idx, fmt.ptr, args));
+ }
+
+ // -------------------------------------------------------------------------
+ // Read (host → target)
+ // -------------------------------------------------------------------------
+
+ /// Read bytes from a down-buffer into `out`.
+ /// Returns the number of bytes actually read.
+ pub fn read(idx: c_uint, out: []u8) c_uint {
+ return sys.SEGGER_RTT_Read(idx, out.ptr, @intCast(out.len));
+ }
+
+ /// Read without locking — use only from a locked / ISR context.
+ pub fn readNoLock(idx: c_uint, out: []u8) c_uint {
+ return sys.SEGGER_RTT_ReadNoLock(idx, out.ptr, @intCast(out.len));
+ }
+
+ /// Read from an up-buffer (host-side mirror; rarely needed on target).
+ pub fn readUpBuffer(idx: c_uint, out: []u8) c_uint {
+ return sys.SEGGER_RTT_ReadUpBuffer(idx, out.ptr, @intCast(out.len));
+ }
+
+ pub fn readUpBufferNoLock(idx: c_uint, out: []u8) c_uint {
+ return sys.SEGGER_RTT_ReadUpBufferNoLock(idx, out.ptr, @intCast(out.len));
+ }
+
+ // -------------------------------------------------------------------------
+ // Keyboard / single-char helpers
+ // -------------------------------------------------------------------------
+
+ /// Read one byte from down-buffer 0. Returns -1 if no data available.
+ pub fn getKey() ?u8 {
+ const rc = sys.SEGGER_RTT_GetKey();
+ return if (rc < 0) null else @intCast(rc);
+ }
+
+ /// Block until a byte is available on down-buffer 0.
+ pub fn waitKey() u8 {
+ return @intCast(sys.SEGGER_RTT_WaitKey());
+ }
+
+ /// Returns `true` if at least one byte is available in the given down-buffer.
+ pub fn hasData(idx: c_uint) bool {
+ return sys.SEGGER_RTT_HasData(idx) != 0;
+ }
+
+ /// Returns `true` if at least one byte is available in down-buffer 0.
+ pub fn hasKey() bool {
+ return sys.SEGGER_RTT_HasKey() != 0;
+ }
+
+ /// Returns `true` if the given up-buffer has unread data (host hasn't read it yet).
+ pub fn hasDataUp(idx: c_uint) bool {
+ return sys.SEGGER_RTT_HasDataUp(idx) != 0;
+ }
+
+ // -------------------------------------------------------------------------
+ // Space / occupancy queries
+ // -------------------------------------------------------------------------
+
+ /// Free space available for writing in an up-buffer (bytes).
+ pub fn getAvailWriteSpace(idx: c_uint) c_uint {
+ return sys.SEGGER_RTT_GetAvailWriteSpace(idx);
+ }
+
+ /// Bytes currently pending in an up-buffer (not yet consumed by host).
+ pub fn getBytesInBuffer(idx: c_uint) c_uint {
+ return sys.SEGGER_RTT_GetBytesInBuffer(idx);
+ }
+
+ // -------------------------------------------------------------------------
+ // Multi-terminal helpers (virtual terminals over a single up-buffer)
+ // -------------------------------------------------------------------------
+
+ /// Select the active virtual terminal for subsequent writes on up-buffer 0.
+ pub fn setTerminal(id: u8) Error!void {
+ return rttResult(sys.SEGGER_RTT_SetTerminal(id));
+ }
+
+ /// Write a null-terminated string directly to a specific virtual terminal.
+ pub fn terminalOut(id: u8, s: [:0]const u8) Error!void {
+ return rttResult(sys.SEGGER_RTT_TerminalOut(id, s.ptr));
+ }
+
+ // -------------------------------------------------------------------------
+ // ESP-IDF specific flush helpers
+ // -------------------------------------------------------------------------
+
+ /// Flush the RTT up-buffer without locking.
+ /// `min_sz`: minimum bytes to flush; `tmo`: timeout in ms.
+ pub fn espFlushNoLock(min_sz: usize, tmo_ms: usize) void {
+ sys.SEGGER_RTT_ESP_FlushNoLock(@intCast(min_sz), @intCast(tmo_ms));
+ }
+
+ /// Flush the RTT up-buffer (with locking).
+ pub fn espFlush(min_sz: usize, tmo_ms: usize) void {
+ sys.SEGGER_RTT_ESP_Flush(@intCast(min_sz), @intCast(tmo_ms));
+ }
+};
diff --git a/software/zig_main/imports/sleep.zig b/software/zig_main/imports/sleep.zig
new file mode 100644
index 0000000..ea91213
--- /dev/null
+++ b/software/zig_main/imports/sleep.zig
@@ -0,0 +1,152 @@
+const sys = @import("sys");
+const errors = @import("error");
+
+// ---------------------------------------------------------------------------
+// Wakeup cause
+// ---------------------------------------------------------------------------
+
+pub const WakeupCause = enum(sys.esp_sleep_wakeup_cause_t) {
+ undefined_cause = sys.ESP_SLEEP_WAKEUP_UNDEFINED,
+ all = sys.ESP_SLEEP_WAKEUP_ALL,
+ ext0 = sys.ESP_SLEEP_WAKEUP_EXT0,
+ ext1 = sys.ESP_SLEEP_WAKEUP_EXT1,
+ timer = sys.ESP_SLEEP_WAKEUP_TIMER,
+ touchpad = sys.ESP_SLEEP_WAKEUP_TOUCHPAD,
+ ulp = sys.ESP_SLEEP_WAKEUP_ULP,
+ gpio = sys.ESP_SLEEP_WAKEUP_GPIO,
+ uart = sys.ESP_SLEEP_WAKEUP_UART,
+ wifi = sys.ESP_SLEEP_WAKEUP_WIFI,
+ bt = sys.ESP_SLEEP_WAKEUP_BT,
+ _,
+};
+
+// ---------------------------------------------------------------------------
+// Power domain configuration
+// ---------------------------------------------------------------------------
+
+pub const PdDomain = enum(sys.esp_sleep_pd_domain_t) {
+ rtc_periph = sys.ESP_PD_DOMAIN_RTC_PERIPH,
+ rtc_slow_mem = sys.ESP_PD_DOMAIN_RTC_SLOW_MEM,
+ rtc_fast_mem = sys.ESP_PD_DOMAIN_RTC_FAST_MEM,
+ xtal = sys.ESP_PD_DOMAIN_XTAL,
+ rc_fast = sys.ESP_PD_DOMAIN_RC_FAST,
+ vddsdio = sys.ESP_PD_DOMAIN_VDDSDIO,
+ modem = sys.ESP_PD_DOMAIN_MODEM,
+ max = sys.ESP_PD_DOMAIN_MAX,
+};
+
+pub const PdOption = enum(sys.esp_sleep_pd_option_t) {
+ off = sys.ESP_PD_OPTION_OFF,
+ on = sys.ESP_PD_OPTION_ON,
+ auto = sys.ESP_PD_OPTION_AUTO,
+};
+
+/// Configure the power state of a peripheral domain during sleep.
+pub fn pdConfig(domain: PdDomain, option: PdOption) !void {
+ try errors.espCheckError(sys.esp_sleep_pd_config(@intFromEnum(domain), @intFromEnum(option)));
+}
+
+// ---------------------------------------------------------------------------
+// Wakeup source configuration
+// ---------------------------------------------------------------------------
+
+/// Enable wakeup by timer after `time_in_us` microseconds.
+pub fn enableTimerWakeup(time_in_us: u64) !void {
+ try errors.espCheckError(sys.esp_sleep_enable_timer_wakeup(time_in_us));
+}
+
+/// Enable wakeup from a GPIO level change (light sleep only on most targets).
+pub fn enableGpioWakeup() !void {
+ try errors.espCheckError(sys.esp_sleep_enable_gpio_wakeup());
+}
+
+/// Enable wakeup by external signal on one RTC IO pin (ESP32 / ESP32-S* only).
+pub fn enableExt0Wakeup(gpio_num: sys.gpio_num_t, level: c_int) !void {
+ try errors.espCheckError(sys.esp_sleep_enable_ext0_wakeup(gpio_num, level));
+}
+
+/// Enable wakeup by any/all of the given RTC IO pins.
+pub fn enableExt1Wakeup(io_mask: u64, level_mode: sys.esp_sleep_ext1_wakeup_mode_t) !void {
+ try errors.espCheckError(sys.esp_sleep_enable_ext1_wakeup(io_mask, level_mode));
+}
+
+/// Enable wakeup from UART activity.
+pub fn enableUartWakeup(uart_num: c_int) !void {
+ try errors.espCheckError(sys.esp_sleep_enable_uart_wakeup(uart_num));
+}
+
+/// Enable Bluetooth as a wakeup source.
+pub fn enableBtWakeup() !void {
+ try errors.espCheckError(sys.esp_sleep_enable_bt_wakeup());
+}
+
+/// Enable WiFi as a wakeup source.
+pub fn enableWifiWakeup() !void {
+ try errors.espCheckError(sys.esp_sleep_enable_wifi_wakeup());
+}
+
+/// Enable ULP co-processor as a wakeup source.
+pub fn enableUlpWakeup() !void {
+ try errors.espCheckError(sys.esp_sleep_enable_ulp_wakeup());
+}
+
+/// Disable a wakeup source.
+pub fn disableWakeupSource(source: sys.esp_sleep_source_t) !void {
+ try errors.espCheckError(sys.esp_sleep_disable_wakeup_source(source));
+}
+
+// ---------------------------------------------------------------------------
+// Sleep entry points
+// ---------------------------------------------------------------------------
+
+/// Enter deep sleep immediately. Does not return.
+/// Configure wakeup sources before calling.
+pub fn deepSleepStart() noreturn {
+ sys.esp_deep_sleep_start();
+ unreachable;
+}
+
+/// Enter deep sleep for exactly `time_in_us` microseconds.
+/// Convenience shortcut — sets timer wakeup and enters deep sleep. Does not return.
+pub fn deepSleep(time_in_us: u64) noreturn {
+ sys.esp_deep_sleep(time_in_us);
+ unreachable;
+}
+
+/// Try to enter deep sleep. Returns an error if not all conditions are met.
+pub fn deepSleepTryToStart() !void {
+ try errors.espCheckError(sys.esp_deep_sleep_try_to_start());
+}
+
+/// Enter light sleep. Returns when a wakeup event is triggered.
+pub fn lightSleepStart() !void {
+ try errors.espCheckError(sys.esp_light_sleep_start());
+}
+
+// ---------------------------------------------------------------------------
+// Wakeup cause query
+// ---------------------------------------------------------------------------
+
+/// Get the reason the chip was last woken from sleep.
+pub fn getWakeupCause() WakeupCause {
+ return @enumFromInt(sys.esp_sleep_get_wakeup_cause());
+}
+
+/// Returns true if the wakeup cause matches `cause`.
+pub fn wasWokenBy(cause: WakeupCause) bool {
+ return getWakeupCause() == cause;
+}
+
+// ---------------------------------------------------------------------------
+// Deep sleep hooks
+// ---------------------------------------------------------------------------
+
+/// Register a callback to run just before deep sleep entry.
+pub fn registerDeepSleepHook(cb: sys.esp_deep_sleep_cb_t) !void {
+ try errors.espCheckError(sys.esp_deep_sleep_register_hook(cb));
+}
+
+/// Unregister a previously registered deep sleep hook.
+pub fn deregisterDeepSleepHook(cb: sys.esp_deep_sleep_cb_t) void {
+ sys.esp_deep_sleep_deregister_hook(cb);
+}
diff --git a/software/zig_main/imports/spi.zig b/software/zig_main/imports/spi.zig
new file mode 100644
index 0000000..bd015de
--- /dev/null
+++ b/software/zig_main/imports/spi.zig
@@ -0,0 +1,110 @@
+const sys = @import("sys");
+const errors = @import("error");
+
+// ---------------------------------------------------------------------------
+// Type aliases — callers can use these instead of the raw sys.* types.
+// ---------------------------------------------------------------------------
+
+pub const Host = sys.spi_host_device_t;
+pub const Device = sys.spi_device_handle_t;
+pub const Transaction = sys.spi_transaction_t;
+pub const ExtTransaction = sys.spi_transaction_ext_t;
+pub const DmaChan = sys.spi_dma_chan_t;
+pub const BusConfig = sys.spi_bus_config_t;
+pub const DeviceConfig = sys.spi_device_interface_config_t;
+
+// ---------------------------------------------------------------------------
+// Bus management
+// ---------------------------------------------------------------------------
+
+pub fn busInitialize(host_id: sys.spi_host_device_t, bus_config: [*c]const sys.spi_bus_config_t, dma_chan: sys.spi_dma_chan_t) !void {
+ return try errors.espCheckError(sys.spi_bus_initialize(host_id, bus_config, dma_chan));
+}
+pub fn busFree(host_id: sys.spi_host_device_t) !void {
+ return try errors.espCheckError(sys.spi_bus_free(host_id));
+}
+pub fn busAddDevice(host_id: sys.spi_host_device_t, dev_config: [*c]const sys.spi_device_interface_config_t, handle: [*c]sys.spi_device_handle_t) !void {
+ return try errors.espCheckError(sys.spi_bus_add_device(host_id, dev_config, handle));
+}
+pub fn busRemoveDevice(handle: sys.spi_device_handle_t) !void {
+ return try errors.espCheckError(sys.spi_bus_remove_device(handle));
+}
+pub fn deviceQueueTrans(handle: sys.spi_device_handle_t, trans_desc: [*c]sys.spi_transaction_t, ticks_to_wait: sys.TickType_t) !void {
+ return try errors.espCheckError(sys.spi_device_queue_trans(handle, trans_desc, ticks_to_wait));
+}
+pub fn deviceGetTransResult(handle: sys.spi_device_handle_t, trans_desc: [*c][*c]sys.spi_transaction_t, ticks_to_wait: sys.TickType_t) !void {
+ return try errors.espCheckError(sys.spi_device_get_trans_result(handle, trans_desc, ticks_to_wait));
+}
+pub fn deviceTransmit(handle: sys.spi_device_handle_t, trans_desc: [*c]sys.spi_transaction_t) !void {
+ return try errors.espCheckError(sys.spi_device_transmit(handle, trans_desc));
+}
+pub fn devicePollingStart(handle: sys.spi_device_handle_t, trans_desc: [*c]sys.spi_transaction_t, ticks_to_wait: sys.TickType_t) !void {
+ return try errors.espCheckError(sys.spi_device_polling_start(handle, trans_desc, ticks_to_wait));
+}
+pub fn devicePollingEnd(handle: sys.spi_device_handle_t, ticks_to_wait: sys.TickType_t) !void {
+ return try errors.espCheckError(sys.spi_device_polling_end(handle, ticks_to_wait));
+}
+pub fn devicePollingTransmit(handle: sys.spi_device_handle_t, trans_desc: [*c]sys.spi_transaction_t) !void {
+ return try errors.espCheckError(sys.spi_device_polling_transmit(handle, trans_desc));
+}
+pub fn deviceAcquireBus(device: sys.spi_device_handle_t, wait: sys.TickType_t) !void {
+ return try errors.espCheckError(sys.spi_device_acquire_bus(device, wait));
+}
+pub fn deviceReleaseBus(dev: sys.spi_device_handle_t) void {
+ sys.spi_device_release_bus(dev);
+}
+/// Returns the actual clock frequency used by the device in kHz.
+pub fn deviceGetActualFreq(handle: sys.spi_device_handle_t) !c_int {
+ var freq_khz: c_int = 0;
+ try errors.espCheckError(sys.spi_device_get_actual_freq(handle, &freq_khz));
+ return freq_khz;
+}
+pub fn getActualClock(fapb: c_int, hz: c_int, duty_cycle: c_int) c_int {
+ return sys.spi_get_actual_clock(fapb, hz, duty_cycle);
+}
+pub fn getTiming(gpio_is_used: bool, input_delay_ns: c_int, eff_clk: c_int, dummy_o: [*c]c_int, cycles_remain_o: [*c]c_int) void {
+ sys.spi_get_timing(gpio_is_used, input_delay_ns, eff_clk, dummy_o, cycles_remain_o);
+}
+pub fn getFreqLimit(gpio_is_used: bool, input_delay_ns: c_int) c_int {
+ return sys.spi_get_freq_limit(gpio_is_used, input_delay_ns);
+}
+/// Returns the maximum allowed transaction length in bytes for the given bus.
+pub fn busGetMaxTransactionLen(host_id: sys.spi_host_device_t) !usize {
+ var max_bytes: usize = 0;
+ try errors.espCheckError(sys.spi_bus_get_max_transaction_len(host_id, &max_bytes));
+ return max_bytes;
+}
+
+pub const SDSPI = struct {
+ pub const Host = struct {
+ pub fn init() !void {
+ return try errors.espCheckError(sys.sdspi_host_init());
+ }
+ pub fn initDevice(dev_config: [*c]const sys.sdspi_device_config_t, out_handle: [*c]sys.sdspi_dev_handle_t) !void {
+ return try errors.espCheckError(sys.sdspi_host_init_device(dev_config, out_handle));
+ }
+ pub fn removeDevice(handle: sys.sdspi_dev_handle_t) !void {
+ return try errors.espCheckError(sys.sdspi_host_remove_device(handle));
+ }
+ pub fn doTransaction(handle: sys.sdspi_dev_handle_t, cmdinfo: [*c]sys.sdmmc_command_t) !void {
+ return try errors.espCheckError(sys.sdspi_host_do_transaction(handle, cmdinfo));
+ }
+ pub fn setCardClk(host: sys.sdspi_dev_handle_t, freq_khz: u32) !void {
+ return try errors.espCheckError(sys.sdspi_host_set_card_clk(host, freq_khz));
+ }
+ pub fn getRealFreq(handle: sys.sdspi_dev_handle_t, real_freq_khz: [*c]c_int) !void {
+ return try errors.espCheckError(sys.sdspi_host_get_real_freq(handle, real_freq_khz));
+ }
+ pub fn deinit() !void {
+ return try errors.espCheckError(sys.sdspi_host_deinit());
+ }
+ pub const IO = struct {
+ pub fn intEnable(handle: sys.sdspi_dev_handle_t) !void {
+ return try errors.espCheckError(sys.sdspi_host_io_int_enable(handle));
+ }
+ pub fn intWait(handle: sys.sdspi_dev_handle_t, timeout_ticks: sys.TickType_t) !void {
+ return try errors.espCheckError(sys.sdspi_host_io_int_wait(handle, timeout_ticks));
+ }
+ };
+ };
+};
diff --git a/software/zig_main/imports/timer.zig b/software/zig_main/imports/timer.zig
new file mode 100644
index 0000000..afcadc1
--- /dev/null
+++ b/software/zig_main/imports/timer.zig
@@ -0,0 +1,96 @@
+const sys = @import("sys");
+const errors = @import("error");
+
+// ---------------------------------------------------------------------------
+// Type aliases
+// ---------------------------------------------------------------------------
+
+pub const Handle = sys.esp_timer_handle_t;
+pub const Callback = sys.esp_timer_cb_t;
+pub const CreateArgs = sys.esp_timer_create_args_t;
+
+/// Dispatch method: callback runs in a dedicated high-priority task (TASK)
+/// or directly from the ISR (ISR — use only for very short callbacks).
+pub const Dispatch = enum(sys.esp_timer_dispatch_t) {
+ task = sys.ESP_TIMER_TASK,
+ max = sys.ESP_TIMER_MAX,
+};
+
+// ---------------------------------------------------------------------------
+// High-resolution timer (esp_timer)
+// ---------------------------------------------------------------------------
+
+pub const Timer = struct {
+ // ── Lifecycle ────────────────────────────────────────────────────────────
+
+ /// Create a new timer. `args.callback` must be non-null.
+ /// Returns the handle; caller owns it and must call `delete()` when done.
+ pub fn create(args: *const CreateArgs) !Handle {
+ var handle: Handle = null;
+ try errors.espCheckError(sys.esp_timer_create(args, &handle));
+ return handle;
+ }
+
+ /// Start a one-shot timer that fires after `timeout_us` microseconds.
+ pub fn startOnce(handle: Handle, timeout_us: u64) !void {
+ try errors.espCheckError(sys.esp_timer_start_once(handle, timeout_us));
+ }
+
+ /// Start a periodic timer that fires every `period_us` microseconds.
+ pub fn startPeriodic(handle: Handle, period_us: u64) !void {
+ try errors.espCheckError(sys.esp_timer_start_periodic(handle, period_us));
+ }
+
+ /// Restart a running timer with a new `timeout_us`.
+ /// Works for both one-shot and periodic timers.
+ pub fn restart(handle: Handle, timeout_us: u64) !void {
+ try errors.espCheckError(sys.esp_timer_restart(handle, timeout_us));
+ }
+
+ /// Stop a running timer. Does not delete it.
+ pub fn stop(handle: Handle) !void {
+ try errors.espCheckError(sys.esp_timer_stop(handle));
+ }
+
+ /// Delete a timer. The timer must be stopped first.
+ pub fn delete(handle: Handle) !void {
+ try errors.espCheckError(sys.esp_timer_delete(handle));
+ }
+
+ // ── Query ────────────────────────────────────────────────────────────────
+
+ /// Return current time in microseconds since boot.
+ /// This never returns an error.
+ pub fn getTime() i64 {
+ return sys.esp_timer_get_time();
+ }
+
+ /// Return the expiry time (µs since boot) of the next pending alarm.
+ pub fn getNextAlarm() i64 {
+ return sys.esp_timer_get_next_alarm();
+ }
+
+ /// Return the expiry time of the next alarm that can wake the system.
+ pub fn getNextAlarmForWakeUp() i64 {
+ return sys.esp_timer_get_next_alarm_for_wake_up();
+ }
+
+ /// Return the period (µs) of a periodic timer.
+ pub fn getPeriod(handle: Handle) !u64 {
+ var period: u64 = 0;
+ try errors.espCheckError(sys.esp_timer_get_period(handle, &period));
+ return period;
+ }
+
+ /// Return the expiry time (µs since boot) for a one-shot timer.
+ pub fn getExpiryTime(handle: Handle) !u64 {
+ var expiry: u64 = 0;
+ try errors.espCheckError(sys.esp_timer_get_expiry_time(handle, &expiry));
+ return expiry;
+ }
+
+ /// Returns `true` if the timer is currently active (started, not yet fired / periodic).
+ pub fn isActive(handle: Handle) bool {
+ return sys.esp_timer_is_active(handle);
+ }
+};
diff --git a/software/zig_main/imports/twai.zig b/software/zig_main/imports/twai.zig
new file mode 100644
index 0000000..f369cdc
--- /dev/null
+++ b/software/zig_main/imports/twai.zig
@@ -0,0 +1,175 @@
+const sys = @import("sys");
+const errors = @import("error");
+
+// ---------------------------------------------------------------------------
+// Type aliases
+// ---------------------------------------------------------------------------
+
+pub const Handle = sys.twai_handle_t;
+pub const Message = sys.twai_message_t;
+pub const FilterConfig = sys.twai_filter_config_t;
+pub const TimingConfig = sys.twai_timing_config_t;
+pub const GeneralConfig = sys.twai_general_config_t;
+pub const StatusInfo = sys.twai_status_info_t;
+pub const FrameHeader = sys.twai_frame_header_t;
+
+/// TWAI operating mode.
+pub const Mode = enum(sys.twai_mode_t) {
+ normal = sys.TWAI_MODE_NORMAL,
+ no_ack = sys.TWAI_MODE_NO_ACK,
+ listen_only = sys.TWAI_MODE_LISTEN_ONLY,
+};
+
+/// Bus error state.
+pub const ErrorState = enum(sys.twai_error_state_t) {
+ active = sys.TWAI_ERROR_ACTIVE,
+ warning = sys.TWAI_ERROR_WARNING,
+ passive = sys.TWAI_ERROR_PASSIVE,
+ bus_off = sys.TWAI_ERROR_BUS_OFF,
+};
+
+/// Driver state.
+pub const State = enum(sys.twai_state_t) {
+ stopped = sys.TWAI_STATE_STOPPED,
+ running = sys.TWAI_STATE_RUNNING,
+ bus_off = sys.TWAI_STATE_BUS_OFF,
+ recovering = sys.TWAI_STATE_RECOVERING,
+};
+
+// ---------------------------------------------------------------------------
+// Alert flag constants
+// ---------------------------------------------------------------------------
+
+pub const Alert = struct {
+ pub const tx_idle: u32 = sys.TWAI_ALERT_TX_IDLE;
+ pub const tx_success: u32 = sys.TWAI_ALERT_TX_SUCCESS;
+ pub const rx_data: u32 = sys.TWAI_ALERT_RX_DATA;
+ pub const below_err_warn: u32 = sys.TWAI_ALERT_BELOW_ERR_WARN;
+ pub const err_active: u32 = sys.TWAI_ALERT_ERR_ACTIVE;
+ pub const recovery_in_progress: u32 = sys.TWAI_ALERT_RECOVERY_IN_PROGRESS;
+ pub const bus_recovered: u32 = sys.TWAI_ALERT_BUS_RECOVERED;
+ pub const arb_lost: u32 = sys.TWAI_ALERT_ARB_LOST;
+ pub const above_err_warn: u32 = sys.TWAI_ALERT_ABOVE_ERR_WARN;
+ pub const bus_error: u32 = sys.TWAI_ALERT_BUS_ERROR;
+ pub const tx_failed: u32 = sys.TWAI_ALERT_TX_FAILED;
+ pub const rx_queue_full: u32 = sys.TWAI_ALERT_RX_QUEUE_FULL;
+ pub const err_pass: u32 = sys.TWAI_ALERT_ERR_PASS;
+ pub const bus_off: u32 = sys.TWAI_ALERT_BUS_OFF;
+ pub const rx_fifo_overrun: u32 = sys.TWAI_ALERT_RX_FIFO_OVERRUN;
+ pub const tx_retried: u32 = sys.TWAI_ALERT_TX_RETRIED;
+ pub const periph_reset: u32 = sys.TWAI_ALERT_PERIPH_RESET;
+ pub const all: u32 = sys.TWAI_ALERT_ALL;
+ pub const none: u32 = sys.TWAI_ALERT_NONE;
+ pub const and_log: u32 = sys.TWAI_ALERT_AND_LOG;
+};
+
+// ---------------------------------------------------------------------------
+// Message flag constants
+// ---------------------------------------------------------------------------
+
+pub const MsgFlag = struct {
+ pub const none: u32 = sys.TWAI_MSG_FLAG_NONE;
+ pub const extd: u32 = sys.TWAI_MSG_FLAG_EXTD;
+ pub const rtr: u32 = sys.TWAI_MSG_FLAG_RTR;
+ pub const ss: u32 = sys.TWAI_MSG_FLAG_SS;
+ pub const self: u32 = sys.TWAI_MSG_FLAG_SELF;
+ pub const dlc_non_comp: u32 = sys.TWAI_MSG_FLAG_DLC_NON_COMP;
+};
+
+// ---------------------------------------------------------------------------
+// Driver — v2 handle-based API (preferred)
+// ---------------------------------------------------------------------------
+
+pub const Driver = struct {
+ // ── Lifecycle ────────────────────────────────────────────────────────────
+
+ /// Install the TWAI driver and return a handle.
+ /// Use `GPIO_NUM_NC` (-1) for unused clkout/bus_off pins.
+ pub fn install(
+ g_config: *const GeneralConfig,
+ t_config: *const TimingConfig,
+ f_config: *const FilterConfig,
+ ) !Handle {
+ var handle: Handle = null;
+ try errors.espCheckError(sys.twai_driver_install_v2(g_config, t_config, f_config, &handle));
+ return handle;
+ }
+
+ /// Uninstall a TWAI driver instance and release resources.
+ pub fn uninstall(handle: Handle) !void {
+ try errors.espCheckError(sys.twai_driver_uninstall_v2(handle));
+ }
+
+ /// Start the TWAI driver (enter running state).
+ pub fn start(handle: Handle) !void {
+ try errors.espCheckError(sys.twai_start_v2(handle));
+ }
+
+ /// Stop the TWAI driver (enter stopped state).
+ pub fn stop(handle: Handle) !void {
+ try errors.espCheckError(sys.twai_stop_v2(handle));
+ }
+
+ // ── Transmit / Receive ───────────────────────────────────────────────────
+
+ /// Queue a message for transmission. `ticks_to_wait` is the FreeRTOS
+ /// tick timeout (use `sys.portMAX_DELAY` to block indefinitely).
+ pub fn transmit(handle: Handle, msg: *const Message, ticks_to_wait: sys.TickType_t) !void {
+ try errors.espCheckError(sys.twai_transmit_v2(handle, msg, ticks_to_wait));
+ }
+
+ /// Receive a message. Blocks until a message is available or timeout.
+ pub fn receive(handle: Handle, ticks_to_wait: sys.TickType_t) !Message {
+ var msg: Message = std.mem.zeroes(Message);
+ try errors.espCheckError(sys.twai_receive_v2(handle, &msg, ticks_to_wait));
+ return msg;
+ }
+
+ // ── Alerts ───────────────────────────────────────────────────────────────
+
+ /// Block until one of the enabled alerts fires (or timeout).
+ /// Returns bitmask of triggered alerts.
+ pub fn readAlerts(handle: Handle, ticks_to_wait: sys.TickType_t) !u32 {
+ var alerts: u32 = 0;
+ try errors.espCheckError(sys.twai_read_alerts_v2(handle, &alerts, ticks_to_wait));
+ return alerts;
+ }
+
+ /// Change the set of enabled alerts at runtime.
+ /// Returns the alerts that were pending at the time of reconfiguration.
+ pub fn reconfigureAlerts(handle: Handle, alerts_enabled: u32) !u32 {
+ var current: u32 = 0;
+ try errors.espCheckError(sys.twai_reconfigure_alerts_v2(handle, alerts_enabled, ¤t));
+ return current;
+ }
+
+ // ── Recovery ──────────────────────────────────────────────────────────────
+
+ /// Initiate bus-off recovery. The driver must be in `bus_off` state.
+ pub fn initiateRecovery(handle: Handle) !void {
+ try errors.espCheckError(sys.twai_initiate_recovery_v2(handle));
+ }
+
+ // ── Status ────────────────────────────────────────────────────────────────
+
+ /// Get the current driver and bus status.
+ pub fn getStatus(handle: Handle) !StatusInfo {
+ var info: StatusInfo = std.mem.zeroes(StatusInfo);
+ try errors.espCheckError(sys.twai_get_status_info_v2(handle, &info));
+ return info;
+ }
+
+ // ── Queue management ─────────────────────────────────────────────────────
+
+ /// Discard all messages pending in the transmit queue.
+ pub fn clearTxQueue(handle: Handle) !void {
+ try errors.espCheckError(sys.twai_clear_transmit_queue_v2(handle));
+ }
+
+ /// Discard all messages in the receive queue.
+ pub fn clearRxQueue(handle: Handle) !void {
+ try errors.espCheckError(sys.twai_clear_receive_queue_v2(handle));
+ }
+};
+
+const std = @import("std");
diff --git a/software/zig_main/imports/uart.zig b/software/zig_main/imports/uart.zig
new file mode 100644
index 0000000..5c52c7b
--- /dev/null
+++ b/software/zig_main/imports/uart.zig
@@ -0,0 +1,370 @@
+const sys = @import("sys");
+const errors = @import("error");
+
+// ─────────────────────────────────────────────────────────────────────────────
+// Re-export ESP-IDF types so callers only need to @import("uart")
+// ─────────────────────────────────────────────────────────────────────────────
+pub const Port = sys.uart_port_t;
+pub const WordLength = sys.uart_word_length_t;
+pub const StopBits = sys.uart_stop_bits_t;
+pub const Parity = sys.uart_parity_t;
+pub const HWFlowCtrl = sys.uart_hw_flowcontrol_t;
+pub const Mode = sys.uart_mode_t;
+pub const Sclk = sys.uart_sclk_t;
+pub const Config = sys.uart_config_t;
+pub const IntrConfig = sys.uart_intr_config_t;
+pub const QueueHandle = sys.QueueHandle_t;
+pub const TickType = sys.TickType_t;
+
+// ─────────────────────────────────────────────────────────────────────────────
+// Pin assignment sentinel — mirrors UART_PIN_NO_CHANGE from esp-idf
+// ─────────────────────────────────────────────────────────────────────────────
+pub const pin_no_change: c_int = -1;
+
+// ─────────────────────────────────────────────────────────────────────────────
+// Driver lifecycle
+// ─────────────────────────────────────────────────────────────────────────────
+
+pub const DriverConfig = struct {
+ rx_buffer_size: usize,
+ tx_buffer_size: usize = 0,
+ /// Pass null to disable event queue.
+ queue_size: c_int = 0,
+ uart_queue: ?*QueueHandle = null,
+ intr_alloc_flags: c_int = 0,
+};
+
+pub fn driverInstall(port: Port, cfg: DriverConfig) !void {
+ return errors.espCheckError(sys.uart_driver_install(
+ port,
+ @intCast(cfg.rx_buffer_size),
+ @intCast(cfg.tx_buffer_size),
+ cfg.queue_size,
+ cfg.uart_queue orelse null,
+ cfg.intr_alloc_flags,
+ ));
+}
+
+pub fn driverDelete(port: Port) !void {
+ return errors.espCheckError(sys.uart_driver_delete(port));
+}
+
+pub fn isDriverInstalled(port: Port) bool {
+ return sys.uart_is_driver_installed(port);
+}
+
+// ─────────────────────────────────────────────────────────────────────────────
+// Parameter configuration (word length, stop bits, parity, baud rate …)
+// ─────────────────────────────────────────────────────────────────────────────
+
+pub fn paramConfig(port: Port, cfg: *const Config) !void {
+ return errors.espCheckError(sys.uart_param_config(port, cfg));
+}
+
+pub fn setWordLength(port: Port, data_bit: WordLength) !void {
+ return errors.espCheckError(sys.uart_set_word_length(port, data_bit));
+}
+
+pub fn getWordLength(port: Port) !WordLength {
+ var v: WordLength = undefined;
+ try errors.espCheckError(sys.uart_get_word_length(port, &v));
+ return v;
+}
+
+pub fn setStopBits(port: Port, stop_bits: StopBits) !void {
+ return errors.espCheckError(sys.uart_set_stop_bits(port, stop_bits));
+}
+
+pub fn getStopBits(port: Port) !StopBits {
+ var v: StopBits = undefined;
+ try errors.espCheckError(sys.uart_get_stop_bits(port, &v));
+ return v;
+}
+
+pub fn setParity(port: Port, parity: Parity) !void {
+ return errors.espCheckError(sys.uart_set_parity(port, parity));
+}
+
+pub fn getParity(port: Port) !Parity {
+ var v: Parity = undefined;
+ try errors.espCheckError(sys.uart_get_parity(port, &v));
+ return v;
+}
+
+pub fn setBaudrate(port: Port, baudrate: u32) !void {
+ return errors.espCheckError(sys.uart_set_baudrate(port, baudrate));
+}
+
+pub fn getBaudrate(port: Port) !u32 {
+ var v: u32 = 0;
+ try errors.espCheckError(sys.uart_get_baudrate(port, &v));
+ return v;
+}
+
+pub fn getSclkFreq(sclk: Sclk) !u32 {
+ var v: u32 = 0;
+ try errors.espCheckError(sys.uart_get_sclk_freq(sclk, &v));
+ return v;
+}
+
+pub fn setLineInverse(port: Port, inverse_mask: u32) !void {
+ return errors.espCheckError(sys.uart_set_line_inverse(port, inverse_mask));
+}
+
+pub fn setMode(port: Port, mode: Mode) !void {
+ return errors.espCheckError(sys.uart_set_mode(port, mode));
+}
+
+pub fn setLoopBack(port: Port, enable: bool) !void {
+ return errors.espCheckError(sys.uart_set_loop_back(port, enable));
+}
+
+pub fn setAlwaysRxTimeout(port: Port, enable: bool) void {
+ sys.uart_set_always_rx_timeout(port, enable);
+}
+
+// ─────────────────────────────────────────────────────────────────────────────
+// Pin mapping
+// ─────────────────────────────────────────────────────────────────────────────
+
+pub const PinConfig = struct {
+ tx: c_int = pin_no_change,
+ rx: c_int = pin_no_change,
+ rts: c_int = pin_no_change,
+ cts: c_int = pin_no_change,
+};
+
+pub fn setPin(port: Port, pins: PinConfig) !void {
+ return errors.espCheckError(sys.uart_set_pin(port, pins.tx, pins.rx, pins.rts, pins.cts));
+}
+
+/// Drive RTS line manually (level: true = high, false = low).
+pub fn setRTS(port: Port, level: bool) !void {
+ return errors.espCheckError(sys.uart_set_rts(port, @intFromBool(level)));
+}
+
+/// Drive DTR line manually.
+pub fn setDTR(port: Port, level: bool) !void {
+ return errors.espCheckError(sys.uart_set_dtr(port, @intFromBool(level)));
+}
+
+pub fn setTXIdleNum(port: Port, idle_num: u16) !void {
+ return errors.espCheckError(sys.uart_set_tx_idle_num(port, idle_num));
+}
+
+// ─────────────────────────────────────────────────────────────────────────────
+// Flow control
+// ─────────────────────────────────────────────────────────────────────────────
+
+pub fn setHWFlowCtrl(port: Port, flow_ctrl: HWFlowCtrl, rx_thresh: u8) !void {
+ return errors.espCheckError(sys.uart_set_hw_flow_ctrl(port, flow_ctrl, rx_thresh));
+}
+
+pub fn getHWFlowCtrl(port: Port) !HWFlowCtrl {
+ var v: HWFlowCtrl = undefined;
+ try errors.espCheckError(sys.uart_get_hw_flow_ctrl(port, &v));
+ return v;
+}
+
+pub fn setSWFlowCtrl(port: Port, enable: bool, rx_thresh_xon: u8, rx_thresh_xoff: u8) !void {
+ return errors.espCheckError(sys.uart_set_sw_flow_ctrl(port, enable, rx_thresh_xon, rx_thresh_xoff));
+}
+
+// ─────────────────────────────────────────────────────────────────────────────
+// Interrupt configuration
+// ─────────────────────────────────────────────────────────────────────────────
+
+pub fn intrConfig(port: Port, cfg: *const IntrConfig) !void {
+ return errors.espCheckError(sys.uart_intr_config(port, cfg));
+}
+
+pub fn clearIntrStatus(port: Port, clr_mask: u32) !void {
+ return errors.espCheckError(sys.uart_clear_intr_status(port, clr_mask));
+}
+
+pub fn enableIntrMask(port: Port, mask: u32) !void {
+ return errors.espCheckError(sys.uart_enable_intr_mask(port, mask));
+}
+
+pub fn disableIntrMask(port: Port, mask: u32) !void {
+ return errors.espCheckError(sys.uart_disable_intr_mask(port, mask));
+}
+
+pub fn enableRXIntr(port: Port) !void {
+ return errors.espCheckError(sys.uart_enable_rx_intr(port));
+}
+
+pub fn disableRXIntr(port: Port) !void {
+ return errors.espCheckError(sys.uart_disable_rx_intr(port));
+}
+
+pub fn enableTXIntr(port: Port, enable: bool, thresh: c_int) !void {
+ return errors.espCheckError(sys.uart_enable_tx_intr(port, @intFromBool(enable), thresh));
+}
+
+pub fn disableTXIntr(port: Port) !void {
+ return errors.espCheckError(sys.uart_disable_tx_intr(port));
+}
+
+// ─────────────────────────────────────────────────────────────────────────────
+// RX / TX thresholds and timeouts
+// ─────────────────────────────────────────────────────────────────────────────
+
+pub fn setRXFullThreshold(port: Port, threshold: c_int) !void {
+ return errors.espCheckError(sys.uart_set_rx_full_threshold(port, threshold));
+}
+
+pub fn setTXEmptyThreshold(port: Port, threshold: c_int) !void {
+ return errors.espCheckError(sys.uart_set_tx_empty_threshold(port, threshold));
+}
+
+pub fn setRXTimeout(port: Port, tout_thresh: u8) !void {
+ return errors.espCheckError(sys.uart_set_rx_timeout(port, tout_thresh));
+}
+
+// ─────────────────────────────────────────────────────────────────────────────
+// Write
+// ─────────────────────────────────────────────────────────────────────────────
+
+/// Transmit raw bytes directly (no ring buffer).
+/// Returns the number of bytes pushed into the TX FIFO.
+pub fn txChars(port: Port, data: []const u8) usize {
+ const rc = sys.uart_tx_chars(port, data.ptr, @intCast(data.len));
+ return if (rc < 0) 0 else @intCast(rc);
+}
+
+/// Write bytes through the TX ring buffer (if installed) or directly.
+/// Returns number of bytes written, or error on failure.
+pub fn writeBytes(port: Port, data: []const u8) !usize {
+ const rc = sys.uart_write_bytes(port, data.ptr, data.len);
+ if (rc < 0) return error.UartWriteFailed;
+ return @intCast(rc);
+}
+
+/// Like `writeBytes` but appends a serial break signal of `brk_len` bit-periods.
+pub fn writeBytesWithBreak(port: Port, data: []const u8, brk_len: c_int) !usize {
+ const rc = sys.uart_write_bytes_with_break(port, data.ptr, data.len, brk_len);
+ if (rc < 0) return error.UartWriteFailed;
+ return @intCast(rc);
+}
+
+pub fn waitTXDone(port: Port, ticks_to_wait: TickType) !void {
+ return errors.espCheckError(sys.uart_wait_tx_done(port, ticks_to_wait));
+}
+
+pub fn waitTXIdlePolling(port: Port) !void {
+ return errors.espCheckError(sys.uart_wait_tx_idle_polling(port));
+}
+
+// ─────────────────────────────────────────────────────────────────────────────
+// Read
+// ─────────────────────────────────────────────────────────────────────────────
+
+/// Read up to `buf.len` bytes from the RX ring buffer.
+/// Returns the number of bytes actually read, or error on failure.
+pub fn readBytes(port: Port, buf: []u8, ticks_to_wait: TickType) !usize {
+ const rc = sys.uart_read_bytes(port, buf.ptr, @intCast(buf.len), ticks_to_wait);
+ if (rc < 0) return error.UartReadFailed;
+ return @intCast(rc);
+}
+
+/// Returns the number of bytes waiting in the RX ring buffer.
+pub fn getBufferedDataLen(port: Port) !usize {
+ var v: usize = 0;
+ try errors.espCheckError(sys.uart_get_buffered_data_len(port, &v));
+ return v;
+}
+
+/// Returns free space in the TX ring buffer (bytes).
+pub fn getTXBufferFreeSize(port: Port) !usize {
+ var v: usize = 0;
+ try errors.espCheckError(sys.uart_get_tx_buffer_free_size(port, &v));
+ return v;
+}
+
+// ─────────────────────────────────────────────────────────────────────────────
+// Flush
+// ─────────────────────────────────────────────────────────────────────────────
+
+/// Flush TX FIFO — waits until all bytes are sent.
+pub fn flush(port: Port) !void {
+ return errors.espCheckError(sys.uart_flush(port));
+}
+
+/// Flush (discard) RX ring buffer contents.
+pub fn flushInput(port: Port) !void {
+ return errors.espCheckError(sys.uart_flush_input(port));
+}
+
+// ─────────────────────────────────────────────────────────────────────────────
+// Pattern detection
+// ─────────────────────────────────────────────────────────────────────────────
+
+pub const PatternConfig = struct {
+ /// Character to detect (e.g. '+' for AT commands).
+ chr: u8,
+ /// Number of consecutive pattern characters required.
+ chr_num: u8,
+ /// Maximum gap between characters (in baud ticks).
+ chr_tout: c_int,
+ /// Idle time after pattern (baud ticks).
+ post_idle: c_int,
+ /// Idle time before pattern (baud ticks).
+ pre_idle: c_int,
+};
+
+pub fn enablePatternDetBaudIntr(port: Port, cfg: PatternConfig) !void {
+ return errors.espCheckError(sys.uart_enable_pattern_det_baud_intr(
+ port,
+ cfg.chr,
+ cfg.chr_num,
+ cfg.chr_tout,
+ cfg.post_idle,
+ cfg.pre_idle,
+ ));
+}
+
+pub fn disablePatternDetIntr(port: Port) !void {
+ return errors.espCheckError(sys.uart_disable_pattern_det_intr(port));
+}
+
+/// Pop the oldest pattern position from the queue.
+/// Returns `null` if the queue is empty or an error occurred.
+pub fn patternPopPos(port: Port) ?usize {
+ const rc = sys.uart_pattern_pop_pos(port);
+ return if (rc < 0) null else @intCast(rc);
+}
+
+/// Peek at the oldest pattern position without removing it.
+pub fn patternGetPos(port: Port) ?usize {
+ const rc = sys.uart_pattern_get_pos(port);
+ return if (rc < 0) null else @intCast(rc);
+}
+
+pub fn patternQueueReset(port: Port, queue_length: c_int) !void {
+ return errors.espCheckError(sys.uart_pattern_queue_reset(port, queue_length));
+}
+
+// ─────────────────────────────────────────────────────────────────────────────
+// RS-485 collision detection
+// ─────────────────────────────────────────────────────────────────────────────
+
+pub fn getCollisionFlag(port: Port) !bool {
+ var v: bool = false;
+ try errors.espCheckError(sys.uart_get_collision_flag(port, &v));
+ return v;
+}
+
+// ─────────────────────────────────────────────────────────────────────────────
+// Light-sleep wakeup
+// ─────────────────────────────────────────────────────────────────────────────
+
+pub fn setWakeupThreshold(port: Port, threshold: c_int) !void {
+ return errors.espCheckError(sys.uart_set_wakeup_threshold(port, threshold));
+}
+
+pub fn getWakeupThreshold(port: Port) !c_int {
+ var v: c_int = 0;
+ try errors.espCheckError(sys.uart_get_wakeup_threshold(port, &v));
+ return v;
+}
diff --git a/software/zig_main/imports/version.zig b/software/zig_main/imports/version.zig
new file mode 100644
index 0000000..aebe62e
--- /dev/null
+++ b/software/zig_main/imports/version.zig
@@ -0,0 +1,50 @@
+const std = @import("std");
+
+pub const Version = struct {
+ major: u32 = 0,
+ minor: u32 = 0,
+ patch: u32 = 0,
+
+ pub fn get() Version {
+ var final_version: Version = .{};
+ const idf_version = std.mem.span(@import("sys").esp_get_idf_version());
+
+ if (!std.mem.startsWith(u8, idf_version, "v"))
+ return final_version;
+
+ // Strip any pre-release suffix (e.g. "v5.3.0-beta1" → "v5.3.0").
+ var strip = std.mem.splitScalar(u8, idf_version, '-');
+ var it = std.mem.tokenizeScalar(u8, strip.first(), '.');
+
+ // Use a positional index (0=major, 1=minor, 2=patch) so that
+ // version components with value 0 are parsed correctly.
+ var field: u32 = 0;
+ while (it.next()) |token| {
+ const raw = if (std.mem.startsWith(u8, token, "v")) token[1..] else token;
+ const digit = std.fmt.parseUnsigned(u32, raw, 10) catch break;
+ switch (field) {
+ 0 => final_version.major = digit,
+ 1 => final_version.minor = digit,
+ 2 => final_version.patch = digit,
+ else => break,
+ }
+ field += 1;
+ }
+
+ return final_version;
+ }
+
+ pub fn toString(self: Version, allocator: std.mem.Allocator) []const u8 {
+ const idf_version = std.mem.span(@import("sys").esp_get_idf_version());
+
+ // Return the raw string for non-release builds (e.g. commit hashes).
+ if (!std.mem.startsWith(u8, idf_version, "v"))
+ return idf_version;
+
+ return std.fmt.allocPrint(allocator, "v{d}.{d}.{d}", .{
+ self.major,
+ self.minor,
+ self.patch,
+ }) catch |err| @panic(@errorName(err));
+ }
+};
diff --git a/software/zig_main/imports/wdt.zig b/software/zig_main/imports/wdt.zig
new file mode 100644
index 0000000..9fc5ae9
--- /dev/null
+++ b/software/zig_main/imports/wdt.zig
@@ -0,0 +1,85 @@
+const sys = @import("sys");
+const errors = @import("error");
+
+// ---------------------------------------------------------------------------
+// Type aliases
+// ---------------------------------------------------------------------------
+
+pub const Config = sys.esp_task_wdt_config_t;
+pub const UserHandle = sys.esp_task_wdt_user_handle_t;
+pub const TaskHandle = sys.TaskHandle_t;
+
+// ---------------------------------------------------------------------------
+// Watchdog initialisation
+// ---------------------------------------------------------------------------
+
+/// Initialise the Task Watchdog Timer.
+///
+/// `config.timeout_ms` — watchdog timeout in milliseconds
+/// `config.idle_core_mask` — bitmask of idle tasks to subscribe (0 = none)
+/// `config.trigger_panic` — whether to trigger a panic on timeout
+pub fn init(config: *const Config) !void {
+ try errors.espCheckError(sys.esp_task_wdt_init(config));
+}
+
+/// Reconfigure a running watchdog (does not reset the timer).
+pub fn reconfigure(config: *const Config) !void {
+ try errors.espCheckError(sys.esp_task_wdt_reconfigure(config));
+}
+
+/// De-initialise the Task Watchdog Timer.
+pub fn deinit() !void {
+ try errors.espCheckError(sys.esp_task_wdt_deinit());
+}
+
+// ---------------------------------------------------------------------------
+// Task subscription
+// ---------------------------------------------------------------------------
+
+/// Subscribe a FreeRTOS task to the watchdog. Pass `null` for the calling task.
+pub fn add(task_handle: TaskHandle) !void {
+ try errors.espCheckError(sys.esp_task_wdt_add(task_handle));
+}
+
+/// Subscribe a named user (not a FreeRTOS task) to the watchdog.
+/// Returns a `UserHandle` for resetting and unsubscribing.
+pub fn addUser(user_name: [*:0]const u8) !UserHandle {
+ var handle: UserHandle = null;
+ try errors.espCheckError(sys.esp_task_wdt_add_user(user_name, &handle));
+ return handle;
+}
+
+/// Unsubscribe a FreeRTOS task from the watchdog.
+pub fn delete(task_handle: TaskHandle) !void {
+ try errors.espCheckError(sys.esp_task_wdt_delete(task_handle));
+}
+
+/// Unsubscribe a user registered with `addUser`.
+pub fn deleteUser(user_handle: UserHandle) !void {
+ try errors.espCheckError(sys.esp_task_wdt_delete_user(user_handle));
+}
+
+// ---------------------------------------------------------------------------
+// Watchdog reset (feed)
+// ---------------------------------------------------------------------------
+
+/// Reset (feed) the watchdog for the calling task.
+/// Call at least once per watchdog period to prevent a timeout.
+pub fn reset() !void {
+ try errors.espCheckError(sys.esp_task_wdt_reset());
+}
+
+/// Reset the watchdog for a named user registered with `addUser`.
+pub fn resetUser(user_handle: UserHandle) !void {
+ try errors.espCheckError(sys.esp_task_wdt_reset_user(user_handle));
+}
+
+// ---------------------------------------------------------------------------
+// Status query
+// ---------------------------------------------------------------------------
+
+/// Returns `ESP_OK` if `task_handle` is subscribed and has recently reset,
+/// or an error if it is overdue or not subscribed.
+pub fn status(task_handle: TaskHandle) !void {
+ try errors.espCheckError(sys.esp_task_wdt_status(task_handle));
+}
diff --git a/software/zig_main/imports/wifi.zig b/software/zig_main/imports/wifi.zig
new file mode 100644
index 0000000..88353be
--- /dev/null
+++ b/software/zig_main/imports/wifi.zig
@@ -0,0 +1,429 @@
+const sys = @import("sys");
+const errors = @import("error");
+
+pub const WIFI_ENABLE_ENTERPRISE = 1 << 7;
+
+pub const CONFIG_FEATURE_WPA3_SAE_BIT = 1 << 0;
+pub const CONFIG_FEATURE_CACHE_TX_BUF_BIT = 1 << 1;
+pub const CONFIG_FEATURE_FTM_INITIATOR_BIT = 1 << 2;
+pub const CONFIG_FEATURE_FTM_RESPONDER_BIT = 1 << 3;
+pub const CONFIG_FEATURE_GCMP_BIT = 1 << 4;
+pub const CONFIG_FEATURE_GMAC_BIT = 1 << 5;
+pub const CONFIG_FEATURE_11R_BIT = 1 << 6;
+pub const CONFIG_FEATURE_WIFI_ENT_BIT = 1 << 7;
+
+pub const WIFI_FEATURE_CAPS =
+ CONFIG_FEATURE_WPA3_SAE_BIT |
+ CONFIG_FEATURE_CACHE_TX_BUF_BIT |
+ CONFIG_FEATURE_FTM_INITIATOR_BIT |
+ CONFIG_FEATURE_FTM_RESPONDER_BIT |
+ CONFIG_FEATURE_GCMP_BIT |
+ CONFIG_FEATURE_GMAC_BIT |
+ CONFIG_FEATURE_11R_BIT |
+ WIFI_ENABLE_ENTERPRISE;
+
+pub const wifi_mode_t = enum(sys.wifi_mode_t) {
+ WIFI_MODE_NULL = sys.WIFI_MODE_NULL,
+ WIFI_MODE_STA = sys.WIFI_MODE_STA,
+ WIFI_MODE_AP = sys.WIFI_MODE_AP,
+ WIFI_MODE_APSTA = sys.WIFI_MODE_APSTA,
+ WIFI_MODE_NAN = sys.WIFI_MODE_NAN,
+ WIFI_MODE_MAX = sys.WIFI_MODE_MAX,
+};
+pub const wifi_interface_t = enum(sys.wifi_interface_t) {
+ WIFI_IF_STA = sys.WIFI_IF_STA,
+ WIFI_IF_AP = sys.WIFI_IF_AP,
+ WIFI_IF_NAN = sys.WIFI_IF_NAN,
+ WIFI_IF_MAX = sys.WIFI_IF_MAX,
+};
+pub const wifi_country_policy_t = enum(sys.wifi_country_policy_t) {
+ WIFI_COUNTRY_POLICY_AUTO = sys.WIFI_COUNTRY_POLICY_AUTO,
+ WIFI_COUNTRY_POLICY_MANUAL = sys.WIFI_COUNTRY_POLICY_MANUAL,
+};
+
+pub fn init(config: *const sys.wifi_init_config_t) !void {
+ return try errors.espCheckError(sys.esp_wifi_init(config));
+}
+pub fn setDefaultWifiStationHandlers() !void {
+ return try errors.espCheckError(sys.esp_wifi_set_default_wifi_sta_handlers());
+}
+pub fn setDefaultWifiAPHandlers() !void {
+ return try errors.espCheckError(sys.esp_wifi_set_default_wifi_ap_handlers());
+}
+pub fn setDefaultWifiNANHandlers() !void {
+ return try errors.espCheckError(sys.esp_wifi_set_default_wifi_nan_handlers());
+}
+pub fn clearDefaultWifiDriverHandlers(esp_netif: ?*anyopaque) !void {
+ return try errors.espCheckError(sys.esp_wifi_clear_default_wifi_driver_and_handlers(esp_netif));
+}
+pub fn deinit() !void {
+ return try errors.espCheckError(sys.esp_wifi_deinit());
+}
+pub fn setMode(mode: wifi_mode_t) !void {
+ return try errors.espCheckError(sys.esp_wifi_set_mode(@intFromEnum(mode)));
+}
+pub fn getMode(mode: [*]wifi_mode_t) !void {
+ return try errors.espCheckError(sys.esp_wifi_get_mode(mode));
+}
+pub fn start() !void {
+ return try errors.espCheckError(sys.esp_wifi_start());
+}
+pub fn stop() !void {
+ return try errors.espCheckError(sys.esp_wifi_stop());
+}
+pub fn restore() !void {
+ return try errors.espCheckError(sys.esp_wifi_restore());
+}
+pub fn connect() !void {
+ return try errors.espCheckError(sys.esp_wifi_connect());
+}
+pub fn disconnect() !void {
+ return try errors.espCheckError(sys.esp_wifi_disconnect());
+}
+pub fn clearFastConnect() !void {
+ return try errors.espCheckError(sys.esp_wifi_clear_fast_connect());
+}
+pub const Scan = struct {
+ pub fn start(config: [*c]const sys.wifi_scan_config_t, block: bool) !void {
+ return try errors.espCheckError(sys.esp_wifi_scan_start(config, block));
+ }
+ pub fn stop() !void {
+ return try errors.espCheckError(sys.esp_wifi_scan_stop());
+ }
+ pub fn getAPNum(number: [*c]u16) !void {
+ return try errors.espCheckError(sys.esp_wifi_scan_get_ap_num(number));
+ }
+ pub fn getAPRecords(number: [*c]u16, ap_records: ?*sys.wifi_ap_record_t) !void {
+ return try errors.espCheckError(sys.esp_wifi_scan_get_ap_records(number, ap_records));
+ }
+ pub fn getAPRecord(ap_record: ?*sys.wifi_ap_record_t) !void {
+ return try errors.espCheckError(sys.esp_wifi_scan_get_ap_record(ap_record));
+ }
+};
+pub const PowerSave = struct {
+ pub fn set(@"type": sys.wifi_ps_type_t) !void {
+ return try errors.espCheckError(sys.esp_wifi_set_ps(@"type"));
+ }
+ pub fn get(@"type": [*c]sys.wifi_ps_type_t) !void {
+ return try errors.espCheckError(sys.esp_wifi_get_ps(@"type"));
+ }
+};
+pub const Protocol = struct {
+ pub fn set(ifx: wifi_interface_t, protocol_bitmap: u8) !void {
+ return try errors.espCheckError(sys.esp_wifi_set_protocol(ifx, protocol_bitmap));
+ }
+ pub fn get(ifx: wifi_interface_t, protocol_bitmap: [*:0]u8) !void {
+ return try errors.espCheckError(sys.esp_wifi_get_protocol(ifx, protocol_bitmap));
+ }
+};
+pub const Bandwidth = struct {
+ pub fn set(ifx: wifi_interface_t, bw: sys.wifi_bandwidth_t) !void {
+ return try errors.espCheckError(sys.esp_wifi_set_bandwidth(ifx, bw));
+ }
+ pub fn get(ifx: wifi_interface_t, bw: [*c]sys.wifi_bandwidth_t) !void {
+ return try errors.espCheckError(sys.esp_wifi_get_bandwidth(ifx, bw));
+ }
+};
+pub const Channel = struct {
+ pub fn set(primary: u8, second: sys.wifi_second_chan_t) !void {
+ return try errors.espCheckError(sys.esp_wifi_set_channel(primary, second));
+ }
+ pub fn get(primary: [*c]u8, second: [*c]sys.wifi_second_chan_t) !void {
+ return try errors.espCheckError(sys.esp_wifi_get_channel(primary, second));
+ }
+};
+pub const Country = struct {
+ pub fn set(country: [*c]const sys.wifi_country_t) !void {
+ return try errors.espCheckError(sys.esp_wifi_set_country(country));
+ }
+ pub fn get(country: [*c]sys.wifi_country_t) !void {
+ return try errors.espCheckError(sys.esp_wifi_get_country(country));
+ }
+ pub fn setCode(country: [*:0]const u8, ieee80211d_enabled: bool) !void {
+ return try errors.espCheckError(sys.esp_wifi_set_country_code(country, ieee80211d_enabled));
+ }
+ pub fn getCode(country: [*:0]u8) !void {
+ return try errors.espCheckError(sys.esp_wifi_get_country_code(country));
+ }
+};
+pub const MAC = struct {
+ pub fn set(ifx: wifi_interface_t, mac: [*:0]const u8) !void {
+ return try errors.espCheckError(sys.esp_wifi_set_mac(ifx, mac));
+ }
+ pub fn get(ifx: wifi_interface_t, mac: [*:0]u8) !void {
+ return try errors.espCheckError(sys.esp_wifi_get_mac(ifx, mac));
+ }
+};
+pub const Promiscuous = struct {
+ pub const promiscuous_callback_type = sys.wifi_promiscuous_cb_t;
+ pub fn setRXCallback(cb: sys.wifi_promiscuous_cb_t) !void {
+ return try errors.espCheckError(sys.esp_wifi_set_promiscuous_rx_cb(cb));
+ }
+ pub fn set(en: bool) !void {
+ return try errors.espCheckError(sys.esp_wifi_set_promiscuous(en));
+ }
+ pub fn get(en: [*c]bool) !void {
+ return try errors.espCheckError(sys.esp_wifi_get_promiscuous(en));
+ }
+ pub fn setFilter(filter: [*c]const sys.wifi_promiscuous_filter_t) !void {
+ return try errors.espCheckError(sys.esp_wifi_set_promiscuous_filter(filter));
+ }
+ pub fn getFilter(filter: [*c]sys.wifi_promiscuous_filter_t) !void {
+ return try errors.espCheckError(sys.esp_wifi_get_promiscuous_filter(filter));
+ }
+ pub fn setCtrlFilter(filter: [*c]const sys.wifi_promiscuous_filter_t) !void {
+ return try errors.espCheckError(sys.esp_wifi_set_promiscuous_ctrl_filter(filter));
+ }
+ pub fn getCtrlFilter(filter: [*c]sys.wifi_promiscuous_filter_t) !void {
+ return try errors.espCheckError(sys.esp_wifi_get_promiscuous_ctrl_filter(filter));
+ }
+};
+pub const wifiConfig = sys.wifi_config_t;
+pub fn setConfig(interface: wifi_interface_t, conf: ?*sys.wifi_config_t) !void {
+ return try errors.espCheckError(sys.esp_wifi_set_config(@intFromEnum(interface), conf));
+}
+pub fn getConfig(interface: wifi_interface_t, conf: ?*sys.wifi_config_t) !void {
+ return try errors.espCheckError(sys.esp_wifi_get_config(interface, conf));
+}
+pub fn setStorage(storage: sys.wifi_storage_t) !void {
+ return try errors.espCheckError(sys.esp_wifi_set_storage(storage));
+}
+pub const Vendor = struct {
+ pub const vendor_ie_callback_id_type = sys.wifi_vendor_ie_id_t;
+ pub fn setIE(enable: bool, @"type": sys.wifi_vendor_ie_type_t, idx: sys.wifi_vendor_ie_id_t, vnd_ie: ?*const anyopaque) !void {
+ return try errors.espCheckError(sys.esp_wifi_set_vendor_ie(enable, @"type", idx, vnd_ie));
+ }
+ pub fn setIECallback(callback: sys.esp_vendor_ie_cb_t, ctx: ?*anyopaque) !void {
+ return try errors.espCheckError(sys.esp_wifi_set_vendor_ie_cb(callback, ctx));
+ }
+};
+pub fn setMaxTXPower(power: i8) !void {
+ return try errors.espCheckError(sys.esp_wifi_set_max_tx_power(power));
+}
+pub fn getMaxTXPower(power: [*c]i8) !void {
+ return try errors.espCheckError(sys.esp_wifi_get_max_tx_power(power));
+}
+pub fn setEventMask(mask: u32) !void {
+ return try errors.espCheckError(sys.esp_wifi_set_event_mask(mask));
+}
+pub fn getEventMask(mask: [*c]u32) !void {
+ return try errors.espCheckError(sys.esp_wifi_get_event_mask(mask));
+}
+pub fn p80211TX(ifx: wifi_interface_t, buffer: ?*const anyopaque, len: c_int, en_sys_seq: bool) !void {
+ return try errors.espCheckError(sys.esp_wifi_80211_tx(ifx, buffer, len, en_sys_seq));
+}
+pub const csi_callback_type = sys.wifi_csi_cb_t;
+pub fn setCsiRXCallback(callback: csi_callback_type, ctx: ?*anyopaque) !void {
+ return try errors.espCheckError(sys.esp_wifi_set_csi_rx_cb(callback, ctx));
+}
+pub fn setCsiConfig(config: ?*const csi_callback_type) !void {
+ return try errors.espCheckError(sys.esp_wifi_set_csi_config(config));
+}
+pub fn setCsi(en: bool) !void {
+ return try errors.espCheckError(sys.esp_wifi_set_csi(en));
+}
+pub fn setAntGPIO(config: [*c]const sys.wifi_ant_gpio_config_t) !void {
+ return try errors.espCheckError(sys.esp_wifi_set_ant_gpio(config));
+}
+pub fn getAntGPIO(config: [*c]sys.wifi_ant_gpio_config_t) !void {
+ return try errors.espCheckError(sys.esp_wifi_get_ant_gpio(config));
+}
+pub fn setAnt(config: ?*const sys.wifi_ant_config_t) !void {
+ return try errors.espCheckError(sys.esp_wifi_set_ant(config));
+}
+pub fn getAnt(config: ?*sys.wifi_ant_config_t) !void {
+ return try errors.espCheckError(sys.esp_wifi_get_ant(config));
+}
+pub fn getTsfTime(interface: wifi_interface_t) i64 {
+ return sys.esp_wifi_get_tsf_time(interface);
+}
+pub fn setInactiveTime(ifx: wifi_interface_t, sec: u16) !void {
+ return try errors.espCheckError(sys.esp_wifi_set_inactive_time(ifx, sec));
+}
+pub fn getInactiveTime(ifx: wifi_interface_t, sec: [*c]u16) !void {
+ return try errors.espCheckError(sys.esp_wifi_get_inactive_time(ifx, sec));
+}
+pub fn statisDump(modules: u32) !void {
+ return try errors.espCheckError(sys.esp_wifi_statis_dump(modules));
+}
+pub fn setRssiThreshold(rssi: i32) !void {
+ return try errors.espCheckError(sys.esp_wifi_set_rssi_threshold(rssi));
+}
+pub const FTM = struct {
+ pub fn initiateSession(cfg: [*c]sys.wifi_ftm_initiator_cfg_t) !void {
+ return try errors.espCheckError(sys.esp_wifi_ftm_initiate_session(cfg));
+ }
+ pub fn endSession() !void {
+ return try errors.espCheckError(sys.esp_wifi_ftm_end_session());
+ }
+ pub fn respSetOffset(offset_cm: i16) !void {
+ return try errors.espCheckError(sys.esp_wifi_ftm_resp_set_offset(offset_cm));
+ }
+};
+pub fn config11bRate(ifx: wifi_interface_t, disable: bool) !void {
+ return try errors.espCheckError(sys.esp_wifi_config_11b_rate(ifx, disable));
+}
+pub fn connectionlessModuleSetWakeInterval(wake_interval: u16) !void {
+ return try errors.espCheckError(sys.esp_wifi_connectionless_module_set_wake_interval(wake_interval));
+}
+pub fn forceWakeupAcquire() !void {
+ return try errors.espCheckError(sys.esp_wifi_force_wakeup_acquire());
+}
+pub fn forceWakeupRelease() !void {
+ return try errors.espCheckError(sys.esp_wifi_force_wakeup_release());
+}
+pub fn config80211TXRate(ifx: wifi_interface_t, rate: sys.wifi_phy_rate_t) !void {
+ return try errors.espCheckError(sys.esp_wifi_config_80211_tx_rate(ifx, rate));
+}
+pub fn disablePMFConfig(ifx: wifi_interface_t) !void {
+ return try errors.espCheckError(sys.esp_wifi_disable_pmf_config(ifx));
+}
+pub fn setDynCS(enabled: bool) !void {
+ return try errors.espCheckError(sys.esp_wifi_set_dynamic_cs(enabled));
+}
+pub const Station = struct {
+ pub fn getAPInfo(ap_info: ?*sys.wifi_ap_record_t) !void {
+ return try errors.espCheckError(sys.esp_wifi_sta_get_ap_info(ap_info));
+ }
+ pub fn getRssi(rssi: [*c]c_int) !void {
+ return try errors.espCheckError(sys.esp_wifi_sta_get_rssi(rssi));
+ }
+ pub fn getAid(aid: [*c]u16) !void {
+ return try errors.espCheckError(sys.esp_wifi_sta_get_aid(aid));
+ }
+ pub fn getNegotiatedPHYMode(phymode: [*c]sys.wifi_phy_mode_t) !void {
+ return try errors.espCheckError(sys.esp_wifi_sta_get_negotiated_phymode(phymode));
+ }
+ pub fn deauth(aid: u16) !void {
+ return try errors.espCheckError(sys.esp_wifi_deauth_sta(aid));
+ }
+ pub const AP = struct {
+ pub fn getList(sta: ?*sys.wifi_sta_list_t) !void {
+ return try errors.espCheckError(sys.esp_wifi_ap_get_sta_list(sta));
+ }
+ pub fn getAid(mac: [*:0]const u8, aid: [*c]u16) !void {
+ return try errors.espCheckError(sys.esp_wifi_ap_get_sta_aid(mac, aid));
+ }
+ pub fn clearList() !void {
+ return try errors.espCheckError(sys.esp_wifi_clear_ap_list());
+ }
+ };
+};
+
+pub const PowerDomain = struct {
+ pub fn On() void {
+ sys.esp_wifi_power_domain_on();
+ }
+ pub fn Off() void {
+ sys.esp_wifi_power_domain_off();
+ }
+};
+
+pub const Enterprise = struct {
+ pub const Station = struct {
+ pub fn enable() !void {
+ return try errors.espCheckError(sys.esp_wifi_sta_enterprise_enable());
+ }
+ pub fn disable() !void {
+ return try errors.espCheckError(sys.esp_wifi_sta_enterprise_disable());
+ }
+ };
+ pub const Client = struct {
+ pub fn setIdentity(identity: [*:0]const u8, len: c_int) !void {
+ return try errors.espCheckError(sys.esp_eap_client_set_identity(identity, len));
+ }
+ pub const clearIdentity = sys.esp_eap_client_clear_identity;
+ pub fn setUsername(username: [*:0]const u8, len: c_int) !void {
+ return try errors.espCheckError(sys.esp_eap_client_set_username(username, len));
+ }
+ pub const clearUsername = sys.esp_eap_client_clear_username;
+ pub fn setPassword(password: [*:0]const u8, len: c_int) !void {
+ return try errors.espCheckError(sys.esp_eap_client_set_password(password, len));
+ }
+ pub const clearPassword = sys.esp_eap_client_clear_new_password;
+ pub fn setNewPassword(new_password: [*:0]const u8, len: c_int) !void {
+ return try errors.espCheckError(sys.esp_eap_client_set_new_password(new_password, len));
+ }
+ pub const clearNewPassword = sys.esp_eap_client_clear_new_password;
+ pub fn setCACertificate(ca_cert: [*:0]const u8, ca_cert_len: c_int) !void {
+ return try errors.espCheckError(sys.esp_eap_client_set_ca_cert(ca_cert, ca_cert_len));
+ }
+ pub const clearCACertificate = sys.esp_eap_client_clear_ca_cert;
+ pub fn setCertificateKey(client_cert: [*:0]const u8, client_cert_len: c_int, private_key: [*:0]const u8, private_key_len: c_int, private_key_password: [*:0]const u8, private_key_passwd_len: c_int) !void {
+ return try errors.espCheckError(sys.esp_eap_client_set_certificate_and_key(client_cert, client_cert_len, private_key, private_key_len, private_key_password, private_key_passwd_len));
+ }
+ pub const clearCertificateKey = sys.esp_eap_client_clear_certificate_and_key;
+ pub fn setDisableTimeCheck(disable: bool) !void {
+ return try errors.espCheckError(sys.esp_eap_client_set_disable_time_check(disable));
+ }
+ pub fn getDisableTimeCheck(disable: [*c]bool) !void {
+ return try errors.espCheckError(sys.esp_eap_client_get_disable_time_check(disable));
+ }
+ pub fn setTTLSPhase2Method(@"type": sys.esp_eap_ttls_phase2_types) !void {
+ return try errors.espCheckError(sys.esp_eap_client_set_ttls_phase2_method(@"type"));
+ }
+ pub fn setSuiteb192bitCertification(enable: bool) !void {
+ return try errors.espCheckError(sys.esp_eap_client_set_suiteb_192bit_certification(enable));
+ }
+ pub fn setPACFile(pac_file: [*:0]const u8, pac_file_len: c_int) !void {
+ return try errors.espCheckError(sys.esp_eap_client_set_pac_file(pac_file, pac_file_len));
+ }
+ pub fn setFastParams(config: sys.esp_eap_fast_config) !void {
+ return try errors.espCheckError(sys.esp_eap_client_set_fast_params(config));
+ }
+ pub fn useDefaultCertificateBundle(use_default_bundle: bool) !void {
+ return try errors.espCheckError(sys.esp_eap_client_use_default_cert_bundle(use_default_bundle));
+ }
+ };
+};
+
+pub const Internal = struct {
+ // STUBS
+ pub fn setStationIp() !void {
+ return try errors.espCheckError(sys.esp_wifi_internal_set_sta_ip());
+ }
+ pub fn registryNetstackBufCallback(ref: sys.wifi_netstack_buf_ref_cb_t, free: sys.wifi_netstack_buf_free_cb_t) !void {
+ return try errors.espCheckError(sys.esp_wifi_internal_reg_netstack_buf_cb(ref, free));
+ }
+ pub fn freeRXBuffer(buffer: ?*anyopaque) !void {
+ if (buffer) |b|
+ return try errors.espCheckError(sys.esp_wifi_internal_free_rx_buffer(b));
+ }
+ pub fn txBuffer(ifx: wifi_interface_t, buffer: ?*anyopaque, len: u16) !void {
+ return try errors.espCheckError(sys.esp_wifi_internal_tx(ifx, buffer, len));
+ }
+ pub fn registryTXCallBack(ifx: wifi_interface_t, @"fn": sys.wifi_rxcb_t) !void {
+ return try errors.espCheckError(sys.esp_wifi_internal_reg_rxcb(ifx, @"fn"));
+ }
+};
+
+pub fn init_config_default() sys.wifi_init_config_t {
+ return sys.wifi_init_config_t{
+ .osi_funcs = &sys.g_wifi_osi_funcs,
+ .wpa_crypto_funcs = sys.g_wifi_default_wpa_crypto_funcs,
+ .static_rx_buf_num = sys.CONFIG_ESP_WIFI_STATIC_RX_BUFFER_NUM,
+ .dynamic_rx_buf_num = sys.CONFIG_ESP_WIFI_DYNAMIC_RX_BUFFER_NUM,
+ .tx_buf_type = sys.CONFIG_ESP_WIFI_TX_BUFFER_TYPE,
+ .static_tx_buf_num = sys.WIFI_STATIC_TX_BUFFER_NUM,
+ .dynamic_tx_buf_num = sys.WIFI_DYNAMIC_TX_BUFFER_NUM,
+ .rx_mgmt_buf_type = sys.CONFIG_ESP_WIFI_DYNAMIC_RX_MGMT_BUF,
+ .rx_mgmt_buf_num = sys.WIFI_RX_MGMT_BUF_NUM_DEF,
+ .cache_tx_buf_num = sys.WIFI_CACHE_TX_BUFFER_NUM,
+ .csi_enable = sys.WIFI_CSI_ENABLED,
+ .ampdu_rx_enable = sys.WIFI_AMPDU_RX_ENABLED,
+ .ampdu_tx_enable = sys.WIFI_AMPDU_TX_ENABLED,
+ .amsdu_tx_enable = sys.WIFI_AMSDU_TX_ENABLED,
+ .nvs_enable = sys.WIFI_NVS_ENABLED,
+ .nano_enable = sys.WIFI_NANO_FORMAT_ENABLED,
+ .rx_ba_win = sys.WIFI_DEFAULT_RX_BA_WIN,
+ .wifi_task_core_id = sys.WIFI_TASK_CORE_ID,
+ .beacon_max_len = sys.WIFI_SOFTAP_BEACON_MAX_LEN,
+ .mgmt_sbuf_num = sys.WIFI_MGMT_SBUF_NUM,
+ .feature_caps = sys.WIFI_FEATURE_CAPS,
+ .sta_disconnected_pm = sys.WIFI_STA_DISCONNECTED_PM_ENABLED != 0,
+ .espnow_max_encrypt_num = sys.CONFIG_ESP_WIFI_ESPNOW_MAX_ENCRYPT_NUM,
+ .tx_hetb_queue_num = sys.WIFI_TX_HETB_QUEUE_NUM,
+ .dump_hesigb_enable = sys.WIFI_DUMP_HESIGB_ENABLED != 0,
+ .magic = sys.WIFI_INIT_CONFIG_MAGIC,
+ };
+}
diff --git a/software/zig_main/imports/wifi_remote.zig b/software/zig_main/imports/wifi_remote.zig
new file mode 100644
index 0000000..55153ff
--- /dev/null
+++ b/software/zig_main/imports/wifi_remote.zig
@@ -0,0 +1,149 @@
+const sys = @import("sys");
+const errors = @import("error");
+
+// ─────────────────────────────────────────────────────────────────────────────
+// Re-exported types
+// ─────────────────────────────────────────────────────────────────────────────
+
+pub const WifiInterface = sys.wifi_interface_t;
+pub const WifiMode = sys.wifi_mode_t;
+pub const WifiPsType = sys.wifi_ps_type_t;
+pub const WifiBandwidth = sys.wifi_bandwidth_t;
+pub const WifiSecondChan = sys.wifi_second_chan_t;
+pub const WifiCountry = sys.wifi_country_t;
+pub const WifiStorage = sys.wifi_storage_t;
+pub const WifiPhyRate = sys.wifi_phy_rate_t;
+pub const WifiBand = sys.wifi_band_t;
+pub const WifiPhyMode = sys.wifi_phy_mode_t;
+
+// ─────────────────────────────────────────────────────────────────────────────
+// Remote Wi-Fi API
+// ─────────────────────────────────────────────────────────────────────────────
+
+pub const WifiRemote = struct {
+
+ // ─── Lifecycle ──────────────────────────────────────────────────────────────
+
+ pub fn init(config: *const sys.wifi_init_config_t) !void {
+ try errors.espCheckError(sys.esp_wifi_remote_init(config));
+ }
+
+ pub fn deinit() !void {
+ try errors.espCheckError(sys.esp_wifi_remote_deinit());
+ }
+
+ pub fn start() !void {
+ try errors.espCheckError(sys.esp_wifi_remote_start());
+ }
+
+ pub fn stop() !void {
+ try errors.espCheckError(sys.esp_wifi_remote_stop());
+ }
+
+ pub fn restore() !void {
+ try errors.espCheckError(sys.esp_wifi_remote_restore());
+ }
+
+ // ─── Mode ───────────────────────────────────────────────────────────────────
+
+ pub fn setMode(mode: WifiMode) !void {
+ try errors.espCheckError(sys.esp_wifi_remote_set_mode(mode));
+ }
+
+ pub fn getMode() !WifiMode {
+ var m: WifiMode = undefined;
+ try errors.espCheckError(sys.esp_wifi_remote_get_mode(&m));
+ return m;
+ }
+
+ // ─── Connect / Disconnect ──────────────────────────────────────────────────
+
+ pub fn connect() !void {
+ try errors.espCheckError(sys.esp_wifi_remote_connect());
+ }
+
+ pub fn disconnect() !void {
+ try errors.espCheckError(sys.esp_wifi_remote_disconnect());
+ }
+
+ // ─── Scan ───────────────────────────────────────────────────────────────────
+
+ pub fn scanStart(config: ?*const sys.wifi_scan_config_t, block: bool) !void {
+ try errors.espCheckError(sys.esp_wifi_remote_scan_start(config, block));
+ }
+
+ pub fn scanStop() !void {
+ try errors.espCheckError(sys.esp_wifi_remote_scan_stop());
+ }
+
+ pub fn getApNum() !u16 {
+ var n: u16 = 0;
+ try errors.espCheckError(sys.esp_wifi_remote_scan_get_ap_num(&n));
+ return n;
+ }
+
+ pub fn getApRecords(records: []sys.wifi_ap_record_t) !u16 {
+ var num = @as(u16, @intCast(records.len));
+ try errors.espCheckError(sys.esp_wifi_remote_scan_get_ap_records(&num, records.ptr));
+ return num;
+ }
+
+ // ─── Station info ───────────────────────────────────────────────────────────
+
+ pub fn staGetApInfo() !sys.wifi_ap_record_t {
+ var info: sys.wifi_ap_record_t = undefined;
+ try errors.espCheckError(sys.esp_wifi_remote_sta_get_ap_info(&info));
+ return info;
+ }
+
+ pub fn staGetRssi() !i32 {
+ var rssi: c_int = 0;
+ try errors.espCheckError(sys.esp_wifi_remote_sta_get_rssi(&rssi));
+ return @intCast(rssi);
+ }
+
+ // ─── Configuration ──────────────────────────────────────────────────────────
+
+ pub fn setConfig(iface: WifiInterface, conf: *const sys.wifi_config_t) !void {
+ try errors.espCheckError(sys.esp_wifi_remote_set_config(iface, @constCast(conf)));
+ }
+
+ pub fn getConfig(iface: WifiInterface) !sys.wifi_config_t {
+ var conf: sys.wifi_config_t = undefined;
+ try errors.espCheckError(sys.esp_wifi_remote_get_config(iface, &conf));
+ return conf;
+ }
+
+ pub fn setCountry(country: *const WifiCountry) !void {
+ try errors.espCheckError(sys.esp_wifi_remote_set_country(country));
+ }
+
+ pub fn setMac(iface: WifiInterface, mac: []const u8) !void {
+ if (mac.len != 6) return error.InvalidMacLength;
+ try errors.espCheckError(sys.esp_wifi_remote_set_mac(iface, mac.ptr));
+ }
+
+ pub fn getMac(iface: WifiInterface) ![6]u8 {
+ var mac: [6]u8 = undefined;
+ try errors.espCheckError(sys.esp_wifi_remote_get_mac(iface, &mac));
+ return mac;
+ }
+
+ // ─── Promiscuous ────────────────────────────────────────────────────────────
+
+ pub fn setPromiscuous(enable: bool) !void {
+ try errors.espCheckError(sys.esp_wifi_remote_set_promiscuous(enable));
+ }
+
+ pub fn setPromiscuousRxCb(cb: sys.wifi_promiscuous_cb_t) !void {
+ try errors.espCheckError(sys.esp_wifi_remote_set_promiscuous_rx_cb(cb));
+ }
+
+ pub fn createDefaultStaNetif() !*sys.esp_netif_t {
+ return sys.esp_wifi_remote_create_default_sta() orelse error.FailedToCreateNetif;
+ }
+
+ pub fn createDefaultApNetif() !*sys.esp_netif_t {
+ return sys.esp_wifi_remote_create_default_ap() orelse error.FailedToCreateNetif;
+ }
+};
diff --git a/software/zig_main/include/bt_stubs.h b/software/zig_main/include/bt_stubs.h
new file mode 100644
index 0000000..a1f43a2
--- /dev/null
+++ b/software/zig_main/include/bt_stubs.h
@@ -0,0 +1,30 @@
+/* bt_stubs.h — Static inline wrappers for BT macros that zig translate-c
+ * cannot handle directly.
+ *
+ * Included by stubs.h AFTER bindings.h (which pulls in esp_bt.h).
+ * The translate-c pipeline converts these into callable Zig functions
+ * inside idf-sys.zig.
+ */
+
+#ifndef BT_STUBS_H
+#define BT_STUBS_H
+
+#ifndef CONFIG_IDF_TARGET_ESP32S2 /* No BT in ESP32-S2 */
+#ifdef CONFIG_BT_ENABLED
+
+/*
+ * BT_CONTROLLER_INIT_CONFIG_DEFAULT() is a macro that expands to a
+ * designated-initialiser struct literal — translate-c chokes on it.
+ * Wrapping it in a static inline function lets the C preprocessor
+ * expand the macro before translate-c sees the AST.
+ */
+static inline esp_bt_controller_config_t zig_bt_controller_default_cfg(void)
+{
+ esp_bt_controller_config_t cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
+ return cfg;
+}
+
+#endif /* CONFIG_BT_ENABLED */
+#endif /* !CONFIG_IDF_TARGET_ESP32S2 */
+
+#endif /* BT_STUBS_H */
diff --git a/software/zig_main/include/freertos-compat/freertos/portmacro.h b/software/zig_main/include/freertos-compat/freertos/portmacro.h
new file mode 100644
index 0000000..8649901
--- /dev/null
+++ b/software/zig_main/include/freertos-compat/freertos/portmacro.h
@@ -0,0 +1,15 @@
+/* Redirect wrapper — fixes Windows mixed-separator translate-c issue (issue #50).
+ *
+ * On Windows, clang joins include dirs with '\' when building resolved paths.
+ * Having both `-I"include"` and `-I"include/freertos"` produces two different
+ * path strings for the same physical file:
+ * include\freertos/portmacro.h (via "freertos/portmacro.h")
+ * include/freertos\portmacro.h (via "portmacro.h")
+ * #pragma once fails because these look like different files to clang's VFS.
+ *
+ * Fix: redirect all "freertos/portmacro.h" (prefixed) includes through this
+ * single wrapper. Angle-bracket include avoids same-dir lookup, finding the
+ * real portmacro.h via -I"portable/ARCH/include/freertos" as the sole path.
+ */
+#pragma once
+#include
diff --git a/software/zig_main/include/matter_closure_patch.h b/software/zig_main/include/matter_closure_patch.h
new file mode 100644
index 0000000..1c02a5b
--- /dev/null
+++ b/software/zig_main/include/matter_closure_patch.h
@@ -0,0 +1,30 @@
+#pragma once
+// CHIP SDK closure-control cluster structs are missing operator==(const T&) required
+// by std::optional comparison in GCC 14 C++23 mode.
+// This header adds free-function operators via ADL so the types satisfy
+// std::equality_comparable without touching managed_components sources.
+// Applied via -include CMake file property to the specific failing TUs only.
+#ifdef __cplusplus
+#include "app/clusters/closure-control-server/closure-control-cluster-objects.h"
+
+namespace chip {
+namespace app {
+namespace Clusters {
+namespace ClosureControl {
+
+inline bool operator==(const GenericOverallCurrentState & a, const GenericOverallCurrentState & b)
+{
+ return a.position == b.position && a.latch == b.latch &&
+ a.speed == b.speed && a.secureState == b.secureState;
+}
+
+inline bool operator==(const GenericOverallTargetState & a, const GenericOverallTargetState & b)
+{
+ return a.position == b.position && a.latch == b.latch && a.speed == b.speed;
+}
+
+} // namespace ClosureControl
+} // namespace Clusters
+} // namespace app
+} // namespace chip
+#endif // __cplusplus
diff --git a/software/zig_main/include/matter_stubs.h b/software/zig_main/include/matter_stubs.h
new file mode 100644
index 0000000..b31e2c5
--- /dev/null
+++ b/software/zig_main/include/matter_stubs.h
@@ -0,0 +1,305 @@
+/*
+ * matter_stubs.h — C wrapper interface for esp_matter (CHIP SDK C++ API)
+ *
+ * This header is included by stubs.h and processed by zig translate-c.
+ * It provides a pure-C interface over the C++ esp_matter API.
+ *
+ * The actual implementations are in main/matter_wrappers.cpp (compiled as C++
+ * by the ESP-IDF CMake build, with full access to CHIP SDK headers).
+ *
+ * NOTE: esp_matter_val_type_t, esp_matter_val_t, esp_matter_attr_val_t and
+ * esp_matter_attr_bounds_t are also defined in the real esp_matter_attribute_utils.h.
+ * Those definitions are guarded with #ifndef __cplusplus so that they are only
+ * visible to zig translate-c (pure C mode). In C++ compilation the real header
+ * definitions are used, avoiding redefinition conflicts.
+ */
+#pragma once
+
+#include
+#include
+#include
+
+/* ── Opaque handle types ─────────────────────────────────────────────────── */
+/* These names do not exist in the real esp_matter C++ headers (which use
+ * node_t / endpoint_t inside namespaces), so they are safe to define in
+ * both C and C++ compilation contexts. */
+
+typedef size_t esp_matter_node_t;
+typedef size_t esp_matter_endpoint_t;
+typedef size_t esp_matter_cluster_t;
+typedef size_t esp_matter_attribute_t;
+
+/* ── Endpoint flags ──────────────────────────────────────────────────────── */
+/* Real esp_matter uses ENDPOINT_FLAG_* (no prefix); these names are unique. */
+
+typedef enum esp_matter_ep_flags {
+ ESP_MATTER_EP_FLAG_NONE = 0x00,
+ ESP_MATTER_EP_FLAG_DESTROYABLE = 0x01,
+ ESP_MATTER_EP_FLAG_BRIDGE = 0x02,
+} esp_matter_ep_flags_t;
+
+/* ── Types that ALSO exist in esp_matter_attribute_utils.h ───────────────── */
+/* Guarded so the real header wins in C++ compilation. */
+
+#ifndef __cplusplus
+
+typedef enum esp_matter_val_type {
+ ESP_MATTER_VAL_TYPE_INVALID = 0,
+ ESP_MATTER_VAL_TYPE_BOOLEAN = 1,
+ ESP_MATTER_VAL_TYPE_INTEGER = 2,
+ ESP_MATTER_VAL_TYPE_FLOAT = 3,
+ ESP_MATTER_VAL_TYPE_ARRAY = 4,
+ ESP_MATTER_VAL_TYPE_CHAR_STRING = 5,
+ ESP_MATTER_VAL_TYPE_OCTET_STRING = 6,
+ ESP_MATTER_VAL_TYPE_INT8 = 7,
+ ESP_MATTER_VAL_TYPE_UINT8 = 8,
+ ESP_MATTER_VAL_TYPE_INT16 = 9,
+ ESP_MATTER_VAL_TYPE_UINT16 = 10,
+ ESP_MATTER_VAL_TYPE_INT32 = 11,
+ ESP_MATTER_VAL_TYPE_UINT32 = 12,
+ ESP_MATTER_VAL_TYPE_INT64 = 13,
+ ESP_MATTER_VAL_TYPE_UINT64 = 14,
+ ESP_MATTER_VAL_TYPE_ENUM8 = 15,
+ ESP_MATTER_VAL_TYPE_BITMAP8 = 16,
+ ESP_MATTER_VAL_TYPE_BITMAP16 = 17,
+ ESP_MATTER_VAL_TYPE_BITMAP32 = 18,
+ ESP_MATTER_VAL_TYPE_ENUM16 = 19,
+ ESP_MATTER_VAL_TYPE_LONG_CHAR_STRING = 20,
+ ESP_MATTER_VAL_TYPE_LONG_OCTET_STRING = 21,
+} esp_matter_val_type_t;
+
+/** Attribute value union — binary-compatible with esp_matter_val_t */
+typedef union esp_matter_val {
+ bool b;
+ int i;
+ float f;
+ int8_t i8;
+ uint8_t u8;
+ int16_t i16;
+ uint16_t u16;
+ int32_t i32;
+ uint32_t u32;
+ int64_t i64;
+ uint64_t u64;
+ struct {
+ uint8_t *b; /* buffer pointer */
+ uint16_t s; /* data size */
+ uint16_t n; /* element count */
+ uint16_t t; /* total allocated size */
+ } a; /* array / string */
+ void *p;
+} esp_matter_val_t;
+
+/** Attribute value struct — binary-compatible with esp_matter_attr_val_t */
+typedef struct esp_matter_attr_val {
+ esp_matter_val_type_t type;
+ esp_matter_val_t val;
+} esp_matter_attr_val_t;
+
+/** Attribute bounds */
+typedef struct esp_matter_attr_bounds {
+ esp_matter_attr_val_t min;
+ esp_matter_attr_val_t max;
+} esp_matter_attr_bounds_t;
+
+#endif /* !__cplusplus */
+
+/* ── Attribute callback ──────────────────────────────────────────────────── */
+/* These types are unique to our C wrapper (not in real esp_matter headers).
+ * They must be visible in both C and C++ so the extern "C" function
+ * declarations below can use them. In C++ mode esp_matter_attr_val_t is
+ * provided by the previously-included real esp_matter_attribute_utils.h. */
+
+typedef enum esp_matter_attr_cb_type {
+ ESP_MATTER_ATTR_CB_PRE_UPDATE = 0,
+ ESP_MATTER_ATTR_CB_POST_UPDATE,
+ ESP_MATTER_ATTR_CB_READ,
+ ESP_MATTER_ATTR_CB_WRITE,
+} esp_matter_attr_cb_type_t;
+
+/**
+ * Attribute update callback — called before/after every attribute change.
+ * Return ESP_OK to allow the update; return error from PRE_UPDATE to block it.
+ */
+typedef esp_err_t (*esp_matter_attr_callback_t)(
+ esp_matter_attr_cb_type_t type,
+ uint16_t endpoint_id,
+ uint32_t cluster_id,
+ uint32_t attribute_id,
+ esp_matter_attr_val_t *val,
+ void *priv_data);
+
+/* ── Identification (Identify cluster) callback ──────────────────────────── */
+
+typedef enum esp_matter_identify_cb_type {
+ ESP_MATTER_IDENTIFY_CB_START = 0,
+ ESP_MATTER_IDENTIFY_CB_STOP,
+ ESP_MATTER_IDENTIFY_CB_EFFECT,
+} esp_matter_identify_cb_type_t;
+
+typedef esp_err_t (*esp_matter_identify_callback_t)(
+ esp_matter_identify_cb_type_t type,
+ uint16_t endpoint_id,
+ uint8_t effect_id,
+ uint8_t effect_variant,
+ void *priv_data);
+
+/* ── Function declarations ───────────────────────────────────────────────── */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** Start the Matter stack (call after building node + endpoints). */
+esp_err_t esp_matter_wrapper_start(
+ esp_matter_attr_callback_t attr_cb,
+ esp_matter_identify_callback_t identify_cb);
+
+/** Perform a factory reset (erases NVS, then reboots). */
+esp_err_t esp_matter_wrapper_factory_reset(void);
+
+/** Return true if Matter has been started. */
+bool esp_matter_wrapper_is_started(void);
+
+/* ── Node ────────────────────────────────────────────────────────────────── */
+
+/**
+ * Create a root node (endpoint 0) with default config.
+ * attr_cb and identify_cb are stored for the lifetime of the node.
+ * priv_data is forwarded to both callbacks.
+ */
+esp_matter_node_t *esp_matter_wrapper_node_create(
+ esp_matter_attr_callback_t attr_cb,
+ esp_matter_identify_callback_t identify_cb,
+ void *priv_data);
+
+/* ── Endpoint ────────────────────────────────────────────────────────────── */
+
+/** Create a generic endpoint on the node. */
+esp_matter_endpoint_t *esp_matter_wrapper_endpoint_create(
+ esp_matter_node_t *node,
+ uint8_t flags,
+ void *priv_data);
+
+/** Destroy an endpoint (only valid if ENDPOINT_FLAG_DESTROYABLE was set). */
+esp_err_t esp_matter_wrapper_endpoint_destroy(
+ esp_matter_node_t *node,
+ esp_matter_endpoint_t *endpoint);
+
+/** Return the endpoint ID assigned by the stack. */
+uint16_t esp_matter_wrapper_endpoint_get_id(esp_matter_endpoint_t *endpoint);
+
+/** Associate a device type with an endpoint. */
+esp_err_t esp_matter_wrapper_endpoint_add_device_type(
+ esp_matter_endpoint_t *endpoint,
+ uint32_t device_type_id,
+ uint8_t version);
+
+/** Enable a dynamically-created endpoint (call after esp_matter::start()). */
+esp_err_t esp_matter_wrapper_endpoint_enable(esp_matter_endpoint_t *endpoint);
+
+/* ── Pre-built device-type endpoints ─────────────────────────────────────── */
+
+/** Create an On/Off Light endpoint (EP_FLAG_NONE recommended). */
+esp_matter_endpoint_t *esp_matter_wrapper_add_on_off_light(
+ esp_matter_node_t *node,
+ uint8_t flags,
+ void *priv_data);
+
+/** Create an On/Off Switch endpoint. */
+esp_matter_endpoint_t *esp_matter_wrapper_add_on_off_switch(
+ esp_matter_node_t *node,
+ uint8_t flags,
+ void *priv_data);
+
+/** Create a Dimmable Light endpoint. */
+esp_matter_endpoint_t *esp_matter_wrapper_add_dimmable_light(
+ esp_matter_node_t *node,
+ uint8_t flags,
+ void *priv_data);
+
+/** Create a Color Temperature Light endpoint. */
+esp_matter_endpoint_t *esp_matter_wrapper_add_color_temperature_light(
+ esp_matter_node_t *node,
+ uint8_t flags,
+ void *priv_data);
+
+/* ── Cluster ─────────────────────────────────────────────────────────────── */
+
+/** Create a cluster on an endpoint. */
+esp_matter_cluster_t *esp_matter_wrapper_cluster_create(
+ esp_matter_endpoint_t *endpoint,
+ uint32_t cluster_id,
+ uint8_t flags);
+
+/* ── Attribute ───────────────────────────────────────────────────────────── */
+
+/** Create an attribute on a cluster. */
+esp_matter_attribute_t *esp_matter_wrapper_attribute_create(
+ esp_matter_cluster_t *cluster,
+ uint32_t attribute_id,
+ uint16_t flags,
+ esp_matter_attr_val_t val);
+
+/** Update an attribute value (use after esp_matter::start()). */
+esp_err_t esp_matter_wrapper_attribute_update(
+ uint16_t endpoint_id,
+ uint32_t cluster_id,
+ uint32_t attribute_id,
+ esp_matter_attr_val_t *val);
+
+/** Get an attribute value. */
+esp_err_t esp_matter_wrapper_attribute_get_val(
+ esp_matter_attribute_t *attribute,
+ esp_matter_attr_val_t *val);
+
+/** Set an attribute value (use before esp_matter::start()). */
+esp_err_t esp_matter_wrapper_attribute_set_val(
+ esp_matter_attribute_t *attribute,
+ esp_matter_attr_val_t *val);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+/* ── Convenience attribute constructors (C-only; use real esp_matter_* in C++) */
+#ifndef __cplusplus
+
+static inline esp_matter_attr_val_t esp_matter_val_bool(bool val) {
+ esp_matter_attr_val_t v;
+ v.type = ESP_MATTER_VAL_TYPE_BOOLEAN;
+ v.val.b = val;
+ return v;
+}
+static inline esp_matter_attr_val_t esp_matter_val_uint8(uint8_t val) {
+ esp_matter_attr_val_t v;
+ v.type = ESP_MATTER_VAL_TYPE_UINT8;
+ v.val.u8 = val;
+ return v;
+}
+static inline esp_matter_attr_val_t esp_matter_val_uint16(uint16_t val) {
+ esp_matter_attr_val_t v;
+ v.type = ESP_MATTER_VAL_TYPE_UINT16;
+ v.val.u16 = val;
+ return v;
+}
+static inline esp_matter_attr_val_t esp_matter_val_uint32(uint32_t val) {
+ esp_matter_attr_val_t v;
+ v.type = ESP_MATTER_VAL_TYPE_UINT32;
+ v.val.u32 = val;
+ return v;
+}
+static inline esp_matter_attr_val_t esp_matter_val_int16(int16_t val) {
+ esp_matter_attr_val_t v;
+ v.type = ESP_MATTER_VAL_TYPE_INT16;
+ v.val.i16 = val;
+ return v;
+}
+static inline esp_matter_attr_val_t esp_matter_val_nullable(void) {
+ esp_matter_attr_val_t v;
+ v.type = ESP_MATTER_VAL_TYPE_INVALID;
+ v.val.u64 = 0;
+ return v;
+}
+
+#endif /* !__cplusplus */
diff --git a/software/zig_main/include/pthread_stubs.h b/software/zig_main/include/pthread_stubs.h
new file mode 100644
index 0000000..bf541fa
--- /dev/null
+++ b/software/zig_main/include/pthread_stubs.h
@@ -0,0 +1,121 @@
+/* pthread_stubs.h — Minimal POSIX pthread declarations for zig translate-c.
+ *
+ * The system pthread.h (newlib) fails translate-c because it includes
+ * , , and — all of which are blocked by
+ * header guards in stubs.h. This file provides the exact types and function
+ * prototypes that ESP-IDF's FreeRTOS-backed pthread implementation exposes,
+ * expressed in a form translate-c can digest without those dependencies.
+ */
+
+#ifndef PTHREAD_STUBS_H
+#define PTHREAD_STUBS_H
+
+#include
+#include
+
+/* ── Opaque handle types ──────────────────────────────────────────────────── */
+
+typedef unsigned int pthread_t;
+typedef unsigned int pthread_mutex_t;
+typedef unsigned int pthread_cond_t;
+typedef unsigned int pthread_key_t;
+
+/* ── Attribute structs ───────────────────────────────────────────────────── */
+
+typedef struct {
+ int is_initialized;
+ void *stackaddr;
+ int stacksize;
+ int contentionscope;
+ int inheritsched;
+ int schedpolicy;
+ int sched_priority; /* inline sched_param to avoid sched.h dependency */
+ int detachstate;
+} pthread_attr_t;
+
+typedef struct {
+ int is_initialized;
+ int type;
+ int recursive;
+} pthread_mutexattr_t;
+
+typedef struct {
+ int is_initialized;
+ unsigned long clock;
+} pthread_condattr_t;
+
+typedef struct {
+ int is_initialized;
+ int init_executed;
+} pthread_once_t;
+
+/* ── timespec (needed for timedlock / timedwait) ─────────────────────────── */
+
+/* Define struct timespec once and mark all known system guards to prevent
+ sys/_timespec.h from redefining it with an incompatible layout. */
+#if !defined(_TIMESPEC_DEFINED) && !defined(_SYS__TIMESPEC_H_)
+#define _TIMESPEC_DEFINED
+#define _SYS__TIMESPEC_H_
+struct timespec {
+ long tv_sec;
+ long tv_nsec;
+};
+#endif
+
+/* ── Thread lifecycle ────────────────────────────────────────────────────── */
+
+int pthread_create(pthread_t *tid, const pthread_attr_t *attr,
+ void *(*start_routine)(void *), void *arg);
+int pthread_join(pthread_t thread, void **value_ptr);
+int pthread_detach(pthread_t thread);
+void pthread_exit(void *value_ptr);
+pthread_t pthread_self(void);
+int pthread_equal(pthread_t t1, pthread_t t2);
+void pthread_yield(void);
+int pthread_once(pthread_once_t *once_control, void (*init_routine)(void));
+int pthread_cancel(pthread_t thread);
+int pthread_setcancelstate(int state, int *oldstate);
+int pthread_setcanceltype(int type, int *oldtype);
+void pthread_testcancel(void);
+
+/* ── Thread attributes ───────────────────────────────────────────────────── */
+
+int pthread_attr_init(pthread_attr_t *attr);
+int pthread_attr_destroy(pthread_attr_t *attr);
+int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);
+int pthread_attr_getstacksize(const pthread_attr_t *attr, size_t *stacksize);
+int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
+int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate);
+
+/* ── Mutex ───────────────────────────────────────────────────────────────── */
+
+int pthread_mutexattr_init(pthread_mutexattr_t *attr);
+int pthread_mutexattr_destroy(pthread_mutexattr_t *attr);
+int pthread_mutexattr_gettype(const pthread_mutexattr_t *attr, int *kind);
+int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int kind);
+int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
+int pthread_mutex_destroy(pthread_mutex_t *mutex);
+int pthread_mutex_lock(pthread_mutex_t *mutex);
+int pthread_mutex_trylock(pthread_mutex_t *mutex);
+int pthread_mutex_unlock(pthread_mutex_t *mutex);
+int pthread_mutex_timedlock(pthread_mutex_t *mutex,
+ const struct timespec *timeout);
+
+/* ── Condition variable ──────────────────────────────────────────────────── */
+
+int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);
+int pthread_cond_destroy(pthread_cond_t *cond);
+int pthread_cond_signal(pthread_cond_t *cond);
+int pthread_cond_broadcast(pthread_cond_t *cond);
+int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
+int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex,
+ const struct timespec *abstime);
+
+/* ── Thread-local storage (TLS) keys ─────────────────────────────────────── */
+
+int pthread_key_create(pthread_key_t *key, void (*destructor)(void *));
+int pthread_key_delete(pthread_key_t key);
+int pthread_setspecific(pthread_key_t key, const void *value);
+void *pthread_getspecific(pthread_key_t key);
+
+#endif /* PTHREAD_STUBS_H */
diff --git a/software/zig_main/include/stubs.h b/software/zig_main/include/stubs.h
new file mode 100644
index 0000000..4220aa1
--- /dev/null
+++ b/software/zig_main/include/stubs.h
@@ -0,0 +1,168 @@
+/* stubs.h - Stubs file for zig translate-c with ESP-IDF */
+
+#ifndef STUBS_H
+#define STUBS_H
+
+/* Prevent inclusion of problematic cstdlib headers */
+#define _STDIO_H_
+#define _STDLIB_H_
+#define _STRING_H_
+#define _WCHAR_H_
+#define _SYS_STAT_H
+#define _SYS_REENT_H
+#define _SYS_TYPES_H
+#define _SYS_SIGNAL_H
+#define _SYS_TIME_H_
+#define _SYS__DEFAULT_FCNTL_H_
+#define _MBSTATE_T
+#define _SYS_LOCK_H
+#define _SYS_UNISTD_H
+#define _UNISTD_H
+#define _TIME_H_
+/* Block system pthread.h — fails translate-c due to _SYS_TYPES_H conflicts;
+ our pthread_stubs.h provides clean, translate-c-compatible declarations. */
+#define __PTHREAD_h
+#define _PTHREAD_H
+
+/* Prevent real header from redefining them */
+#define __ESP_ASSERT_H__
+#define __ESP_VFS_H__
+
+/* Avoid redefining types that exist in real headers */
+#undef _mbstate_t
+#undef __mbstate_t_defined
+
+/* Define only what's strictly necessary without conflicting */
+typedef void *FILE;
+// typedef int _LOCK_T;
+typedef void *__VALIST;
+typedef long off_t;
+typedef long _off_t;
+typedef unsigned int wint_t;
+typedef unsigned int mode_t;
+
+/* Disable macros and attributes that confuse zig translate-c */
+#define __restrict
+#define __extension__
+#define __attribute__(x)
+#define __THROW
+#define __wur
+#define __volatile__
+#define __inline
+
+/* Disable IDF-specific attributes */
+#define IRAM_ATTR
+#define DRAM_ATTR
+#define RTC_DATA_ATTR
+#define SECTION_ATTR_IMPL(x, y)
+#ifdef ESP_STATIC_ASSERT
+#undef ESP_STATIC_ASSERT
+#endif
+#define ESP_STATIC_ASSERT(expr, msg)
+
+/* Block multibyte functions from stdlib */
+#define mblen
+#define mbtowc
+#define wctomb
+#define mbstowcs
+#define wcstombs
+
+/* Workaround for FreeRTOS TLS dummy field (prevents struct _reent usage) */
+#define configTLS_BLOCK_TYPE int
+#define portTLS_BLOCK_TYPE int
+
+/* ────────────────────────────────────────────────────────────────────────────
+ IDF component guards – mirror sdkconfig.h / bindings.h logic
+ Enable ONLY what your project actually uses
+ ────────────────────────────────────────────────────────────────────────────
+ */
+#include "sdkconfig.h"
+/* Always available basics */
+#define ESP_IDF_COMP_SOC_ENABLED
+#define ESP_IDF_COMP_ESP_DRIVER_GPIO_ENABLED // almost always needed
+
+/* Commonly used – uncomment what you need */
+#define ESP_IDF_COMP_ESP_WIFI_ENABLED
+#define ESP_IDF_COMP_ESP_NETIF_ENABLED
+#define ESP_IDF_COMP_ESP_EVENT_ENABLED
+#define ESP_IDF_COMP_NVS_FLASH_ENABLED
+#define ESP_IDF_COMP_ESP_TIMER_ENABLED
+#define ESP_IDF_COMP_ESP_PM_ENABLED
+#define ESP_IDF_COMP_ESP_TLS_ENABLED
+#define ESP_IDF_COMP_MBEDTLS_ENABLED
+#define ESP_IDF_COMP_LWIP_ENABLED
+#define ESP_IDF_COMP_VFS_ENABLED
+
+/* Drivers (split since ~v5.1–5.3) – enable per driver you use */
+#define ESP_IDF_COMP_ESP_DRIVER_UART_ENABLED
+#define ESP_IDF_COMP_ESP_DRIVER_I2C_ENABLED
+#define ESP_IDF_COMP_ESP_DRIVER_SPI_ENABLED
+#define ESP_IDF_COMP_ESP_DRIVER_RMT_ENABLED
+#define ESP_IDF_COMP_ESP_DRIVER_LEDC_ENABLED
+#define ESP_IDF_COMP_ESP_DRIVER_GPTIMER_ENABLED
+#define ESP_IDF_COMP_ESP_DRIVER_ADC_ENABLED
+#define ESP_IDF_COMP_ESP_DRIVER_TWAI_ENABLED
+
+#define ESP_IDF_COMP_PTHREAD_ENABLED
+
+/* Optional / advanced – enable only if used */
+#define ESP_IDF_COMP_ESP_HTTP_CLIENT_ENABLED
+#define ESP_IDF_COMP_ESP_HTTP_SERVER_ENABLED
+#define ESP_IDF_COMP_ESP_HTTPS_OTA_ENABLED
+#define ESP_IDF_COMP_ESP_NOW_ENABLED
+#define ESP_IDF_COMP_WPA_SUPPLICANT_ENABLED
+#define ESP_IDF_COMP_ESP_COEX_ENABLED
+#define ESP_IDF_COMP_ESP_PSRAM_ENABLED // himem/psram
+#define ESP_IDF_COMP_ESP_LCD_ENABLED
+
+// #define ESP_IDF_COMP_ESPRESSIF__ESP_TINYUSB_ENABLED
+
+/* Conditional component defines used in bindings.h */
+#ifdef ESP_IDF_COMP_ESP_WIFI_ENABLED
+#define ESP_IDF_COMP_ESP_NETIF_ENABLED
+#endif
+#ifdef ESP_IDF_COMP_WPA_SUPPLICANT_ENABLED
+#define ESP_IDF_COMP_ESP_WIFI_ENABLED
+#endif
+
+/* Version-aware stubs */
+#if ESP_IDF_VERSION_MAJOR >= 5
+#define ESP_IDF_COMP_ESP_DRIVER_ENABLED 0 // drivers are now per-component
+#else
+#define ESP_IDF_COMP_DRIVER_ENABLED
+#endif
+
+#if ESP_IDF_VERSION_MAJOR >= 5 && ESP_IDF_VERSION_MINOR >= 1
+#define ESP_IDF_COMP_ESP_COEX_ENABLED // coexist moved
+#endif
+
+#if HAS_ESP_WIFI_REMOTE
+#define ESP_IDF_COMP_ESP_WIFI_REMOTE_ENABLED
+#endif
+#if HAS_ESP_HOSTED
+#define ESP_IDF_COMP_ESP_HOSTED_ENABLED
+#endif
+
+#include // for size_t, ptrdiff_t, NULL
+#include // for uint8_t, uint32_t, etc.
+#include // for additional POSIX types
+
+/* Include AFTER all the protections */
+#include "pthread_stubs.h"
+#include "bindings.h"
+#include "bt_stubs.h"
+#include "wifi_stubs.h"
+
+// Optional/Managed Components
+// check cmake/extra-components.cmake
+#if HAS_LED_STRIP
+#include "led_strip.h"
+#endif
+#if HAS_ESP_DSP
+#include "esp_dsp.h"
+#endif
+#if HAS_ESP_MATTER
+#include "matter_stubs.h"
+#endif
+
+#endif // STUBS_H
\ No newline at end of file
diff --git a/software/zig_main/include/wifi_stubs.h b/software/zig_main/include/wifi_stubs.h
new file mode 100644
index 0000000..5e66e95
--- /dev/null
+++ b/software/zig_main/include/wifi_stubs.h
@@ -0,0 +1,47 @@
+/* The examples use WiFi configuration that you can set via project
+ configuration menu
+
+ If you'd rather not, just change the below entries to strings with
+ the config you want - ie #define EXAMPLE_WIFI_SSID "mywifissid"
+*/
+#define EXAMPLE_ESP_WIFI_SSID CONFIG_ESP_WIFI_SSID
+#define EXAMPLE_ESP_WIFI_PASS CONFIG_ESP_WIFI_PASSWORD
+#define EXAMPLE_ESP_MAXIMUM_RETRY CONFIG_ESP_MAXIMUM_RETRY
+
+#if CONFIG_ESP_WPA3_SAE_PWE_HUNT_AND_PECK
+#define ESP_WIFI_SAE_MODE WPA3_SAE_PWE_HUNT_AND_PECK
+#define EXAMPLE_H2E_IDENTIFIER ""
+#elif CONFIG_ESP_WPA3_SAE_PWE_HASH_TO_ELEMENT
+#define ESP_WIFI_SAE_MODE WPA3_SAE_PWE_HASH_TO_ELEMENT
+#define EXAMPLE_H2E_IDENTIFIER CONFIG_ESP_WIFI_PW_ID
+#elif CONFIG_ESP_WPA3_SAE_PWE_BOTH
+#define ESP_WIFI_SAE_MODE WPA3_SAE_PWE_BOTH
+#define EXAMPLE_H2E_IDENTIFIER CONFIG_ESP_WIFI_PW_ID
+#endif
+#if CONFIG_ESP_WIFI_AUTH_OPEN
+#define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_OPEN
+#elif CONFIG_ESP_WIFI_AUTH_WEP
+#define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WEP
+#elif CONFIG_ESP_WIFI_AUTH_WPA_PSK
+#define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WPA_PSK
+#elif CONFIG_ESP_WIFI_AUTH_WPA2_PSK
+#define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WPA2_PSK
+#elif CONFIG_ESP_WIFI_AUTH_WPA_WPA2_PSK
+#define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WPA_WPA2_PSK
+#elif CONFIG_ESP_WIFI_AUTH_WPA3_PSK
+#define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WPA3_PSK
+#elif CONFIG_ESP_WIFI_AUTH_WPA2_WPA3_PSK
+#define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WPA2_WPA3_PSK
+#elif CONFIG_ESP_WIFI_AUTH_WAPI_PSK
+#define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WAPI_PSK
+#endif
+
+/* FreeRTOS event group to signal when we are connected*/
+static EventGroupHandle_t s_wifi_event_group;
+
+/* The event group allows multiple bits for each event, but we only care about
+ * two events:
+ * - we are connected to the AP with an IP
+ * - we failed to connect after the maximum amount of retries */
+#define WIFI_CONNECTED_BIT BIT0
+#define WIFI_FAIL_BIT BIT1
\ No newline at end of file
diff --git a/software/zig_main/main/CMakeLists.txt b/software/zig_main/main/CMakeLists.txt
new file mode 100644
index 0000000..6dc2ffc
--- /dev/null
+++ b/software/zig_main/main/CMakeLists.txt
@@ -0,0 +1,98 @@
+set(DEPS nvs_flash)
+if(CONFIG_ESP_WIFI_ENABLED)
+ list(APPEND DEPS esp_wifi esp_netif esp_event)
+endif()
+if(CONFIG_BT_ENABLED)
+ list(APPEND DEPS bt)
+endif()
+
+set(MAIN_SRCS "placeholder.c")
+
+# Add C++ Matter wrapper shim only when esp_matter is an active build component.
+# The component must be added via idf.py add-dependency espressif/esp_matter.
+# Note: esp_matter 1.4.x is not yet compatible with IDF v6.0 (requires json/mqtt builtins).
+if(TARGET __idf_espressif__esp_matter AND IDF_VERSION_MAJOR LESS 6)
+ list(APPEND MAIN_SRCS "matter_wrappers.cpp")
+ list(APPEND DEPS espressif__esp_matter)
+ message(STATUS "ESP Matter: C++ wrapper shim enabled")
+endif()
+
+idf_component_register(
+ SRCS ${MAIN_SRCS}
+ INCLUDE_DIRS "." "${CMAKE_SOURCE_DIR}/include"
+ REQUIRES ${DEPS}
+)
+
+# Build your project or examples
+
+set(ZIG_EXAMPLE_ARG "") # default is main/app.zig
+
+if(CONFIG_ZIG_EXAMPLE_SMARTLED_RGB)
+ set(ZIG_ROOT_EXAMPLE_SOURCE "main/examples/smartled-rgb.zig")
+ set(ZIG_EXAMPLE_ARG "-Dexample=${ZIG_ROOT_EXAMPLE_SOURCE}")
+elseif(CONFIG_ZIG_EXAMPLE_WIFI_STATION AND CONFIG_ESP_WIFI_ENABLED)
+ set(ZIG_ROOT_EXAMPLE_SOURCE "main/examples/wifi-station.zig")
+ set(ZIG_EXAMPLE_ARG "-Dexample=${ZIG_ROOT_EXAMPLE_SOURCE}")
+elseif(CONFIG_ZIG_EXAMPLE_DSP_MATH)
+ set(ZIG_ROOT_EXAMPLE_SOURCE "main/examples/dsp-math.zig")
+ set(ZIG_EXAMPLE_ARG "-Dexample=${ZIG_ROOT_EXAMPLE_SOURCE}")
+elseif(CONFIG_ZIG_EXAMPLE_GPIO_BLINK)
+ set(ZIG_ROOT_EXAMPLE_SOURCE "main/examples/gpio-blink.zig")
+ set(ZIG_EXAMPLE_ARG "-Dexample=${ZIG_ROOT_EXAMPLE_SOURCE}")
+elseif(CONFIG_ZIG_EXAMPLE_HTTP_SERVER)
+ set(ZIG_ROOT_EXAMPLE_SOURCE "main/examples/http-server.zig")
+ set(ZIG_EXAMPLE_ARG "-Dexample=${ZIG_ROOT_EXAMPLE_SOURCE}")
+elseif(CONFIG_ZIG_EXAMPLE_BLE_GATT_SERVER AND CONFIG_BT_ENABLED)
+ set(ZIG_ROOT_EXAMPLE_SOURCE "main/examples/ble-gatt-server.zig")
+ set(ZIG_EXAMPLE_ARG "-Dexample=${ZIG_ROOT_EXAMPLE_SOURCE}")
+elseif(CONFIG_ZIG_EXAMPLE_I2C_SCAN)
+ set(ZIG_ROOT_EXAMPLE_SOURCE "main/examples/i2c-scan.zig")
+ set(ZIG_EXAMPLE_ARG "-Dexample=${ZIG_ROOT_EXAMPLE_SOURCE}")
+elseif(CONFIG_ZIG_EXAMPLE_UART_ECHO)
+ set(ZIG_ROOT_EXAMPLE_SOURCE "main/examples/uart-echo.zig")
+ set(ZIG_EXAMPLE_ARG "-Dexample=${ZIG_ROOT_EXAMPLE_SOURCE}")
+elseif(CONFIG_ZIG_EXAMPLE_MATTER_LIGHT)
+ set(ZIG_ROOT_EXAMPLE_SOURCE "main/examples/matter-light.zig")
+ set(ZIG_EXAMPLE_ARG "-Dexample=${ZIG_ROOT_EXAMPLE_SOURCE}")
+endif()
+
+if(ZIG_EXAMPLE_ARG)
+ message(STATUS "Zig Example: ${ZIG_ROOT_EXAMPLE_SOURCE}")
+else()
+ message(STATUS "Zig Example: using default (main/app.zig)")
+endif()
+
+include(${CMAKE_SOURCE_DIR}/cmake/zig-config.cmake)
+
+# FIXME: need add for build kalman
+if(TARGET __idf_espressif__esp-dsp AND IDF_VERSION_MAJOR EQUAL 6)
+ target_compile_options(__idf_espressif__esp-dsp PRIVATE
+ $<$:-includecmath>
+ )
+endif()
+
+# CHIP SDK: suppress -Werror=format-nonliteral warnings-as-errors that GCC 14
+# raises on format strings forwarded through function pointers (BLEManagerImpl etc.).
+if(TARGET __idf_espressif__esp_matter AND IDF_VERSION_MAJOR LESS 6)
+ target_compile_options(__idf_espressif__esp_matter PRIVATE
+ $<$:-Wno-error=format-nonliteral>
+ )
+endif()
+
+# CHIP SDK closure-control cluster structs are missing operator==(const T&), which
+# GCC 14 C++23 mode rejects when used in std::optional comparisons.
+# Inject a patch header via -include on only the two failing translation units so
+# that the types satisfy std::equality_comparable without touching managed_components.
+if(TARGET __idf_espressif__esp_matter AND IDF_VERSION_MAJOR LESS 6)
+ set(_CHIP_CLOSURE_DIR
+ "${CMAKE_SOURCE_DIR}/managed_components/espressif__esp_matter/connectedhomeip/connectedhomeip/src/app/clusters/closure-control-server"
+ )
+ set_source_files_properties(
+ "${_CHIP_CLOSURE_DIR}/closure-control-cluster-logic.cpp"
+ "${_CHIP_CLOSURE_DIR}/closure-control-server.cpp"
+ DIRECTORY "${CMAKE_SOURCE_DIR}/managed_components/espressif__esp_matter"
+ PROPERTIES COMPILE_FLAGS
+ "-include${CMAKE_SOURCE_DIR}/include/matter_closure_patch.h"
+ )
+ unset(_CHIP_CLOSURE_DIR)
+endif()
diff --git a/software/zig_main/main/Kconfig.projbuild b/software/zig_main/main/Kconfig.projbuild
new file mode 100644
index 0000000..5f26f13
--- /dev/null
+++ b/software/zig_main/main/Kconfig.projbuild
@@ -0,0 +1,67 @@
+menu "Example Configuration"
+
+ config ESP_WIFI_SSID
+ string "WiFi SSID"
+ default "myssid"
+ help
+ SSID (network name) for the example to connect to.
+
+ config ESP_WIFI_PASSWORD
+ string "WiFi Password"
+ default "mypassword"
+ help
+ WiFi password (WPA or WPA2) for the example to use.
+
+ choice ESP_WIFI_SAE_MODE
+ prompt "WPA3 SAE mode selection"
+ default ESP_WPA3_SAE_PWE_BOTH
+ help
+ Select mode for SAE as Hunt and Peck, H2E or both.
+ config ESP_WPA3_SAE_PWE_HUNT_AND_PECK
+ bool "HUNT AND PECK"
+ config ESP_WPA3_SAE_PWE_HASH_TO_ELEMENT
+ bool "H2E"
+ config ESP_WPA3_SAE_PWE_BOTH
+ bool "BOTH"
+ endchoice
+
+ config ESP_WIFI_PW_ID
+ string "PASSWORD IDENTIFIER"
+ depends on ESP_WPA3_SAE_PWE_HASH_TO_ELEMENT|| ESP_WPA3_SAE_PWE_BOTH
+ default ""
+ help
+ password identifier for SAE H2E
+
+ config ESP_MAXIMUM_RETRY
+ int "Maximum retry"
+ default 5
+ help
+ Set the Maximum retry to avoid station reconnecting to the AP unlimited when the AP is really inexistent.
+
+ choice ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD
+ prompt "WiFi Scan auth mode threshold"
+ default ESP_WIFI_AUTH_WPA2_PSK
+ help
+ The weakest authmode to accept in the scan mode.
+ This value defaults to ESP_WIFI_AUTH_WPA2_PSK incase password is present and ESP_WIFI_AUTH_OPEN is used.
+ Please select ESP_WIFI_AUTH_WEP/ESP_WIFI_AUTH_WPA_PSK incase AP is operating in WEP/WPA mode.
+
+ config ESP_WIFI_AUTH_OPEN
+ bool "OPEN"
+ config ESP_WIFI_AUTH_WEP
+ bool "WEP"
+ config ESP_WIFI_AUTH_WPA_PSK
+ bool "WPA PSK"
+ config ESP_WIFI_AUTH_WPA2_PSK
+ bool "WPA2 PSK"
+ config ESP_WIFI_AUTH_WPA_WPA2_PSK
+ bool "WPA/WPA2 PSK"
+ config ESP_WIFI_AUTH_WPA3_PSK
+ bool "WPA3 PSK"
+ config ESP_WIFI_AUTH_WPA2_WPA3_PSK
+ bool "WPA2/WPA3 PSK"
+ config ESP_WIFI_AUTH_WAPI_PSK
+ bool "WAPI PSK"
+ endchoice
+
+endmenu
diff --git a/software/zig_main/main/app.zig b/software/zig_main/main/app.zig
new file mode 100644
index 0000000..629a67b
--- /dev/null
+++ b/software/zig_main/main/app.zig
@@ -0,0 +1,121 @@
+const std = @import("std");
+const builtin = @import("builtin");
+const idf = @import("esp_idf");
+const ver = idf.ver.Version;
+const mem = std.mem;
+
+comptime {
+ @export(&main, .{ .name = "app_main" });
+}
+
+fn main() callconv(.c) void {
+ // This allocator is safe to use as the backing allocator w/ arena allocator
+
+ // custom allocators (based on old raw_c_allocator)
+ // idf.heap.HeapCapsAllocator
+ // idf.heap.MultiHeapAllocator
+ // idf.heap.VPortAllocator
+
+ var heap = idf.heap.HeapCapsAllocator.init(.{ .@"8bit" = true });
+ var arena = std.heap.ArenaAllocator.init(heap.allocator());
+ defer arena.deinit();
+ const allocator = arena.allocator();
+
+ log.info("Hello, world from Zig!", .{});
+
+ log.info(
+ \\[Zig Info]
+ \\* Version: {s}
+ \\* Compiler Backend: {s}
+ , .{
+ @as([]const u8, builtin.zig_version_string),
+ @tagName(builtin.zig_backend),
+ });
+
+ log.info(
+ \\[ESP-IDF Info]
+ \\* Version: {s}
+ , .{ver.get().toString(allocator)});
+
+ log.info(
+ \\[Memory Info]
+ \\* Total: {d}
+ \\* Free: {d}
+ \\* Minimum: {d}
+ , .{
+ heap.totalSize(),
+ heap.freeSize(),
+ heap.minimumFreeSize(),
+ });
+
+ log.info("Let's have a look at your shiny {s} - {s} system! :)", .{
+ @tagName(builtin.cpu.arch),
+ builtin.cpu.model.name,
+ });
+
+ arraylist(allocator) catch |err| {
+ log.err("Error: {s}", .{@errorName(err)});
+ };
+
+ if (builtin.mode == .Debug)
+ heap.dump();
+
+ // FreeRTOS Tasks — Task.create returns !Handle; on failure panic with a clear message.
+ _ = idf.rtos.Task.create(fooTask, "foo", 1024 * 3, null, 1) catch @panic("Task foo not created");
+ _ = idf.rtos.Task.create(barTask, "bar", 1024 * 3, null, 2) catch @panic("Task bar not created");
+ _ = idf.rtos.Task.create(blinkTask, "blink", 1024 * 2, null, 5) catch @panic("Task blink not created");
+}
+
+fn blinkLED(delay_ms: u32) !void {
+ try idf.gpio.Direction.set(.@"18", .output);
+ while (true) {
+ log.info("LED: ON", .{});
+ try idf.gpio.Level.set(.@"18", 1);
+ idf.rtos.Task.delayMs(delay_ms);
+
+ log.info("LED: OFF", .{});
+ try idf.gpio.Level.set(.@"18", 0);
+ idf.rtos.Task.delayMs(delay_ms);
+ }
+}
+
+fn arraylist(allocator: mem.Allocator) !void {
+ var arr: std.ArrayList(u32) = .empty;
+ defer arr.deinit(allocator);
+
+ try arr.append(allocator, 10);
+ try arr.append(allocator, 20);
+ try arr.append(allocator, 30);
+
+ for (arr.items) |value| {
+ idf.log.ESP_LOG(allocator, idf.log.default_level, "EXAMPLE", "Arr value: {}\n", .{value});
+ }
+}
+
+export fn blinkTask(_: ?*anyopaque) callconv(.c) void {
+ blinkLED(1000) catch |err| @panic(@errorName(err));
+}
+
+export fn fooTask(_: ?*anyopaque) callconv(.c) void {
+ while (true) {
+ log.info("Demo_Task foo printing..", .{});
+ idf.rtos.Task.delayMs(2000);
+ }
+}
+
+export fn barTask(_: ?*anyopaque) callconv(.c) void {
+ while (true) {
+ log.info("Demo_Task bar printing..", .{});
+ idf.rtos.Task.delayMs(1000);
+ }
+}
+
+pub const panic = idf.esp_panic.panic;
+const log = std.log.scoped(idf.log.default_log_scope);
+pub const std_options: std.Options = .{
+ .log_level = switch (builtin.mode) {
+ .Debug => .debug,
+ else => .info,
+ },
+ .logFn = idf.log.espLogFn,
+};
diff --git a/software/zig_main/main/idf_component.yml b/software/zig_main/main/idf_component.yml
new file mode 100644
index 0000000..a7f8507
--- /dev/null
+++ b/software/zig_main/main/idf_component.yml
@@ -0,0 +1,16 @@
+## IDF Component Manager Manifest File
+dependencies:
+ ## Required IDF version
+ idf:
+ version: '>=5.3.0'
+ # # Put list of dependencies here
+ # # For components maintained by Espressif:
+ # component: "~1.0.0"
+ # # For 3rd party components:
+ # username/component: ">=1.0.0,<2.0.0"
+ # username2/component2:
+ # version: "~1.0.0"
+ # # For transient dependencies `public` flag can be set.
+ # # `public` flag doesn't have an effect dependencies of the `main` component.
+ # # All dependencies of `main` are public by default.
+ # public: true
diff --git a/software/zig_main/main/matter_wrappers.cpp b/software/zig_main/main/matter_wrappers.cpp
new file mode 100644
index 0000000..a143870
--- /dev/null
+++ b/software/zig_main/main/matter_wrappers.cpp
@@ -0,0 +1,190 @@
+/*
+ * matter_wrappers.cpp — extern "C" shims that bridge the C wrapper interface
+ * (matter_stubs.h) to the C++ esp_matter API.
+ *
+ * This file is compiled as C++ by the ESP-IDF CMake build, giving it full
+ * access to CHIP SDK headers and the esp_matter C++ namespace.
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "matter_stubs.h"
+
+static const char *TAG = "matter_wrap";
+
+/* ── Type alias shorthands ────────────────────────────────────────────────── */
+
+using namespace esp_matter;
+
+/* ── Callback adapter types ───────────────────────────────────────────────── */
+
+/*
+ * The C callback types in matter_stubs.h are binary-compatible with the C++
+ * types declared in esp_matter_attribute_utils.h and esp_matter_identify.h.
+ * We use reinterpret_cast to pass them across the ABI boundary.
+ */
+
+static attribute::callback_t s_attr_cb = nullptr;
+static identification::callback_t s_ident_cb = nullptr;
+
+/* ── Core ─────────────────────────────────────────────────────────────────── */
+
+extern "C" esp_err_t esp_matter_wrapper_start(
+ esp_matter_attr_callback_t attr_cb,
+ esp_matter_identify_callback_t identify_cb)
+{
+ s_attr_cb = reinterpret_cast(attr_cb);
+ s_ident_cb = reinterpret_cast(identify_cb);
+ return esp_matter::start(nullptr);
+}
+
+extern "C" esp_err_t esp_matter_wrapper_factory_reset(void)
+{
+ return esp_matter::factory_reset();
+}
+
+extern "C" bool esp_matter_wrapper_is_started(void)
+{
+ return esp_matter::is_started();
+}
+
+/* ── Node ─────────────────────────────────────────────────────────────────── */
+
+extern "C" esp_matter_node_t *esp_matter_wrapper_node_create(
+ esp_matter_attr_callback_t attr_cb,
+ esp_matter_identify_callback_t identify_cb,
+ void *priv_data)
+{
+ node::config_t node_cfg;
+ auto *attr_cpp = reinterpret_cast(attr_cb);
+ auto *ident_cpp = reinterpret_cast(identify_cb);
+ return node::create(&node_cfg, attr_cpp, ident_cpp, priv_data);
+}
+
+/* ── Endpoint ─────────────────────────────────────────────────────────────── */
+
+extern "C" esp_matter_endpoint_t *esp_matter_wrapper_endpoint_create(
+ esp_matter_node_t *node,
+ uint8_t flags,
+ void *priv_data)
+{
+ return endpoint::create(node, flags, priv_data);
+}
+
+extern "C" esp_err_t esp_matter_wrapper_endpoint_destroy(
+ esp_matter_node_t *node,
+ esp_matter_endpoint_t *ep)
+{
+ return endpoint::destroy(node, ep);
+}
+
+extern "C" uint16_t esp_matter_wrapper_endpoint_get_id(
+ esp_matter_endpoint_t *ep)
+{
+ return endpoint::get_id(ep);
+}
+
+extern "C" esp_err_t esp_matter_wrapper_endpoint_add_device_type(
+ esp_matter_endpoint_t *ep,
+ uint32_t device_type_id,
+ uint8_t version)
+{
+ return endpoint::add_device_type(ep, device_type_id, version);
+}
+
+extern "C" esp_err_t esp_matter_wrapper_endpoint_enable(
+ esp_matter_endpoint_t *ep)
+{
+ return endpoint::enable(ep);
+}
+
+/* ── Pre-built device-type endpoints ─────────────────────────────────────── */
+
+extern "C" esp_matter_endpoint_t *esp_matter_wrapper_add_on_off_light(
+ esp_matter_node_t *node,
+ uint8_t flags,
+ void *priv_data)
+{
+ endpoint::on_off_light::config_t cfg;
+ return endpoint::on_off_light::create(node, &cfg, flags, priv_data);
+}
+
+extern "C" esp_matter_endpoint_t *esp_matter_wrapper_add_on_off_switch(
+ esp_matter_node_t *node,
+ uint8_t flags,
+ void *priv_data)
+{
+ endpoint::on_off_switch::config_t cfg;
+ return endpoint::on_off_switch::create(node, &cfg, flags, priv_data);
+}
+
+extern "C" esp_matter_endpoint_t *esp_matter_wrapper_add_dimmable_light(
+ esp_matter_node_t *node,
+ uint8_t flags,
+ void *priv_data)
+{
+ endpoint::dimmable_light::config_t cfg;
+ return endpoint::dimmable_light::create(node, &cfg, flags, priv_data);
+}
+
+extern "C" esp_matter_endpoint_t *esp_matter_wrapper_add_color_temperature_light(
+ esp_matter_node_t *node,
+ uint8_t flags,
+ void *priv_data)
+{
+ endpoint::color_temperature_light::config_t cfg;
+ return endpoint::color_temperature_light::create(node, &cfg, flags, priv_data);
+}
+
+/* ── Cluster ──────────────────────────────────────────────────────────────── */
+
+extern "C" esp_matter_cluster_t *esp_matter_wrapper_cluster_create(
+ esp_matter_endpoint_t *ep,
+ uint32_t cluster_id,
+ uint8_t flags)
+{
+ return cluster::create(ep, cluster_id, flags);
+}
+
+/* ── Attribute ────────────────────────────────────────────────────────────── */
+
+extern "C" esp_matter_attribute_t *esp_matter_wrapper_attribute_create(
+ esp_matter_cluster_t *cl,
+ uint32_t attribute_id,
+ uint16_t flags,
+ esp_matter_attr_val_t val)
+{
+ return attribute::create(cl, attribute_id, flags, val);
+}
+
+extern "C" esp_err_t esp_matter_wrapper_attribute_update(
+ uint16_t endpoint_id,
+ uint32_t cluster_id,
+ uint32_t attribute_id,
+ esp_matter_attr_val_t *val)
+{
+ return attribute::update(endpoint_id, cluster_id, attribute_id, val);
+}
+
+extern "C" esp_err_t esp_matter_wrapper_attribute_get_val(
+ esp_matter_attribute_t *attr,
+ esp_matter_attr_val_t *val)
+{
+ return attribute::get_val(attr, val);
+}
+
+extern "C" esp_err_t esp_matter_wrapper_attribute_set_val(
+ esp_matter_attribute_t *attr,
+ esp_matter_attr_val_t *val)
+{
+ return attribute::set_val(attr, val);
+}
diff --git a/software/zig_main/main/placeholder.c b/software/zig_main/main/placeholder.c
new file mode 100644
index 0000000..b01da3b
--- /dev/null
+++ b/software/zig_main/main/placeholder.c
@@ -0,0 +1 @@
+// empty file
\ No newline at end of file
diff --git a/software/zig_main/patches/led_strip/led_color_component_format_t.zig b/software/zig_main/patches/led_strip/led_color_component_format_t.zig
new file mode 100644
index 0000000..40cbe67
--- /dev/null
+++ b/software/zig_main/patches/led_strip/led_color_component_format_t.zig
@@ -0,0 +1,4 @@
+pub const led_color_component_format_t = extern union {
+ format: struct_format_layout_15,
+ format_id: u32,
+};
diff --git a/software/zig_main/patches/led_strip/led_strip_config_t.zig b/software/zig_main/patches/led_strip/led_strip_config_t.zig
new file mode 100644
index 0000000..33a5712
--- /dev/null
+++ b/software/zig_main/patches/led_strip/led_strip_config_t.zig
@@ -0,0 +1,10 @@
+pub const led_strip_config_t = extern struct {
+ strip_gpio_num: c_int = 0,
+ max_leds: u32 = 0,
+ led_model: led_model_t = @import("std").mem.zeroes(led_model_t),
+ color_component_format: led_color_component_format_t = @import("std").mem.zeroes(led_color_component_format_t),
+ flags: led_strip_flags = @import("std").mem.zeroes(led_strip_flags),
+ pub const led_strip_new_rmt_device = __root.led_strip_new_rmt_device;
+ pub const led_strip_new_spi_device = __root.led_strip_new_spi_device;
+ pub const device = __root.led_strip_new_rmt_device;
+};
diff --git a/software/zig_main/patches/led_strip/led_strip_rmt_config_t.zig b/software/zig_main/patches/led_strip/led_strip_rmt_config_t.zig
new file mode 100644
index 0000000..fcd28dd
--- /dev/null
+++ b/software/zig_main/patches/led_strip/led_strip_rmt_config_t.zig
@@ -0,0 +1,10 @@
+pub const led_strip_rmt_config_t = extern struct {
+ clk_src: rmt_clock_source_t = @import("std").mem.zeroes(rmt_clock_source_t),
+ resolution_hz: u32 = 0,
+ mem_block_symbols: usize = 0,
+ flags: led_strip_flags = @import("std").mem.zeroes(led_strip_flags),
+};
+
+const led_strip_flags = extern struct {
+ invert_out: u32,
+};
diff --git a/software/zig_main/patches/led_strip/led_strip_rmt_extra_config_20.zig b/software/zig_main/patches/led_strip/led_strip_rmt_extra_config_20.zig
new file mode 100644
index 0000000..4af5237
--- /dev/null
+++ b/software/zig_main/patches/led_strip/led_strip_rmt_extra_config_20.zig
@@ -0,0 +1,3 @@
+pub const struct_led_strip_rmt_extra_config_20 = extern struct {
+ with_dma: u32 = 0,
+};
diff --git a/software/zig_main/patches/led_strip/led_strip_struct_format_layout_15.zig b/software/zig_main/patches/led_strip/led_strip_struct_format_layout_15.zig
new file mode 100644
index 0000000..efa704a
--- /dev/null
+++ b/software/zig_main/patches/led_strip/led_strip_struct_format_layout_15.zig
@@ -0,0 +1,9 @@
+pub const struct_format_layout_15 = extern struct {
+ r_pos: u32 = 0,
+ g_pos: u32 = 0,
+ b_pos: u32 = 0,
+ w_pos: u32 = 0,
+ reserved: u32 = 0,
+ bytes_per_color: u32 = 0,
+ num_components: u32 = 0,
+};
diff --git a/software/zig_main/patches/porttick_period_ms.zig b/software/zig_main/patches/porttick_period_ms.zig
new file mode 100644
index 0000000..88d297b
--- /dev/null
+++ b/software/zig_main/patches/porttick_period_ms.zig
@@ -0,0 +1 @@
+pub const portTICK_PERIOD_MS: TickType_t = @as(TickType_t, @divExact(@as(c_int, 1000), configTICK_RATE_HZ));
diff --git a/software/zig_main/patches/wifi/wifi_ap_config_t.zig b/software/zig_main/patches/wifi/wifi_ap_config_t.zig
new file mode 100644
index 0000000..b5f87e0
--- /dev/null
+++ b/software/zig_main/patches/wifi/wifi_ap_config_t.zig
@@ -0,0 +1,22 @@
+pub const wifi_ap_config_t = extern struct {
+ ssid: [32]u8 = @import("std").mem.zeroes([32]u8),
+ password: [64]u8 = @import("std").mem.zeroes([64]u8),
+ ssid_len: u8 = 0,
+ channel: u8 = 0,
+ authmode: wifi_auth_mode_t = @import("std").mem.zeroes(wifi_auth_mode_t),
+ ssid_hidden: u8 = 0,
+ max_connection: u8 = 0,
+ beacon_interval: u16 = 0,
+ csa_count: u8 = 0,
+ dtim_period: u8 = 0,
+ pairwise_cipher: wifi_cipher_type_t = @import("std").mem.zeroes(wifi_cipher_type_t),
+ ftm_responder: bool = false,
+ pmf_cfg: wifi_pmf_config_t = @import("std").mem.zeroes(wifi_pmf_config_t),
+ sae_pwe_h2e: wifi_sae_pwe_method_t = @import("std").mem.zeroes(wifi_sae_pwe_method_t),
+ transition_disable: u8 = 0,
+ sae_ext: u8 = 0,
+ wpa3_compatible_mode: u8 = 0,
+ reserved: u8 = 0,
+ bss_max_idle_cfg: wifi_bss_max_idle_config_t = @import("std").mem.zeroes(wifi_bss_max_idle_config_t),
+ gtk_rekey_interval: u16 = 0,
+};
diff --git a/software/zig_main/patches/wifi/wifi_sta_config_t.zig b/software/zig_main/patches/wifi/wifi_sta_config_t.zig
new file mode 100644
index 0000000..4a49522
--- /dev/null
+++ b/software/zig_main/patches/wifi/wifi_sta_config_t.zig
@@ -0,0 +1,40 @@
+pub const wifi_sta_config_t = extern struct {
+ ssid: [32]u8 = @import("std").mem.zeroes([32]u8),
+ password: [64]u8 = @import("std").mem.zeroes([64]u8),
+ scan_method: wifi_scan_method_t = @import("std").mem.zeroes(wifi_scan_method_t),
+ bssid_set: bool = false,
+ bssid: [6]u8 = @import("std").mem.zeroes([6]u8),
+ channel: u8 = 0,
+ listen_interval: u16 = 3,
+ sort_method: wifi_sort_method_t = @import("std").mem.zeroes(wifi_sort_method_t),
+ threshold: wifi_scan_threshold_t = @import("std").mem.zeroes(wifi_scan_threshold_t),
+ pmf_cfg: wifi_pmf_config_t = @import("std").mem.zeroes(wifi_pmf_config_t),
+
+ // Connection Feature
+ // 0 rm_enabled
+ // 1 btm_enabled
+ // 2 mbo_enabled
+ // 3 ft_enabled
+ // 4 owe_enabled
+ // 5 transition_disable
+ // 6-31 reserved
+ connection_features: u32 = 0, // Stores BTM, RM, MBO, FT, OWE, etc.
+
+ sae_pwe_h2e: wifi_sae_pwe_method_t = @import("std").mem.zeroes(wifi_sae_pwe_method_t),
+ sae_pk_mode: wifi_sae_pk_mode_t = @import("std").mem.zeroes(wifi_sae_pk_mode_t),
+ failure_retry_cnt: u8 = 0,
+
+ // HE Capabilities
+ // 0 he_dcm_set
+ // 1-2 he_dcm_max_constellation_tx (2-bit)
+ // 3-4 he_dcm_max_constellation_rx (2-bit)
+ // 5 he_mcs9_enabled
+ // 6 he_su_beamformee_disabled
+ // 7 he_trig_su_bmforming_feedback_disabled
+ // 8 he_trig_mu_bmforming_partial_feedback_disabled
+ // 9 he_trig_cqi_feedback_disabled
+ // 10-31 he_reserved
+ he_capabilities: u32 = 0, // Stores HE-related flags (DCM, MCS9, beamforming, CQI, etc.)
+
+ sae_h2e_identifier: [32]u8 = @import("std").mem.zeroes([32]u8),
+};
diff --git a/software/zig_main/patches/xport_can_yield.zig b/software/zig_main/patches/xport_can_yield.zig
new file mode 100644
index 0000000..458ed33
--- /dev/null
+++ b/software/zig_main/patches/xport_can_yield.zig
@@ -0,0 +1,8 @@
+pub fn xPortCanYield() callconv(.c) bool {
+ var threshold: u32 = blk: {
+ break :blk @as([*c]volatile u32, @ptrFromInt(@as(c_int, 545259520) + @as(c_int, 8))).*;
+ };
+ _ = &threshold;
+ threshold = threshold >> @intCast(@as(c_int, 24) + (@as(c_int, 8) - @as(c_int, 3)));
+ return threshold == @as(u32, @bitCast(@as(c_int, 0)));
+}
\ No newline at end of file
diff --git a/software/zig_main/sdkconfig.defaults b/software/zig_main/sdkconfig.defaults
new file mode 100644
index 0000000..6f7c3a0
--- /dev/null
+++ b/software/zig_main/sdkconfig.defaults
@@ -0,0 +1,28 @@
+# Required by esp_matter / CHIP SDK (HKDF for crypto)
+#CONFIG_MBEDTLS_HKDF_C=y
+
+# Matter example binary (~2.2 MB) requires 4 MB flash and a custom partition table.
+# CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
+# CONFIG_PARTITION_TABLE_CUSTOM=y
+# CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions_matter.csv"
+
+CONFIG_COMPILER_OPTIMIZATION_NONE=n
+CONFIG_COMPILER_OPTIMIZATION_DEFAULT=y
+CONFIG_COMPILER_OPTIMIZATION_PERF=n
+CONFIG_COMPILER_OPTIMIZATION_SIZE=n
+CONFIG_ESP_WIFI_ENABLED=y
+# CONFIG_SPI_FLASH_DANGEROUS_WRITE_FAILS=y
+# CONFIG_COMPILER_STACK_CHECK_MODE_STRONG=y
+# CONFIG_COMPILER_STACK_CHECK=y
+
+
+# This allows to use 1 ms granuality for thread sleeps (10 ms by default).
+# CONFIG_FREERTOS_HZ=1000
+
+# not working using `HeapCapsAllocator`,
+# but std `raw_c_allocator` or `VPortAllocator` works
+# CONFIG_HEAP_TASK_TRACKING=y
+
+# CONFIG_BLINK_LED_STRIP=y
+# CONFIG_BLINK_LED_STRIP_BACKEND_SPI=y
+# CONFIG_BLINK_GPIO=48
\ No newline at end of file
diff --git a/software/zig_main/sdkconfig.defaults.esp32 b/software/zig_main/sdkconfig.defaults.esp32
new file mode 100644
index 0000000..70b4b1d
--- /dev/null
+++ b/software/zig_main/sdkconfig.defaults.esp32
@@ -0,0 +1,7 @@
+CONFIG_ESP_TIMER_PROFILING=y
+CONFIG_COMPILER_OPTIMIZATION_PERF=y
+CONFIG_ESP_WIFI_ENABLED=y
+CONFIG_BT_ENABLED=y
+CONFIG_ESP_COEX_ENABLED=y
+CONFIG_COMPILER_CXX_EXCEPTIONS=y
+CONFIG_COMPILER_CXX_RTTI=y