📋 專案概述

汐止的家後門出去就是山腳步道,夏天的陽台是埃及斑蚊與白線斑蚊的自助餐廳——而我太太是那種「全桌只有她被咬」的體質。市面上的捕蚊燈我們買過兩盞:一盞 NT$690 的「智能款」,智慧之處是可以用手機設定定時開關(對,就這);另一盞電擊式,半夜「啪」一聲把女兒嚇哭,而且電擊網打中大蚊蟲時會把蟲屍碎片噴到燈周圍 30 公分——那盞燈底下的桌面我們後來都是用酒精擦的。

但真正的坑是開啟時機。家用蚊子的活動高峰是傍晚 17:30–19:30 與清晨 5:30–7:30兩個窗口(黃昏蚊的習性,被蚊媒學術文獻翻來覆去研究的那種)。定時開關款要嘛整晚開(濾燈燒得快、白天對著太陽浪費),要嘛你記得每個傍晚去開——而蚊子從不遲到,你一定會。這台的做法:光敏電阻自己看天黑、自己掐著雙峰曲線拉燈,收工後自己降檔,早上七點準時關燈睡覺。全部材料 NT$240:

帳本: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 分鐘眼睛會花。風扇葉在收集盒上游狹窄風道裡,手指物理上進不去。

🔌 接線圖

ESP32-C2 SuperMini · 深睡 5µA GPIO3 PWM-UV GPIO4 PWM-FAN GPIO10 CDS-ADC GPIO2 CNT GPIO5 RGB PT4103 恆流板 DIM←GPIO3 VIN 5V · GND →395nm×6 70mm 渦扇(5V) 黑→S8055 D 紅→5V S8055(G←GPIO4) ITR2020 計數 OUT→GPIO2 VCC 3V3 · GND 光敏分壓 GPIO10←中點 100k↑3V3 · CDS↓GND WS2812B ×3 DIN←GPIO5
▲ 全機五個受控物件:恆流板 DIM、風扇閘極、計數中斷、光敏 ADC、燈條數據。白天能醒著看光的只有 GPIO10 的 ADC 輪詢(每 5 分鐘 20ms),其餘時間晶片在睡。

詳細接線

ESP32-C2 腳位連接說明
GPIO3(LEDC)PT4103 DIM5kHz PWM 調光;恆流板把 0~100% 調光線性化,LED 不會調暗偏色
GPIO4(LEDC)S8055 G 端風扇夜間降檔;S 接 GND,風扇黑線走 D
GPIO2(中斷)ITR2020 OUT擋光翻轉計一次;C2 任意 GPIO 可中斷,選 2 純粹是它離收集盒近
GPIO10(ADC)光敏分壓中點100k 上拉+GL5528 下拉:天黑電阻升、ADC 讀值升,單調性好
GPIO5WS2812B DINRMT 輸出;燈條 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
風道中段(渦扇座+防逃錐上段)1PETG灰~1.8 hr
防逃錐下段(縮徑錐)1半透明 PETG自然色~50 min
收集盒(带濾網蓋)1PETG灰~40 min
燈柱夾(夾陽台欄杆)1PETG黑~1.1 hr
光敏遮陽罩(帽簷式)1PETG白~8 min

💻 程式碼

📐 註解規範:區塊以框註分段;函式以框註說明用途/參數/回傳;行內註解解釋「為什麼」。開發板 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); }

🏗️ 組裝步驟

📘 圖文版組裝說明書:從燈頭環焊接到防逃錐方向防呆,六步附 SVG 示意圖與 A4 列印樣式 → 開啟 HTML 組裝說明書
  1. 列印件檢查:燈頭罩紫外線透光測試(手電筒贴照不漏光)、防逃錐 14mm 口徑通規(一支鉛筆能過、橡皮擦不過)
  2. UV 燈環焊接:6 顆 LED 焊鋁基板(散熱膏!UV 光衰比可見光快),2 串 3 並進恆流板;燈環與鋁板間墊 0.5mm 矽膠墊壓合
  3. 恆流板預調:先接好 DIM 上電,PWM 50% 量 LED 電流 ≈ 350mA(恆流板輸出端即為 2 串總流),調板上電位器到標定值再燈環上機
  4. 風道與計數器:渦扇四角減震螺絲鎖中段;ITR2020 卡進收集盒入口內壁卡槽,發射面水平朝心——兩側要能「隔著 22mm 通道看見彼此」
  5. 光敏與主板:光敏進遮陽罩(帽簷朝上)、走線進燈柱;主板塞燈柱頂腔,USB 線從柱底防水出線口出
  6. 上電與首夜空跑:序列埠校時成功後,用手擋光敏模擬天黑 → 風扇燈應按曲線啟動;整夜後核對計數與收集盒實際數量的比例(首夜校準靈敏度)

✅ 驗收測試

測試步驟合格標準
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 握手易超時);路由器加中繼;戰報失敗屬正常,燈照開
計數永遠 0ITR2020 出廠是「漫反射」版,反光面距離不對感測面到對面壁 15~25mm 最佳;裝反光片(銀色膠布)可救
計數虛高風道震動造成感測器晃動誤觸感測器卡槽補熱熔膠;去抖從 25ms 加到 40ms
收蟲量不如舊電擊燈正常:風吸式本來就少了「電擊弹飛」的誤傷計數看三日總量曲線而非單日;把收集盒倒到白紙上會看到果蠅/蛾蚋也都在帳上
戰報四桶全 0 但有總數NTP 未就緒就入桶(timeOk=false)等 1 分鐘自動補上;或 configTime server 換 time.apple.com

🎯 進階延伸

📁 專案檔案結構

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 元,和一個不再被咬的夏天。

← 上一篇:ESP32-C3 智慧烙鐵抽煙機 📚 系列索引 下一篇:ESP32 全系列 × 3D 列印系列索引 →