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

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
#define MEMORY_SENSOR_POSITION_MSB 0x08 //int (2 Bytes)
#define MEMORY_SENSOR_POSITION_LSB 0x09
#define MEMORY_MOTOR_STEPS_B0 0x0A //long (4 Bytes)
#define MEMORY_MOTOR_STEPS_B1 0x0B
#define MEMORY_MOTOR_STEPS_B2 0x0C
#define MEMORY_MOTOR_STEPS_B3 0x0D

/* 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_LOOK_FOR_SWITCH 0x06
#define MOTOR_STATE_GOTO_SYRINGE_START 0x07
#define MOTOR_STATE_LOOK_FOR_SYRINGE 0x08
#define MOTOR_STATE_RELOAD 0x09

#define MOTOR_STATE_ERROR 0xF0
#define MOTOR_STATE_JAMMED 0xF1

#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 automatic movements
#define CONFIG_MOTOR_AUTO_MOVE_SPEED 1024

//change this to chose the speed of syringe approach movements
#define CONFIG_MOTOR_SYRINGE_APPROACH_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;

//motor pulses counter
volatile long motor_steps = 0;
//motor direction (0 = towards end of syringe)
volatile int motor_dir =0;

//motor storage of the injection start position
long motor_injection_start_steps;

//pulse counting interrupt
ISR(TIMER1_COMPA_vect){

  if(!motor_dir){
    motor_steps++;
  }
  else{
    motor_steps--;
  }
}


/* Sensors */

//position encoder
#define PIN_POSITION_SENSOR_A 2
#define PIN_POSITION_SENSOR_B 3

volatile int sensor_position_counter = 0;
volatile int pos_sensor_buffer=0;

//this variable is used to store the motor_steps value when a sensor pulse occurs
volatile long sensor_last_motor_steps = 0;

void pos_sensor_ISR() {
  int a;
    a = digitalRead(PIN_POSITION_SENSOR_A);
    if(a != pos_sensor_buffer){
      if(digitalRead(PIN_POSITION_SENSOR_B) != a){
        sensor_position_counter--;
      }
      else{
        sensor_position_counter++;
      }
      //store the motor steps value
      sensor_last_motor_steps = motor_steps;
    }
    pos_sensor_buffer = a;
}

//end switch
#define PIN_END_SWITCH A6

/* Hardware limitations */

//maximum motor step between two sensor pulse : if above, hardware is considered mechanically wrong or jammed.
#define SENSOR_MAX_MOTOR_STEPS_PER_PULSE 20000L

//maximum sensor steps : if above, hardware is considered mechanically wrong or jammed.
#define SENSOR_MAX_VALUE 115

