PROJECT

Smart Irrigation Control System.

โปรเจกต์ Smart Irrigation ที่แยก Measurement, Decision, Actuation และ Feedback ออกจากกัน พร้อม freshness, hysteresis, offline/manual behavior และ fail-closed control guards

Smart FarmIoTAutomationElectricalโดย Thospaakเผยแพร่ 15 ต.ค. 2569อัปเดต 15 ต.ค. 2569

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 หลักออกจากกัน:

  1. evaluateIrrigationDecision() — ตัดสินใจว่าจะ Start / Continue / Stop / Hold / Block
  2. evaluateTankSupply() + evaluateIrrigationDecisionWithTank() — แปลง Tank measurement ที่มี quality/freshness เป็น water-supply guard โดยไม่ลดเหลือ boolean ตั้งแต่ต้นทาง
  3. selectIrrigationCommandOwner() — เลือกเจ้าของ shared Pump แบบ deterministic ระหว่างหลาย Zone และรักษา active owner จนกว่าจะ release
  4. evaluateIrrigationFeedback() — ตรวจว่าคำสั่งที่ส่งออกไปเกิดผลจริงหรือไม่จาก Pump, Valve และ Flow feedback
  5. evaluateIrrigationZoneRuntime() — คุม state IDLE / WATERING / FAULT, maximum duration, latched fault และ recovery
  6. createIrrigationEvent() — 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 ของเวลาที่วัดจริง
  • sourceId
  • quality: good, suspect, invalid
  • freshness ที่คำนวณจาก timestamp เทียบกับ stale policy ของ measurement นั้น

Freshness มีสามสถานะ:

  • fresh — ใช้ประกอบ decision ได้ตาม policy
  • stale — มีค่าล่าสุดแต่เก่าเกินกำหนด ห้ามแสดงเหมือนเป็น current measurement
  • unavailable — ไม่มีค่า, 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 อยู่ใน automatic mode
  • Zone เปิดใช้งาน
  • Soil measurement fresh + good
  • Water supply พร้อม
  • Pump พร้อม
  • Valve พร้อม

ถ้า guard ใดไม่ผ่าน Decision ต้องบอกเหตุผลที่ machine-readable ได้ เช่น:

  • soil-data-unavailable
  • water-supply-unavailable
  • pump-unavailable
  • valve-unavailable
  • zone-disabled
  • system-disabled

Multi-zone command ownership

เมื่อหลาย Zone ใช้ Pump ร่วมกัน Decision ของแต่ละ Zone ยังไม่ควรสั่ง Hardware โดยตรง เพราะอาจเกิดคำสั่งชนกันได้

selectIrrigationCommandOwner() รับ candidate ของแต่ละ Zone แล้วเลือก owner เพียงหนึ่ง Zone ตาม policy ที่ deterministic:

  1. ถ้า Zone ปัจจุบันยัง continue watering ได้ ให้รักษา owner เดิมก่อนเพื่อไม่ preempt กลางรอบ
  2. ถ้า owner เดิม release แล้ว ให้เลือก request ที่ start ได้ตาม priority สูงสุด
  3. ถ้า priority เท่ากัน ใช้ request ที่เก่ากว่าก่อน แล้วใช้ zoneId เป็น deterministic tie-breaker
  4. 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-verified
  • pump-feedback-mismatch
  • valve-feedback-mismatch
  • no-flow
  • high-flow
  • feedback-unavailable
  • unexpected-flow
  • idle-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 / FAULT transition
  • 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 ปัจจุบันครอบคลุม:

  1. multi-zone start และ shared-pump owner selection
  2. verified watering ที่ตรวจ Pump / Valve / Flow feedback
  3. low tank ที่ block automatic start
  4. stale soil ที่ห้ามใช้ค่าครั้งสุดท้ายเป็น current measurement
  5. no-flow fault และ owner release
  6. maximum watering timeout
  7. acknowledged fault recovery โดยไม่ restart Pump อัตโนมัติ
  8. multi-tick progression ที่ carry state, owner และ latched fault ข้าม cycle
  9. configurable Soil/Tank/Flow value, age, quality และ Pump/Valve feedback
  10. operator acknowledge + condition-cleared actions ผ่าน simulator contract ก่อนให้ runtime evaluate recovery ใน tick ถัดไป
  11. session event/history timeline ต่อเนื่อง และ JSON export/import สำหรับ replay/regression case
  12. request provenance แยก soil-policy, schedule-weather และ operator-manual พร้อม intent/request timestamp ใน Decision/Event/History
  13. Manual Start/Stop contract ที่ข้าม Soil policy ได้แต่ยังห้ามข้าม Tank/Pump/Valve hard guards, maximum duration และ feedback verification
  14. deterministic owner handoff เมื่อ owner เดิม stop/target reached แล้วมี Zone อื่นรอ shared Pump อยู่
  15. dynamic Zone controls ที่ render จาก session configuration และรองรับ scenario มากกว่า Zone A/B
  16. regression cases สำหรับ 3-zone priority, owner handoff, Schedule/Weather request และ stale-during-watering
  17. field command envelope ที่แยก desired Pump/Valve state ออกจาก observed feedback และ carry owner/request provenance ไปถึง field adapter boundary
  18. Schedule/Weather signal adapter ที่รับ sourceId + intent + timestamp + quality และ normalize เป็น control request เฉพาะเมื่อ signal fresh/good ตาม policy
  19. temporal ingestion guard ที่รับ telemetry/request ใหม่กว่าเท่านั้น และ reject duplicate, conflicting-same-timestamp, invalid timestamp และ out-of-order update
  20. 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 และ deterministic commandId
  • ownerZoneId
  • desired.pumpOn
  • desired.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