Compare commits

..

2 Commits

115 changed files with 15738 additions and 0 deletions

View File

@@ -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"]

View File

@@ -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"
]
}

13
software/zig_main/.gitignore vendored Normal file
View File

@@ -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

View File

@@ -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)

View File

@@ -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.

View File

@@ -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.

150
software/zig_main/README.md Normal file
View File

@@ -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
<table>
<thead>
<tr>
<th>Target</th>
<th>Architecture</th>
<th>Features</th>
<th>Zig Build Configuration</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>ESP32</strong></td>
<td>Xtensa LX6</td>
<td>Dual-core, WiFi, BT Classic, BLE</td>
<td><code>-Dtarget=xtensa-freestanding-none -Dcpu=esp32</code></td>
</tr>
<tr>
<td><strong>ESP32-S2</strong></td>
<td>Xtensa LX7</td>
<td>Single-core, WiFi, USB OTG</td>
<td><code>-Dtarget=xtensa-freestanding-none -Dcpu=esp32s2</code></td>
</tr>
<tr>
<td><strong>ESP32-S3</strong></td>
<td>Xtensa LX7</td>
<td>Dual-core, WiFi, BLE 5.0, USB OTG, AI</td>
<td><code>-Dtarget=xtensa-freestanding-none -Dcpu=esp32s3</code></td>
</tr>
<tr>
<td><strong>ESP32-C2</strong></td>
<td>RISC-V</td>
<td>Single-core, WiFi, BLE 5.0, Low-cost</td>
<td rowspan="2"><code>-Dtarget=riscv32-freestanding-none -Dcpu=generic_rv32+m+c+zicsr+zifencei</code></td>
</tr>
<tr>
<td><strong>ESP32-C3</strong></td>
<td>RISC-V</td>
<td>Single-core, WiFi, BLE 5.0, Low-power</td>
</tr>
<tr>
<td><strong>ESP32-C5</strong></td>
<td>RISC-V</td>
<td>Single-core, WiFi 6, BLE 5.0</td>
<td rowspan="5"><code>-Dtarget=riscv32-freestanding-none -Dcpu=generic_rv32+m+a+c+zicsr+zifencei</code></td>
</tr>
<tr>
<td><strong>ESP32-C6</strong></td>
<td>RISC-V</td>
<td>Single-core, WiFi 6, BLE 5.0, Zigbee, Thread</td>
</tr>
<tr>
<td><strong>ESP32-C61</strong></td>
<td>RISC-V</td>
<td>Single-core, WiFi 6, BLE 5.0, Low-cost</td>
</tr>
<tr>
<td><strong>ESP32-H2</strong></td>
<td>RISC-V</td>
<td>BLE 5.0, Zigbee 3.0, Thread, No WiFi</td>
</tr>
<tr>
<td><strong>ESP32-H21</strong></td>
<td>RISC-V</td>
<td>BLE 5.0, Zigbee 3.0, Thread, No WiFi</td>
</tr>
<tr>
<td><strong>ESP32-H4</strong></td>
<td>RISC-V</td>
<td>BLE 5.2, Zigbee, Thread, FPU, No WiFi</td>
<td><code>-Dtarget=riscv32-freestanding-eabihf -Dcpu=esp32h4</code> (Espressif fork) / <code>generic_rv32+m+a+c+f+zicsr+zifencei</code> (upstream)</td>
</tr>
<tr>
<td><strong>ESP32-P4</strong></td>
<td>RISC-V</td>
<td>Dual-core, AI, DSP, FPU, No WiFi/BT</td>
<td><code>-Dtarget=riscv32-freestanding-eabihf -Dcpu=esp32p4</code> (Espressif fork) / <code>generic_rv32+m+a+c+f+zicsr+zifencei</code> (upstream)</td>
</tr>
</tbody>
</table>
> [!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)

258
software/zig_main/build.zig Normal file
View File

@@ -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;
}

View File

