aboutsummaryrefslogtreecommitdiffstats
path: root/Software/Embedded_SW/Embedded/Modules/Control/PIDAlgo.h
blob: 03c36313189b62dcadfe6d3dbf54d94863a24ab3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#ifndef MODULES_PIDALGO_H_
#define MODULES_PIDALGO_H_
#include <stdint.h>

typedef struct
{
    float epsilon;
    float dt;
    float MAX;
    float MIN;
    float Kp;
    float Kd;
    float Ki;
}PID_Config_Params;
float PIDAlgorithmCalculation(float _setPoint,float _mesuredParam , PID_Config_Params *params, float *_pre_error, float *_integral);

#endif /* MODULES_PIDALGO_H_ */
/************************************************************************************************************************
 * Ids_print.c
 * Printing module is responsible for :
 * operating the dispensers according to predefined dispensing rate from the UI
 **************************************************************************************************************************/
#include "include.h"
#include "ids.h"
#include "ids_ex.h"
#include "../control/control.h"
#include "../control/pidalgo.h"
#include "../thread/thread.h"
#include "PMR/Hardware/Hardwaremotor.pb-c.h"
#include "PMR/Hardware/HardwareDispenser.pb-c.h"
#include "StateMachines/Printing/printingSTM.h"
#include "drivers/motors/motor.h"
#include "drivers/valves/valve.h"

#include "modules/heaters/heaters.h"


typedef struct
{
    bool                m_isEnabled;
    float            m_SetParam;
    float            m_mesuredParam;
    float               m_preError;
    float               m_integral;
    float               m_calculatedError;
    bool                m_isReady;
    PID_Config_Params   m_params;
}DispenserControlConfig_t;
HardwarePidControl DispensersControl[MAX_SYSTEM_DISPENSERS] = {0};

int32_t DispenserSamples[MAX_SYSTEM_DISPENSERS][MAX_CONTROL_SAMPLES] = {0};
int DispenserSamplePointer[MAX_SYSTEM_DISPENSERS] = {0};
double DispenserNormalizedErrorCoEfficient[MAX_SYSTEM_DISPENSERS] = {0};

HardwarePidControlType ThreadDispenserIdToControlId[MAX_SYSTEM_DISPENSERS] = {  HARDWARE_PID_CONTROL_TYPE__Dispenser1,HARDWARE_PID_CONTROL_TYPE__Dispenser2,HARDWARE_PID_CONTROL_TYPE__Dispenser3,HARDWARE_PID_CONTROL_TYPE__Dispenser4,HARDWARE_PID_CONTROL_TYPE__Dispenser5,HARDWARE_PID_CONTROL_TYPE__Dispenser6,HARDWARE_PID_CONTROL_TYPE__Dispenser7,HARDWARE_PID_CONTROL_TYPE__Dispenser8};

bool    DispenserReady[MAX_SYSTEM_DISPENSERS] = {true};
bool IDS_Active = false;
/******************** STRUCTURES AND ENUMs  ********************************************/
uint32_t IDS_Valve_DistanceToSpoolReady(uint32_t deviceID, uint32_t ReadValue);
uint32_t IDS_Valve_PresegmentReady(uint32_t deviceID, uint32_t ReadValue);
bool IDS_isDispenserUsedNextSegment(void *JobDetails,int DispenserId, int SegmentId);
/******************** GLOBAL PARAMETERS  ********************************************/
DispenserControlConfig_t DispenserControlConfig[MAX_SYSTEM_DISPENSERS];
uint32_t    ControlIdtoDispenserId [MAX_SYSTEM_DISPENSERS] = {0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF};
int OriginalDispenserSpd_2PPS[MAX_SYSTEM_DISPENSERS] = {0,0,0,0,0,0,0,0};
bool         DispenserPreSegmentReady[MAX_SYSTEM_DISPENSERS] = {true,true,true,true,true,true,true,true};
bool         DispenserSegmentReady[MAX_SYSTEM_DISPENSERS] = {true,true,true,true,true,true,true,true};
bool         DispenserDistanceToSpoolReady[MAX_SYSTEM_DISPENSERS] = {true,true,true,true,true,true,true,true};
bool         DispenserUsedInJob[MAX_SYSTEM_DISPENSERS] = {false,false,false,false,false,false,false,false};
bool         DispensersAlarmState[ MAX_SYSTEM_DISPENSERS] = {false,false,false,false,false,false,false,false};
int JobBrushStopId = 0;

