📋 專案概述
去年 12 月,東北季風第 14 個濕天。我週五晚上開始印一批 PA6-CF 的功能件,噴嘴裡傳來熟悉的「劈啪」聲——那是水在 245°C 的噴嘴裡爆炸的聲音。印出來的件表面全是氣孔,層間強度用兩根手指就能摳開,16 小時的列印時間加上 900 元的尼龍,整盤進垃圾桶。更扯的是隔天打開另一捲號稱「密封保存」的 PETG,起絲球捲得像長了毛的毛線。
我認真算過這筆帳:北部 11 月到 3 月空氣濕度常態 80%+,尼龍 24 小時就能吸到影響列印的含水量。對策不是「快點印完」,是把料盒變成恆濕倉。查過市面上的解決方案:
- 品牌除濕乾貨機(Polymaker 起跳 2,300 元):只能放一捲、濕度顯示要開機按三轮才看得到、沒有曲線沒有推播——2,300 元買一個「有溫度的盒子」。
- 賣場 300 元恆溫烘碗機改:只有溫度沒有濕度閉環。PLA 玻璃轉化溫度約 60°C,保溫 65°C 的烘碗機放一晚,線盤直接黏死在轉軸上——我朋友親手砸過 1,800 元的 ASA。
- 密封盒 + 乾燥劑(150 元):對,這是正確方向,但你永遠不知道裡面現在是 20% 還是 60%,乾燥劑飽和了也不會跟你說。
關鍵洞察:乾燥箱的本質是一個濕度閉環系統,而濕度閉環最昂貴的部分(控制器)恰好是 ESP32 免費送你的。把「密封盒 + 乾燥劑」升級成「密封盒 + 乾燥劑 + 會自己開加熱、會自己報警的大腦」,總成本 463 元:
- 濕度閉環:RH > 30% 開 PTC 加熱輔助除濕、< 25% 停止——5% 遲滯,加熱器一天啟停不到 6 次
- 雙重過溫保護:軟體 50°C 強制斷加熱 + 獨立 55°C 溫度保險絲直接串在加熱迴路——韌體掛了也不燒線盤
- 門磁偵測:開門換料自動暫停迴路,關門後 10 分鐘重估(不然換料 30 秒的進濕量會讓加熱器白跑一小時)
- Telegram 推播:RH > 50% 撐 30 分鐘=門沒關好或乾燥劑失效;啟停週期異常短=乾燥劑飽和,提前通知換劑
- OLED 儀表:即時溫濕度、加熱狀態、除濕進度條,走過瞄一眼就知道
- 5GHz Wi-Fi:工作間在陽台鐵窗內,兩道牆 + 鄰居 30 幾個 2.4GHz AP,C5 是全系列唯一能穩定連 5GHz 的型號
成本效益:品牌乾貨機 2,300 元不含推播與曲線;本專案 463 元全部都有,而且盒子壞了換盒子、電路壞了換電路——乾燥箱是 24 小時運轉 365 天的設備,可維修性就是省錢。
為什麼選 ESP32-C5?
| 需求 | C5 的優勢 |
|---|---|
| 兩道牆外的路由器 | 全系列唯一雙頻 Wi-Fi 6,5GHz 穿牆後 SNR 遠勝被 AP 海淹沒的 2.4GHz |
| 24/7 運轉 | RISC-V 雙核 + 4nm 製程,控制 + Wi-Fi 待機平均比經典 ESP32 低約 40%,一年電費差一杯手搖 |
| 控制不能斷 | 雙核分工:核心 0 跑濕度狀態機,核心 1 跑 Wi-Fi 推播,重連網路不會卡住加熱控制 |
| GPIO 剛夠 | 加熱 + 風扇 + 門磁 + I2C×2 = 5 支腳,C5 的 27 支 GPIO 大材小用,留給 v2 的 PM2.5 濾芯監測 |
🧱 硬體清單
| 項目 | 型號 | 數量 | 預估單價 | 備註 |
|---|---|---|---|---|
| 主控板 | ESP32-C5-DevKitM-1(N4R4) | 1 | ~120 元 | 選帶外部天線座版本更佳;本專案走 5GHz,板載天線別埋金屬箱壁後方 |
| 溫濕度 | SHT31-D(I2C,±2%RH) | 1 | ~85 元 | 不要用 DHT22:它的 ±5%RH 在 25~30% 控制帶裡根本分不出啟停點 |
| 加熱器 | PTC 陶瓷加熱片 12V 10W(帶鋁殼) | 1 | ~60 元 | PTC 自限溫(表面最高 ~70°C),比電阻絲安全一個等級 |
| 加熱開關 | IRLZ44N 邏輯級 MOSFET | 1 | ~10 元 | 10kΩ 下拉 + 100Ω 閘極電阻;10W 負載連散熱片都不用 |
| 風扇 | 4010 5V 無刷風扇 | 1 | ~25 元 | 經 S8050 + 1N4148 由 GPIO 控制,讓箱內空氣循環過乾燥劑 |
| 顯示 | SSD1306 0.96" I2C OLED | 1 | ~75 元 | 與 SHT31 共總線(0x3C / 0x44 不撞號) |
| 門磁 | 常開型磁簧開關 + 磁石 | 1 組 | ~8 元 | 開關裝箱框、磁石裝門片,間隙 ≤ 8mm |
| 降壓 | MP1584 模組(12V→5V) | 1 | ~15 元 | 與列印機共用 12V 主電源,列印機關機乾燥箱也休息——燈絲只在「可能要用」時需要乾 |
| 安全 | 55°C 常閉溫度保險絲(KSD9700) | 1 | ~15 元 | 串在加熱器 12V 迴路裡,與韌體完全獨立的硬體層保護 |
| 其他 | 1N5822、1000µF/25V、電阻組、矽膠線 | 1 組 | ~50 元 | 加熱器端並 1000µF 抑制 PTC 啟停雜訊 |
| 合計 | ~463 元 | 不含密封箱(IKEA 密封盒 150 元或既有料箱沿用) |
🔌 接線圖
詳細接線
| ESP32-C5 腳位 | 連接 | 說明 |
|---|---|---|
| GPIO5 / GPIO6 | SHT31 + OLED 的 SDA / SCL | 共 I2C 總線;模組自帶上拉,不要再加 |
| GPIO4 | IRLZ44N 閘極(經 100Ω) | 10kΩ 下拉到 GND:復位期間閘極不浮空,加熱器預設關 |
| GPIO3 | S8050 基極(經 1kΩ) | 風扇 5V 從降壓板輸出取,不佔主板 LDO;1N4148 反並線圈 |
| GPIO7 | 磁簧開關 → GND | 內部上拉,開門 = LOW |
| 5V(MP1584 輸出) | 主板 5V、風扇 5V | 降壓板輸出並 470µF,避免 PTC 啟停瞬間把 5V 拉垮造成重啟 |
| GND | 全部共地單點匯流 | 加熱器迴路地線直接回電源端,不穿過主板地 |
ASCII 對照表(C5-DevKitM 左列 / 右列)
┌──────────────────────────┐
│ ESP32-C5 DevKitM-1 │
│ GPIO5 ○──── SHT31 SDA ─┬─ OLED SDA
│ GPIO6 ○──── SHT31 SCL ─┴─ OLED SCL
│ GPIO4 ○──── IRLZ44N 閘極(100Ω 串 + 10k 下拉)
│ GPIO3 ○──── S8050 基極(1kΩ)→ 4010 風扇
│ GPIO7 ○──── 磁簧開關 ── GND(開門 = LOW)
│ 5V ○──── MP1584 輸出(並 470µF)
│ GND ○──── 單點共地排
└──────────────────────────┘
加熱迴路(不過主板):
12V(+) ── 55°C KSD9700 ── PTC(+) ── PTC(−) ── MOSFET D
MOSFET S ── 電源 GND(與主板單點共地)
⚠️ 55°C 溫度保險絲必須貼著加熱片本體綁定(鐵氟龍帶纏緊),不是丟在空氣中——它要在加熱片失控的第一時間斷電,而不是等箱內空氣燒到 55°C。
🖨️ 3D 列印零件
零件清單
| 零件 | 數量 | 材質建議 | 顏色 | 列印時間 |
|---|---|---|---|---|
| 控制側掛盒(夾箱壁) | 1 | PETG | 黑 | ~70 min |
| SHT31 通風護罩 | 1 | PETG | 黑 | ~20 min |
| 風扇濾網蓋(內側) | 1 | PETG | 灰 | ~25 min |
| 磁簧固定座(框 + 門片) | 2 | PETG | 黑 | ~15 min |
列印設定
- 材質:全部 PETG——箱內工作溫度上看 45°C,PLA 在 50~60°C 會軟化變形,這是乾燥箱列印件的第一原則
- 填滿:15~20%(掛盒受靜載,不需要高填充)
- 層厚:0.2mm;護罩側壁做 0.8mm 薄壁 + 3 條透氣槽,讓 SHT31 讀「箱內空氣」而不是「盒內空氣」
- 注意:掛盒夾爪內側預留 0.4mm 間隙配 3M 泡棉墊,夾在 4~6mm 厚的壓克力/玻璃箱壁上不掉
設計要點
- 側掛式控制盒:電子件全部掛箱外,箱體只開兩個 Ø25mm 孔(風扇、感測器探頭)——密封性靠箱蓋膠條,電子艙與箱內氣密完全解耦,換電池/燒錄不用拆密封。
- 氣流路徑:風扇在箱內把空氣打過乾燥劑層 → 加熱片在箱外側掛艙內預熱回風,形成「乾燥劑 → 風扇 → 加熱 → 箱內」的單向循環;加熱片不直接對著線盤吹,避免局部過熱。
- 感測器探頭反輻射:SHT31 護罩內加擋板,隔開加熱片輻射熱;護罩朝下開孔,防冷凝水直接落在感測面上。
- 磁簧對位:固定座帶 8mm 間隙規(列印時直接當量規用),裝好即對位,不用試錯。
💻 程式碼
📐 註解規範:區塊以框註分段;函式以框註說明用途/參數/回傳;行內註解一律解釋「為什麼」而非「做什麼」。全系列文章採同一規範。
📦 相依:Arduino core for ESP32 ≥ 3.0(開發板選「ESP32C5 Dev Module」)、Adafruit_SHT31 ≥ 1.2、Adafruit SSD1306 ≥ 2.5、Adafruit GFX(SSD1306 依賴)。
📦 相依:Arduino core for ESP32 ≥ 3.0(開發板選「ESP32C5 Dev Module」)、Adafruit_SHT31 ≥ 1.2、Adafruit SSD1306 ≥ 2.5、Adafruit GFX(SSD1306 依賴)。
/* ============================================================
* ESP32-C5 智慧燈絲乾燥箱 | Smart Filament Dry Box
* ------------------------------------------------------------
* 功能:濕度閉環除濕(30/25% 遲滯)+ 雙重過溫保護 + 門磁暫停
* + OLED 儀表 + Telegram 推播(乾燥劑失效/門沒關/週報)
* 硬體:ESP32-C5 DevKitM-1 + SHT31-D + SSD1306 + PTC 12V 10W
* + IRLZ44N + S8050 風扇級 + 磁簧開關 + 55°C 保險絲
* 供電:12V 主電源 → MP1584 5V → 板 5V(加熱走 12V 獨立迴路)
* 相依:Arduino core for ESP32 ≥3.0(板型 ESP32C5 Dev Module)
* Adafruit_SHT31 / Adafruit_SSD1306 / WiFi / HTTPClient
* 安全:軟體上限 50°C + 硬體 55°C 保險絲;開機預設加熱關閉
* ============================================================ */
#include <WiFi.h>
#include <HTTPClient.h>
#include <Wire.h>
#include <Adafruit_SHT31.h>
#include <Adafruit_SSD1306.h>
/* ------------------------------------------------------------
* 1. 設定區(改這裡就好)
* ------------------------------------------------------------ */
const char* WIFI_SSID = "YOUR_WIFI_5G"; // 注意:連 5GHz SSID
const char* WIFI_PASS = "YOUR_PASSWORD";
const char* TG_TOKEN = "YOUR_BOT_TOKEN"; // Telegram Bot Token
const char* TG_CHAT = "YOUR_CHAT_ID";
/* ------------------------------------------------------------
* 2. 腳位定義
* ------------------------------------------------------------ */
#define PIN_HEATER 4 // IRLZ44N 閘極(10k 下拉,復位期間預設關)
#define PIN_FAN 3 // S8050 基極(風扇循環)
#define PIN_REED 7 // 磁簧開關(開門 = LOW)
#define PIN_SDA 5
#define PIN_SCL 6
#define OLED_ADDR 0x3C
#define SCREEN_W 128
#define SCREEN_H 64
/* ------------------------------------------------------------
* 3. 控制參數
* ------------------------------------------------------------ */
const float RH_ON = 30.0f; // RH 高於此 → 啟動除濕
const float RH_OFF = 25.0f; // RH 低於此 → 停止(5% 遲滯)
const float T_FORCE_OFF = 50.0f; // 箱內溫度軟性上限(PLA 線盤安全)
const float T_FAN_ONLY = 45.0f; // 超過此溫度只停加熱、風扇繼續散
const uint32_t SENSOR_MS = 2000; // SHT31 每 2 秒取樣(I2C 壓力小、夠新)
const uint32_t DOOR_RECHECK = 10UL * 60000; // 關門後 10 分鐘才重估濕度
const uint32_t HUMID_ALARM = 30UL * 60000; // RH>50% 撐 30 分鐘 → 推播
const uint32_t WEEKLY_MS = 7UL * 86400000;
Adafruit_SHT31 sht31;
Adafruit_SSD1306 oled(SCREEN_W, SCREEN_H, &Wire, 0);
/* ------------------------------------------------------------
* 4. 除濕狀態機
* IDLE ─ 未達啟動條件;DRYING ─ 加熱+風扇;
* COOLDOWN ─ 到 45°C 停加熱留風扇;PAUSED ─ 門開
* ------------------------------------------------------------ */
enum DryState { IDLE, DRYING, COOLDOWN, PAUSED, FAULT };
DryState state = IDLE;
float gTemp = 0, gHum = 0;
bool sensorOk = false, doorOpen = false;
uint8_t sensorFailCnt = 0;
uint32_t dryStartMs = 0; // 本輪除濕開始時間
uint32_t doorClosedMs = 0; // 關門時間戳(重估倒數)
uint32_t humidAlarmMs = 0; // RH>50% 計時起點(0 = 未計時)
uint32_t lastCycleMs = 0; // 上次除濕結束(診斷乾燥劑)
uint32_t weeklyMs = 0;
bool heaterOn = false, fanOn = false;
/* 推播佇列:控制迴圈不阻塞,失敗最多滯留 3 則 */
String tgQueue[3];
uint8_t tgHead = 0, tgTail = 0;
void tgPush(const String& msg) {
uint8_t next = (tgHead + 1) % 3;
if (next == tgTail) return; // 佇列滿:丟棄最舊不如丟棄最新,保留現場紀錄
tgQueue[tgHead] = msg;
tgHead = next;
}
/* ------------------------------------------------------------
* tgFlush —— 非阻塞送出佇列(只在 Wi-Fi 連線時)
* 為什麼不同步發:HTTPClient 阻塞可達數秒,
* 控制迴圈卡住 = 過溫保護慢數秒,不可接受
* ------------------------------------------------------------ */
void tgFlush() {
if (tgTail == tgHead || WiFi.status() != WL_CONNECTED) 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=" + urlencode(tgQueue[tgTail]);
int code = http.POST(body);
http.end();
if (code == 200) tgTail = (tgTail + 1) % 3; // 失敗不彈出:下輪重試同一則
}
/* ------------------------------------------------------------
* readSensor —— SHT31 讀值 + 健康檢查
* 回傳:成功 true;連續 3 次失敗標記 FAULT 並停加熱
* 為什麼嚴苛:感測器失靈時還在加熱,是乾燥箱最壞情境
* ------------------------------------------------------------ */
bool readSensor() {
float t = sht31.readTemperature();
float h = sht31.readHumidity();
if (isnan(t) || isnan(h) || h < 0 || h > 100) {
if (++sensorFailCnt >= 3) {
state = FAULT;
tgPush("乾燥箱:SHT31 連續讀取失敗,已停止加熱,請檢查感測器");
return false;
}
return false;
}
sensorFailCnt = 0;
gTemp = t; gHum = h;
return true;
}
/* ------------------------------------------------------------
* setActuators —— 唯一允許操作加熱/風扇的出口
* 所有過溫判斷收斂到這裡,避免多處直接 digitalWrite 漏判
* ------------------------------------------------------------ */
void setActuators(bool heat, bool fan) {
if (gTemp >= T_FORCE_OFF) heat = false; // 軟性上限壓過一切請求
if (heat != heaterOn) { heaterOn = heat; digitalWrite(PIN_HEATER, heat ? HIGH : LOW); }
if (fan != fanOn) { fanOn = fan; digitalWrite(PIN_FAN, fan ? HIGH : LOW); }
}
/* ------------------------------------------------------------
* controlLoop —— 2 秒一次的濕度閉環
* 遲滯:30% 啟動 / 25% 停止;啟停週期 < 5 分鐘 = 乾燥劑失效訊號
* ------------------------------------------------------------ */
void controlLoop(uint32_t now) {
bool reedNow = (digitalRead(PIN_REED) == LOW);
if (reedNow != doorOpen) {
doorOpen = reedNow;
if (doorOpen) { state = PAUSED; setActuators(false, false); } // 開門:全停,別對整個房間除濕
else doorClosedMs = now;
}
if (state == PAUSED) {
if (!doorOpen && now - doorClosedMs > DOOR_RECHECK) state = IDLE; // 關門 10 分鐘後重估
return;
}
if (state == FAULT) { setActuators(false, false); return; }
if (!sensorOk) return;
switch (state) {
case IDLE:
setActuators(false, false);
if (gHum > RH_ON) { state = DRYING; dryStartMs = now; }
break;
case DRYING:
if (gTemp > T_FAN_ONLY) { state = COOLDOWN; setActuators(false, true); break; }
setActuators(true, true);
if (gHum < RH_OFF) {
uint32_t cycleMin = (now - dryStartMs) / 60000;
state = IDLE; setActuators(false, false); lastCycleMs = now;
if (cycleMin < 5) tgPush("乾燥箱:除濕僅 " + String(cycleMin) + " 分鐘即達標又快速回升——乾燥劑可能已飽和");
}
break;
case COOLDOWN:
setActuators(false, true);
if (gTemp < T_FAN_ONLY - 3.0f) state = DRYING; // 降 3°C 才回調,防抖
break;
default: break;
}
}
/* ------------------------------------------------------------
* alarmLoop —— 濕度異常 + 每週提醒
* ------------------------------------------------------------ */
void alarmLoop(uint32_t now) {
if (!sensorOk) return;
if (gHum > 50.0f) {
if (humidAlarmMs == 0) humidAlarmMs = now;
else if (now - humidAlarmMs > HUMID_ALARM) {
humidAlarmMs = 0; // 發一次就重置,避免洗版
tgPush("乾燥箱:濕度 > 50% 已持續 30 分鐘,請確認門是否關好/乾燥劑狀態");
}
} else humidAlarmMs = 0;
if (now - weeklyMs > WEEKLY_MS) {
weeklyMs = now;
tgPush("乾燥箱週報:目前 " + String(gTemp, 1) + "°C / " + String(gHum, 1) + "%RH");
}
}
/* ------------------------------------------------------------
* drawOled —— 500ms 刷新儀表(進度條 = 25~35%RH 線性映射)
* ------------------------------------------------------------ */
void drawOled() {
oled.clearDisplay();
oled.setTextSize(2);
oled.setTextColor(SSD1306_WHITE);
if (!sensorOk) {
oled.setCursor(0, 8); oled.setTextSize(1);
oled.println(state == FAULT ? "SENSOR FAULT" : "SENSOR WAIT");
oled.setCursor(0, 28); oled.println("HEATER LOCKED");
} else {
oled.setCursor(0, 0);
oled.print(gHum, 1); oled.print("%");
oled.setTextSize(1);
oled.setCursor(0, 22);
oled.print(gTemp, 1); oled.print("C ");
switch (state) {
case DRYING: oled.print("DRY+"); break;
case COOLDOWN: oled.print("COOL"); break;
case PAUSED: oled.print("PAUSE"); break;
case FAULT: oled.print("FAULT"); break;
default: oled.print("IDLE"); break;
}
// 進度條:35%RH=空、25%RH=滿(越乾越滿)
int pct = constrain((int)((35.0f - gHum) / 10.0f * 126), 0, 126);
oled.drawRect(0, 44, 128, 10, SSD1306_WHITE);
oled.fillRect(1, 45, pct, 8, SSD1306_WHITE);
}
oled.display();
}
void setup() {
Serial.begin(115200);
delay(1000);
// 致動器先歸零再開放任何邏輯(含燒錄 reset 期間的下拉保護)
pinMode(PIN_HEATER, OUTPUT); digitalWrite(PIN_HEATER, LOW);
pinMode(PIN_FAN, OUTPUT); digitalWrite(PIN_FAN, LOW);
pinMode(PIN_REED, INPUT_PULLUP);
Wire.begin(PIN_SDA, PIN_SCL);
oled.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR);
oled.display();
bool shtOk = sht31.begin(0x44);
Serial.println(shtOk ? "SHT31 OK" : "SHT31 FAIL(加熱鎖定)");
if (!shtOk) state = FAULT;
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_PASS);
Serial.print("連 5GHz Wi-Fi");
uint32_t t0 = millis();
while (WiFi.status() != WL_CONNECTED && millis() - t0 < 15000) { delay(300); Serial.print("."); }
Serial.println(WiFi.status() == WL_CONNECTED ? " OK" : " 失敗(離線控制仍運作)");
tgPush("乾燥箱上電:" + String(state == FAULT ? "感測器異常,加熱已鎖定" : "控制迴路啟動"));
}
void loop() {
uint32_t now = millis();
static uint32_t lastSensor = 0, lastOled = 0;
if (now - lastSensor >= SENSOR_MS) {
lastSensor = now;
sensorOk = readSensor();
controlLoop(now);
alarmLoop(now);
}
if (now - lastOled >= 500) { lastOled = now; drawOled(); }
tgFlush();
delay(5);
}
🏗️ 組裝步驟
📘 圖文版組裝說明書:每一步附示意圖、預估時間與難度標記,可直接列印 A4 帶到工作檯 → 開啟 HTML 組裝說明書
- 列印零件:控制側掛盒、SHT31 護罩、風扇濾網蓋、磁簧固定座×2(全 PETG)
- 箱體開孔:側壁 Ø25mm 兩孔(風扇、感測探頭),開孔蓋圈用熱熔膠密封
- 焊加熱迴路:12V → 55°C 保險絲 → PTC → MOSFET;保險絲鐵氟龍帶纏緊在加熱片本體
- 焊主板:I2C、MOSFET 閘極、風扇驅動、磁簧依表上板;全部焊死 + 熱縮管
- 安全測試(必做):不裝乾燥劑,手捂感測器升濕驗證加熱啟動;把加熱片溫度超過 50°C 驗證軟體斷電;短接保險絲端驗證迴路斷開
- 入殼:主板入側掛盒、乾燥劑鋪箱底、線盤上架、OLED 朝外
- 調參:連續觀察 3 天啟停曲線,潮濕季可把 RH_ON 收緊到 25%
🔧 問題排除
| 問題 | 原因 | 解決方法 |
|---|---|---|
| 濕度降不下來 | 箱體密封不良/乾燥劑飽和 | 紙條夾門縫測試密封;換新乾燥劑後觀察一輪除濕時間 |
| 濕度顯示比實際高很多 | SHT31 護罩被灰塵堵死或貼到箱壁 | 清護罩透氣槽;護罩必須懸在箱內空氣中 |
| 加熱器不啟動作業無訊息 | 感測器 FAULT 鎖定 | 看 OLED 是否顯示 FAULT;查 I2C 接線與 SHT31 地址 |
| Wi-Fi 連不上 | 連到 2.4GHz SSID 或天線被金屬箱壁擋 | C5 預設掃全部頻段,確認路由器 5GHz 有開;板子天線端露出塑膠掛盒 |
| OLED 閃爍重啟 | PTC 啟停瞬間拉垮 5V | 降壓板輸出加 470µF;加熱器地線不與主板共用細線 |
| 門磁誤判常開 | 磁石與開關間隙 > 8mm 或裝反側 | 用列印間隙規對位;磁簧對磁極不敏感但對距離敏感 |
🎯 進階延伸
- 雙艙版:再加一組 SHT31 + 風扇,尼龍艙(15%RH)與 PETG 艙(25%RH)分開控制——C5 的 GPIO 還剩一大把
- 列印機聯動:偵測列印機電源開啟時提前 30 分鐘預熱除濕,列印時濕度正好在低點
- 濕度曲線上雲:每 10 分鐘 POST 到 Home Assistant MQTT,畫出「開門事件 vs 回濕速度」——回濕速度變快就是密封膠條該換了
- 真空抽氣版:加 12V 隔膜泵微負壓,濕度閉環改成壓力+濕度雙閉環,除濕速度翻倍
📦 檔案清單
filament-dry-box/
├── src/
│ └── filament-dry-box.ino # ESP32 程式碼(本頁完整程式)
├── stl/
│ ├── control-pod.stl # 控制側掛盒
│ ├── sht31-shroud.stl # SHT31 通風護罩
│ ├── fan-grille.stl # 風扇濾網蓋
│ └── reed-mount.stl # 磁簧固定座(含 8mm 間隙規)
├── f3d/
│ └── filament-dry-box.f3d # Fusion 360 原始檔
├── manual/
│ └── assembly.html # 圖文組裝說明書
└── README.md # 說明文件