@@ -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 <url>` 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",
},
}

View File

@@ -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()

View File

@@ -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()

View File

@@ -0,0 +1,227 @@
# ─── 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_unmanaged_component COMPONENT_NAME VENDOR PACKAGE DEFINE_NAME)
set(COMP_PATHS "")
set(COMP_BASE "${CMAKE_SOURCE_DIR}/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()
# 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_unmanaged_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")

View File

@@ -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}")

View File

@@ -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
)

View File

@@ -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")

View File

@@ -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()

View File

@@ -0,0 +1 @@
28621486f77229aaf81c71f5e15d6fbf36c2949cf11094e07090593e659e7639

View File

@@ -0,0 +1,67 @@
## 3.0.3
- Support WS2816 with 16-bit color
## 3.0.1
- Support WS2811 bit timing
## 3.0.0
- Discontinued support for ESP-IDF v4.x
- Added configuration for user-defined color component format
## 2.5.5
- Simplified the led_strip component dependency, the time of full build with ESP-IDF v5.3 can now be shorter.
## 2.5.4
- Inserted extra delay when initialize the SPI LED device, to ensure all LEDs are in the reset state correctly
## 2.5.3
- Extend reset time (280us) to support WS2812B-V5
## 2.5.2
- Added API reference doc (api.md)
## 2.5.0
- Enabled support for IDF4.4 and above
- with RMT backend only
- Added API `led_strip_set_pixel_hsv`
## 2.4.0
- Support configurable SPI mode to control leds
- recommend enabling DMA when using SPI mode
## 2.3.0
- Support configurable RMT channel size by setting `mem_block_symbols`
## 2.2.0
- Support for 4 components RGBW leds (SK6812):
- in led_strip_config_t new fields
led_pixel_format, controlling byte format (LED_PIXEL_FORMAT_GRB, LED_PIXEL_FORMAT_GRBW)
led_model, used to configure bit timing (LED_MODEL_WS2812, LED_MODEL_SK6812)
- new API led_strip_set_pixel_rgbw
- new interface type set_pixel_rgbw
## 2.1.0
- Support DMA feature, which offloads the CPU by a lot when it comes to drive a bunch of LEDs
- Support various RMT clock sources
- Acquire and release the power management lock before and after each refresh
- New driver flag: `invert_out` which can invert the led control signal by hardware
## 2.0.0
- Reimplemented the driver using the new RMT driver (`driver/rmt_tx.h`)
## 1.0.0
- Initial driver version, based on the legacy RMT driver (`driver/rmt.h`)

View File

@@ -0,0 +1 @@
{"version":"1.0","algorithm":"sha256","created_at":"2026-02-04T21:14:01.842522+00:00","files":[{"path":"CHANGELOG.md","size":1667,"hash":"b445b45b8ce496848e247b569090efc3ea1a8680b8e69a7309dbdeeb97eb9d51"},{"path":"CMakeLists.txt","size":917,"hash":"038cbe6ba04c27101892e51d9d6a0627d64130f666f5d61b1f097462f982955b"},{"path":"LICENSE","size":11358,"hash":"cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30"},{"path":"README.md","size":2072,"hash":"12e83a316c51d85c6c1ee2e5eecfb46691f6be42ce685eece2ce063a9c949001"},{"path":"idf_component.yml","size":492,"hash":"f4f09e02e4e53be5b49defeab1b6da6cdb9acfe377df892dfef5a0c370a7f88e"},{"path":"docs/Doxyfile","size":738,"hash":"7f64bdef18c3ed6f2e3d6397066e2fad4b5e31c2052744ca9631f34f69fdff79"},{"path":"docs/book.toml","size":297,"hash":"5d66624796168a4b8d0d87631c438c392b973206f4f7c53d9897a0b7ca7ce5b4"},{"path":"include/led_strip.h","size":4313,"hash":"36344aae936d7e0e764954188d77160c357d3ae1990ede1c7451e2bd1c0e85eb"},{"path":"include/led_strip_rmt.h","size":1630,"hash":"c63a152ab4aa187080b8d29cdb49365a9ea03b6ca7c41c66920e5c58ac0d0c52"},{"path":"include/led_strip_spi.h","size":1599,"hash":"cf0dcd5c748a7f11bf55077325b68a64ea826e55fc8e7b38aaad6fc0eb5345e5"},{"path":"include/led_strip_types.h","size":4276,"hash":"168b30b21ecbc5789903204753958c781304324e6a44d40a5fcb8676f12cb29c"},{"path":"interface/led_strip_interface.h","size":2934,"hash":"5b7d0c326d0d0d9748830d4aec46d765400e1446055d4a1197c83111e937d74c"},{"path":"src/led_strip_api.c","size":3841,"hash":"61968f0dde1cf1720f6b00bf3cb5d2c2b990aac6de00c90d9465746afc3e03d5"},{"path":"src/led_strip_rmt_dev.c","size":8925,"hash":"0e69d4743e65956ef2494d1347b81b99f283e0a79d6d341733fd1d58c1c6e97e"},{"path":"src/led_strip_rmt_encoder.c","size":8080,"hash":"4ab03dfbba4a90f392af3234665f76ccd21c004c61f5575e1e428354512d1748"},{"path":"src/led_strip_rmt_encoder.h","size":977,"hash":"690381c35ace2703a5c7156f6547a8524f4cbfe5bef40be619e2097960120a40"},{"path":"src/led_strip_spi_dev.c","size":11600,"hash":"79a4e4c42185afdbdf2163bd945a6339bf09965a85fb20525b3d7b106d695ab2"},{"path":"examples/led_strip_rmt_ws2812/CMakeLists.txt","size":140,"hash":"526f16308e57fafd25d0fd79d872152a9214c28967f78aa9c94ebe9e73040940"},{"path":"examples/led_strip_rmt_ws2812/README.md","size":1200,"hash":"a5f39b31c5f7cbf548ee31b61ab22e430a6c823404c0ddb113703512bcb3ad3c"},{"path":"examples/led_strip_spi_ws2812/CMakeLists.txt","size":140,"hash":"61255dc48f295f09e84abd7895ae5767763ac3decb4b4584e38681ea877427e8"},{"path":"examples/led_strip_spi_ws2812/README.md","size":1201,"hash":"2c02a29197cd1f2d4af4c4c9cd44677e303b0e168a1773eef9fc3fdb39377d27"},{"path":"examples/led_strip_spi_ws2812/main/CMakeLists.txt","size":99,"hash":"34e7f83d26bca924c629ea2012e6f200b415d486907863fe936d94872ff739eb"},{"path":"examples/led_strip_spi_ws2812/main/idf_component.yml","size":68,"hash":"a0c6b9b94056e8459a9acb8d7828540b36b4f7fe9ced9011ea97ba23b2fc96d4"},{"path":"examples/led_strip_spi_ws2812/main/led_strip_spi_ws2812_main.c","size":2808,"hash":"ef7ee688e7e1f451879a7b238b2a7133ccf880adb6d0e551328150acf86f656d"},{"path":"examples/led_strip_rmt_ws2812/main/CMakeLists.txt","size":99,"hash":"8960b68811805d3aa40e1a7f44ddf7400c0d0731829b6d2b3b1584d8dcd3b392"},{"path":"examples/led_strip_rmt_ws2812/main/idf_component.yml","size":53,"hash":"d52c7e09ecb7a6e4946fb6e697d6d7127918d4334858973f8c7434b1d2f120f0"},{"path":"examples/led_strip_rmt_ws2812/main/led_strip_rmt_ws2812_main.c","size":3253,"hash":"8835bd39d38dac8fb27c5e1298cb12ddf4c6ed430b4a2a1e061334f56d77f470"},{"path":"docs/src/SUMMARY.md","size":110,"hash":"b3a38ed25d2e5187928554682b1bd7154444e1bc1ce8183e6a3d328e720f7b61"},{"path":"docs/src/api.md","size":128,"hash":"d06c809c85c02f6ae22bd090331e1150dad89bd57034f056dbf3df0449cdc22b"},{"path":"docs/src/index.md","size":2967,"hash":"db944dabd24b1faa4d61a8f8db4f734334cefc2d1efb6d023a51fb94d1c3311f"}]}

View File

@@ -0,0 +1,26 @@
include($ENV{IDF_PATH}/tools/cmake/version.cmake)
set(srcs "src/led_strip_api.c")
set(public_requires)
if(CONFIG_SOC_RMT_SUPPORTED)
list(APPEND srcs "src/led_strip_rmt_dev.c" "src/led_strip_rmt_encoder.c")
endif()
# the SPI backend driver relies on some feature that was available in IDF 5.1
if("${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}" VERSION_GREATER_EQUAL "5.1")
if(CONFIG_SOC_GPSPI_SUPPORTED)
list(APPEND srcs "src/led_strip_spi_dev.c")
endif()
endif()
# Starting from esp-idf v5.3, the RMT and SPI drivers are moved to separate components
if("${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}" VERSION_GREATER_EQUAL "5.3")
list(APPEND public_requires "esp_driver_rmt" "esp_driver_spi")
else()
list(APPEND public_requires "driver")
endif()
idf_component_register(SRCS ${srcs}
INCLUDE_DIRS "include" "interface"
REQUIRES ${public_requires})

View File

@@ -0,0 +1,202 @@
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.

View File

@@ -0,0 +1,23 @@
# LED Strip Driver
[![Component Registry](https://components.espressif.com/components/espressif/led_strip/badge.svg)](https://components.espressif.com/components/espressif/led_strip)
This driver is designed for addressable LEDs like [WS2812](http://www.world-semi.com/Certifications/WS2812B.html), where each LED is controlled by a single data line.
## Supported Backend Peripherals
The LED strip driver supports two different backend peripherals to generate the timing signals required by addressable LEDs:
### The [RMT](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/rmt.html) Peripheral
This is the most economical way to drive the LEDs because it only consumes one RMT channel, leaving other channels free to use. However, the memory usage increases dramatically with the number of LEDs. If the RMT hardware can't be assist by DMA, the driver will going into interrupt very frequently, thus result in a high CPU usage. What's worse, if the RMT interrupt is delayed or not serviced in time (e.g. if Wi-Fi interrupt happens on the same CPU core), the RMT transaction will be corrupted and the LEDs will display incorrect colors. If you want to use RMT to drive a large number of LEDs, you'd better to enable the DMA feature if possible [^1].
### The [SPI](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/spi_master.html) Peripheral
SPI peripheral can also be used to generate the timing required by the LED strip, in a so-called "Clock-less" mode. However this backend is not as economical as the RMT one, because it will take up the whole **bus**. You **CANNOT** connect other devices to the same SPI bus if it's been used by the led_strip, because the led_strip doesn't have the concept of "Chip Select".
## Documentation
For detailed information about the LED Strip component, including API reference and user guides, please visit:
- **Programming Guide & API Reference**: [LED Strip Documentation](https://espressif.github.io/idf-extra-components/latest/led_strip/index.html)

View File

@@ -0,0 +1,30 @@
# Set this to the header file you want
INPUT = \
../include/ \
../interface/
# The output directory for the generated XML documentation
OUTPUT_DIRECTORY = doxygen_output
# Warning-related settings, it's recommended to keep them enabled
WARN_IF_UNDOC_ENUM_VAL = YES
WARN_AS_ERROR = YES
# Other common settings
FULL_PATH_NAMES = YES
STRIP_FROM_PATH = ../
STRIP_FROM_INC_PATH = ../
ENABLE_PREPROCESSING = YES
MACRO_EXPANSION = YES
OPTIMIZE_OUTPUT_FOR_C = YES
EXPAND_ONLY_PREDEF = YES
EXTRACT_ALL = YES
PREDEFINED = $(ENV_DOXYGEN_DEFINES)
HAVE_DOT = NO
GENERATE_XML = YES
XML_OUTPUT = xml
GENERATE_HTML = NO
HAVE_DOT = NO
GENERATE_LATEX = NO
QUIET = YES
MARKDOWN_SUPPORT = YES

View File

@@ -0,0 +1,8 @@
[book]
title = "LED Strip Documentation"
language = "en"
[output.html]
default-theme = "light"
git-repository-url = "https://github.com/espressif/idf-extra-components/tree/master/led_strip"
edit-url-template = "https://github.com/espressif/idf-extra-components/edit/master/led_strip/docs/{path}"

View File

@@ -0,0 +1,13 @@
# Summary
---
# Programming Guide
- [LED Strip](index.md)
---
# API Reference
- [API Reference](api.md)

View File

@@ -0,0 +1,9 @@
# API Reference
<div class="warning">
This file is automatically generated by esp-doxybook.
DO NOT edit it manually.
</div>

View File

@@ -0,0 +1,75 @@
# LED Strip Programming Guide
## Allocate LED Strip Object with RMT Backend
```c
#define BLINK_GPIO 0
/// LED strip common configuration
led_strip_config_t strip_config = {
.strip_gpio_num = BLINK_GPIO, // The GPIO that connected to the LED strip's data line
.max_leds = 1, // The number of LEDs in the strip,
.led_model = LED_MODEL_WS2812, // LED strip model, it determines the bit timing
.color_component_format = LED_STRIP_COLOR_COMPONENT_FMT_GRB, // The color component format is G-R-B
.flags = {
.invert_out = false, // don't invert the output signal
}
};
/// RMT backend specific configuration
led_strip_rmt_config_t rmt_config = {
.clk_src = RMT_CLK_SRC_DEFAULT, // different clock source can lead to different power consumption
.resolution_hz = 10 * 1000 * 1000, // RMT counter clock frequency: 10MHz
.mem_block_symbols = 64, // the memory size of each RMT channel, in words (4 bytes)
.flags = {
.with_dma = false, // DMA feature is available on chips like ESP32-S3/P4
}
};
/// Create the LED strip object
led_strip_handle_t led_strip = NULL;
ESP_ERROR_CHECK(led_strip_new_rmt_device(&strip_config, &rmt_config, &led_strip));
```
---
You can create multiple LED strip objects with different GPIOs and pixel numbers. The backend driver will automatically allocate sufficient RMT channels for you wherever possible. If the RMT channels are not enough, the [led_strip_new_rmt_device](api.md#function-led_strip_new_rmt_device) will return an error.
## Allocate LED Strip Object with SPI Backend
```c
#define BLINK_GPIO 0
/// LED strip common configuration
led_strip_config_t strip_config = {
.strip_gpio_num = BLINK_GPIO, // The GPIO that connected to the LED strip's data line
.max_leds = 1, // The number of LEDs in the strip,
.led_model = LED_MODEL_WS2812, // LED strip model, it determines the bit timing
.color_component_format = LED_STRIP_COLOR_COMPONENT_FMT_GRB, // The color component format is G-R-B
.flags = {
.invert_out = false, // don't invert the output signal
}
};
/// SPI backend specific configuration
led_strip_spi_config_t spi_config = {
.clk_src = SPI_CLK_SRC_DEFAULT, // different clock source can lead to different power consumption
.spi_bus = SPI2_HOST, // SPI bus ID
.flags = {
.with_dma = true, // Using DMA can improve performance and help drive more LEDs
}
};
/// Create the LED strip object
led_strip_handle_t led_strip = NULL;
ESP_ERROR_CHECK(led_strip_new_spi_device(&strip_config, &spi_config, &led_strip));
```
---
The number of LED strip objects can be created depends on how many free SPI controllers are free to use in your project.
## FAQ
- How to set the brightness of the LED strip?
- You can tune the brightness by scaling the value of each R-G-B element with a **same** factor. But pay attention to the overflow of the value.

View File

@@ -0,0 +1,6 @@
cmake_minimum_required(VERSION 3.16)
set(COMPONENTS main)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(led_strip_rmt_ws2812)

View File

@@ -0,0 +1,31 @@
# LED Strip Example (RMT backend + WS2812)
This example demonstrates how to blink the WS2812 LED using the [led_strip](https://components.espressif.com/component/espressif/led_strip) component.
## How to Use Example
### Hardware Required
* A development board with Espressif SoC
* A USB cable for Power supply and programming
* WS2812 LED strip
### Configure the Example
Before project configuration and build, be sure to set the correct chip target using `idf.py set-target <chip_name>`. Then assign the proper GPIO in the [source file](main/led_strip_rmt_ws2812_main.c). If your led strip has multiple LEDs, don't forget update the number.
### Build and Flash
Run `idf.py -p PORT build flash monitor` to build, flash and monitor the project.
(To exit the serial monitor, type ``Ctrl-]``.)
See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects.
## Example Output
```text
I (299) gpio: GPIO[8]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
I (309) example: Created LED strip object with RMT backend
I (309) example: Start blinking LED strip
```

View File

@@ -0,0 +1,2 @@
idf_component_register(SRCS "led_strip_rmt_ws2812_main.c"
INCLUDE_DIRS ".")

View File

@@ -0,0 +1,3 @@
dependencies:
espressif/led_strip:
version: ^3

View File

@@ -0,0 +1,89 @@
/*
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "led_strip.h"
#include "esp_log.h"
#include "esp_err.h"
// Set to 1 to use DMA for driving the LED strip, 0 otherwise
// Please note the RMT DMA feature is only available on chips e.g. ESP32-S3/P4
#define LED_STRIP_USE_DMA 0
#if LED_STRIP_USE_DMA
// Numbers of the LED in the strip
#define LED_STRIP_LED_COUNT 256
#define LED_STRIP_MEMORY_BLOCK_WORDS 1024 // this determines the DMA block size
#else
// Numbers of the LED in the strip
#define LED_STRIP_LED_COUNT 24
#define LED_STRIP_MEMORY_BLOCK_WORDS 0 // let the driver choose a proper memory block size automatically
#endif // LED_STRIP_USE_DMA
// GPIO assignment
#define LED_STRIP_GPIO_PIN 2
// 10MHz resolution, 1 tick = 0.1us (led strip needs a high resolution)
#define LED_STRIP_RMT_RES_HZ (10 * 1000 * 1000)
static const char *TAG = "example";
led_strip_handle_t configure_led(void)
{
// LED strip general initialization, according to your led board design
led_strip_config_t strip_config = {
.strip_gpio_num = LED_STRIP_GPIO_PIN, // The GPIO that connected to the LED strip's data line
.max_leds = LED_STRIP_LED_COUNT, // The number of LEDs in the strip,
.led_model = LED_MODEL_WS2812, // LED strip model
.color_component_format = LED_STRIP_COLOR_COMPONENT_FMT_GRB, // The color order of the strip: GRB
.flags = {
.invert_out = false, // don't invert the output signal
}
};
// LED strip backend configuration: RMT
led_strip_rmt_config_t rmt_config = {
.clk_src = RMT_CLK_SRC_DEFAULT, // different clock source can lead to different power consumption
.resolution_hz = LED_STRIP_RMT_RES_HZ, // RMT counter clock frequency
.mem_block_symbols = LED_STRIP_MEMORY_BLOCK_WORDS, // the memory block size used by the RMT channel
.flags = {
.with_dma = LED_STRIP_USE_DMA, // Using DMA can improve performance when driving more LEDs
}
};
// LED Strip object handle
led_strip_handle_t led_strip;
ESP_ERROR_CHECK(led_strip_new_rmt_device(&strip_config, &rmt_config, &led_strip));
ESP_LOGI(TAG, "Created LED strip object with RMT backend");
return led_strip;
}
void app_main(void)
{
led_strip_handle_t led_strip = configure_led();
bool led_on_off = false;
ESP_LOGI(TAG, "Start blinking LED strip");
while (1) {
if (led_on_off) {
/* Set the LED pixel using RGB from 0 (0%) to 255 (100%) for each color */
for (int i = 0; i < LED_STRIP_LED_COUNT; i++) {
ESP_ERROR_CHECK(led_strip_set_pixel(led_strip, i, 5, 5, 5));
}
/* Refresh the strip to send data */
ESP_ERROR_CHECK(led_strip_refresh(led_strip));
ESP_LOGI(TAG, "LED ON!");
} else {
/* Set all LED off to clear all pixels */
ESP_ERROR_CHECK(led_strip_clear(led_strip));
ESP_LOGI(TAG, "LED OFF!");
}
led_on_off = !led_on_off;
vTaskDelay(pdMS_TO_TICKS(500));
}
}

View File

@@ -0,0 +1,6 @@
cmake_minimum_required(VERSION 3.16)
set(COMPONENTS main)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(led_strip_spi_ws2812)

View File

@@ -0,0 +1,31 @@
# LED Strip Example (SPI backend + WS2812)
This example demonstrates how to blink the WS2812 LED using the [led_strip](https://components.espressif.com/component/espressif/led_strip) component.
## How to Use Example
### Hardware Required
* A development board with Espressif SoC
* A USB cable for Power supply and programming
* WS2812 LED strip
### Configure the Example
Before project configuration and build, be sure to set the correct chip target using `idf.py set-target <chip_name>`. Then assign the proper GPIO in the [source file](main/led_strip_spi_ws2812_main.c). If your led strip has multiple LEDs, don't forget update the number.
### Build and Flash
Run `idf.py -p PORT build flash monitor` to build, flash and monitor the project.
(To exit the serial monitor, type ``Ctrl-]``.)
See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects.
## Example Output
```text
I (299) gpio: GPIO[14]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
I (309) example: Created LED strip object with SPI backend
I (309) example: Start blinking LED strip
```

View File

@@ -0,0 +1,2 @@
idf_component_register(SRCS "led_strip_spi_ws2812_main.c"
INCLUDE_DIRS ".")

View File

@@ -0,0 +1,4 @@
dependencies:
espressif/led_strip:
version: ^3
idf: '>=5.1'

View File

@@ -0,0 +1,81 @@
/*
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "led_strip.h"
#include "esp_log.h"
#include "esp_err.h"
// GPIO assignment
#define LED_STRIP_GPIO_PIN 2
// Numbers of the LED in the strip
#define LED_STRIP_LED_COUNT 24
static const char *TAG = "example";
led_strip_handle_t configure_led(void)
{
// LED strip general initialization, according to your led board design
led_strip_config_t strip_config = {
.strip_gpio_num = LED_STRIP_GPIO_PIN, // The GPIO that connected to the LED strip's data line
.max_leds = LED_STRIP_LED_COUNT, // The number of LEDs in the strip,
.led_model = LED_MODEL_WS2812, // LED strip model
// set the color order of the strip: GRB
.color_component_format = {
.format = {
.r_pos = 1, // red is the second byte in the color data
.g_pos = 0, // green is the first byte in the color data
.b_pos = 2, // blue is the third byte in the color data
.num_components = 3, // total 3 color components
},
},
.flags = {
.invert_out = false, // don't invert the output signal
}
};
// LED strip backend configuration: SPI
led_strip_spi_config_t spi_config = {
.clk_src = SPI_CLK_SRC_DEFAULT, // different clock source can lead to different power consumption
.spi_bus = SPI2_HOST, // SPI bus ID
.flags = {
.with_dma = true, // Using DMA can improve performance and help drive more LEDs
}
};
// LED Strip object handle
led_strip_handle_t led_strip;
ESP_ERROR_CHECK(led_strip_new_spi_device(&strip_config, &spi_config, &led_strip));
ESP_LOGI(TAG, "Created LED strip object with SPI backend");
return led_strip;
}
void app_main(void)
{
led_strip_handle_t led_strip = configure_led();
bool led_on_off = false;
ESP_LOGI(TAG, "Start blinking LED strip");
while (1) {
if (led_on_off) {
/* Set the LED pixel using RGB from 0 (0%) to 255 (100%) for each color */
for (int i = 0; i < LED_STRIP_LED_COUNT; i++) {
ESP_ERROR_CHECK(led_strip_set_pixel(led_strip, i, 5, 5, 5));
}
/* Refresh the strip to send data */
ESP_ERROR_CHECK(led_strip_refresh(led_strip));
ESP_LOGI(TAG, "LED ON!");
} else {
/* Set all LED off to clear all pixels */
ESP_ERROR_CHECK(led_strip_clear(led_strip));
ESP_LOGI(TAG, "LED OFF!");
}
led_on_off = !led_on_off;
vTaskDelay(pdMS_TO_TICKS(500));
}
}

View File

@@ -0,0 +1,11 @@
dependencies:
idf: '>=5.0'
description: Driver for Addressable LED Strip (WS2812, etc)
documentation: https://espressif.github.io/idf-extra-components/latest/led_strip/index.html
issues: https://github.com/espressif/idf-extra-components/issues
repository: git://github.com/espressif/idf-extra-components.git
repository_info:
commit_sha: 7cd447361ca2f0a1c01aa3089e3031f6171b6c7e
path: led_strip
url: https://github.com/espressif/idf-extra-components/tree/master/led_strip
version: 3.0.3

View File

@@ -0,0 +1,123 @@
/*
* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include "esp_err.h"
#include "led_strip_rmt.h"
#include "led_strip_spi.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Set RGB for a specific pixel
*
* @param strip: LED strip
* @param index: index of pixel to set
* @param red: red part of color
* @param green: green part of color
* @param blue: blue part of color
*
* @return
* - ESP_OK: Set RGB for a specific pixel successfully
* - ESP_ERR_INVALID_ARG: Set RGB for a specific pixel failed because of invalid parameters
* - ESP_FAIL: Set RGB for a specific pixel failed because other error occurred
*/
esp_err_t led_strip_set_pixel(led_strip_handle_t strip, uint32_t index, uint32_t red, uint32_t green, uint32_t blue);
/**
* @brief Set RGBW for a specific pixel
*
* @note Only call this function if your led strip does have the white component (e.g. SK6812-RGBW)
* @note Also see `led_strip_set_pixel` if you only want to specify the RGB part of the color and bypass the white component
*
* @param strip: LED strip
* @param index: index of pixel to set
* @param red: red part of color
* @param green: green part of color
* @param blue: blue part of color
* @param white: separate white component
*
* @return
* - ESP_OK: Set RGBW color for a specific pixel successfully
* - ESP_ERR_INVALID_ARG: Set RGBW color for a specific pixel failed because of an invalid argument
* - ESP_FAIL: Set RGBW color for a specific pixel failed because other error occurred
*/
esp_err_t led_strip_set_pixel_rgbw(led_strip_handle_t strip, uint32_t index, uint32_t red, uint32_t green, uint32_t blue, uint32_t white);
/**
* @brief Set HSV for a specific pixel
*
* @param strip: LED strip
* @param index: index of pixel to set
* @param hue: hue part of color (0 - 360)
* @param saturation: saturation part of color (0 - 255, rescaled from 0 - 1. e.g. saturation = 0.5, rescaled to 127)
* @param value: value part of color (0 - 255, rescaled from 0 - 1. e.g. value = 0.5, rescaled to 127)
*
* @return
* - ESP_OK: Set HSV color for a specific pixel successfully
* - ESP_ERR_INVALID_ARG: Set HSV color for a specific pixel failed because of an invalid argument
* - ESP_FAIL: Set HSV color for a specific pixel failed because other error occurred
*/
esp_err_t led_strip_set_pixel_hsv(led_strip_handle_t strip, uint32_t index, uint16_t hue, uint8_t saturation, uint8_t value);
/**
* @brief Set HSV for a specific pixel in 16-bit resolution
*
* @param strip: LED strip
* @param index: index of pixel to set
* @param hue: hue part of color (0 - 360)
* @param saturation: saturation part of color (0 - 65535, rescaled from 0 - 1. e.g. saturation = 0.5, rescaled to 32767)
* @param value: value part of color (0 - 65535, rescaled from 0 - 1. e.g. value = 0.5, rescaled to 32767)
*
* @return
* - ESP_OK: Set HSV color for a specific pixel successfully
* - ESP_ERR_INVALID_ARG: Set HSV color for a specific pixel failed because of an invalid argument
* - ESP_FAIL: Set HSV color for a specific pixel failed because other error occurred
*/
esp_err_t led_strip_set_pixel_hsv_16(led_strip_handle_t strip, uint32_t index, uint16_t hue, uint16_t saturation, uint16_t value);
/**
* @brief Refresh memory colors to LEDs
*
* @param strip: LED strip
*
* @return
* - ESP_OK: Refresh successfully
* - ESP_FAIL: Refresh failed because some other error occurred
*
* @note:
* After updating the LED colors in the memory, a following invocation of this API is needed to flush colors to strip.
*/
esp_err_t led_strip_refresh(led_strip_handle_t strip);
/**
* @brief Clear LED strip (turn off all LEDs)
*
* @param strip: LED strip
*
* @return
* - ESP_OK: Clear LEDs successfully
* - ESP_FAIL: Clear LEDs failed because some other error occurred
*/
esp_err_t led_strip_clear(led_strip_handle_t strip);
/**
* @brief Free LED strip resources
*
* @param strip: LED strip
*
* @return
* - ESP_OK: Free resources successfully
* - ESP_FAIL: Free resources failed because error occurred
*/
esp_err_t led_strip_del(led_strip_handle_t strip);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,47 @@
/*
* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include "esp_err.h"
#include "led_strip_types.h"
#include "esp_idf_version.h"
#include "driver/rmt_types.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief LED Strip RMT specific configuration
*/
typedef struct {
rmt_clock_source_t clk_src; /*!< RMT clock source */
uint32_t resolution_hz; /*!< RMT tick resolution, if set to zero, a default resolution (10MHz) will be applied */
size_t mem_block_symbols; /*!< How many RMT symbols can one RMT channel hold at one time. Set to 0 will fallback to use the default size. */
/*!< Extra RMT specific driver flags */
struct led_strip_rmt_extra_config {
uint32_t with_dma: 1; /*!< Use DMA to transmit data */
} flags; /*!< Extra driver flags */
} led_strip_rmt_config_t;
/**
* @brief Create LED strip based on RMT TX channel
*
* @param led_config LED strip configuration
* @param rmt_config RMT specific configuration
* @param ret_strip Returned LED strip handle
* @return
* - ESP_OK: create LED strip handle successfully
* - ESP_ERR_INVALID_ARG: create LED strip handle failed because of invalid argument
* - ESP_ERR_NO_MEM: create LED strip handle failed because of out of memory
* - ESP_FAIL: create LED strip handle failed because some other error
*/
esp_err_t led_strip_new_rmt_device(const led_strip_config2_t *led_config, const led_strip_rmt_config_t *rmt_config, led_strip_handle_t *ret_strip);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,47 @@
/*
* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include "esp_err.h"
#include "driver/spi_master.h"
#include "led_strip_types.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief LED Strip SPI specific configuration
*/
typedef struct {
spi_clock_source_t clk_src; /*!< SPI clock source */
spi_host_device_t spi_bus; /*!< SPI bus ID. Which buses are available depends on the specific chip */
struct {
uint32_t with_dma: 1; /*!< Use DMA to transmit data */
} flags; /*!< Extra driver flags */
} led_strip_spi_config_t;
/**
* @brief Create LED strip based on SPI MOSI channel
*
* @note Although only the MOSI line is used for generating the signal, the whole SPI bus can't be used for other purposes.
*
* @param led_config LED strip configuration
* @param spi_config SPI specific configuration
* @param ret_strip Returned LED strip handle
* @return
* - ESP_OK: create LED strip handle successfully
* - ESP_ERR_INVALID_ARG: create LED strip handle failed because of invalid argument
* - ESP_ERR_NOT_SUPPORTED: create LED strip handle failed because of unsupported configuration
* - ESP_ERR_NO_MEM: create LED strip handle failed because of out of memory
* - ESP_FAIL: create LED strip handle failed because some other error
*/
esp_err_t led_strip_new_spi_device(const led_strip_config2_t *led_config, const led_strip_spi_config_t *spi_config, led_strip_handle_t *ret_strip);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,76 @@
/*
* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Type of LED strip handle
*/
typedef struct led_strip_t *led_strip_handle_t;
/**
* @brief LED strip model
* @note Different led model may have different timing parameters, so we need to distinguish them.
*/
typedef enum {
LED_MODEL_WS2812, /*!< LED strip model: WS2812 */
LED_MODEL_SK6812, /*!< LED strip model: SK6812 */
LED_MODEL_WS2811, /*!< LED strip model: WS2811 */
LED_MODEL_WS2816, /*!< LED strip model: WS2816 */
LED_MODEL_INVALID /*!< Invalid LED strip model */
} led_model_t;
/**
* @brief LED color component format
* @note The format is used to specify the order of color components in each pixel, also the number of color components.
*/
typedef union {
struct format_layout {
uint32_t r_pos: 2; /*!< Position of the red channel in the color order: 0~3 */
uint32_t g_pos: 2; /*!< Position of the green channel in the color order: 0~3 */
uint32_t b_pos: 2; /*!< Position of the blue channel in the color order: 0~3 */
uint32_t w_pos: 2; /*!< Position of the white channel in the color order: 0~3 */
uint32_t reserved: 19; /*!< Reserved */
uint32_t bytes_per_color: 2; /*!< Bytes per color component: 1 or 2. If set to 0, it will fallback to 1 */
uint32_t num_components: 3; /*!< Number of color components per pixel: 3 or 4. If set to 0, it will fallback to 3 */
} format; /*!< Format layout */
uint32_t format_id; /*!< Format ID */
} led_color_component_format_t;
/// Helper macros to set the color component format
#define LED_STRIP_COLOR_COMPONENT_FMT_GRB (led_color_component_format_t){.format = {.r_pos = 1, .g_pos = 0, .b_pos = 2, .w_pos = 3, .reserved = 0, .bytes_per_color = 1, .num_components = 3}}
#define LED_STRIP_COLOR_COMPONENT_FMT_GRB_16 (led_color_component_format_t){.format = {.r_pos = 1, .g_pos = 0, .b_pos = 2, .w_pos = 3, .reserved = 0, .bytes_per_color = 2, .num_components = 3}}
#define LED_STRIP_COLOR_COMPONENT_FMT_GRBW (led_color_component_format_t){.format = {.r_pos = 1, .g_pos = 0, .b_pos = 2, .w_pos = 3, .reserved = 0, .bytes_per_color = 1, .num_components = 4}}
#define LED_STRIP_COLOR_COMPONENT_FMT_GRBW_16 (led_color_component_format_t){.format = {.r_pos = 1, .g_pos = 0, .b_pos = 2, .w_pos = 3, .reserved = 0, .bytes_per_color = 2, .num_components = 4}}
#define LED_STRIP_COLOR_COMPONENT_FMT_RGB (led_color_component_format_t){.format = {.r_pos = 0, .g_pos = 1, .b_pos = 2, .w_pos = 3, .reserved = 0, .bytes_per_color = 1, .num_components = 3}}
#define LED_STRIP_COLOR_COMPONENT_FMT_RGB_16 (led_color_component_format_t){.format = {.r_pos = 0, .g_pos = 1, .b_pos = 2, .w_pos = 3, .reserved = 0, .bytes_per_color = 2, .num_components = 3}}
#define LED_STRIP_COLOR_COMPONENT_FMT_RGBW (led_color_component_format_t){.format = {.r_pos = 0, .g_pos = 1, .b_pos = 2, .w_pos = 3, .reserved = 0, .bytes_per_color = 1, .num_components = 4}}
#define LED_STRIP_COLOR_COMPONENT_FMT_RGBW_16 (led_color_component_format_t){.format = {.r_pos = 0, .g_pos = 1, .b_pos = 2, .w_pos = 3, .reserved = 0, .bytes_per_color = 2, .num_components = 4}}
/**
* @brief LED Strip common configurations
* The common configurations are not specific to any backend peripheral.
*/
typedef struct {
int strip_gpio_num; /*!< GPIO number that used by LED strip */
uint32_t max_leds; /*!< Maximum number of LEDs that can be controlled in a single strip */
led_model_t led_model; /*!< Specifies the LED strip model (e.g., WS2812, SK6812) */
led_color_component_format_t color_component_format; /*!< Specifies the order of color components in each pixel.
Use helper macros like `LED_STRIP_COLOR_COMPONENT_FMT_GRB` to set the format */
/*!< LED strip extra driver flags */
struct led_strip_extra_flags {
uint32_t invert_out: 1; /*!< Invert output signal */
} flags; /*!< Extra driver flags */
} led_strip_config2_t;
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,95 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include "esp_err.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct led_strip_t led_strip_t; /*!< Type of LED strip */
/**
* @brief LED strip interface definition
*/
struct led_strip_t {
/**
* @brief Set RGB for a specific pixel
*
* @param strip: LED strip
* @param index: index of pixel to set
* @param red: red part of color
* @param green: green part of color
* @param blue: blue part of color
*
* @return
* - ESP_OK: Set RGB for a specific pixel successfully
* - ESP_ERR_INVALID_ARG: Set RGB for a specific pixel failed because of invalid parameters
* - ESP_FAIL: Set RGB for a specific pixel failed because other error occurred
*/
esp_err_t (*set_pixel)(led_strip_t *strip, uint32_t index, uint32_t red, uint32_t green, uint32_t blue);
/**
* @brief Set RGBW for a specific pixel. Similar to `set_pixel` but also set the white component
*
* @param strip: LED strip
* @param index: index of pixel to set
* @param red: red part of color
* @param green: green part of color
* @param blue: blue part of color
* @param white: separate white component
*
* @return
* - ESP_OK: Set RGBW color for a specific pixel successfully
* - ESP_ERR_INVALID_ARG: Set RGBW color for a specific pixel failed because of an invalid argument
* - ESP_FAIL: Set RGBW color for a specific pixel failed because other error occurred
*/
esp_err_t (*set_pixel_rgbw)(led_strip_t *strip, uint32_t index, uint32_t red, uint32_t green, uint32_t blue, uint32_t white);
/**
* @brief Refresh memory colors to LEDs
*
* @param strip: LED strip
* @param timeout_ms: timeout value for refreshing task
*
* @return
* - ESP_OK: Refresh successfully
* - ESP_FAIL: Refresh failed because some other error occurred
*
* @note:
* After updating the LED colors in the memory, a following invocation of this API is needed to flush colors to strip.
*/
esp_err_t (*refresh)(led_strip_t *strip);
/**
* @brief Clear LED strip (turn off all LEDs)
*
* @param strip: LED strip
* @param timeout_ms: timeout value for clearing task
*
* @return
* - ESP_OK: Clear LEDs successfully
* - ESP_FAIL: Clear LEDs failed because some other error occurred
*/
esp_err_t (*clear)(led_strip_t *strip);
/**
* @brief Free LED strip resources
*
* @param strip: LED strip
*
* @return
* - ESP_OK: Free resources successfully
* - ESP_FAIL: Free resources failed because error occurred
*/
esp_err_t (*del)(led_strip_t *strip);
};
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,147 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "esp_log.h"
#include "esp_check.h"
#include "led_strip.h"
#include "led_strip_interface.h"
static const char *TAG = "led_strip";
esp_err_t led_strip_set_pixel(led_strip_handle_t strip, uint32_t index, uint32_t red, uint32_t green, uint32_t blue)
{
ESP_RETURN_ON_FALSE(strip, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
return strip->set_pixel(strip, index, red, green, blue);
}
esp_err_t led_strip_set_pixel_hsv(led_strip_handle_t strip, uint32_t index, uint16_t hue, uint8_t saturation, uint8_t value)
{
ESP_RETURN_ON_FALSE(strip, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
uint32_t red = 0;
uint32_t green = 0;
uint32_t blue = 0;
uint32_t rgb_max = value;
uint32_t rgb_min = rgb_max * (255 - saturation) / 255;
uint32_t i = hue / 60;
uint32_t diff = hue % 60;
// RGB adjustment amount by hue
uint32_t rgb_adj = (rgb_max - rgb_min) * diff / 60;
switch (i) {
case 0:
red = rgb_max;
green = rgb_min + rgb_adj;
blue = rgb_min;
break;
case 1:
red = rgb_max - rgb_adj;
green = rgb_max;
blue = rgb_min;
break;
case 2:
red = rgb_min;
green = rgb_max;
blue = rgb_min + rgb_adj;
break;
case 3:
red = rgb_min;
green = rgb_max - rgb_adj;
blue = rgb_max;
break;
case 4:
red = rgb_min + rgb_adj;
green = rgb_min;
blue = rgb_max;
break;
default:
red = rgb_max;
green = rgb_min;
blue = rgb_max - rgb_adj;
break;
}
return strip->set_pixel(strip, index, red, green, blue);
}
esp_err_t led_strip_set_pixel_hsv_16(led_strip_handle_t strip, uint32_t index, uint16_t hue, uint16_t saturation, uint16_t value)
{
ESP_RETURN_ON_FALSE(strip, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
uint32_t red = 0;
uint32_t green = 0;
uint32_t blue = 0;
uint32_t rgb_max = value;
uint32_t rgb_min = rgb_max * (65535 - saturation) / 65535;
uint32_t i = hue / 60;
uint32_t diff = hue % 60;
// RGB adjustment amount by hue
uint32_t rgb_adj = (rgb_max - rgb_min) * diff / 60;
switch (i) {
case 0:
red = rgb_max;
green = rgb_min + rgb_adj;
blue = rgb_min;
break;
case 1:
red = rgb_max - rgb_adj;
green = rgb_max;
blue = rgb_min;
break;
case 2:
red = rgb_min;
green = rgb_max;
blue = rgb_min + rgb_adj;
break;
case 3:
red = rgb_min;
green = rgb_max - rgb_adj;
blue = rgb_max;
break;
case 4:
red = rgb_min + rgb_adj;
green = rgb_min;
blue = rgb_max;
break;
default:
red = rgb_max;
green = rgb_min;
blue = rgb_max - rgb_adj;
break;
}
return strip->set_pixel(strip, index, red, green, blue);
}
esp_err_t led_strip_set_pixel_rgbw(led_strip_handle_t strip, uint32_t index, uint32_t red, uint32_t green, uint32_t blue, uint32_t white)
{
ESP_RETURN_ON_FALSE(strip, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
return strip->set_pixel_rgbw(strip, index, red, green, blue, white);
}
esp_err_t led_strip_refresh(led_strip_handle_t strip)
{
ESP_RETURN_ON_FALSE(strip, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
return strip->refresh(strip);
}
esp_err_t led_strip_clear(led_strip_handle_t strip)
{
ESP_RETURN_ON_FALSE(strip, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
return strip->clear(strip);
}
esp_err_t led_strip_del(led_strip_handle_t strip)
{
ESP_RETURN_ON_FALSE(strip, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
return strip->del(strip);
}

View File

@@ -0,0 +1,198 @@
/*
* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdlib.h>
#include <string.h>
#include <sys/cdefs.h>
#include "esp_log.h"
#include "esp_check.h"
#include "driver/rmt_tx.h"
#include "led_strip.h"
#include "led_strip_interface.h"
#include "led_strip_rmt_encoder.h"
#define LED_STRIP_RMT_DEFAULT_RESOLUTION 10000000 // 10MHz resolution
#define LED_STRIP_RMT_DEFAULT_TRANS_QUEUE_SIZE 4
// the memory size of each RMT channel, in words (4 bytes)
#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2
#define LED_STRIP_RMT_DEFAULT_MEM_BLOCK_SYMBOLS 64
#else
#define LED_STRIP_RMT_DEFAULT_MEM_BLOCK_SYMBOLS 48
#endif
static const char *TAG = "led_strip_rmt";
typedef struct {
led_strip_t base;
rmt_channel_handle_t rmt_chan;
rmt_encoder_handle_t strip_encoder;
uint32_t strip_len;
uint8_t bytes_per_pixel;
led_color_component_format_t component_fmt;
uint8_t pixel_buf[];
} led_strip_rmt_obj;
static esp_err_t led_strip_rmt_set_pixel(led_strip_t *strip, uint32_t index, uint32_t red, uint32_t green, uint32_t blue)
{
led_strip_rmt_obj *rmt_strip = __containerof(strip, led_strip_rmt_obj, base);
ESP_RETURN_ON_FALSE(index < rmt_strip->strip_len, ESP_ERR_INVALID_ARG, TAG, "index out of maximum number of LEDs");
struct format_layout format = rmt_strip->component_fmt.format;
uint32_t start = index * rmt_strip->bytes_per_pixel;
uint8_t *pixel_buf = rmt_strip->pixel_buf;
uint8_t pos_bytes = format.bytes_per_color;
for (uint8_t i = 0; i < format.bytes_per_color; i++) {
uint8_t color_shift = 8 * (format.bytes_per_color - 1 - i);
pixel_buf[start + format.r_pos * pos_bytes + i] = (red >> color_shift) & 0xFF;
pixel_buf[start + format.g_pos * pos_bytes + i] = (green >> color_shift) & 0xFF;
pixel_buf[start + format.b_pos * pos_bytes + i] = (blue >> color_shift) & 0xFF;
if (format.num_components > 3) {
pixel_buf[start + format.w_pos * pos_bytes + i] = 0;
}
}
return ESP_OK;
}
static esp_err_t led_strip_rmt_set_pixel_rgbw(led_strip_t *strip, uint32_t index, uint32_t red, uint32_t green, uint32_t blue, uint32_t white)
{
led_strip_rmt_obj *rmt_strip = __containerof(strip, led_strip_rmt_obj, base);
struct format_layout format = rmt_strip->component_fmt.format;
ESP_RETURN_ON_FALSE(index < rmt_strip->strip_len, ESP_ERR_INVALID_ARG, TAG, "index out of maximum number of LEDs");
ESP_RETURN_ON_FALSE(format.num_components == 4, ESP_ERR_INVALID_ARG, TAG, "led doesn't have 4 components");
uint32_t start = index * rmt_strip->bytes_per_pixel;
uint8_t *pixel_buf = rmt_strip->pixel_buf;
uint8_t pos_bytes = format.bytes_per_color;
for (uint8_t i = 0; i < format.bytes_per_color; i++) {
uint8_t color_shift = 8 * (format.bytes_per_color - 1 - i);
pixel_buf[start + format.r_pos * pos_bytes + i] = (red >> color_shift) & 0xFF;
pixel_buf[start + format.g_pos * pos_bytes + i] = (green >> color_shift) & 0xFF;
pixel_buf[start + format.b_pos * pos_bytes + i] = (blue >> color_shift) & 0xFF;
pixel_buf[start + format.w_pos * pos_bytes + i] = (white >> color_shift) & 0xFF;
}
return ESP_OK;
}
static esp_err_t led_strip_rmt_refresh(led_strip_t *strip)
{
led_strip_rmt_obj *rmt_strip = __containerof(strip, led_strip_rmt_obj, base);
rmt_transmit_config_t tx_conf = {
.loop_count = 0,
};
ESP_RETURN_ON_ERROR(rmt_enable(rmt_strip->rmt_chan), TAG, "enable RMT channel failed");
ESP_RETURN_ON_ERROR(rmt_transmit(rmt_strip->rmt_chan, rmt_strip->strip_encoder, rmt_strip->pixel_buf,
rmt_strip->strip_len * rmt_strip->bytes_per_pixel, &tx_conf), TAG, "transmit pixels by RMT failed");
ESP_RETURN_ON_ERROR(rmt_tx_wait_all_done(rmt_strip->rmt_chan, -1), TAG, "flush RMT channel failed");
ESP_RETURN_ON_ERROR(rmt_disable(rmt_strip->rmt_chan), TAG, "disable RMT channel failed");
return ESP_OK;
}
static esp_err_t led_strip_rmt_clear(led_strip_t *strip)
{
led_strip_rmt_obj *rmt_strip = __containerof(strip, led_strip_rmt_obj, base);
// Write zero to turn off all leds
memset(rmt_strip->pixel_buf, 0, rmt_strip->strip_len * rmt_strip->bytes_per_pixel);
return led_strip_rmt_refresh(strip);
}
static esp_err_t led_strip_rmt_del(led_strip_t *strip)
{
led_strip_rmt_obj *rmt_strip = __containerof(strip, led_strip_rmt_obj, base);
ESP_RETURN_ON_ERROR(rmt_del_channel(rmt_strip->rmt_chan), TAG, "delete RMT channel failed");
ESP_RETURN_ON_ERROR(rmt_del_encoder(rmt_strip->strip_encoder), TAG, "delete strip encoder failed");
free(rmt_strip);
return ESP_OK;
}
esp_err_t led_strip_new_rmt_device(const led_strip_config2_t *led_config, const led_strip_rmt_config_t *rmt_config, led_strip_handle_t *ret_strip)
{
led_strip_rmt_obj *rmt_strip = NULL;
esp_err_t ret = ESP_OK;
ESP_GOTO_ON_FALSE(led_config && rmt_config && ret_strip, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
led_color_component_format_t component_fmt = led_config->color_component_format;
// If R/G/B order is not specified, set default GRB order as fallback
if (component_fmt.format_id == 0) {
component_fmt = LED_STRIP_COLOR_COMPONENT_FMT_GRB;
}
if (led_config->led_model == LED_MODEL_WS2816) {
component_fmt.format.bytes_per_color = 2;
}
if (component_fmt.format.bytes_per_color == 0) {
component_fmt.format.bytes_per_color = 1;
}
// check the validation of the color component format
uint8_t mask = 0;
if (component_fmt.format.num_components == 3) {
mask = BIT(component_fmt.format.r_pos) | BIT(component_fmt.format.g_pos) | BIT(component_fmt.format.b_pos);
// Check for invalid values
ESP_RETURN_ON_FALSE(mask == 0x07, ESP_ERR_INVALID_ARG, TAG, "invalid order argument");
} else if (component_fmt.format.num_components == 4) {
mask = BIT(component_fmt.format.r_pos) | BIT(component_fmt.format.g_pos) | BIT(component_fmt.format.b_pos) | BIT(component_fmt.format.w_pos);
// Check for invalid values
ESP_RETURN_ON_FALSE(mask == 0x0F, ESP_ERR_INVALID_ARG, TAG, "invalid order argument");
} else {
ESP_RETURN_ON_FALSE(false, ESP_ERR_INVALID_ARG, TAG, "invalid number of color components: %d", component_fmt.format.num_components);
}
uint8_t bytes_per_pixel = component_fmt.format.num_components;
if (component_fmt.format.bytes_per_color > 1) {
bytes_per_pixel *= component_fmt.format.bytes_per_color;
}
rmt_strip = calloc(1, sizeof(led_strip_rmt_obj) + led_config->max_leds * bytes_per_pixel);
ESP_GOTO_ON_FALSE(rmt_strip, ESP_ERR_NO_MEM, err, TAG, "no mem for rmt strip");
uint32_t resolution = rmt_config->resolution_hz ? rmt_config->resolution_hz : LED_STRIP_RMT_DEFAULT_RESOLUTION;
// for backward compatibility, if the user does not set the clk_src, use the default value
rmt_clock_source_t clk_src = RMT_CLK_SRC_DEFAULT;
if (rmt_config->clk_src) {
clk_src = rmt_config->clk_src;
}
size_t mem_block_symbols = LED_STRIP_RMT_DEFAULT_MEM_BLOCK_SYMBOLS;
// override the default value if the user sets it
if (rmt_config->mem_block_symbols) {
mem_block_symbols = rmt_config->mem_block_symbols;
}
rmt_tx_channel_config_t rmt_chan_config = {
.clk_src = clk_src,
.gpio_num = led_config->strip_gpio_num,
.mem_block_symbols = mem_block_symbols,
.resolution_hz = resolution,
.trans_queue_depth = LED_STRIP_RMT_DEFAULT_TRANS_QUEUE_SIZE,
.flags.with_dma = rmt_config->flags.with_dma,
.flags.invert_out = led_config->flags.invert_out,
};
ESP_GOTO_ON_ERROR(rmt_new_tx_channel(&rmt_chan_config, &rmt_strip->rmt_chan), err, TAG, "create RMT TX channel failed");
led_strip_encoder_config_t strip_encoder_conf = {
.resolution = resolution,
.led_model = led_config->led_model
};
ESP_GOTO_ON_ERROR(rmt_new_led_strip_encoder(&strip_encoder_conf, &rmt_strip->strip_encoder), err, TAG, "create LED strip encoder failed");
rmt_strip->component_fmt = component_fmt;
rmt_strip->bytes_per_pixel = bytes_per_pixel;
rmt_strip->strip_len = led_config->max_leds;
rmt_strip->base.set_pixel = led_strip_rmt_set_pixel;
rmt_strip->base.set_pixel_rgbw = led_strip_rmt_set_pixel_rgbw;
rmt_strip->base.refresh = led_strip_rmt_refresh;
rmt_strip->base.clear = led_strip_rmt_clear;
rmt_strip->base.del = led_strip_rmt_del;
*ret_strip = &rmt_strip->base;
return ESP_OK;
err:
if (rmt_strip) {
if (rmt_strip->rmt_chan) {
rmt_del_channel(rmt_strip->rmt_chan);
}
if (rmt_strip->strip_encoder) {
rmt_del_encoder(rmt_strip->strip_encoder);
}
free(rmt_strip);
}
return ret;
}

View File

@@ -0,0 +1,194 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "sdkconfig.h"
#include "esp_idf_version.h"
#include "esp_check.h"
#include "esp_attr.h"
#include "led_strip_rmt_encoder.h"
static const char *TAG = "led_rmt_encoder";
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 5, 0)
#if CONFIG_RMT_ISR_IRAM_SAFE
#define RMT_ENCODER_FUNC_ATTR IRAM_ATTR
#else
#define RMT_ENCODER_FUNC_ATTR
#endif // CONFIG_RMT_ISR_IRAM_SAFE
#endif // ESP_IDF_VERSION
typedef struct {
rmt_encoder_t base;
rmt_encoder_t *bytes_encoder;
rmt_encoder_t *copy_encoder;
int state;
rmt_symbol_word_t reset_code;
} rmt_led_strip_encoder_t;
RMT_ENCODER_FUNC_ATTR
static size_t rmt_encode_led_strip(rmt_encoder_t *encoder, rmt_channel_handle_t channel, const void *primary_data, size_t data_size, rmt_encode_state_t *ret_state)
{
rmt_led_strip_encoder_t *led_encoder = __containerof(encoder, rmt_led_strip_encoder_t, base);
rmt_encoder_handle_t bytes_encoder = led_encoder->bytes_encoder;
rmt_encoder_handle_t copy_encoder = led_encoder->copy_encoder;
rmt_encode_state_t session_state = 0;
rmt_encode_state_t state = 0;
size_t encoded_symbols = 0;
switch (led_encoder->state) {
case 0: // send RGB data
encoded_symbols += bytes_encoder->encode(bytes_encoder, channel, primary_data, data_size, &session_state);
if (session_state & RMT_ENCODING_COMPLETE) {
led_encoder->state = 1; // switch to next state when current encoding session finished
}
if (session_state & RMT_ENCODING_MEM_FULL) {
state |= RMT_ENCODING_MEM_FULL;
goto out; // yield if there's no free space for encoding artifacts
}
// fall-through
case 1: // send reset code
encoded_symbols += copy_encoder->encode(copy_encoder, channel, &led_encoder->reset_code,
sizeof(led_encoder->reset_code), &session_state);
if (session_state & RMT_ENCODING_COMPLETE) {
led_encoder->state = 0; // back to the initial encoding session
state |= RMT_ENCODING_COMPLETE;
}
if (session_state & RMT_ENCODING_MEM_FULL) {
state |= RMT_ENCODING_MEM_FULL;
goto out; // yield if there's no free space for encoding artifacts
}
}
out:
*ret_state = state;
return encoded_symbols;
}
static esp_err_t rmt_del_led_strip_encoder(rmt_encoder_t *encoder)
{
rmt_led_strip_encoder_t *led_encoder = __containerof(encoder, rmt_led_strip_encoder_t, base);
rmt_del_encoder(led_encoder->bytes_encoder);
rmt_del_encoder(led_encoder->copy_encoder);
free(led_encoder);
return ESP_OK;
}
RMT_ENCODER_FUNC_ATTR
static esp_err_t rmt_led_strip_encoder_reset(rmt_encoder_t *encoder)
{
rmt_led_strip_encoder_t *led_encoder = __containerof(encoder, rmt_led_strip_encoder_t, base);
rmt_encoder_reset(led_encoder->bytes_encoder);
rmt_encoder_reset(led_encoder->copy_encoder);
led_encoder->state = 0;
return ESP_OK;
}
esp_err_t rmt_new_led_strip_encoder(const led_strip_encoder_config_t *config, rmt_encoder_handle_t *ret_encoder)
{
esp_err_t ret = ESP_OK;
rmt_led_strip_encoder_t *led_encoder = NULL;
ESP_GOTO_ON_FALSE(config && ret_encoder, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
ESP_GOTO_ON_FALSE(config->led_model < LED_MODEL_INVALID, ESP_ERR_INVALID_ARG, err, TAG, "invalid led model");
led_encoder = calloc(1, sizeof(rmt_led_strip_encoder_t));
ESP_GOTO_ON_FALSE(led_encoder, ESP_ERR_NO_MEM, err, TAG, "no mem for led strip encoder");
led_encoder->base.encode = rmt_encode_led_strip;
led_encoder->base.del = rmt_del_led_strip_encoder;
led_encoder->base.reset = rmt_led_strip_encoder_reset;
rmt_bytes_encoder_config_t bytes_encoder_config;
uint32_t reset_ticks = config->resolution / 1000000 * 280 / 2; // reset code duration defaults to 280us to accommodate WS2812B-V5
if (config->led_model == LED_MODEL_SK6812) {
bytes_encoder_config = (rmt_bytes_encoder_config_t) {
.bit0 = {
.level0 = 1,
.duration0 = 0.3 * config->resolution / 1000000, // T0H=0.3us
.level1 = 0,
.duration1 = 0.9 * config->resolution / 1000000, // T0L=0.9us
},
.bit1 = {
.level0 = 1,
.duration0 = 0.6 * config->resolution / 1000000, // T1H=0.6us
.level1 = 0,
.duration1 = 0.6 * config->resolution / 1000000, // T1L=0.6us
},
.flags.msb_first = 1 // SK6812 transfer bit order: G7...G0R7...R0B7...B0(W7...W0)
};
} else if (config->led_model == LED_MODEL_WS2812) {
// different led strip might have its own timing requirements, following parameter is for WS2812
bytes_encoder_config = (rmt_bytes_encoder_config_t) {
.bit0 = {
.level0 = 1,
.duration0 = 0.3 * config->resolution / 1000000, // T0H=0.3us
.level1 = 0,
.duration1 = 0.9 * config->resolution / 1000000, // T0L=0.9us
},
.bit1 = {
.level0 = 1,
.duration0 = 0.9 * config->resolution / 1000000, // T1H=0.9us
.level1 = 0,
.duration1 = 0.3 * config->resolution / 1000000, // T1L=0.3us
},
.flags.msb_first = 1 // WS2812 transfer bit order: G7...G0R7...R0B7...B0
};
} else if (config->led_model == LED_MODEL_WS2811) {
// different led strip might have its own timing requirements, following parameter is for WS2811
bytes_encoder_config = (rmt_bytes_encoder_config_t) {
.bit0 = {
.level0 = 1,
.duration0 = 0.5 * config->resolution / 1000000., // T0H=0.5us
.level1 = 0,
.duration1 = 2.0 * config->resolution / 1000000., // T0L=2.0us
},
.bit1 = {
.level0 = 1,
.duration0 = 1.2 * config->resolution / 1000000., // T1H=1.2us
.level1 = 0,
.duration1 = 1.3 * config->resolution / 1000000., // T1L=1.3us
},
.flags.msb_first = 1
};
reset_ticks = config->resolution / 1000000 * 50 / 2; // divide by 2... signal is sent twice
} else if (config->led_model == LED_MODEL_WS2816) {
// different led strip might have its own timing requirements, following parameter is for WS2816
bytes_encoder_config = (rmt_bytes_encoder_config_t) {
.bit0 = {
.level0 = 1,
.duration0 = 0.3 * config->resolution / 1000000, // T0H=0.3us
.level1 = 0,
.duration1 = 0.95 * config->resolution / 1000000, // T0L=0.95us
},
.bit1 = {
.level0 = 1,
.duration0 = 0.75 * config->resolution / 1000000, // T1H=0.75us
.level1 = 0,
.duration1 = 0.5 * config->resolution / 1000000, // T1L=0.5us
},
.flags.msb_first = 1
};
} else {
assert(false);
}
ESP_GOTO_ON_ERROR(rmt_new_bytes_encoder(&bytes_encoder_config, &led_encoder->bytes_encoder), err, TAG, "create bytes encoder failed");
rmt_copy_encoder_config_t copy_encoder_config = {};
ESP_GOTO_ON_ERROR(rmt_new_copy_encoder(&copy_encoder_config, &led_encoder->copy_encoder), err, TAG, "create copy encoder failed");
led_encoder->reset_code = (rmt_symbol_word_t) {
.level0 = 0,
.duration0 = reset_ticks,
.level1 = 0,
.duration1 = reset_ticks,
};
*ret_encoder = &led_encoder->base;
return ESP_OK;
err:
if (led_encoder) {
if (led_encoder->bytes_encoder) {
rmt_del_encoder(led_encoder->bytes_encoder);
}
if (led_encoder->copy_encoder) {
rmt_del_encoder(led_encoder->copy_encoder);
}
free(led_encoder);
}
return ret;
}

View File

@@ -0,0 +1,38 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include "driver/rmt_encoder.h"
#include "led_strip_types.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Type of led strip encoder configuration
*/
typedef struct {
uint32_t resolution; /*!< Encoder resolution, in Hz */
led_model_t led_model; /*!< LED model */
} led_strip_encoder_config_t;
/**
* @brief Create RMT encoder for encoding LED strip pixels into RMT symbols
*
* @param[in] config Encoder configuration
* @param[out] ret_encoder Returned encoder handle
* @return
* - ESP_ERR_INVALID_ARG for any invalid arguments
* - ESP_ERR_NO_MEM out of memory when creating led strip encoder
* - ESP_OK if creating encoder successfully
*/
esp_err_t rmt_new_led_strip_encoder(const led_strip_encoder_config_t *config, rmt_encoder_handle_t *ret_encoder);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,248 @@
/*
* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdlib.h>
#include <string.h>
#include <sys/cdefs.h>
#include "esp_log.h"
#include "esp_check.h"
#include "esp_rom_gpio.h"
#include "soc/spi_periph.h"
#include "led_strip.h"
#include "led_strip_interface.h"
#include "esp_heap_caps.h"
#define LED_STRIP_SPI_DEFAULT_RESOLUTION (2.5 * 1000 * 1000) // 2.5MHz resolution
#define LED_STRIP_SPI_DEFAULT_TRANS_QUEUE_SIZE 4
#define SPI_BYTES_PER_COLOR_BYTE 3
#define SPI_BITS_PER_COLOR_BYTE (SPI_BYTES_PER_COLOR_BYTE * 8)
static const char *TAG = "led_strip_spi";
typedef struct {
led_strip_t base;
spi_host_device_t spi_host;
spi_device_handle_t spi_device;
uint32_t strip_len;
uint8_t bytes_per_pixel;
led_color_component_format_t component_fmt;
uint8_t pixel_buf[];
} led_strip_spi_obj;
// please make sure to zero-initialize the buf before calling this function
static void __led_strip_spi_bit(uint8_t data, uint8_t *buf)
{
// Each color of 1 bit is represented by 3 bits of SPI, low_level:100 ,high_level:110
// So a color byte occupies 3 bytes of SPI.
*(buf + 2) |= data & BIT(0) ? BIT(2) | BIT(1) : BIT(2);
*(buf + 2) |= data & BIT(1) ? BIT(5) | BIT(4) : BIT(5);
*(buf + 2) |= data & BIT(2) ? BIT(7) : 0x00;
*(buf + 1) |= BIT(0);
*(buf + 1) |= data & BIT(3) ? BIT(3) | BIT(2) : BIT(3);
*(buf + 1) |= data & BIT(4) ? BIT(6) | BIT(5) : BIT(6);
*(buf + 0) |= data & BIT(5) ? BIT(1) | BIT(0) : BIT(1);
*(buf + 0) |= data & BIT(6) ? BIT(4) | BIT(3) : BIT(4);
*(buf + 0) |= data & BIT(7) ? BIT(7) | BIT(6) : BIT(7);
}
static esp_err_t led_strip_spi_set_pixel(led_strip_t *strip, uint32_t index, uint32_t red, uint32_t green, uint32_t blue)
{
led_strip_spi_obj *spi_strip = __containerof(strip, led_strip_spi_obj, base);
ESP_RETURN_ON_FALSE(index < spi_strip->strip_len, ESP_ERR_INVALID_ARG, TAG, "index out of maximum number of LEDs");
// 3 pixels take 72bits(9bytes)
uint32_t start = index * spi_strip->bytes_per_pixel * SPI_BYTES_PER_COLOR_BYTE;
uint8_t *pixel_buf = spi_strip->pixel_buf;
struct format_layout format = spi_strip->component_fmt.format;
memset(pixel_buf + start, 0, spi_strip->bytes_per_pixel * SPI_BYTES_PER_COLOR_BYTE);
uint8_t pos_bytes = format.bytes_per_color;
for (uint8_t i = 0; i < format.bytes_per_color; i++) {
uint8_t color_shift = 8 * (format.bytes_per_color - 1 - i);
__led_strip_spi_bit((red >> color_shift) & 0xFF, &pixel_buf[start + SPI_BYTES_PER_COLOR_BYTE * (format.r_pos * pos_bytes + i)]);
__led_strip_spi_bit((green >> color_shift) & 0xFF, &pixel_buf[start + SPI_BYTES_PER_COLOR_BYTE * (format.g_pos * pos_bytes + i)]);
__led_strip_spi_bit((blue >> color_shift) & 0xFF, &pixel_buf[start + SPI_BYTES_PER_COLOR_BYTE * (format.b_pos * pos_bytes + i)]);
if (format.num_components > 3) {
__led_strip_spi_bit(0, &pixel_buf[start + SPI_BYTES_PER_COLOR_BYTE * (format.w_pos * pos_bytes + i)]);
}
}
return ESP_OK;
}
static esp_err_t led_strip_spi_set_pixel_rgbw(led_strip_t *strip, uint32_t index, uint32_t red, uint32_t green, uint32_t blue, uint32_t white)
{
led_strip_spi_obj *spi_strip = __containerof(strip, led_strip_spi_obj, base);
struct format_layout format = spi_strip->component_fmt.format;
ESP_RETURN_ON_FALSE(index < spi_strip->strip_len, ESP_ERR_INVALID_ARG, TAG, "index out of maximum number of LEDs");
ESP_RETURN_ON_FALSE(format.num_components == 4, ESP_ERR_INVALID_ARG, TAG, "led doesn't have 4 components");
// LED_PIXEL_FORMAT_GRBW takes 96bits(12bytes)
uint32_t start = index * spi_strip->bytes_per_pixel * SPI_BYTES_PER_COLOR_BYTE;
uint8_t *pixel_buf = spi_strip->pixel_buf;
memset(pixel_buf + start, 0, spi_strip->bytes_per_pixel * SPI_BYTES_PER_COLOR_BYTE);
uint8_t pos_bytes = format.bytes_per_color;
for (uint8_t i = 0; i < format.bytes_per_color; i++) {
uint8_t color_shift = 8 * (format.bytes_per_color - 1 - i);
__led_strip_spi_bit((red >> color_shift) & 0xFF, &pixel_buf[start + SPI_BYTES_PER_COLOR_BYTE * (format.r_pos * pos_bytes + i)]);
__led_strip_spi_bit((green >> color_shift) & 0xFF, &pixel_buf[start + SPI_BYTES_PER_COLOR_BYTE * (format.g_pos * pos_bytes + i)]);
__led_strip_spi_bit((blue >> color_shift) & 0xFF, &pixel_buf[start + SPI_BYTES_PER_COLOR_BYTE * (format.b_pos * pos_bytes + i)]);
__led_strip_spi_bit((white >> color_shift) & 0xFF, &pixel_buf[start + SPI_BYTES_PER_COLOR_BYTE * (format.w_pos * pos_bytes + i)]);
}
return ESP_OK;
}
static esp_err_t led_strip_spi_refresh(led_strip_t *strip)
{
led_strip_spi_obj *spi_strip = __containerof(strip, led_strip_spi_obj, base);
spi_transaction_t tx_conf;
memset(&tx_conf, 0, sizeof(tx_conf));
tx_conf.length = spi_strip->strip_len * spi_strip->bytes_per_pixel * SPI_BITS_PER_COLOR_BYTE;
tx_conf.tx_buffer = spi_strip->pixel_buf;
tx_conf.rx_buffer = NULL;
ESP_RETURN_ON_ERROR(spi_device_transmit(spi_strip->spi_device, &tx_conf), TAG, "transmit pixels by SPI failed");
return ESP_OK;
}
static esp_err_t led_strip_spi_clear(led_strip_t *strip)
{
led_strip_spi_obj *spi_strip = __containerof(strip, led_strip_spi_obj, base);
//Write zero to turn off all leds
memset(spi_strip->pixel_buf, 0, spi_strip->strip_len * spi_strip->bytes_per_pixel * SPI_BYTES_PER_COLOR_BYTE);
uint8_t *buf = spi_strip->pixel_buf;
for (int index = 0; index < spi_strip->strip_len * spi_strip->bytes_per_pixel; index++) {
__led_strip_spi_bit(0, buf);
buf += SPI_BYTES_PER_COLOR_BYTE;
}
return led_strip_spi_refresh(strip);
}
static esp_err_t led_strip_spi_del(led_strip_t *strip)
{
led_strip_spi_obj *spi_strip = __containerof(strip, led_strip_spi_obj, base);
ESP_RETURN_ON_ERROR(spi_bus_remove_device(spi_strip->spi_device), TAG, "delete spi device failed");
ESP_RETURN_ON_ERROR(spi_bus_free(spi_strip->spi_host), TAG, "free spi bus failed");
free(spi_strip);
return ESP_OK;
}
esp_err_t led_strip_new_spi_device(const led_strip_config2_t *led_config, const led_strip_spi_config_t *spi_config, led_strip_handle_t *ret_strip)
{
led_strip_spi_obj *spi_strip = NULL;
esp_err_t ret = ESP_OK;
ESP_GOTO_ON_FALSE(led_config && spi_config && ret_strip, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
led_color_component_format_t component_fmt = led_config->color_component_format;
// If R/G/B order is not specified, set default GRB order as fallback
if (component_fmt.format_id == 0) {
component_fmt = LED_STRIP_COLOR_COMPONENT_FMT_GRB;
}
if (led_config->led_model == LED_MODEL_WS2816) {
component_fmt.format.bytes_per_color = 2;
}
if (component_fmt.format.bytes_per_color == 0) {
component_fmt.format.bytes_per_color = 1;
}
uint8_t mask = 0;
if (component_fmt.format.num_components == 3) {
mask = BIT(component_fmt.format.r_pos) | BIT(component_fmt.format.g_pos) | BIT(component_fmt.format.b_pos);
// Check for invalid values
ESP_RETURN_ON_FALSE(mask == 0x07, ESP_ERR_INVALID_ARG, TAG, "invalid order argument");
} else if (component_fmt.format.num_components == 4) {
mask = BIT(component_fmt.format.r_pos) | BIT(component_fmt.format.g_pos) | BIT(component_fmt.format.b_pos) | BIT(component_fmt.format.w_pos);
// Check for invalid values
ESP_RETURN_ON_FALSE(mask == 0x0F, ESP_ERR_INVALID_ARG, TAG, "invalid order argument");
} else {
ESP_RETURN_ON_FALSE(false, ESP_ERR_INVALID_ARG, TAG, "invalid number of color components: %d", component_fmt.format.num_components);
}
uint8_t bytes_per_pixel = component_fmt.format.num_components;
if (component_fmt.format.bytes_per_color > 1) {
bytes_per_pixel *= component_fmt.format.bytes_per_color;
}
uint32_t mem_caps = MALLOC_CAP_DEFAULT;
if (spi_config->flags.with_dma) {
// DMA buffer must be placed in internal SRAM
mem_caps |= MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA;
}
spi_strip = heap_caps_calloc(1, sizeof(led_strip_spi_obj) + led_config->max_leds * bytes_per_pixel * SPI_BYTES_PER_COLOR_BYTE, mem_caps);
ESP_GOTO_ON_FALSE(spi_strip, ESP_ERR_NO_MEM, err, TAG, "no mem for spi strip");
spi_strip->spi_host = spi_config->spi_bus;
// for backward compatibility, if the user does not set the clk_src, use the default value
spi_clock_source_t clk_src = SPI_CLK_SRC_DEFAULT;
if (spi_config->clk_src) {
clk_src = spi_config->clk_src;
}
spi_bus_config_t spi_bus_cfg = {
.mosi_io_num = led_config->strip_gpio_num,
//Only use MOSI to generate the signal, set -1 when other pins are not used.
.miso_io_num = -1,
.sclk_io_num = -1,
.quadwp_io_num = -1,
.quadhd_io_num = -1,
.max_transfer_sz = led_config->max_leds * bytes_per_pixel * SPI_BYTES_PER_COLOR_BYTE,
};
ESP_GOTO_ON_ERROR(spi_bus_initialize(spi_strip->spi_host, &spi_bus_cfg, spi_config->flags.with_dma ? SPI_DMA_CH_AUTO : SPI_DMA_DISABLED), err, TAG, "create SPI bus failed");
if (led_config->flags.invert_out == true) {
esp_rom_gpio_connect_out_signal(led_config->strip_gpio_num, spi_periph_signal[spi_strip->spi_host].spid_out, true, false);
}
spi_device_interface_config_t spi_dev_cfg = {
.clock_source = clk_src,
.command_bits = 0,
.address_bits = 0,
.dummy_bits = 0,
.clock_speed_hz = LED_STRIP_SPI_DEFAULT_RESOLUTION,
.mode = 0,
//set -1 when CS is not used
.spics_io_num = -1,
.queue_size = LED_STRIP_SPI_DEFAULT_TRANS_QUEUE_SIZE,
};
ESP_GOTO_ON_ERROR(spi_bus_add_device(spi_strip->spi_host, &spi_dev_cfg, &spi_strip->spi_device), err, TAG, "Failed to add spi device");
//ensure the reset time is enough
esp_rom_delay_us(10);
int clock_resolution_khz = 0;
spi_device_get_actual_freq(spi_strip->spi_device, &clock_resolution_khz);
// TODO: ideally we should decide the SPI_BYTES_PER_COLOR_BYTE by the real clock resolution
// But now, let's fixed the resolution, the downside is, we don't support a clock source whose frequency is not multiple of LED_STRIP_SPI_DEFAULT_RESOLUTION
// clock_resolution between 2.2MHz to 2.8MHz is supported
ESP_GOTO_ON_FALSE((clock_resolution_khz < LED_STRIP_SPI_DEFAULT_RESOLUTION / 1000 + 300) && (clock_resolution_khz > LED_STRIP_SPI_DEFAULT_RESOLUTION / 1000 - 300), ESP_ERR_NOT_SUPPORTED, err,
TAG, "unsupported clock resolution:%dKHz", clock_resolution_khz);
if (led_config->led_model != LED_MODEL_WS2812) {
ESP_LOGW(TAG, "Only support WS2812. The timing requirements for other models may not be met");
}
spi_strip->component_fmt = component_fmt;
spi_strip->bytes_per_pixel = bytes_per_pixel;
spi_strip->strip_len = led_config->max_leds;
spi_strip->base.set_pixel = led_strip_spi_set_pixel;
spi_strip->base.set_pixel_rgbw = led_strip_spi_set_pixel_rgbw;
spi_strip->base.refresh = led_strip_spi_refresh;
spi_strip->base.clear = led_strip_spi_clear;
spi_strip->base.del = led_strip_spi_del;
*ret_strip = &spi_strip->base;
return ESP_OK;
err:
if (spi_strip) {
if (spi_strip->spi_device) {
spi_bus_remove_device(spi_strip->spi_device);
}
if (spi_strip->spi_host) {
spi_bus_free(spi_strip->spi_host);
}
free(spi_strip);
}
return ret;
}

View File

@@ -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 <esp-device>
```
**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; │
└─────────────────────────────────────────────────────────────────────────┘
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

