組員:411214202屠玟琦/411214208柯采彤/411214302林昕潔/411214326張瓊予
說明:
利用 Wi-Fi 訊號進行人體感測。這個技術不需要依賴傳統的攝影機或穿戴裝置來偵測。而是透過分析無線訊號在空間中的反射與變化,來推測人體的動作與狀態。
在無線通訊中,Wi-Fi 訊號會受到環境中物體(如牆壁、家具與人體)的影響產生變化。RuView 是嘗試利用機器學習方法,將 CSI 數據轉換為人體活動資訊,例如姿勢、移動甚至呼吸狀態。
動機:
現今常見的人體偵測方法多依賴攝影機或紅外線感測器,但仍存在以下問題:
攝影機涉及隱私疑慮
紅外線感測器容易受環境影響(如溫度)
部分設備需額外安裝,增加成本
目前π RuView也因為 AI 與IoT技術的進步逐漸發展起來。
材料:
Wi-Fi裝置:提供穩定 Wi-Fi 訊號來源、建立實驗環境
ESP32:發送與接收 Wi-Fi 訊號、讀取訊號強度(RSSI)或相關資料、作為主要感測裝置
筆電:接收 ESP32 傳送資料、顯示與記錄數據、分析訊號變化
接線:ESP32的COM端接電腦USB端(上傳程式碼時),其餘時間有接電即可
製作的詳細流程:
安裝韌體與環境設定:
1.先讓ESP32 匯入網路資料。
2.板子連接到電腦,使用 esptool 燒錄。
3.用Python 設定 WiFi 與目標 IP。
節點數據採集:
1.使用 Arduino IDE 編譯並將 CSI 監聽程式燒錄至 ESP32-S3 開發板 。
2.EPS32 自動擷取 CSI/RSSI 訊號,封裝為 JSON 格式,透過 UDP 協定即時回傳給伺服器 。
做UI的過程:
1.執行 cargo run 啟動 Rust 伺服器,並透過瀏覽器開啟 Web UI 。
2.燒錄韌體並設定目標 IP,透過 PowerShell 監聽 UDP 5005 埠,確認 ESP32 能成功傳送 JSON 數據 。
3.檢查 UI 是否完整顯示 ESP32 連接狀態、除錯面板與實驗錄製功能 。
4.驗證系統能區分 Real 與 Mock 數據來源,確保錄製的 CSV 資料純淨且無干擾 。
ESP32板子的Code:
#include <WiFi.h>
#include <WiFiUdp.h>
#include "esp_wifi.h"
#include "esp_wifi_types.h"
// ====== 每片 ESP32 要改這裡 ======
#define DEVICE_ID "esp32_1"
// ====== Wi-Fi 設定 ======
const char* WIFI_SSID = "你的WiFi名稱";
const char* WIFI_PASS = "你的WiFi密碼";
// ====== 你的電腦 IP 與後端 UDP port ======
const char* SERVER_IP = "192.168.50.89";
const int SERVER_PORT = 5005;
// ====== UDP 與封包計數 ======
WiFiUDP udp;
unsigned long packetCount = 0;
// ====== CSI 最新狀態,只存「真的 callback 收到的資料」======
volatile bool latestCsiValid = false;
volatile uint16_t latestCsiLen = 0;
volatile int latestCsiRssi = 0;
volatile uint8_t latestCsiChannel = 0;
volatile uint32_t latestCsiCount = 0;
volatile unsigned long latestCsiMillis = 0;
// CSI callback:收到 Wi-Fi CSI 時會自動進來這裡
void csiCallback(void *ctx, wifi_csi_info_t *info) {
if (info == nullptr || info->buf == nullptr || info->len == 0) {
return;
}
// 注意:callback 裡不要 Serial.print、不要 UDP 傳輸、不要做重工作
latestCsiValid = true;
latestCsiLen = info->len;
latestCsiRssi = info->rx_ctrl.rssi;
latestCsiChannel = info->rx_ctrl.channel;
latestCsiCount++;
latestCsiMillis = millis();
}
void setupCsi() {
Serial.println("Setting up real CSI callback...");
// 關閉 Wi-Fi 省電,讓封包接收比較穩
esp_err_t err = esp_wifi_set_ps(WIFI_PS_NONE);
Serial.print("esp_wifi_set_ps(WIFI_PS_NONE) = ");
Serial.println(err);
// 有些 CSI 情境需要 promiscuous mode 才比較穩定收到 CSI
err = esp_wifi_set_promiscuous(true);
Serial.print("esp_wifi_set_promiscuous(true) = ");
Serial.println(err);
#if defined(CONFIG_SOC_WIFI_HE_SUPPORT) && CONFIG_SOC_WIFI_HE_SUPPORT
// 給支援 HE / Wi-Fi 6 的 ESP32-C6/C5 類型使用
wifi_csi_config_t csi_config = {
.enable = true,
.acquire_csi_legacy = true,
.acquire_csi_ht20 = true,
.acquire_csi_ht40 = true,
.acquire_csi_su = false,
.acquire_csi_mu = false,
.acquire_csi_dcm = false,
.acquire_csi_beamformed = false,
.acquire_csi_he_stbc = 0,
.val_scale_cfg = 0,
.dump_ack_en = false,
.reserved = 0
};
#else
// ESP32 / ESP32-S3 常用 CSI 設定
wifi_csi_config_t csi_config = {
.lltf_en = true,
.htltf_en = true,
.stbc_htltf2_en = true,
.ltf_merge_en = true,
.channel_filter_en = false,
.manu_scale = false,
.shift = 0
};
#endif
err = esp_wifi_set_csi_rx_cb(csiCallback, NULL);
Serial.print("esp_wifi_set_csi_rx_cb = ");
Serial.println(err);
err = esp_wifi_set_csi_config(&csi_config);
Serial.print("esp_wifi_set_csi_config = ");
Serial.println(err);
err = esp_wifi_set_csi(true);
Serial.print("esp_wifi_set_csi(true) = ");
Serial.println(err);
if (err == ESP_OK) {
Serial.println("CSI enabled.");
} else {
Serial.println("CSI enable failed. If csi_valid stays false, check board/core support.");
}
}
uint8_t getCurrentWifiChannel() {
uint8_t primary = 0;
wifi_second_chan_t second;
esp_err_t err = esp_wifi_get_channel(&primary, &second);
if (err == ESP_OK) {
return primary;
}
return 0;
}
void setup() {
Serial.begin(115200);
delay(1000);
Serial.println();
Serial.print("DEVICE_ID=");
Serial.println(DEVICE_ID);
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_PASS);
Serial.print("Connecting WiFi");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println();
Serial.println("WiFi connected");
Serial.print("ESP32 local IP=");
Serial.println(WiFi.localIP());
Serial.print("Target server=");
Serial.print(SERVER_IP);
Serial.print(":");
Serial.println(SERVER_PORT);
setupCsi();
}
void loop() {
packetCount++;
// 先抓最新 CSI 狀態
bool csiValid = latestCsiValid;
uint16_t csiLen = latestCsiLen;
int csiRssi = latestCsiRssi;
uint8_t csiChannel = latestCsiChannel;
uint32_t csiCount = latestCsiCount;
unsigned long csiMillis = latestCsiMillis;
// 如果目前還沒有收到 CSI,就不要假裝有 CSI
int rssiToSend = csiValid ? csiRssi : WiFi.RSSI();
uint16_t csiLenToSend = csiValid ? csiLen : 0;
uint8_t channelToSend = csiValid ? csiChannel : getCurrentWifiChannel();
String msg = "{";
msg += "\"device_id\":\"" + String(DEVICE_ID) + "\",";
msg += "\"rssi\":" + String(rssiToSend) + ",";
msg += "\"csi_len\":" + String(csiLenToSend) + ",";
msg += "\"csi_valid\":" + String(csiValid ? "true" : "false") + ",";
msg += "\"csi_packets\":" + String(csiCount) + ",";
msg += "\"channel\":" + String(channelToSend) + ",";
msg += "\"packet_count\":" + String(packetCount) + ",";
msg += "\"timestamp\":" + String(millis()) + ",";
msg += "\"csi_timestamp\":" + String(csiMillis);
msg += "}";
udp.beginPacket(SERVER_IP, SERVER_PORT);
udp.print(msg);
int ok = udp.endPacket();
Serial.print("udp_end=");
Serial.print(ok);
Serial.print(" | ");
Serial.println(msg);
delay(1000);
網頁上給的UI介面:
用docker和終端機開啟網頁給的ui介面
Live Demo頁面
用docker和終端機開啟網頁給的ui介面
training頁面
但是以上這個網頁給我們的都只是demo用的介面,所以我們無法修改上面的資料,無法直接連接並傳輸至esp32所獲取的資訊,也不能做training,所以我們只能自己架一個可以正常使用的ui介面。
ui 介面 Setup:
ESP32 Experiment Data Collection Setup
This phase validates real ESP32 transport and collects RSSI/CSI CSV data for later Jupyter analysis. Do not treat heatmaps, pose maps, or vital-sign visuals as validated until the raw data shows separable scenario patterns.
Fixed Device IDs
Each ESP32 must have a stable firmware-level DEVICE_ID:
esp32_1
esp32_2
esp32_3
esp32_4
The UI and backend read the fixed device list from config/devices.json and always show all four devices. A device is connected only when the backend received data for that device_id within the last 3 seconds.
Do not use Serial Port, COM Port, USB Port, or IP address as the device identity. Those values can change when boards are moved to wall power, battery power, a different USB port, or a different router lease. DEVICE_ID must travel inside the ESP32 data packet so the backend can update the correct device.
Firmware Device ID
The firmware includes a build-time default:
#define DEVICE_ID "esp32_1"
When flashing four boards, build or configure each image with a different value:
# Board 1
idf.py build -DDEVICE_ID=\"esp32_1\"
# Board 2
idf.py build -DDEVICE_ID=\"esp32_2\"
# Board 3
idf.py build -DDEVICE_ID=\"esp32_3\"
# Board 4
idf.py build -DDEVICE_ID=\"esp32_4\"
If your ESP-IDF wrapper does not pass -DDEVICE_ID directly, set the compile definition in the firmware build config before building each board:
target_compile_definitions(${COMPONENT_LIB} PRIVATE DEVICE_ID=\"esp32_1\")
ESP32 logs should include the identity, for example:
device_id=esp32_1 CSI cb #300: len=384 rssi=-39 ch=9
Each JSON summary packet sent to the backend includes:
{
"device_id": "esp32_1",
"rssi": -39,
"csi_len": 384,
"channel": 9,
"packet_count": 300,
"timestamp": 1710000000000
}
The binary CSI frame still carries a numeric node_id; the firmware derives it from the DEVICE_ID suffix so DEVICE_ID="esp32_2" sends node_id=2. The backend maps node_id=1 to esp32_1, node_id=2 to esp32_2, and so on for recording.
UI Test Flow
Start the Rust sensing server in ESP32 mode.
Open the UI and go to ESP32 Connection.
Confirm the mode badge says REAL MODE - ESP32 data only.
Confirm all four fixed devices are visible: esp32_1, esp32_2, esp32_3, esp32_4.
Power on each ESP32 and wait until all four show connected.
Check the Raw Data Debug Panel and confirm recent packets include the expected device_id, RSSI, CSI length, packet count, and timestamp.
Use Mock only to test dashboard layout. Mock mode must be visibly labeled MOCK MODE and must not be used for real experiment CSV collection.
Recording Flow
Record each scenario separately from the Experiment Recording panel. CSV files are saved to data/raw/:
data/raw/YYYY-MM-DD_HH-MM-SS_empty_room.csv
data/raw/YYYY-MM-DD_HH-MM-SS_person_static.csv
data/raw/YYYY-MM-DD_HH-MM-SS_person_walking.csv
Recommended order:
Record empty_room baseline.
Record person_static.
Record person_walking.
Optionally record position labels: person_center, person_front_left, person_front_right, person_back_left, person_back_right.
Open notebooks/esp32_data_check.ipynb.
Compare RSSI time series, scenario RSSI distributions, packet counts, missing-data intervals, and CSI length.
Only after the notebook shows real differences between scenarios should heatmap, presence, pose, or vital-sign visualization work continue.
Validation Status
Validated by real data in this phase:
Four fixed ESP32 device statuses by device_id
Latest RSSI
Latest CSI length
Packet counts
Raw packet debug view
CSV recording to data/raw/
Jupyter RSSI/CSI-length comparison
Preliminary:
RSSI motion interpretation
CSI feature summaries
Scenario classification ideas
Mock only / not validated:
Heatmaps
Pose maps
Vital-sign charts
Presence or person-count visualizations
架設ui過程:
安裝 Rust 與 Cargo
先安裝 Rust 開發環境(Cargo 會一起安裝)。
安裝完成後確認:
cargo --version
rustc --version
進入專案資料夾
執行:cargo check
若成功會看到類似訊息:Finished dev profile...,代表Rust後端可以編譯。
啟動後端 Server
執行:cargo run
成功後 Backend Server 會開始運作。
確認前端 UI (Real / Mock Mode)
確認 UI 裡有:
Real / Mock mode
ESP32 Status Panel
esp32_1 ~ esp32_4
Raw Data Debug Panel
Experiment Recording Panel
讓esp32送資料到電腦ip
修改 ESP32 Firmware:讓 ESP32可以連接 Wi-Fi、擷取 CSI 資料、透過 UDP 傳送 JSON。
並修改 CSI 程式碼。
UDP 傳送至電腦 IP,PowerShell 接收 UDP
證明:ESP32 真的有連上 Wi-Fi
成功會顯示:From 192.168.50.229 => {"device_id":"esp32_1","rssi":-55,...}
代表:ESP32 真的有送 UDP 封包、電腦真的有收到 ESP32 資料
驗證 Real / Mock 沒有混在一起
目的:避免模擬資料污染真實實驗結果。
Real Mode 測試:實際 ESP32 傳送資料。
系統回報:
2.5 秒內收到 3 個 real event、0 個 simulated event
Real CSV 成功寫入 3 筆,全部 source=real
Mock 測試寫入 8 筆,全部 source=mock
Real connected 數值維持不變。
代表:
真實資料正常接收
沒有混入模擬資料。
UI成果圖:
UI成果圖(主要介面,未連接)
UI成果圖(主要介面,已連接)
UI成果圖(錄製start後的csv檔)
下學期目標:
1.分析錄製後的數據
2.UI介面增加live即時更新人體動態