EST. 2026 — บันทึกงานเซิร์ฟเวอร์จริง SYSADMIN · SECURITY · AI · DIRECTADMIN เล่มที่ 01
THE SYSADMIN ALMANAC
หน้าหลัก / Hardware / ไม่มีมิเตอร์ kWh แต่อยากรู้ค่าไฟรายวัน คิดจากกระแสที่วัดได้
Hardware

ไม่มีมิเตอร์ kWh แต่อยากรู้ค่าไฟรายวัน คิดจากกระแสที่วัดได้

โดย Ton-Or ·15 ต.ค. 2026·อ่าน 10 นาที
ไม่มีมิเตอร์ kWh แต่อยากรู้ค่าไฟรายวัน คิดจากกระแสที่วัดได้

หัววัดกระแสบอกได้แค่แอมป์ ไม่ได้บอกหน่วยไฟ แต่ถ้ายอมรับสมมุติฐานเรื่องแรงดันกับตัวประกอบกำลังได้ มันพอจะแปลงเป็นตัวเลขค่าไฟรายวันที่ใช้ตัดสินใจได้ ขอแค่รู้ว่ากำลังประมาณอะไรอยู่และไม่เผลอเอาไปเทียบกับบิลตรงๆ

สรุปใน 1 นาที

อาการ/ปัญหา
ไม่มีมิเตอร์ kWh แต่อยากรู้ค่าไฟรายวันแยกช่วง peak กับ off-peak
ใครควรอ่าน
คนที่มีหัววัดกระแส CT และอยากประมาณหน่วยไฟกับค่าไฟ
ทางแก้แบบสั้น
แปลงแอมป์เป็นกิโลวัตต์โดยสมมุติแรงดันกับตัวประกอบกำลัง ข้ามช่วงไฟดับ แยกช่วงด้วยเลขคณิตบน epoch และสรุปเป็นตารางรายวัน
ข้ามไปขั้นตอน

พอดีระบบที่ผมดูแลอยู่มีโมดูลวัดกระแสต่ออยู่กับหัววัด CT หลายช่อง อ่านผ่าน Modbus ได้ค่าเป็นแอมป์ตรงๆ แต่คำถามที่เจ้าของระบบถามคือ "เครื่องตัวนี้กินไฟวันละกี่บาท" ซึ่งกระโดดจากแอมป์ไปเป็นบาทมันมีหลายขั้นอยู่ ผมเลยทำระบบคิดค่าไฟประมาณการขึ้นมาบนข้อมูลที่มีอยู่ แล้วเจอกับดักสามสี่จุดระหว่างทางที่ถ้าไม่ระวังตัวเลขจะเพี้ยนแบบเนียนมาก บทความนี้ผมเล่าตั้งแต่สูตรไปจนถึงตารางสรุปที่ทำให้หน้าเว็บไม่อืด

ตัวเลขนี้เป็นค่าประมาณ ไม่ใช่มิเตอร์

หัววัด CT วัดได้แค่ กระแส มันไม่ได้วัดแรงดันและไม่ได้วัดตัวประกอบกำลัง สองค่านั้นเราต้องสมมุติเอา ตัวเลขที่ได้เลยเป็นค่าประมาณเสมอ และยัง ไม่รวมค่าความต้องการพลังไฟฟ้า (demand charge ที่คิดจากพีคระดับทั้งโรงงาน) กับ ค่าบริการรายเดือน ซึ่งเป็นรายการที่บวกเพิ่มในบิลจริง ใช้ตัวเลขชุดนี้เพื่อเทียบว่าวันไหนกินมากกว่าวันไหน หรือการปรับวิธีเดินเครื่องช่วยประหยัดได้จริงไหม อย่าเอาไปเถียงกับบิลของการไฟฟ้าเป็นบาทต่อบาท

จากแอมป์เป็นกิโลวัตต์ ต้องยอมสมมุติสองค่า

โหลดสามเฟสสมดุล กำลังไฟฟ้าจริงคิดได้จากสูตรมาตรฐาน

import math

SQRT3 = math.sqrt(3)      # 1.7320508...
V_LINE = 380.0            # แรงดันระหว่างสาย สมมุติค่าคงที่
PF     = 0.85             # ตัวประกอบกำลัง สมมุติตามชนิดโหลด

