Smart Irrigation Control System.
โปรเจกต์ Smart Irrigation ที่แยก Measurement, Decision, Actuation และ Feedback ออกจากกัน พร้อม freshness, hysteresis, offline/manual behavior และ fail-closed control guards
Objective
สร้างแกนควบคุม Smart Irrigation ที่ วัด → ตัดสินใจ → สั่งงาน → ตรวจผล ได้แบบ deterministic และอธิบายเหตุผลของทุก decision ได้ โดยไม่ให้ Dashboard, Cloud หรือ Sensor ตัวเดียวกลายเป็น dependency ที่ทำให้ Pump/Valve ทำงานผิดเมื่อข้อมูล stale หรือระบบบางชั้นล้มเหลว
Project นี้เป็นขั้นถัดจากบท Smart Irrigation System และใช้ Foundation จาก Soil Moisture, Pump/Valve, Pressure/Flow และ Offline/Fail-safe มารวมเป็น behavior ที่ทดสอบได้จริง
System boundary
Soil / Tank / Field Measurements
↓
Quality + Freshness Gate
↓
Irrigation Decision Core
↓
Command Owner / Zone State
↓
Pump + Valve Output
↓
Pump / Valve / Flow Feedback
↓
Verify → Continue / Stop / Fault
↓
Telemetry / Dashboard / Alerts
หลักสำคัญคือ Dashboard ไม่ใช่ control loop และ Cloud ไม่ใช่เจ้าของ state หลักของการให้น้ำแบบ local ใน MVP นี้
Deterministic core
Implementation milestone นี้เพิ่ม pure domain logic ที่ src/lib/smart-irrigation.ts, controller-cycle/scenario adapter ที่ src/lib/smart-irrigation-simulator.ts และ validation fixtures ที่ scripts/validate-smart-irrigation.mjs เพื่อ lock behavior สำคัญก่อนต่อ Hardware จริง
Core ตอนนี้แยก responsibility หลักออกจากกัน:
evaluateIrrigationDecision()— ตัดสินใจว่าจะ Start / Continue / Stop / Hold / BlockevaluateTankSupply()+evaluateIrrigationDecisionWithTank()— แปลง Tank measurement ที่มี quality/freshness เป็น water-supply guard โดยไม่ลดเหลือ boolean ตั้งแต่ต้นทางselectIrrigationCommandOwner()— เลือกเจ้าของ shared Pump แบบ deterministic ระหว่างหลาย Zone และรักษา active owner จนกว่าจะ releaseevaluateIrrigationFeedback()— ตรวจว่าคำสั่งที่ส่งออกไปเกิดผลจริงหรือไม่จาก Pump, Valve และ Flow feedbackevaluateIrrigationZoneRuntime()— คุม stateIDLE / WATERING / FAULT, maximum duration, latched fault และ recoverycreateIrrigationEvent()— contract สำหรับ Decision / Feedback / State Transition / Command Owner event ที่ชั้น logging ใช้ต่อได้
การแยก responsibility เหล่านี้ทำให้ Command ≠ Physical Result, หลาย Zone ไม่แย่ง shared Pump กัน และ fault ไม่ถูกซ่อนไว้หลัง retry loop อัตโนมัติ
Measurement contract
Measurement ทุกตัวที่มีผลต่อ control ต้องมีอย่างน้อย:
value- engineering
unit timestampของเวลาที่วัดจริงsourceIdquality:good,suspect,invalid- freshness ที่คำนวณจาก
timestampเทียบกับ stale policy ของ measurement นั้น
Freshness มีสามสถานะ:
fresh— ใช้ประกอบ decision ได้ตาม policystale— มีค่าล่าสุดแต่เก่าเกินกำหนด ห้ามแสดงเหมือนเป็น current measurementunavailable— ไม่มีค่า, timestamp ใช้ไม่ได้ หรือ quality เป็น invalid
สำหรับ automatic control ใน MVP นี้ Soil Moisture ต้องเป็นทั้ง fresh และ good มิฉะนั้นระบบ fail closed และไม่เริ่ม watering ใหม่
Tank level ใช้ contract เดียวกันแทน boolean waterSupplyAvailable ใน controller ชั้นใหม่ โดย evaluateTankSupply() แยก available, tank-level-low, tank-data-unavailable และ invalid-tank-policy ออกจากกัน ก่อนส่ง availability เข้า decision core จึงยังเก็บ provenance และเหตุผลของ water-supply guard ไว้ได้
Irrigation decision contract
แต่ละ Zone มี threshold แบบ hysteresis:
startBelowPct < stopAtOrAbovePct
ตัวอย่าง:
Start watering เมื่อ Soil Moisture <= 30%
Continue watering ระหว่าง 30–37%
Stop watering เมื่อ Soil Moisture >= 38%
Hysteresis ป้องกัน relay/pump/valve สลับ ON/OFF ถี่เมื่อค่า Sensor แกว่งใกล้ threshold เดียว
ก่อน Automatic Start ต้องผ่าน guard ต่อไปนี้ทั้งหมด:
- System อยู่ใน
automaticmode - Zone เปิดใช้งาน
- Soil measurement fresh + good
- Water supply พร้อม
- Pump พร้อม
- Valve พร้อม
ถ้า guard ใดไม่ผ่าน Decision ต้องบอกเหตุผลที่ machine-readable ได้ เช่น:
soil-data-unavailablewater-supply-unavailablepump-unavailablevalve-unavailablezone-disabledsystem-disabled
Multi-zone command ownership
เมื่อหลาย Zone ใช้ Pump ร่วมกัน Decision ของแต่ละ Zone ยังไม่ควรสั่ง Hardware โดยตรง เพราะอาจเกิดคำสั่งชนกันได้
selectIrrigationCommandOwner() รับ candidate ของแต่ละ Zone แล้วเลือก owner เพียงหนึ่ง Zone ตาม policy ที่ deterministic:
- ถ้า Zone ปัจจุบันยัง
continuewatering ได้ ให้รักษา owner เดิมก่อนเพื่อไม่ preempt กลางรอบ - ถ้า owner เดิม release แล้ว ให้เลือก request ที่
startได้ตาม priority สูงสุด - ถ้า priority เท่ากัน ใช้ request ที่เก่ากว่าก่อน แล้วใช้
zoneIdเป็น deterministic tie-breaker - candidate ที่ timestamp ใช้ไม่ได้หรือ decision ไม่ใช่
startจะไม่ชิง ownership
ดังนั้น shared Pump มี command owner ชัดเจน และ Zone อื่นต้องรอรอบถัดไปแทนการเปิดหลาย Valve/Pump policy พร้อมกันโดยไม่ตั้งใจ
Manual mode
manual mode ใน domain core ไม่แปลว่า bypass protection แล้วบังคับ Pump ON แต่หมายถึง automatic soil-threshold policy หยุดเป็น command owner และรอคำสั่ง operator จาก control layer ที่แยกต่างหาก
Manual command ใน implementation ถัดไปยังต้องผ่าน minimum hard guards เช่น actuator availability, timeout และ electrical/mechanical protection ที่เหมาะกับ hardware จริง
Feedback verification
เมื่อ command active ระบบต้องตรวจ Physical Feedback แยกจาก Command State:
Command active
↓
Pump feedback ON?
↓
Valve feedback OPEN?
↓
Flow fresh + plausible?
↓
Watering verified
MVP feedback states:
watering-verifiedpump-feedback-mismatchvalve-feedback-mismatchno-flowhigh-flowfeedback-unavailableunexpected-flowidle-verified
ถ้ากำลังสั่ง watering แล้ว feedback stale, Pump/Valve state ไม่ตรง หรือ Flow อยู่นอก expected range ระบบ core ส่ง shouldStopCommand = true เพื่อให้ control layer หยุด command และเข้าสู่ fault handling แทนการเดินต่อแบบ blind control
unexpected-flow ขณะ command inactive เป็นคนละกรณีกับ high-flow ระหว่าง watering และควรถูกใช้เป็น signal สำหรับ leak, valve leakage หรือ state mismatch ในระบบจริง
Control state machine
evaluateIrrigationZoneRuntime() ทำ state transition ระดับ Zone แบบ explicit:
IDLE
↓ start request + guards pass
WATERING
↓ target reached
IDLE
WATERING
↓ feedback fault / blocked critical input / maximum duration
FAULT
↓ condition cleared + operator acknowledge
IDLE
ก่อนเริ่ม watering runtime policy และ clock ต้อง valid และเมื่อเข้า WATERING จะจำ wateringStartedAt เพื่อบังคับ maximumWateringSeconds ไม่ให้ Pump เดินต่อแบบไม่จำกัด แม้ Soil target ยังไม่ถึง
Fault จาก no-flow, high-flow, feedback mismatch, stale feedback, critical input หรือ timeout จะ latched อยู่ใน FAULT จนกว่าจะมีทั้ง faultConditionCleared และ faultAcknowledged จึงกลับ IDLE ได้ ไม่มี automatic retry ที่ซ่อนสาเหตุ
ขณะ IDLE ถ้าพบ unexpected-flow ระบบก็เข้า FAULT ได้ แม้ไม่มี active command เพราะอาจหมายถึง valve leakage, pipe state mismatch หรือ flow ที่ไม่ได้เกิดจาก command owner
Event contract
createIrrigationEvent() กำหนด event shape กลางสำหรับ decision, feedback, state-transition และ command-owner โดยทุก event ต้องมี timestamp, zoneId, machine-readable code และ details ที่ serialize ได้
MVP นี้ยังไม่เลือก database หรือ retention policy แต่ lock contract ก่อนเพื่อให้ Simulator/Dashboard และ persistent event store ในอนาคตใช้ความหมายเดียวกัน
Monitoring ≠ Control ≠ Safety
Project นี้รักษา boundary สามชั้นให้ชัด:
- Monitoring — แสดง measurement, freshness, state, events และ alarms
- Control — policy สำหรับ request, command ownership, sequence และ feedback verification
- Safety/Protection — breaker, overload, motor protection, dry-run protection หรือ interlock ที่ hardware/process ต้องมีตามการออกแบบจริง
Software automation ไม่ถูกใช้แทน electrical protection หรือ mechanical safety device
Validation fixtures
Validation ปัจจุบัน lock อย่างน้อยกรณีต่อไปนี้:
- Fresh / stale / invalid / future-dated measurement
- Soil hysteresis: Start / Hold / Continue / Stop
- Manual / Disabled / Zone disabled
- invalid threshold และ invalid runtime policy
- Tank fresh/low/stale/invalid-policy และการ map เข้า water-supply guard
- shared Pump owner selection, priority, oldest-request tie-break และ owner retention
- maximum watering duration และ invalid runtime clock
- explicit
IDLE / WATERING / FAULTtransition - latched fault + acknowledgement/recovery
- Pump/Valve feedback mismatch
- No-flow / High-flow / stale flow feedback
- Unexpected flow ขณะไม่มี watering command
- Event contract validation
Fixture ชุดนี้ตั้งใจให้เป็น contract สำหรับ Simulator/App/Controller layer ถัดไป ไม่ใช่ simulation ของ sensor/hardware รุ่นใดรุ่นหนึ่ง
Simulator / Dashboard MVP
/apps/smart-irrigation-simulator/ ใช้ controller-cycle adapter เดียวกับ Project และเปิด deterministic scenarios ให้ตรวจ behavior ได้โดยไม่เขียน decision rules ซ้ำใน UI ปัจจุบันครอบคลุม:
- multi-zone start และ shared-pump owner selection
- verified watering ที่ตรวจ Pump / Valve / Flow feedback
- low tank ที่ block automatic start
- stale soil ที่ห้ามใช้ค่าครั้งสุดท้ายเป็น current measurement
- no-flow fault และ owner release
- maximum watering timeout
- acknowledged fault recovery โดยไม่ restart Pump อัตโนมัติ
- multi-tick progression ที่ carry state, owner และ latched fault ข้าม cycle
- configurable Soil/Tank/Flow value, age, quality และ Pump/Valve feedback
- operator acknowledge + condition-cleared actions ผ่าน simulator contract ก่อนให้ runtime evaluate recovery ใน tick ถัดไป
- session event/history timeline ต่อเนื่อง และ JSON export/import สำหรับ replay/regression case
- request provenance แยก
soil-policy,schedule-weatherและoperator-manualพร้อม intent/request timestamp ใน Decision/Event/History - Manual Start/Stop contract ที่ข้าม Soil policy ได้แต่ยังห้ามข้าม Tank/Pump/Valve hard guards, maximum duration และ feedback verification
- deterministic owner handoff เมื่อ owner เดิม stop/target reached แล้วมี Zone อื่นรอ shared Pump อยู่
- dynamic Zone controls ที่ render จาก session configuration และรองรับ scenario มากกว่า Zone A/B
- regression cases สำหรับ 3-zone priority, owner handoff, Schedule/Weather request และ stale-during-watering
- field command envelope ที่แยก
desired Pump/Valve stateออกจาก observed feedback และ carry owner/request provenance ไปถึง field adapter boundary - Schedule/Weather signal adapter ที่รับ
sourceId + intent + timestamp + qualityและ normalize เป็น control request เฉพาะเมื่อ signal fresh/good ตาม policy - temporal ingestion guard ที่รับ telemetry/request ใหม่กว่าเท่านั้น และ reject duplicate, conflicting-same-timestamp, invalid timestamp และ out-of-order update
- communication-failure scenarios สำหรับ stale Schedule/Weather และ delayed Flow telemetry โดย fail closed ก่อนสั่ง actuator ต่อ
Measurement timestamp จะไม่ถูก refresh อัตโนมัติเมื่อกด Step ดังนั้นข้อมูลที่ไม่ update จะค่อย ๆ กลายเป็น stale ตาม policy จริง แทน Simulator ที่ทำให้ Sensor ดู fresh ตลอดเวลาโดยไม่ตั้งใจ
Replay schema ปัจจุบันเป็น version 3 เพราะ lastResult รวม field command envelope แล้ว Payload v1/v2 จึง fail closed แทนการเดาว่า contract เก่ายัง compatible
Field I/O boundary
Controller-cycle output มี command envelope version 1 เป็น desired state เท่านั้น:
issuedAtและ deterministiccommandIdownerZoneIddesired.pumpOndesired.openValveZoneId- request provenance: source / intent / requestedAt
Envelope นี้ไม่ใช่หลักฐานว่า Pump หรือ Valve ทำงานจริง การยืนยันฝั่ง process ยังมาจาก observed feedback แยกกัน ได้แก่ Pump feedback, Valve feedback และ Flow measurement พร้อม freshness/quality contract เดิม ดังนั้น Command ON ≠ Pump ON ≠ Water Flow ยังคงเป็น boundary หลักของระบบ
Schedule/Weather ฝั่ง input ก็ไม่ส่ง boolean เข้ากฎควบคุมโดยตรง แต่ผ่าน adapter contract ที่มี sourceId, intent, timestamp, quality และ stale policy ก่อน normalize เป็น schedule-weather request หาก signal stale, suspect/invalid, future/invalid timestamp หรือ policy ผิด ระบบจะไม่สร้าง start request ที่ใช้งานได้
สำหรับ field telemetry และ external request ที่มาถึงหลายครั้ง adapter layer ใช้ timestamp ordering ก่อนเปลี่ยน controller state: newer update เท่านั้นที่ถูก accept; duplicate ถูก ignore แบบ idempotent; same-timestamp conflict และ out-of-order update ถูก reject โดยไม่ย้อนค่าปัจจุบันกลับไปหา packet เก่า และ Schedule/Weather จะไม่แทรกทับ active Operator Manual request
Next implementation slice
Project + Simulator milestone ปัจจุบันมี domain core, runtime safety, replay, provenance และ field I/O boundary เพียงพอสำหรับปิด PR นี้โดยไม่ต้องสร้าง backend platform เพิ่ม งานถัดไปควรเริ่มเมื่อมี concrete field adapter/deployment requirement จริง เช่น ESP32/PLC/Gateway protocol, remote ingestion หรือ notification requirement แล้วค่อยตัดสินว่า Worker, database, device registry และ persistence ส่วนใดจำเป็น
ถ้าจะต่อ field integration ใน PR ถัดไป ให้เริ่มจาก adapter ที่แปลง command envelope เป็น protocol-specific desired command และแปลง telemetry กลับเข้า measurement/update guards เดิม โดยห้ามให้ transport layer ข้าม decision/runtime safety contract


