From 678f16111bd88fe2d72a516ec300787b632c400d Mon Sep 17 00:00:00 2001 From: Rodrigo Arias Mallo Date: Sun, 2 Nov 2025 19:24:02 +0100 Subject: [PATCH] Add thermostat and heater control --- barista/barista.ino | 66 +++++++++++++++++++++++++++----------------- barista/heater.c | 47 +++++++++++++++++++++++++++++++ barista/heater.h | 30 ++++++++++++++++++++ barista/thermostat.c | 41 +++++++++++++++++++++++++++ barista/thermostat.h | 32 +++++++++++++++++++++ 5 files changed, 191 insertions(+), 25 deletions(-) create mode 100644 barista/heater.c create mode 100644 barista/heater.h create mode 100644 barista/thermostat.c create mode 100644 barista/thermostat.h diff --git a/barista/barista.ino b/barista/barista.ino index d608590..4e07f17 100644 --- a/barista/barista.ino +++ b/barista/barista.ino @@ -4,6 +4,8 @@ #include "ntc.h" #include "overheat.h" #include "led.h" +#include "heater.h" +#include "thermostat.h" #include "pinout.h" #include @@ -13,8 +15,7 @@ enum logic { }; #define DEBOUNCE_TIME 20L /* ms */ -#define TEMP_MIN 40.0f /* C */ -#define TEMP_MAX 50.0f /* C */ +#define TEMP_HOT 70.0f /* C */ #define LED_MIN_VALUE 0 @@ -51,7 +52,6 @@ enum buzz_state { BUZZ_ACTIVE, }; - int button_pin[MAX_BTN] = { [BTN_ON] = PIN_POWER_ON, [BTN_HOT] = PIN_HOT, @@ -94,6 +94,9 @@ struct state { struct led red_led; struct led green_led; + + struct heater heater; + struct thermostat thermostat; } g_st; int read_input(int pin) @@ -191,27 +194,29 @@ void proc_buttons(struct state *state, const struct input *input) int red_min = 50; int red_state = red_min; -unsigned long brewing_max_time = 3000UL; /* 3 seconds */ -unsigned long cooling_time = 3000UL; /* 3 seconds */ -unsigned long overheat_time = 10000UL; /* 10 seconds */ -unsigned long max_heating_time = 60000UL; /* 60 seconds */ -unsigned long max_idle_time = 10000UL; /* 10 seconds */ +unsigned long brewing_max_time = 30000UL; /* 30 seconds */ +unsigned long cooling_time = 3000UL; /* 3 seconds */ +unsigned long overheat_time = 10000UL; /* 10 seconds */ +unsigned long max_heating_time = 60000UL; /* 60 seconds */ +unsigned long max_idle_time = 120000UL; /* 120 seconds */ void proc_machine(struct state *st) { + static unsigned long last_print_t = 0; + float temp = st->ntc_T; int on = (st->btn[BTN_ON].state == RELEASED); int brew_hot = (st->btn[BTN_HOT].state == PRESSED); + unsigned long t = millis(); - Serial.print("t="); - Serial.print(millis()); - Serial.print(" state="); - Serial.print(st->mstate); - Serial.print(" on="); - Serial.print(on); - Serial.print(" temp="); - Serial.print(temp); - Serial.println(" C"); + if (t - last_print_t > 100) { + Serial.print(t); + Serial.print(" "); + Serial.print(st->mstate); + Serial.print(" "); + Serial.println(temp); + last_print_t = t; + } /* If the machine is overheating */ if (overheat_panic(&st->overheat)) { @@ -239,7 +244,7 @@ void proc_machine(struct state *st) Serial.println("heating"); } } else if (st->mstate == HEATING) { - if (temp > TEMP_MIN) { + if (temp > TEMP_HOT) { st->mstate = HOT; st->hot_t0 = millis(); st->buzz_state = BUZZ_HEY; @@ -349,14 +354,25 @@ output_leds(const struct state *st) void output_heater(const struct state *st) { - if (st->mstate == HEATING || st->mstate == HOT || st->mstate == BREWING_HOT) { - if (st->ntc_T < TEMP_MIN) - relay(PIN_HEAT, ON); - else if (st->ntc_T > TEMP_MAX) - relay(PIN_HEAT, OFF); - } else { + unsigned long t = millis(); + + /* First configure thermostate */ + if (st->mstate == HEATING || st->mstate == HOT || st->mstate == BREWING_HOT) + thermostat_set(&st->thermostat, TEMP_HOT); + else + thermostat_off(&st->thermostat); + + /* Then update heater state from thermostate */ + if (thermostat_state(&st->thermostat, st->ntc_T)) + heater_on(&st->heater, t); + else + heater_off(&st->heater); + + /* Then switch relays accordingly */ + if (heater_state(&st->heater, t)) + relay(PIN_HEAT, ON); + else relay(PIN_HEAT, OFF); - } } void diff --git a/barista/heater.c b/barista/heater.c new file mode 100644 index 0000000..cef5791 --- /dev/null +++ b/barista/heater.c @@ -0,0 +1,47 @@ +/* Copyright (c) 2025 Rodrigo Arias Mallo + * SPDX-License-Identifier: GPL-3.0-or-later */ + +#include "heater.h" + +#define HEATER_T_ON 500UL /* ms */ +#define HEATER_T_OFF 5000UL /* ms */ + +void +heater_on(struct heater *h, unsigned long t_ms) +{ + if (h->st == HEATER_ON) + return; + + h->next_t = t_ms + HEATER_T_ON; + h->st = HEATER_ON; + h->turn_on = 1; +} + +void +heater_off(struct heater *h) +{ + h->st = HEATER_OFF; + h->turn_on = 0; +} + +int +heater_state(struct heater *h, unsigned long t_ms) +{ + if (h->st == HEATER_OFF) + return 0; + + /* Switch state if current time exceeds time limit + * in the current state */ + /* FIXME: Integer overflow can cause the heater to turn on forever */ + if (t_ms > h->next_t) { + if (h->turn_on) { + h->turn_on = 0; + h->next_t = t_ms + HEATER_T_OFF; + } else { + h->turn_on = 1; + h->next_t = t_ms + HEATER_T_ON; + } + } + + return h->turn_on; +} diff --git a/barista/heater.h b/barista/heater.h new file mode 100644 index 0000000..cf0aa16 --- /dev/null +++ b/barista/heater.h @@ -0,0 +1,30 @@ +/* Copyright (c) 2025 Rodrigo Arias Mallo + * SPDX-License-Identifier: GPL-3.0-or-later */ + +#ifndef BARISTA_HEATER_H +#define BARISTA_HEATER_H + +#ifdef __cplusplus +extern "C" { +#endif + +enum heater_state { + HEATER_OFF = 0, + HEATER_ON, +}; + +struct heater { + enum heater_state st; + int turn_on; + unsigned long next_t; /* in ms */ +}; + +void heater_on(struct heater *h, unsigned long t_ms); +void heater_off(struct heater *h); +int heater_state(struct heater *h, unsigned long t_ms); + +#ifdef __cplusplus +} +#endif + +#endif /* BARISTA_HEATER_H */ diff --git a/barista/thermostat.c b/barista/thermostat.c new file mode 100644 index 0000000..0438f50 --- /dev/null +++ b/barista/thermostat.c @@ -0,0 +1,41 @@ +/* Copyright (c) 2025 Rodrigo Arias Mallo + * SPDX-License-Identifier: GPL-3.0-or-later */ + +#include "thermostat.h" + +#define DELTA_LOW 1.0 +#define DELTA_HIGH 1.0 + +void +thermostat_set(struct thermostat *th, float temp_target) +{ + if (th->st == THERMOSTAT_ON && th->temp_target == temp_target) + return; + + th->st = THERMOSTAT_ON; + th->temp_target = temp_target; + th->temp_min = temp_target - DELTA_LOW; + th->temp_max = temp_target + DELTA_HIGH; + th->on = 1; +} + +void +thermostat_off(struct thermostat *th) +{ + th->st = THERMOSTAT_OFF; + th->on = 0; +} + +int +thermostat_state(struct thermostat *th, float temp) +{ + if (th->st == THERMOSTAT_OFF) + return 0; + + if (th->on && temp > th->temp_max) + th->on = 0; + else if (!th->on && temp < th->temp_min) + th->on = 1; + + return th->on; +} diff --git a/barista/thermostat.h b/barista/thermostat.h new file mode 100644 index 0000000..c0d89d7 --- /dev/null +++ b/barista/thermostat.h @@ -0,0 +1,32 @@ +/* Copyright (c) 2025 Rodrigo Arias Mallo + * SPDX-License-Identifier: GPL-3.0-or-later */ + +#ifndef BARISTA_THERMOSTAT_H +#define BARISTA_THERMOSTAT_H + +#ifdef __cplusplus +extern "C" { +#endif + +enum thermostat_state { + THERMOSTAT_OFF = 0, + THERMOSTAT_ON, +}; + +struct thermostat { + enum thermostat_state st; + float temp_target; + float temp_min; + float temp_max; + int on; +}; + +void thermostat_set(struct thermostat *th, float temp_target); +void thermostat_off(struct thermostat *th); +int thermostat_state(struct thermostat *th, float temp); + +#ifdef __cplusplus +} +#endif + +#endif /* BARISTA_THERMOSTAT_H */