def kw_from_amp(amp: float) -> float:
    """กระแสหนึ่งค่า -> กำลังไฟฟ้าเป็นกิโลวัตต์ (ค่าประมาณ)"""
    return SQRT3 * V_LINE * amp * PF / 1000.0

# ตัวอย่าง: คอมเพรสเซอร์ที่อ่านได้ 36.6 A
print(round(kw_from_amp(36.6), 2))    # ~20.5 kW

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

1.732รากที่สองของสาม
380 Vแรงดันสมมุติ
0.85ตัวประกอบกำลัง
1 วิหนึ่งตัวอย่าง

จังหวะการอ่านเป็นส่วนหนึ่งของสูตร ห้ามเปลี่ยนเล่นๆ

อันนี้คือกับดักที่ผมอยากเตือนที่สุด กำลังไฟฟ้ามีหน่วยเป็นกิโลวัตต์ ส่วนพลังงานมีหน่วยเป็นกิโลวัตต์ชั่วโมง การแปลงจากอันแรกเป็นอันหลังคือการคูณด้วยเวลา ซึ่งในระบบที่อ่านค่าเป็นรอบๆ แบบนี้ "เวลา" ก็คือช่วงห่างระหว่างการอ่านสองครั้ง

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

ถ้าจะเปลี่ยนรอบการอ่าน ต้องแก้ตัวคูณพร้อมกัน

เขียนคอมเมนต์ตัวใหญ่ๆ ไว้ตรงจุดที่ตั้งรอบการอ่านเลยว่าค่านี้ผูกกับการคิดพลังงานอยู่ ทางที่ดีกว่าคืออย่าสมมุติว่าแต่ละตัวอย่างห่างกันเท่าไหร่ ให้เก็บ timestamp จริงไว้แล้วคำนวณจากส่วนต่างของเวลาระหว่างสองแถวที่ติดกัน วิธีหลังแพงกว่านิดหน่อยตอน query แต่ทนต่อการเปลี่ยนรอบการอ่านและทนต่อการที่ตัวอ่านสะดุดไปบางช่วง

def kwh_between(rows):
    """rows = [(ts_unix, amp), ...] เรียงตามเวลา
       คิดพลังงานจากส่วนต่างเวลาจริง ไม่สมมุติว่าห่างกันกี่วินาที"""
    MAX_GAP = 15          # ห่างเกินนี้ถือว่าข้อมูลขาด ไม่ใช่เครื่องเดินต่อเนื่อง
    total = 0.0
    for (t0, a0), (t1, _a1) in zip(rows, rows[1:]):
        dt = t1 - t0
        if dt <= 0 or dt > MAX_GAP:
            continue                       # ข้ามช่วงที่ข้อมูลขาด
        total += kw_from_amp(a0) * dt / 3600.0
    return total

ช่วงไฟดับต้องข้าม ไม่ใช่ลากเส้นเชื่อม

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

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

แถวข้อมูลรายวินาทีรอบช่วงไฟดับห่างกัน 1 วินาทีห่างกัน 1 วินาทีไฟดับ ไม่มีแถวใหม่คิดพลังงานจากส่วนต่างเวลาคิดพลังงานจากส่วนต่างเวลาช่วงห่างเกิน 15 วินาที (MAX_GAP) ข้าม ไม่คิดพลังงานบนหน้าจอ ถ้าข้อมูลล่าสุดเก่ากว่า 30 วินาที ขึ้นแถบเตือนและโชว์ค่าเป็นขีด
แถวที่ห่างกันไม่เกิน 15 วินาทีถูกนำมาคิดพลังงาน ช่วงที่ข้อมูลขาดถูกข้ามแทนการลากเส้นเชื่อมสองฝั่ง
ทางเดินของตัวเลขCTวัดแอมป์->Modbusอ่านทุก 1 วิ->คิด kWสูตรสามเฟส->สะสม kWhข้ามช่วงขาด->แยกช่วงเวลาpeak / off-peak->คูณอัตรา+ Ft + VAT

แยก peak กับ off-peak ด้วยเลขคณิตบน epoch

