#include <Arduino.h>
#ifdef __PLATFORMIO_BUILD_DEBUG__
#include <avr_debugger.h>
#endif
#include "State.h"
#include "LiquidCrystal_I2C.h"
#include "Interface.h"
#include "NumberBuilder.h"
#include "Calculator.h"
#include "InterruptUtils.h"
#include "Keypad.h"
#include <EEPROM.h>
#include "Memoizator.h"
constexpr int kLcdType = 0x27;
constexpr int kLcdWidth = 16;
constexpr int kLcdHeight = 2;
constexpr int kMaxDigitsCount = 8;
constexpr int kKeypadRowsCount = 4;
constexpr int kKeypadColumnsCount = 4;
constexpr int kNumpadStatesCount = 10;
constexpr int kAdditionalStatesCount = 4;
using MyKeypad = Keypad<kKeypadRowsCount, kKeypadColumnsCount>;
using NumpadStates = Array<ButtonState*, kNumpadStatesCount>;
using AdditionalStates = Array<ButtonState*, kAdditionalStatesCount>;
auto calculator = MakeCalculator([](const int& left, const int& right){return left | right;});
volatile bool need_redraw = false;
State current_state = State::kMainMenuCalculator;
NumberBuilder numberBuilder(kMaxDigitsCount);
Memoizator stored_value;
const MyKeypad::RowsPins keypad_rows = { 2, 3, 4, 5 };
const MyKeypad::ColumnsPins keypad_columns = { 6, 7, 8, 9 };
MyKeypad keypad(keypad_rows, keypad_columns);
const NumpadStates numpad_states
{
&keypad.get_last_state(3, 1), // 0
&keypad.get_last_state(0, 0), // 1
&keypad.get_last_state(0, 1), // 2
&keypad.get_last_state(0, 2), // 3
&keypad.get_last_state(1, 0), // 4
&keypad.get_last_state(1, 1), // 5
&keypad.get_last_state(1, 2), // 6
&keypad.get_last_state(2, 0), // 7
&keypad.get_last_state(2, 1), // 8
&keypad.get_last_state(2, 2) // 9
};
const AdditionalStates additional_states
{
&keypad.get_last_state(0, 3), // D1
&keypad.get_last_state(1, 3), // D2
&keypad.get_last_state(2, 3), // D3
&keypad.get_last_state(3, 3) // D4
};
const ButtonState& star_state = keypad.get_last_state(3, 0); // *
const ButtonState& sharp_state = keypad.get_last_state(3, 2); // #
const ButtonState& calculator_backspace = *additional_states[1];
const ButtonState& calculator_enter = *additional_states[2];
const ButtonState& dialog_yes = *additional_states[1];
const ButtonState& dialog_no = *additional_states[2];
auto ui = LCDSerialUserInterface(LiquidCrystal_I2C(kLcdType, kLcdWidth, kLcdHeight));
void Redraw()
{
switch (current_state)
{
case State::kMainMenuCalculator:
ui.DrawCalculatorMenu();
break;
case State::kMainMenuMemory:
ui.DrawMemoryMenu(stored_value);
break;
case State::kMainMenuClear:
ui.DrawClearMenu();
break;
case State::kCalculatorInputFirst:
ui.DrawFirstDialog(numberBuilder.number());
break;
case State::kCalculatorInputSecond:
ui.DrawSecondDialog(numberBuilder.number());
break;
case State::kCalculatorResult:
ui.DrawResult(calculator.count());
break;
case State::kCalculatorSaveResultDialog:
ui.DrawSaveDialog();
break;
case State::kClearMemoryDialog:
ui.DrawClearDialog();
break;
}
need_redraw = false;
}
void MenuDownHandler()
{
Serial.println("MenuDownHandler");
const int SIZE = 3;
int next = (int)current_state + 1;
if (next >= SIZE)
next = 0;
current_state = (State)next;
need_redraw = true;
}
void MenuEnterHandler()
{
Serial.println("MenuEnterHandler");
if (current_state != State::kMainMenuMemory)
{
switch (current_state)
{
case State::kMainMenuCalculator:
current_state = State::kCalculatorInputFirst;
stop_interrupt();
break;
case State::kMainMenuClear:
current_state = State::kClearMemoryDialog;
stop_interrupt();
break;
default:
break;
}
need_redraw = true;
}
}
bool PressedAnyKey(const MyKeypad::ButtonStates& states, int size)
{
for (int i = 0; i < size; ++i)
if (states[i].click_count() > 0)
return true;
return false;
}
bool isClickedOrPressed(const ButtonState& state)
{
return state.click_count() > 0 || state.is_on_pressed();
}
void MainMenuLoop()
{
invoke_interrupts();
}
void InternalSaveDialog(int saved_value)
{
keypad.update();
if (dialog_yes.click_count() > 0)
{
Serial.println(saved_value);
stored_value = saved_value;
Serial.println(stored_value);
current_state = State::kMainMenuCalculator;
start_interrupt();
need_redraw = true;
} else if (dialog_no.click_count() > 0)
{
current_state = State::kMainMenuCalculator;
start_interrupt();
need_redraw = true;
}
}
void ClearDialogLoop()
{
InternalSaveDialog(42);
}
void SaveDialogLoop()
{
InternalSaveDialog(calculator.count());
}
void CalculatorLoop()
{
constexpr int kDigitsCount = 10;
keypad.update();
if (current_state == State::kCalculatorResult)
{
if (PressedAnyKey(keypad.get_last_states(), kKeypadRowsCount * kKeypadColumnsCount))
{
current_state = State::kCalculatorSaveResultDialog;
need_redraw = true;
}
return;
}
for (int i = 0; i < kDigitsCount; ++i)
if (isClickedOrPressed(*numpad_states[i]))
{
if (!numberBuilder.can_add_digit())
break;
numberBuilder.add_digit(i);
need_redraw = true;
}
if (isClickedOrPressed(sharp_state)){
switch (current_state)
{
case State::kCalculatorInputFirst:
calculator.set_op1(stored_value);
break;
case State::kCalculatorInputSecond:
calculator.set_op2(stored_value);
break;
default:
break;
}
numberBuilder.clear();
current_state = (State)((int)current_state + 1);
need_redraw = true;
}
else if (isClickedOrPressed(calculator_backspace))
{
if (numberBuilder.can_backspace())
{
numberBuilder.backspace();
need_redraw = true;
}
} else if (calculator_enter.click_count() > 0)
{
switch (current_state)
{
case State::kCalculatorInputFirst:
calculator.set_op1(numberBuilder.number());
break;
case State::kCalculatorInputSecond:
calculator.set_op2(numberBuilder.number());
break;
default:
break;
}
numberBuilder.clear();
current_state = (State)((int)current_state + 1);
need_redraw = true;
}
}
void setup()
{
#ifdef __PLATFORMIO_BUILD_DEBUG__
debug_init();
#endif
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, LOW);
init_interrupt();
set_handler(0, MenuDownHandler);
set_handler(1, MenuEnterHandler);
start_interrupt();
ui.init();
need_redraw = true;
}
void loop()
{
if (need_redraw)
Redraw();
switch (current_state)
{
case State::kMainMenuCalculator:
case State::kMainMenuMemory:
case State::kMainMenuClear:
MainMenuLoop();
break;
case State::kCalculatorInputFirst:
case State::kCalculatorInputSecond:
case State::kCalculatorResult:
CalculatorLoop();
break;
case State::kCalculatorSaveResultDialog:
SaveDialogLoop();
break;
case State::kClearMemoryDialog:
ClearDialogLoop();
break;
}
}