You are designing BAS control logic for an AHU economizer with heating and mechanical cooling. Build a clear state machine + control loops (pseudo-code or structured steps). Keep it implementable on Niagara 4 / generic BAS. No psychrometrics—use only outside-air dry-bulb and outside-air dew point. Provide final outputs:
- heat_valve (0–100%)
- cool_valve (0–100%)
- econ_damper (0–100%)
## Objective
Maintain Supply Air Temperature (SAT) at setpoint. Allow free cooling (economizer) when outside conditions are suitable. In free cooling modes, **no heating is allowed**, but **additional mechanical cooling IS allowed** if econ alone can’t meet SAT.
## Inputs (read-only unless noted)
- OAT: Outside Air Temperature (°F)
- OADP: Outside Air Dew Point (°F)
- SAT: Supply Air Temperature (°F)
- SAT_SP: Supply Air Temperature Setpoint (°F)
- HEAT_SP: Heating activation temperature setpoint (°F) (typ. SAT_SP - heat_db)
- COOL_SP: Cooling activation temperature setpoint (°F) (typ. SAT_SP + cool_db)
- free_cool_enable_oat (°F) # enable threshold (e.g., ≤ 60°F)
- free_cool_disable_oat (°F) # disable threshold (e.g., ≥ 65°F)
- free_cool_max_dewpoint (°F) # dew point must be ≤ this to allow econ (e.g., 55°F)
- hysteresis_db (°F) # (e.g., 2°F)
- mech_cool_add_threshold (°F) # SAT error above which to add mech cooling in free-cool (e.g., +1.5°F)
- min_off_time_s (sec) # anti-short-cycle timer for mode changes (e.g., 300s)
- sample_time_s (sec) # loop execution period (e.g., 2s)
## Writable setpoints
- min_oa_enable_status (bool, writable) # when TRUE, enforce minimum OA damper outside of free cooling
- min_oa_damper (%, writable) # default 20 (%)
## Outputs (to compute each loop)
- heat_valve 0–100% # driven ONLY by the heating PID and ONLY in HEAT state
- cool_valve 0–100% # driven by cooling PID when in FREE_COOL_PLUS_MECH or MECH_COOL
- econ_damper 0–100% # modulated by economizer logic; respects minimum OA rules
## States (mutually exclusive)
1) HEAT
2) FREE_COOL
3) FREE_COOL_PLUS_MECH (economizer + mechanical cooling)
4) MECH_COOL
## Mode gating / eligibility
- Free cooling eligibility:
- ENABLE when (OAT ≤ free_cool_enable_oat) AND (OADP ≤ free_cool_max_dewpoint)
- DISABLE when (OAT ≥ free_cool_disable_oat OR OADP > free_cool_max_dewpoint + hysteresis_db)
- Heating eligibility:
- SAT ≤ HEAT_SP (with deadband) and free cooling NOT eligible (or SAT demand clearly heating)
- Mechanical cooling eligibility:
- SAT ≥ COOL_SP (with deadband). In FREE_COOL, add mech cooling when SAT error > mech_cool_add_threshold.
## OA damper rules
- If min_oa_enable_status == TRUE AND state is NOT FREE_COOL or FREE_COOL_PLUS_MECH:
- econ_damper := max(econ_damper, min_oa_damper) # enforce minimum OA outside free-cool modes
- In FREE_COOL or FREE_COOL_PLUS_MECH:
- econ_damper may open above min_oa_damper as needed by the cooling PID (see below).
## Control loops (anti-windup + clamping required)
- Heating PID (targets SAT_SP):
- Active ONLY in HEAT state.
- Output → heat_valve (0–100). Clamp and apply anti-windup.
- Cooling PID (targets SAT_SP):
- In FREE_COOL: PID drives econ_damper (0–100). cool_valve = 0.
- In FREE_COOL_PLUS_MECH:
* First drive econ_damper toward 100% as needed by PID;
* If SAT still > SAT_SP + mech_cool_add_threshold, enable cool_valve via cooling PID (second output or cascaded).
- In MECH_COOL: econ_damper held at min (if min_oa_enable_status TRUE), otherwise at ventilation min. PID drives cool_valve.
- Clamp outputs and apply anti-windup.
Suggested default PID tuning placeholders (to be site-tuned):
- Heat PID: Kp=1.2, Ki=0.02, Kd=0.0
- Cool PID (econ & mech): Kp=1.5, Ki=0.03, Kd=0.0
## State selection logic (evaluate in order, respect min_off_time_s)
1) If free-cool eligible:
- If SAT ≥ COOL_SP:
-> If econ_damper near max and SAT still too high by > mech_cool_add_threshold, choose FREE_COOL_PLUS_MECH
-> Else choose FREE_COOL
- Else if SAT ≤ HEAT_SP:
-> (Do NOT heat while free-cool eligible) choose FREE_COOL (cooling PID may settle econ closed if SAT below SP)
- Else:
-> FREE_COOL (economizer PID maintains SAT_SP)
2) Else if NOT free-cool eligible:
- If SAT ≤ HEAT_SP -> HEAT
- Else if SAT ≥ COOL_SP -> MECH_COOL
- Else -> hold last stable state (no heat/cool valves active; econ at min if required)
## Outputs by state (each loop)
- HEAT:
- heat_valve := HeatPID(SAT_SP - SAT)
- cool_valve := 0
- econ_damper := (min_oa_enable_status ? max(current, min_oa_damper) : ventilation_min)
- FREE_COOL:
- heat_valve := 0
- cool_valve := 0
- econ_damper := CoolPID_as_econ(SAT_SP - SAT) # 0–100; clamp
- FREE_COOL_PLUS_MECH:
- heat_valve := 0
- econ_damper := CoolPID_as_econ(SAT_SP - SAT) # bias toward opening; allow up to 100%
- If SAT > SAT_SP + mech_cool_add_threshold:
cool_valve := CoolPID_as_mech(SAT_SP - SAT) # 0–100; clamp
Else:
cool_valve := 0
- MECH_COOL:
- heat_valve := 0
- econ_damper := (min_oa_enable_status ? max(ventilation_min, min_oa_damper) : ventilation_min)
- cool_valve := CoolPID_as_mech(SAT_SP - SAT)
## Safeties & details
- Mutual exclusion: heat_valve and cool_valve are never >0 simultaneously.
- Apply deadbands: heat_db and cool_db around SAT_SP to avoid hunting.
- Apply min_off_time_s before switching between HEAT and any COOL state, and before enabling mech cooling.
- Bumpless transfer: freeze/reset integral when switching states; ramp outputs over 5–10s.
- Clamp all outputs 0–100 and NaN-guard inputs; on sensor fault, fail to safe (econ at min; heat/cool = 0).
## Deliverables
1) A readable state-machine description with the above rules.
2) Pseudocode implementing the loop, including:
- state selection with timers/hysteresis
- two PIDs (heat and cool) with anti-windup
- computation of heat_valve, cool_valve, econ_damper
3) A concise parameter block for all tunables (setpoints, thresholds, gains, timers).