อัตราค่าไฟแบบแยกตามช่วงเวลาคิดคนละราคาระหว่างช่วงพีคกับช่วงนอกพีค สำหรับผู้ใช้กลุ่มที่ผมดูแล ช่วงพีคคือวันจันทร์ถึงศุกร์ เวลา 9 โมงเช้าถึง 4 ทุ่ม นอกนั้นเป็นนอกพีคหมด รวมทั้งเสาร์อาทิตย์ทั้งวัน

ช่วงพีคและนอกพีคในหนึ่งสัปดาห์092224จันทร์อังคารพุธพฤหัสศุกร์เสาร์อาทิตย์นอกพีคนอกพีคpeak 09:00-22:00นอกพีคทั้งวันนอกพีคทั้งวันวันหยุดที่การไฟฟ้าประกาศ คิดเป็นนอกพีคทั้งวัน
ช่วงพีคมีเฉพาะวันจันทร์ถึงศุกร์ ตั้งแต่ 9 โมงเช้าถึง 4 ทุ่ม เสาร์อาทิตย์และวันหยุดที่ประกาศเป็นนอกพีคทั้งวัน

ทีแรกผมกะจะดึงข้อมูลดิบทั้งเดือนมาวนใน PHP แล้วใช้ฟังก์ชันวันที่ตัดสินทีละแถว ซึ่งช้ามากเพราะข้อมูลระดับวินาที สุดท้ายผมย้ายไปทำใน SQL ด้วยเลขคณิตบน epoch ล้วนๆ ไม่แตะฟังก์ชันวันที่และไม่พึ่งการตั้ง timezone ของเซิร์ฟเวอร์เลย ซึ่งได้ผลพลอยได้คือย้ายเครื่องแล้วตัวเลขไม่เปลี่ยน

-- เลข 25200 = 7 ชั่วโมง เลื่อน epoch ให้เป็นเวลาท้องถิ่นก่อนหารวัน
--   วันที่แบบเลข : FLOOR((ts_unix + 25200) / 86400)
--   วันในสัปดาห์ : (วันที่แบบเลข + 4) % 7   -> 0=อาทิตย์ ... 6=เสาร์
--   ชั่วโมง      : FLOOR(MOD(ts_unix + 25200, 86400) / 3600)
SELECT
  SUM(CASE WHEN (FLOOR((ts_unix + 25200) / 86400) + 4) % 7 BETWEEN 1 AND 5
            AND FLOOR(MOD(ts_unix + 25200, 86400) / 3600) BETWEEN 9 AND 21
            AND FLOOR((ts_unix + 25200) / 86400) NOT IN (:holiday_days)
           THEN kwh ELSE 0 END) AS kwh_peak,
  SUM(CASE WHEN (FLOOR((ts_unix + 25200) / 86400) + 4) % 7 BETWEEN 1 AND 5
            AND FLOOR(MOD(ts_unix + 25200, 86400) / 3600) BETWEEN 9 AND 21
            AND FLOOR((ts_unix + 25200) / 86400) NOT IN (:holiday_days)
           THEN 0 ELSE kwh END) AS kwh_offpeak
FROM  energy_seconds
WHERE ts_unix >= :from_ts AND ts_unix < :to_ts;

ตัวเลข + 4 ในสูตรหาวันในสัปดาห์มาจากการที่ epoch วันที่ศูนย์ตรงกับวันพฤหัสบดี ใครจะเอาไปใช้ลองตรวจกับวันที่รู้แน่ๆ สักสองสามวันก่อนนะครับ ผิดไปหนึ่งวันแล้วค่าไฟทั้งเดือนเพี้ยนแบบดูไม่ออก

วันหยุดราชการคิดเป็นนอกพีคทั้งวัน

ตัวแปร :holiday_days ข้างบนคือรายการวันหยุดที่การไฟฟ้าประกาศให้คิดเป็นนอกพีคทั้งวัน ซึ่งไม่ตรงกับวันหยุดราชการทั่วไปเป๊ะๆ มีบางวันที่เป็นวันหยุดราชการแต่ยังคิดพีคตามปกติ และวันหยุดชดเชยก็ไม่ได้นับให้ทุกตัว ผมเลยเก็บเป็นรายการตายตัวในไฟล์ตั้งค่า แล้วตั้งเตือนตัวเองไว้ว่าต้องอัปเดตทุกต้นปี

