
/*
                                _                     
  ___  _ __   ___ _ __    _ __ (_) ___ ___  _   _ ____
 / _ \| '_ \ / _ \ '_ \  | '_ \| |/ __/ _ \| | | |_  /
| (_) | |_) |  __/ | | | | |_) | | (_| (_) | |_| |/ / 
 \___/| .__/ \___|_| |_| | .__/|_|\___\___/ \__,_/___|
      |_|                |_|                          

This software is released under the GPL, Copyleft Samuel Lesueur @electrolab

DISCLAIMER : I am not a medicine professionnal, use at your one risks !
This code is probably not state of the art but it is written in a safe way and should 
allow you to build reliable devices to help during the COVID outbreak.
This .ino is designed to be used with the open picouz hardware that was specifically designed
to be easy to solder and only use common components in the maker world.

*/

//LCD HD44780 library
#include <LiquidCrystal.h>
// EEprom library
#include <EEPROM.h>

#define SOFTWARE_REVISION "V 0.8 WIP!" // 10 chars max

/* Hardware definitions */

/* Memory map */
//Here we store the injection parameters and the state of the device in order to restore them at boot time
#define MEMORY_LAST_STATE_MSB 0x00 //int (2 Bytes)
#define MEMORY_LAST_STATE_LSB 0x01 
#define MEMORY_SYRINGE_INDEX_MSB 0x02 //int (2 Bytes)
#define MEMORY_SYRINGE_INDEX_LSB 0x03 
#define MEMORY_INJECTION_SPEED_MSB 0x04 //int (2 Bytes)
#define MEMORY_INJECTION_SPEED_LSB 0x05
#define MEMORY_REMAINING_TIME_ALARM_MSB 0x06 //int (2 Bytes)
#define MEMORY_REMAINING_TIME_ALARM_LSB 0x07

/* Watchdog stuff */
// The hardware watchdog is basically monitoring the voltage on a capacitor. putting a  High level on PIN_WD_IO will increase this voltage,
// putting a low level will decrease it, switching the pin to input will let it float and drop slowly.
// We use PIN_WD_ADC to monitor the voltage of the capacitor with the arduino to keep it bwteen the tolerances of the hardware watchdog (between 2 and 4V)
// We use PIN_WD_FB to know if the watchdog has been triggered (and latched) by the hardware to display a message if the code is still running.

#define PIN_WD_FB A7
#define PIN_WD_IO 13
#define PIN_WD_ADC A0

#define WD_THRESHOLD_MIN 512
#define WD_THRESHOLD_MAX 640
int wd_count=0;
int wd_debounce=0;

/* Motor control */
// the motor control is done using a pololu (A4988 Stepper Motor Driver Carrier) or any step / dir compatible stepper driver circuit
// we chose the pololu as we know many makers use them and will have a few drivers to spare for this project.

#define PIN_MOTOR_ENABLE 8
#define PIN_MOTOR_STEP 9
#define PIN_MOTOR_DIR 10

#define MOTOR_STATE_BOOTING 0x01
#define MOTOR_STATE_READY 0x02
#define MOTOR_STATE_INJECTING 0x03
#define MOTOR_STATE_FREE_MOVE 0x04
#define MOTOR_STATE_BOLUS 0x05

#define MOTOR_STATE_WATCHDOG 0xFF

// Change this to chose the speed of the motor during manual movements through the LCD menus
#define CONFIG_MOTOR_FREE_MOVE_SPEED 1024

// Change this to chose the speed of the motor during bolus mode
#define CONFIG_MOTOR_BOLUS_SPEED 1024

//motor state machine state storage
int motor_state=0, motor_old_state=0;

//motor state machine messenger between UI and motor
int motor_requested_state=0;

/* UI */
// We are also using a very common LCD interface that should be able to talk with most alphanumerical displays
// To save a few pins on the arduino, the LCD is driven in 4 bits mode so W/R, LCD_IO3, LCD_IO2, LCD_IO1, LCD_IO0 should be tied to GND
#define PIN_LCD_RS 11
#define PIN_LCD_EN  12
#define PIN_LCD_IO4 4
#define PIN_LCD_IO5 5
#define PIN_LCD_IO6 6
#define PIN_LCD_IO7 7
#define PIN_BTN_PRV A2
#define PIN_BTN_NXT A3
#define PIN_BTN_OK A1

#define PIN_BUZZER A4
#define PIN_GREEN_LED A5

//States of the UI
#define UI_STATE_BOOTING 0x01
#define UI_STATE_READY 0x02
#define UI_STATE_CONFIRM_INJECTION 0x03
#define UI_STATE_RUNNING 0x04
#define UI_STATE_BOLUS 0x05
#define UI_STATE_CONFIRM_STOP 0x06
#define UI_STATE_END 0x07

#define UI_STATE_MAIN_MENU 0xF0
#define UI_STATE_MENU_MANUAL 0xF1
#define UI_STATE_MENU_SPEED 0xF2
#define UI_STATE_MENU_SYRINGE 0xF3
#define UI_STATE_MENU_ALARM_TIME 0xF4
#define UI_STATE_MENU_CALIBRATE 0xF5
#define UI_STATE_MENU_SELFTEST 0xF6
#define UI_STATE_MENU_ABOUT 0xF7

#define UI_STATE_MENU_CONFIRM_VALUE 0xFA
#define UI_STATE_MENU_CONFIRM_RESUME 0xFB

#define UI_STATE_WATCHDOG 0xFF

//Change this to control the selection characters that will be displayed around the selected option in the menus
#define UI_SELECT_CHAR_LEFT 0x7E
#define UI_SELECT_CHAR_RIGHT 0x7F

//UI state machine state storage
int ui_state=0, ui_old_state=0;

//buttons press counter (in 0.1s)
int ui_btn_prev=0, ui_btn_next=0, ui_btn_ok=0;

//misc variables available for each sate to use as temporary variables at its discretion
int ui_blink=0, ui_selection=0, ui_state_count=0;

//In running state we have a remaining time alarm (stored in eeprom)
int ui_remaining_time_alarm = 0; //in minutes
int ui_injection_time = 7200; //in seconds
int ui_elapsed_time = 0; //in seconds
int ui_current_injected_volume=0; //in mL
int ui_current_bolus_injected_volume=0; //in mL

