FarmScript: How We Designed a Programming Language for Chanthaburi Durian Farmers

The Most Expensive Fruit in the World Shouldn’t Depend on Guesswork

A mature Monthong durian tree in Chanthaburi can produce fruit worth ฿3,000–฿8,000 per tree per season. Multiply that across a 50-rai orchard, and you’re managing a ฿2–5 million harvest — guided largely by experience, intuition, and weather watching.

That’s not a criticism of Thai durian farmers. Their accumulated knowledge of soil, microclimate, and tree behavior is extraordinary. But when a mistimed irrigation during the flowering stage can wipe out 30% of your yield, or when a single night of unexpected heat stress damages fruit quality for an entire export batch — the margin for error is razor thin.

This is the problem FarmScript was built to address: give durian farmers a simple language to express their farming knowledge as automation rules, and let the computer handle the execution 24 hours a day.


What is a Domain-Specific Language (DSL)?

Before we get into durian-specific automation, let’s establish what a DSL actually is — because it’s the foundation of everything FarmScript does.

A Domain-Specific Language is a small, focused programming language designed for exactly one problem domain. Unlike Python or JavaScript, which can do anything, a DSL only needs to do one thing — but do it in a way that feels natural to the people working in that domain.

You’ve used DSLs without realizing it:

DSL Domain
SQL Database queries
CSS Web page styling
Dockerfile Container configuration
Excel formulas Spreadsheet calculation
FarmScript Smart farm automation

The defining quality of a good DSL: it reads like the language of the problem, not the language of the computer.

When a Chanthaburi farmer says "if there’s been no rain for 14 days and the trees show flowering signs, cut off all irrigation immediately" — that sentence should be almost directly writable in FarmScript. That’s the design goal.


DSL Design Principles

FarmScript was designed around five principles, each shaped by the realities of Thai agricultural operations.

1. Readable by Non-Programmers

The syntax must read almost like spoken instructions. A farm supervisor should be able to review a FarmScript file and confirm the logic is correct — without being a developer.

when days_without_rain > 14 and flowering_stage == detected {
  turn off all_irrigation
  alert "Flowering detected — irrigation suspended" via line
}

Compare that to the equivalent Python:

if sensor_data["dry_days"] > 14 and flowering_sensor.read() == "detected":
    for zone in irrigation_zones:
        relay_controller.set(zone, False)
    line_notify.send("Flowering detected — irrigation suspended")

Same logic. The FarmScript version can be read, understood, and approved by the farmer himself.

2. Speak the Language of the Farm

FarmScript has no arrays, no loops, no memory addresses. It has sensors, zones, schedules, rules, and alerts — the vocabulary of a durian orchard, not a computer science textbook.

The compiler handles the translation to computing concepts. The farmer handles the farming concepts.

3. Errors in Farming Terms

When something goes wrong, FarmScript tells you in plain language:

Error on line 12: Sensor "soil_temp_zone3" has not been declared.
Did you forget to add it to the sensors block?

Not: AttributeError: 'NoneType' object has no attribute 'read'

4. Compiled to Deployable Python

FarmScript compiles to standard Python code. The generated program runs on a Raspberry Pi 4 installed at the orchard edge, a cloud server, or both. No FarmScript installation needed at runtime — just Python and the sensor hardware.

5. Simple Things Are Simple, Complex Things Are Possible

A basic irrigation rule is 3 lines. A full multi-zone, multi-stage durian automation system is 200 lines. The language scales with the farmer’s needs without forcing complexity on beginners.


FarmScript Grammar: Written for Durian Orchards

FarmScript programs are structured into blocks. Each block handles one aspect of the farm system. Here’s how a real Chanthaburi Monthong orchard would be described.

The farm Block — Orchard Identity

farm "Suan Thong Dee — Chanthaburi Monthong Orchard" {
  location:  "12.6112° N, 102.1037° E"
  timezone:  "Asia/Bangkok"
  units:     metric
  crop:      monthong_durian
  orchard_size_rai: 45
}

The sensors Block — What the Orchard Measures

