firmware: verify against actual target hardware, fix real conflicts
Researched the exact modules the user is building with and fixed three
concrete issues the earlier speculative firmware got wrong:
1. WiFi: confirmed the target board (Waveshare ESP32-P4-Module-DEV-KIT,
chip ESP32-P4NRW32) bridges WiFi through an onboard ESP32-C6
co-processor over a fixed 7-pin SDIO link (CLK18/CMD19/D0-14/D1-15/
D2-16/D3-17/RESET54, cross-confirmed against Espressif's own
esp-hosted-mcu docs). wifi_manager.c's esp_wifi_init()/esp_wifi_start()
calls don't need to change — esp_wifi_remote/esp_hosted provide a
drop-in-compatible API — but the component manifest (new
main/idf_component.yml) and sdkconfig.defaults were missing entirely.
2. Real pin conflict: the presence sensor's original UART pins (17/18)
directly collided with the SDIO CLK/D3 pins above — wiring it there
would have broken WiFi, the sensor, or both. Moved to GPIO4/5.
3. Swapped placeholder parts for the user's actual hardware:
- BME280 -> BMP280 (GY-BMP280 module): temp+pressure only, no humidity.
Rewrote the driver rather than just renaming it — the old code would
have read nonexistent humidity registers and reported garbage
forever. 3.3V-only wiring note added (the BME280 assumption of
5V-tolerant logic doesn't hold for this specific breakout).
- LD2410 -> RD-03E (Ai-Thinker, not Hi-Link — a different manufacturer
with a different, incompatible UART protocol). Rewrote the frame
parser against the RD-03E's actual (if less-documented) 5-byte
simple-report format. Reports numeric distance instead of a boolean,
which better fits both the hardware's actual output and the backend's
statistical anomaly detector.
README, CMakeLists.txt, and all cross-references updated to match.
This commit is contained in:
@@ -9,13 +9,20 @@ backend's `POST /api/device/telemetry` endpoint, which feeds them into the
|
||||
séance's live anomaly-detection pipeline as a sixth signal source alongside
|
||||
`wire`/`evp`/`radio`/`emf`.
|
||||
|
||||
**Target board:** [Waveshare ESP32-P4-Module-DEV-KIT](https://www.waveshare.com/esp32-p4-module-dev-kit.htm)
|
||||
(chip: **ESP32-P4NRW32**, 16MB NOR flash, onboard **ESP32-C6** Wi-Fi
|
||||
6/Bluetooth 5 co-processor over SDIO, USB OTG 2.0 HS, 40-pin 2×20 header
|
||||
with 28 programmable GPIOs). All pin assignments and the Wi-Fi bring-up
|
||||
approach below are specific to this board — see the Wi-Fi section for the
|
||||
reserved SDIO pins and [Wiring / pinout](#wiring--pinout) for sensor pins.
|
||||
|
||||
## Honesty policy — READ THIS FIRST
|
||||
|
||||
> This app's whole ethos is "real signal processing on real data, and it
|
||||
> says so when something is unverified."
|
||||
|
||||
**Nobody working on this had physical ESP32-P4 hardware, a BME280, or an
|
||||
LD2410 module to flash and test against.** Everything in this directory is
|
||||
**Nobody working on this had physical ESP32-P4 hardware, a BMP280, or an
|
||||
RD-03E module to flash and test against.** Everything in this directory is
|
||||
real, structurally-correct ESP-IDF C, written against ESP-IDF's documented
|
||||
APIs and each sensor's public datasheet/protocol documentation, and reasoned
|
||||
about carefully — but it has **never been compiled with a real ESP-IDF
|
||||
@@ -33,17 +40,21 @@ firmware/esp32p4-sensor-node/
|
||||
├── CMakeLists.txt top-level ESP-IDF project file
|
||||
├── sdkconfig.defaults seed config (idf.py generates the real sdkconfig)
|
||||
├── README.md this file
|
||||
├── components/ reserved for Workstream J (RTL-SDR), empty here
|
||||
├── components/
|
||||
│ ├── README.md
|
||||
│ └── rtlsdr_experimental/ Workstream J — opt-in USB-host RTL-SDR module
|
||||
└── main/
|
||||
├── CMakeLists.txt component registration
|
||||
├── idf_component.yml managed deps: esp_wifi_remote, esp_hosted
|
||||
├── app_main.c entry point / boot sequence
|
||||
├── device_config.h.example template you copy to device_config.h
|
||||
├── wifi_manager.{h,c} Wi-Fi station mode connect/reconnect
|
||||
├── wifi_manager.{h,c} Wi-Fi station mode connect/reconnect (via
|
||||
│ the onboard ESP32-C6 co-processor — see below)
|
||||
├── telemetry_client.{h,c} HTTP POST task -> /api/device/telemetry
|
||||
├── sensor_driver.h the sensor_driver_t registry interface
|
||||
├── sensor_registry.{h,c} the concrete list of compiled-in drivers
|
||||
├── bme280.{h,c} temperature/humidity/pressure over I2C
|
||||
└── ld2410.{h,c} presence/distance over UART
|
||||
├── bmp280.{h,c} temperature/pressure over I2C
|
||||
└── rd03e.{h,c} presence/distance/gesture over UART
|
||||
```
|
||||
|
||||
## Build instructions
|
||||
@@ -108,21 +119,23 @@ consistent; no known syntax errors or obviously-wrong API usage):
|
||||
- HTTP client (`telemetry_client.c`) builds the exact JSON shape the spec's
|
||||
contract defines and POSTs it via `esp_http_client` with
|
||||
`Authorization: Bearer <token>` and `Content-Type: application/json`.
|
||||
- BME280 driver (`bme280.c`): register map and the double-precision
|
||||
compensation formulas are transcribed from Bosch's public BME280
|
||||
datasheet (rev 1.23, §4.2.2–4.2.3) — this is well-trodden, publicly
|
||||
- BMP280 driver (`bmp280.c`): register map and the double-precision
|
||||
compensation formulas are transcribed from Bosch's public BMP280
|
||||
datasheet (rev 1.23, §3.11.1–3.11.3) — this is well-trodden, publicly
|
||||
documented territory, and the formulas are checkable line-by-line against
|
||||
the datasheet. Uses ESP-IDF's newer `driver/i2c_master.h` API (the
|
||||
current idiomatic choice; the older `driver/i2c.h` is being phased out).
|
||||
- LD2410 driver (`ld2410.c`): UART frame envelope (header/footer magic
|
||||
bytes, length-prefixed payload) follows the shape consistently reported
|
||||
across public LD2410 protocol write-ups. **The exact payload byte offsets
|
||||
for target state / distances / energies are the single least-certain
|
||||
piece of code in this entire firmware** — see the detailed note in
|
||||
`ld2410_parse_payload()`. The driver defends itself with a head/tail
|
||||
marker sanity check (`0xAA`/`0x55`) and silently skips anything that
|
||||
doesn't match rather than reporting garbage, but that check catches
|
||||
gross corruption, not subtle off-by-one offset errors.
|
||||
Temperature + pressure only — the part in use (a GY-BMP280 breakout) has
|
||||
no humidity sensor, unlike its BME280 sibling.
|
||||
- RD-03E driver (`rd03e.c`): the 5-byte "simple report" UART frame
|
||||
(`0xAA` header, gesture byte, little-endian distance, `0x55 0x55`
|
||||
footer) is reconstructed from a third-party bring-up write-up, not
|
||||
Ai-Thinker's own datasheet (not available while writing this) — **the
|
||||
single least-certain piece of code in this entire firmware.** The
|
||||
gesture byte's exact value-to-meaning mapping is unconfirmed, so the
|
||||
driver reports it as a raw code in `metadata` rather than guessing at a
|
||||
translated label. Cross-confirmed from multiple sources: 256000 baud,
|
||||
8N1 UART framing.
|
||||
- Sensor driver registry (`sensor_driver.h`, `sensor_registry.c`): a
|
||||
`sensor_driver_t { name, init, read }` struct, a compile-time array of
|
||||
them, and generic init/collect functions that `app_main.c` and
|
||||
@@ -136,15 +149,16 @@ consistent; no known syntax errors or obviously-wrong API usage):
|
||||
version you build with that only a real compile will surface.
|
||||
- I2C timing/electricals: pull-up resistor values, bus speed headroom,
|
||||
cable length — none of this has been bench-tested.
|
||||
- BME280 compensation formula correctness in practice: the math is
|
||||
- BMP280 compensation formula correctness in practice: the math is
|
||||
transcribed carefully, but "matches the datasheet" and "produces a
|
||||
plausible number when this exact C runs on this exact silicon" are
|
||||
different claims until someone compares a real reading to a reference
|
||||
thermometer/barometer.
|
||||
- LD2410 frame parsing, as above — verify against a logic analyzer capture
|
||||
or a known-good reference implementation (e.g. the `ncmreynolds/ld2410`
|
||||
or `iavorvel/MyLD2410` Arduino libraries, cross-checked) before trusting
|
||||
field values.
|
||||
- RD-03E frame parsing, as above — this one especially, since the frame
|
||||
format itself (not just the implementation) is reconstructed from a
|
||||
third-party source rather than an official datasheet. Verify against a
|
||||
logic analyzer capture before trusting field values, and treat the
|
||||
gesture code's meaning as genuinely unknown until cross-checked.
|
||||
- Wi-Fi reconnect behavior under real-world conditions (router reboot,
|
||||
weak signal, captive portals) — the backoff logic is reasoned about, not
|
||||
soak-tested.
|
||||
@@ -154,78 +168,114 @@ consistent; no known syntax errors or obviously-wrong API usage):
|
||||
but that's untested against the live deploy.
|
||||
- Timing/power: task stack sizes (`telemetry_task`'s 8192 words, etc.) are
|
||||
reasonable guesses, not measured high-water-marks from a real run.
|
||||
- **The ESP32-P4-has-no-integrated-Wi-Fi caveat below** — this is a real
|
||||
hardware architecture question, not just an untested detail.
|
||||
- Everything under [Wiring / pinout](#wiring--pinout) and the Wi-Fi section
|
||||
below — confirmed against the actual target board's published specs, but
|
||||
never checked against the physical hardware itself.
|
||||
|
||||
## Important hardware caveat: ESP32-P4 has no integrated Wi-Fi radio
|
||||
## Wi-Fi: ESP32-P4 has no integrated radio — confirmed target hardware
|
||||
|
||||
The ESP32-P4 SoC (per Espressif's own published specs) has **no built-in
|
||||
2.4GHz radio**. A real deployment needs one of:
|
||||
The ESP32-P4 SoC has **no built-in 2.4GHz radio**. This was flagged
|
||||
speculatively in an earlier pass; it's now confirmed against the actual
|
||||
target hardware — **Waveshare ESP32-P4-Module-DEV-KIT**, chip
|
||||
**ESP32-P4NRW32** — which solves this the way Espressif's own reference
|
||||
design does: an onboard **ESP32-C6** co-processor, wired to the P4 over a
|
||||
**fixed 7-pin SDIO link**, running Wi-Fi 6 + Bluetooth 5 on the seeker's
|
||||
behalf. These 7 pins are physically fixed by the board's own PCB routing —
|
||||
**do not reuse them for sensors**:
|
||||
|
||||
- **A companion Wi-Fi chip** (e.g. ESP32-C6) wired to the P4 via SDIO or
|
||||
SPI, running Espressif's "esp-hosted" firmware/driver stack. Critically,
|
||||
esp-hosted presents the *same* `esp_wifi`/`esp_netif` API this firmware
|
||||
already uses — so `wifi_manager.c` should not need to change, only board
|
||||
wiring and `sdkconfig` (host-side esp-hosted config) would.
|
||||
- **Building this same code against a Wi-Fi-native target instead**, e.g.
|
||||
`idf.py set-target esp32s3` or `esp32c6`. The application code
|
||||
(`wifi_manager.c`, `telemetry_client.c`, the sensor drivers) is written
|
||||
against the standard API surface and doesn't reference P4-specific
|
||||
peripherals for anything except I2C/UART GPIO numbers, so it should be
|
||||
largely target-portable.
|
||||
| Signal | GPIO |
|
||||
|---|---|
|
||||
| SDIO CLK | 18 |
|
||||
| SDIO CMD | 19 |
|
||||
| SDIO D0 | 14 |
|
||||
| SDIO D1 | 15 |
|
||||
| SDIO D2 | 16 |
|
||||
| SDIO D3 | 17 |
|
||||
| C6 Reset | 54 |
|
||||
|
||||
This wasn't in the original spec's framing but matters enough for a real
|
||||
build that it's called out here explicitly, in the honesty-policy spirit —
|
||||
better to flag a real hardware-architecture gap than let someone discover
|
||||
it after ordering a bare P4 dev board expecting it to just join Wi-Fi.
|
||||
(Sourced from Waveshare's own documentation and independently cross-checked
|
||||
against Espressif's `esp-hosted-mcu` reference docs for their nearly
|
||||
identical ESP32-P4-Function-EV-Board, which uses the same 7 pins — strong
|
||||
agreement across two independent sources, though still not a substitute for
|
||||
checking the physical board's silkscreen.)
|
||||
|
||||
Wi-Fi is brought up via two managed components — `espressif/esp_wifi_remote`
|
||||
and `espressif/esp_hosted` — declared in `main/idf_component.yml` and
|
||||
configured in `sdkconfig.defaults` (`CONFIG_SLAVE_IDF_TARGET_ESP32C6`,
|
||||
`CONFIG_ESP_HOSTED_CP_TARGET_ESP32C6`, `CONFIG_ESP_HOSTED_P4_DEV_BOARD_FUNC_BOARD`,
|
||||
plus buffer/window tuning). Critically, these components provide a
|
||||
**drop-in-compatible** `esp_wifi_*`/`esp_netif_*` API — `wifi_manager.c`'s
|
||||
`esp_wifi_init()`/`esp_wifi_start()` calls are exactly what they'd be on a
|
||||
Wi-Fi-native chip; only the component manifest and sdkconfig differ, not
|
||||
the application code. `idf.py build` will need network access the first
|
||||
time to fetch these two components from the ESP Component Registry.
|
||||
|
||||
**What's still unverified here specifically:** whether
|
||||
`CONFIG_ESP_HOSTED_P4_DEV_BOARD_FUNC_BOARD` (a preset named for Espressif's
|
||||
own eval board) configures correctly for Waveshare's board given they
|
||||
share the same SDIO pin assignment — likely fine, but the first thing to
|
||||
check in `idf.py menuconfig` if Wi-Fi bring-up misbehaves is the Wi-Fi
|
||||
Remote / ESP-Hosted config screens directly rather than trusting the preset
|
||||
blindly.
|
||||
|
||||
## Wiring / pinout
|
||||
|
||||
### BME280 (I2C) — temperature, humidity, pressure
|
||||
Confirmed against the target board (Waveshare ESP32-P4-Module-DEV-KIT):
|
||||
GPIOs 14-19 and 54 are reserved for the onboard Wi-Fi co-processor's SDIO
|
||||
link (see the Wi-Fi section above) — never wire a sensor to those pins on
|
||||
this board.
|
||||
|
||||
Chosen as the concrete default sensor per the spec ("a common,
|
||||
well-documented sensor... pick this as the concrete default since no
|
||||
specific part number was given").
|
||||
### BMP280 (I2C) — temperature, pressure
|
||||
|
||||
| BME280 pin | Connects to |
|
||||
Confirmed against the actual part in use: a **GY-BMP280** breakout module
|
||||
(Bosch BMP280 — temperature + pressure only, no humidity). GPIO8/9 don't
|
||||
conflict with the board's reserved SDIO range. **3.3V only** — the
|
||||
GY-BMP280 module is not 5V tolerant, unlike the RD-03E below.
|
||||
|
||||
| BMP280 pin | Connects to |
|
||||
|------------|---------------------------------------|
|
||||
| VCC | 3V3 |
|
||||
| VCC | 3V3 (3.3V ONLY — do not connect to 5V)|
|
||||
| GND | GND |
|
||||
| SDA | GPIO8 (`BME280_I2C_SDA_GPIO`) |
|
||||
| SCL | GPIO9 (`BME280_I2C_SCL_GPIO`) |
|
||||
| SDA | GPIO8 (`BMP280_I2C_SDA_GPIO`) |
|
||||
| SCL | GPIO9 (`BMP280_I2C_SCL_GPIO`) |
|
||||
| CSB | VCC (selects I2C mode, not SPI) |
|
||||
| SDO | GND → I2C address `0x76` (default assumed; tie to VCC + change `BME280_I2C_ADDR` for `0x77`) |
|
||||
| SDO | GND → I2C address `0x76` (default assumed; tie to VCC + change `BMP280_I2C_ADDR` for `0x77`) |
|
||||
|
||||
GPIO numbers are `#define`s at the top of `bme280.h` — override them there
|
||||
GPIO numbers are `#define`s at the top of `bmp280.h` — override them there
|
||||
(or via a future `idf.py menuconfig` entry) to match your actual wiring.
|
||||
100kHz I2C clock by default (`BME280_I2C_CLK_HZ`); the part supports faster
|
||||
100kHz I2C clock by default (`BMP280_I2C_CLK_HZ`); the part supports faster
|
||||
modes if your wiring/pull-ups support it.
|
||||
|
||||
### LD2410 (UART) — presence, distance, motion
|
||||
### RD-03E (UART) — presence, distance, gesture
|
||||
|
||||
**Chosen over a plain PIR** — see the rationale in `ld2410.h`'s header
|
||||
comment: the LD2410 reports moving-target and stationary-target distance
|
||||
and energy separately, not just a boolean, which is richer signal for the
|
||||
anomaly pipeline and better matches this app's "believable" ethos (it can
|
||||
distinguish "someone crossed the room" from "the sitter shifted in their
|
||||
chair" in a way a boolean PIR cannot). The tradeoff is a materially more
|
||||
complex protocol than a PIR's single GPIO pin — see the honesty note in
|
||||
[What's verified vs. not](#whats-verified-vs-not) about the LD2410 frame
|
||||
parser being the least-certain code in this firmware. If you'd rather start
|
||||
with a boolean PIR for a faster, more certain first bring-up, it fits the
|
||||
same `sensor_driver_t` interface — see
|
||||
[Adding a new sensor](#adding-a-new-sensor) below.
|
||||
Confirmed against the actual part in use: an **Ai-Thinker RD-03E** 24GHz
|
||||
mmWave radar ("Human Gesture Recognition... Precise Ranging & Positioning
|
||||
Radar Sensor Module"). Originally speculatively planned as a Hi-Link
|
||||
LD2410 — a **different manufacturer with a different, incompatible UART
|
||||
protocol** — so this driver was rewritten from scratch against the RD-03E's
|
||||
actual (if less-documented) frame format rather than adapted from the
|
||||
LD2410 code. Reports ranged distance (not just a boolean), fitting a
|
||||
handheld "sense a presence at a distance" device well — see the honesty
|
||||
note in [What's verified vs. not](#whats-verified-vs-not) about the frame
|
||||
parser being the single least-certain code in this firmware.
|
||||
|
||||
| LD2410 pin | Connects to |
|
||||
| RD-03E pin | Connects to |
|
||||
|------------|----------------------------------------|
|
||||
| VCC | 5V (sensor front-end runs at 5V; confirm your board revision's UART logic level before wiring directly to a 3.3V-only UART pin) |
|
||||
| VCC | 5V (module power; UART logic is 0-3.3V, ESP32-safe) |
|
||||
| GND | GND |
|
||||
| TX | GPIO17 (`LD2410_UART_RX_GPIO`, ESP32 RX) |
|
||||
| RX | GPIO18 (`LD2410_UART_TX_GPIO`, ESP32 TX) |
|
||||
| OT1 | GPIO4 (`RD03E_UART_RX_GPIO`, ESP32 RX) — module's UART TX output |
|
||||
| RX | GPIO5 (`RD03E_UART_TX_GPIO`, ESP32 TX) — module's UART RX input |
|
||||
| OT2 | not connected (reserved on the module, unused here) |
|
||||
|
||||
Default UART settings: 256000 baud, 8N1 (module factory default), reporting
|
||||
in "basic" (non-engineering) mode. GPIO numbers and baud rate are
|
||||
`#define`s at the top of `ld2410.h`.
|
||||
(GPIO4/5 avoid this board's reserved SDIO pins and the BMP280's I2C pins —
|
||||
a reasonable, currently-unused pick, not verified against the physical
|
||||
board's full pin map beyond confirming it's outside those known-reserved
|
||||
ranges.)
|
||||
|
||||
Default UART settings: 256000 baud, 8N1 (module factory default), reading
|
||||
only the module's free-running "simple report" frames — no configuration
|
||||
handshake is sent or required. GPIO numbers and baud rate are `#define`s
|
||||
at the top of `rd03e.h`.
|
||||
|
||||
## Sensor driver registry — the extensibility pattern
|
||||
|
||||
@@ -246,10 +296,10 @@ typedef struct sensor_driver {
|
||||
} sensor_driver_t;
|
||||
```
|
||||
|
||||
`sensor_registry.c` holds a compile-time array of these (currently BME280
|
||||
and LD2410) and two generic functions, `sensor_registry_init_all()` and
|
||||
`sensor_registry.c` holds a compile-time array of these (currently BMP280
|
||||
and RD-03E) and two generic functions, `sensor_registry_init_all()` and
|
||||
`sensor_registry_collect()`, that `app_main.c` and `telemetry_client.c`
|
||||
call without ever referencing `bme280.c`/`ld2410.c` directly. One driver
|
||||
call without ever referencing `bmp280.c`/`rd03e.c` directly. One driver
|
||||
failing `init()` or `read()` is logged and skipped — it doesn't take the
|
||||
whole node offline.
|
||||
|
||||
@@ -287,11 +337,12 @@ Content-Type: application/json
|
||||
}
|
||||
```
|
||||
|
||||
This firmware's BME280 driver emits `temperature`/`humidity`/`pressure`
|
||||
exactly as shown; its LD2410 driver emits `presence` as a `0`/`1` boolean
|
||||
in `value` with the richer distance/energy data folded into `metadata`
|
||||
(`moving_distance_cm`, `moving_energy`, `stationary_distance_cm`,
|
||||
`stationary_energy`, `detection_distance_cm`, `target_state`).
|
||||
This firmware's BMP280 driver emits `temperature`/`pressure` (no
|
||||
`humidity` — see above); its RD-03E driver emits `presence` as a numeric
|
||||
distance in centimeters (`unit: "cm"`, not a `0`/`1` boolean) so the
|
||||
backend's statistical anomaly detector can treat "something got suddenly
|
||||
close" as the signal, with the module's raw gesture code folded into
|
||||
`metadata` (`gesture_code`) rather than a boolean transition detector.
|
||||
|
||||
## Out of scope here
|
||||
|
||||
@@ -520,7 +571,7 @@ likely to break first":
|
||||
here for whoever does the merge.
|
||||
9. **Memory footprint.** Multiple in-flight 16KB bulk transfer buffers
|
||||
plus a forward queue plus `esp_http_client` buffers, alongside whatever
|
||||
Workstream I's WiFi/HTTP/BME280/presence stack already needs, on a
|
||||
Workstream I's WiFi/HTTP/BMP280/RD-03E stack already needs, on a
|
||||
single ESP32-P4's RAM — not sized or measured against a real linker
|
||||
map.
|
||||
|
||||
|
||||
@@ -12,8 +12,8 @@ idf_component_register(
|
||||
"wifi_manager.c"
|
||||
"telemetry_client.c"
|
||||
"sensor_registry.c"
|
||||
"bme280.c"
|
||||
"ld2410.c"
|
||||
"bmp280.c"
|
||||
"rd03e.c"
|
||||
INCLUDE_DIRS
|
||||
"."
|
||||
REQUIRES
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Quantumancy ESP32-P4 Sensor Node — entry point.
|
||||
//
|
||||
// UNVERIFIED AGAINST REAL HARDWARE. Nobody working on this workstream has a
|
||||
// physical ESP32-P4 (or the BME280 / LD2410 modules) to flash and test
|
||||
// physical ESP32-P4 (or the BMP280 / RD-03E modules) to flash and test
|
||||
// against. This firmware is real, structurally-sound ESP-IDF C, reasoned
|
||||
// about carefully against ESP-IDF's documented APIs and the public
|
||||
// datasheets/protocol docs for each sensor -- but "compiles and reads
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
// Bosch BME280 driver (temperature / humidity / pressure) over I2C.
|
||||
//
|
||||
// Implements the sensor_driver_t interface (see sensor_driver.h) so the
|
||||
// registry can init/read it generically. Register map and compensation
|
||||
// formulas are transcribed from Bosch's public BME280 datasheet (Rev 1.23,
|
||||
// section 4.2.3 "Compensation formulas", double-precision reference
|
||||
// implementation) — publicly documented, well-trodden territory, not
|
||||
// guesswork. What IS unverified: this has never talked to a real BME280 —
|
||||
// see README.md's honesty-policy section.
|
||||
//
|
||||
// Default wiring (override via `idf.py menuconfig` or by editing the pin
|
||||
// macros below before building) — see README.md's wiring section for the
|
||||
// full pinout table:
|
||||
// BME280 SDA -> GPIO8 (BME280_I2C_SDA_GPIO)
|
||||
// BME280 SCL -> GPIO9 (BME280_I2C_SCL_GPIO)
|
||||
// BME280 VCC -> 3V3, GND -> GND
|
||||
// BME280 CSB -> VCC (selects I2C mode, not SPI)
|
||||
// BME280 SDO -> GND selects address 0x76 (default assumed here); tie SDO
|
||||
// to VCC instead for address 0x77 and change
|
||||
// BME280_I2C_ADDR accordingly.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "esp_err.h"
|
||||
#include "sensor_driver.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#ifndef BME280_I2C_PORT
|
||||
#define BME280_I2C_PORT 0
|
||||
#endif
|
||||
|
||||
#ifndef BME280_I2C_SDA_GPIO
|
||||
#define BME280_I2C_SDA_GPIO 8
|
||||
#endif
|
||||
|
||||
#ifndef BME280_I2C_SCL_GPIO
|
||||
#define BME280_I2C_SCL_GPIO 9
|
||||
#endif
|
||||
|
||||
#ifndef BME280_I2C_ADDR
|
||||
#define BME280_I2C_ADDR 0x76 // 0x77 if SDO is tied to VCC instead of GND
|
||||
#endif
|
||||
|
||||
#ifndef BME280_I2C_CLK_HZ
|
||||
#define BME280_I2C_CLK_HZ 100000 // 100kHz standard mode; BME280 supports up to 3.4MHz
|
||||
#endif
|
||||
|
||||
// sensor_driver_t-compatible entry points.
|
||||
esp_err_t bme280_init(void);
|
||||
esp_err_t bme280_read(sensor_reading_t *out, size_t max_out, size_t *out_count);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -1,40 +1,42 @@
|
||||
// Bosch BME280 driver — see bme280.h for wiring and honesty notes.
|
||||
// Bosch BMP280 driver — see bmp280.h for wiring and honesty notes.
|
||||
//
|
||||
// UNVERIFIED AGAINST REAL HARDWARE: this has been written against the public
|
||||
// BME280 datasheet (Bosch Sensortec, document rev 1.23) and ESP-IDF's
|
||||
// BMP280 datasheet (Bosch Sensortec, document rev 1.23) and ESP-IDF's
|
||||
// documented `driver/i2c_master.h` API surface, and reasoned about carefully,
|
||||
// but never compiled with a real ESP-IDF toolchain nor run against a real
|
||||
// sensor. Register addresses, the calibration-word packing, and the
|
||||
// compensation formulas below are transcribed as directly as possible from
|
||||
// the datasheet's section 4.2.2 (register map) and 4.2.3 (double-precision
|
||||
// compensation formula reference implementation) to minimize transcription
|
||||
// risk, but a real bring-up should sanity-check first readings against a
|
||||
// known-good reference (e.g. compare to a household thermometer/barometer).
|
||||
// sensor. Register addresses and the compensation formula below are
|
||||
// transcribed as directly as possible from the datasheet's section 3.11.1
|
||||
// (register map) and 3.11.3 (double-precision compensation formula
|
||||
// reference implementation) to minimize transcription risk, but a real
|
||||
// bring-up should sanity-check first readings against a known-good
|
||||
// reference (e.g. compare to a household thermometer/barometer).
|
||||
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include <math.h>
|
||||
#include "bme280.h"
|
||||
#include "bmp280.h"
|
||||
#include "driver/i2c_master.h"
|
||||
#include "esp_log.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
static const char *TAG = "bme280";
|
||||
static const char *TAG = "bmp280";
|
||||
|
||||
// --- Register map (BME280 datasheet section 4.2.2) -------------------------
|
||||
// --- Register map (BMP280 datasheet section 3.11.1) ------------------------
|
||||
#define REG_CHIP_ID 0xD0
|
||||
#define REG_RESET 0xE0
|
||||
#define REG_CTRL_HUM 0xF2
|
||||
#define REG_STATUS 0xF3
|
||||
#define REG_CTRL_MEAS 0xF4
|
||||
#define REG_CONFIG 0xF5
|
||||
#define REG_PRESS_MSB 0xF7 // press(3) + temp(3) + hum(2) = 8 bytes, burst-read from here
|
||||
#define REG_CALIB00 0x88 // dig_T1..dig_P9, 26 bytes: 0x88-0xA1
|
||||
#define REG_CALIB_H1 0xA1 // dig_H1, 1 byte
|
||||
#define REG_CALIB26 0xE1 // dig_H2..dig_H6, 7 bytes: 0xE1-0xE7
|
||||
#define REG_PRESS_MSB 0xF7 // press(3) + temp(3) = 6 bytes, burst-read from here
|
||||
#define REG_CALIB00 0x88 // dig_T1..dig_P9, 24 bytes: 0x88-0x9F
|
||||
|
||||
#define CHIP_ID_EXPECTED 0x58 // BMP280 (vs. BME280's 0x60 — a chip-ID
|
||||
// mismatch here likely means a BME280 is
|
||||
// wired instead; we proceed anyway since the
|
||||
// temp/pressure register map and compensation
|
||||
// math is identical between the two parts)
|
||||
|
||||
#define CHIP_ID_EXPECTED 0x60
|
||||
#define RESET_MAGIC 0xB6
|
||||
|
||||
#define STATUS_MEASURING_BIT 0x08
|
||||
@@ -52,17 +54,11 @@ typedef struct {
|
||||
int16_t dig_P7;
|
||||
int16_t dig_P8;
|
||||
int16_t dig_P9;
|
||||
uint8_t dig_H1;
|
||||
int16_t dig_H2;
|
||||
uint8_t dig_H3;
|
||||
int16_t dig_H4;
|
||||
int16_t dig_H5;
|
||||
int8_t dig_H6;
|
||||
} bme280_calib_t;
|
||||
} bmp280_calib_t;
|
||||
|
||||
static i2c_master_bus_handle_t s_bus = NULL;
|
||||
static i2c_master_dev_handle_t s_dev = NULL;
|
||||
static bme280_calib_t s_calib;
|
||||
static bmp280_calib_t s_calib;
|
||||
static bool s_ready = false;
|
||||
|
||||
static esp_err_t write_reg(uint8_t reg, uint8_t val) {
|
||||
@@ -82,60 +78,32 @@ static uint16_t u16(uint8_t lsb, uint8_t msb) {
|
||||
}
|
||||
|
||||
static esp_err_t read_calibration(void) {
|
||||
uint8_t buf1[26]; // 0x88..0xA1
|
||||
uint8_t h1;
|
||||
uint8_t buf2[7]; // 0xE1..0xE7
|
||||
uint8_t buf[24]; // 0x88..0x9F
|
||||
|
||||
esp_err_t err = read_regs(REG_CALIB00, buf1, sizeof(buf1));
|
||||
if (err != ESP_OK) return err;
|
||||
err = read_regs(REG_CALIB_H1, &h1, 1);
|
||||
if (err != ESP_OK) return err;
|
||||
err = read_regs(REG_CALIB26, buf2, sizeof(buf2));
|
||||
esp_err_t err = read_regs(REG_CALIB00, buf, sizeof(buf));
|
||||
if (err != ESP_OK) return err;
|
||||
|
||||
s_calib.dig_T1 = u16(buf1[0], buf1[1]);
|
||||
s_calib.dig_T2 = s16(buf1[2], buf1[3]);
|
||||
s_calib.dig_T3 = s16(buf1[4], buf1[5]);
|
||||
s_calib.dig_P1 = u16(buf1[6], buf1[7]);
|
||||
s_calib.dig_P2 = s16(buf1[8], buf1[9]);
|
||||
s_calib.dig_P3 = s16(buf1[10], buf1[11]);
|
||||
s_calib.dig_P4 = s16(buf1[12], buf1[13]);
|
||||
s_calib.dig_P5 = s16(buf1[14], buf1[15]);
|
||||
s_calib.dig_P6 = s16(buf1[16], buf1[17]);
|
||||
s_calib.dig_P7 = s16(buf1[18], buf1[19]);
|
||||
s_calib.dig_P8 = s16(buf1[20], buf1[21]);
|
||||
s_calib.dig_P9 = s16(buf1[22], buf1[23]);
|
||||
// buf1[24] is reserved (0xA0), buf1[25] would be dig_H1 duplicate on some
|
||||
// parts — we read dig_H1 explicitly from 0xA1 above instead of relying
|
||||
// on that, to match the datasheet's documented address exactly.
|
||||
s_calib.dig_H1 = h1;
|
||||
|
||||
// dig_H4/dig_H5 have an odd 12-bit packing across 3 bytes (datasheet
|
||||
// 4.2.2, table 16):
|
||||
// dig_H4 = (E4[11:4] << 4) | E5[3:0]
|
||||
// dig_H5 = (E6[11:4] << 4) | (E5[7:4])
|
||||
uint8_t e1 = buf2[0]; // 0xE1 -> dig_H2 lsb
|
||||
uint8_t e2 = buf2[1]; // 0xE2 -> dig_H2 msb
|
||||
uint8_t e3 = buf2[2]; // 0xE3 -> dig_H3
|
||||
uint8_t e4 = buf2[3]; // 0xE4
|
||||
uint8_t e5 = buf2[4]; // 0xE5
|
||||
uint8_t e6 = buf2[5]; // 0xE6
|
||||
uint8_t e7 = buf2[6]; // 0xE7 -> dig_H6
|
||||
|
||||
s_calib.dig_H2 = s16(e1, e2);
|
||||
s_calib.dig_H3 = e3;
|
||||
s_calib.dig_H4 = (int16_t)(((int8_t)e4 << 4) | (e5 & 0x0F));
|
||||
s_calib.dig_H5 = (int16_t)(((int8_t)e6 << 4) | (e5 >> 4));
|
||||
s_calib.dig_H6 = (int8_t)e7;
|
||||
s_calib.dig_T1 = u16(buf[0], buf[1]);
|
||||
s_calib.dig_T2 = s16(buf[2], buf[3]);
|
||||
s_calib.dig_T3 = s16(buf[4], buf[5]);
|
||||
s_calib.dig_P1 = u16(buf[6], buf[7]);
|
||||
s_calib.dig_P2 = s16(buf[8], buf[9]);
|
||||
s_calib.dig_P3 = s16(buf[10], buf[11]);
|
||||
s_calib.dig_P4 = s16(buf[12], buf[13]);
|
||||
s_calib.dig_P5 = s16(buf[14], buf[15]);
|
||||
s_calib.dig_P6 = s16(buf[16], buf[17]);
|
||||
s_calib.dig_P7 = s16(buf[18], buf[19]);
|
||||
s_calib.dig_P8 = s16(buf[20], buf[21]);
|
||||
s_calib.dig_P9 = s16(buf[22], buf[23]);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t bme280_init(void) {
|
||||
esp_err_t bmp280_init(void) {
|
||||
i2c_master_bus_config_t bus_cfg = {
|
||||
.i2c_port = BME280_I2C_PORT,
|
||||
.sda_io_num = BME280_I2C_SDA_GPIO,
|
||||
.scl_io_num = BME280_I2C_SCL_GPIO,
|
||||
.i2c_port = BMP280_I2C_PORT,
|
||||
.sda_io_num = BMP280_I2C_SDA_GPIO,
|
||||
.scl_io_num = BMP280_I2C_SCL_GPIO,
|
||||
.clk_source = I2C_CLK_SRC_DEFAULT,
|
||||
.glitch_ignore_cnt = 7,
|
||||
.flags.enable_internal_pullup = true,
|
||||
@@ -148,8 +116,8 @@ esp_err_t bme280_init(void) {
|
||||
|
||||
i2c_device_config_t dev_cfg = {
|
||||
.dev_addr_length = I2C_ADDR_BIT_LEN_7,
|
||||
.device_address = BME280_I2C_ADDR,
|
||||
.scl_speed_hz = BME280_I2C_CLK_HZ,
|
||||
.device_address = BMP280_I2C_ADDR,
|
||||
.scl_speed_hz = BMP280_I2C_CLK_HZ,
|
||||
};
|
||||
err = i2c_master_bus_add_device(s_bus, &dev_cfg, &s_dev);
|
||||
if (err != ESP_OK) {
|
||||
@@ -164,10 +132,10 @@ esp_err_t bme280_init(void) {
|
||||
return err;
|
||||
}
|
||||
if (chip_id != CHIP_ID_EXPECTED) {
|
||||
ESP_LOGW(TAG, "unexpected chip id 0x%02x (want 0x%02x) -- wrong wiring/address, or a BMP280 (no humidity)?", chip_id, CHIP_ID_EXPECTED);
|
||||
// Don't hard-fail: a BMP280 (temp+pressure only, same register map
|
||||
// minus humidity) would also land here and can still usefully report
|
||||
// two of the three readings. We proceed and let real data speak.
|
||||
ESP_LOGW(TAG, "unexpected chip id 0x%02x (want 0x%02x) -- check wiring/address", chip_id, CHIP_ID_EXPECTED);
|
||||
// Don't hard-fail: temp+pressure register map/compensation is
|
||||
// identical on a BME280 too, so a mis-wired-but-present sensor of
|
||||
// either part can still usefully report both readings.
|
||||
}
|
||||
|
||||
err = write_reg(REG_RESET, RESET_MAGIC);
|
||||
@@ -180,17 +148,12 @@ esp_err_t bme280_init(void) {
|
||||
return err;
|
||||
}
|
||||
|
||||
// Humidity oversampling x1. Must be written before ctrl_meas for the
|
||||
// change to take effect (datasheet 5.4.3).
|
||||
err = write_reg(REG_CTRL_HUM, 0x01);
|
||||
if (err != ESP_OK) return err;
|
||||
|
||||
s_ready = true;
|
||||
ESP_LOGI(TAG, "BME280 init ok (chip id 0x%02x)", chip_id);
|
||||
ESP_LOGI(TAG, "BMP280 init ok (chip id 0x%02x)", chip_id);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
// Bosch datasheet 4.2.3 double-precision reference compensation formulas,
|
||||
// Bosch datasheet 3.11.3 double-precision reference compensation formulas,
|
||||
// transcribed near-verbatim (variable names kept close to the original so
|
||||
// it's checkable against the datasheet PDF side-by-side).
|
||||
|
||||
@@ -220,23 +183,12 @@ static double compensate_pressure(int32_t adc_P, double t_fine) {
|
||||
return p; // Pa
|
||||
}
|
||||
|
||||
static double compensate_humidity(int32_t adc_H, double t_fine) {
|
||||
double var_h = (t_fine - 76800.0);
|
||||
var_h = (adc_H - (((double)s_calib.dig_H4) * 64.0 + ((double)s_calib.dig_H5) / 16384.0 * var_h)) *
|
||||
(((double)s_calib.dig_H2) / 65536.0 * (1.0 + ((double)s_calib.dig_H6) / 67108864.0 * var_h *
|
||||
(1.0 + ((double)s_calib.dig_H3) / 67108864.0 * var_h)));
|
||||
var_h = var_h * (1.0 - ((double)s_calib.dig_H1) * var_h / 524288.0);
|
||||
if (var_h > 100.0) var_h = 100.0;
|
||||
if (var_h < 0.0) var_h = 0.0;
|
||||
return var_h; // %RH
|
||||
}
|
||||
|
||||
esp_err_t bme280_read(sensor_reading_t *out, size_t max_out, size_t *out_count) {
|
||||
esp_err_t bmp280_read(sensor_reading_t *out, size_t max_out, size_t *out_count) {
|
||||
*out_count = 0;
|
||||
if (!s_ready) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
if (max_out < 3) {
|
||||
if (max_out < 2) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
@@ -261,18 +213,16 @@ esp_err_t bme280_read(sensor_reading_t *out, size_t max_out, size_t *out_count)
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t raw[8];
|
||||
uint8_t raw[6];
|
||||
err = read_regs(REG_PRESS_MSB, raw, sizeof(raw));
|
||||
if (err != ESP_OK) return err;
|
||||
|
||||
int32_t adc_P = ((int32_t)raw[0] << 12) | ((int32_t)raw[1] << 4) | (raw[2] >> 4);
|
||||
int32_t adc_T = ((int32_t)raw[3] << 12) | ((int32_t)raw[4] << 4) | (raw[5] >> 4);
|
||||
int32_t adc_H = ((int32_t)raw[6] << 8) | raw[7];
|
||||
|
||||
double t_fine = 0.0;
|
||||
double temp_c = compensate_temperature(adc_T, &t_fine);
|
||||
double press_pa = compensate_pressure(adc_P, t_fine);
|
||||
double hum_pct = compensate_humidity(adc_H, t_fine);
|
||||
|
||||
size_t n = 0;
|
||||
memset(&out[n], 0, sizeof(out[n]));
|
||||
@@ -282,13 +232,6 @@ esp_err_t bme280_read(sensor_reading_t *out, size_t max_out, size_t *out_count)
|
||||
out[n].metadata = NULL;
|
||||
n++;
|
||||
|
||||
memset(&out[n], 0, sizeof(out[n]));
|
||||
strncpy(out[n].sensor_type, "humidity", SENSOR_READING_TYPE_MAXLEN - 1);
|
||||
out[n].value = hum_pct;
|
||||
strncpy(out[n].unit, "pct", SENSOR_READING_UNIT_MAXLEN - 1);
|
||||
out[n].metadata = NULL;
|
||||
n++;
|
||||
|
||||
memset(&out[n], 0, sizeof(out[n]));
|
||||
strncpy(out[n].sensor_type, "pressure", SENSOR_READING_TYPE_MAXLEN - 1);
|
||||
out[n].value = press_pa / 100.0; // Pa -> hPa
|
||||
63
firmware/esp32p4-sensor-node/main/bmp280.h
Normal file
63
firmware/esp32p4-sensor-node/main/bmp280.h
Normal file
@@ -0,0 +1,63 @@
|
||||
// Bosch BMP280 driver (temperature / pressure only) over I2C.
|
||||
//
|
||||
// Implements the sensor_driver_t interface (see sensor_driver.h) so the
|
||||
// registry can init/read it generically. Register map and compensation
|
||||
// formulas are transcribed from Bosch's public BMP280 datasheet (Rev 1.23,
|
||||
// section 3.11.3 "Compensation formulas", double-precision reference
|
||||
// implementation) — publicly documented, well-trodden territory, not
|
||||
// guesswork. What IS unverified: this has never talked to a real BMP280 —
|
||||
// see README.md's honesty-policy section.
|
||||
//
|
||||
// Confirmed against the actual part in use: a GY-BMP280 breakout module
|
||||
// (Bosch BMP280 — temperature + pressure ONLY, no humidity; do not confuse
|
||||
// with the BME280, which adds a humidity sensor on an otherwise near-
|
||||
// identical register map). 3.3V logic only — the GY-BMP280 module is not
|
||||
// 5V tolerant, unlike the LD2410-family modules elsewhere in this project.
|
||||
//
|
||||
// Default wiring (override via `idf.py menuconfig` or by editing the pin
|
||||
// macros below before building) — see README.md's wiring section for the
|
||||
// full pinout table:
|
||||
// BMP280 SDA -> GPIO8 (BMP280_I2C_SDA_GPIO)
|
||||
// BMP280 SCL -> GPIO9 (BMP280_I2C_SCL_GPIO)
|
||||
// BMP280 VCC -> 3V3, GND -> GND (3.3V ONLY — see above)
|
||||
// BMP280 CSB -> VCC (selects I2C mode, not SPI)
|
||||
// BMP280 SDO -> GND selects address 0x76 (default assumed here); tie SDO
|
||||
// to VCC instead for address 0x77 and change
|
||||
// BMP280_I2C_ADDR accordingly.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "esp_err.h"
|
||||
#include "sensor_driver.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#ifndef BMP280_I2C_PORT
|
||||
#define BMP280_I2C_PORT 0
|
||||
#endif
|
||||
|
||||
#ifndef BMP280_I2C_SDA_GPIO
|
||||
#define BMP280_I2C_SDA_GPIO 8
|
||||
#endif
|
||||
|
||||
#ifndef BMP280_I2C_SCL_GPIO
|
||||
#define BMP280_I2C_SCL_GPIO 9
|
||||
#endif
|
||||
|
||||
#ifndef BMP280_I2C_ADDR
|
||||
#define BMP280_I2C_ADDR 0x76 // 0x77 if SDO is tied to VCC instead of GND
|
||||
#endif
|
||||
|
||||
#ifndef BMP280_I2C_CLK_HZ
|
||||
#define BMP280_I2C_CLK_HZ 100000 // 100kHz standard mode; BMP280 supports up to 3.4MHz
|
||||
#endif
|
||||
|
||||
// sensor_driver_t-compatible entry points.
|
||||
esp_err_t bmp280_init(void);
|
||||
esp_err_t bmp280_read(sensor_reading_t *out, size_t max_out, size_t *out_count);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
14
firmware/esp32p4-sensor-node/main/idf_component.yml
Normal file
14
firmware/esp32p4-sensor-node/main/idf_component.yml
Normal file
@@ -0,0 +1,14 @@
|
||||
# Managed component dependencies for the Quantumancy sensor node.
|
||||
#
|
||||
# esp_wifi_remote + esp_hosted: the ESP32-P4 has NO integrated Wi-Fi/BT
|
||||
# radio (confirmed against the target hardware — Waveshare
|
||||
# ESP32-P4-Module-DEV-KIT, chip ESP32-P4NRW32 — which pairs it with an
|
||||
# onboard ESP32-C6 co-processor reachable over SDIO). esp_wifi_remote
|
||||
# provides a drop-in-compatible esp_wifi_* API that transparently proxies
|
||||
# to the C6 over that SDIO link via esp_hosted's RPC layer — wifi_manager.c
|
||||
# calls the normal esp_wifi_init()/esp_wifi_start() and does not need to
|
||||
# know the radio isn't local. See README.md's Wi-Fi section for the SDIO
|
||||
# pin assignments this depends on and why they're reserved from sensor use.
|
||||
dependencies:
|
||||
espressif/esp_wifi_remote: "*"
|
||||
espressif/esp_hosted: "*"
|
||||
@@ -1,194 +0,0 @@
|
||||
// HLK-LD2410 driver — see ld2410.h for wiring/rationale and the honesty
|
||||
// note below for exactly how confident to be in this file.
|
||||
//
|
||||
// UNVERIFIED AGAINST REAL HARDWARE, and — unlike bme280.c, which transcribes
|
||||
// formulas from Bosch's official public datasheet — this frame parser is
|
||||
// reconstructed from public community reverse-engineering write-ups of the
|
||||
// LD2410 UART protocol (Hi-Link's official protocol document was not
|
||||
// available while writing this). The header/footer magic bytes (F4 F3 F2 F1
|
||||
// / F8 F7 F6 F5) and the general envelope shape (4-byte header, 2-byte LE
|
||||
// length, payload, 4-byte footer) are consistently reported across sources
|
||||
// and are probably solid. The exact payload byte offsets for target
|
||||
// state / distances / energies are the single least-certain part of this
|
||||
// entire firmware — see the detailed note in ld2410_parse_payload() below.
|
||||
// Real bring-up should verify them against a logic analyzer capture or a
|
||||
// known-good reference implementation before trusting field values.
|
||||
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include "ld2410.h"
|
||||
#include "driver/uart.h"
|
||||
#include "esp_log.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
static const char *TAG = "ld2410";
|
||||
|
||||
#define LD2410_RX_BUF_SIZE 512
|
||||
#define LD2410_SCRATCH_SIZE 256
|
||||
|
||||
static const uint8_t FRAME_HEADER[4] = { 0xF4, 0xF3, 0xF2, 0xF1 };
|
||||
static const uint8_t FRAME_FOOTER[4] = { 0xF8, 0xF7, 0xF6, 0xF5 };
|
||||
|
||||
static bool s_ready = false;
|
||||
|
||||
typedef struct {
|
||||
uint8_t target_state; // bit0 = moving target, bit1 = stationary target
|
||||
uint16_t moving_distance_cm;
|
||||
uint8_t moving_energy; // 0-100
|
||||
uint16_t stationary_distance_cm;
|
||||
uint8_t stationary_energy; // 0-100
|
||||
uint16_t detection_distance_cm;
|
||||
} ld2410_frame_t;
|
||||
|
||||
esp_err_t ld2410_init(void) {
|
||||
uart_config_t cfg = {
|
||||
.baud_rate = LD2410_UART_BAUD,
|
||||
.data_bits = UART_DATA_8_BITS,
|
||||
.parity = UART_PARITY_DISABLE,
|
||||
.stop_bits = UART_STOP_BITS_1,
|
||||
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
|
||||
.source_clk = UART_SCLK_DEFAULT,
|
||||
};
|
||||
|
||||
esp_err_t err = uart_param_config(LD2410_UART_PORT, &cfg);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "uart_param_config failed: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
|
||||
// uart_set_pin(port, tx_pin, rx_pin, rts_pin, cts_pin) -- our TX GPIO
|
||||
// wires to the module's RX, and our RX GPIO wires to the module's TX.
|
||||
err = uart_set_pin(LD2410_UART_PORT, LD2410_UART_TX_GPIO, LD2410_UART_RX_GPIO,
|
||||
UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "uart_set_pin failed: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
|
||||
err = uart_driver_install(LD2410_UART_PORT, LD2410_RX_BUF_SIZE, 0, 0, NULL, 0);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "uart_driver_install failed: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
|
||||
s_ready = true;
|
||||
ESP_LOGI(TAG, "LD2410 UART init ok on port %d (%d baud)", LD2410_UART_PORT, LD2410_UART_BAUD);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static bool ld2410_parse_payload(const uint8_t *p, uint16_t len, ld2410_frame_t *out) {
|
||||
// Basic (engineering-mode-off) target report payload, as reported by
|
||||
// public LD2410 protocol write-ups:
|
||||
// p[0] data type byte (0x02 observed for normal reports; not
|
||||
// strictly checked here -- see honesty note above)
|
||||
// p[1] 0xAA head-of-intra-frame-data marker
|
||||
// p[2] target state: 0=none, 1=moving, 2=stationary, 3=both
|
||||
// p[3..4] moving target distance, cm, little-endian uint16
|
||||
// p[5] moving target energy, 0-100
|
||||
// p[6..7] stationary target distance, cm, little-endian uint16
|
||||
// p[8] stationary target energy, 0-100
|
||||
// p[9..10] detection distance, cm, little-endian uint16
|
||||
// p[11] 0x55 end-of-intra-frame-data marker
|
||||
// p[12] trailing byte (ignored)
|
||||
//
|
||||
// We only trust a frame whose head/end markers (p[1], p[11]) match --
|
||||
// a cheap sanity check against having latched onto the wrong byte
|
||||
// offsets or a corrupted frame. Anything that fails this is treated as
|
||||
// "no valid frame this cycle", not a hard error.
|
||||
if (len < 13) {
|
||||
return false;
|
||||
}
|
||||
if (p[1] != 0xAA || p[11] != 0x55) {
|
||||
return false;
|
||||
}
|
||||
|
||||
out->target_state = p[2];
|
||||
out->moving_distance_cm = (uint16_t)p[3] | ((uint16_t)p[4] << 8);
|
||||
out->moving_energy = p[5];
|
||||
out->stationary_distance_cm = (uint16_t)p[6] | ((uint16_t)p[7] << 8);
|
||||
out->stationary_energy = p[8];
|
||||
out->detection_distance_cm = (uint16_t)p[9] | ((uint16_t)p[10] << 8);
|
||||
return true;
|
||||
}
|
||||
|
||||
esp_err_t ld2410_read(sensor_reading_t *out, size_t max_out, size_t *out_count) {
|
||||
*out_count = 0;
|
||||
if (!s_ready) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
if (max_out < 1) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
uint8_t buf[LD2410_SCRATCH_SIZE];
|
||||
// The LD2410 free-runs, pushing a report frame roughly every 100ms, so
|
||||
// as long as the driver's RX ring buffer isn't empty a short read
|
||||
// should find at least one complete frame already queued.
|
||||
int len = uart_read_bytes(LD2410_UART_PORT, buf, sizeof(buf), pdMS_TO_TICKS(200));
|
||||
if (len < 0) {
|
||||
ESP_LOGW(TAG, "uart_read_bytes error");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
if (len == 0) {
|
||||
ESP_LOGD(TAG, "no UART data from LD2410 this cycle");
|
||||
return ESP_OK; // nothing new isn't a driver failure
|
||||
}
|
||||
|
||||
bool parsed_any = false;
|
||||
ld2410_frame_t latest = {0};
|
||||
|
||||
// Scan for the newest complete, validated frame in whatever arrived
|
||||
// this cycle; keep overwriting `latest` so we report the freshest one.
|
||||
for (int i = 0; i + 4 <= len; i++) {
|
||||
if (memcmp(&buf[i], FRAME_HEADER, 4) != 0) {
|
||||
continue;
|
||||
}
|
||||
if (i + 6 > len) {
|
||||
break; // not enough bytes left even for the length field
|
||||
}
|
||||
uint16_t data_len = (uint16_t)buf[i + 4] | ((uint16_t)buf[i + 5] << 8);
|
||||
size_t frame_total = 4 + 2 + (size_t)data_len + 4;
|
||||
if (i + (int)frame_total > len) {
|
||||
continue; // incomplete frame in this read window, skip it
|
||||
}
|
||||
const uint8_t *payload = &buf[i + 6];
|
||||
const uint8_t *footer = &buf[i + 6 + data_len];
|
||||
if (memcmp(footer, FRAME_FOOTER, 4) != 0) {
|
||||
ESP_LOGD(TAG, "footer mismatch at offset %d, discarding candidate frame", i);
|
||||
continue;
|
||||
}
|
||||
if (ld2410_parse_payload(payload, data_len, &latest)) {
|
||||
parsed_any = true;
|
||||
i += (int)frame_total - 1; // loop's i++ moves past this frame
|
||||
}
|
||||
}
|
||||
|
||||
if (!parsed_any) {
|
||||
ESP_LOGD(TAG, "no complete/valid LD2410 frame in this read window");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
memset(&out[0], 0, sizeof(out[0]));
|
||||
strncpy(out[0].sensor_type, "presence", SENSOR_READING_TYPE_MAXLEN - 1);
|
||||
out[0].value = (latest.target_state != 0) ? 1.0 : 0.0;
|
||||
strncpy(out[0].unit, "bool", SENSOR_READING_UNIT_MAXLEN - 1);
|
||||
|
||||
// This is the whole reason to prefer the LD2410 over a plain PIR: pack
|
||||
// the richer distance/energy data into metadata instead of throwing it
|
||||
// away, so it's available to the backend/frontend even though the
|
||||
// top-level `value` stays a simple presence boolean per the contract.
|
||||
cJSON *meta = cJSON_CreateObject();
|
||||
if (meta != NULL) {
|
||||
cJSON_AddNumberToObject(meta, "target_state", latest.target_state);
|
||||
cJSON_AddNumberToObject(meta, "moving_distance_cm", latest.moving_distance_cm);
|
||||
cJSON_AddNumberToObject(meta, "moving_energy", latest.moving_energy);
|
||||
cJSON_AddNumberToObject(meta, "stationary_distance_cm", latest.stationary_distance_cm);
|
||||
cJSON_AddNumberToObject(meta, "stationary_energy", latest.stationary_energy);
|
||||
cJSON_AddNumberToObject(meta, "detection_distance_cm", latest.detection_distance_cm);
|
||||
}
|
||||
out[0].metadata = meta;
|
||||
|
||||
*out_count = 1;
|
||||
return ESP_OK;
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
// HLK-LD2410 mmWave presence/distance sensor driver, over UART.
|
||||
//
|
||||
// Chosen over a plain PIR for this app's "believable" ethos (per the spec):
|
||||
// the LD2410 reports moving-target distance/energy AND stationary-target
|
||||
// distance/energy separately, not just a boolean "something moved" — richer
|
||||
// signal for the anomaly pipeline (Workstream K) to work with, and it can
|
||||
// tell a séance's "someone opened a door" from "the sitter shifted in their
|
||||
// chair" in a way a PIR fundamentally cannot. Tradeoff: it's a more complex
|
||||
// protocol than a PIR's single GPIO pin, and this driver's frame parser is
|
||||
// UNVERIFIED against a real module — see README.md's honesty section and
|
||||
// the parsing notes in ld2410.c.
|
||||
//
|
||||
// Default wiring (3.3V logic — do NOT wire directly to a 5V-logic UART):
|
||||
// LD2410 TX -> ESP32 RX (LD2410_UART_RX_GPIO)
|
||||
// LD2410 RX -> ESP32 TX (LD2410_UART_TX_GPIO)
|
||||
// LD2410 VCC -> 5V (module runs its sensor front-end at 5V; UART logic is
|
||||
// 3.3V-tolerant per module datasheet -- double check your
|
||||
// specific board revision before wiring)
|
||||
// LD2410 GND -> GND
|
||||
// Default UART settings: 256000 baud, 8N1 (module factory default).
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "esp_err.h"
|
||||
#include "sensor_driver.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#ifndef LD2410_UART_PORT
|
||||
#define LD2410_UART_PORT 1
|
||||
#endif
|
||||
|
||||
#ifndef LD2410_UART_RX_GPIO
|
||||
#define LD2410_UART_RX_GPIO 17
|
||||
#endif
|
||||
|
||||
#ifndef LD2410_UART_TX_GPIO
|
||||
#define LD2410_UART_TX_GPIO 18
|
||||
#endif
|
||||
|
||||
#ifndef LD2410_UART_BAUD
|
||||
#define LD2410_UART_BAUD 256000
|
||||
#endif
|
||||
|
||||
// sensor_driver_t-compatible entry points.
|
||||
esp_err_t ld2410_init(void);
|
||||
esp_err_t ld2410_read(sensor_reading_t *out, size_t max_out, size_t *out_count);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
154
firmware/esp32p4-sensor-node/main/rd03e.c
Normal file
154
firmware/esp32p4-sensor-node/main/rd03e.c
Normal file
@@ -0,0 +1,154 @@
|
||||
// Ai-Thinker RD-03E driver — see rd03e.h for wiring and honesty notes.
|
||||
//
|
||||
// UNVERIFIED AGAINST REAL HARDWARE, and the frame format below is
|
||||
// reconstructed from a third-party bring-up write-up (electroniclinic.com's
|
||||
// RD-03E/ESP32 tutorial), not Ai-Thinker's own datasheet PDF (not available
|
||||
// while writing this) — treat this as the least-certain protocol detail in
|
||||
// this driver. What's cross-confirmed from multiple independent sources:
|
||||
// UART is 256000 baud / 8N1, and the module also has a separate, more
|
||||
// complex configuration-frame protocol (0xFD 0xFC 0xFB 0xFA header /
|
||||
// 0x04 0x03 0x02 0x01 footer) for calibration and firmware queries — this
|
||||
// driver does NOT implement that; it only reads the module's free-running
|
||||
// "simple report" output frames, which need no configuration to start
|
||||
// streaming after power-up.
|
||||
//
|
||||
// Simple report frame, as reconstructed (5 bytes total):
|
||||
// [0] 0xAA frame header
|
||||
// [1] gesture code raw value, meaning not confirmed against an
|
||||
// official datasheet — reported as-is in
|
||||
// metadata rather than translated to a label
|
||||
// that might be wrong
|
||||
// [2] distance lo byte distance_cm = lo | (hi << 8), little-endian
|
||||
// [3] distance hi byte
|
||||
// [4..5] 0x55 0x55 frame footer
|
||||
// Real bring-up should verify this against a logic analyzer capture before
|
||||
// trusting field values, same as the LD2410 driver this replaced.
|
||||
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include "rd03e.h"
|
||||
#include "driver/uart.h"
|
||||
#include "esp_log.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
static const char *TAG = "rd03e";
|
||||
|
||||
#define RD03E_RX_BUF_SIZE 512
|
||||
#define RD03E_SCRATCH_SIZE 256
|
||||
#define RD03E_FRAME_LEN 5
|
||||
|
||||
static const uint8_t FRAME_HEADER = 0xAA;
|
||||
static const uint8_t FRAME_FOOTER[2] = { 0x55, 0x55 };
|
||||
|
||||
static bool s_ready = false;
|
||||
|
||||
typedef struct {
|
||||
uint8_t gesture;
|
||||
uint16_t distance_cm;
|
||||
} rd03e_frame_t;
|
||||
|
||||
esp_err_t rd03e_init(void) {
|
||||
uart_config_t cfg = {
|
||||
.baud_rate = RD03E_UART_BAUD,
|
||||
.data_bits = UART_DATA_8_BITS,
|
||||
.parity = UART_PARITY_DISABLE,
|
||||
.stop_bits = UART_STOP_BITS_1,
|
||||
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
|
||||
.source_clk = UART_SCLK_DEFAULT,
|
||||
};
|
||||
|
||||
esp_err_t err = uart_param_config(RD03E_UART_PORT, &cfg);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "uart_param_config failed: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
|
||||
// uart_set_pin(port, tx_pin, rx_pin, rts_pin, cts_pin) -- our TX GPIO
|
||||
// wires to the module's RX (labeled "RX" on the module), and our RX
|
||||
// GPIO wires to the module's TX output (labeled "OT1" on the module).
|
||||
err = uart_set_pin(RD03E_UART_PORT, RD03E_UART_TX_GPIO, RD03E_UART_RX_GPIO,
|
||||
UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "uart_set_pin failed: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
|
||||
err = uart_driver_install(RD03E_UART_PORT, RD03E_RX_BUF_SIZE, 0, 0, NULL, 0);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "uart_driver_install failed: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
|
||||
s_ready = true;
|
||||
ESP_LOGI(TAG, "RD-03E UART init ok on port %d (%d baud)", RD03E_UART_PORT, RD03E_UART_BAUD);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t rd03e_read(sensor_reading_t *out, size_t max_out, size_t *out_count) {
|
||||
*out_count = 0;
|
||||
if (!s_ready) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
if (max_out < 1) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
uint8_t buf[RD03E_SCRATCH_SIZE];
|
||||
// The module free-runs its simple report frames, so a short read should
|
||||
// find at least one complete frame already queued in the RX ring buffer.
|
||||
int len = uart_read_bytes(RD03E_UART_PORT, buf, sizeof(buf), pdMS_TO_TICKS(200));
|
||||
if (len < 0) {
|
||||
ESP_LOGW(TAG, "uart_read_bytes error");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
if (len == 0) {
|
||||
ESP_LOGD(TAG, "no UART data from RD-03E this cycle");
|
||||
return ESP_OK; // nothing new isn't a driver failure
|
||||
}
|
||||
|
||||
bool parsed_any = false;
|
||||
rd03e_frame_t latest = {0};
|
||||
|
||||
// Scan for the newest complete, validated frame in whatever arrived
|
||||
// this cycle; keep overwriting `latest` so we report the freshest one.
|
||||
for (int i = 0; i + RD03E_FRAME_LEN <= len; i++) {
|
||||
if (buf[i] != FRAME_HEADER) {
|
||||
continue;
|
||||
}
|
||||
if (memcmp(&buf[i + 3], FRAME_FOOTER, 2) != 0) {
|
||||
continue; // not a real header byte, or a corrupted frame
|
||||
}
|
||||
latest.gesture = buf[i + 1];
|
||||
latest.distance_cm = (uint16_t)buf[i + 2] | ((uint16_t)buf[i + 3] << 8);
|
||||
parsed_any = true;
|
||||
i += RD03E_FRAME_LEN - 1; // loop's i++ moves past this frame
|
||||
}
|
||||
|
||||
if (!parsed_any) {
|
||||
ESP_LOGD(TAG, "no complete/valid RD-03E frame in this read window");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
// Reported as a numeric distance (not a boolean "presence" flag, unlike
|
||||
// the LD2410 this replaced) — a handheld "sense a presence at a
|
||||
// distance" device wants magnitude, and it lets the backend's
|
||||
// statistical anomaly detector treat "something got suddenly close" as
|
||||
// the anomaly signal, the same way it already treats a temperature
|
||||
// spike, rather than only firing on a coarse absent/present transition.
|
||||
memset(&out[0], 0, sizeof(out[0]));
|
||||
strncpy(out[0].sensor_type, "presence", SENSOR_READING_TYPE_MAXLEN - 1);
|
||||
out[0].value = (double)latest.distance_cm;
|
||||
strncpy(out[0].unit, "cm", SENSOR_READING_UNIT_MAXLEN - 1);
|
||||
|
||||
cJSON *meta = cJSON_CreateObject();
|
||||
if (meta != NULL) {
|
||||
// Raw code, not translated to a label -- see the honesty note above
|
||||
// about the gesture byte's exact meaning being unconfirmed.
|
||||
cJSON_AddNumberToObject(meta, "gesture_code", latest.gesture);
|
||||
}
|
||||
out[0].metadata = meta;
|
||||
|
||||
*out_count = 1;
|
||||
return ESP_OK;
|
||||
}
|
||||
57
firmware/esp32p4-sensor-node/main/rd03e.h
Normal file
57
firmware/esp32p4-sensor-node/main/rd03e.h
Normal file
@@ -0,0 +1,57 @@
|
||||
// Ai-Thinker RD-03E 24GHz mmWave human-movement/gesture radar, over UART.
|
||||
//
|
||||
// Confirmed against the actual part in use (not the originally-assumed
|
||||
// Hi-Link LD2410 — a different manufacturer with a different, incompatible
|
||||
// UART protocol; do not mix up wiring or frame parsing between the two).
|
||||
// Reports distance-ranged human presence (not just a boolean, per the
|
||||
// module's own "Precise Ranging & Positioning" naming) plus a gesture
|
||||
// code, better suited to a handheld device meant to sense "how close" and
|
||||
// "what kind of movement" rather than just "something moved."
|
||||
//
|
||||
// Implements the sensor_driver_t interface (see sensor_driver.h).
|
||||
//
|
||||
// Default wiring — see README.md's wiring section for the full pinout
|
||||
// table:
|
||||
// RD-03E OT1 (module's UART TX) -> ESP32 RX (RD03E_UART_RX_GPIO)
|
||||
// RD-03E RX (module's UART RX) -> ESP32 TX (RD03E_UART_TX_GPIO)
|
||||
// RD-03E VCC -> 5V (module power; UART logic is 0-3.3V, ESP32-safe)
|
||||
// RD-03E GND -> GND
|
||||
// RD-03E OT2 -> not connected (reserved on the module, unused here)
|
||||
// Default UART settings: 256000 baud, 8N1 (module factory default).
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "esp_err.h"
|
||||
#include "sensor_driver.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#ifndef RD03E_UART_PORT
|
||||
#define RD03E_UART_PORT 1
|
||||
#endif
|
||||
|
||||
// GPIO 14-19 and 54 are reserved on the target board (Waveshare
|
||||
// ESP32-P4-Module-DEV-KIT) for the onboard Wi-Fi co-processor's SDIO link
|
||||
// — see README.md and sdkconfig.defaults. These pins avoid that range and
|
||||
// the BMP280's I2C pins (8/9).
|
||||
#ifndef RD03E_UART_RX_GPIO
|
||||
#define RD03E_UART_RX_GPIO 4
|
||||
#endif
|
||||
|
||||
#ifndef RD03E_UART_TX_GPIO
|
||||
#define RD03E_UART_TX_GPIO 5
|
||||
#endif
|
||||
|
||||
#ifndef RD03E_UART_BAUD
|
||||
#define RD03E_UART_BAUD 256000
|
||||
#endif
|
||||
|
||||
// sensor_driver_t-compatible entry points.
|
||||
esp_err_t rd03e_init(void);
|
||||
esp_err_t rd03e_read(sensor_reading_t *out, size_t max_out, size_t *out_count);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -9,8 +9,8 @@
|
||||
|
||||
#include <stdbool.h>
|
||||
#include "sensor_registry.h"
|
||||
#include "bme280.h"
|
||||
#include "ld2410.h"
|
||||
#include "bmp280.h"
|
||||
#include "rd03e.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
static const char *TAG = "sensor_registry";
|
||||
@@ -19,8 +19,8 @@ static const char *TAG = "sensor_registry";
|
||||
// matters only for init() sequencing (e.g. bus setup before device use);
|
||||
// read() order just determines readings array ordering in the POST body.
|
||||
static const sensor_driver_t s_drivers[] = {
|
||||
{ .name = "bme280", .init = bme280_init, .read = bme280_read },
|
||||
{ .name = "ld2410", .init = ld2410_init, .read = ld2410_read },
|
||||
{ .name = "bmp280", .init = bmp280_init, .read = bmp280_read },
|
||||
{ .name = "rd03e", .init = rd03e_init, .read = rd03e_read },
|
||||
// Add new drivers here, e.g.:
|
||||
// { .name = "my_sensor", .init = my_sensor_init, .read = my_sensor_read },
|
||||
};
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
// Wi-Fi station-mode connection manager — see wifi_manager.h for the
|
||||
// hardware caveat about ESP32-P4 not having an integrated radio.
|
||||
// Wi-Fi station-mode connection manager — see wifi_manager.h for how Wi-Fi
|
||||
// actually reaches the network on this target (ESP32-C6 co-processor over
|
||||
// SDIO via esp_wifi_remote/esp_hosted). The esp_wifi_*/esp_netif_* calls
|
||||
// below are the same ones a Wi-Fi-native chip would use.
|
||||
//
|
||||
// Structure follows ESP-IDF's own documented Wi-Fi station example pattern
|
||||
// (event-driven connect via WIFI_EVENT/IP_EVENT + an event group the rest
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
// Wi-Fi station-mode connection management, using the credentials in
|
||||
// device_config.h.
|
||||
//
|
||||
// IMPORTANT hardware caveat (honesty note, not code): the ESP32-P4 SoC has
|
||||
// no integrated 2.4GHz radio -- real Wi-Fi on P4 hardware requires a
|
||||
// companion chip (e.g. ESP32-C6) wired via SDIO/SPI running "esp-hosted",
|
||||
// which presents this exact same esp_wifi/esp_netif API to application code.
|
||||
// This file is written against that standard API surface, so it should be
|
||||
// portable unchanged to a hosted-mode P4 board or to a Wi-Fi-native target
|
||||
// (e.g. `idf.py set-target esp32s3`) -- only sdkconfig / board wiring
|
||||
// differs, not this code. See README.md for the full explanation. This is
|
||||
// exactly the kind of thing nobody can verify without the real board, so
|
||||
// it's called out explicitly rather than glossed over.
|
||||
// IMPORTANT hardware note: the ESP32-P4 SoC has no integrated 2.4GHz radio.
|
||||
// Confirmed against the actual target hardware (Waveshare
|
||||
// ESP32-P4-Module-DEV-KIT, chip ESP32-P4NRW32): it pairs the P4 with an
|
||||
// onboard ESP32-C6 co-processor over a fixed 7-pin SDIO link, and Wi-Fi is
|
||||
// brought up via the `espressif/esp_wifi_remote` + `espressif/esp_hosted`
|
||||
// managed components (declared in main/idf_component.yml, configured in
|
||||
// sdkconfig.defaults). Those components provide a drop-in-compatible
|
||||
// esp_wifi_*/esp_netif_* API — this file's esp_wifi_init()/esp_wifi_start()
|
||||
// calls are unchanged from what they'd be on a Wi-Fi-native chip; only the
|
||||
// component manifest and sdkconfig differ, not this code. See README.md's
|
||||
// Wi-Fi section for the reserved SDIO pins (14-19, 54) and sdkconfig.defaults
|
||||
// for the exact config values — still unverified against real hardware
|
||||
// (nobody in this build has the physical board), but now backed by a
|
||||
// specific, sourced hardware architecture rather than a general caveat.
|
||||
|
||||
#pragma once
|
||||
|
||||
|
||||
@@ -8,6 +8,30 @@
|
||||
|
||||
CONFIG_IDF_TARGET="esp32p4"
|
||||
|
||||
# --- Wi-Fi via the onboard ESP32-C6 co-processor (esp_wifi_remote/esp_hosted) ---
|
||||
# The ESP32-P4 has no integrated Wi-Fi radio. On the target hardware
|
||||
# (Waveshare ESP32-P4-Module-DEV-KIT), an ESP32-C6 co-processor is wired to
|
||||
# it over SDIO — 7 fixed pins: CLK=18, CMD=19, D0=14, D1=15, D2=16, D3=17,
|
||||
# RESET=54 — and esp_hosted/esp_wifi_remote handle those transparently once
|
||||
# configured; wifi_manager.c's esp_wifi_init()/esp_wifi_start() calls need
|
||||
# no changes. These CONFIG values match Espressif's own reference
|
||||
# ESP32-P4-Function-EV-Board, whose SDIO pin assignment is identical to
|
||||
# Waveshare's board (verified against two independent sources) — still,
|
||||
# this is the single most important thing to confirm against the physical
|
||||
# board before flashing, since it was never run against real hardware.
|
||||
CONFIG_SLAVE_IDF_TARGET_ESP32C6=y
|
||||
CONFIG_ESP_HOSTED_CP_TARGET_ESP32C6=y
|
||||
CONFIG_ESP_HOSTED_P4_DEV_BOARD_FUNC_BOARD=y
|
||||
CONFIG_WIFI_RMT_STATIC_RX_BUFFER_NUM=16
|
||||
CONFIG_WIFI_RMT_DYNAMIC_RX_BUFFER_NUM=64
|
||||
CONFIG_WIFI_RMT_DYNAMIC_TX_BUFFER_NUM=64
|
||||
CONFIG_WIFI_RMT_AMPDU_TX_ENABLED=y
|
||||
CONFIG_WIFI_RMT_TX_BA_WIN=32
|
||||
CONFIG_WIFI_RMT_AMPDU_RX_ENABLED=y
|
||||
CONFIG_WIFI_RMT_RX_BA_WIN=32
|
||||
CONFIG_LWIP_TCP_SND_BUF_DEFAULT=65534
|
||||
CONFIG_LWIP_TCP_WND_DEFAULT=65534
|
||||
|
||||
# The backend is served over HTTPS in production (see deploy/), so the HTTP
|
||||
# client needs mbedTLS's bundled CA store to verify the TLS cert.
|
||||
CONFIG_MBEDTLS_CERTIFICATE_BUNDLE=y
|
||||
|
||||
Reference in New Issue
Block a user