/*
User requests:
-memory stores the cashless and cash income, served tokens, everything must be erasable
-2 LCD displays
-custom receipt printing
Error codes:
1 -
2 -
3 -
4 -
5 - hopper empty
6 - hopper dispense timer overflow
7 - write 4 bytes function error (can't write ROM)
8 - read 4 bytes function error (can't read ROM)
9 - RTC read error
10 - RTC write error
Dependency: Liquidcrystal I2C library by Frank de Brabander
2022.03.05
*/
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <string.h>
//constants
#define pulse_multiplier_bv 1 //pulse multipler for bill validator
#define pulse_multiplier_nx 1 //same as above, for nx
#define led_timer 500 //leds on-off time
#define pulse_timeout 150
#define pulse_gap 20
#define btn_cd 3000 //button is not debounced, it only has cooldown
#define keepalive_rate 1000 //change every sec.
#define hopper_empty_chk_rate 500 //check on hopper empty signal so often
#define token_allowed_delay 6000
#define menu_timeout 15000
#define receipt_timeout 30000
//inputs
#define btn_1 A2
#define btn_2 A1
#define btn_menu 13
#define btn_print A0
#define bv_pulse_pin 2
#define bv_busy_pin 4
#define nx_pulse_pin 10
#define hopper_empty_pin 6
#define hopper_opto_pin 8
//outputs
#define btn_print_LED 11 //also serves as error led
#define hopper_motor_pin 12
#define bv_inh_pin 3
#define nx_inh_pin 9
//LCD and eeprom constants
#define LCD_main_addr 0x27 //default address for PCF 8574T
#define LCD_secondary_addr 0x26 //A0 soldered short
#define eeprom_addr 0x57
#define mem_a_price 0x20
#define mem_a_token_sum 0x30
#define mem_a_sum_nx 0x40
#define mem_a_sum_bv 0x50
#define mem_a_print_counter 0x60
#define rtc_addr 0x68
const uint8_t LCD_COLS = 16;
const uint8_t LCD_ROWS = 2;
const uint16_t upd_time_rate = 10000; //check clock every 10s
const uint16_t menu_idle = 10000;
LiquidCrystal_I2C lcd(LCD_main_addr, LCD_COLS, LCD_ROWS); // create main lcd object
LiquidCrystal_I2C lcd2(LCD_secondary_addr, LCD_COLS, LCD_ROWS); // create secondary lcd object
const int BAUDRATE = 9600; //for serial communication
char line0[17]; //global LCD variables
char line1[17];
char print_line[20];
uint8_t input_pins[] = {bv_pulse_pin, bv_busy_pin, nx_pulse_pin, hopper_empty_pin, hopper_opto_pin};
uint8_t output_pins[] = {btn_print_LED, hopper_motor_pin, bv_inh_pin, nx_inh_pin};
uint8_t btn_pins[] = {btn_1, btn_2, btn_menu, btn_print};
uint8_t count_nx, count_bv, valid_pulses, token_price, token_price_prev, menu_lvl, menu_lvl_prev, view_page, view_page_prev;
uint8_t error_code = 0; //0 is normal operation
uint8_t token_counter = 0;
uint8_t last_tokens = 0;
uint8_t last_buy_nx = 0;
uint8_t last_buy_bv = 0;
uint8_t hopper_empty_chk_counter = 0;
uint32_t bank = 0;
uint32_t bank_tmp = 0;
uint32_t cash_sum_nx = 0;
uint32_t cash_sum_bv = 0;
uint32_t token_sum = 0;
uint32_t print_counter = 0;
uint32_t* const prnt_cnt = &print_counter; //the pointer is itself constant, but the value it points to isn't
uint32_t menu_start_ms, lcd_upd_ms, btn_print_ms, btn_menu_ms, nx_ms, nx_timeout_ms, bv_ms, bv_timeout_ms, bv_busy_ms, led_ms, keepalive_ms, hopper_empty_chk_ms, receipt_ms, read_tmp;
byte time_arr[7];
byte time_arr_tmp[7];
bool error_flag = false; //error flag
bool stby_forced = 1;
bool lcds_reset = 0;
bool bv_busy = 0;
bool hopper_busy = 0;
bool menu_active = 0;
bool receipt_available = 0;
bool firstboot = 1;
bool return_from_service = 0;
bool conf_flag, runonce, prev_counter_state, ledstate, show_cash, keepalive_state, busy_mssg, menu_refresh, menu_forced_exit;
void setup(){
delay(2000);
Serial.begin(BAUDRATE);
Wire.begin();
lcd.init(); lcd2.init();
lcd.backlight(); lcd2.backlight();
/*
lcd.createChar(0, aa); //char á
//WARNING! Addressing 0x00 will cause error.
//CGRAM rolls over, so it can be called as 0x08
lcd.createChar(1, ee); //char é
lcd.createChar(2, ii); //char í
lcd.createChar(3, oo); //char ó
lcd.createChar(4, uu); //char ú
lcd.createChar(5, oe); //char ö
lcd.createChar(6, ue); //char ü
lcd.createChar(7, ooe); //char ő
*/
sprintf(line0," G-Ton v1.00");
sprintf(line1," G-Trade Inpex");
lcd.blink(); lcd2.blink();
upd_lcd();
delay(2500);
for (uint8_t i = 0; i < (sizeof(input_pins)/sizeof(input_pins[0])); i++){ //all input pins have pullups
pinMode(input_pins[i], INPUT);
}
for (uint8_t i = 0; i < (sizeof(btn_pins)/sizeof(btn_pins[0])); i++){ //pull-up btn pins
pinMode(btn_pins[i], INPUT_PULLUP);
}
for (uint8_t i = 0; i < (sizeof(output_pins)/sizeof(output_pins[0])); i++){
pinMode(output_pins[i], OUTPUT);
}
for (uint8_t i = 0; i < (sizeof(output_pins)/sizeof(output_pins[0])); i++){
digitalWrite(output_pins[i], LOW);
}
//read in rom contents
read_tmp = read4bytes(uint8_t(0x00), mem_a_price);
token_price = uint8_t(read_tmp & 0xFF);
token_sum = read4bytes(uint8_t(0x00), mem_a_token_sum);
cash_sum_nx = read4bytes(uint8_t(0x00), mem_a_sum_nx);
cash_sum_bv = read4bytes(uint8_t(0x00), mem_a_sum_bv);
*prnt_cnt = read4bytes(uint8_t(0x00), mem_a_print_counter);
prnt_soft_reset();
delay(3000);
codepage_1250(); //set thermal printer codepage
delay(10);
prnt_set_center(); //print everything in the middle
delay(10);
lcd.noBlink();
lcd2.noBlink();
//updatetime(); stby_forced will take care of it
if(token_price == 0 || token_price == 255){
token_price = 5;
}// test write
Serial.println("--------------");
Serial.println(token_price);
Serial.println(token_sum);
Serial.println(cash_sum_nx);
Serial.println(cash_sum_bv);
Serial.println(*prnt_cnt);
}
void loop(){
//error handling first
while(error_flag == true){ //check if error was solved, till then blinky code. Errors above 5 can be only reset with power cycle
if(runonce == false){ //runonce should be reset before leaving this while loop
nx_on(0);
bv_on(0);
sprintf(line0,"Defectiune: %u", error_code); //Error
if(error_code == 5){
sprintf(line1,"Jetoane terminat"); //Hopper empty
}else{
sprintf(line1," ");
}
if(bank != 0){
sprintf(line1,"%lu,00 RON, %u Tn", bank, token_counter);
}
upd_lcd();
runonce = true;
}
if(error_code == 5){ //hopper empty
if(millis() - hopper_empty_chk_ms >= hopper_empty_chk_rate){
if(digitalRead(hopper_empty_pin) == HIGH && hopper_empty_chk_counter < 6){
hopper_empty_chk_counter++;
}
if(digitalRead(hopper_empty_pin) == LOW && hopper_empty_chk_counter > 0){
hopper_empty_chk_counter--;
}
if(hopper_empty_chk_counter == 0){
runonce = false;
error_code = 0;
error_flag = 0;
nx_on(1);
bv_on(1);
stby_forced = 1;
}
hopper_empty_chk_ms = millis();
}
}
if(error_code > 5){ //serious errors
while(1){
err_blinky(error_code);
}
}
delay(10);
}
//reset error display drive
if(runonce == true){
runonce = false;
}
//reset LCD every hour
if((menu_active == false) && (bank == 0) && (lcds_reset == true) && (hopper_busy == false) && (error_flag == false)){
nx_on(0);
bv_on(0);
lcd.noDisplay();
lcd2.noDisplay();
delay(500);
lcd.init(); lcd2.init();
lcd.backlight(); lcd2.backlight();
delay(500);
upd_lcd();
nx_on(1);
bv_on(1);
lcds_reset = false;
}
//check on hopper contents
if((millis() - hopper_empty_chk_ms >= hopper_empty_chk_rate) && (menu_active == false) && (hopper_busy == 0) && (count_nx == 0) && (count_bv == 0) && (digitalRead(nx_inh_pin) == HIGH) && (digitalRead(bv_busy_pin) == HIGH) && (bank == 0)){
if(digitalRead(hopper_empty_pin) == HIGH && hopper_empty_chk_counter < 6){
hopper_empty_chk_counter++;
}
if(digitalRead(hopper_empty_pin) == LOW && hopper_empty_chk_counter > 0){
hopper_empty_chk_counter--;
}
if(hopper_empty_chk_counter == 6){
error_code = 5;
error_flag = 1;
}
hopper_empty_chk_ms = millis();
}
//stby display update
if((count_nx == 0) && (count_bv == 0) && (bank == 0) && (menu_active == false) && (hopper_busy == 0) && (((millis() - lcd_upd_ms) >= upd_time_rate) || stby_forced == 1)){
if(stby_forced == 1){
sprintf(line1, " In asteptare"); //Standby
stby_forced = 0;
}
updatetime();
lcd_upd_ms = millis();
}
//keepalive
if((count_nx == 0) && (count_bv == 0) && (menu_active == false) && (hopper_busy == 0) && (digitalRead(nx_inh_pin) == HIGH) && (digitalRead(bv_busy_pin) == HIGH) && (bank == 0) && ((millis() - keepalive_ms) >= keepalive_rate)){
lcd.setCursor(13,0); lcd2.setCursor(13,0);
if(keepalive_state == true){
lcd.print(" "); lcd2.print(" ");
}else{
lcd.print(":"); lcd2.print(":");
}
keepalive_state = !keepalive_state;
keepalive_ms = millis();
}
//count incoming pulses from nx and update bank
if((digitalRead(nx_pulse_pin) == LOW) && (count_nx == 0) && (menu_active == false)){
bv_on(0);
hopper_busy = 1;
if(digitalRead(btn_print_LED) == HIGH){
digitalWrite(btn_print_LED, LOW);
}
valid_pulses = 0;
count_nx = 1;
nx_ms = millis(); //falling edge detected
sprintf(line0," Numarare"); //Counting
sprintf(line1,"Asteptati!.."); //Please wait..
upd_lcd();
}
if(count_nx == 1){
if((valid_pulses == 0) && ((millis() - nx_ms) >= pulse_gap)){
if(digitalRead(nx_pulse_pin) == LOW){
count_nx = 2;
valid_pulses = 1;
prev_counter_state = 0;
nx_timeout_ms = nx_ms = millis();
}else{
count_nx = 0;
valid_pulses = 0;
bv_on(1);
nx_on(1);
hopper_busy = 0;
}
}
}
if((count_nx == 2) && ((millis() - nx_ms) >= pulse_gap)){
if(digitalRead(nx_pulse_pin) == HIGH && prev_counter_state == 0){
nx_timeout_ms = millis();
prev_counter_state = 1;
}
if(digitalRead(nx_pulse_pin) == LOW && prev_counter_state == 1){
nx_timeout_ms = millis();
prev_counter_state = 0;
valid_pulses++;
}
if(millis() - nx_timeout_ms > pulse_timeout){
count_nx = 3;
}
nx_ms = millis();
}
if(count_nx == 3){
bank += valid_pulses*pulse_multiplier_nx; //used for lcd display
cash_sum_nx += valid_pulses*pulse_multiplier_nx; //used for eprom write
last_buy_nx += valid_pulses*pulse_multiplier_nx; //used for printing
write4bytes(uint8_t(0x00), mem_a_sum_nx, cash_sum_nx);
count_nx = 0;
valid_pulses = 0;
nx_on(0);
}
//count incoming pulses from bv and update bank
if((digitalRead(bv_pulse_pin) == LOW) && (count_bv == 0) && (menu_active == false)){
nx_on(0);
hopper_busy = 1;
if(digitalRead(btn_print_LED) == HIGH){
digitalWrite(btn_print_LED, LOW);
}
valid_pulses = 0;
count_bv = 1;
bv_ms = millis(); //falling edge detected
sprintf(line0," Numarare"); //Counting
sprintf(line1,"Asteptati!.."); //Please wait..
upd_lcd();
}
if(count_bv == 1){
if((valid_pulses == 0) && ((millis() - bv_ms) >= pulse_gap)){
if(digitalRead(bv_pulse_pin) == LOW){
count_bv = 2;
valid_pulses = 1;
prev_counter_state = 0;
bv_timeout_ms = bv_ms = millis();
}else{
hopper_busy = 0;
count_bv = 0;
valid_pulses = 0;
bv_on(1);
nx_on(1);
}
}
}
if((count_bv == 2) && ((millis() - bv_ms) >= pulse_gap)){
if(digitalRead(bv_pulse_pin) == HIGH && prev_counter_state == 0){
bv_timeout_ms = millis();
prev_counter_state = 1;
}
if(digitalRead(bv_pulse_pin) == LOW && prev_counter_state == 1){
bv_timeout_ms = millis();
prev_counter_state = 0;
valid_pulses++;
}
if(millis() - bv_timeout_ms > pulse_timeout){
count_bv = 3;
}
bv_ms = millis();
}
if(count_bv == 3){
bank += valid_pulses*pulse_multiplier_bv; //used for lcd display
cash_sum_bv += valid_pulses*pulse_multiplier_bv; //used for eprom
last_buy_nx += valid_pulses*pulse_multiplier_bv; //used for printer
write4bytes(uint8_t(0x00), mem_a_sum_bv, cash_sum_bv);
count_bv = 0;
valid_pulses = 0;
bv_on(0);
}
//bank value changed
if(bank_tmp != bank && menu_active == false){
if(bank < token_price && bank != 0){ //cash was sent in, but less than the token price
sprintf(line0,"%lu,00 RON", bank);
sprintf(line1,"Pret jeton: %u,00", token_price); //Token price is:
upd_lcd();
bank_tmp = bank;
hopper_busy = 0;
busy_mssg = 0;
}
if(bank >= token_price){
hopper_busy = 1;
token_counter += bank / token_price;
token_sum += token_counter;
last_tokens = token_counter;
if(bank % token_price != 0){
bank = bank % token_price;
bank_tmp = 0;
}else{
bank = 0;
}
sprintf(line0," Eliberare"); //Dispensing
sprintf(line1,"Asteptati!..."); //Please wait...
upd_lcd();
delay(100);
write4bytes(uint8_t(0x00), mem_a_token_sum, token_sum);
drive_hopper();
if(error_flag == false){ //returned with no error
sprintf(line0," Multumim!"); //Thank you
sprintf(line1," La revedere"); //Goodbye
upd_lcd();
delay(1500);
receipt_available = 1;
receipt_ms = millis();
hopper_busy = 0;
stby_forced = 1;
return_from_service = true;
}
}
if(bank == 0){
bank_tmp = bank;
}
}
//set-reset inhibits
if(digitalRead(bv_busy_pin) == LOW && error_flag == false && menu_active == false){
if(digitalRead(nx_inh_pin) == HIGH){
nx_on(0);
if(digitalRead(btn_print_LED) == HIGH){
digitalWrite(btn_print_LED, LOW);
}
}
if(busy_mssg == 0){
sprintf(line0," Procesare"); //Validator busy
sprintf(line1,"Asteptati!."); //Please wait.
upd_lcd();
busy_mssg = 1;
return_from_service = true;
}
}
if(digitalRead(bv_busy_pin) == HIGH && error_flag == false && hopper_busy == 0 && menu_active == false){ //turn on nx if left in inhibit
if(digitalRead(nx_inh_pin) == LOW){
nx_on(1);
}
if(digitalRead(bv_inh_pin) == HIGH){
bv_on(1);
}
if(busy_mssg == 1){
busy_mssg = 0;
bank_tmp = 0; //this forces the display to show remaining cash
if(bank == 0){
stby_forced = 1;
}
}
}
//print functions
if(millis() - receipt_ms > receipt_timeout && receipt_available == true){
if(digitalRead(btn_print_LED) == HIGH){
digitalWrite(btn_print_LED, LOW);
}
receipt_available = false;
}
//print button pushed
if((digitalRead(btn_print) == LOW) && (millis() - btn_print_ms > btn_cd) && (receipt_available == true)){ //print receipt
delay(33);
if(digitalRead(btn_print) == LOW){
nx_on(0);
bv_on(0);
if(digitalRead(btn_print_LED) == LOW){
digitalWrite(btn_print_LED, HIGH);
}
(*prnt_cnt)++; //increase before printing, so there won't be 0000 ticket
Serial.println(*prnt_cnt); //test
write4bytes(uint8_t(0x00), mem_a_print_counter, *prnt_cnt);
delay(100);
if(error_flag == false){ //memory operation successful
uint32_t temp_m = 0;
sprintf(line0," Imprimare"); //Printing
sprintf(line1,"Asteptati!"); //Please wait
upd_lcd();
//print it!
clear_buf();
prnt_char_set(0b111); //set to print double h,w with underline
Serial.println("SC. Stebali Trans srl.");
prnt_char_set(0); //set to print small chars
Serial.println("pl. Salonta");
Serial.println("str. Menumorut nr. 2");
Serial.println("jud. Bihor");
Serial.println("CIF RO10094495");
prnt_char_set(0b01); //set to double h
sprintf(print_line, "1 bucata jeton = %u,00 RON", token_price);
Serial.println(print_line);
prnt_char_set(0b11);
sprintf(print_line, "%u buc X %u,00 RON", last_tokens, token_price);
Serial.println(print_line);
temp_m = last_tokens;
temp_m = temp_m*token_price;
sprintf(print_line, "Total %lu,00 RON", temp_m);
Serial.println(print_line);
prnt_char_set(0);
//sprintf(print_line, "%u", *prnt_cnt);
Serial.println(*prnt_cnt);
sprintf(print_line,"2%03X-%02X-%02X %02X:%02X", time_arr[6], time_arr[5], time_arr[4], time_arr[2], time_arr[1]);
Serial.println(print_line);
prnt_lf(0x03);
endcut_partial();
delay(2500);
prnt_char_set(0); //just to be sure
if(digitalRead(btn_print_LED) == HIGH){
digitalWrite(btn_print_LED, LOW);
}
receipt_available = false;
if(bank != 0){
bank_tmp = 0; //so it will display credit
}else{
stby_forced = 1;
return_from_service = true;
}
}
btn_print_ms = millis();
}
}
//menu button pushed
if((digitalRead(btn_menu) == LOW) && (millis() - btn_menu_ms > btn_cd) && hopper_busy == 0 && menu_active == false){ //set menu_active
delay(53);
if(digitalRead(btn_menu) == LOW){
nx_on(0);
bv_on(0);
delay(500);
if(digitalRead(btn_menu) == LOW){
if(digitalRead(btn_print_LED) == HIGH){
digitalWrite(btn_print_LED, LOW);
}
menu_lvl = 1;
menu_lvl_prev = 0;
menu_active = true;
btn_menu_ms = menu_start_ms = millis();
sprintf(line0," -Meniu-");
sprintf(line1," ");
upd_lcd();
delay(1000);
menu_refresh = false;
menu_forced_exit = false;
}else{
nx_on(1);
bv_on(1);
}
}
}
//menu
if(menu_active == true){
if(menu_lvl != menu_lvl_prev){
if(menu_lvl == 1){
sprintf(line1,"Setari ora/data"); //Set date-time
upd_lcd();
if(menu_refresh == true){
delay(500);
menu_refresh = false;
}
}
if(menu_lvl == 2){
sprintf(line1,"Sters total"); //Delete all
upd_lcd();
if(menu_refresh == true){
delay(500);
menu_refresh = false;
}
}
if(menu_lvl == 3){
sprintf(line1,"Sters jetoane"); //Delete tokens
upd_lcd();
if(menu_refresh == true){
delay(500);
menu_refresh = false;
}
}
if(menu_lvl == 4){
sprintf(line1,"Sters plati Card"); //Delete cashless
upd_lcd();
if(menu_refresh == true){
delay(500);
menu_refresh = false;
}
}
if(menu_lvl == 5){
sprintf(line1,"Sters plati Cash"); //Delete cash
upd_lcd();
if(menu_refresh == true){
delay(500);
menu_refresh = false;
}
}
if(menu_lvl == 6){
sprintf(line1,"Configurare pret"); //Set price
upd_lcd();
if(menu_refresh == true){
delay(500);
menu_refresh = false;
}
}
if(menu_lvl == 7){
sprintf(line1,"Consultare date"); //View data
upd_lcd();
if(menu_refresh == true){
delay(500);
menu_refresh = false;
}
}
menu_lvl_prev = menu_lvl;
}
if(digitalRead(btn_1) == LOW && menu_lvl < 7){
menu_lvl++;
menu_start_ms = millis();
menu_refresh = true;
}
if(digitalRead(btn_2) == LOW && menu_lvl > 1){
menu_lvl--;
menu_start_ms = millis();
menu_refresh = true;
}
if(digitalRead(btn_menu) == LOW){
delay(32);
if(menu_btn_validator() == true){ //longpress accepted, enter menu according
if(menu_lvl == 1){
//set date and time
settime();
ok_mssg(1);
menu_forced_exit = true;
}
if(menu_lvl == 2){
//delete all
token_sum = 0;
cash_sum_nx = 0;
cash_sum_bv = 0;
*prnt_cnt = 0;
write4bytes(uint8_t(0x00), mem_a_token_sum, token_sum);
if(error_flag == false){
write4bytes(uint8_t(0x00), mem_a_sum_nx, cash_sum_nx);
if(error_flag == false){
write4bytes(uint8_t(0x00), mem_a_sum_bv, cash_sum_bv);
if(error_flag == false){
write4bytes(uint8_t(0x00), mem_a_print_counter, *prnt_cnt);
}
}
}
if(error_flag == false){ //all 3 memory operations were successful
ok_mssg(2);
}
}
if(menu_lvl == 3){
//delete tokens
token_sum = 0;
write4bytes(uint8_t(0x00), mem_a_token_sum, token_sum);
if(error_flag == false){ //all memory operations were successful
ok_mssg(2);
}
}
if(menu_lvl == 4){
//delete cashless
cash_sum_nx = 0;
write4bytes(uint8_t(0x00), mem_a_sum_nx, cash_sum_nx);
if(error_flag == false){ //all memory operations were successful
ok_mssg(2);
}
}
if(menu_lvl == 5){
//delete cash
cash_sum_bv = 0;
write4bytes(uint8_t(0x00), mem_a_sum_bv, cash_sum_bv);
if(error_flag == false){ //all memory operations were successful
ok_mssg(2);
}
}
if(menu_lvl == 6){
//set price
conf_flag = true;
token_price_prev = token_price;
sprintf(line0,"Pret jeton:"); //Token price
sprintf(line1,"%u,00 RON", token_price);
upd_lcd();
while(conf_flag == true){
if(digitalRead(btn_1) == LOW && token_price < 250){
token_price++;
menu_start_ms = millis();
}
if(digitalRead(btn_2) == LOW && token_price > 1){
token_price--;
menu_start_ms = millis();
}
if(token_price_prev != token_price){
sprintf(line1,"%u,00 RON", token_price);
upd_lcd();
token_price_prev = token_price;
delay(300);
}
if(digitalRead(btn_menu) == LOW){
delay(32);
if(menu_btn_validator() == true){ //longpress OK
//save value to ROM and exit
write4bytes(uint8_t(0x00), mem_a_price, token_price); //function input is uint32_t, price is uint8_t! works now
ok_mssg(1);
conf_flag = false;
menu_forced_exit = true;
}
menu_start_ms = millis();
}
if(millis() - menu_start_ms > menu_timeout){
conf_flag = false;
}
}
}
if(menu_lvl == 7){
//view data
conf_flag = true;
view_page_prev = view_page = 1;
sprintf(line0,"Pret jeton:"); //Token price
sprintf(line1,"%u,00 RON", token_price);
upd_lcd();
while(conf_flag == true){
if(digitalRead(btn_1) == LOW && view_page < 5){
view_page++;
menu_start_ms = millis();
}
if(digitalRead(btn_2) == LOW && view_page > 1){
view_page--;
menu_start_ms = millis();
}
if(view_page_prev != view_page){
if(view_page == 1){
sprintf(line0,"Pret jeton:"); //Token price
sprintf(line1,"%u,00 RON", token_price);
}else if(view_page == 2){
sprintf(line0,"Jetoaneeliberate"); //Tokens out:
sprintf(line1,"%lu buc", token_sum);
}else if(view_page == 3){
sprintf(line0,"Plata cu CARD:"); //Cashless in:
sprintf(line1,"%lu,00 RON", cash_sum_nx);
}else if(view_page == 4){
sprintf(line0,"Plata CASH"); //Cash in:
sprintf(line1,"%lu,00 RON", cash_sum_bv);
}else if(view_page == 5){
sprintf(line0,"nr. bon"); //print nr.
sprintf(line1,"%lu", *prnt_cnt);
}
upd_lcd();
view_page_prev = view_page;
delay(300);
}
if(digitalRead(btn_menu) == LOW){
delay(32);
if(menu_btn_validator() == true){ //longpress OK
ok_mssg(0);
conf_flag = false;
menu_forced_exit = true;
}
menu_start_ms = millis();
}
if(millis() - menu_start_ms > menu_timeout){
conf_flag = false;
}
}
}
}
menu_start_ms = millis();
}
if((millis() - menu_start_ms > menu_timeout) || (menu_forced_exit == true)){ //shut down menu
menu_active = false;
if(bank != 0){
bank_tmp = 0; //so it will display credit
}else{
stby_forced = 1;
}
}
}
//led showoff
if((receipt_available == true) && ((millis() - led_ms) >= led_timer) && (hopper_busy == 0) && (menu_active == false)){
if(ledstate == true){
digitalWrite(btn_print_LED, HIGH);
}else{
digitalWrite(btn_print_LED, LOW);
}
ledstate = !ledstate;
led_ms = millis();
}
} //end of main loop
//delete OK message
void ok_mssg(uint8_t type){
if(type == 1){
sprintf(line0," Salvare OK"); //Save OK
}else if(type == 2){
sprintf(line0," Stergere OK"); //Delete OK
}else if(type == 0){
sprintf(line0," Iesire"); //Exiting
}
sprintf(line1," ");
upd_lcd();
menu_forced_exit = true;
delay(2000);
}
//menu button longpress check
bool menu_btn_validator(){
bool retval = false;
if(digitalRead(btn_menu) == LOW){
lcd.setCursor(13,0);
lcd.print(">");
lcd2.setCursor(13,0);
lcd2.print(">");
delay(1000);
if(digitalRead(btn_menu) == LOW){
lcd.print(">");
lcd2.print(">");
delay(1000);
if(digitalRead(btn_menu) == LOW){
lcd.print(">");
lcd2.print(">");
delay(300);
retval = true;
}
}
if(retval == false){ //transient, false button press
lcd.setCursor(13,0);
lcd.print(" ");
lcd2.setCursor(13,0);
lcd2.print(" ");
}
}
return retval;
}
//update both LCD lines with line0 and line1 char arrays
void upd_lcd(){ //write line0 and line1 on LCD
lcd.clear();
lcd2.clear(); //clear is also home positioning
lcd.print(line0);
lcd.setCursor(0,1); //must move to second row
lcd.print(line1);
lcd2.print(line0);
lcd2.setCursor(0,1);
lcd2.print(line1);
}
//update time
void updatetime(){
uint8_t diff = 0;
uint8_t j;
raw_time(); //fills time_arr with actual data
for(j = 1; j < (sizeof(time_arr)/sizeof(time_arr[0])); j++){ //time_arr[0] is seconds, so j starts from 1
if(time_arr[j] != time_arr_tmp[j]){
diff++;
}
}
if((time_arr[2] != time_arr_tmp[2]) && (firstboot == false)){ //1 hour passed, time to reset the LCDs
lcds_reset = true;
}
if(diff != 0 || stby_forced == 1 || menu_forced_exit == true || (return_from_service == true && busy_mssg == false)){
for(j = 0; j < (sizeof(time_arr)/sizeof(time_arr[0])); j++){
time_arr_tmp[j] = time_arr[j];
}
menu_forced_exit = false;
return_from_service = false;
sprintf(line0,"2%03X-%02X-%02X %02X:%02X", time_arr[6], time_arr[5], time_arr[4], time_arr[2], time_arr[1]);
upd_lcd();
}
if(firstboot == true){
firstboot = false;
}
}
//fill up time array with data
void raw_time(){
int idx = 0;
uint8_t cnt = 0;
byte temp = 0;
bool rdy = 0;
while(cnt < 5 && rdy == 0){
Wire.beginTransmission(rtc_addr);
Wire.write(0x00); // Set device to start read reg 0
if(Wire.endTransmission() == 0){
Wire.requestFrom(rtc_addr, 7, 1); // request 7 bytes from the DS3231 and release the I2C bus
while(Wire.available() > 0){ // read the bytes into an array
temp = Wire.read(); // read each byte from the register
time_arr[idx] = temp; // store each single byte in the array
idx++;
}
rdy = 1;
error_code = 0; //in case there is a previous error read
error_flag = false;
}else{
if(cnt == 4){ //last run
for(uint8_t i = 0; i < (sizeof(time_arr)/sizeof(time_arr[0])); i++){
time_arr[i] = 0;
}
}
error_code = 9; // RTC read error
error_flag = true;
cnt++;
delay(10);
}
}
}
//drive hopper
void drive_hopper(){
uint32_t motor_start_ms = millis();
uint8_t dispense_lvl = 0;
uint8_t sent_tokens = 0;
while(token_counter > 0 && ((millis() - motor_start_ms) < token_allowed_delay)){ //must be enough to dispense 1 token, otherwise error
if(dispense_lvl == 0){
digitalWrite(hopper_motor_pin, HIGH);
dispense_lvl = 1;
}
if(dispense_lvl == 1){
if(digitalRead(hopper_opto_pin) == LOW){ //falling edge detected
delay(10);
if(digitalRead(hopper_opto_pin) == LOW){ //still low
digitalWrite(hopper_motor_pin, LOW);
dispense_lvl = 2;
}
}
}
if(dispense_lvl == 2){ //wait for rising edge
if(digitalRead(hopper_opto_pin) == HIGH){ //rising edge detected
delay(20);
if(digitalRead(hopper_opto_pin) == HIGH){ //coin left the opto gate
sent_tokens++;
lcd.setCursor(13,0);
lcd.print(sent_tokens);
lcd2.setCursor(13,0);
lcd2.print(sent_tokens);
token_counter--;
dispense_lvl = 0;
motor_start_ms = millis();
}
}
}
}
if(digitalRead(hopper_motor_pin) == HIGH){
digitalWrite(hopper_motor_pin, LOW);
}
if(token_counter != 0){ //timer overflow, hopper error
error_code = 6;
error_flag = true;
}
delay(800); //only to make last sent_token display readable
}
//bill validator inhibit
void bv_on(bool state){
if (state == true){
digitalWrite(bv_inh_pin, LOW);
}else{
digitalWrite(bv_inh_pin, HIGH);
}
}
//nx inhibit
void nx_on(bool state){
if (state == true){
digitalWrite(nx_inh_pin, HIGH);
}else{
digitalWrite(nx_inh_pin, LOW);
}
}
//check if rom is busy
uint8_t chk_rom_busy(void){
uint8_t retVal = 4;
Wire.beginTransmission(eeprom_addr);
retVal = Wire.endTransmission(); //returns 0 if OK, >=1 otherwise
return retVal; //return busy status, 0 is not busy
}
//write rom
void write4bytes(uint8_t addr_h, uint8_t addr_l, uint32_t datacore){ //returns void, only sets global error flags on fault
uint8_t retries = 0;
uint8_t data_out[4];
while(chk_rom_busy() != 0 && retries < 5){
delay(10);
retries++;
}
if(retries == 5){
error_flag = true;
error_code = 7;
}else{
data_out[3] = uint8_t((datacore & 0xFF));
data_out[2] = uint8_t(((datacore >> 8) & 0xFF));
data_out[1] = uint8_t(((datacore >> 16) & 0xFF));
data_out[0] = uint8_t(((datacore >> 24) & 0xFF));
Wire.beginTransmission(eeprom_addr);
Wire.write(addr_h); //First Word Address
Wire.write(addr_l); //Second Word Address
for(uint8_t k = 0; k < 4; k++){ //MSB first!
Wire.write(data_out[k]);
}
if(Wire.endTransmission() != 0){ // End I2C transmission, 0 is OK, other number is error
error_flag = true;
error_code = 7;
}
}
}
//read rom
uint32_t read4bytes(uint8_t addr_h, uint8_t addr_l){
uint32_t retv = 0;
uint32_t temp;
uint8_t retries = 0;
uint8_t received_bytes;
while(chk_rom_busy() != 0 && retries < 5){
delay(10);
retries++;
}
if(retries == 5){
retv = 0;
error_flag = true;
error_code = 8;
}else{
Wire.beginTransmission(eeprom_addr);
Wire.write(addr_h); // Send memory address bits [15:8]
Wire.write(addr_l); // Send memory address bits [7:0]
Wire.endTransmission(0); // Send restart condition
Wire.requestFrom(eeprom_addr, 4);
received_bytes = Wire.available();
if(received_bytes == 4){
uint8_t k = 3;
for(uint8_t j = 0; j < 4; j++){
temp = Wire.read();
temp = (temp << (8*k));
retv += temp;
k--;
}
}else{
retv = 0;
error_flag = true;
error_code = 8;
}
}
return retv;
}
//printer functions
void clear_buf(){ //clear line buffer
byte tosend[] = {0x13, 0x43};
Serial.write(tosend, sizeof(tosend)/sizeof(tosend[0]));
}
void codepage_1250(){ //set code page to display special chars
byte tosend[] = {0x1B, 0x74, 0x03};
Serial.write(tosend, sizeof(tosend)/sizeof(tosend[0]));
}
void prnt_set_center(){ //set printer to print center aligned
byte tosend[] = {0x1B, 0x61, 0x01};
Serial.write(tosend, sizeof(tosend)/sizeof(tosend[0]));
}
void prnt_char_set(uint8_t mode){ //set height, width, underline. 000 is all off, 001 is double height, 010 is double width, 100 is underlined
uint8_t bin_out = 0;
if(mode == 0){ //normal small characters
bin_out = 0;
}else if(mode == 0b1){ //double height
bin_out = 0b10000;
}else if(mode == 0b10){ //double width
bin_out = 0b100000;
}else if(mode == 0b100){ //underline
bin_out = 0b10000000;
}else if(mode == 0b11){ //double height and width
bin_out = 0b110000;
}else if(mode == 0b101){ //double height with underline
bin_out = 0b10010000;
}else if(mode == 0b110){ //double width with underline
bin_out = 0b10100000;
}else if(mode == 0b111){ //double height and width with underline
bin_out = 0b10110000;
}
byte tosend[] = {0x1B, 0x21, bin_out}; // 0b00110000
Serial.write(tosend, sizeof(tosend)/sizeof(tosend[0]));
}
void prnt_lf(byte val){ //print and line feed given lines
byte tosend[] = {0x1B, 0x64, val};
Serial.write(tosend, sizeof(tosend)/sizeof(tosend[0]));
}
void prnt_lfcr(){ //lf cr
byte tosend[] = {0x0A, 0x0D};
Serial.write(tosend, sizeof(tosend)/sizeof(tosend[0]));
}
void prnt_soft_reset(){ //Soft reset
byte tosend[] = {0x11};
Serial.write(tosend, sizeof(tosend)/sizeof(tosend[0]));
}
void endcut_full(){ //full paper cut
byte tosend[] = {0x1B, 0x69};
Serial.write(tosend, sizeof(tosend)/sizeof(tosend[0]));
}
void endcut_partial(){ //partial paper cut
byte tosend[] = {0x1B, 0x6D};
Serial.write(tosend, sizeof(tosend)/sizeof(tosend[0]));
}
void err_blinky(int rate){
for (int i = 0; i < rate; i++){
digitalWrite(btn_print_LED, 1);
delay((rate*500)/((rate*2)-1));
digitalWrite(btn_print_LED, 0);
delay((rate*500)/((rate*2)-1));
}
delay(1700);
}
//set date-time
void settime(){
int curs = 0;
int vc = 0;
int q;
int validpos[5] = {3,6,9,12,15};
int ps_in_t_arr[5] = {6,5,4,2,1};
int min_vals[5] = {0,1,1,0,0};
int max_vals[5] = {99,12,31,23,59};
int t_arr_dec[5];
bool set_ok = false;
bool val_chgd;
for(q = 0; q < 5; q++){
t_arr_dec[q] = bcdToDec(time_arr[ps_in_t_arr[q]]);
}
sprintf(line0,"2%03d-%02d-%02d %02d:%02d", t_arr_dec[0], t_arr_dec[1], t_arr_dec[2], t_arr_dec[3], t_arr_dec[4]);
upd_lcd();
lcd.setCursor(validpos[curs], 0);
lcd.blink();
lcd2.setCursor(validpos[curs], 0);
lcd2.blink();
delay(1000);
while(set_ok == false){
if(digitalRead(btn_1) == LOW || digitalRead(btn_2) == LOW || digitalRead(btn_menu) == LOW){ //crude debounce
if(vc >= 2000){
vc = 200;
}
vc++;
if(curs == 4 && vc >= 300){
set_ok = true;
}
delay(5);
if(vc == 10 && t_arr_dec[curs] <= max_vals[curs] && t_arr_dec[curs] >= min_vals[curs]){
if(digitalRead(btn_1) == LOW){
if(t_arr_dec[curs] < max_vals[curs]){
t_arr_dec[curs]++;
val_chgd = 1;
}
}else if(digitalRead(btn_2) == LOW){
if(t_arr_dec[curs] > min_vals[curs]){
t_arr_dec[curs]--;
val_chgd = 1;
}
}else if(digitalRead(btn_menu) == LOW){
if(curs < 4){
curs++;
val_chgd = 1;
}
}
}
}else{
vc = 0;
}
if(val_chgd == 1){
val_chgd = 0;
sprintf(line0,"2%03d-%02d-%02d %02d:%02d", t_arr_dec[0], t_arr_dec[1], t_arr_dec[2], t_arr_dec[3], t_arr_dec[4]);
upd_lcd();
lcd.setCursor(validpos[curs],0);
lcd2.setCursor(validpos[curs],0);
}
}
//write values back to RTC
Wire.beginTransmission(rtc_addr);
Wire.write(0x01);
Wire.write(decToBcd(t_arr_dec[4]));
Wire.write(decToBcd(t_arr_dec[3]));
Wire.write(time_arr[3]); //day won't change - may be wrong?
Wire.write(decToBcd(t_arr_dec[2]));
Wire.write(decToBcd(t_arr_dec[1]));
Wire.write(decToBcd(t_arr_dec[0]));
if(Wire.endTransmission() != 0){
error_code = 10;
error_flag = true;
}
lcd.noBlink();
lcd2.noBlink();
}
//conversions
byte bcdToDec(byte val){
return((val/16*10) + (val%16));
}
byte decToBcd(byte val){
return((val/10*16) + (val%10));
}