EST. 2026 — บันทึกงานเซิร์ฟเวอร์จริง SYSADMIN · SECURITY · AI · DIRECTADMIN เล่มที่ 01
THE SYSADMIN ALMANAC
หน้าหลัก / Linux / อนุมานจังหวะ defrost ห้องเย็นจากความชันอุณหภูมิ ไม่ง้อเซนเซอร์ความชื้น
Linux

อนุมานจังหวะ defrost ห้องเย็นจากความชันอุณหภูมิ ไม่ง้อเซนเซอร์ความชื้น

โดย Ton-Or ·22 ก.ค. 2026·อ่าน 10 นาที
อนุมานจังหวะ defrost ห้องเย็นจากความชันอุณหภูมิ ไม่ง้อเซนเซอร์ความชื้น

ไม่มีเซนเซอร์ความชื้นสักตัวในห้อง แต่กราฟอุณหภูมิบอกได้หมดว่าคอยล์เริ่มตันด้วยน้ำแข็ง เพราะ frost ที่หนาขึ้นทำให้ห้องเย็นช้าลงตามฟิสิกส์ ไม่ใช่เรื่องบังเอิญ ถ้าอ่านความชันเป็น ก็สั่ง defrost ตอนที่ควรได้ ไม่ต้องตั้ง timer เดาไว้ล่วงหน้า

พอดีมีห้องเย็นตัวหนึ่งที่ใส่วุ้นเส้นเปียกเข้าไปเพื่อรีดน้ำออกจากเส้น (freeze-dehydration) ต้องคุมอุณหภูมิราว -11 ถึง -14°C แต่ทั้งห้องไม่มีเซนเซอร์ความชื้นสักตัว มีแค่ 4 จุดวัดอุณหภูมิกับกระแสไฟของคอมเพรสเซอร์/พัดลม/ฮีตเตอร์ defrost ที่อ่านผ่าน RS485 ทุกไม่กี่วินาที คำถามคือจะรู้ได้ยังไงว่าคอยล์เริ่มตันด้วยน้ำแข็งแล้วควรสั่ง defrost ผมเลยไปไล่ดูว่าความชันของกราฟอุณหภูมิมันบอกอะไรได้บ้าง กลายเป็นว่าบอกได้เกือบหมด

ชื่อโรงงานและเลข register ในบทความเป็นตัวอย่าง ส่วนสมการ ค่าคงที่ และโค้ดคือของจริงที่คำนวณตามได้

frost เกาะคอยล์ ทำไมกราฟอุณหภูมิถึงราบลง

หัวใจคือสมการสมดุลพลังงานของห้องแบบก้อนรวม (lumped-capacitance) มองห้อง+ของ+โครงเป็นมวลความร้อนก้อนเดียว:

# สมดุลพลังงานของห้อง (lumped-capacitance)
#   (m*cp)_eff * dT/dt = Q_load - Q_evap
#
# (m*cp)_eff : thermal mass ของอากาศ + วุ้นเส้น + โครง (J/K)
# Q_load     : โหลดเข้า (แทรกซึม + ของที่ใส่ + พัดลม + ความร้อน defrost ตกค้าง)
# Q_evap     : ความเย็นที่คอยล์ดึงออก (W)
#
# ตอนคอมเดินดึงอุณหภูมิลง จัดรูปได้เป็น
#   dT/dt = -(Q_evap - Q_load) / (m*cp)_eff
# แปลว่า |dT/dt| (ความชันการลด) แปรผันตรงกับ Q_evap

ทีนี้ Q_evap ที่คอยล์เขียนได้เป็น Q_evap = U * A * ΔT_lm โดย U คือ overall heat-transfer coefficient, A คือพื้นที่แลกเปลี่ยนความร้อน, ΔT_lm คือผลต่างอุณหภูมิเฉลี่ยแบบ log-mean ระหว่างน้ำยากับอากาศ

frost ที่เกาะคอยล์ทำร้าย Q_evap พร้อมกันสองทาง คือ frost เป็นฉนวน (k ราว 0.1 W/m·K) ไปเพิ่มความต้านทานความร้อน ทำให้ U ลด และตัวมันเองไปอุดช่องลมระหว่างครีบ ลมผ่านคอยล์ได้น้อยลง A กับ h_air ลด ตามไปด้วย ผลรวมคือ U·A ตกลงเรื่อยๆ ตามเวลา Q_evap เลยลด และเมื่อ Q_evap ลด ความชันการลดอุณหภูมิของห้องก็ราบลงตามสมการข้างบน นี่แหละที่สังเกตได้ว่า "ห้องลดช้าลง = น้ำเกาะคอยล์"

