/* * Drives thermostat.c with synthetic time to prove the protections actually * fire. Nothing here touches hardware; time is a variable, so two hours of * furnace behaviour runs in milliseconds. * * This logic controls a gas valve, so it gets verified before it gets flashed: * * gcc -Wall -Wextra -I main/control -o /tmp/therm_test \ * tools/thermostat_test.c main/control/thermostat.c -lm && /tmp/therm_test * * Exits non-zero on failure. Worth re-running after any edit to thermostat.c. */ #include #include #include "thermostat.h" static therm_state_t last = (therm_state_t)-1; static uint32_t t_ms; static const char *sname(therm_state_t s) { switch (s) { case THERM_STATE_OFF: return "OFF"; case THERM_STATE_IDLE: return "IDLE"; case THERM_STATE_HEATING: return "HEATING"; case THERM_STATE_LOCKOUT: return "LOCKOUT"; case THERM_STATE_NO_SENSOR: return "NO_SENSOR"; } return "?"; } /* Advance `secs` seconds at 1Hz, printing only transitions. */ static void run(float temp, int temp_valid, int secs) { therm_status_t st; for (int i = 0; i < secs; i++) { t_ms += 1000; thermostat_tick(temp, temp_valid, t_ms); thermostat_get_status(&st); if (st.state != last) { char txt[32]; thermostat_state_text(&st, txt, sizeof(txt)); printf(" %6.1f min %-9s heat=%d fan=%d cycles=%u \"%s\"\n", t_ms / 60000.0, sname(st.state), st.heat_relay, st.fan_relay, st.cycles_last_hour, txt); last = st.state; } } } int main(void) { therm_status_t st; printf("min_on=%us min_off=%us max_cph=%d deadband=%.1fF\n\n", THERM_MIN_ON_MS / 1000, THERM_MIN_OFF_MS / 1000, THERM_MAX_CYCLES_PER_HOUR, THERM_DEADBAND_F); thermostat_init(0); thermostat_set_setpoint(68.0f); printf("1. cold house at 60F, boot lockout then heat\n"); run(60.0f, 1, 120); printf("\n2. house reaches 69F -- must serve min-on before stopping\n"); run(69.0f, 1, 300); printf("\n3. cold again immediately -- must serve min-off\n"); run(60.0f, 1, 400); printf("\n4. hammer it: alternate hot/cold to trip the cycles-per-hour cap\n"); for (int i = 0; i < 12; i++) { run(69.0f, 1, 200); run(60.0f, 1, 400); } thermostat_get_status(&st); printf(" -> cycles in last hour: %u (cap %d)\n", st.cycles_last_hour, THERM_MAX_CYCLES_PER_HOUR); if (st.cycles_last_hour > THERM_MAX_CYCLES_PER_HOUR) { printf(" *** FAIL: cap exceeded\n"); return 1; } printf("\n5. sensor goes away -- rides it out, then forces heat off\n"); thermostat_init(t_ms); last = (therm_state_t)-1; thermostat_set_setpoint(68.0f); run(60.0f, 1, 120); /* heating */ run(0.0f, 0, 14 * 60); /* stale but within the grace window */ run(0.0f, 0, 3 * 60); /* past it */ thermostat_get_status(&st); if (st.state != THERM_STATE_NO_SENSOR || st.heat_relay) { printf(" *** FAIL: expected NO_SENSOR with heat off\n"); return 1; } printf("\n6. mode off stops heat immediately, ignoring min-on\n"); thermostat_init(t_ms); last = (therm_state_t)-1; thermostat_set_setpoint(68.0f); run(60.0f, 1, 120); thermostat_get_status(&st); if (!st.heat_relay) { printf(" *** FAIL: expected to be heating first\n"); return 1; } thermostat_set_mode(THERM_MODE_OFF); run(60.0f, 1, 2); thermostat_get_status(&st); if (st.heat_relay) { printf(" *** FAIL: heat still on after mode off\n"); return 1; } printf("\n7. fan is independent of heat, and never follows a heat call\n"); thermostat_init(t_ms); last = (therm_state_t)-1; thermostat_set_setpoint(68.0f); run(60.0f, 1, 120); thermostat_get_status(&st); if (st.fan_relay) { printf(" *** FAIL: fan energized during heat (furnace owns the blower)\n"); return 1; } thermostat_set_fan(THERM_FAN_ON); run(60.0f, 1, 2); thermostat_get_status(&st); if (!st.fan_relay) { printf(" *** FAIL: fan did not turn on\n"); return 1; } printf("\nall checks passed\n"); return 0; }