// This arduino code is used to run the syringe pump module
// The communication is based on https://forum.arduino.cc/t/serial-input-basics-updated/382007
// The stepper is driven uding the AccelStepper library, which is documented here //https://hackaday.io/project/183279-accelstepper-the-missing-manual/details


//Librabies
#include <AccelStepper.h> 


// Pin definitions
const int dirPin = 2; // pin used to set the direction the stepper motor should rotate
const int stepPin = 3; // pin used to make one step in the specified direction
#define max_fill_button 4 //pin of button which is pressed, when the syringe is full
#define min_fill_button 5  //pin of button which is pressed, when the syringe is empty

// Define motor interface type
#define motorInterfaceType 1


// Global variables
int last_time = 0; //last time measurement data was sent by the arduino in s
int time = 0; //actual time
int timeinterval = 1; //time interval in which measurements should be made and their data sent
const byte numChars = 32;     // number of characters in the message received from the PC. Can be much bigger, because the buffer is cleared before it is full
char receivedChars[numChars]; // Array for received characters
char tempChars[numChars];     // temporary array for use when parsing to not change the original data receivedChars
const int stepsPerRevolution = 200;  // change this to fit the number of steps per revolution of the stepper motor
float mm_per_ml = 42.0/5.0; //mm traveled per volume of syringe
float steps_per_mm =200/(10*3.1415); //conversion factor from steps to distance in mm
long set_steps=0; //by the set distance specified number of steps, which should be traveled

// variables to hold the parsed data
// The received data has the format "<Arduino,Actor,Actorvalue>" with the name of the arduino and the actor, whichs value should be changed to the actorvalue
char Arduino[numChars] = {0};   // Variable for the name of the arduino, whichs actors should act
char Actor[numChars] = {0};   // Variable for the name of the actor, whichs value should be changed
float Actorvalue = 0;           // Variable for the value the actor should be changed to
boolean NewInstructions = false; // Indicates if message is completely received

// Creates an instance
AccelStepper myStepper(motorInterfaceType, stepPin, dirPin);


// Setup of pin's and communication
void setup() {
    Serial.begin(9600);
    myStepper.setMaxSpeed(1000); //sets the maximum speed in steps/s. Speeds over 1000 may be unreliable.
    myStepper.setAcceleration(50); //sets the acceleration in steps/s^2
    myStepper.setSpeed(200); //sets the speed in steps/s. Speeds over 1000 may be unreliable. This speed is only used when calling runSpeed() and not when calling run()
    myStepper.setCurrentPosition(0); //makes the current motor position and the target position equal to the value specified
    pinMode(max_fill_button, INPUT_PULLUP); //limit switch is pulled up to 5 V and pulled down to ground, when the switch is pressed
    pinMode(min_fill_button, INPUT_PULLUP); //limit switch is pulled up to 5 V and pulled down to ground, when the switch is pressed
}


// Main loop running continuously
void loop() {

    ReceiveInstructions();               // the ReceiveInstructions function is defined below and receives the instructions from the PC to the arduino using serial communication

    if (NewInstructions == true) {       //if new instructions have been received
        ExecuteInstructions();           // execute the instructions by setting the values of the actors. The ExecuteInstructions function is defined below
        NewInstructions = false;         //set the variable to show, that the instructions have been executed
    }

    time = millis()/1000;                // calculate actual time in s
    if((time-last_time)>=timeinterval){  // check if it is time to send new measurement data
      SendData();                        // send the data using the SendData function defined below
      last_time = time;                  // remember the last time data has been sent
    }
}