//misc variables available for each state to use as temporaray variables at its discretion
int h=0,m=0,s=0;

//Main configuration Menu
#define UI_MAIN_MENU_ITEMS 7
const char ui_menu_names[UI_MAIN_MENU_ITEMS +1][21] = {"Mouvement chariot", "Posologie", "Taille seringue", "Alarme fin", "Calibration", "Autotest", "A propos", "-------retour-------"};

//should be here so it can define a global lcd object
LiquidCrystal lcd(PIN_LCD_RS,PIN_LCD_EN, PIN_LCD_IO4, PIN_LCD_IO5, PIN_LCD_IO6, PIN_LCD_IO7);

#define UI_WELCOME_STRING_1 "    Open syringe !"
#define UI_WELCOME_STRING_2 "    by ELECTROLAB"

#define UI_DISCLAIMER_STRING_1 "  === ATTENTION ==="
#define UI_DISCLAIMER_STRING_2 "Produit non certifie"
#define UI_DISCLAIMER_STRING_3 "Utilisation R&D et"
#define UI_DISCLAIMER_STRING_4 "crises sanitaires"

//nice syringe icon :)
unsigned char Syringe_char[] = {
  B00100,
  B01110,
  B01010,
  B01010,
  B01010,
  B01110,
  B00100,
  B01110
};

/* Process Definition */
#define CONFIG_MAX_INJECTION_SPEED 500 // ml/h

#define CONFIG_MAX_REMAINING_TIME_ALARM  120//in minutes

//process variables (stored in eeprom)
int current_syringe=0, current_injection_speed=0;

//Syringe list : syringe name, syringe step per ml ration and syringe start position
#define UI_MAX_SYRINGE 4
const int ui_syringe_list[UI_MAX_SYRINGE+1] = {10,20,50,100,150};
const int config_syringe_step_per_ml[UI_MAX_SYRINGE+1] = {1115,558,223,111,74};
const int config_syringe_start_pos [UI_MAX_SYRINGE+1] = {0,0,0,0,0};

//this is where we convert a ml per hour target into a timer value for tmr 1
unsigned int ml_per_h_to_timer_value(int desired_ml_per_h, int syringe_step_per_ml){
  // value for 1ms : 7.8125 = (8*10^6) / (1000*1024) 
  // value for 1ms : 31.2500 = (8*10^6) / (1000*256) 
  return ((31.2500*3600000UL)/((long)desired_ml_per_h*(long)syringe_step_per_ml));  
}

void setup() {
  Serial.begin(9600);  

  //output pins setup
  pinMode(PIN_WD_IO, OUTPUT);  
  pinMode(PIN_WD_FB, INPUT);

  pinMode(PIN_MOTOR_ENABLE, OUTPUT);
  pinMode(PIN_MOTOR_STEP, OUTPUT);
  pinMode(PIN_MOTOR_DIR, OUTPUT);

  digitalWrite(PIN_MOTOR_ENABLE, LOW); 
  digitalWrite(PIN_MOTOR_STEP, LOW);
  digitalWrite(PIN_MOTOR_DIR, LOW);
  
  pinMode(PIN_BUZZER, OUTPUT);
  pinMode(PIN_GREEN_LED, OUTPUT);
  
  digitalWrite(PIN_GREEN_LED, HIGH);
  digitalWrite(PIN_BUZZER, HIGH); //inverted
  
  //this sets the requested output pins so we dont have to
  lcd.begin(20, 4);
  
  //storing the custom chars in memory
  lcd.createChar(0, Syringe_char);
  
  //reading last values from eeprom
  current_syringe = (EEPROM.read(MEMORY_SYRINGE_INDEX_MSB) << 8) |  EEPROM.read(MEMORY_SYRINGE_INDEX_LSB);
  current_injection_speed = (EEPROM.read(MEMORY_INJECTION_SPEED_MSB) << 8) |  EEPROM.read(MEMORY_INJECTION_SPEED_LSB);
  ui_remaining_time_alarm = (EEPROM.read(MEMORY_REMAINING_TIME_ALARM_MSB) << 8) |  EEPROM.read(MEMORY_REMAINING_TIME_ALARM_LSB);
  
  //verifying the read values 
  if((current_syringe > UI_MAX_SYRINGE) || (current_syringe < 0) ) current_syringe = 0;
  if((current_injection_speed > CONFIG_MAX_INJECTION_SPEED) || (current_injection_speed < 0) ) current_injection_speed = 0;
  if((ui_remaining_time_alarm > CONFIG_MAX_REMAINING_TIME_ALARM) || (ui_remaining_time_alarm < 0) ) ui_remaining_time_alarm = 0;
  
  //setting the motor state machine in booting state
  motor_state = MOTOR_STATE_BOOTING;
  motor_old_state = MOTOR_STATE_BOOTING;
  
  //setting the UI state machine in booting state
  ui_state = UI_STATE_BOOTING;
  ui_old_state = UI_STATE_BOOTING;

}