/* 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_WAITING 0x02
#define UI_STATE_READY 0x03
#define UI_STATE_CONFIRM_INJECTION 0x04
#define UI_STATE_RUNNING 0x05
#define UI_STATE_BOLUS 0x06
#define UI_STATE_CONFIRM_STOP 0x07
#define UI_STATE_END 0x08

#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_MENU_CONFIRM_CALIBRATE 0xFC

#define UI_STATE_ALARM 0xFE
#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

//change this to control the time before a menu timeout (in 0.1 secs)
#define UI_MENU_TIMEOUT 300

//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}; //in motor steps
const int config_syringe_start_pos [UI_MAX_SYRINGE+1] = {0,0,16,0,0}; // in sensor steps

//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

  //sensor pins
  pinMode(PIN_POSITION_SENSOR_A, INPUT);
  pinMode(PIN_POSITION_SENSOR_B, INPUT);
  pinMode(PIN_END_SWITCH, INPUT);

  //register sensor interrupt
  attachInterrupt(0, pos_sensor_ISR, CHANGE);

  //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_WAITING;
      }
    break;
    
    //simple Waiting screen, go back to ready when the motor state machine is ready
    case UI_STATE_WAITING:
      if(ui_old_state != UI_STATE_WAITING){
        lcd.clear();
        lcd.setCursor(0, 2);
        lcd.print(" VEUILLEZ PATIENTER");
        ui_selection=0;
        ui_state_count=0;
        ui_old_state = ui_state;
      }
      if(motor_state == MOTOR_STATE_READY){
        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;
        }
      }

      //if the motor state machine is not ready anymore, display the waiting screen
      if(motor_state != MOTOR_STATE_READY){
        ui_state = UI_STATE_WAITING;
      }

    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_state_count=0;
        ui_selection = !ui_selection;
      }

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

    // menu timout
    if(ui_state_count > UI_MENU_TIMEOUT){
      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("  Vitesse");
        lcd.setCursor(11, 0);
        lcd.print(current_injection_speed);
        lcd.setCursor(14, 0);
        lcd.print("mL/h");
        lcd.setCursor(1, 3);
        lcd.print("stop");
        lcd.setCursor(7, 3);
        lcd.print("ml/h");
        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 back to ready, go to stop mode
        if(motor_state == MOTOR_STATE_READY){
          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(" ");
      }

      if(motor_state == MOTOR_STATE_INJECTING){
        //Refresh the values every seconds
        if(!(ui_state_count %10)){
          
          lcd.setCursor(0, 1);
          lcd.print("Volume     :      mL");
          lcd.setCursor(0, 2);
          lcd.print("Temps restant:   :  ");
          
          lcd.setCursor(14, 1);
          lcd.print(ui_current_injected_volume + ui_current_bolus_injected_volume);
  
          //display the remaining time
          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);
        }
      }
      else{
          lcd.setCursor(0, 1);
          lcd.print("     RECHERCHE      ");
          lcd.setCursor(0, 2);
          lcd.print("     SERINGUE       ");
      }

      //display item selection
      if(ui_selection == 0){
        lcd.setCursor(0, 3);
        lcd.write(UI_SELECT_CHAR_LEFT);
        lcd.setCursor(5, 3);
        lcd.write(UI_SELECT_CHAR_RIGHT);
        lcd.setCursor(6, 3);
        lcd.print(" ");
        lcd.setCursor(11, 3);
        lcd.print(" ");
        lcd.setCursor(13, 3);
        lcd.print(" ");
        lcd.setCursor(19, 3);
        lcd.print(" ");
      }
       else if(ui_selection == 1){
        lcd.setCursor(0, 3);
        lcd.print(" ");
        lcd.setCursor(5, 3);
        lcd.print(" ");
        lcd.setCursor(6, 3);
        lcd.write(UI_SELECT_CHAR_LEFT);
        lcd.setCursor(11, 3);
        lcd.write(UI_SELECT_CHAR_RIGHT);
        lcd.setCursor(13, 3);
        lcd.print(" ");
        lcd.setCursor(19, 3);
        lcd.print(" ");
      }     
      else if(ui_selection == 2){
        lcd.setCursor(0, 3);
        lcd.print(" ");
        lcd.setCursor(5, 3);
        lcd.print(" ");
        lcd.setCursor(6, 3);
        lcd.print(" ");
        lcd.setCursor(11, 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_selection >0)){
        ui_selection--;
      }
      else if ((ui_btn_next == 2) && (ui_selection <2)){
        ui_selection++;
      }

      if(ui_btn_ok == 2){

        if(ui_selection == 0){
           ui_state = UI_STATE_CONFIRM_STOP;
        }
        else if(ui_selection == 1){
          ui_state = UI_STATE_MENU_SPEED;
        }
        else if(ui_selection ==2){
          ui_state = UI_STATE_BOLUS;
        }
      }

      //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){
        ui_state_count = 0;
        //for the video
        if(!(ui_btn_next %10)) ui_current_bolus_injected_volume++;
      }

      if(ui_btn_ok == 2){
        ui_state_count = 0;
        ui_state = UI_STATE_RUNNING;
      }

    // menu timout
    if(ui_state_count > UI_MENU_TIMEOUT){
      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_state_count = 0;
        ui_selection = !ui_selection;
      }

      if(ui_btn_ok == 2){
        ui_state_count = 0;
        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;
        }
      }

     // menu timout
    if(ui_state_count > UI_MENU_TIMEOUT){
      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){
         ui_state_count = 0;
         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 > UI_MENU_TIMEOUT){
        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;
      }

      //handle button press
      if(ui_btn_ok == 2){
        ui_state_count = 0;
        ui_state = UI_STATE_MAIN_MENU;
        motor_requested_state = MOTOR_STATE_READY;
      }

      //just here to reset the timeout
      if((ui_btn_prev > 2) || (ui_btn_next > 2)) ui_state_count = 0;

      // menu timout
      if(ui_state_count > UI_MENU_TIMEOUT){
        ui_state = UI_STATE_MAIN_MENU;
        motor_requested_state = MOTOR_STATE_READY;
      }

    break;

    //this menu allows the user to set the injection speed in ml/h
    //this is the only menu that can be called while injecting.
    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= current_injection_speed;
        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_selection > 10) && !(ui_selection % 10) && (ui_btn_prev > 50)){
          ui_selection -= 10;
        }
        else if(ui_selection > 0){
          ui_selection --;
        }

        lcd.setCursor(0, 2);
        lcd.print("   ");
        lcd.setCursor(0, 2);
        lcd.print(ui_selection);
        ui_state_count=0;
      }

      if((ui_btn_next == 2) || ((ui_btn_next > 0) && !(ui_btn_next % 2))){

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

      if(ui_btn_ok == 2){
        //save the new value
        current_injection_speed = ui_selection;
        ui_state = UI_STATE_MENU_CONFIRM_VALUE;
      }

      // menu timout
      if(ui_state_count > UI_MENU_TIMEOUT){
        //if we are injecting, go back to the running state
        if(motor_requested_state == MOTOR_STATE_INJECTING){
          ui_state = UI_STATE_RUNNING;
        }
        else{
          //if not, return to the main menu
          ui_state = UI_STATE_MAIN_MENU;
        }
      }

     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=current_syringe;
        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]);
        ui_state_count=0;
      }

       if(ui_btn_ok == 2){
        //save the new value
        current_syringe = ui_selection;
        ui_state = UI_STATE_MENU_CONFIRM_VALUE;
      }

     // menu timout
     if(ui_state_count > UI_MENU_TIMEOUT){
        ui_state = UI_STATE_MAIN_MENU;
     }

    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=ui_remaining_time_alarm;
        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_selection > 10) && !(ui_selection % 10) && (ui_btn_prev > 50)){
          ui_selection -= 10;
        }
        else if(ui_selection > 0){
          ui_selection --;
        }

        lcd.setCursor(0, 2);
        lcd.print("   ");
        lcd.setCursor(0, 2);
        lcd.print(ui_selection);
        ui_state_count=0;
      }

      if((ui_btn_next == 2) || ((ui_btn_next > 0) && !(ui_btn_next % 2))){

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

      if(ui_btn_ok == 2){
        //save the new value
        ui_remaining_time_alarm = ui_selection;
        ui_state = UI_STATE_MENU_CONFIRM_VALUE;
      }

     // menu timout
     if(ui_state_count > UI_MENU_TIMEOUT){
        ui_state = UI_STATE_MAIN_MENU;
     }

    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){
        //if we are injecting, go back to the running state
        if(motor_requested_state == MOTOR_STATE_INJECTING){
          //request a reload of the speed
          motor_requested_state = MOTOR_STATE_RELOAD;
          ui_state = UI_STATE_RUNNING;
        }
        else{
          //if not, return to the main menu
          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("non");
        lcd.setCursor(16, 3);
        lcd.print("oui");
        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:
      if(ui_old_state != UI_STATE_MENU_SELFTEST){
        //send a start message to the motor state machine
        motor_requested_state = MOTOR_STATE_LOOK_FOR_SWITCH;

        lcd.clear();
        lcd.print("Selftest  . . .");
        ui_selection=0;
        ui_state_count=0;
        ui_old_state = ui_state;
      }

      // 200 seconds timout
      if(ui_state_count > 2000){
        ui_state = UI_STATE_MAIN_MENU;
      }

    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 displays an alarm, require user acknoledge to dismiss
    case UI_STATE_ALARM:
      if(ui_old_state != UI_STATE_ALARM){
        lcd.clear();
        lcd.print("  === ATTENTION === ");
        lcd.setCursor(0, 1);
        lcd.print("Blocage detecte ");
        lcd.setCursor(0, 2);
        lcd.print("Verifiez chariot et");
        lcd.setCursor(0, 3);
        lcd.print("appuyez sur OK");
        ui_selection=0;
        ui_state_count=0;
        ui_old_state = ui_state;
      }

      if(ui_blink){
        lcd.setCursor(0, 0);
        lcd.print("                    ");
        digitalWrite(PIN_BUZZER, HIGH);
      }
      else{
        lcd.setCursor(0, 0);
        lcd.print("  === ATTENTION === ");
        digitalWrite(PIN_BUZZER, LOW);
      }

      //handle buttons press
      if(ui_btn_ok == 2){
        digitalWrite(PIN_BUZZER, HIGH);

        //clear error by rebooting the motor state machine
        motor_requested_state = MOTOR_STATE_BOOTING;

        //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 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;
  }
  //if the motor is in error state, and this error is not ack, we override the ui to diplay a message
  else if((motor_state >= MOTOR_STATE_ERROR && motor_state < MOTOR_STATE_WATCHDOG) && (motor_requested_state != MOTOR_STATE_BOOTING)){
    ui_state = UI_STATE_ALARM;
  }

  //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_dir = 0;

      // enable timer compare interrupt
      TIMSK1 |= (1 << OCIE1A);

      //here we do everything related to interupt changed variables
      noInterrupts();

      //if we have no steps / sensor value (probably after a cold boot)
      if((motor_steps <100) && (sensor_position_counter == 0)){
        //we try to load last known position from flash memory if the last known status was injecting
        if(((EEPROM.read(MEMORY_LAST_STATE_MSB) << 8) |  EEPROM.read(MEMORY_LAST_STATE_LSB)) == MOTOR_STATE_INJECTING){
          motor_steps = (EEPROM.read(MEMORY_MOTOR_STEPS_B0) << 24) | (EEPROM.read(MEMORY_MOTOR_STEPS_B1) << 16) | (EEPROM.read(MEMORY_MOTOR_STEPS_B2) << 8) | EEPROM.read(MEMORY_MOTOR_STEPS_B3);
          sensor_position_counter = (EEPROM.read(MEMORY_SENSOR_POSITION_MSB) << 8) |  EEPROM.read(MEMORY_SENSOR_POSITION_LSB);
          sensor_last_motor_steps = motor_steps;
        }
      }

      //clear Jamming error
      sensor_last_motor_steps = motor_steps;
      interrupts();

      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);
        motor_dir = 0;
   
        //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);
        }
        //moving from jammed to ready means the LOOK FOR SWITCH STATE failed.
        else if(motor_old_state == MOTOR_STATE_JAMMED){
          //retry
          motor_state = MOTOR_STATE_LOOK_FOR_SWITCH;
        }
        motor_old_state = motor_state;
      }

      //if we are lost (we did not move, we are not on the switch an no pulses were registered), we recover
      if((motor_steps <100) && (sensor_position_counter == 0) && (analogRead(PIN_END_SWITCH) > 512)){
          motor_state = MOTOR_STATE_LOOK_FOR_SWITCH;
      }
      //handle requests from the UI
      else if(motor_requested_state == MOTOR_STATE_INJECTING){
        //quick sanity checks before allowing motor movement
        if(current_injection_speed==0) break;
        //then go to the start of the syringe
        motor_state = MOTOR_STATE_GOTO_SYRINGE_START;
      }
      else if(motor_requested_state == MOTOR_STATE_FREE_MOVE){
        motor_state = MOTOR_STATE_FREE_MOVE;
      }
      else if(motor_requested_state == MOTOR_STATE_LOOK_FOR_SWITCH){
        motor_state = MOTOR_STATE_LOOK_FOR_SWITCH;
      }
    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);
        motor_dir = 0;

        //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;
      }

      //every four sensor pulses (approx 5mm), we backup the motor_steps and position_counter values. It may be a bit harsh for the flash, might change this later to improve memory life
      //critical section as all those values are modified in interruption !
      noInterrupts();
      if(!(sensor_position_counter % 4)){
        EEPROM.write(MEMORY_MOTOR_STEPS_B0, motor_steps >> 24);
        EEPROM.write(MEMORY_MOTOR_STEPS_B1, motor_steps >> 16);
        EEPROM.write(MEMORY_MOTOR_STEPS_B2, motor_steps >> 8);
        EEPROM.write(MEMORY_MOTOR_STEPS_B3, motor_steps & 0x000000FF);
        EEPROM.write(MEMORY_SENSOR_POSITION_MSB, sensor_position_counter >> 8);
        EEPROM.write(MEMORY_SENSOR_POSITION_LSB, sensor_position_counter & 0x00FF);
      }
      interrupts();

      //handle requests from the UI
      if(motor_requested_state == MOTOR_STATE_READY){
        //return syringe to waiting position
        motor_state = MOTOR_STATE_LOOK_FOR_SWITCH;
      }
      else if(motor_requested_state == MOTOR_STATE_BOLUS){
        motor_state = MOTOR_STATE_BOLUS;
      }
      else if(motor_requested_state == MOTOR_STATE_RELOAD){
        motor_state = MOTOR_STATE_RELOAD;
      }

    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);
          motor_dir = 1;
          //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);
          motor_dir = 0;
          //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);
          motor_dir = 0;
          //shut down the timer
          TCCR1A = 0;
          TCCR1B = 0;
        }
      }
      else{
        //stop the motor
        digitalWrite(PIN_MOTOR_ENABLE, LOW);
        digitalWrite(PIN_MOTOR_DIR, LOW);
        motor_dir = 0;
        //shut down the timer
        TCCR1A = 0;
        TCCR1B = 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);
          motor_dir = 0;

          //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 dynamically changes the injection speed
    case MOTOR_STATE_RELOAD:
      if(motor_old_state != MOTOR_STATE_RELOAD){
         motor_old_state = motor_state;
      }
        //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);
        
        //set the requested state back to injecting
        motor_requested_state = MOTOR_STATE_INJECTING;
        //then go back to injecting
        motor_state = MOTOR_STATE_INJECTING;
    break;


    //this state reset the motor position to 0 (wide open)
    // the CONFIG_MOTOR_AUTO_MOVE_SPEED parameter can change the speed of this movement
    case MOTOR_STATE_LOOK_FOR_SWITCH:
      if(motor_old_state != MOTOR_STATE_LOOK_FOR_SWITCH){

        //critical section as all those values are modified in interruption !
        noInterrupts();
        //reset the jamming protection
        sensor_last_motor_steps = motor_steps;
        interrupts();

        //move back
        digitalWrite(PIN_MOTOR_ENABLE, HIGH);
        digitalWrite(PIN_MOTOR_DIR, HIGH);
        motor_dir = 1;
        //start the timer
        OCR1A = CONFIG_MOTOR_AUTO_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

        motor_old_state = motor_state;
      }

      //wait for the switch contact
      if(analogRead(PIN_END_SWITCH) < 512){
        //stop the motor
        digitalWrite(PIN_MOTOR_ENABLE, LOW);
        digitalWrite(PIN_MOTOR_DIR, LOW);
        motor_dir = 0;
        //shut down the timer
        TCCR1A = 0;
        TCCR1B = 0;

        //critical section as all those values are modified in interruption !
        noInterrupts();
        //reset all counters
        sensor_position_counter = 0;
        motor_steps = 0;
        sensor_last_motor_steps = 0;
        interrupts();

        //we clear the motor request flag so it wont restart by itself
        motor_requested_state = MOTOR_STATE_READY;
        //then go back to ready
        motor_state = MOTOR_STATE_READY;
      }
    break;

    //this state brings the motor to the theoretical start position of the syringe (if defined)
    // the CONFIG_MOTOR_AUTO_MOVE_SPEED parameter can change the speed of this movement
    case MOTOR_STATE_GOTO_SYRINGE_START:
      if(motor_old_state != MOTOR_STATE_GOTO_SYRINGE_START){
        if(config_syringe_start_pos[current_syringe] != 0){
          if(sensor_position_counter < config_syringe_start_pos[current_syringe]){
            //move forward
            digitalWrite(PIN_MOTOR_ENABLE, HIGH);
            digitalWrite(PIN_MOTOR_DIR, LOW);
            motor_dir = 0;
            //start the timer
            OCR1A = CONFIG_MOTOR_AUTO_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{
            //something is clearly wrong, maybe a half full syringe ? maybe the user disengaged the drive and moved it ?
            //anyway we jump to the fine approach state
            motor_state = MOTOR_STATE_LOOK_FOR_SYRINGE;
            break;
          }
        }
        else{
          //if we dont know where to go, just stay there
          motor_state = MOTOR_STATE_LOOK_FOR_SYRINGE;
          break;
        }
        motor_old_state = motor_state;
      }

      //arriving
      if(sensor_position_counter >= config_syringe_start_pos[current_syringe]){
        //stop the motor
        digitalWrite(PIN_MOTOR_ENABLE, LOW);
        digitalWrite(PIN_MOTOR_DIR, LOW);
        motor_dir = 0;
        //shut down the timer
        TCCR1A = 0;
        TCCR1B = 0;
        motor_state = MOTOR_STATE_LOOK_FOR_SYRINGE;

      }

    break;

    //this state brings the motor closer and closer to the syringe until the pressure sensor detects it
    // the CONFIG_MOTOR_SYRINGE_APPROACH_SPEED parameter can change the speed of this movement
    case MOTOR_STATE_LOOK_FOR_SYRINGE:
 
      if(motor_old_state != MOTOR_STATE_LOOK_FOR_SYRINGE){
        motor_old_state = motor_state;
      }

      //as we dont have the sensor yet, we just store the position and move on
      //critical section as all those values are modified in interruption !
      noInterrupts();
      motor_injection_start_steps = motor_steps;
      interrupts();
      motor_state = MOTOR_STATE_INJECTING;

    break;

    //this state is entered in case of errors and must be acknoledged by the ui
    case MOTOR_STATE_ERROR:
    case MOTOR_STATE_JAMMED:

      if(motor_old_state != motor_state){

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

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

        motor_old_state = motor_state;
      }

      //the only thng the ui can do now is acknoledge the error by requesting a reboot
      if(motor_requested_state == MOTOR_STATE_BOOTING){
        //then go for it !
        motor_state = MOTOR_STATE_BOOTING;
      }
    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);
        motor_dir = 0;

        //shut down the timer
        TCCR1A = 0;
        TCCR1B = 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;
  }
  //if no watchdog and the UI is not clearing errors, we check on the motor setp / sensor pulses consistency to detect any jamming
  else if(motor_requested_state != MOTOR_STATE_BOOTING) {
    //critical section as all those values are modified in interruption !
    noInterrupts();
    if(abs(motor_steps - sensor_last_motor_steps) >= SENSOR_MAX_MOTOR_STEPS_PER_PULSE){
      motor_state = MOTOR_STATE_JAMMED;
    }
    interrupts();
  }


  // 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("  motor_jamm = ");
  //Serial.println(abs(motor_steps - sensor_last_motor_steps));
  //Serial.println(sensor_position_counter);
  //Serial.println(motor_steps);
  //Serial.println(analogRead(PIN_END_SWITCH));
  //Serial.print("ui_state = ");
  //Serial.print(ui_state);
  //Serial.print(" motor_state = ");
  //Serial.print(motor_state);
  //Serial.print(" motor_requested_state = ");
  //Serial.println(motor_requested_state);

  //Serial.print(" motor_old_state = ");
  //Serial.println(motor_old_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);
}