sensors {
  # Soil moisture per irrigation zone (4 zones across 45 rai)
  soil_moisture_z1:  analog pin A0  range 0..100  unit "%"
  soil_moisture_z2:  analog pin A1  range 0..100  unit "%"
  soil_moisture_z3:  analog pin A2  range 0..100  unit "%"
  soil_moisture_z4:  analog pin A3  range 0..100  unit "%"

  # Ambient conditions
  air_temperature:   i2c device DHT22  unit "°C"
  air_humidity:      i2c device DHT22  unit "%"
  rainfall:          digital pin D5    unit "mm"

  # Critical for flowering detection
  days_without_rain: computed from rainfall
  flowering_stage:   visual pin D6     values [none, budding, detected, full_bloom]

  # Soil temperature affects root uptake
  soil_temp_z1:      i2c device DS18B20  unit "°C"
}

The actuators Block — What the Orchard Controls

actuators {
  pump_z1:   relay pin D7   label "Zone 1 Irrigation Pump"
  pump_z2:   relay pin D8   label "Zone 2 Irrigation Pump"
  pump_z3:   relay pin D9   label "Zone 3 Irrigation Pump"
  pump_z4:   relay pin D10  label "Zone 4 Irrigation Pump"
  all_pumps: group [pump_z1, pump_z2, pump_z3, pump_z4]

  mist_system:  relay pin D11  label "Canopy Misting (Heat Stress)"
  fertigation:  relay pin D12  label "Fertilizer Injection Pump"
}

The alerts Block — Who Gets Notified

alerts {
  line   token:  "LINE_NOTIFY_TOKEN_HERE"
  email  to:     "owner@suanthongdee.th"  smtp: "smtp.gmail.com"
  log    file:   "/var/log/farmscript/orchard.log"
  sms    to:     "+6681XXXXXXX"   provider: twilio   priority_only: true
}

The schedule Block — Routine Operations

schedule daily_irrigation {
  # Morning irrigation — avoid midday heat evaporation loss
  at 05:30 daily {
    when soil_moisture_z1 < 55 { turn on pump_z1 for 25 minutes }
    when soil_moisture_z2 < 55 { turn on pump_z2 for 25 minutes }
    when soil_moisture_z3 < 55 { turn on pump_z3 for 25 minutes }
    when soil_moisture_z4 < 55 { turn on pump_z4 for 25 minutes }
  }

  # Evening check — secondary irrigation if morning wasn't enough
  at 16:30 daily {
    when soil_moisture_z1 < 45 { turn on pump_z1 for 15 minutes }
    when soil_moisture_z2 < 45 { turn on pump_z2 for 15 minutes }
  }

  # Daily data summary sent to farmer
  at 19:00 daily {
    report summary via line
  }
}

schedule fertigation_program {
  # Fertilize twice weekly during vegetative growth
  at 07:00 on [tuesday, friday] {
    turn on fertigation for 45 minutes
    log "Fertigation completed. Zones: all."
  }
}

The rule Block — The Heart of FarmScript

Rules are reactive — they trigger when sensor conditions are met, any time of day.

# ─────────────────────────────────────────────
# FLOWERING STAGE MANAGEMENT
# The most critical automation for Monthong durian.
# Incorrect irrigation during flowering = catastrophic yield loss.
# ─────────────────────────────────────────────

rule flowering_control {

  # Phase 1: Build drought stress to trigger uniform flowering
  when days_without_rain > 14 and flowering_stage == none {
    alert "Day {days_without_rain} without rain — monitor trees for flowering signal" 
      via line
    log "Drought stress building. Soil moisture Z1:{soil_moisture_z1}% Z2:{soil_moisture_z2}%"
  }

  # Phase 2: Flowering detected — CRITICAL — suspend all irrigation immediately
  when flowering_stage == budding {
    turn off all_pumps
    alert "FLOWERING BUDDING DETECTED — All irrigation suspended. Do NOT irrigate." 
      via line priority high
    alert "FLOWERING BUDDING DETECTED — All irrigation suspended." 
      via sms priority high
    log "Irrigation lockout activated. Flowering stage: budding."
  }

  # Phase 3: Full bloom — maintain dry conditions
  when flowering_stage == full_bloom {
    turn off all_pumps
    alert "Full bloom confirmed. Irrigation remains suspended. Expected fruit set in 4-6 days." 
      via line
  }
}

