ไม่มีมิเตอร์ 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
สองค่าที่เราสมมุติคือแรงดันกับตัวประกอบกำลัง แรงดันจริงแกว่งได้พอสมควรตามช่วงเวลาและระยะห่างจากหม้อแปลง ส่วนตัวประกอบกำลังของมอเตอร์เปลี่ยนตามภาระที่แบกอยู่ ตอนเดินเบากับตอนเดินเต็มโหลดไม่เท่ากันเลย ผมเลือกใช้ค่าคงที่เพราะต้องการตัวเลขที่เปรียบเทียบกันเองได้ข้ามวัน ไม่ได้ต้องการความแม่นระดับมิเตอร์ ถ้าใครอยากแม่นกว่านี้ทางเดียวคือเปลี่ยนไปใช้มิเตอร์ที่วัดแรงดันด้วย ซึ่งก็คืออีกงานหนึ่ง
จังหวะการอ่านเป็นส่วนหนึ่งของสูตร ห้ามเปลี่ยนเล่นๆ
อันนี้คือกับดักที่ผมอยากเตือนที่สุด กำลังไฟฟ้ามีหน่วยเป็นกิโลวัตต์ ส่วนพลังงานมีหน่วยเป็นกิโลวัตต์ชั่วโมง การแปลงจากอันแรกเป็นอันหลังคือการคูณด้วยเวลา ซึ่งในระบบที่อ่านค่าเป็นรอบๆ แบบนี้ "เวลา" ก็คือช่วงห่างระหว่างการอ่านสองครั้ง
ผมตั้งให้ตัวอ่านเก็บค่ากระแสทุก หนึ่งวินาที แล้วเขียนโค้ดคิดพลังงานแบบง่ายที่สุดคือ หนึ่งตัวอย่างเท่ากับหนึ่งวินาที คูณกำลังไฟฟ้าแล้วหารสามพันหกร้อยได้หน่วยไฟ ซึ่งใช้ได้ดีจนกระทั่งมีคนไปปรับจังหวะการอ่านให้ช้าลงเพื่อลดภาระบัส แล้วตัวเลขค่าไฟตกฮวบทันทีโดยไม่มีใครรู้ว่าทำไม
เขียนคอมเมนต์ตัวใหญ่ๆ ไว้ตรงจุดที่ตั้งรอบการอ่านเลยว่าค่านี้ผูกกับการคิดพลังงานอยู่ ทางที่ดีกว่าคืออย่าสมมุติว่าแต่ละตัวอย่างห่างกันเท่าไหร่ ให้เก็บ 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 ในโค้ดข้างบนคือตัวกันเรื่องนี้ ผมตั้งไว้ที่สิบห้าวินาทีสำหรับข้อมูลที่อ่านทุกวินาที คือยอมให้สะดุดได้บ้างแต่ถ้าหายเกินนั้นถือว่าขาด และบนหน้าจอผมทำเพิ่มอีกชั้นคือถ้าข้อมูลล่าสุดเก่ากว่าสามสิบวินาที ให้ขึ้นแถบเตือนว่า "ไฟดับ หรือสายสัญญาณหลุด" แล้วโชว์ค่าเป็นขีดแทนที่จะโชว์ค่าเก่าค้างไว้ เพราะค่าเก่าที่ค้างอยู่บนจอคือสิ่งที่หลอกคนดูได้แนบเนียนที่สุด
แยก peak กับ off-peak ด้วยเลขคณิตบน epoch
อัตราค่าไฟแบบแยกตามช่วงเวลาคิดคนละราคาระหว่างช่วงพีคกับช่วงนอกพีค สำหรับผู้ใช้กลุ่มที่ผมดูแล ช่วงพีคคือวันจันทร์ถึงศุกร์ เวลา 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 รวบยอดของเมื่อวานตอนตีหนึ่ง หน้าเว็บอ่านจากแถวสรุปที่มีไม่กี่ร้อยแถวต่อเดือน ส่วนข้อมูลของวันนี้ที่ยังไม่ปิดยอด ค่อยคิดสดเฉพาะช่วงตั้งแต่เที่ยงคืนถึงตอนนี้ซึ่งสั้นพอ
- cron ตอนตีหนึ่ง รวบยอดของ "เมื่อวาน" ลง energy_day แล้วเขียนทับแถวเดิมได้ (ใช้ REPLACE หรือ ON DUPLICATE KEY UPDATE)
- หน้าสรุปเดือน อ่านจาก energy_day ล้วน ไม่แตะข้อมูลดิบ
- หน้าสรุปวันนี้ คิดสดจากข้อมูลดิบเฉพาะตั้งแต่เที่ยงคืนถึงปัจจุบัน
- ถ้าแก้อัตราหรือสูตรย้อนหลัง ต้องสั่งคำนวณ energy_day ใหม่ทั้งช่วง อย่าลืมข้อนี้
สรุปว่าตัวเลขชุดนี้ตอบอะไรได้จริง
สิ่งที่ระบบนี้ตอบได้ดีมากคือคำถามเชิงเปรียบเทียบ เช่น การปรับวิธีเดินเครื่องแบบใหม่ประหยัดกว่าแบบเดิมกี่เปอร์เซ็นต์ หรือรอบการทำงานรอบไหนคุ้มที่สุดเมื่อเทียบผลงานต่อหน่วยไฟ เพราะสมมุติฐานเรื่องแรงดันกับตัวประกอบกำลังมันคงที่ทั้งสองฝั่งของการเปรียบเทียบ ความคลาดเคลื่อนเลยหักล้างกันไปเยอะ ส่วนคำถามที่ตอบไม่ได้คือ "เดือนนี้ต้องจ่ายเท่าไหร่" อันนั้นต้องรอบิลอย่างเดียว
ส่วนตัวผมว่าประโยชน์ที่ได้จริงจากการทำเรื่องนี้ ไม่ใช่ตัวเลขบาทที่โชว์บนจอ แต่เป็นการที่พอมีตัวเลขให้เห็นรายวันแล้ว คนเริ่มสังเกตได้ว่าวันไหนผิดปกติ ซึ่งนำไปสู่การเจอว่ามีขั้นตอนหนึ่งที่เดินเครื่องนานเกินจำเป็นอยู่หลายนาทีทุกรอบ พอแก้ตรงนั้นได้คือประหยัดจริงเป็นกอบเป็นกำ ทั้งที่ตัวเลขค่าไฟที่ใช้ชี้เป้าเป็นแค่ค่าประมาณ ใครมีแต่หัววัดกระแสอยู่แล้วยังไม่ได้ทำอะไรกับมัน ลองเริ่มจากสูตรกับตารางสรุปรายวันก่อนก็พอครับ ตรงไหนที่อัตราค่าไฟของแต่ละกลุ่มผู้ใช้ไม่ตรงกับที่ผมยกมา เดี๋ยวกลับมาแก้เพิ่มให้ภายหลังครับ
เคยเจองานแบบนี้ไหม? ติดตรงไหน?
มาแลกวิธีกันต่อในบอร์ด LinuxThai



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