frost เกาะคอยล์แล้วเกิดอะไรต่อfrost หนาขึ้นฉนวน k~0.1 W/m·K→U·A ลดต้านความร้อน + อุดช่องลม→Q_evap ลดคอยล์ดึงความร้อนได้น้อยลง→|dT/dt| ราบห้องเย็นช้าลง

อ้างอิงตัวเลขที่มีคนวัดไว้ frost หนาเกิน 5mm เริ่มเสียพลังงาน 5-30%, หนาราว 25mm capacity ตกได้ถึง ~32% เพราะฉะนั้นความชันที่ราบลง ไม่ใช่แค่สัญญาณคร่าวๆ มันคือตัวแทน (proxy) ของ U·A ที่ลดลงตรงๆ

จับ frost ด้วย time-constant τ ไม่ใช่ slope ดิบ

ปัญหาของการเอา slope ดิบมาเทียบกันคือ ตอนห้องเข้าใกล้ setpoint slope จะราบลงเองตามฟิสิกส์ (exponential approach) ไม่ใช่เพราะ frost ผมเลยไม่วัด slope เป็นตัวเลขเดียว แต่ fit ทั้งเส้น pull-down เข้ากับสมการ Newton cooling แล้วดึงค่า time-constant τ ออกมา ซึ่ง τ = (m·cp)_eff / (U·A) — frost ทำให้ U·A ลด τ เลยโตขึ้น ตรงไปตรงมา

ช่วง pull-down หนึ่งรอบ ระยะห่างจาก T_inf (สัดส่วนเทียบตอนเริ่ม)0102030405060เวลาหลังคอมเริ่มดึง (นาที)เหลือ 37% ที่เวลา = τคอยล์เพิ่งล้าง τ ~ 18 นาทีน้ำเกาะแล้ว τ ~ 26 นาที (แย่ลง 44%)τ 18τ 26ถ้า τ แย่กว่า baseline ตั้งแต่ 30% ขึ้นไป ระบบสั่ง defrost
เส้นโค้งเป็นภาพร่างตามสมการ Newton cooling เส้นที่ τ โตขึ้นลดลงช้ากว่า ความต่างตรงนี้คือสิ่งที่ใช้ตัดสินว่าถึงเวลา defrost
import numpy as np
from scipy.optimize import curve_fit

def newton_cooling(t_min, T_inf, T0, tau):
    # T(t) = T_inf + (T0 - T_inf) * exp(-t/tau)
    return T_inf + (T0 - T_inf) * np.exp(-t_min / tau)

def fit_tau(t_min, temp_c):
    """fit หนึ่งรอบ pull-down -> คืน tau (นาที). tau ยิ่งมาก = คอยล์ยิ่งตัน"""
    p0 = (float(np.min(temp_c)), float(temp_c[0]), 20.0)   # เดา T_inf, T0, tau เริ่มต้น
    (T_inf, T0, tau), _ = curve_fit(newton_cooling, t_min, temp_c, p0=p0, maxfev=10000)
    return tau

# ตัวอย่างจากรอบจริง: คอยล์เพิ่งล้าง fit ได้ tau ~ 18 นาที
# อีกไม่กี่รอบต่อมาน้ำเกาะจน fit ได้ tau ~ 26 นาที = แย่ลง +44%

ก่อนจะ fit ต้องแยก "รอบ pull-down" ออกมาให้สะอาดก่อน คือเอาเฉพาะช่วงที่คอมเดินต่อเนื่องและไม่ได้ defrost อยู่ ไม่งั้นค่า τ เพี้ยนหมด:

import pandas as pd

def pulldown_cycles(df, comp_thr=5.0):
    """
    df: คอลัมน์ ts(unix), temp(°C กลางห้อง), i_comp(A), i_defrost(A)
    คืน list ของ DataFrame แต่ละรอบ pull-down (comp ON, defrost OFF)
    """
    on = (df["i_comp"] > comp_thr) & (df["i_defrost"] < 1.0)   # กรอง comp ON + defrost OFF
    grp = (on != on.shift()).cumsum()                          # นับ segment ต่อเนื่อง
    cycles = []
    for _, seg in df[on].groupby(grp[on]):
        if len(seg) < 30:                 # รอบสั้นเกินไป ตัดทิ้ง
            continue
        seg = seg.copy()
        seg["t_min"] = (seg["ts"] - seg["ts"].iloc[0]) / 60.0
        cycles.append(seg)
    return cycles
อย่าเทียบ slope ดิบข้ามช่วงอุณหภูมิ

