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 @@ +[![Ask DeepWiki](https://deepwiki.com/badge.svg)](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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TargetArchitectureFeaturesZig Build Configuration
ESP32Xtensa LX6Dual-core, WiFi, BT Classic, BLE-Dtarget=xtensa-freestanding-none -Dcpu=esp32
ESP32-S2Xtensa LX7Single-core, WiFi, USB OTG-Dtarget=xtensa-freestanding-none -Dcpu=esp32s2
ESP32-S3Xtensa LX7Dual-core, WiFi, BLE 5.0, USB OTG, AI-Dtarget=xtensa-freestanding-none -Dcpu=esp32s3
ESP32-C2RISC-VSingle-core, WiFi, BLE 5.0, Low-cost-Dtarget=riscv32-freestanding-none -Dcpu=generic_rv32+m+c+zicsr+zifencei
ESP32-C3RISC-VSingle-core, WiFi, BLE 5.0, Low-power
ESP32-C5RISC-VSingle-core, WiFi 6, BLE 5.0-Dtarget=riscv32-freestanding-none -Dcpu=generic_rv32+m+a+c+zicsr+zifencei
ESP32-C6RISC-VSingle-core, WiFi 6, BLE 5.0, Zigbee, Thread
ESP32-C61RISC-VSingle-core, WiFi 6, BLE 5.0, Low-cost
ESP32-H2RISC-VBLE 5.0, Zigbee 3.0, Thread, No WiFi
ESP32-H21RISC-VBLE 5.0, Zigbee 3.0, Thread, No WiFi
ESP32-H4RISC-VBLE 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-P4RISC-VDual-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: + + ![Build Scheme](build-scheme.png) + +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