void loop() {
  
  //watchdog management and debounce
  pinMode(PIN_WD_FB, INPUT);
  if(analogRead(PIN_WD_FB) < 512){
    wd_debounce++;
  }
  else{
    wd_debounce=0;
  }

  //buttons management and debounce
  if(!digitalRead(PIN_BTN_PRV)){
    ui_btn_prev++;
  }
  else{
    ui_btn_prev = 0;
  }
   if(!digitalRead(PIN_BTN_NXT)){
    ui_btn_next++;
  }
  else{
    ui_btn_next = 0;
  }
  if(!digitalRead(PIN_BTN_OK)){
    ui_btn_ok++;
  }
  else{
    ui_btn_ok = 0;
  }
 
   
 /* UI state machine */
 //All ui related calculations are done in this section

  switch(ui_state) {
    
    //This state is the first state the UI starts in. It displays a welcome message and jump to 
    //ready or confirm resume state depending on the MEMORY_LAST_STATE value
    //user has no possible input
    //2 Secs timeout
    case UI_STATE_BOOTING:
      lcd.clear();
      lcd.print(UI_WELCOME_STRING_1);
      lcd.setCursor(0, 2);
      lcd.print(UI_WELCOME_STRING_2);
      lcd.setCursor(0, 3);
      lcd.print("Version : ");
      lcd.print(SOFTWARE_REVISION);

      //this is the only blocking wait in the state machine, it is just here to prevent the watchdog message to kick in at boot time before
      //we can read the software version
      while(millis()/1000 <= 2);
      
      lcd.clear();
      lcd.print(UI_DISCLAIMER_STRING_1);
      lcd.setCursor(0, 1);
      lcd.print(UI_DISCLAIMER_STRING_2);
      lcd.setCursor(0, 2);
      lcd.print(UI_DISCLAIMER_STRING_3);
      lcd.setCursor(0, 3);
      lcd.print(UI_DISCLAIMER_STRING_4);

      //this is the only blocking wait in the state machine, it is just here to prevent the watchdog message to kick in at boot time before
      //we can read the disclaimer
      while(millis()/1000 <= 6);



      //trying to resume injection if needed
      if((EEPROM.read(MEMORY_LAST_STATE_MSB) << 8) |  EEPROM.read(MEMORY_LAST_STATE_LSB) == MOTOR_STATE_INJECTING){
        ui_state = UI_STATE_MENU_CONFIRM_RESUME;
      }
      else{
        ui_state = UI_STATE_READY;
      }
    break;
    
    //This state is the waiting state. It allows the user to start injection or entering the main menu 
    case UI_STATE_READY:
    
       if(ui_old_state != UI_STATE_READY){
        //send a stop message to the motor state machine
        motor_requested_state = MOTOR_STATE_READY;
        lcd.clear();
        lcd.print(ui_syringe_list[current_syringe]);
        lcd.setCursor(3, 0);
        lcd.print("mL =PRET= ");
        lcd.setCursor(13, 0);
        lcd.print(current_injection_speed);
        lcd.setCursor(16, 0);
        lcd.print("mL/h");
        lcd.setCursor(1, 3);
        lcd.print("menu");
        lcd.setCursor(11, 3);
        lcd.print("injecter");
        ui_selection=0;
        ui_state_count=0;
        ui_old_state = ui_state;
      }
      
      //tasteful animation in idle mode
      lcd.setCursor(6, 0);
      if(ui_blink){
        lcd.print("-");
      }
      else{
        lcd.print("|");
      }
      lcd.setCursor(11, 0);
      if(ui_blink){
        lcd.print("-");
      }
      else{
        lcd.print("|");
      }
      
      //display item selection
      if(!ui_selection){
        lcd.setCursor(0, 3);
        lcd.write(UI_SELECT_CHAR_LEFT);
        lcd.setCursor(5, 3);
        lcd.write(UI_SELECT_CHAR_RIGHT);
        lcd.setCursor(10, 3);
        lcd.print(" ");
        lcd.setCursor(19, 3);
        lcd.print(" "); 
      }
      else{
        lcd.setCursor(0, 3);
        lcd.print(" ");
        lcd.setCursor(5, 3);
        lcd.print(" ");
        lcd.setCursor(10, 3);
        lcd.write(UI_SELECT_CHAR_LEFT);
        lcd.setCursor(19, 3);
        lcd.write(UI_SELECT_CHAR_RIGHT);
      }
      
      //handle the ui button press
      if((ui_btn_prev == 2)|| (ui_btn_next == 2)){
        
        ui_selection = !ui_selection;
      }
      
      if(ui_btn_ok == 2){
        
        if(ui_selection){
          ui_state = UI_STATE_CONFIRM_INJECTION;
        }
        else{
          ui_state = UI_STATE_MAIN_MENU;
        }
      }
      
    break;

    //this state is a simple yes/no question to confirm the start of the injection
    //depending on the user input it will switch the UI in the RUNNING state or back to ready.
    case UI_STATE_CONFIRM_INJECTION:
    
       if(ui_old_state != UI_STATE_CONFIRM_INJECTION){
        lcd.clear();
        lcd.print("CONFIRMEZ INJECTION?");
        lcd.setCursor(0, 1);
        lcd.print("Seringue  : ");
        lcd.print(ui_syringe_list[current_syringe]);
        lcd.print("mL");
        lcd.setCursor(0, 2);
        lcd.print("Posologie : ");
        lcd.print(current_injection_speed);
        lcd.print("mL/h");
        lcd.setCursor(1, 3);
        lcd.print("retour");
        lcd.setCursor(11, 3);
        lcd.print("injecter");
        ui_selection=0;
        ui_state_count=0;
        ui_old_state = ui_state;
      }
      
      //display item selection
      if(!ui_selection){
        lcd.setCursor(0, 3);
        lcd.write(UI_SELECT_CHAR_LEFT);
        lcd.setCursor(7, 3);
        lcd.write(UI_SELECT_CHAR_RIGHT);
        lcd.setCursor(10, 3);
        lcd.print(" ");
        lcd.setCursor(19, 3);
        lcd.print(" "); 
      }
      else{
        lcd.setCursor(0, 3);
        lcd.print(" ");
        lcd.setCursor(7, 3);
        lcd.print(" ");
        lcd.setCursor(10, 3);
        lcd.write(UI_SELECT_CHAR_LEFT);
        lcd.setCursor(19, 3);
        lcd.write(UI_SELECT_CHAR_RIGHT);
      }
      
      //handle the ui button press
      if((ui_btn_prev == 2) || (ui_btn_next == 2)){
        
        ui_selection = !ui_selection;

      }
      
      if(ui_btn_ok == 2){
        
        if(ui_selection){
          ui_state = UI_STATE_RUNNING;
        }
        else{
          ui_state = UI_STATE_READY;
        }
      }

    // 10 seconds timout
    if(ui_state_count > 100){
      ui_state = UI_STATE_READY;
    }

    break;

    //this state is where the injection happens. It triggers the Motor sate machine and diplays the progress.
    //the user can stop the injection with the stop menu or initiate a bolus mode. this would send the UI
    //in the UI_STATE_CONFIRM_STOP or UI_STATE_BOLUS
    case UI_STATE_RUNNING:
    
       if(ui_old_state != UI_STATE_RUNNING){
        //send a start message to the motor state machine
        motor_requested_state = MOTOR_STATE_INJECTING;
        lcd.clear();
        lcd.print("  Injection");
        lcd.setCursor(12, 0);
        lcd.print(current_injection_speed);
        lcd.setCursor(14, 0);
        lcd.print("mL/h");
        lcd.setCursor(0, 1);
        lcd.print("Volume     :  0   mL");
        lcd.setCursor(0, 2);
        lcd.print("Temps restant:   :  ");
        lcd.setCursor(1, 3);
        lcd.print("arret");
        lcd.setCursor(14, 3);
        lcd.print("bolus");
        ui_selection=0;
        ui_state_count=0;
        ui_old_state = ui_state;
      }
      else{
        //if the motor state machine is not injecting go to stop mode
        if(motor_state != MOTOR_STATE_INJECTING){
          ui_state = UI_STATE_END;
        }
      }
      
      //syringe animation     
      if(ui_blink){
        lcd.setCursor(0, 0);
        lcd.write((byte)0x00);
        lcd.setCursor(19, 0);
        lcd.write((byte)0x00);
      }
      else{
        lcd.setCursor(0, 0);
        lcd.print(" ");
        lcd.setCursor(19, 0);
        lcd.print(" ");
      }
      
      //Refresh the values every seconds 
      if(!(ui_state_count %10)){
        //display the injected volume
        lcd.setCursor(14, 1);
        lcd.print("   ");
        lcd.setCursor(14, 1);
        lcd.print(ui_current_injected_volume + ui_current_bolus_injected_volume);
      
        //display the remaining time
        lcd.setCursor(14, 2);
        lcd.print("  :  ");
        lcd.setCursor(14, 2);
        s=ui_injection_time - ui_elapsed_time;
        h=(s / 3600);
        m=(s - (h*3600))/60;
        lcd.setCursor(14, 2);
        lcd.print(h);
        lcd.setCursor(17, 2);
        lcd.print(m);
      }
      
      //display item selection
      if(!ui_selection){
        lcd.setCursor(0, 3);
        lcd.write(UI_SELECT_CHAR_LEFT);
        lcd.setCursor(6, 3);
        lcd.write(UI_SELECT_CHAR_RIGHT);
        lcd.setCursor(13, 3);
        lcd.print(" ");
        lcd.setCursor(19, 3);
        lcd.print(" "); 
      }
      else{
        lcd.setCursor(0, 3);
        lcd.print(" ");
        lcd.setCursor(6, 3);
        lcd.print(" ");
        lcd.setCursor(13, 3);
        lcd.write(UI_SELECT_CHAR_LEFT);
        lcd.setCursor(19, 3);
        lcd.write(UI_SELECT_CHAR_RIGHT);
      }
      
      //handle the ui button press
      if((ui_btn_prev == 2) || (ui_btn_next == 2)){
        
        ui_selection = !ui_selection;
      }

      if(ui_btn_ok == 2){
        
        if(ui_selection){
          ui_state = UI_STATE_BOLUS;
        }
        else{
          ui_state = UI_STATE_CONFIRM_STOP;
        }
      }
      
      //just for the video :]
      if(ui_state_count !=0){
        //every second
        if(!(ui_state_count %10)){
          ui_elapsed_time++;
        }
         //every 400 seconds
        if(!(ui_state_count %400)){
          ui_current_injected_volume++;
        }
      }
      
    break;

    //this state is a specific mode of injection. The user forces a volume of medicin into the patient. It triggers the Motor sate machine at a specific speed as long as the user presses the button.
    case UI_STATE_BOLUS:
      if(ui_old_state != UI_STATE_BOLUS){
      //send a bolus message to the motor state machine
      motor_requested_state = MOTOR_STATE_BOLUS;
      
      lcd.clear();
      lcd.print("     Mode BOLUS");
      lcd.setCursor(0, 1);
      lcd.print("Volume    :   000 mL");
      lcd.setCursor(0, 3);
      lcd.print("Ok pour revenir");
      ui_selection=0;
      ui_state_count=0;
      ui_old_state = ui_state;

    }
       
      //display the bolus value
      
      lcd.setCursor(14, 1);
      lcd.print("   ");
      lcd.setCursor(14, 1);
      lcd.print(ui_current_bolus_injected_volume);
          
      //handle the ui button press
      if(ui_btn_next > 2){
        //for the video
         if(!(ui_btn_next %10)) ui_current_bolus_injected_volume++;
      }

      if(ui_btn_ok == 2){
          ui_state = UI_STATE_RUNNING;
      }
      
    // 10 seconds timout
    if(ui_state_count > 100){
      ui_state = UI_STATE_RUNNING;
    }


    break;

    
    //simple yes/no question stop ton confirm injection abort. Will send the ui
    //in the UI_STATE_END or back to UI_STATE_RUNNING
    case UI_STATE_CONFIRM_STOP:
    
       if(ui_old_state != UI_STATE_CONFIRM_STOP){
        lcd.clear();
        lcd.print("  CONFIRMEZ ARRET?");
        lcd.setCursor(1, 3);
        lcd.print("retour");
        lcd.setCursor(13, 3);
        lcd.print("arret");
        ui_selection=0;
        ui_state_count=0;
        ui_old_state = ui_state;
      }
      
      //display item selection
      if(!ui_selection){
        lcd.setCursor(0, 3);
        lcd.write(UI_SELECT_CHAR_LEFT);
        lcd.setCursor(7, 3);
        lcd.write(UI_SELECT_CHAR_RIGHT);
        lcd.setCursor(12, 3);
        lcd.print(" ");
        lcd.setCursor(19, 3);
        lcd.print(" "); 
      }
      else{
        lcd.setCursor(0, 3);
        lcd.print(" ");
        lcd.setCursor(7, 3);
        lcd.print(" ");
        lcd.setCursor(12, 3);
        lcd.write(UI_SELECT_CHAR_LEFT);
        lcd.setCursor(19, 3);
        lcd.write(UI_SELECT_CHAR_RIGHT);       
        
      }

      //handle the ui button press
      if((ui_btn_prev == 2) || (ui_btn_next == 2)){
        
        ui_selection = !ui_selection;
      }
      
      if(ui_btn_ok == 2){
        
        if(ui_selection){
          ui_state = UI_STATE_END;
        }
        else{
          ui_state = UI_STATE_RUNNING;
        }
      }

     // 5 seconds timout
    if(ui_state_count > 50){
      ui_state = UI_STATE_RUNNING;
    }

    break;

    //this state is the end of the injection. It can be triggered by user input or by
    //the motor state machine if it reaches the end of the syringe
    //If user acknoledge the end of injection by pressing the OK button, th ui will go back to the READY state
    case UI_STATE_END:
    
       if(ui_old_state != UI_STATE_END){
        //send a start message to the motor state machine
        motor_requested_state = MOTOR_STATE_READY;
        lcd.clear();
        lcd.print("  Fin d'injection !");
        lcd.setCursor(0, 2);
        lcd.print(ui_current_injected_volume + ui_current_bolus_injected_volume);
        lcd.setCursor(3, 2);
        lcd.print("mL en   :  :");
        lcd.setCursor(9, 2);
        h = (ui_elapsed_time / 3600);
        m = (ui_elapsed_time - (h*3600))/60;
        s = ui_elapsed_time - (h*3600) - (m*60);
        lcd.print(h);
        lcd.setCursor(12, 2);
        lcd.print(m);
        lcd.setCursor(15, 2);
        lcd.print(s);        
        lcd.setCursor(0, 3);
        lcd.print("Dont");
        lcd.setCursor(5, 3);
        lcd.print(ui_current_bolus_injected_volume);
        lcd.setCursor(8, 3);
        lcd.print(" mL en bolus");
        digitalWrite(PIN_BUZZER, LOW);
        ui_selection=0;
        ui_state_count=0;
        ui_old_state = ui_state;
      }

      //handle the ui button press   
      if(ui_btn_ok == 2){
        digitalWrite(PIN_BUZZER, HIGH);      
        ui_state = UI_STATE_READY;
      }
      
    break;

    //this step is simply displaying the main menu and allow the user to select
    //the next UI step (within the step menus of course)
    case UI_STATE_MAIN_MENU:
    
       if(ui_old_state != UI_STATE_MAIN_MENU){
        lcd.clear();
        lcd.print("   MENU PRINCIPAL");
        lcd.setCursor(0, 2);
        lcd.print(ui_menu_names[0]);
        ui_selection=0;
        ui_state_count=0;
        ui_old_state = ui_state;
      }
      
      //handle the ui button press
      if((ui_btn_prev == 2) && (ui_selection > 0)){
        ui_selection --;
      }
      
      if((ui_btn_next == 2) && (ui_selection < UI_MAIN_MENU_ITEMS)){
        ui_selection ++;
      }

      if((ui_btn_prev == 2) || (ui_btn_next == 2)){
        lcd.setCursor(0, 2);
        lcd.print("                    ");
        lcd.setCursor(0, 2);
        
        lcd.print(ui_menu_names[ui_selection]);
        ui_state_count=0;
      }

      if(ui_btn_ok == 2){
         if(ui_selection == UI_MAIN_MENU_ITEMS){
           ui_state = UI_STATE_READY;
         }
         else{
           ui_state = UI_STATE_MAIN_MENU + 1 + ui_selection;
         }
      }
      
      // 60 seconds timout
      if(ui_state_count > 600){
        ui_state = UI_STATE_READY;
      }

    break;

    //this menu state allows the user to move the syringe manually.
    //it swithes the motor state machine in a specific mode and wait for the user
    //to end the process using the ok button
    case UI_STATE_MENU_MANUAL:
       if(ui_old_state != UI_STATE_MENU_MANUAL){
        //send a start message to the motor state machine
        motor_requested_state = MOTOR_STATE_FREE_MOVE;
        lcd.clear();
        lcd.print("Deplacement chariot");        
        lcd.setCursor(0, 3);
        lcd.print("btn ok pour quitter");
        ui_selection=0;
        ui_state_count=0;
        ui_old_state = ui_state;
      }
      
      if(ui_btn_ok == 2){
        ui_state = UI_STATE_MAIN_MENU;
        motor_requested_state = MOTOR_STATE_READY;
      }
      
      // 60 seconds timout
      if(ui_state_count > 600){
        ui_state = UI_STATE_READY;
        motor_requested_state = MOTOR_STATE_READY;
      }
      
    break;
    
    //this menu allows the user to set the injection speed in ml/h
    case UI_STATE_MENU_SPEED:
       if(ui_old_state != UI_STATE_MENU_SPEED){
        lcd.clear();
        lcd.print("Vitesse injection");
        lcd.setCursor(0, 2);
        lcd.print(current_injection_speed);
        lcd.setCursor(4, 2);
        lcd.print("mL/h");
        ui_selection=0;
        ui_state_count=0;
        ui_old_state = ui_state;
      }

      
      //handle the ui buttons press
      if((ui_btn_prev == 2) || ((ui_btn_prev > 0) && !(ui_btn_prev % 2))){

        if((current_injection_speed > 10) && !(current_injection_speed % 10) && (ui_btn_prev > 50)){
          current_injection_speed -= 10;
        }
        else if(current_injection_speed > 0){
          current_injection_speed --;
        }
        
        lcd.setCursor(0, 2);
        lcd.print("   ");
        lcd.setCursor(0, 2);
        lcd.print(current_injection_speed);
        ui_state_count=0;
      }
      
      if((ui_btn_next == 2) || ((ui_btn_next > 0) && !(ui_btn_next % 2))){
        
        if((current_injection_speed >= 10) && !(current_injection_speed % 10) && (ui_btn_next > 50) && (current_injection_speed + 10 < CONFIG_MAX_INJECTION_SPEED)){
          current_injection_speed += 10;
        }
        else if(current_injection_speed < CONFIG_MAX_INJECTION_SPEED){
          current_injection_speed ++;
        }
        lcd.setCursor(0, 2);
        lcd.print("   ");
        lcd.setCursor(0, 2);
        lcd.print(current_injection_speed);
        ui_state_count=0;
      }

      if(ui_btn_ok == 2){
        ui_state = UI_STATE_MENU_CONFIRM_VALUE;
      }
    
    break;
    
    //this menu allows the user to select a syringe from the list
    case UI_STATE_MENU_SYRINGE:
        if(ui_old_state != UI_STATE_MENU_SYRINGE){
        lcd.clear();
        lcd.print("Taille seringue");
        lcd.setCursor(0, 2);
        lcd.print(ui_syringe_list[0]);
        lcd.setCursor(4, 2);
        lcd.print("mL");
        ui_selection=0;
        ui_state_count=0;
        ui_old_state = ui_state;
      }
      
      //handle the ui buttons press
      if((ui_btn_prev == 2) && (ui_selection > 0)){
        ui_selection --;
      }
      
      if((ui_btn_next == 2) && (ui_selection < UI_MAX_SYRINGE)){
        ui_selection ++;
      }

      if((ui_btn_prev == 2) || (ui_btn_next == 2)){
        lcd.setCursor(0, 2);
        lcd.print("   ");
        lcd.setCursor(0, 2);
        lcd.print(ui_syringe_list[ui_selection]);
      }
      
       if(ui_btn_ok == 2){
        current_syringe = ui_selection;
        ui_state = UI_STATE_MENU_CONFIRM_VALUE;
      }
  
    break;
    
    //this menu allows the user to select an alarm time
    case UI_STATE_MENU_ALARM_TIME:
       if(ui_old_state != UI_STATE_MENU_ALARM_TIME){
        lcd.clear();
        lcd.print("Sonnerie fin inject");
        lcd.setCursor(0, 2);
        lcd.print(ui_remaining_time_alarm);
        lcd.setCursor(4, 2);
        lcd.print("min");
        ui_selection=0;
        ui_state_count=0;
        ui_old_state = ui_state;
      }

      
      //handle the ui buttons press
      if((ui_btn_prev == 2) || ((ui_btn_prev > 0) && !(ui_btn_prev % 2))){

        if((ui_remaining_time_alarm > 10) && !(ui_remaining_time_alarm % 10) && (ui_btn_prev > 50)){
          ui_remaining_time_alarm -= 10;
        }
        else if(ui_remaining_time_alarm > 0){
          ui_remaining_time_alarm --;
        }
        
        lcd.setCursor(0, 2);
        lcd.print("   ");
        lcd.setCursor(0, 2);
        lcd.print(ui_remaining_time_alarm);
        ui_state_count=0;
      }
      
      if((ui_btn_next == 2) || ((ui_btn_next > 0) && !(ui_btn_next % 2))){
        
        if((ui_remaining_time_alarm >= 10) && !(ui_remaining_time_alarm % 10) && (ui_btn_next > 50) && (ui_remaining_time_alarm + 10 < CONFIG_MAX_REMAINING_TIME_ALARM)){
          ui_remaining_time_alarm += 10;
        }
        else if(ui_remaining_time_alarm < CONFIG_MAX_REMAINING_TIME_ALARM){
          ui_remaining_time_alarm ++;
        }
        lcd.setCursor(0, 2);
        lcd.print("   ");
        lcd.setCursor(0, 2);
        lcd.print(ui_remaining_time_alarm);
        ui_state_count=0;
      }

      if(ui_btn_ok == 2){
        ui_state = UI_STATE_MENU_CONFIRM_VALUE;
      }
    
    break;
  
    //this state is called after every menu that can change an important value.
    //it stores the values in the EEPROM and display a confirmation message
    case UI_STATE_MENU_CONFIRM_VALUE:
      if(ui_old_state != UI_STATE_MENU_CONFIRM_VALUE){
        //storing configuration values in eeprom
        EEPROM.write(MEMORY_SYRINGE_INDEX_MSB, current_syringe >> 8);
        EEPROM.write(MEMORY_SYRINGE_INDEX_LSB, current_syringe & 0x00FF);
        EEPROM.write(MEMORY_INJECTION_SPEED_MSB, current_injection_speed >> 8);
        EEPROM.write(MEMORY_INJECTION_SPEED_LSB, current_injection_speed & 0x00FF);
        EEPROM.write(MEMORY_REMAINING_TIME_ALARM_MSB,  ui_remaining_time_alarm >> 8);
        EEPROM.write(MEMORY_REMAINING_TIME_ALARM_LSB, ui_remaining_time_alarm & 0x00FF);
        
        lcd.setCursor(0, 0);
        lcd.print("VALEUR ENREGISTREE !");
        ui_selection=0;
        ui_state_count=0;
        ui_old_state = ui_state;
      } 
      
      // 2 seconds timout
      if(ui_state_count > 20){
        ui_state = UI_STATE_MAIN_MENU;
      }
  
    break;

    //this state is called if an aborted injection can be resumed
    //it provides the user with a yes/no question and waits indefinetly for the answer  
    case UI_STATE_MENU_CONFIRM_RESUME:
      if(ui_old_state != UI_STATE_MENU_CONFIRM_RESUME){
        lcd.clear();
        lcd.setCursor(0, 0);
        lcd.print("REPRENDRE INJECTION?");
        lcd.setCursor(1, 3);
        lcd.print("oui");
        lcd.setCursor(16, 3);
        lcd.print("non");
        lcd.setCursor(7, 1);
        lcd.print(current_injection_speed);
        lcd.setCursor(10, 1);
        lcd.print("mL/h");
        ui_selection=0;
        ui_state_count=0;
        ui_old_state = ui_state;
      } 
      
      //display item selection
      if(!ui_selection){
        lcd.setCursor(0, 3);
        lcd.write(UI_SELECT_CHAR_LEFT);
        lcd.setCursor(4, 3);
        lcd.write(UI_SELECT_CHAR_RIGHT);
        lcd.setCursor(15, 3);
        lcd.print(" ");
        lcd.setCursor(19, 3);
        lcd.print(" "); 
      }
      else{
        lcd.setCursor(0, 3);
        lcd.print(" ");
        lcd.setCursor(4, 3);
        lcd.print(" ");
        lcd.setCursor(15, 3);
        lcd.write(UI_SELECT_CHAR_LEFT);
        lcd.setCursor(19, 3);
        lcd.write(UI_SELECT_CHAR_RIGHT);       
        
      }

      //handle the ui buttons press
      if((ui_btn_prev == 2) || (ui_btn_next == 2)){
        
        ui_selection = !ui_selection;
      }
      
      if(ui_btn_ok == 2){
        
        if(ui_selection){
          //storing the status to disable recover 
          EEPROM.write(MEMORY_LAST_STATE_MSB, MOTOR_STATE_READY >> 8);
          EEPROM.write(MEMORY_LAST_STATE_LSB, MOTOR_STATE_READY & 0x00FF);

          ui_state = UI_STATE_END;
        }
        else{
          ui_state = UI_STATE_RUNNING;
        }
      }

  
    break;

    
    case UI_STATE_MENU_CALIBRATE:
    break;
    
    case UI_STATE_MENU_SELFTEST:
    break;

    //this menu simply displays the welcome string and software version for two seconds
    //before returning to the MAIN_MENU state
    case UI_STATE_MENU_ABOUT:
      if(ui_old_state != UI_STATE_MENU_ABOUT){
        lcd.clear();
        lcd.print(UI_WELCOME_STRING_1);
        lcd.setCursor(0, 1);
        lcd.print("Version : ");
        lcd.print(SOFTWARE_REVISION);
        ui_selection=0;
        ui_state_count=0;
        ui_old_state = ui_state;
      } 
      
      // 2 seconds timout
      if(ui_state_count > 20){
        ui_state = UI_STATE_MAIN_MENU;
      }
  
    break;

    //this state is called if the hardware watchdog triggers.
    //users cant do anyting until the watchdog is reset.
    case UI_STATE_WATCHDOG:
      if(ui_old_state != UI_STATE_WATCHDOG){
        //we clear the motor request flag so it wont restart by itself
        motor_requested_state == MOTOR_STATE_READY;
        lcd.clear();
        lcd.print("  === ATTENTION === ");
        lcd.setCursor(0, 1);
        lcd.print("! SECURITE ACTIVE !");
        lcd.setCursor(0, 3);
        lcd.print("Appuyez sur reset");
        ui_state_count=0;
        ui_old_state = ui_state;
      }
      if(ui_blink){
        lcd.setCursor(0, 1);
        lcd.print("                    ");
      }
      else{
        lcd.setCursor(0, 1);
        lcd.print("! SECURITE ACTIVE !");
      }
      
      if(wd_debounce == 0){
        if((EEPROM.read(MEMORY_LAST_STATE_MSB) << 8) |  EEPROM.read(MEMORY_LAST_STATE_LSB) == MOTOR_STATE_INJECTING){
          ui_state = UI_STATE_MENU_CONFIRM_RESUME;
        }
        else{
          ui_state = UI_STATE_READY;
        }
      }
      
    break;
    
    //should never happen...
    default:
        lcd.clear();
        lcd.print("!! ETAT INCONU !!");
        lcd.setCursor(0, 1);
        lcd.print(ui_state);
        //suicide
        while(1);

    break;
  }
  
  //if the watchdog is triggered we override the ui state
  if(wd_debounce > 2){
    ui_state = UI_STATE_WATCHDOG;
  }

  //simple counter that allows states to keep track of their active time
  ui_state_count++;

  //every second
  if(!(ui_state_count %10)){
      ui_blink = !ui_blink;
      if(ui_blink){
        digitalWrite(PIN_GREEN_LED, HIGH);
      }
      else if (motor_requested_state != MOTOR_STATE_INJECTING){
        digitalWrite(PIN_GREEN_LED, LOW);
      }
  }
  
  
  /* Motor state machine */
  //this state machine is where the real magic happens.
  //it uses the timer 1 to generate pulses depending on the needs.
  switch(motor_state){
    
    //this is the first state after boot. We make sur the timer is off and move on to the READY state
    case MOTOR_STATE_BOOTING:
      // initialize timer1 
      TCCR1A = 0;
      TCCR1B = 0;
      
      TCNT1= 0;
      OCR1A = 0;
      
      TCCR1B |= (1 << CS11);    // /8 prescaler on timer 1 clock
      //stop the motor (to be sure)
      digitalWrite(PIN_MOTOR_ENABLE, LOW); 
      digitalWrite(PIN_MOTOR_DIR, LOW);


      motor_state = MOTOR_STATE_READY;
    break;

    //this is the idle state, nothing much happening here, we wait for the 
    //motor_requested_state to switch the motor to the INJECTING or FREE_MOVE state
    case MOTOR_STATE_READY:
      if(motor_old_state != MOTOR_STATE_READY){
        //stop the motor
        digitalWrite(PIN_MOTOR_ENABLE, LOW); 
        digitalWrite(PIN_MOTOR_DIR, LOW);

        //shut down the timer
        TCCR1A = 0;
        TCCR1B = 0;
        
        //if we were injecting, we store the state again to disable resume
        if(motor_old_state == MOTOR_STATE_INJECTING){
          //storing the status to recover 
          EEPROM.write(MEMORY_LAST_STATE_MSB, motor_state >> 8);
          EEPROM.write(MEMORY_LAST_STATE_LSB, motor_state & 0x00FF);
        }
        
        motor_old_state = motor_state;
      }

      //handle requests from the UI
      if(motor_requested_state == MOTOR_STATE_INJECTING){
        //quick sanity checks before allowing motor movement
        if(current_injection_speed==0) break;
        //then go for it !
        motor_state = MOTOR_STATE_INJECTING;
      }
      else if(motor_requested_state == MOTOR_STATE_FREE_MOVE){
        motor_state = MOTOR_STATE_FREE_MOVE;
      }
    break;

    //In this state we generate pulses to slowly empty the syringe according to the configuration
    case MOTOR_STATE_INJECTING:
      if(motor_old_state != MOTOR_STATE_INJECTING){

        //start the motor
        digitalWrite(PIN_MOTOR_ENABLE, HIGH); 
        digitalWrite(PIN_MOTOR_DIR, LOW);

        //make sure the clock is still set to a slow value
        TCCR1B |= (1 << CS11);    // /8 prescaler on timer 1 clock

        //start the timer using the calculated value
        // value for 1ms : 7.8125 = (8*10^6) / (1000*1024) 
        OCR1A = ml_per_h_to_timer_value(current_injection_speed, config_syringe_step_per_ml[current_syringe]);

        TCCR1A |= (1<< COM1A0); //toggle Oc1a on compare,
        TCCR1B |= (1<< WGM12); //CTC mode (clr TCNT1 on compare);
        
        //storing the status to recover 
        EEPROM.write(MEMORY_LAST_STATE_MSB, motor_state >> 8);
        EEPROM.write(MEMORY_LAST_STATE_LSB, motor_state & 0x00FF);


        motor_old_state = motor_state;
      }

      //handle requests from the UI
      if(motor_requested_state == MOTOR_STATE_READY){
        //then go for it !
        motor_state = MOTOR_STATE_READY;
      }
      else if(motor_requested_state == MOTOR_STATE_BOLUS){
        motor_state = MOTOR_STATE_BOLUS;
      }

    break;

    //this state allows the user to control de syringe and make high speed movements. Speed is set by
    // the CONFIG_MOTOR_FREE_MOVE_SPEED parameter
    case MOTOR_STATE_FREE_MOVE:
      if(motor_old_state != MOTOR_STATE_FREE_MOVE){

        //stop the motor
        digitalWrite(PIN_MOTOR_ENABLE, LOW); 
        digitalWrite(PIN_MOTOR_DIR, LOW);

        //shut down the timer
        TCCR1A = 0;
        TCCR1B = 0;

        motor_old_state = motor_state;
      }

      //handle requests from the UI
      if(ui_state == UI_STATE_MENU_MANUAL){
        if(ui_btn_prev == 2){
          //move left
          digitalWrite(PIN_MOTOR_ENABLE, HIGH); 
          digitalWrite(PIN_MOTOR_DIR, HIGH);
          //start the timer
          OCR1A = CONFIG_MOTOR_FREE_MOVE_SPEED;
          TCCR1A |= (1<< COM1A0); //toggle Oc1a on compare,
          TCCR1B |= (1<< WGM12); //CTC mode (clr TCNT1 on compare);
          TCCR1B |= (1 << CS10);    // /1 prescaler on timer 1 clock
        }
        else if((ui_btn_next == 2)){
          //move right
          digitalWrite(PIN_MOTOR_ENABLE, HIGH); 
          digitalWrite(PIN_MOTOR_DIR, LOW);
          //start the timer
          OCR1A = CONFIG_MOTOR_FREE_MOVE_SPEED;
          TCCR1A |= (1<< COM1A0); //toggle Oc1a on compare,
          TCCR1B |= (1<< WGM12); //CTC mode (clr TCNT1 on compare);
          TCCR1B |= (1 << CS10);    // /64 prescaler on timer 1 clock
        }
        else if((ui_btn_prev == 0) && (ui_btn_next == 0)){
          //stop the motor
          digitalWrite(PIN_MOTOR_ENABLE, LOW); 
          digitalWrite(PIN_MOTOR_DIR, LOW);
          //shut down the timer
          TCCR1A = 0;
          TCCR1B = 0;
        }
      }
      else{
        //stop the motor
        digitalWrite(PIN_MOTOR_ENABLE, LOW); 
        digitalWrite(PIN_MOTOR_DIR, LOW);
        //shut down the timer
        TCCR1A = 0;
      }
      
      if(motor_requested_state == MOTOR_STATE_READY){
        //then go for it !
        motor_state = MOTOR_STATE_READY;
      }
    break;

    //this state is a mix between INJECTING and FREE MOVE : without any user action the motor will stay on its motion, user can only speed things up
    // to generate a bolus effect. Speed is set by the CONFIG_MOTOR_BOLUS_SPEED parameter
    case MOTOR_STATE_BOLUS:
      if(motor_old_state != MOTOR_STATE_BOLUS){
        motor_old_state = motor_state;
      }

      //handle requests from the UI
      if(ui_state == UI_STATE_BOLUS){
        if(ui_btn_next >= 2){
          //move left
          digitalWrite(PIN_MOTOR_ENABLE, HIGH); 
          digitalWrite(PIN_MOTOR_DIR, LOW);
          //start the timer
          OCR1A = CONFIG_MOTOR_BOLUS_SPEED;
          TCCR1A = (1<< COM1A0); //toggle Oc1a on compare,
          TCCR1B = (1<< WGM12); //CTC mode (clr TCNT1 on compare);
          TCCR1B |= (1 << CS10);    // /1 prescaler on timer 1 clock
        }
        else{
          //restore the motor previous state
          //make sure the clock is still set to a slow value
          TCCR1B = (1 << CS11);    // /8 prescaler on timer 1 clock

          //start the timer using the calculated value
          // value for 1ms : 7.8125 = (8*10^6) / (1000*1024) 
          OCR1A = ml_per_h_to_timer_value(current_injection_speed, config_syringe_step_per_ml[current_syringe]);

          TCCR1A = (1<< COM1A0); //toggle Oc1a on compare,
          TCCR1B |= (1<< WGM12); //CTC mode (clr TCNT1 on compare);
          }
      }
      
      if(motor_requested_state == MOTOR_STATE_INJECTING){
        //then go for it !
        motor_state = MOTOR_STATE_INJECTING;
      }
    break;

    //this state is triggered by the watchdog timer. stops everything and go back to READY
    case MOTOR_STATE_WATCHDOG:
      if(motor_old_state != MOTOR_STATE_WATCHDOG){

        //we clear the motor request flag so it wont restart by itself
        motor_requested_state = MOTOR_STATE_READY;

        //stop the motor
        digitalWrite(PIN_MOTOR_ENABLE, LOW); 
        digitalWrite(PIN_MOTOR_DIR, LOW);
        //shut down the timer
        TCCR1A = 0;
        motor_old_state = motor_state;
      }

      //if watchdog reset, go back to ready
      if(wd_debounce == 0){
        motor_state = MOTOR_STATE_READY;
      }

    break;

    //should never happen
    default:
      while(1); //suicide to trigger the watchdog
    break;
  }
  
  //if the watchdog is triggered we override the motor state
  if(wd_debounce > 2){
    motor_state = MOTOR_STATE_WATCHDOG;
  }

  
  // Watchdog subroutine : should be called at least every second to prevent the hardware from going into safe mode
  wd_count=0;
  if(analogRead(PIN_WD_ADC)< WD_THRESHOLD_MIN)
  while(analogRead(PIN_WD_ADC) < WD_THRESHOLD_MAX){
      pinMode(PIN_WD_IO, OUTPUT);
      digitalWrite(PIN_WD_IO, HIGH);
      delay(1);
      wd_count ++;
  }
  pinMode(PIN_WD_IO, INPUT);

  //debug
  
  Serial.print("ui_state = ");
  Serial.print(ui_state);
  Serial.print(" motor_state = ");
  Serial.print(motor_state);
  Serial.print(" OCR1A = ");
  Serial.print(OCR1A);
  Serial.print("\n");

  //wait between loops (I know, not the best thing to do but... might do better later)
  if(wd_count < 100) delay(100 - wd_count);               
}
