demeter

Autonomous Hydroponic Intelligence

agent/tools/actuation.py (4860B)


  1from pydantic import BaseModel
  2from typing import Dict, Any
  3
  4# --- USER PROVIDED MODEL ---
  5class FarmAction(BaseModel):
  6    acid_dosage_ml: float = 0.0
  7    base_dosage_ml: float = 0.0
  8    nutrient_dosage_ml: float = 0.0
  9    fan_speed_pct: float = 0.0
 10    water_refill_l: float = 0.0
 11
 12# --- CALIBRATION CONSTANTS ---
 13# How much does 1ml of solution change the reservoir?
 14# Assuming a ~50L reservoir for this simulation
 15RESERVOIR_LITERS = 50.0 
 16PH_STRENGTH = 0.02  # 1ml changes 50L by 0.02 pH
 17EC_STRENGTH = 0.05  # 1ml changes 50L by 0.05 EC
 18
 19def convert_targets_to_actions(current_state: Dict[str, float], target_state: Dict[str, float]) -> FarmAction:
 20    """
 21    Acts as a Proportional Controller.
 22    Calculates the exact dosages/fan speeds needed to hit the targets.
 23    """
 24    action = FarmAction()
 25
 26    print(f"Current State: {current_state}")
 27    print(f"Target State: {target_state}")
 28    
 29    # Extract common values
 30    current_temp = current_state.get('air_temp', 25)
 31    target_temp = target_state.get('air_temp', 25)
 32    current_rh = current_state.get('humidity', 60)
 33    target_rh = target_state.get('humidity', 60)
 34    current_light = current_state.get('light_intensity', 50)
 35    target_light = target_state.get('light_intensity', 50)
 36    
 37    # 1. pH CONTROL (Acid/Base)
 38    current_ph = next((v for k, v in current_state.items() if k.lower() == 'ph'), 6.0)
 39    target_ph = next((v for k, v in target_state.items() if k.lower() == 'ph'), 6.0)
 40    ph_error = target_ph - current_ph
 41    
 42    # Deadband: Don't dose if within 0.1
 43    if abs(ph_error) > 0.1:
 44        needed_change = abs(ph_error)
 45        # Formula: Dose = (Delta / Strength)
 46        dose = needed_change / PH_STRENGTH
 47        
 48        if ph_error < 0: 
 49            # Current is too high -> Need Acid
 50            action.acid_dosage_ml = round(dose, 2)
 51        else:
 52            # Current is too low -> Need Base
 53            action.base_dosage_ml = round(dose, 2)
 54
 55    # 2. EC CONTROL (Nutrients/Water)
 56    current_ec = next((v for k, v in current_state.items() if k.lower() == 'ec'), 6.0)
 57    target_ec = next((v for k, v in target_state.items() if k.lower() == 'ec'), 6.0)
 58    ec_error = target_ec - current_ec
 59    
 60    # Track water needed for EC control
 61    water_for_dilution = 0.0
 62    
 63    if abs(ec_error) > 0.1:
 64        if ec_error > 0:
 65            # Current is too low -> Add Nutrients
 66            dose = ec_error / EC_STRENGTH
 67            action.nutrient_dosage_ml = round(dose, 2)
 68        else:
 69            # Current is too high -> Dilute with Water
 70            # Rough heuristic: Add 1L water to drop EC by ~0.1
 71            water_for_dilution = abs(ec_error) * 10
 72
 73    # 3. TRANSPIRATION-BASED WATER REPLENISHMENT
 74    # Plants lose water continuously through transpiration based on environmental conditions
 75    # Base transpiration: 0.5 L/day for a typical hydroponic system
 76    # Increase with temperature, decrease with humidity, increase with light
 77    
 78    base_transpiration_per_4h = 0.5 / 6  # ~0.083 L per 4-hour cycle
 79    
 80    # Temperature effect: higher = more transpiration (optimal ~25C, max at 30C+)
 81    temp_factor = 1.0
 82    if target_temp > 25:
 83        temp_factor += (target_temp - 25) * 0.05  # +5% per degree above 25C
 84    elif target_temp < 20:
 85        temp_factor -= (20 - target_temp) * 0.02  # -2% per degree below 20C
 86    temp_factor = max(0.5, min(2.0, temp_factor))  # Clamp between 0.5 and 2.0
 87    
 88    # Humidity effect: lower humidity = more transpiration (inverse)
 89    humidity_factor = 1.0
 90    if target_rh < 50:
 91        humidity_factor += (50 - target_rh) * 0.01  # Drier = more transpiration
 92    elif target_rh > 80:
 93        humidity_factor -= (target_rh - 80) * 0.01  # Very humid = less transpiration
 94    humidity_factor = max(0.5, min(2.0, humidity_factor))
 95    
 96    # Light effect: more light = more photosynthesis = more transpiration
 97    light_factor = 1.0 + (target_light / 100.0) * 0.5  # 0% light = 1x, 100% light = 1.5x
 98    
 99    # Calculate total transpiration water needed
100    transpiration_water = base_transpiration_per_4h * temp_factor * humidity_factor * light_factor
101    transpiration_water = round(transpiration_water, 2)
102    
103    # 4. TOTAL WATER REFILL = Transpiration + Dilution
104    action.water_refill_l = round(transpiration_water + water_for_dilution, 2)
105
106    # 5. ATMOSPHERIC CONTROL (Fans)
107    # Fans cool down air and lower humidity
108    
109    # Simple Logic: If too hot OR too humid, ramp up fans
110    temp_error = current_temp - target_temp
111    rh_error = current_rh - target_rh
112    
113    fan_speed = 0.0
114    if temp_error > 0: fan_speed += temp_error * 10 # +1C = +10% speed
115    if rh_error > 0: fan_speed += rh_error * 2      # +1% RH = +2% speed
116    
117    # Minimum circulation
118    fan_speed = max(10.0, fan_speed)
119    # Clamp to 100%
120    action.fan_speed_pct = min(100.0, round(fan_speed, 1))
121
122    return action