{
  "updated_at": "2026-10-29T06:15:02+07:00",
  "rate": {
    "peak": 4.1839,
    "offpeak": 2.6037,
    "ft": 0.1623,
    "vat": 0.07
  },
  "peak_window": { "days": [1, 2, 3, 4, 5], "hour_from": 9, "hour_to": 21 },
  "holidays": ["2026-01-01", "2026-04-13", "2026-04-14", "2026-04-15",
               "2026-05-01", "2026-12-05", "2026-12-10", "2026-12-31"],
  "note": "รายการวันหยุดต้องอัปเดตทุกต้นปี ค่า ft มาจากตัวดึงอัตโนมัติรายสัปดาห์"
}
แยกให้ชัดว่าอะไรดึงอัตโนมัติ อะไรแก้มือ

ในไฟล์นี้มีค่าเดียวที่ตัวดึงอัตโนมัติเขียนทับได้คือ ft ส่วนอัตราพีค นอกพีค และรายการวันหยุด ผมกำหนดให้แก้ด้วยมือเท่านั้น เหตุผลคือสามตัวหลังเปลี่ยนไม่บ่อยแต่กระทบตัวเลขทั้งเดือน ถ้าปล่อยให้สคริปต์ไปไล่ดึงจากหน้าเว็บแล้ววันหนึ่งเขาเปลี่ยนหน้า สคริปต์จะเขียนค่าว่างทับลงมาเงียบๆ แล้วค่าไฟทั้งระบบกลายเป็นศูนย์โดยไม่มีใครรู้

ค่า Ft เปลี่ยนเป็นงวด ตั้ง cron ไปดึงมาเอง

ค่า Ft เป็นค่าที่ประกาศเป็นงวด ไม่ได้เปลี่ยนบ่อยแต่ลืมอัปเดตแล้วตัวเลขคลาดทั้งงวด ผมเลยเขียนตัวดึงเล็กๆ ให้เดินทุกสัปดาห์ ไปอ่านหน้าประกาศอัตราค่าไฟสาธารณะ แล้วเขียนลงไฟล์ตั้งค่าเฉพาะตอนที่ค่าเปลี่ยนจริง

import json, pathlib, re, urllib.request

CFG  = pathlib.Path("/var/www/html/energy/rate_config.json")
PAGE = "https://www.example-utility.or.th/tariff/ft"

def fetch_ft():
    html = urllib.request.urlopen(PAGE, timeout=30).read().decode("utf-8", "ignore")
    m = re.search(r"Current Ft.*?<span>([-\d.]+)</span>", html, re.S)
    if not m:
        raise RuntimeError("หน้าเว็บเปลี่ยนรูปแบบ หา Ft ไม่เจอ")
    return float(m.group(1))

cfg = json.loads(CFG.read_text(encoding="utf-8"))
new = fetch_ft()
old = cfg["rate"]["ft"]

if abs(new - old) < 1e-9:
    print(f"[OK] Ft ยังเท่าเดิม {old}")
else:
    cfg["rate"]["ft"] = new
    CFG.write_text(json.dumps(cfg, ensure_ascii=False, indent=2), encoding="utf-8")
    print(f"[UPDATE] Ft {old} -> {new}")
# ดึงค่า Ft ทุกเช้าวันจันทร์ แล้วเก็บ log ไว้ดูย้อนหลัง
0 6 * * 1 /usr/bin/python3 /opt/energy/rate_update.py >> /var/log/energy/rate.log 2>&1

ไฟล์ตั้งค่านี้ผมให้เป็นแหล่งความจริงที่เดียว ทั้งหน้าเว็บที่เครื่องหน้างานและระบบรวมศูนย์อ่านจากตัวเดียวกัน เวลาอัตราเปลี่ยนแก้ที่เดียวจบ ไม่ต้องไล่แก้ค่าคงที่ที่กระจายอยู่ในโค้ดสามที่แล้วลืมไปหนึ่งที่

ตารางสรุปรายวัน ทำให้หน้าเว็บเร็วขึ้นเจ็ดเท่า

