/* Copyright (c) 2025 Rodrigo Arias Mallo * SPDX-License-Identifier: GPL-3.0-or-later */ #include "ntc.h" #include "pinout.h" enum logic { ON = 1, OFF = 0, }; #define DEBOUNCE_TIME 20L /* ms */ #define TEMP_MIN 80.0f #define TEMP_MAX 90.0f #define LED_MIN_VALUE 0 enum machine_state { SLEEPING = 0, DEBOUNCE, HEATING, HOT, BREWING, COOLING, }; //int state = SLEEPING; enum btn_index { BTN_ON = 0, BTN_HOT, MAX_BTN, }; enum btn_state { RESTING = 0, PRESSING, PRESSED, RELEASING, RELEASED, }; enum buzz_state { BUZZ_OFF = 0, BUZZ_HEY, BUZZ_ACTIVE, }; int button_pin[MAX_BTN] = { [BTN_ON] = PIN_POWER_ON, [BTN_HOT] = PIN_HOT, }; struct btn { enum btn_state state; unsigned long press_t0; unsigned long release_t0; }; struct input { int ntc_V; enum logic btn[MAX_BTN]; } g_in; #define MAX_SAMPLES 16 struct state { enum machine_state mstate; unsigned long brewing_t0; unsigned long cooling_t0; unsigned long heating_t0; unsigned long hot_t0; int ntc_i; /* Next available place */ float ntc_R; float ntc_last_T; float ntc_array_T[MAX_SAMPLES]; float ntc_T; /* average */ struct btn btn[MAX_BTN]; enum buzz_state buzz_state; unsigned long buzz_t0; } g_st; int read_input(int pin) { return !digitalRead(pin); } void relay(int pin, enum logic st) { /* Relays are active low */ if (st == ON) digitalWrite(pin, 0); else digitalWrite(pin, 1); } void setled(int pin, enum logic st) { /* LEDs are active high */ if (st == ON) digitalWrite(pin, 1); else digitalWrite(pin, 0); } void do_input(struct input *input) { /* Read buttons */ for (int i = 0; i < MAX_BTN; i++) input->btn[i] = read_input(button_pin[i]); /* Read temperature sensor */ input->ntc_V = analogRead(PIN_NTC); } void proc_ntc(struct state *state, const struct input *input) { state->ntc_R = ntc_resistance(input->ntc_V); state->ntc_last_T = ntc_temp(state->ntc_R); state->ntc_array_T[state->ntc_i++] = state->ntc_last_T; if (state->ntc_i >= MAX_SAMPLES) state->ntc_i = 0; float avg = 0; for (int i = 0; i < MAX_SAMPLES; i++) avg += state->ntc_array_T[i]; state->ntc_T = avg / MAX_SAMPLES; } void proc_buttons(struct state *state, const struct input *input) { for (int i = 0; i < MAX_BTN; i++) { struct btn *btn = &state->btn[i]; int v = input->btn[i]; if (btn->state == RESTING) { if (v == ON) { btn->state = PRESSING; btn->press_t0 = millis(); } } else if (btn->state == PRESSING) { if (v != ON) { btn->state = RESTING; } else if (millis() - btn->press_t0 > DEBOUNCE_TIME) { btn->state = PRESSED; } } else if (btn->state == PRESSED) { if (v != ON) { btn->state = RELEASING; btn->release_t0 = millis(); } } else if (btn->state == RELEASING) { if (v == ON) { btn->state = PRESSED; } else if (millis() - btn->release_t0 > DEBOUNCE_TIME) { btn->state = RELEASED; } } else if (btn->state == RELEASED) { btn->state = RESTING; } } } int red_min = 50; int red_state = red_min; unsigned long brewing_time = 3000UL; /* 3 seconds */ unsigned long cooling_time = 3000UL; /* 3 seconds */ unsigned long max_heating_time = 10000UL; /* 10 seconds */ unsigned long max_idle_time = 10000UL; /* 10 seconds */ void proc_machine(struct state *st) { float temp = st->ntc_T; int on = (st->btn[BTN_ON].state == RELEASED); int hot = (st->btn[BTN_HOT].state == PRESSED); Serial.print("state="); Serial.print(st->mstate); Serial.print(" on="); Serial.print(on); Serial.print(" temp="); Serial.print(temp); Serial.println(" C"); /* Pressing ON cancels any operation */ if (st->mstate != SLEEPING && on) { st->mstate = SLEEPING; st->buzz_state = BUZZ_OFF; return; } if (st->mstate == SLEEPING) { if (on) { st->mstate = HEATING; st->heating_t0 = millis(); Serial.println("heating"); } } else if (st->mstate == HEATING) { if (temp > TEMP_MAX) { st->mstate = HOT; st->hot_t0 = millis(); st->buzz_state = BUZZ_HEY; Serial.println("hot"); } else if (millis() - st->heating_t0 > max_heating_time) { /* TODO: Add alarm state */ st->mstate = SLEEPING; Serial.println("cannot heat, going to sleep"); } } else if (st->mstate == HOT) { if (hot) { st->mstate = BREWING; st->brewing_t0 = millis(); Serial.println("brewing"); } else if (millis() - st->hot_t0 > max_idle_time) { st->mstate = SLEEPING; Serial.println("idle timeout, going to sleep"); } } else if (st->mstate == BREWING) { if (millis() - st->brewing_t0 > brewing_time) { st->mstate = COOLING; st->cooling_t0 = millis(); Serial.println("cooling"); } } else if (st->mstate == COOLING) { /* TODO: Wait a bit and go back to heating */ if (millis() - st->cooling_t0 > cooling_time) { st->mstate = HEATING; st->heating_t0 = millis(); Serial.println("heating"); } } } void proc_buzz(struct state *st) { if (st->buzz_state == BUZZ_HEY) { tone(PIN_BUZZ, 1500); st->buzz_state = BUZZ_ACTIVE; st->buzz_t0 = millis(); } else if (st->buzz_state == BUZZ_ACTIVE) { if (millis() - st->buzz_t0 > 20) { st->buzz_state = BUZZ_OFF; noTone(PIN_BUZZ); } } else { noTone(PIN_BUZZ); } } void do_proc(struct state *st, const struct input *input) { proc_ntc(st, input); proc_buttons(st, input); proc_machine(st); proc_buzz(st); } void output_leds(const struct state *st) { static int r = 0; static int g = 0; if (st->mstate == HEATING || st->mstate == COOLING) { analogWrite(PIN_LED_RED, r); setled(PIN_LED_GREEN, 0); if (r >= 255) r = 0; else r += 3; } else if (st->mstate == HOT) { setled(PIN_LED_RED, 0); setled(PIN_LED_GREEN, 1); r = 0; } else if (st->mstate == BREWING) { setled(PIN_LED_RED, 0); analogWrite(PIN_LED_GREEN, g); if (g >= 255) g = 0; else g += 3; } else { setled(PIN_LED_RED, 0); setled(PIN_LED_GREEN, 0); r = 0; } } void output_heater(const struct state *st) { if (st->mstate == HEATING || st->mstate == HOT || st->mstate == BREWING) { if (st->ntc_T < TEMP_MIN) relay(PIN_HEAT, ON); else if (st->ntc_T > TEMP_MAX) relay(PIN_HEAT, OFF); } else { relay(PIN_HEAT, OFF); } } void output_pump(const struct state *st) { if (st->mstate == BREWING) relay(PIN_PUMP, ON); else relay(PIN_PUMP, OFF); } void do_output(const struct state *st) { output_leds(st); output_heater(st); } void setup() { Serial.begin(9600); Serial.println("Booting"); pinMode(PIN_POWER_ON, INPUT); pinMode(PIN_HOT, INPUT); pinMode(PIN_LED_RED, OUTPUT); pinMode(PIN_LED_GREEN, OUTPUT); pinMode(PIN_HEAT, OUTPUT); pinMode(PIN_PUMP, OUTPUT); /* Turn all relays off */ relay(PIN_HEAT, OFF); relay(PIN_PUMP, OFF); Serial.println("Ready"); } void loop() { do_input(&g_in); do_proc(&g_st, &g_in); do_output(&g_st); delay(5); }