← 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
2
Atur ID node dan WiFi
3
Flash node-nya
4
Lihat aliran CSI
5
Cek LED status dan kembangkan mesh

Config perangkat

Config lengkap — salin, unduh, atau ikuti panduan pemasangan.

# 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