/*
* Binary-clock-based irrigation UI
* - ATmega328, 32.768 kHz crystal on Timer2 (async RTC, 1 Hz overflow)
* - WS2811 4x4 matrix: columns = [tensH, H, tensM, M]
* - One button, handled via Button2
* - Sleep in power-save, wake via PCINT on button
* - Auto-sleep after 30 s inactivity in UI_NORMAL
* - Edit timeout after 15 s in UI_EDIT
* - Edit fields: tensH, H, tensM, M, irrigation interval (1..3)
*/
#include <avr/sleep.h>
#include <avr/power.h>
#include <avr/interrupt.h>
#include <Button2.h>
#include "auxFunctions.h"
// -------------------- Hardware config --------------------
#define BUTTON_PIN 2 // example: PD2
// -------------------- State & event definitions --------------------
enum Event {
EV_NONE,
EV_WAKE,
EV_BTN_SHORT,
EV_BTN_LONG,
EV_TIMEOUT_EDIT,
EV_MINUTE_TICK,
EV_TIMEOUT_SLEEP
};
enum UiState {
UI_SLEEP,
UI_NORMAL,
UI_EDIT
};
enum EditFocus {
EDIT_FOCUS_TENS_H,
EDIT_FOCUS_H,
EDIT_FOCUS_TENS_M,
EDIT_FOCUS_M,
EDIT_FOCUS_INTERVAL
};
// -------------------- Globals --------------------
Button2 btn(BUTTON_PIN);
volatile uint32_t rtcSeconds = 0; // 1 Hz from Timer2 overflow
UiState uiState = UI_SLEEP;
EditFocus editFocus = EDIT_FOCUS_TENS_H;
uint8_t tensH = 1; // 0..2
uint8_t h = 3; // 0..9
uint8_t tensM = 4; // 0..5
uint8_t m = 7; // 0..9
uint8_t wateringIntervals = 1; // 1..3
uint32_t lastMinuteSec = 0;
uint32_t lastEditActionSec = 0;
uint32_t lastUserActionSec = 0;
const uint32_t EDIT_TIMEOUT_S = 15; // leave edit after 15 s
const uint32_t UI_IDLE_SLEEP_S = 30; // auto-sleep after 30 s in NORMAL
volatile bool wakeFlag = false;
bool firstPressAfterWake = false;
// -------------------- RTC: Timer2 async with 32.768 kHz crystal --------------------
ISR(TIMER2_OVF_vect) {
rtcSeconds++;
}
void setupRTC_Timer2() {
// Async Timer2 with external 32.768 kHz crystal on TOSC1/TOSC2
ASSR = (1 << AS2); // Timer2 async
TCCR2A = 0; // normal mode
// 32.768 kHz / 128 = 256 Hz → 256 counts → 1 s overflow
TCCR2B = (1 << CS22) | (1 << CS20); // prescaler 128
TCNT2 = 0;
TIFR2 = (1 << TOV2); // clear pending overflow
TIMSK2 = (1 << TOIE2); // enable overflow interrupt
// Wait for async register updates
while (ASSR & ((1 << TCR2BUB) | (1 << TCR2AUB) | (1 << OCR2AUB) |
(1 << OCR2BUB) | (1 << TCN2UB))) {
// wait
}
}
// -------------------- Button wake via PCINT --------------------
ISR(PCINT2_vect) { // PORTD PCINT[23:16]
wakeFlag = true;
}
void setupButtonWakePCINT() {
pinMode(BUTTON_PIN, INPUT_PULLUP);
// BUTTON_PIN = PD2 → PCINT18
PCICR |= (1 << PCIE2); // enable PCINT[23:16]
PCMSK2 |= (1 << PCINT18); // enable PCINT for PD2
}
// -------------------- Button2 callbacks --------------------
void setupButton2() {
btn.setPressedHandler([](Button2 &b) {
if (uiState == UI_SLEEP) {
firstPressAfterWake = true;
// wakeFlag already set by PCINT ISR
// treat as EV_WAKE
handleEvent(EV_WAKE);
return;
}
});
btn.setReleasedHandler([](Button2 &b) {
if (firstPressAfterWake) {
// ignore release after wake
firstPressAfterWake = false;
return;
}
});
btn.setTapHandler([](Button2 &b) {
if (uiState != UI_SLEEP) {
handleEvent(EV_BTN_SHORT);
}
});
btn.setLongClickHandler([](Button2 &b) {
if (uiState != UI_SLEEP) {
handleEvent(EV_BTN_LONG);
}
});
}
// -------------------- Sleep helpers --------------------
void prepareForSleep() {
// Turn off LEDs, sensors, etc.
ledsOff();
// power_adc_disable();
// power_spi_disable();
// power_twi_disable();
// Timer2 must stay enabled for RTC
}
void enterDeepSleep() {
set_sleep_mode(SLEEP_MODE_PWR_SAVE); // Timer2 async keeps running
sleep_enable();
sei();
sleep_cpu(); // sleep until interrupt
sleep_disable();
}
// -------------------- Event polling --------------------
Event pollPeriodicEvents() {
uint32_t nowSec = rtcSeconds;
// minute tick
if (nowSec - lastMinuteSec >= 60) {
lastMinuteSec += 60;
return EV_MINUTE_TICK;
}
// edit timeout
if (uiState == UI_EDIT && (nowSec - lastEditActionSec) > EDIT_TIMEOUT_S) {
return EV_TIMEOUT_EDIT;
}
// auto-sleep in NORMAL
if (uiState == UI_NORMAL && (nowSec - lastUserActionSec) > UI_IDLE_SLEEP_S) {
return EV_TIMEOUT_SLEEP;
}
return EV_NONE;
}
// -------------------- State handlers --------------------
void handleSleepState(Event ev);
void handleNormalState(Event ev);
void handleEditState(Event ev);
void handleEvent(Event ev) {
switch (uiState) {
case UI_SLEEP: handleSleepState(ev); break;
case UI_NORMAL: handleNormalState(ev); break;
case UI_EDIT: handleEditState(ev); break;
}
}
void handleSleepState(Event ev) {
if (ev == EV_WAKE) {
uiState = UI_NORMAL;
lastUserActionSec = rtcSeconds;
// Re-enable peripherals, LEDs, etc.
// initPeripherals();
// showWakeAnimation();
}
}
void handleNormalState(Event ev) {
switch (ev) {
case EV_BTN_SHORT:
// user activity only
Serial.println("ev: EV_BTN_SHORT");
lastUserActionSec = rtcSeconds;
break;
case EV_BTN_LONG:
// enter edit mode
Serial.println("ev: EV_BTN_LONG");
uiState = UI_EDIT;
editFocus = EDIT_FOCUS_TENS_H;
lastEditActionSec = rtcSeconds;
lastUserActionSec = rtcSeconds;
// flashAllLeds(2);
break;
case EV_MINUTE_TICK:
// incrementTime();
// updateBatteryColor();
break;
case EV_TIMEOUT_SLEEP:
uiState = UI_SLEEP;
ledsOff();
break;
default:
break;
}
}
void handleEditState(Event ev) {
switch (ev) {
case EV_BTN_SHORT:
// cycle focus
lastEditActionSec = rtcSeconds;
lastUserActionSec = rtcSeconds;
switch (editFocus) {
case EDIT_FOCUS_TENS_H: editFocus = EDIT_FOCUS_H; break;
case EDIT_FOCUS_H: editFocus = EDIT_FOCUS_TENS_M; break;
case EDIT_FOCUS_TENS_M: editFocus = EDIT_FOCUS_M; break;
//case EDIT_FOCUS_M: editFocus = EDIT_FOCUS_INTERVAL; break;
//case EDIT_FOCUS_INTERVAL: editFocus = EDIT_FOCUS_TENS_H; break;
case EDIT_FOCUS_M: editFocus = EDIT_FOCUS_TENS_H; break; // wihtout interval setting
}
break;
case EV_BTN_LONG:
// increment current field
lastEditActionSec = rtcSeconds;
lastUserActionSec = rtcSeconds;
switch (editFocus) {
case EDIT_FOCUS_TENS_H:
// increment tensH with valid range (0..2 or 0..1 depending on format)
incrementTensH();
break;
case EDIT_FOCUS_H:
incrementH();
break;
case EDIT_FOCUS_TENS_M:
incrementTensM();
break;
case EDIT_FOCUS_M:
incrementM();
break;
case EDIT_FOCUS_INTERVAL:
wateringIntervals = (wateringIntervals % 3) + 1; // 1→2→3→1
break;
}
break;
case EV_TIMEOUT_EDIT:
// leave edit mode, commit settings
uiState = UI_NORMAL;
lastUserActionSec = rtcSeconds;
// saveSettings();
// flashAllLeds(1);
break;
case EV_MINUTE_TICK:
// updateBatteryColor();
break;
default:
break;
}
}
// -------------------- Rendering --------------------
void renderDisplay() {
if (uiState == UI_SLEEP) {
// LEDs off
ledsOff();
return;
}
//clearLeds();
if (uiState == UI_NORMAL) {
setTimeDigitsToMatrix(tensH, h, tensM, m);
// overlayBatteryColor(); // green/yellow/red
}
if (uiState == UI_EDIT) {
// Base: show current time digits
setTimeDigitsToMatrix(tensH, h, tensM, m);
switch (editFocus) {
case EDIT_FOCUS_TENS_H:
highlightColumn(0, tensH);
break;
case EDIT_FOCUS_H:
highlightColumn(1, h);
break;
case EDIT_FOCUS_TENS_M:
highlightColumn(2, tensM);
break;
case EDIT_FOCUS_M:
highlightColumn(3, m);
break;
case EDIT_FOCUS_INTERVAL:
// showIntervalsOnMinutesColumn(wateringIntervals); // different color
break;
}
// overlayBatteryColor();
}
// leds.show();
}
// -------------------- Setup & loop --------------------
void setup() {
Serial.begin(19600);
//aFunction(); // example for externally managed code
initLeds();
testMatrixfill();
// Basic I/O
pinMode(BUTTON_PIN, INPUT_PULLUP);
// WS2811 init (stub)
// initLeds();
setupRTC_Timer2();
setupButtonWakePCINT();
setupButton2();
sei(); // enable global interrupts
// uiState = UI_SLEEP; // start in sleep
uiState = UI_NORMAL; // start in normal after power up
}
void loop() {
if (uiState == UI_SLEEP) {
prepareForSleep();
enterDeepSleep();
return;
}
btn.loop(); // Button2 processing while awake
Event ev = pollPeriodicEvents();
if (ev != EV_NONE) {
handleEvent(ev);
}
renderDisplay();
}
// -------------------- Placeholder increment helpers --------------------
// Implement these according to your time representation and constraints
void incrementTensH() {
// example: 0..2, ensure (tensH*10 + h) is valid hour
tensH++;
if (tensH > 2) { tensH = 0; }
}
void incrementH() {
// example: 0..9, ensure valid hour with tensH
h++;
if ( tensH < 2 && h > 9) { h = 0; }
if ( tensH == 2 && h > 3) { h = 0; }
}
void incrementTensM() {
// 0..5
tensM++;
if ( tensM > 5) { tensM = 0; }
}
void incrementM() {
// 0..9
m++;
if ( m > 9) { m = 0; }
}