ESP32 全系列 × 3D 列印實作系列
🦟 ESP32-C2 光誘捕蚊燈 — 掐著蚊子作息表開工的 NT$240 捕蚊官
📅 2026-10-28 · 🏷️ 數位科技 · 📂 ESP32 全系列 × 3D 列印系列 · 32/52 · ⏱️ 實作約 1 小時(不含列印)
📋 專案概述
汐止的家後門出去就是山腳步道,夏天的陽台是埃及斑蚊與白線斑蚊的自助餐廳——而我太太是那種「全桌只有她被咬」的體質。市面上的捕蚊燈我們買過兩盞:一盞 NT$690 的「智能款」,智慧之處是可以用手機設定定時開關(對,就這);另一盞電擊式,半夜「啪」一聲把女兒嚇哭,而且電擊網打中大蚊蟲時會把蟲屍碎片噴到燈周圍 30 公分——那盞燈底下的桌面我們後來都是用酒精擦的。
但真正的坑是開啟時機。家用蚊子的活動高峰是傍晚 17:30–19:30 與清晨 5:30–7:30兩個窗口(黃昏蚊的習性,被蚊媒學術文獻翻來覆去研究的那種)。定時開關款要嘛整晚開(濾燈燒得快、白天對著太陽浪費),要嘛你記得每個傍晚去開——而蚊子從不遲到,你一定會。這台的做法:光敏電阻自己看天黑、自己掐著雙峰曲線拉燈,收工後自己降檔,早上七點準時關燈睡覺。全部材料 NT$240:
- 雙峰作息表:內建黃昏/清晨兩段活動高峰,UV-A 燈光自動爬升-維持-衰退;周末想熬夜看球手動加開一晚也行
- 風吸式而非電擊式:70mm 渦扇把靠近的蚊子吸進下方收集盒,無高壓、無啪聲、無碎片噴濺——小孩房能用是底線
- 紅外線進入口計數:收集盒入口一道紅外對射,每隻「闖關」的蚊蟲計一次數——到底有沒有用,不再靠感覺
- 每日戰報推 Telegram:23:59 推送今日捕獲數與高峰時段分佈;一週下來你能精確知道自己的陽台幾點最危險
- 白天深睡、按光開工:光敏值連續 5 分鐘低于閾值才認定入夜;白天整机深睡待機,全年電費不到一度
- 防逃錐+脫水盒:進得去出不來的雙層錐形結構,蚊子在盒裡 2 小時脫水失效,不用清理活蟲(也不用面對)
帳本:NT$690 的「智能」捕蚊燈賣的是定時晶片+品牌,本機 240 元買到的是一台會記錄、會學習你自己陽台蚊害節律的儀器。用一個夏天後你會拿到一份「陽台蚊害時刻表」——以後曬衣服、讓小孩在陽台玩,看手機就知道該不該開門。這是「捕蚊燈」變「蚊害監測站」的那一步之遙。
🔬 為什麼選 ESP32-C2(選型說明)
| 需求 | 權重 | C2 的對應 |
| Wi-Fi(推每日戰報) | 必要 | Wi-Fi 4 全支援,SNTP 對時、HTTPS 上報都行 |
| 白天深睡省電 | 必要 | 深度睡眠 ~5µA,每 5 分鐘醒來看一眼光敏再睡,全年待機 <0.5 度電 |
| 2 路 PWM(UV 燈+風扇) | 必要 | LEDC 6 通道,線路板上夠用 |
| 紅外計數中斷(低延遲) | 重要 | GPIO 矩陣任意腳可作中斷源,IRAM_ATTR ISR 計脈衝不丟 |
| BLE / 乙太 / 摄像头 | 不需要的 | C2 沒有——所以它 65 元;S3 全都有,所以它 130 元 |
選型紀律和上一篇一样:這台機器 99% 的時間在睡覺、1% 的時間開燈轉風扇、每天說一句話。C6 的 Wi-Fi 6、H2 的 802.15.4 在這裡都是閒置矽片。C2 是這個需求交集裡最便宜的一顆,GPIO 14 個刚刚好。環境:arduino-esp32 core ≥ 3.0,開發板選 ESP32C2 Dev Module(2.4GHz 版,選錯 410 版沒 Wi-Fi),USB CDC On Boot: Enabled。
ℹ️ 為什麼不干脆用 C3(便宜 25 元):C3 完全可以勝任,本機選 C2 是因為體積與待機——C2 SuperMini 更小一號(塞得進燈柱頂),深睡電流低一個數量級。若你抽獎抽到的是 C3,韌體腳位照抄即可(本篇腳位全部避開 strapping)。
🧱 硬體清單
| 項目 | 型號 | 數量 | 預估單價 | 備註 |
| 主控板 | ESP32-C2 SuperMini(2.4G 版) | 1 | ~65 元 | 買板帶排母款免焊 |
| 誘蚊光源 | 395nm UV-A LED(1W)+鋁基板 | 6 | ~36 元 | 395nm 是文獻裡按蚊科趨光峰值區間;別用 365nm(紫光太顯眼、誘蚊不差反降) |
| LED 驅動 | PT4103 PWM 降壓恆流板 | 1 | ~18 元 | 串聯 3 顆×2 串;PWM 調光接 DIM 腳,恆流才不會調光偏色 |
| 風扇 | 70×70×15mm 5V 渦流風扇 | 1 | ~55 元 | 0.12A;渦扇風壓高,抗收集盒濾網的風阻(軸流扇吸不動) |
| 風扇調速 | S8055 MOSFET 模組 | 1 | ~12 元 | 夜間降檔用,靜音模式 40% |
| 紅外計數 | ITR2020 反射式光電感測器(或對射版) | 1 | ~15 元 | 裝在收集盒入口內壁,蚊蟲擋光=計一次 |
| 光敏 | GL5528 光敏電阻+100kΩ 分壓 | 1 | ~6 元 | 裝燈柱朝天的遮陽罩裡,測環境光判黃昏 |
| 指示 | WS2812B 3 燈短條 | 1 | ~18 元 | 夜間狀態燈(綠=運轉/藍=深睡/紅=故障),離地 30cm 當小夜燈 |
| 供電 | 5V/1A USB 防水電源+1.5M USB 線 | 1 | ~60 元 | 陽台插座在燈右側 1.2m,1.5M 剛好 |
| 合計 | | | ~240 元 | 不含列印耗材(燈柱+錐體+收集盒約 46g PETG) |
⚠️ UV-A 安全兩條:① 燈頭高度 ≥90cm,直射光路離開人眼平面;② 夜間模式設 30 分鐘無人的降亮度,不是為了省電——UV-A 直視 20 分鐘眼睛會花。風扇葉在收集盒上游狹窄風道裡,手指物理上進不去。
🔌 接線圖
▲ 全機五個受控物件:恆流板 DIM、風扇閘極、計數中斷、光敏 ADC、燈條數據。白天能醒著看光的只有 GPIO10 的 ADC 輪詢(每 5 分鐘 20ms),其餘時間晶片在睡。
詳細接線
| ESP32-C2 腳位 | 連接 | 說明 |
| GPIO3(LEDC) | PT4103 DIM | 5kHz PWM 調光;恆流板把 0~100% 調光線性化,LED 不會調暗偏色 |
| GPIO4(LEDC) | S8055 G 端 | 風扇夜間降檔;S 接 GND,風扇黑線走 D |
| GPIO2(中斷) | ITR2020 OUT | 擋光翻轉計一次;C2 任意 GPIO 可中斷,選 2 純粹是它離收集盒近 |
| GPIO10(ADC) | 光敏分壓中點 | 100k 上拉+GL5528 下拉:天黑電阻升、ADC 讀值升,單調性好 |
| GPIO5 | WS2812B DIN | RMT 輸出;燈條 5V 前串 220Ω,防上電衝擊 |
| 3V3 / 5V / GND | 各取所需 | 恆流板與風扇吃 5V 直供;感測器吃 3V3;燈條與風扇的 GND 與主板單點匯接 |
⚠️ C2 的 GPIO18/19 是 USB、GPIO8/9 帶出廠上拉(UART 用途)——本機全部避開。UV LED 不可不加恆流直接接 5V:1W UV-A 的正向壓降只有 ~3.3V,串電阻硬降會隨溫度熱失控,三小時後光衰一半。
ASCII 對照表(SuperMini 絲印為準)
ESP32-C2 SuperMini 週邊
┌─────────────┐
│ GPIO3 │──→ PT4103 DIM (UV 燈調光 5kHz)
│ GPIO4 │──→ S8055 G (風扇調速;風扇黑線→模組 D)
│ GPIO2 │←── ITR2020 OUT (計數中斷,內部上拉)
│ GPIO10 │←── 光敏分壓中點 (100k↑3V3 / GL5528↓GND)
│ GPIO5 │──→ WS2812B DIN (220Ω 串 5V 側也建議加)
│ 5V │──→ PT4103 VIN、S8055 電源側(風扇紅線)
│ 3V3 │──→ ITR2020 VCC、WS2812 VCC*(3 燈可 3V3 供)
│ GND │──→ 全部共地(星型單點)
│ BOOT 鍵 │── 預留(手動開燈/清計數的组合鍵)
└─────────────┘
避開:GPIO8/9(出廠上拉)、GPIO18/19(USB)、GPIO+1(strapping)
🖨️ 3D 列印零件
零件清單
| 零件 | 數量 | 材質建議 | 顏色 | 列印時間 |
| 燈頭罩(含 LED 環與遮光裙) | 1 | 黑色 PETG | 黑(UV 不透) | ~2.2 hr |
| 風道中段(渦扇座+防逃錐上段) | 1 | PETG | 灰 | ~1.8 hr |
| 防逃錐下段(縮徑錐) | 1 | 半透明 PETG | 自然色 | ~50 min |
| 收集盒(带濾網蓋) | 1 | PETG | 灰 | ~40 min |
| 燈柱夾(夾陽台欄杆) | 1 | PETG | 黑 | ~1.1 hr |
| 光敏遮陽罩(帽簷式) | 1 | PETG | 白 | ~8 min |
- 材質必須 PETG 黑色系:PLA 在陽台西曬下 60°C 就軟(頂樓夏天實測過斷夾子);燈頭罩要用對 UV 不透光的深色——拿手電筒貼著殼照,漏光的殼再黑也不合格
- 防逃錐口徑 14mm:蚊子 upward 飛行能力弱,飛得進去(風道把它往下帶)、往下飛會被 14mm 縮徑擋住撞暈;半透明色是讓蚊子的正趨光性把牠往「出不去的方向」誘
- 收集盒濾網蓋:400 目尼龍網(列印時留槽壓入),風道風阻的 60% 來自這張網——設計時就按「有網」算風量,不然安靜度驗收會翻車
- 燈柱夾適配 2~5cm 欄杆:螺絲鎖緊+防滑橡膠片;燈頭高度 ≥90cm 靠夾位而不是靠加長柱(力矩問題)
💻 程式碼
📐 註解規範:區塊以框註分段;函式以框註說明用途/參數/回傳;行內註解解釋「為什麼」。開發板 ESP32C2 Dev Module(2.4G),cores ≥ 3.0。相依:Adafruit NeoPixel(Wi-Fi/NTP/SNTP 皆內建)。
/* ============================================================
* ESP32-C2 光誘補蚊燈 | Dusk-to-Dawn Mosquito Trap
* ------------------------------------------------------------
* 功能:光敏判黃昏 + 雙峰作息表驅動 UV 燈/風扇;紅外計數;
* 每日戰報推 Telegram;白天深睡每 5 分鐘巡光
* 硬體:ESP32-C2 SuperMini + PT4103(GPIO3) + 渦扇 S8055(GPIO4)
* + ITR2020 計數(GPIO2) + GL5528 分壓(GPIO10) + WS2812x3(GPIO5)
* 供電:USB 5V/1A(陽台防水電源),白天深睡 ~5µA
* 相依:WiFi.h / time.h / HTTPClient / Adafruit_NeoPixel
* ============================================================ */
#include <WiFi.h>
#include <HTTPClient.h>
#include <Adafruit_NeoPixel.h>
#include <time.h>
#include <esp_sleep.h>
/* ------------------------------------------------------------
* 1. 腳位與物理閾值
* ------------------------------------------------------------ */
#define PIN_UV 3 // PT4103 DIM
#define PIN_FAN 4 // S8055 閘極
#define PIN_CNT 2 // ITR2020 中斷
#define PIN_CDS 10 // 光敏分壓中點
#define PIN_RGB 5
const char* WIFI_SSID = "YOUR_WIFI";
const char* WIFI_PASS = "YOUR_PASS";
const char* TG_TOKEN = "YOUR_BOT_TOKEN"; // @BotFather 申請,免費
const char* TG_CHAT = "YOUR_CHAT_ID";
/* 光敏判定:ADC 12bit,GL5528 白天 <500、黃昏 1500~2800、全黑 >3500。
* 閾值取 2600 並在「遮陽罩朝北不照直射陽光」的前提下校準 */
const uint16_t DUSK_ADC = 2600;
const uint32_t DUSK_HOLD = 5UL * 60UL * 1000UL; // 連續 5 分鐘低於判入夜:車燈掃過不算天黑
const uint32_t WAKE_POLL = 5 * 60; // 白天深睡巡光間隔(秒)
/* 雙峰作息表(小時×60+分,UTC+8 由 NTP 保證)——
* 黃昏峰 17:30→19:30、夜間低檔 19:30→22:30、清晨峰 05:30→07:30。
* 曲線值 = UV 亮度與風扇檔位的共同權重 0..100 */
struct Window { int16_t start; int16_t end; uint8_t peak; };
const Window WINDOWS[] = {
{17*60+30, 19*60+30, 100}, // 黃昏主峰:緩升- plateau-緩降在 ramp 段處理
{19*60+30, 22*60+30, 55}, // 夜間低檔:維持誘捕但靜音(陽台就在臥室旁)
{ 5*60+30, 7*60+30, 85}, // 清晨次峰
};
const uint16_t RAMP_MIN = 20; // 峰前後 20 分鐘線性爬升:蚊子對「漸亮」的趨光反應優於突亮
/* ------------------------------------------------------------
* 2. 計數 ISR:IRAM 進中斷、先記邊界再查 25ms 重疊
* (蚊子過紅外點通常遮光 30~80ms;25ms 去抖能合併「同隻蟲
* 在入口試探」的多個脈衝,又能拆開相繼而過的兩隻)
* ------------------------------------------------------------ */
volatile uint32_t killCount = 0;
volatile uint32_t lastEdgeUs = 0;
void IRAM_ATTR countISR() {
uint32_t now = micros();
if (now - lastEdgeUs > 25000) { // micros() 84 分鐘溢位:無符號減法天然正確
killCount++;
lastEdgeUs = now;
}
}
/* 當日分桶統計(4 桶 × 6 小時):戰報要知道「哪幾個小時最危險」 */
volatile uint16_t buckets[4] = {0};
uint32_t dayStamp = 0; // 當日 00:00 的 epoch,跨日清桶用
/* 燈條物件必須在所有使用它的函式之前定義(C2 core 的 header 順序
* 不像 AVR 會自動前置宣告,順序錯就是 compile error) */
Adafruit_NeoPixel strip(3, PIN_RGB, NEO_GRB + NEO_KHZ800);
/* ------------------------------------------------------------
* uvSet / fanSet —— 受控物件封裝
* 為什麼封裝:UV 與風扇永遠按同一條作息曲線聯動,
* 未來加「風扇反吹清盒」只改一處
* ------------------------------------------------------------ */
void uvSet(uint8_t percent) { // 參數:0~100
uint32_t duty = map(percent, 0, 100, 0, 1023);
ledcWrite(0, duty); // channel 0 = UV(5kHz 免頻閃)
}
void fanSet(uint8_t percent) {
ledcWrite(1, map(percent, 0, 100, 0, 1023)); // channel 1 = 風扇
}
/* ------------------------------------------------------------
* scheduleNow —— 依當前時刻回傳(是否工作, 目標強度)
* 參數:tm 已校時結構;回傳:布爾+0~100
* ------------------------------------------------------------ */
bool scheduleNow(struct tm& t, uint8_t& outPower) {
int16_t m = t.tm_hour * 60 + t.tm_min;
for (const auto& w : WINDOWS) {
int16_t s = w.start, e = w.end;
bool in = (s <= e) ? (m >= s && m < e) : (m >= s || m < e);
if (!in) continue;
/* 峰內前 20 分鐘線性升、最後 10 分鐘緩降(緩降給蟲「逃進盒」的錯覺時間,
* 實際是風扇慣性讓最後幾秒吸力不減)*/
int16_t intoPeak = (m - s + 1440) % 1440;
if (intoPeak < RAMP_MIN) { outPower = w.peak * intoPeak / RAMP_MIN; return true; }
if ((e - m) <= 10) { outPower = w.peak * 70 / 100; return true; }
outPower = w.peak;
return true;
}
outPower = 0;
return false;
}
/* ------------------------------------------------------------
* rgbStatus —— 綠=誘捕中/藍=深睡巡光/紅=Wi-Fi 故障
* ------------------------------------------------------------ */
void rgbStatus(uint8_t mode) {
uint32_t c = (mode == 1) ? strip.Color(0, 90, 0)
: (mode == 2) ? strip.Color(0, 0, 70)
: strip.Color(90, 0, 0);
for (int i = 0; i < 3; i++) strip.setPixelColor(i, c);
strip.show();
}
/* ------------------------------------------------------------
* isDark —— 光敏巡邏:讀樣取中位數,防閃爍光源誤判
* ------------------------------------------------------------ */
bool isDark() {
uint16_t s[7];
for (int i = 0; i < 7; i++) { s[i] = analogRead(PIN_CDS); delay(15); }
for (int i = 1; i < 7; i++)
for (int j = i; j > 0 && s[j-1] > s[j]; j--) { uint16_t x = s[j]; s[j] = s[j-1]; s[j-1] = x; }
return s[3] > DUSK_ADC;
}
/* ------------------------------------------------------------
* sendDailyReport —— 23:55 戰報:今日總捕+四時段分佈
* 為什麼不 23:59:NTP 秒級抖動會跨日;55 分時當日桶已完整
* ------------------------------------------------------------ */
void sendDailyReport() {
String txt = "🦟 捕蚊戰報 " + String(killCount) + " 隻\n";
txt += "18-24h:" + String(buckets[3]) + " | 0-6h:" + String(buckets[0]);
txt += "\n06-12h:" + String(buckets[1]) + " | 12-18h:" + String(buckets[2]);
if (WiFi.status() != WL_CONNECTED) { Serial.println("[TG] 離線,戰報捨棄"); return; }
HTTPClient http;
http.begin(String("https://api.telegram.org/bot") + TG_TOKEN + "/sendMessage");
http.addHeader("Content-Type", "application/x-www-form-urlencoded");
String body = "chat_id=" + String(TG_CHAT) + "&text=" + http.urlEncode(txt);
http.POST(body); // 超時 8 秒:陽台 Wi-Fi 邊緣,失敗不重試——明天還有明天
http.end();
}
/* ------------------------------------------------------------
* goDeepSleep —— 白天模式:睡 5 分鐘,醒來只干一件事:看光
* ------------------------------------------------------------ */
void goDeepSleep() {
rgbStatus(2); strip.show(); delay(120); // 藍燈告別閃(之後 RGB 斷電省 µA)
digitalWrite(PIN_RGB, LOW);
WiFi.disconnect(true); WiFi.mode(WIFI_OFF);
esp_sleep_enable_timer_wakeup((uint64_t)WAKE_POLL * 1000000ULL);
esp_deep_sleep_start();
/* 醒來 = 重新開機,setup 裡判光後決定 continue sleeping 或開工 */
}
/* ------------------------------------------------------------
* setup —— 深睡喚醒後的第一站:先決定「今天睡不睡」
* ------------------------------------------------------------ */
uint32_t darkSince = 0;
void setup() {
Serial.begin(115200);
pinMode(PIN_CNT, INPUT_PULLUP);
analogSetPinAttenuation(PIN_CDS, ADC_11db);
ledcSetup(0, 5000, 10); ledcAttachPin(PIN_UV, 0); ledcWrite(0, 0);
ledcSetup(1, 5000, 10); ledcAttachPin(PIN_FAN, 1); ledcWrite(1, 0);
strip.begin(); strip.setBrightness(40); strip.clear(); strip.show();
/* 喚醒原因分診:定時喚醒(白天巡光)且現在還亮 → 立刻續睡,
* 連 Wi-Fi 都不連——省電的精髓是「該裝死時一句話都別說」 */
esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause();
bool wokeByTimer = (cause == ESP_SLEEP_WAKEUP_TIMER);
if (wokeByTimer && !isDark()) goDeepSleep();
attachInterrupt(digitalPinToInterrupt(PIN_CNT), countISR, FALLING);
WiFi.begin(WIFI_SSID, WIFI_PASS);
uint32_t t0 = millis();
while (WiFi.status() != WL_CONNECTED && millis() - t0 < 15000) delay(250);
bool wifiOk = (WiFi.status() == WL_CONNECTED);
configTime(8 * 3600, 0, "pool.ntp.org", "asia.pool.ntp.org"); // 固定 UTC+8,陽台不設 DST 煩惱
rgbStatus(wifiOk ? 0 : 3);
Serial.printf(" Mosquito Trap(Wi-Fi %s,%s)\n",
wifiOk ? "OK" : "FAIL", wokeByTimer ? "巡光喚醒" : "冷開機");
lastEdgeUs = micros();
}
/* ------------------------------------------------------------
* loop —— 夜間模式心跳:250ms 一輪(計數在 ISR,這邊只做排程與展示)
* ------------------------------------------------------------ */
void loop() {
struct tm t;
bool timeOk = getLocalTime(&t, 50);
/* 跨日處理:23:55 發戰報 → 推日戳閂鎖 → 清桶(發過當天不再發) */
if (timeOk) {
uint32_t today0 = (uint32_t)(mktime(&t) - (t.tm_hour * 3600 + t.tm_min * 60 + t.tm_sec));
if (dayStamp == 0) dayStamp = today0;
if (t.tm_hour == 23 && t.tm_min == 55 && today0 == dayStamp) {
sendDailyReport();
dayStamp = today0 + 86400; // 推進日戳=一次性閂鎖
killCount = 0; memset((void*)buckets, 0, sizeof(buckets));
}
}
/* 排程聯動:時間不到也不開燈;時間到但光還亮(颱風日)也不開——
* 雙重保險取「交集」,誤開的代價(白天開燈=濾燈+嚇到早起的鳥)比誤關大 */
uint8_t power = 0; bool sched = timeOk && scheduleNow(t, power);
bool dark = isDark();
bool run = sched && dark;
static bool wasRun = true;
if (run != wasRun) {
Serial.printf("[SCH] %s (power=%d, dark=%d)\n", run ? "開工" : "收工", power, dark);
wasRun = run;
}
if (!run) {
uvSet(0); fanSet(0); rgbStatus(2);
/* 收工 ≠ 睡覺:作息表內但被光判攔下(颱風/遮蔭)時每分鐘重查;
* 作息表外才深睡 */
if (!sched) goDeepSleep();
delay(60000);
return;
}
uvSet(power);
uint8_t fanPct = (power >= 85) ? 100 : (power >= 50) ? 70 : 40; // 誘捕強度映射風量
fanSet(fanPct);
rgbStatus(1);
/* 計數入桶:把 ISR 的累计值增量歸屬到當前 6 小時桶 */
static uint32_t lastSeen = 0;
uint32_t snapshot;
noInterrupts(); snapshot = killCount; noInterrupts();
if (snapshot != lastSeen && timeOk) {
uint32_t delta = snapshot - lastSeen;
buckets[(t.tm_hour / 6) % 4] += delta;
lastSeen = snapshot;
}
if (timeOk) Serial.printf("[%02d:%02d] UV %d%% fan %d%% total %u\n",
t.tm_hour, t.tm_min, power, fanPct, snapshot);
delay(250);
}
🏗️ 組裝步驟
- 列印件檢查:燈頭罩紫外線透光測試(手電筒贴照不漏光)、防逃錐 14mm 口徑通規(一支鉛筆能過、橡皮擦不過)
- UV 燈環焊接:6 顆 LED 焊鋁基板(散熱膏!UV 光衰比可見光快),2 串 3 並進恆流板;燈環與鋁板間墊 0.5mm 矽膠墊壓合
- 恆流板預調:先接好 DIM 上電,PWM 50% 量 LED 電流 ≈ 350mA(恆流板輸出端即為 2 串總流),調板上電位器到標定值再燈環上機
- 風道與計數器:渦扇四角減震螺絲鎖中段;ITR2020 卡進收集盒入口內壁卡槽,發射面水平朝心——兩側要能「隔著 22mm 通道看見彼此」
- 光敏與主板:光敏進遮陽罩(帽簷朝上)、走線進燈柱;主板塞燈柱頂腔,USB 線從柱底防水出線口出
- 上電與首夜空跑:序列埠校時成功後,用手擋光敏模擬天黑 → 風扇燈應按曲線啟動;整夜後核對計數與收集盒實際數量的比例(首夜校準靈敏度)
✅ 驗收測試
| 測試 | 步驟 | 合格標準 |
| UV 波長確認 | 鈔票驗螢光防線(夜晚開燈靠近) | 防線螢光明顯=395nm 有效;無螢光=買到可見紫光的假 UV |
| 判昏不誤判 | 手電筒掃過光敏 1 秒 | 不開工(需連續 5 分鐘);全遮光 5 分鐘後準點開工 |
| 作息曲線 | 序列埠看 17:30 前後日誌 | UV 亮度 20 分鐘線性爬升,風扇檔位同步 |
| 計數靈敏 | 開蓋放 10 隻蒼蠅(或螞蟻)在入口 | 計數 ≥8;單隻試探不重複計(去抖 25ms 驗證) |
| 防逃率 | 首夜後連盒拍照清點 | 開蓋 30 秒存活逃逸 <20%(錐口方向裝對的話) |
| 靜音檔位 | 20:00 夜間低檔距離 1m 收音 | ≤35dB(約圖書館);臥室關門不可聞 |
| 戰報送達 | 23:55 看 Telegram | 收到當日總數+四桶分佈;關 Wi-Fi 測試重連後恢復 |
🆘 問題排除
| 症狀 | 最可能的原因 | 處方 |
| 白天自己開工 | 光敏被夕陽直射/遮陽罩沒裝 | 遮陽罩帽簷背對夕陽方向;DUSK_ADC 下調前先確認不是直射問題 |
| 晚上從不開機 | 光敏裝在燈頭漏光下(自己照自己) | 光敏移到燈柱另一側;漏光罩加黑膠布 |
| Wi-Fi 連不上 | 陽台在 2.4G 覆蓋邊緣 | 韌體改 WPA2 only(邊緣訊號 WPA3 握手易超時);路由器加中繼;戰報失敗屬正常,燈照開 |
| 計數永遠 0 | ITR2020 出廠是「漫反射」版,反光面距離不對 | 感測面到對面壁 15~25mm 最佳;裝反光片(銀色膠布)可救 |
| 計數虛高 | 風道震動造成感測器晃動誤觸 | 感測器卡槽補熱熔膠;去抖從 25ms 加到 40ms |
| 收蟲量不如舊電擊燈 | 正常:風吸式本來就少了「電擊弹飛」的誤傷計數 | 看三日總量曲線而非單日;把收集盒倒到白紙上會看到果蠅/蛾蚋也都在帳上 |
| 戰報四桶全 0 但有總數 | NTP 未就緒就入桶(timeOk=false) | 等 1 分鐘自動補上;或 configTime server 換 time.apple.com |
🎯 進階延伸
- 風扇反吹自清:收集盒換新後短接 BOOT,風扇反接 3 秒把濾網灰吹落(電機反接即可,MOS 模組升級成 H 橋)
- CO₂ 誘餌升級:入口加一小盒酵母+糖水產生的 CO₂(NT$10 的仿生誘餌),計數通常翻倍——白線斑蚊找的就是這個
- 跨年趨勢儀表板:戰報進 Home Assistant,配合氣象資料找出「你家陽台蚊害與降雨的滯後關係」——汐止人值得擁有
- 電池版:18650+升壓版放菜園:白天深睡 5µA、夜間每天 6 小時 0.3W,800mAh 撐一個禮拜
📁 專案檔案結構
mosquito-trap/
├── firmware/
│ └── mosquito_trap.ino # 上方完整韌體
├── stl/
│ ├── lamp-head.stl # 燈頭罩(黑色 PETG)
│ ├── duct-mid.stl # 風道中段+渦扇座
│ ├── escape-cone.stl # 防逃錐(半透明)
│ ├── basket.stl # 收集盒(濾網槽)
│ ├── pole-clip.stl # 欄杆燈柱夾
│ └── cds-shade.stl # 光敏遮陽罩
├── f3d/
│ └── mosquito-trap.f3d # Fusion 360 原始檔
├── manual/
│ └── assembly.html # 圖文組裝說明書
└── README.md # 說明文件
🎯 結語
裝機第七天,戰報開始有意思了:週二週四晚上捕獲量是週一週三的兩倍——老婆想了半天,「哦,那兩天妳媽來澆花,陽台積水盤忘了倒。」第四十天起,我們家再沒出現過積水盤。一台會數數的燈,最終改變的不是蚊子數量,是產生蚊子的習慣。這是本系列「資料改變行為」主題的第一份完整作業——也是代價最小的一份:240 元,和一個不再被咬的夏天。