This logic implements a modular supply air temperature reset strategy based on ASHRAE Guideline 36, section 5.16.2. VAVs issue SAT requests based on zone-level temperature demand, which are summed and used by the AHU to reset the discharge air temperature via trim and respond logic.
Each VAV compares zone temperature against the occupied cooling setpoint and determines how many SAT reset requests to send.
const limit = (value, min, max) => {
if (value > max) return max;
if (value < min) return min;
return value;
};
module.exports = async ({ points, sdk, groupVariables }) => {
const CoolingBackOff = groupVariables.byLabel("CoolingLoopBackOff");
const isInCoolingBackOffLoop = Boolean(CoolingBackOff.latestValue?.value);
const setIsInCoolingBackOffLoop = async (status) => {
await CoolingBackOff.write(status ? 1 : 0);
};
const VavEquip = points.byLabel("vav").first();
const ZoneTemperature = points.byLabel("zone-air-temp-sensor").first();
const ZoneTemperatureSetpoint = points.byLabel("zone-air-temp-occ-cooling-sp").first();
const CoolingLoop = points.byLabel("cool-cmd").first();
if (!ZoneTemperature || !ZoneTemperatureSetpoint || !CoolingLoop) {
return { result: "success", message: "Missing required points." };
}
async function sendRequest(count) {
let importanceMultiplier = Number(VavEquip?.attrs.importanceMultiplier) || 1;
const adjustedRequests = importanceMultiplier * count;
await groupVariables.byLabel("Cooling_SAT_Requests").write({ real: adjustedRequests });
}
if (isInCoolingBackOffLoop) {
if (CoolingLoop.latestValue?.value < 85) {
await setIsInCoolingBackOffLoop(false);
} else {
await sendRequest(1);
return { result: "success", message: "Sent 1 request. Staying in loop." };
}
}
const getSuppressedUntilTime = () => {
const supressionMinutes = limit(
Math.abs(ZoneTemperatureSetpoint.latestValue.value - ZoneTemperatureSetpoint.latestValue.value) * 5,
0, 30
);
return new Date().getTime() + supressionMinutes * 60000;
};
const suppressedUntil = getSuppressedUntilTime() ?? 0;
if (ZoneTemperatureSetpoint.isChanged()) {
await groupVariables.byLabel("SuppressedUntilTime").write(suppressedUntil);
return;
} else if (suppressedUntil > new Date().getTime()) {
return;
}
if (
await ZoneTemperature.trueFor("2m", v => v.value > ZoneTemperatureSetpoint.latestValue.value + 5)
) {
await sendRequest(3);
return { result: "success", message: "Sent 3 requests" };
} else if (
await ZoneTemperature.trueFor("2m", v => v.value > ZoneTemperatureSetpoint.latestValue.value + 3)
) {
await sendRequest(2);
return { result: "success", message: "Sent 2 requests" };
} else if (CoolingLoop.latestValue?.value > 95) {
await sendRequest(1);
return { result: "success", message: "Sent 1 request." };
} else {
await sendRequest(0);
return { result: "success", message: "Sent 0 requests." };
}
};
This code intelligently monitors each zone’s thermal demand by comparing the actual zone temperature to its occupied cooling setpoint, and assigns 0 to 3 cooling requests based on how far the zone is above that setpoint. The greater the deviation, the higher the number of requests sent, reflecting the urgency of cooling needed in that space. If the zone temperature exceeds the setpoint by more than 5°F for at least two minutes, it generates the maximum of three requests, signaling significant discomfort. Lesser deviations trigger fewer requests accordingly. The logic also incorporates a damper-based loop that ensures at least one request persists when the cooling command is active and the damper is fully open, even if temperature thresholds aren’t exceeded—indicating the VAV is trying to cool but can't meet the load. To avoid unnecessary oscillations and request spikes, a suppression mechanism is employed when the setpoint changes, temporarily pausing request generation to give the system time to stabilize. Each request is scaled using an optional importance multiplier, giving more weight to high-priority or critical zones. The net result is a responsive, zone-driven signal that feeds into the AHU-level SAT reset logic, ensuring that supply air temperature is dynamically adjusted based on actual, time-weighted comfort needs throughout the building.
The AHU aggregates all SAT requests from zones into a single total value.
module.exports = async ({ points, sdk, groupVariables }) => {
points.forEach(p => {
if (p.latestValue.value < 0) {
sdk.logEvent("Negative value detected");
}
});
const values = points.map(x => x.latestValue.value ?? 0);
const requests = values.reduce((a, b) => a + b, 0);
const RequestVariable = groupVariables.byLabel("Total SAT Requests");
await RequestVariable.write(requests);
return { result: "success", message: `Request Total: ${requests}` };
};
This AHU control logic adjusts the discharge air temperature setpoint based on total cooling requests and outside air temperature.
const { limit, interpolate } = require('@intellimation-optimization/lib');
module.exports = async ({ points, groupVariables, sdk }) => {
if (sdk.groupKey === '') return { result: 'success', message: 'Dropping empty group.' };
const minClgSAT = 55;
const maxClgSAT = groupVariables.byLabel('maxClgSAT')?.latestValue?.value ?? 70;
const OATMin = 60;
const OATMax = 70;
const totalRequests = points.where(p => p.attrs.label === 'Total SAT Requests').first();
const SATSetpoint = points.byLabel('discharge-air-temp-sp').first();
const outsideAirTemp = points.byLabel('air-temp-sensor').first()?.latestValue.value;
const SP0 = maxClgSAT;
const SPmin = minClgSAT;
const SPmax = maxClgSAT;
const I = 2;
const SPtrim = 0.2;
const SPres = -0.3;
const SPResMax = -1.0;
const systemStatus = points.byLabel('fan-run-cmd').first();
const resetToInitial = systemStatus?.latestValue.value === 1 &&
systemStatus.latestValue.ts.getTime() === systemStatus.changeTime.getTime();
if (resetToInitial) {
await SATSetpoint.write({ real: SP0 });
return;
}
const runTandRLoop = await systemStatus.trueFor('10m', v => v.value === 1);
const getProportionalSetpoint = (tMax) => {
return interpolate(
outsideAirTemp,
[
{ x: OATMin, y: tMax },
{ x: OATMax, y: minClgSAT }
],
{ min: minClgSAT, max: maxClgSAT }
);
};
if (runTandRLoop) {
if (totalRequests.latestValue.value <= I) {
const currentSetpoint = SATSetpoint?.latestValue?.value || 0;
const tMax = limit(currentSetpoint + SPtrim, SPmin, SPmax);
await groupVariables.byLabel('tMax')?.write(tMax);
const newSetpoint = getProportionalSetpoint(tMax);
return { result: 'success', message: `Trimming to ${newSetpoint}` };
} else {
const currentSetpoint = SATSetpoint?.latestValue?.value || 0;
const respondAmount = Math.max(SPres * (totalRequests.latestValue.value - I), SPResMax);
const tMax = limit(currentSetpoint + respondAmount, SPmin, SPmax);
await groupVariables.byLabel('tMax')?.write(tMax);
const newSetpoint = getProportionalSetpoint(tMax);
return { result: 'success', message: `Responding to ${newSetpoint}` };
}
}
};
This logic implements an advanced, weather-aware Trim and Respond strategy for dynamically resetting the AHU’s supply air temperature (SAT) in response to real-time zone-level cooling demand. It continuously monitors the total number of SAT requests generated by VAV zones and uses that count to determine how aggressively the AHU should cool. When demand is low—indicated by request counts below a defined threshold—the system trims the SAT setpoint upward incrementally, reducing cooling energy while still maintaining comfort. When demand exceeds the threshold, it responds by lowering the SAT proportionally, allowing the system to rapidly meet increasing cooling needs. To prevent overcorrection and excessive SAT swings, the logic enforces upper and lower bounds on setpoint changes and uses a rate-limited response curve to modulate reset intensity. Crucially, this logic is further refined by interpolating against the outdoor air temperature (OAT): reset actions are softened when the weather is mild and intensified when the OAT rises, allowing for seasonally adaptive cooling strategies. Additionally, the logic includes a high-OAT lockout that forces the SAT to the minimum allowable value (typically 55°F) when outdoor air exceeds 70°F, ensuring maximum cooling capacity during extreme heat. A startup safeguard is also embedded: if the AHU fan is newly energized, the SAT resets to its default value to stabilize initial system operation. Altogether, this control strategy ensures precise, demand-driven SAT management that maximizes occupant comfort, reduces energy consumption, and adapts intelligently to both indoor and outdoor conditions.
- VAVs detect unmet cooling and issue requests to the AHU.
- The AHU sums zone requests and adjusts the SAT using trim/respond logic.
- OAT-based interpolation ensures SAT reset is responsive to outdoor conditions.
- Suppression periods and fan-on state detection prevent false triggers.