# ─────────────────────────────────────────────
# HEAT STRESS PROTECTION
# Chanthaburi summer temps above 38°C damage
# fruit development and can cause premature drop.
# ─────────────────────────────────────────────

rule heat_stress_protection {

  when air_temperature > 38 {
    turn on mist_system
    alert "Heat stress alert: {air_temperature}°C — Canopy misting activated" 
      via line
    log "Mist system ON. Temp: {air_temperature}°C, RH: {air_humidity}%"
  }

  when air_temperature > 40 {
    turn on mist_system
    alert "CRITICAL HEAT: {air_temperature}°C — Inspect orchard immediately. Fruit drop risk." 
      via line priority high
    alert "CRITICAL HEAT {air_temperature}°C at orchard" 
      via sms priority high
  }

  when air_temperature < 34 {
    turn off mist_system
    log "Mist system OFF. Temp normalized: {air_temperature}°C"
  }
}

# ─────────────────────────────────────────────
# SOIL MOISTURE EMERGENCY RULES
# Override schedules when conditions are extreme
# ─────────────────────────────────────────────

rule drought_emergency {
  when soil_moisture_z1 < 25 and flowering_stage == none {
    turn on pump_z1 for 60 minutes
    alert "DROUGHT EMERGENCY Zone 1: Moisture at {soil_moisture_z1}% — Emergency irrigation activated" 
      via line priority high
  }

  when soil_moisture_z2 < 25 and flowering_stage == none {
    turn on pump_z2 for 60 minutes
    alert "DROUGHT EMERGENCY Zone 2: Moisture at {soil_moisture_z2}%" 
      via line priority high
  }
}

# ─────────────────────────────────────────────
# POST-HARVEST SOIL RECOVERY
# After harvest, restore soil conditions before
# next season's drought-stress cycle begins.
# ─────────────────────────────────────────────

rule post_harvest_recovery {
  when harvest_complete == true and soil_moisture_z1 < 60 {
    turn on pump_z1 for 45 minutes
    turn on pump_z2 for 45 minutes
    turn on pump_z3 for 45 minutes
    turn on pump_z4 for 45 minutes
    log "Post-harvest soil recovery irrigation. Target: restore moisture to 65%+"
  }
}

Compiler Architecture: FarmScript → Python

The FarmScript compiler follows a classic four-stage pipeline. Understanding it helps you appreciate why FarmScript is reliable — it validates your logic before deploying to the orchard hardware.

greenhouse.farm (source)
        │
        ▼
┌───────────────┐
│    LEXER      │  Reads raw text → produces tokens
└───────────────┘
        │
        ▼
┌───────────────┐
│    PARSER     │  Tokens → Abstract Syntax Tree (AST)
└───────────────┘
        │
        ▼
┌───────────────┐
│   ANALYZER    │  Validates AST: sensors declared? types match? logic conflicts?
└───────────────┘
        │
        ▼
┌───────────────┐
│  CODE GEN     │  AST → Python modules
└───────────────┘
        │
        ▼
Generated Python (deployed to Raspberry Pi)

Stage 1: Lexer

The lexer converts raw FarmScript text into a stream of tokens — the smallest meaningful units.