ตอนแรกหน้าสรุปรายเดือนคำนวณสดจากข้อมูลดิบระดับวินาทีทุกครั้งที่มีคนเปิด ผลคือรอห้าวินาทีต่อการกดหนึ่งครั้ง ซึ่งใช้ไม่ได้ ผมเลยทำตารางสรุปรายวันขึ้นมา ให้ cron รวบยอดของเมื่อวานทุกเช้า แล้วหน้าเว็บอ่านจากตารางสรุปแทน

CREATE TABLE energy_day (
  day_epoch   INT         NOT NULL,   -- FLOOR((ts+25200)/86400) เวลาท้องถิ่น
  channel     SMALLINT    NOT NULL,   -- ช่อง CT / อุปกรณ์
  kwh_peak    DECIMAL(12,4) NOT NULL DEFAULT 0,
  kwh_offpeak DECIMAL(12,4) NOT NULL DEFAULT 0,
  samples     INT         NOT NULL DEFAULT 0,   -- ไว้ดูว่าวันนั้นข้อมูลครบไหม
  updated_at  TIMESTAMP   NOT NULL DEFAULT CURRENT_TIMESTAMP
                          ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (day_epoch, channel)
);

คอลัมน์ samples มีไว้เพราะวันที่ไฟดับไปครึ่งวันกับวันที่ข้อมูลครบ ตัวเลขหน่วยไฟจะต่างกันมาก ถ้าไม่เก็บจำนวนตัวอย่างไว้ เวลามีคนถามว่า "ทำไมวันที่ยี่สิบใช้ไฟน้อยจัง" เราจะตอบไม่ได้ว่าเพราะเครื่องหยุดจริง หรือเพราะตัวอ่านตายไปครึ่งวัน

ก่อนมีตารางสรุป

รอ 5 วินาทีต่อหนึ่งคลิก

คิดสดจากข้อมูลดิบระดับวินาทีทุกครั้ง ยิ่งเดือนไหนข้อมูลเยอะยิ่งช้า พอมีคนเปิดพร้อมกันสองสามคนก็เริ่มไปกวนงานอื่นบนเครื่องเดียวกัน

หลังมีตารางสรุป

เหลือ 0.7 วินาที

cron รวบยอดของเมื่อวานตอนตีหนึ่ง หน้าเว็บอ่านจากแถวสรุปที่มีไม่กี่ร้อยแถวต่อเดือน ส่วนข้อมูลของวันนี้ที่ยังไม่ปิดยอด ค่อยคิดสดเฉพาะช่วงตั้งแต่เที่ยงคืนถึงตอนนี้ซึ่งสั้นพอ

  1. cron ตอนตีหนึ่ง รวบยอดของ "เมื่อวาน" ลง energy_day แล้วเขียนทับแถวเดิมได้ (ใช้ REPLACE หรือ ON DUPLICATE KEY UPDATE)
  2. หน้าสรุปเดือน อ่านจาก energy_day ล้วน ไม่แตะข้อมูลดิบ
  3. หน้าสรุปวันนี้ คิดสดจากข้อมูลดิบเฉพาะตั้งแต่เที่ยงคืนถึงปัจจุบัน
  4. ถ้าแก้อัตราหรือสูตรย้อนหลัง ต้องสั่งคำนวณ energy_day ใหม่ทั้งช่วง อย่าลืมข้อนี้

สรุปว่าตัวเลขชุดนี้ตอบอะไรได้จริง

ใช้เพื่อเปรียบเทียบ ไม่ใช่เพื่อกระทบยอด

สิ่งที่ระบบนี้ตอบได้ดีมากคือคำถามเชิงเปรียบเทียบ เช่น การปรับวิธีเดินเครื่องแบบใหม่ประหยัดกว่าแบบเดิมกี่เปอร์เซ็นต์ หรือรอบการทำงานรอบไหนคุ้มที่สุดเมื่อเทียบผลงานต่อหน่วยไฟ เพราะสมมุติฐานเรื่องแรงดันกับตัวประกอบกำลังมันคงที่ทั้งสองฝั่งของการเปรียบเทียบ ความคลาดเคลื่อนเลยหักล้างกันไปเยอะ ส่วนคำถามที่ตอบไม่ได้คือ "เดือนนี้ต้องจ่ายเท่าไหร่" อันนั้นต้องรอบิลอย่างเดียว

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

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

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

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

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

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