ตอนห้องเข้าใกล้ setpoint slope จะราบลงเองตามฟิสิกส์ ไม่ใช่ frost เสมอ ผมเลยไม่เอา slope ดิบมาเทียบตรงๆ แต่ใช้ τ ทั้งเส้น หรือถ้าจะเทียบ slope ก็ต้องจับเวลาช่วงเดียวกันทุกครั้ง เช่น -5 → -10°C ใช้กี่นาที และต้องกรองเฉพาะ comp ON กับ defrost OFF ถ้าเผลอเอาช่วงคอมหยุดเข้ามาปน ค่าจะพังทันที

กฎ demand-defrost: τ แย่กว่า baseline เกินเกณฑ์ = สั่งละลาย

พอมี τ ต่อรอบแล้ว ที่เหลือคือเทียบกับ baseline ของคอยล์ที่เพิ่งล้างเสร็จ ถ้า τ โตเกินเกณฑ์ (ผมเริ่มที่ 30%) แปลว่า frost สะสมพอที่จะคุ้มค่าละลายแล้ว logic ทั้งหมดสั้นแค่นี้:

def decide_defrost(tau_now, tau_baseline, threshold=0.30):
    """คืน (should_defrost, degrade) — degrade = τ แย่ลงกี่ %"""
    degrade = (tau_now - tau_baseline) / tau_baseline
    return degrade >= threshold, degrade

# ต่อกับ pipeline จริง
cycles = pulldown_cycles(df)
tau_series = [fit_tau(c["t_min"].to_numpy(), c["temp"].to_numpy()) for c in cycles]

tau_baseline = tau_series[0]          # รอบแรกหลังล้างคอยล์ = คอยล์สะอาด
tau_now = tau_series[-1]
go, degrade = decide_defrost(tau_now, tau_baseline)
print(f"tau baseline={tau_baseline:.1f} min  now={tau_now:.1f} min  "
      f"degrade={degrade*100:.0f}%  -> {'DEFROST' if go else 'hold'}")

ข้อดีที่เห็นชัดคือมันปรับตามของเอง ช่วงแรกที่วุ้นเส้นยังเปียกมาก น้ำระเหยไปเกาะคอยล์เร็ว τ แย่เร็ว ระบบก็ defrost ถี่เองเพื่อระบายน้ำออกไว พอเส้นแห้งขึ้น frost ช้าลง τ ก็ค่อยๆ แย่ช้าลง defrost ห่างออกเอง ไม่ต้องมานั่งจูน timer ตามฤดูหรือตามน้ำหนักของที่ใส่

Timer defrost

ตั้งเวลาตายตัว เดาไว้ล่วงหน้า

ช่วงของเปียกมาก frost เกาะเร็ว แต่ timer ยังไม่ถึงเวลา คอยล์ตันไปแล้ว รีดน้ำช้า กินไฟเพิ่ม พอเส้นแห้ง frost ช้าลง timer กลับสั่ง defrost ถี่เกินจำเป็น เสียทั้งค่าฮีตเตอร์และภาระ re-cooling ฟรีๆ

Demand defrost

สั่งตาม τ ที่วัดได้จริง

τ แย่ถึงเกณฑ์เมื่อไหร่ค่อยละลาย ปรับความถี่ตามน้ำที่ออกจากเส้นเอง เปียกมาก defrost ถี่ แห้งแล้วห่างออก ไม่ต้องแก้ตัวเลข timer เลย

COP กับค่าไฟ ทำไม frost แพงกว่าที่คิด

frost ไม่ได้แค่ทำให้เย็นช้า มันทำให้กินไฟต่อความเย็นแพงขึ้นด้วย เพราะพอ Q_evap ตก คอมต้องเดินนานขึ้นเพื่อดึงความร้อนก้อนเดิม ตรงนี้ต้องรู้ COP จริงของระบบก่อน อย่าเดา R404A ที่อุณหภูมิ low-temp แบบนี้ COP จริงจะอยู่ราว 0.4-0.6 ของ Carnot เท่านั้น ผมคำนวณจาก enthalpy จริงด้วย CoolProp ที่ Te/Tc ที่วัดได้:

import math
from CoolProp.CoolProp import PropsSI