uint32_t IDS_DispenserPidRequestMessage(HardwarePidControl* request)
{
    int Dispenser_i,i;
    //int temp;
    for (i=0;i<MAX_SYSTEM_DISPENSERS;i++)
    {
        if (ThreadDispenserIdToControlId[i] == request->hardwarepidcontroltype)
        {
            Dispenser_i = i;
            break;
        }
    }
    memcpy (&DispensersControl[Dispenser_i],request,sizeof(HardwarePidControl));
    if (DispensersControl[Dispenser_i].pvinputfilterfactormode > MAX_CONTROL_SAMPLES)
        DispensersControl[Dispenser_i].pvinputfilterfactormode = MAX_CONTROL_SAMPLES;
    for (i = 0;i < DispensersControl[Dispenser_i].pvinputfilterfactormode; i++)
        DispenserSamples[Dispenser_i][i] = 0;  //reset the samples value for control beginning
    /*DispenserNormalizedErrorCoEfficient[Dispenser_i] = (2*PI*DancersCfg[ThreadDispenserIdToDancerId[Dispenser_i]].armlength);
    temp = 1<<(DancersCfg[ThreadDispenserIdToDancerId[Dispenser_i]].resolutionbits);
    temp=(100*(temp-1)*DancersCfg[ThreadDispenserIdToDancerId[Dispenser_i]].maximalmovementmm);
    DispenserNormalizedErrorCoEfficient[Dispenser_i] = DispenserNormalizedErrorCoEfficient[Dispenser_i] / temp;*/

    return OK;
}

/*
 * IDS Printing support
 * Prepare: build pressure in all participating dispensers
 * Print - dispense ink to the printing head
 * stop - stop dispensing
 *
 * control processes:
 * on prepare stage - each 10msec against the pressure sensors
 * on print stage - every 10/100 msec against the speed sensor
 *
 * */
//Dispenser Pressure control
//callback - calls printing stm with the result

// registration - 10 msec, dispenser pressure sensor
//    AddControlCallback(DeviceId2Dispenser[DispenserId], DispenserControlCBFunction, eHundredMillisecond);
// start the dispenser pressure building - move up in a TBD speed, valve closed
//Dispenser Speed control
//callback - handles speed
// registration - 10msec, dispenser speed senseo
// start the dispensing - move up according to the segment defined speed and microstepping, valve opened
//
void DispenserPrepareReady(void)
{
    int i;
    for (i=0;i<MAX_SYSTEM_DISPENSERS;i++)
    {
        if (DispenserReady[i] == false)
        {
            return; //not all configured Dispensers are ready
        }
    }
    REPORT_MSG(Module_IDS,"DispenserPrepareReady");
    PrepareReady(Module_IDS,ModuleDone);
}
//********************************************************************************************************************
 uint32_t IDS_PrepareReady(uint32_t deviceID, uint32_t ReadValue)
 {
     if (IDS_Active == false)
        return ERROR;

     DispenserReady[deviceID] = true;
     REPORT_MSG(deviceID,"Dispenser prepare ready");
     DispenserPrepareReady();

     return OK; // all configured heaters are ready
 }
 bool IDS_MapDispenserUsedinJob(void *JobDetails)
 {
     JobTicket* JobTicket = JobDetails;
     int Dispenser_i, Segment_i,Brush_i,DispenserId;

     for (Dispenser_i = 0;Dispenser_i<MAX_SYSTEM_DISPENSERS;Dispenser_i++)
     {
         DispenserUsedInJob[Dispenser_i] = false;
     }
     if (JobTicket->n_segments == 0)
         return false;

     for (Segment_i=0;Segment_i<JobTicket->n_segments;Segment_i++)
         {
         for (Brush_i=0;Brush_i<JobTicket->segments[Segment_i]->n_brushstops;Brush_i++)
             {
                 if (JobTicket->segments[Segment_i]->brushstops[Brush_i]->n_dispensers)
                 {
                     for (Dispenser_i = 0;Dispenser_i < JobTicket->segments[Segment_i]->brushstops[Brush_i]->n_dispensers;Dispenser_i++)
                     {
                         //prepare the SW structures
                         DispenserId = JobTicket->segments[Segment_i]->brushstops[Brush_i]->dispensers[Dispenser_i]->index;
                         if (JobTicket->segments[Segment_i]->brushstops[Brush_i]->dispensers[Dispenser_i]->nanolitterpersecond>0.0)
                         {
                             DispenserUsedInJob[DispenserId] = true;
                         }
                     }//for dispenser
                 }//if dispensers
             }//for brush
         }//for segments

     return false;

 }