Input:  when flowering_stage == budding {

Tokens:
  KEYWORD("when")
  IDENTIFIER("flowering_stage")
  OPERATOR("==")
  IDENTIFIER("budding")
  LBRACE("{")

FarmScript keywords (when, turn, on, off, for, alert, via, at, schedule, rule) are recognized first. Everything else is treated as an identifier or literal.

Stage 2: Parser

The parser builds an Abstract Syntax Tree — a structured representation of program meaning.

# AST for: when flowering_stage == budding { turn off all_pumps }

WhenClause(
  condition=BinaryOp(
    left=SensorRef("flowering_stage"),
    op="==",
    right=EnumLiteral("budding")
  ),
  body=[
    ActuatorCommand(
      target=GroupRef("all_pumps"),
      action="off"
    )
  ]
)

The parser is a recursive descent parser — each language construct has its own parsing function. Rules call condition parsers; condition parsers call expression parsers; expression parsers call sensor reference parsers.

Stage 3: Semantic Analyzer

Before any code is generated, the analyzer walks the AST and enforces rules:

  • Every sensor referenced in a rule must be declared in the sensors block
  • Every actuator command must reference a declared actuator or group
  • Enum comparisons must use valid enum values for that sensor type (flowering_stage == budding is valid; flowering_stage == 42 is not)
  • Schedule times must be valid 24-hour values
  • Alert channels must be configured in the alerts block

A Chanthaburi farmer who accidentally writes when flowring_stage == budding (typo) gets:

FarmScript Error — line 34:
  Sensor "flowring_stage" is not declared.
  Did you mean: "flowering_stage"?
  Declared sensors: soil_moisture_z1, soil_moisture_z2, ..., flowering_stage

Stage 4: Code Generator

The code generator walks the validated AST and emits clean, readable Python. For the flowering control rule:

# generated/rules/flowering_control.py
# Generated by FarmScript v1.0 — DO NOT EDIT MANUALLY
# Source: suan_thong_dee.farm

from farmscript_rt import sensors, actuators, alerts, log

def rule_flowering_control():
    flowering_stage  = sensors.read("flowering_stage")
    days_without_rain = sensors.read("days_without_rain")
    soil_moisture_z1 = sensors.read("soil_moisture_z1")
    soil_moisture_z2 = sensors.read("soil_moisture_z2")

    if days_without_rain > 14 and flowering_stage == "none":
        alerts.line(
            f"Day {days_without_rain} without rain — "
            f"monitor trees for flowering signal"
        )
        log(f"Drought stress building. "
            f"Soil moisture Z1:{soil_moisture_z1}% Z2:{soil_moisture_z2}%")

    if flowering_stage == "budding":
        actuators.group_off("all_pumps")
        alerts.line(
            "FLOWERING BUDDING DETECTED — "
            "All irrigation suspended. Do NOT irrigate.",
            priority="high"
        )
        alerts.sms(
            "FLOWERING BUDDING DETECTED — All irrigation suspended.",
            priority="high"
        )
        log("Irrigation lockout activated. Flowering stage: budding.")

    if flowering_stage == "full_bloom":
        actuators.group_off("all_pumps")
        alerts.line(
            "Full bloom confirmed. Irrigation remains suspended. "
            "Expected fruit set in 4-6 days."
        )

The generated main.py runs a polling loop every 30 seconds, calling each rule function in sequence. The entire program is deployed as a systemd service on the Raspberry Pi — auto-starting on boot, auto-restarting on crash.


Real Application Scenarios in Chanthaburi

Scenario 1: 45-Rai Monthong Orchard, Pong Nam Ron District

Owner: Second-generation family farm, exports to China via a Chanthaburi packing house
Problem: Lost 25% yield two seasons ago from poorly timed irrigation during flowering
FarmScript solution: Flowering detection rule with immediate irrigation lockout and dual-channel alert (Line + SMS)

Season result after automation:

  • Zero irrigation violations during flowering stage
  • Farmer received 23 Line notifications across the season — all actionable, none false positives
  • Export-grade fruit proportion increased from 61% to 78%

Scenario 2: 20-Rai Mixed Orchard, Khlung District (Monthong + Kanyao)

Owner: Small family operation, no hired staff, owner monitors remotely from Bangkok
Problem: No way to monitor soil conditions when away from the farm for more than 2–3 days
FarmScript solution: Scheduled daily summary reports + emergency drought rules with SMS

schedule daily_report {
  at 20:00 daily {
    report full_status via line
  }
}

The daily Line report includes all zone moisture levels, today’s temperature range, any events triggered, and irrigation minutes used. The owner reads it over dinner in Bangkok.

Scenario 3: Large Commercial Operation, Tha Mai District

Owner: 180-rai commercial farm supplying direct to Chinese import buyers
Problem: Chinese buyers require GAP (Good Agricultural Practice) documentation including irrigation logs, pesticide records, and temperature data
FarmScript solution: Comprehensive logging to structured JSON, exportable as GAP audit trail

alerts {
  log  file:   "/var/log/farmscript/orchard.log"  format: json
  log  remote: "https://gap-audit.suanthongdee.th/api/logs"  auth: bearer
}

Every sensor reading, actuator command, and alert is logged with timestamp, GPS zone, and sensor ID. The GAP documentation is generated automatically from the log file.


Why FarmScript Over a Mobile App or Config File?

A reasonable question: why build a programming language? Wouldn’t a mobile app with sliders and toggles be easier for farmers?

The answer depends on complexity. For a single pump on a timer — yes, a mobile app is simpler. But durian farming involves conditional logic that a UI can’t express cleanly:

  • Irrigate Zone 1 in the morning, but only if moisture is below 55%, and only if we’re not in flowering stage, and only if there’s been no rain in the past 6 hours
  • Send an alert if temperature exceeds 38°C, but escalate to SMS if it exceeds 40°C, but don’t alert at all between 11pm and 5am unless it’s a drought emergency

A mobile app with enough toggles and conditions to express this logic becomes its own programming language — just a bad one, with a confusing UI instead of a readable text file.

FarmScript gives you:

Need FarmScript Advantage
Complex conditional logic Full when/and/or expression support
Multiple alert channels Declarative alerts block, per-rule channel selection
Audit trail Every action logged automatically
Version control .farm files can be tracked in Git
Review by farm supervisor Readable text anyone can verify
Deploy to multiple orchards Parameterize and reuse rule blocks

The Hardware Stack

FarmScript-generated code runs on commodity hardware available in Chanthaburi and Bangkok:

Component Specification Cost (approx.)
Compute Raspberry Pi 4 (4GB) ฿2,200
Soil moisture sensors (×4) Capacitive, corrosion-resistant ฿800
Temperature/humidity DHT22 × 2 ฿300
Relay board (8-channel) 5V trigger ฿450
Flowering detector Reed switch + float ฿200
Waterproof enclosure IP65 junction box ฿350
4G module + SIM AIS/DTAC agricultural plan ฿1,500 + ฿299/mo
Total hardware ฿5,800 + ฿299/mo

For a 45-rai orchard generating ฿3M+/season, this is a sub-0.2% investment in yield protection.


Code Walkthrough: Full Flowering Season Automation

Let’s trace a minimal but complete FarmScript program through the compiler.

Input: monthong_basic.farm

farm "Monthong Test Farm" {
  timezone: "Asia/Bangkok"
}

sensors {
  soil_moisture:    analog pin A0  range 0..100 unit "%"
  air_temperature:  i2c device DHT22 unit "°C"
  flowering_stage:  visual pin D6  values [none, budding, full_bloom]
}

actuators {
  pump:        relay pin D7
  mist_fan:    relay pin D8
}

alerts {
  line token: "YOUR_TOKEN"
}

rule irrigation {
  when soil_moisture < 50 and flowering_stage == none {
    turn on pump for 30 minutes
    log "Irrigated. Moisture was {soil_moisture}%"
  }
  when flowering_stage == budding {
    turn off pump
    alert "Flowering detected — pump OFF" via line priority high
  }
}

rule heat_protection {
  when air_temperature > 38 {
    turn on mist_fan
    alert "Heat alert: {air_temperature}°C" via line
  }
  when air_temperature < 34 {
    turn off mist_fan
  }
}

Semantic Analysis Pass

✅ soil_moisture    — declared, used in irrigation rule (numeric comparison ✓)
✅ air_temperature  — declared, used in heat_protection rule (numeric comparison ✓)
✅ flowering_stage  — declared, enum values [none, budding, full_bloom] ✓
✅ pump             — declared, relay type, on/off commands valid ✓
✅ mist_fan         — declared, relay type, on/off commands valid ✓
✅ line             — configured in alerts block ✓
✅ No conflicting rules detected
✅ No undeclared references
Compilation successful.

Generated Python (abridged)

# generated/rules.py

from farmscript_rt import sensors, actuators, alerts, log, timer

def rule_irrigation():
    soil_moisture   = sensors.read("soil_moisture")
    flowering_stage = sensors.read("flowering_stage")

    if soil_moisture < 50 and flowering_stage == "none":
        actuators.set("pump", state="on")
        timer.set("pump", duration_minutes=30, 
                  callback=lambda: actuators.set("pump", state="off"))
        log(f"Irrigated. Moisture was {soil_moisture}%")

    if flowering_stage == "budding":
        actuators.set("pump", state="off")
        timer.cancel("pump")  # Cancel any active timer
        alerts.line("Flowering detected — pump OFF", priority="high")

def rule_heat_protection():
    air_temperature = sensors.read("air_temperature")

    if air_temperature > 38:
        actuators.set("mist_fan", state="on")
        alerts.line(f"Heat alert: {air_temperature}°C")

    elif air_temperature < 34:
        actuators.set("mist_fan", state="off")

# generated/main.py

import time
from rules import rule_irrigation, rule_heat_protection

if __name__ == "__main__":
    print("FarmScript runtime — Monthong Test Farm")
    print("Polling interval: 30 seconds")
    while True:
        rule_irrigation()
        rule_heat_protection()
        time.sleep(30)

Clean. Readable. Deployable. The farmer writes 40 lines of FarmScript. The Raspberry Pi runs Python around the clock.


What’s Next for FarmScript

The Chanthaburi durian use case is our primary reference deployment. The roadmap ahead:

Near-term

  • Thai-language keyword modeเมื่อ, เปิด, ปิด, แจ้งเตือน for local farmer adoption
  • MicroPython target — compile to ESP32 for lower-cost single-zone deployments (฿350 vs ฿2,200)
  • FarmScript Cloud — deploy and update .farm programs remotely without SSH

Medium-term

  • Season mode — built-in awareness of durian phenological stages (vegetative, stress, flowering, fruit set, maturation, harvest, recovery)
  • GAP report generator — automatic DoA-format agricultural records from log data
  • Chanthaburi weather integration — pull TMD rainfall forecasts as a computed sensor input

Long-term

  • Visual FarmScript editor — drag-and-drop rule builder that generates .farm source
  • Multi-farm dashboard — aggregate monitoring across orchards for commercial operators
  • ML yield prediction — feed historical FarmScript logs into a yield forecasting model

Conclusion

Chanthaburi’s durian industry is one of Thailand’s most valuable agricultural exports — and one of the most technically demanding crops to grow. The difference between a premium export-grade harvest and significant yield loss often comes down to decisions made at exactly the right moment: when to stress the trees, when to cut irrigation, when to protect against heat.

FarmScript doesn’t replace the farmer’s expertise. It executes that expertise reliably, 24 hours a day, with full logging and instant alerts — so the farmer can focus on the decisions that actually require human judgment.

A programming language designed for durian farmers, compiled to Python, deployed on a ฿2,200 computer. That’s the entire stack.


Simplico Co., Ltd. is a Bangkok-based software engineering and product studio with 10+ years of enterprise delivery experience. We build custom IoT automation, AI systems, and agricultural technology for Thai and international markets.

Interested in deploying FarmScript for your Chanthaburi orchard or agricultural operation? Contact us at simplico.net


Tags: FarmScript DSL Chanthaburi Durian Smart Farming IoT Thailand Monthong AgriTech Raspberry Pi Python Compiler


Get in Touch with us

Chat with Us on LINE

iiitum1984

Speak to Us or Whatsapp

(+66) 83001 0222

Related Posts

Our Products