def cop_r404a(Te_C, Tc_C, superheat=5.0, subcool=3.0, eta_isen=0.65):
    Te, Tc = Te_C + 273.15, Tc_C + 273.15
    p_low  = PropsSI('P', 'T', Te, 'Q', 1, 'R404A')   # ความดันด้าน evaporator
    p_high = PropsSI('P', 'T', Tc, 'Q', 0, 'R404A')   # ความดันด้าน condenser

    # state 1: ทางออก evaporator (superheated)
    h1 = PropsSI('H', 'T', Te + superheat, 'P', p_low, 'R404A')
    s1 = PropsSI('S', 'T', Te + superheat, 'P', p_low, 'R404A')
    # state 2: อัดแบบ isentropic ไป p_high แล้วหารด้วยประสิทธิภาพคอมจริง
    h2s = PropsSI('H', 'P', p_high, 'S', s1, 'R404A')
    h2  = h1 + (h2s - h1) / eta_isen
    # state 3: ทางออก condenser (subcooled), state 4: หลัง expansion (h4 = h3)
    h3 = PropsSI('H', 'T', Tc - subcool, 'P', p_high, 'R404A')
    h4 = h3

    cop = (h1 - h4) / (h2 - h1)            # COP = Q_evap / W_comp
    cop_carnot = Te / (Tc - Te)            # เพดาน Carnot
    return cop, cop_carnot

cop, ceil = cop_r404a(Te_C=-25.0, Tc_C=40.0)
print(f"COP(R404A) = {cop:.2f}   Carnot ceiling = {ceil:.2f}   ratio = {cop/ceil:.2f}")
# ~ COP 1.7, Carnot 3.8, ratio ~0.45  <- ตรงกับที่คาดไว้สำหรับ low-temp R404A

จุดที่ต้องจำคือ Te ยิ่งต่ำ (ห้องเย็นจัด) และ Tc ยิ่งสูง (วันร้อน คอนเดนเซอร์ระบายไม่ทัน) COP ยิ่งตก ยิ่งกินไฟต่อความเย็นหนึ่งหน่วยแพงขึ้น ส่วนกำลังไฟฟ้าที่วัดจากกระแสจริงต่อ 3 เฟส คิดง่ายๆ ด้วย P = √3·V·I·PF:

def power_3ph_kw(current_a, v_line=380.0, pf=0.85):
    return math.sqrt(3) * v_line * current_a * pf / 1000.0

p_comp    = power_3ph_kw(33.0)              # กระแสคอมจาก register
p_defrost = power_3ph_kw(28.0, pf=1.0)      # ฮีตเตอร์เป็น resistive -> PF = 1
print(f"comp ~ {p_comp:.1f} kW   defrost heater ~ {p_defrost:.1f} kW")
2838kJ/kg h_sublimation น้ำแข็ง→ไอ
334kJ/kg h_fusion ละลาย frost
~0.1W/m·K k ของ frost (ฉนวน)
0.45xCOP จริง เทียบ Carnot R404A

รีดน้ำออกกี่กิโลต่อรอบ defrost

พอ defrost ละลาย frost น้ำก็ไหลออกทางท่อ drain ออกนอกระบบ นี่แหละคือขั้นที่เก็บเกี่ยวน้ำออกจากห้องจริงๆ (frost ที่เกาะคอยล์ก็คือน้ำที่ออกจากเส้นไปเกาะจุดเย็นสุด) ถ้าอยากรู้คร่าวๆ ว่ารีดน้ำออกกี่กิโลต่อรอบ ใช้พลังงาน defrost หารด้วยความร้อนแฝงการหลอมเหลว โดยหักส่วนที่ไปอุ่นโลหะ/อากาศทิ้ง:

h_sf = 334.0      # kJ/kg  latent heat of fusion (ละลายน้ำแข็ง->น้ำ)
h_sg = 2838.0     # kJ/kg  latent heat of sublimation (น้ำแข็ง->ไอ)

def water_harvested_kg(p_heater_kw, t_defrost_s, melt_fraction=0.6):
    """melt_fraction = สัดส่วนพลังงานฮีตเตอร์ที่ใช้ละลายจริง (ที่เหลืออุ่นโลหะ/ห้อง)"""
    e_kj = p_heater_kw * t_defrost_s       # kW * s = kJ
    e_melt = e_kj * melt_fraction
    return e_melt / h_sf

m = water_harvested_kg(p_heater_kw=18.4, t_defrost_s=600)
print(f"น้ำที่รีดออกราว {m:.1f} kg/รอบ defrost")