View File

@@ -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

View File

@@ -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;
```

View File

@@ -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(&params)`
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();
}
};

View File

@@ -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);
}
};

View File

@@ -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;

View File

@@ -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);
}
};

View File

@@ -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;
}

View File

@@ -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));
}

View File

@@ -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)));
}

View File

@@ -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));
}
};

View File

@@ -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));
}
};

View File

@@ -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));
}
};
};

View File

@@ -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));
}

View File

@@ -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));
}

View File

@@ -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;
}

View File

@@ -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));
}

View File

@@ -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)));
}
};

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -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));
}
};

View File

@@ -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);
}
};

View File

@@ -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");

View File

@@ -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)));
}
};

View File

@@ -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);
}

View File

@@ -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 });
}
}

View File

@@ -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));
}

View File

@@ -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));
}
};
};
};

View File

@@ -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);
}

View File

@@ -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));
}
};

View File

@@ -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());
}
};

View File

@@ -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);
}
};
};

View File

@@ -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));
}
};

View File

@@ -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);
}

View File

@@ -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));
}
};
};
};

View File

@@ -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);
}
};

View File

@@ -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, &current));
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");

View File

@@ -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;
}

View File

@@ -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));
}
};

View File

@@ -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));
}

View File

@@ -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,
};
}

View File

@@ -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;
}
};

View File

@@ -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 */

View File

@@ -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 <portmacro.h>

View File

@@ -0,0 +1,30 @@
#pragma once
// CHIP SDK closure-control cluster structs are missing operator==(const T&) required
// by std::optional<T> 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

View File

@@ -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 <esp_err.h>
#include <stdbool.h>
#include <stdint.h>
/* ── 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 */

View File

@@ -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
* <sys/types.h>, <time.h>, and <sched.h> — 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 <stddef.h>
#include <stdint.h>
/* ── 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 */

View File

@@ -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.15.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 <stddef.h> // for size_t, ptrdiff_t, NULL
#include <stdint.h> // for uint8_t, uint32_t, etc.
#include <sys/types.h> // 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

View File

@@ -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

View File

@@ -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 <cmath> for build kalman
if(TARGET __idf_espressif__esp-dsp AND IDF_VERSION_MAJOR EQUAL 6)
target_compile_options(__idf_espressif__esp-dsp PRIVATE
$<$<COMPILE_LANGUAGE:CXX>:-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
$<$<COMPILE_LANGUAGE:CXX>:-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()

View File

@@ -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

Some files were not shown because too many files have changed in this diff Show More