//********************************************************************************************************************
 uint32_t IDSPrepareState(void *JobDetails)
{
    int Motor_i, HW_Motor_Id, Pid_Id,i;
    //start IDS control for all motors
    IDS_Active = true;
    Valve_Set(VALVE_MIXCHIP_WASTECH, Mixer_Waste);
    for (Motor_i = 0;Motor_i < MAX_SYSTEM_DISPENSERS;Motor_i++)
    {
        HW_Motor_Id = DispenserIdToMotorId[Motor_i];
        Pid_Id = Motor_i;/*IDSMotorIdToControlId[Motor_i];*/
            DispenserControlConfig[Motor_i].m_params.MAX = 1;
            DispenserControlConfig[Motor_i].m_params.MIN = DispensersControl[Pid_Id].outputproportionalpowerlimit*-1;
            DispenserControlConfig[Motor_i].m_params.Kd = DispensersControl[Pid_Id].derivativetime;
            DispenserControlConfig[Motor_i].m_params.Kp = DispensersControl[Pid_Id].proportionalgain;
            DispenserControlConfig[Motor_i].m_params.Ki = DispensersControl[Pid_Id].integraltime;
            DispenserControlConfig[Motor_i].m_params.epsilon = 0.01;
            DispenserControlConfig[Motor_i].m_params.dt = eHundredMillisecond;
            DispenserControlConfig[Motor_i].m_calculatedError = 0;
            DispenserControlConfig[Motor_i].m_integral = 0;
            DispenserControlConfig[Motor_i].m_isEnabled = true;
            DispenserControlConfig[Motor_i].m_isReady = true;
            DispenserControlConfig[Motor_i].m_mesuredParam = 0;
            DispenserControlConfig[Motor_i].m_preError = 0;
            DispenserControlConfig[Motor_i].m_SetParam = 0;//need to update SetParams on presegment stage

            MotorSetDirection((TimerMotors_t)HW_Motor_Id,MotorsCfg[HW_Motor_Id].directionthreadwize); //set the dispenser to the
            /*Start the dispensers to build initial pressure
             * check different handling for dispensers that participate in the first segment and idle dispensers
             * start control for initial pressure
             *
             */
            //ValveCommand (Enable,MixerDirection);
    }
    //set 3 dancers to the profile positions
    IDS_MapDispenserUsedinJob(JobDetails);
    for (i = 0; i < MAX_SYSTEM_DISPENSERS; i++)
    {
        if (DispenserUsedInJob[i] == true) //we actually should check for all dispensers
        {
            DispenserReady[i] = false;
            IDS_Dispenser_Build_Pressure(i, IDS_PrepareReady);
            REPORT_MSG(i,"Dispenser prepare called");
        }
        else
            DispenserReady[i] = true;
    }
    DispenserPrepareReady();
    return OK;
}
bool IDS_isDispenserUsedNextSegment(void *JobDetails,int DispenserId, int SegmentId)
{
    JobTicket* JobTicket = JobDetails;
    int Dispenser_i,n_dispensers;
    if (JobTicket->n_segments == 0)
        return false;
    if (JobTicket->n_segments <= SegmentId)
        return false;
    if (JobTicket->segments[SegmentId]->brushstops[0]->n_dispensers)
    {
        n_dispensers = JobTicket->segments[SegmentId]->brushstops[0]->n_dispensers;
        for (Dispenser_i = 0;Dispenser_i < n_dispensers;Dispenser_i++)
        {
            if (DispenserId == JobTicket->segments[SegmentId]->brushstops[0]->dispensers[Dispenser_i]->index) //dispenser is in use next segment
            {
                if (JobTicket->segments[SegmentId]->brushstops[0]->dispensers[Dispenser_i]->nanolitterpersecond>0)
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }
        }
    }

    return false;

}
 //********************************************************************************************************************
 uint32_t IDS_Valve_PresegmentValveReady(uint32_t deviceID, uint32_t ReadValue)
 {
     //TimerMotors_t  HW_Motor_Id = DispenserIdToMotorId[deviceID];
     //MotorStop(HW_Motor_Id,Hard_Hiz);
     //REPORT_MSG(deviceID,"Dispenser PreSegment called");
     IDS_Valve_PresegmentReady( deviceID,  ReadValue);
     return OK;
 }
 //********************************************************************************************************************
 uint32_t IDS_Valve_PresegmentReady(uint32_t deviceID, uint32_t ReadValue)
 {
     int i;
	 if (IDS_Active == false)
	 	return ERROR;
     DispenserPreSegmentReady[deviceID] = true;
     REPORT_MSG(deviceID,"IDS_Valve_Presegment Ready");
     for (i=0;i<MAX_SYSTEM_DISPENSERS;i++)
     {
         if (DispenserPreSegmentReady[i] == false)
         {
             REPORT_MSG(i,"IDS_Valve_Presegment Not Ready");
             return OK; //not all configured heaters are ready
         }
     }
     REPORT_MSG(deviceID,"IDS_Valve_Presegment all Ready!!");
     PreSegmentReady(Module_IDS,ModuleDone);
  return OK; // all configured heaters are ready
 }