ค่านี้เป็นแค่ประมาณการ ทางที่แม่นกว่าคือชั่งน้ำหนักวุ้นเส้นก่อน/หลัง หรือวัดน้ำที่ drain ตรงๆ เพราะไม่มีเซนเซอร์ความชื้น อันนั้นคือ ground truth ที่ดีสุด ส่วนพลังงานที่ต้องป้อนเพื่อรีดน้ำออกจากเส้นเองนั้นสูงมาก น้ำ 1 kg ที่ระเหิด (sublimate) ออกไปต้องใช้ราว 2838 kJ ซึ่งคอยล์ต้องรับภาระนี้แล้วคายกลับมาเป็น frost อีกที เข้าใจตรงนี้แล้วจะเห็นว่าทำไม frost บนคอยล์ถึงทำให้อัตราการรีดน้ำช้าลง เพราะพอผิวคอยล์ถูก frost หุ้มจนอุ่นขึ้น ความดันไอที่ผิวก็สูงขึ้น แรงขับที่ดึงน้ำจากเส้น (ผลต่างความดันไอ) เลยลดลง อีกเหตุผลที่ต้อง defrost เพื่อคงอัตราแห้ง

trade-off ที่ต้อง optimize

defrost ถี่ไปคอยล์สะอาดตลอด Q สูง แต่เปลืองค่าฮีตเตอร์ + re-cooling + เวลาคอมหยุด · defrost ห่างไป frost หนา Q ตก เย็นช้า รีดน้ำช้า จุดเหมาะคือ demand-defrost ตาม τ ไม่ใช่ timer ตายตัว เป้าที่ผมเล็งคือ kWh ต่อ kg-น้ำ ต่ำสุด ไม่ใช่คอยล์สวยที่สุด

ต่อ pipeline เข้า cron

ที่เหลือคือทำให้มันเดินเอง ผมแยกเป็นสองขา ขาแรกตัวอ่านค่าจาก controller ผ่าน RS485 (Modbus RTU) แล้วต่อท้ายไฟล์ csv ขาสองตัวตัดสินใจที่รันเป็นระยะแล้วสั่ง defrost ผ่าน relay:

  1. poller อ่าน register (temp กลางห้อง, กระแสคอม, กระแสฮีตเตอร์ defrost) ทุก 1-5 วินาที เขียนลง csv
  2. ตัวตัดสินใจอ่าน csv ย้อนหลัง ตัดรอบ pull-down กรอง comp ON + defrost OFF
  3. fit τ ทีละรอบ เทียบ baseline ถ้าแย่เกินเกณฑ์สั่ง defrost แล้วรีเซ็ต baseline ใหม่หลังละลายเสร็จ
#!/usr/bin/env bash
# coldroom-poll.sh — อ่าน RS485 แล้วต่อท้าย csv (ตัวอย่าง register/พอร์ต)
PORT=/dev/ttyUSB0          # USB-RS485 adapter
UNIT=1                     # Modbus unit id
OUT=/var/log/coldroom/telemetry.csv
mkdir -p "$(dirname "$OUT")"
while true; do
  ts=$(date +%s)
  # reg4=temp*10(°C), reg8=กระแสคอม*10(A), reg11=กระแสฮีตเตอร์*10(A)
  read t i_comp i_def < <(python3 /opt/coldroom/read_regs.py "$PORT" "$UNIT" 4 8 11)
  echo "$ts,$t,$i_comp,$i_def" >> "$OUT"
  sleep 5
done

ตัวตัดสินใจไม่ต้องรันค้าง ให้ cron เรียกทุก 5 นาทีพอ อ่าน csv ย้อนหลังพอให้ครอบคลุมรอบ pull-down ล่าสุด:

# /etc/cron.d/coldroom-defrost
*/5 * * * * coldroom /opt/coldroom/venv/bin/python /opt/coldroom/defrost_decide.py >> /var/log/coldroom/defrost.log 2>&1

เดินมาถึงตรงนี้ผมได้ตัวเลขที่จับต้องได้แทนการเดา τ ตัวเดียวเล่าได้ทั้งว่าคอยล์ตันแค่ไหน ควรละลายตอนไหน และรีดน้ำออกได้เท่าไหร่ต่อรอบ ทั้งหมดจากอุณหภูมิกับกระแสไฟที่มีอยู่แล้ว ไม่ต้องซื้อเซนเซอร์ความชื้นเพิ่มเลย ใครจะเอาโครงนี้ไปปรับ เกณฑ์ 30% กับ melt_fraction ควรจูนตามเครื่องของตัวเองนะครับ ค่าที่ผมใส่เป็นจุดตั้งต้น ตรงไหนผมอธิบายตกหล่นหรือพิมพ์ผิด เดี๋ยวกลับมาแก้เพิ่มให้ภายหลังครับ

เคยเจองานแบบนี้ไหม? ติดตรงไหน?

มาแลกวิธีกันต่อในบอร์ด LinuxThai

→ คุยต่อในบอร์ด

ความคิดเห็น 0

ยังไม่มีความคิดเห็น — เป็นคนแรกเลย