← Kembali Stabil Menengah Deteksi kehadiran
Node Kehadiran WiFi CSI Deteksi orang menembus dinding lewat fisika WiFi — tanpa kamera, tanpa isu privasi.
Board ESP32-S3 (T-Dongle-S3 or DevKitC-1)
Perkiraan biaya ~$15
Waktu pasang 45 mnt
Sensor WiFi CSI (56 subcarriers), RSSI
Setiap paket WiFi membawa Channel State Information: 56 angka yang menggambarkan bagaimana gelombang radio membelok di sekitar tubuh dan perabot dalam perjalanan ke antena. Node ini menangkap CSI pada 50 Hz, bergabung dengan mesh ESP-NOW bersama node lain, dan mengalirkan JSON lewat USB — bahan mentah untuk deteksi kehadiran, analisis gerakan, dan semua yang dilakukan ekosistem Latent. Ini firmware yang sama dengan yang berjalan di deployment mesh kami sendiri.
Dirawat dan diuji lapangan oleh tim Latent.
Mulai pemasangan terpandu Yang kamu butuhkan Board ESP32-S3 (T-Dongle-S3 atau DevKitC-1) ~$10 Kabel USB-C ~$3 Jaringan WiFi 2,4 GHz (router apa pun) ~$0 Perkiraan biaya ~$15
Langkah pemasangan 1 Siapkan komponen eksternal
5 Cek LED status dan kembangkan mesh
Config perangkat Config lengkap — salin, unduh, atau ikuti panduan pemasangan.
Salin Unduh
# LatentField CSI Mesh Node
# ========================
# Hardware: Qiheng T-Dongle-S3 (ESP32-S3)
# Purpose: WiFi CSI capture + ESP-NOW mesh broadcast
# Flash: esphome run csi_node.yaml
# Connect: USB-C to Mac, 921600 baud, JSON lines over UART0
#
# Architecture:
# 6 CSI nodes (this firmware) + 1 rotary collector
# Each node broadcasts ESP-NOW beacons at 50Hz.
# Other nodes extract CSI from received beacons.
# CSI data is streamed over USB serial as JSON lines.
#
# T-Dongle-S3 Pin Map:
# GPIO43 — USB UART TX (to Mac)
# GPIO44 — USB UART RX (from Mac)
# GPIO39 — Status LED (WS2812 / built-in)
# GPIO00 — BOOT button
#
# Serial output format (one JSON line per CSI frame):
# {"node_id":1,"ts":12345678,"rssi":-42,"csi_amp":[...],"csi_phase":[...]}
substitutions:
device_name: "latentfield-node-1"
friendly: "LatentField Node 1"
# Change per device: 1..6 for the six CSI nodes
node_id: "1"
esphome:
name: ${device_name}
friendly_name: ${friendly}
platformio_options:
board_build.flash_mode: dio
esp32:
board: esp32-s3-devkitc-1
variant: esp32s3
framework:
type: esp-idf
version: recommended
sdkconfig_options:
# Enable CSI in IDF kconfig
CONFIG_ESP_WIFI_CSI_ENABLED: "y"
# ESP-NOW requires WiFi
CONFIG_ESP_WIFI_ESPNOW_MAX_ENCRYPT_NUM: "7"
# ---------- External components ----------
# Reuses the same custom components as the rotary firmware
external_components:
- source:
type: local
path: external_components
components: [latentfield_csi, latentfield_espnow]
# ---------- WiFi ----------
# ESP-NOW can work without AP association, but CSI capture needs
# WiFi initialized. We connect to the same network as the rotary
# so all nodes share the same channel.
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
power_save_mode: none # CSI requires power save off
# ---------- Serial bridge to operator station ----------
# 921600 baud for high-throughput CSI streaming (50Hz * ~200B = 10KB/s)
uart:
id: usb_uart
tx_pin: GPIO43
rx_pin: GPIO44
baud_rate: 921600
rx_buffer_size: 1024
logger:
level: INFO
baud_rate: 0 # Disable default logger on UART0 — we own it for data
# ---------- LatentField CSI capture ----------
latentfield_csi:
id: csi
num_subcarriers: 56
ring_size: 8 # Deeper buffer for 50Hz streaming
min_rssi: -85
# filter_mac: AA:BB:CC:DD:EE:FF # Optional: restrict to specific tx node
# ---------- LatentField ESP-NOW mesh ----------
# Each node acts as broadcaster (sends beacons) + peer (receives CSI)
latentfield_espnow:
id: mesh
node_id: ${node_id}
role: broadcaster # Auto-sends beacons for other nodes to measure CSI from
channel: 0 # 0 = use WiFi manager's channel (must be same across all nodes)
ring_size: 16
# Pre-register other nodes if known:
# peers:
# - 30:AE:A4:XX:XX:X1
# - 30:AE:A4:XX:XX:X2
# - 30:AE:A4:XX:XX:X3
# - 30:AE:A4:XX:XX:X4
# - 30:AE:A4:XX:XX:X5
# - 30:AE:A4:XX:XX:X6
# ---------- Status LED (WS2812 on T-Dongle-S3) ----------
# T-Dongle-S3 has a single WS2812 LED on GPIO39
light:
- platform: esp32_rmt_led_strip
id: status_led
pin: GPIO39
num_leds: 1
rmt_channel: 0
chipset: WS2812
rgb_order: GRB
name: "Status LED"
# ---------- Globals ----------
globals:
- { id: frame_count, type: unsigned int, initial_value: '0' }
- { id: tx_count, type: unsigned int, initial_value: '0' }
# ---------- Intervals ----------
interval:
# Broadcast ESP-NOW beacon every 20ms (50Hz)
# Other nodes extract CSI from these packets
- interval: 20ms
then:
- lambda: |-
// Send a minimal beacon so other nodes can measure CSI from it.
// The beacon payload carries our node_id and frame counter.
uint8_t payload[4];
payload[0] = ${node_id};
payload[1] = (uint8_t)(id(frame_count) & 0xFF);
payload[2] = (uint8_t)((id(frame_count) >> 8) & 0xFF);
payload[3] = 0; // reserved
id(mesh).broadcast(4 /*LF_MSG_RAW*/, payload, sizeof(payload));
id(tx_count)++;
# Stream CSI data over USB serial at ~50Hz
# Each line is a JSON object with node_id, timestamp, RSSI, amplitude, phase
- interval: 20ms
then:
- lambda: |-
// Pop the latest CSI frame and send as JSON line
std::string csi_json = id(csi).get_latest_json();
if (csi_json.empty() || csi_json == "{}") return;
// Inject our node_id into the JSON
// csi_json format: {"rssi":-42,"wifi_csi":[...],"wifi_phase":[...]}
// We transform to: {"node_id":N,"ts":T,"rssi":-42,"csi_amp":[...],"csi_phase":[...]}
id(frame_count)++;
char header[64];
snprintf(header, sizeof(header),
"{\"node_id\":%d,\"ts\":%lu,",
${node_id}, (unsigned long)(esp_timer_get_time()));
// Replace the opening "{" of csi_json with our header
std::string out;
out.reserve(csi_json.size() + 64);
out += header;
// Strip leading "{" from csi_json and remap field names
// Input fields: "rssi", "wifi_csi", "wifi_phase"
// Output fields: "rssi", "csi_amp", "csi_phase"
std::string body = csi_json.substr(1); // skip '{'
// Simple string replace for field names
size_t pos;
pos = body.find("\"wifi_csi\"");
if (pos != std::string::npos)
body.replace(pos, 10, "\"csi_amp\"");
pos = body.find("\"wifi_phase\"");
if (pos != std::string::npos)
body.replace(pos, 12, "\"csi_phase\"");
out += body;
out += "\n";
id(usb_uart).write_array(
reinterpret_cast<const uint8_t*>(out.data()), out.size());
# Status LED: green blink = healthy, red = no CSI
- interval: 1000ms
then:
- lambda: |-
if (id(frame_count) > 0) {
// Green pulse — node is capturing CSI
auto call = id(status_led).turn_on();
call.set_rgb(0.0f, 0.3f, 0.0f);
call.set_brightness(0.3f);
call.perform();
} else {
// Red — no CSI frames yet
auto call = id(status_led).turn_on();
call.set_rgb(0.3f, 0.0f, 0.0f);
call.set_brightness(0.3f);
call.perform();
}
Ulasan (0) Belum ada ulasan — jadilah yang pertama.
Masuk dulu untuk fitur ini — hanya 20 detik dan gratis. Masuk