//********************************************************************************************************************
uint32_t IDSPreSegmentState(void *JobDetails, int SegmentId)
{
//set the speed only before the first segment, speed is constant accros job
    JobTicket* JobTicket = JobDetails;
    int Dispenser_i,n_dispensers,DispenserId;
    TimerMotors_t HW_Motor_Id;

    JobBrushStopId = 0;

    // activate control fr all motors
    /* wait for all dispensers  to get to the required pressure
     * move the presegment ready when all dispensers are ready.
     */
    if (JobTicket->n_segments == 0)
        return OK;
    if (SegmentId>=JobTicket->n_segments)
        {
            LOG_ERROR(SegmentId,"Error Segment");
            return ERROR;
        }

    REPORT_MSG(SegmentId,"IDSPreSegmentState");
    if (JobBrushStopId>=JobTicket->segments[SegmentId]->n_brushstops)
    {
        LOG_ERROR(JobBrushStopId,"Error JobBrushStopId");
        return ERROR;
    }
    if (( JobTicket->enableintersegment == true)&&(JobTicket->intersegmentlength>0))
    {
        Valve_Set(VALVE_MIXCHIP_WASTECH, Mixer_Waste); //if intersegment is defined throw the ink away
    }

    if (JobTicket->segments[SegmentId]->brushstops[JobBrushStopId]->n_dispensers)
    {
        n_dispensers = JobTicket->segments[SegmentId]->brushstops[JobBrushStopId]->n_dispensers;
        for (Dispenser_i = 0;Dispenser_i < n_dispensers;Dispenser_i++)
        {
            DispenserId = JobTicket->segments[SegmentId]->brushstops[JobBrushStopId]->dispensers[Dispenser_i]->index;
            HW_Motor_Id = DispenserIdToMotorId[DispenserId];
            if (MotorsCfg[HW_Motor_Id].hardwaremotortype != DispenserIdToMotorId[DispenserId])//unconfigured dispenser
            {
                REPORT_MSG(DispenserId,"Dispenser PreSegment not configured");
                continue;
            }
            DispenserPreSegmentReady[DispenserId] = false;
            REPORT_MSG(DispenserId,"IDS_Valve_Presegment start");
            IDS_Dispenser_Set_Flow_Params(DispenserId,0,0);
            if (JobTicket->segments[DispenserId]->brushstops[JobBrushStopId]->dispensers[Dispenser_i]->dispenserstepdivision != DISPENSER_STEP_DIVISION__Auto)
            {
                MotorSetMicroStep(HW_Motor_Id, JobTicket->segments[DispenserId]->brushstops[JobBrushStopId]->dispensers[Dispenser_i]->dispenserstepdivision);
            }
            else
            {
                MotorSetMicroStep(HW_Motor_Id, MotorsCfg[HW_Motor_Id].microstep);
            }

            if (JobTicket->segments[SegmentId]->brushstops[0]->dispensers[Dispenser_i]->nanolitterpersecond==0)
            {
                //MotorStop(HW_Motor_Id,Hard_Hiz);
                //Control3WayValvesWithCallback ((Valves_t)DispenserId, MidTank_Dispenser, IDS_Valve_PresegmentValveReady); //direction: MidTank_Dispenser or Dispenser_Mixer
                IDS_Dispenser_Close_Valve_And_Stop_Motor(DispenserId,IDS_Valve_PresegmentValveReady);
                REPORT_MSG(DispenserId,"Dispenser Not Used Next Segment");
            }
            else
            {
                if ((JobTicket->intersegmentlength)&&(SegmentId>0)) //there is an intersegment, stop all the dispensers. otherwise stop only dispensers that are not in use in the next segment.
                {
                    //Control3WayValvesWithCallback ((Valves_t)DispenserId, MidTank_Dispenser, IDS_Valve_PresegmentValveReady); //direction: MidTank_Dispenser or Dispenser_Mixer
                    IDS_Dispenser_Close_Valve_And_Stop_Motor(DispenserId,IDS_Valve_PresegmentValveReady);
                    REPORT_MSG(DispenserId,"Dispenser Used Next Segment with intersegment");
                }
                else
                {
                    IDS_Valve_PresegmentReady(DispenserId,0);
                }
                REPORT_MSG(DispenserId,"Dispenser is Used Next Segment");
            }
        }
    }

    return OK;
}
//********************************************************************************************************************

 uint32_t IDS_Valve_SegmentReady(uint32_t deviceID, uint32_t ReadValue)
 {
     int i;
     DispenserSegmentReady[deviceID] = true;
     for (i=0;i<MAX_SYSTEM_DISPENSERS;i++)
     {
         if (DispenserSegmentReady[i] == false)
         {
             return OK; //not all configured heaters are ready
         }
     }
     SegmentReady(Module_IDS,ModuleDone);
  return OK; // all configured heaters are ready
 }

 char IdsMessage[100];