void ReceiveInstructions() { //receives the instructions from the PC to the arduino using serial communication
    static boolean recvInProgress = false; //indicates if startMarker has been received and message is collected
    static byte ndx = 0; //index for the char array
    char startMarker = '<'; //startMarker for the array
    char endMarker = '>'; //endMarker for the array "New Line" in arduino IDE serial monitor puts a '\n' at the end of each message as endMarker
    char rc; // single received character which is put into the receivedChars array

    while (Serial.available() > 0 && NewInstructions == false) { //if new data is available and the message is not completed 
        rc = Serial.read(); //read single character from buffer

        if (recvInProgress == true) { //if the start marker has been received and the message is now collected
            if (rc != endMarker) { //if the received character is not the endMarker
                receivedChars[ndx] = rc; //attach the character to the array
                ndx++; //increase index of the received character array
                if (ndx >= numChars) { //check that not too many characters have been received
                    ndx = numChars - 1;
                }
            }
            else { //if the received character is the endMarker
                receivedChars[ndx] = '\0'; // terminate the string
                recvInProgress = false; //set the indicator that the message is completely received
                ndx = 0; //reset the array index
                NewInstructions = true; //set the indicator that the data can be displayed
            }
        }

        else if (rc == startMarker) { //if the received character is the startMarker
            recvInProgress = true; //set the indicator that the characters should be collected to the message
        }
    }
}



void ExecuteInstructions() {  //process the received data and execute the instructions

    // Parse the received Data into it's components
    char * strtokIndx; // char * is a pointer (the memory adress for char data) this is used by strtok() as an index
    strcpy(tempChars, receivedChars); //copies the string in receivedChars into tempChars
    // this temporary copy is necessary to protect the original data
    // because strtok() used in parseData() replaces the commas with \0
    strtokIndx = strtok(tempChars,",");      // strtok splits a string (tempChars) into pieces (tokens) using a delimiter (,). it replaces the delimiter with \0 (NULL) and returns the pointer to the next token each time it is called
    strcpy(Arduino, strtokIndx); // copy it to messageFromPC
    strtokIndx = strtok(NULL, ","); // this continues where the previous call left off
    strcpy(Actor, strtokIndx); // copy it to messageFromPC
    strtokIndx = strtok(NULL, ","); // this continues where the previous call left off
    Actorvalue = atof(strtokIndx);     // convert this part to a float number
  
  // Execute the received instructions
  if (strcmp(Arduino,"Ard_Syringe")==0){
    if (strcmp(Actor,"Syringe")==0){
        set_steps=steps_per_mm*mm_per_ml*Actorvalue;
        myStepper.moveTo(myStepper.currentPosition()+set_steps); //moveTo set the desired absolute target position to the current position (returned by currentPosition()) plus the number of steps
        while((myStepper.distanceToGo()!=0) && (((digitalRead(max_fill_button)==HIGH) && (set_steps > 0)) || ((digitalRead(min_fill_button)==HIGH) && (set_steps < 0) ))){ //distanceToGo returns the distance from the current position to the target position in steps. If this distance is not zero and none of the limit switches is pressed, the motor should run
            myStepper.run(); //make one step to the target position
        }
        myStepper.setCurrentPosition(0); //makes the current motor position and the target position equal to the value specified and therefore prevents the motor from pushing against the limit switches
        set_steps=-set_steps;
        myStepper.moveTo(myStepper.currentPosition()+set_steps); //moveTo set the desired absolute target position to the current position (returned by currentPosition()) plus the number of steps
        while((myStepper.distanceToGo()!=0) && (((digitalRead(max_fill_button)==HIGH) && (set_steps > 0)) || ((digitalRead(min_fill_button)==HIGH) && (set_steps < 0) ))){ //distanceToGo returns the distance from the current position to the target position in steps. If this distance is not zero and none of the limit switches is pressed, the motor should run
            myStepper.run(); //make one step to the target position
        }
        myStepper.setCurrentPosition(0); //makes the current motor position and the target position equal to the value specified and therefore prevents the motor from pushing against the limit switches    
    }
  }
}




void SendData() { //sent the acutal timestamp and the measurement values from the arduino to the pc
  Serial.print("max_fill_button");
  Serial.print(',');
  Serial.print(digitalRead(max_fill_button));
  Serial.print(',');
  Serial.print("min_fill_button");
  Serial.print(',');
  Serial.println(digitalRead(min_fill_button));
}