//********************************************************************************************************************
 uint32_t IDSSegmentState(void *JobDetails, int SegmentId)
{
     JobTicket* JobTicket = JobDetails;
     int Dispenser_i,n_dispensers,DispenserId;
     TimerMotors_t HW_Motor_Id;
     double segmentfirst_speed;
     int CurrentSegment = SegmentId;

     if (CurrentSegment>=JobTicket->n_segments)
         {
             LOG_ERROR(CurrentSegment,"Error Segment");
             return ERROR;
         }
     if (JobBrushStopId>=JobTicket->segments[CurrentSegment]->n_brushstops)
     {
         LOG_ERROR(JobBrushStopId,"Error JobBrushStopId");
         return ERROR;
     }
     Valve_Set(VALVE_MIXCHIP_WASTECH, Mixer_Head);
     if (JobTicket->segments[CurrentSegment]->brushstops[JobBrushStopId]->n_dispensers)
     {
         n_dispensers = JobTicket->segments[CurrentSegment]->brushstops[JobBrushStopId]->n_dispensers;
         for (Dispenser_i = 0;Dispenser_i < n_dispensers;Dispenser_i++)
         {
             DispenserId = JobTicket->segments[CurrentSegment]->brushstops[JobBrushStopId]->dispensers[Dispenser_i]->index;
             HW_Motor_Id = DispenserIdToMotorId[DispenserId];
             if (MotorsCfg[HW_Motor_Id].hardwaremotortype != DispenserIdToMotorId[DispenserId])//unconfigured dispenser
                 continue;
             //(Speed*uStep*PPR)/((2*PI*Dispenser_Radius)
             segmentfirst_speed = JobTicket->segments[CurrentSegment]->brushstops[JobBrushStopId]->dispensers[Dispenser_i]->nanolitterpersecond/
                     JobTicket->segments[CurrentSegment]->brushstops[JobBrushStopId]->dispensers[Dispenser_i]->nanoliterperpulse;
             if (JobTicket->segments[CurrentSegment]->brushstops[JobBrushStopId]->dispensers[Dispenser_i]->dispenserstepdivision != DISPENSER_STEP_DIVISION__Auto)
             {
                 //MotorSetMicroStep(HW_Motor_Id, JobTicket->segments[CurrentSegment]->brushstops[JobBrushStopId]->dispensers[Dispenser_i]->dispenserstepdivision);
                 segmentfirst_speed/=JobTicket->segments[CurrentSegment]->brushstops[JobBrushStopId]->dispensers[Dispenser_i]->dispenserstepdivision;  //the dye supply is calculated based on a 1/8 microstep
                 IDS_Dispenser_Set_Flow_Params ( DispenserId, JobTicket->segments[CurrentSegment]->brushstops[JobBrushStopId]->dispensers[Dispenser_i]->nanoliterperpulse
                                                 ,  JobTicket->segments[CurrentSegment]->brushstops[JobBrushStopId]->dispensers[Dispenser_i]->dispenserstepdivision);
             }
             else
             {
                 //segmentfirst_speed/=MotorsCfg[HW_Motor_Id].microstep;  //the dye supply is calculated based on a 1/8 microstep
                 IDS_Dispenser_Set_Flow_Params ( DispenserId, JobTicket->segments[CurrentSegment]->brushstops[JobBrushStopId]->dispensers[Dispenser_i]->nanoliterperpulse
                                                 ,MotorsCfg[HW_Motor_Id].microstep);
             }
             if ((int)segmentfirst_speed > 0)
                 {
                     DispenserSegmentReady[DispenserId] = false;
                     //Control3WayValvesWithCallback (DispenserId, Dispenser_Mixer, NULL); //direction: MidTank_Dispenser or Dispenser_Mixer
                     IDS_Dispenser_Start_Motor_and_Open_Valve(DispenserId,segmentfirst_speed,NULL);
                     usnprintf(IdsMessage, 80, "Dispenser %d nl/sec %d nl/pulse %d speed %d",Dispenser_i,(int)JobTicket->segments[CurrentSegment]->brushstops[JobBrushStopId]->dispensers[Dispenser_i]->nanolitterpersecond,
                               (int)JobTicket->segments[CurrentSegment]->brushstops[JobBrushStopId]->dispensers[Dispenser_i]->nanoliterperpulse,(int)segmentfirst_speed);
                     //REPORT_MSG(segmentfirst_speed,IdsMessage);
                     Report(IdsMessage,__FILE__,__LINE__,Dispenser_i,RpWarning,segmentfirst_speed,0);
                     SendJobProgress(0.0,0,false, IdsMessage);


                 }
         }
     }

    return OK;
}
 //********************************************************************************************************************
 uint32_t IDS_Valve_DistanceToSpoolValveReady(uint32_t deviceID, uint32_t ReadValue)
 {
     //TimerMotors_t  HW_Motor_Id = DispenserIdToMotorId[deviceID];
     //MotorStop(HW_Motor_Id,Hard_Hiz);
     //REPORT_MSG(deviceID,"Dispenser DTS called");
     IDS_Valve_DistanceToSpoolReady( deviceID,  ReadValue);
     return OK;
}

 //********************************************************************************************************************
  uint32_t IDS_Valve_DistanceToSpoolReady(uint32_t deviceID, uint32_t ReadValue)
  {
      int i;
      DispenserDistanceToSpoolReady[deviceID] = true;
      for (i=0;i<MAX_SYSTEM_DISPENSERS;i++)
      {
          if (DispenserDistanceToSpoolReady[i] == false)
          {
              return OK; //not all configured heaters are ready
          }
      }
      DistanceToSpoolReady(Module_IDS,ModuleDone);
   return OK; // all configured heaters are ready
  }
 //********************************************************************************************************************
 uint32_t IDSDistanceToSpoolState(void)
 {
     int Dispenser_i;

     REPORT_MSG(100,"Dispenser DTS");
     Valve_Set(VALVE_MIXCHIP_WASTECH, Mixer_Waste);  //#bug 323
         for (Dispenser_i = 0;Dispenser_i < MAX_SYSTEM_DISPENSERS;Dispenser_i++)
         {
             if (DispenserUsedInJob[Dispenser_i]==false)//unconfigured dispenser
                 continue;
             DispenserDistanceToSpoolReady[Dispenser_i] = false;
             IDS_Dispenser_Set_Flow_Params(Dispenser_i,0,0);
             //MotorStop(HW_Motor_Id,Hard_Hiz);
             //Control3WayValvesWithCallback ((Valves_t)Dispenser_i, MidTank_Dispenser, IDS_Valve_DistanceToSpoolValveReady); //direction: MidTank_Dispenser or Dispenser_Mixer
             IDS_Dispenser_Close_Valve_And_Stop_Motor(Dispenser_i,IDS_Valve_DistanceToSpoolValveReady);
         }

     return OK;
 }
 //********************************************************************************************************************
 uint32_t IDS_Valve_EndValveReady(uint32_t deviceID, uint32_t ReadValue)
 {
     //TimerMotors_t  HW_Motor_Id = DispenserIdToMotorId[deviceID];
     //REPORT_MSG(deviceID,"Dispenser End called");
     //MotorStop(HW_Motor_Id,Hard_Hiz);
     return OK;
 }
//********************************************************************************************************************
 uint32_t IDSEndState(void *JobDetails)
{
     int Dispenser_i;
     IDS_Active = false;
     Valve_Set(VALVE_MIXCHIP_WASTECH, Mixer_Waste);
     REPORT_MSG(0,"Dispenser End Start");
     for ( Dispenser_i = 0;Dispenser_i < MAX_SYSTEM_DISPENSERS;Dispenser_i++)
     {
        //MotorStop(DispenserIdToMotorId[Dispenser_i],Hard_Hiz);
        //Control3WayValvesWithCallback (Dispenser_i, MidTank_Dispenser, IDS_Valve_EndValveReady); //direction: MidTank_Dispenser or Dispenser_Mixer
        IDS_Dispenser_Close_Valve_And_Stop_Motor(Dispenser_i,IDS_Valve_EndValveReady);
        IDS_Dispenser_Set_Flow_Params(Dispenser_i,0,0);

     }

    return OK;
}