
using namespace std;

class dosing
{
    public:
        dosing();
        void printDebug(short pinID);  // used to print info to terminal
        void compileDoseJson();  // compile data to be sent to front end
        void calculateDoses(short pinID);  // calculate number of doses and runtime
        void startupDosing();  // run at startup
        void ruleStartStirrer(short pinID, short interval, float doseML);  // this is called from custom rules
        void startStirrer(short pinID, short source, float doseML);  // this is called from rules and schedules and starts the dosing process
        void stopStirrer(short pinID);  // used to turn off stirrer before dosing
        void startDosing(short pinID);  // used to start dosing pump
        void stopDosing(short pinID);  // used to stop dosing pump
        void monitorDosingPumps();  // monitor the dosing pumps in main loop
        void manualDose(short pinID, std::string doseML);  // manual dose
        void manualDoseCalibrate(short pinID);  // manual dose when calibrating
        void refillReservoir(short pinID);  // refill reservoir
        // void saveDoseAmount(short pinID, short v);  // save dose amount
        void saveReservoirSize(short pinID, short v);  // save reservoir size
        void savePumpRate(short pinID, short v);  // save pump rate
        void saveStirrer(short pinID, short v);  // save stirrer status
        void saveCalibrationAmount(short pinID, short v);  // save calibration amount
        void saveDoseInterval(short pinID, short v);  // save minimum interval between doses
        void doseCalibrationDone(short pinID);  // update system that calibration is finished
        void saveDisableRes(short pinID, short v);  // save disable reservoir setting
        void moveDoseSpeedSlider(short pinID, short v);  // adjust dosing pump speed while moving slider
        void stopDoseSpeedSlider(short pinID, short v);  // save dosing pump speed after moving slider
        void saveResEmail(short pinID, short v);  // save reservoir email alert status
        void saveResAlert(short pinID, short v);  // save reservoir dashboard alert status
        void saveResAlertML(short pinID, short v);  // save reservoir alert level
        void saveStirrerRuntime(short pinID, short v);  // save stirrer runtime
        void saveSettlingRuntime(short pinID, short v);  // save settling runtime
        void saveStirrerPort(short stirID, short doserPinID);  // save stirrer port attached to dosing pump
        void moveStirrerSpeedSlider(short pinID, short v);  // adjust stirrer speed while moving slider
        void stopStirrerSpeedSlider(short pinID, short v);  // save stirrer speed after moving slider
    private:
        short dosePinID[20] = {};  // unique pinID from pinsAvailable table
        short dosePin[20] = {};  // gpio pin number
        short doseDriverID[20] = {};  // driverID
        float doseAmount[20] = {};  // stores dose amount in ml
        short doseReservoirSize[20] = {};  // stores reservoir size in ml
        float doseReservoirRemain[20] = {};  // stores remaining space in reservoir in ml
        short dosePumpRate[20] = {};  // stores pump rate in ms/ml
        short doseCalibrateAmount[20] = {};  // tracks dose amount used for calibrating vessel
        unsigned int doseRunTime[20] = {};  // stores how long pump should run in millis
        short doseStepRunning[20] = {};  // tracks which step the system is in during the dosing process
        short doseCalibrateRunning[20] = {};  // tracks is calibration is running
        short doseInterval[20] = {};  // track minimum interval between doses
		unsigned long long* prevMillisDosing = new unsigned long long[20];  // tracks millis when pump started running
		unsigned long long* delayBetweenDosesMillis = new unsigned long long[20];  // track how long until next dose is allowed
        short totalDosers = 0;  // total dosing ports found
        short tempDoseInterval = 0;  // track if a pump was started from custom rule
        short doseDisableRes[20] = {};  // track if reservoir is disabled
        short dosePumpSpeed[20] = {};  // track speed of dosing pump
        short doseResEmail[20] = {};  // track if reservoir email alert is enabled
        short doseResAlert[20] = {};  // track if reservoir dashboard alert is enabled
        short doseResAlertML[20] = {};  // track level to be alert on low reservoir
        std::string doseName[20] = {};  // track dosing pump name for emails
        short doseStirrer[20] = {};  // stores if stirrer is enabled
        short doseStirrerID[20] = {};  // get stirrerID attached to doser so we can match up
        short stirrerID[20] = {};  // track stirrerID
        short stirrerPinID[20] = {};  // unique pinID from pinsAvailable table
        short stirrerDriverID[20] = {};  // driverID
        short stirrerPin[20] = {};  // gpio pin number
        short stirrerRuntime[20] = {};  // track stirrer runtime
        short settlingRuntime[20] = {};  // track settling runtime
        short stirrerSpeed[20] = {};  // track stirrer speed
        short totalStirrers = 0;  // total dosing ports found
        short debug = 0;  // used to print info to terminal
};
dosing::dosing(){}

void dosing::compileDoseJson() {  // compile data to be sent to front end
    for(short a=0; a < totalDosers; a++) {  // get pinID and status of each dosing port
        outgoingDataString += "{\"id\":\"";
        outgoingDataString += to_string(dosePinID[a]);

        outgoingDataString += "\",\"v\":\"";        
        outgoingDataString += to_string(doseStepRunning[a]);

        outgoingDataString += "\",\"d\":\"";        
        outgoingDataString += to_string(doseReservoirRemain[a]);

        outgoingDataString += "\"}";
        if (a < totalDosers - 1) outgoingDataString += ",";  // add comma if another sensor is coming
    }
}

void dosing::printDebug(short pinID) {  // debug dosing pumps
    if (debug == 1) {
        short arrayLoc = 0;
        for(short a=0; a < totalDosers; a++) {  // search through all dosing pumps to find match
            if (dosePinID[a] == pinID) {arrayLoc = a; break;}  // get array location
        }
        std::cout << "pinID                = " << pinID << std::endl;
        
        std::cout << "doseAmount           = " << doseAmount[arrayLoc] << std::endl;
        std::cout << "doseReservoirSize    = " << doseReservoirSize[arrayLoc] << std::endl;
        std::cout << "doseReservoirRemain  = " << doseReservoirRemain[arrayLoc] << std::endl;
        std::cout << "dosePumpRate         = " << dosePumpRate[arrayLoc] << std::endl;
        std::cout << "doseStirrer          = " << doseStirrer[arrayLoc] << std::endl;
        std::cout << "doseRunTime          = " << doseRunTime[arrayLoc] << std::endl;
        std::cout << "doseCalibrateRunning = " << doseCalibrateRunning[arrayLoc] << std::endl; 
        std::cout << "" << std::endl;   
    }
}

void dosing::calculateDoses(short pinID) {  // calculate number of doses and runtime
    short arrayLoc = 0;
    for(short a=0; a < totalDosers; a++) {  // search through all dosing pumps to find match
        if (dosePinID[a] == pinID) {arrayLoc = a; break;}  // get array location
    }

    doseRunTime[arrayLoc] = dosePumpRate[arrayLoc] * doseAmount[arrayLoc];
    // dosesRemaining[arrayLoc] = (doseReservoirRemain[arrayLoc] / doseAmount[arrayLoc]);
}

void dosing::startupDosing() {  // run at startup
	if (startupComplete == 0) {
        std::cout << " " << std::endl;
        std::cout << "startupDosing------------------------------" << std::endl;
    }

    std::fill_n(dosePinID, 20, 0);  // reset array
    std::fill_n(doseDriverID, 20, 0);  // reset array
    std::fill_n(dosePin, 20, 0);  // reset array
    std::fill_n(doseAmount, 20, 0);  // reset array
    std::fill_n(doseReservoirRemain, 20, 0);  // reset array
    std::fill_n(doseReservoirSize, 20, 0);  // reset array
    std::fill_n(dosePumpRate, 20, 0);  // reset array
    std::fill_n(doseResAlertML, 20, 0);  // reset array
    std::fill_n(doseCalibrateAmount, 20, 0);  // reset array
    std::fill_n(doseRunTime, 20, 0);  // reset array
    std::fill_n(doseCalibrateRunning, 20, 0);  // reset array
    std::fill_n(doseInterval, 20, 0);  // reset array
    std::fill_n(doseDisableRes, 20, 0);  // reset array
    std::fill_n(dosePumpSpeed, 20, 0);  // reset array
    std::fill_n(doseResEmail, 20, 0);  // reset array
    std::fill_n(doseResAlert, 20, 0);  // reset array
    std::fill_n(doseResAlertML, 20, 0);  // reset array
    std::fill_n(doseStirrer, 20, 0);  // reset array
    std::fill_n(doseStirrerID, 20, 0);  // reset array
    std::fill_n(stirrerID, 20, 0);  // reset array
    std::fill_n(stirrerPinID, 20, 0);  // reset array
    std::fill_n(stirrerDriverID, 20, 0);  // reset array
    std::fill_n(stirrerPin, 20, 0);  // reset array
    std::fill_n(stirrerRuntime, 20, 0);  // reset array
    std::fill_n(settlingRuntime, 20, 0);  // reset array
    std::fill_n(stirrerSpeed, 20, 0);  // reset array


    MYSQL_RES *res;
    MYSQL_ROW row;

    connectMysql();
    
    // search database for pins defined as a dosing port and enabled
    std::string query = "SELECT pinsAvailable.pinID, pinsAvailable.driverID, pinsAvailable.gpio,\
        dosingPumps.reservoirRemain, dosingPumps.reservoirSize, dosingPumps.dosePumpRate, dosingPumps.doseStirrer, dosingPumps.calibrateAmount, dosingPumps.doseInterval, dosingPumps.disableRes, dosingPumps.pumpSpeed, dosingPumps.resEmail, dosingPumps.resAlert, dosingPumps.doseName, dosingPumps.resAlertML, dosingPumps.stirrerRuntime, dosingPumps.settlingRuntime, dosingPumps.doseStirrerID, dosingPumps.stirrerSpeed\
        FROM pinsAvailable\
        LEFT JOIN dosingPumps\
        ON pinsAvailable.pinID = dosingPumps.dosePinID\
        WHERE pinsAvailable.pinTypeID = 12 AND pinsAvailable.assigned = 1\
        ORDER BY dosingPumps.doseID ASC";

    mysql_query(conn, query.c_str());  // run query
    res = mysql_use_result(conn);

    totalDosers = 0;
    while ((row = mysql_fetch_row(res)) != NULL)
    {
        dosePinID[totalDosers] = atoi(row[0]);
        doseDriverID[totalDosers] = atoi(row[1]);
        dosePin[totalDosers] = atoi(row[2]);
        doseAmount[totalDosers] = 0;
        doseReservoirRemain[totalDosers] = atof(row[3]);
        doseReservoirSize[totalDosers] = atoi(row[4]);
        dosePumpRate[totalDosers] = atoi(row[5]);
        doseStirrer[totalDosers] = atoi(row[6]);
        doseCalibrateAmount[totalDosers] = atoi(row[7]);
        doseInterval[totalDosers] = atoi(row[8]);
        doseDisableRes[totalDosers] = atoi(row[9]);
        dosePumpSpeed[totalDosers] = atoi(row[10]);
        doseResEmail[totalDosers] = atoi(row[11]);
        doseResAlert[totalDosers] = atoi(row[12]);
        doseName[totalDosers] = row[13];
        doseResAlertML[totalDosers] = atoi(row[14]);
        stirrerRuntime[totalDosers] = atoi(row[15]);
        settlingRuntime[totalDosers] = atoi(row[16]);
        doseStirrerID[totalDosers] = atoi(row[17]);
        stirrerSpeed[totalDosers] = atoi(row[18]);
        delayBetweenDosesMillis[totalDosers] = ((doseInterval[totalDosers] * 60) * 1000) + currentMillisTime;  // no dosing until dose interval has passed
        doseRunTime[totalDosers] = 0;
        doseStepRunning[totalDosers] = 0;
        doseCalibrateRunning[totalDosers] = 0;
        prevMillisDosing[totalDosers] = 0;

        if (doseDriverID[totalDosers] == 2) {  // if dosing port is attached to Pi set pin as an output
	        pinMode(dosePin[totalDosers],OUTPUT);
        }

        totalDosers++;  // count number of dosing pumps
        calculateDoses(dosePinID[totalDosers-1]);
    }
    mysql_free_result(res);
    mysql_disconnect();
    
    connectMysql();
    
    // search database for pins defined as a dosing port and enabled
    query = "SELECT pinsAvailable.driverID, pinsAvailable.gpio, stirrers.stirrerPinID, stirrers.stirrerID, dosingPumps.dosePinID, dosingPumps.doseStirrerID\
        FROM pinsAvailable\
        LEFT JOIN stirrers\
        ON pinsAvailable.pinID = stirrers.stirrerPinID\
        LEFT JOIN dosingPumps\
        ON dosingPumps.doseStirrerID = stirrers.stirrerID\
        WHERE pinsAvailable.pinTypeID = 15 AND pinsAvailable.assigned = 1 AND doseStirrerID IS NOT NULL\
        ORDER BY dosingPumps.doseID ASC";

    mysql_query(conn, query.c_str());  // run query
    res = mysql_use_result(conn);

    totalStirrers = 0;
    while ((row = mysql_fetch_row(res)) != NULL)
    {
        stirrerDriverID[totalStirrers] = atoi(row[0]);
        stirrerPin[totalStirrers] = atoi(row[1]);
        stirrerPinID[totalStirrers] = atoi(row[2]);
        stirrerID[totalStirrers] = atoi(row[3]);

        if (stirrerDriverID[totalStirrers] == 2) {  // if dosing port is attached to Pi set pin as an output
	        pinMode(stirrerPin[totalStirrers],OUTPUT);
        }

        totalStirrers++;  // count number of dosing pumps
    }
    mysql_free_result(res);
    mysql_disconnect();

    if (startupComplete == 0) {  // force pumps off when program starts
        for(short a=0; a < totalDosers; a++) 
        {
            if (doseDriverID[a] == 2) {digitalWrite(dosePin[a],0);}  // driver is Raspberry Pi
            else {pca9685.adjustPinLevel(dosePinID[a], 0);}  // other pca9685 driver
        }
        for(short a=0; a < totalStirrers; a++) 
        {
            if (stirrerDriverID[a] == 2) {digitalWrite(stirrerPin[a],0);}  // driver is Raspberry Pi
            else {pca9685.adjustPinLevel(stirrerPinID[a], 0);}  // other pca9685 driver
        }
    }

	if (startupComplete == 0) {
        std::cout << "dosePinID------------ ";
        for(short a=0; a < totalDosers; a++) {std::cout << dosePinID[a] << ", ";}
        std::cout << "" << std::endl;
        
        std::cout << "doseDriverID--------- ";
        for(short a=0; a < totalDosers; a++) {std::cout << doseDriverID[a] << ", ";}
        std::cout << "" << std::endl;
        
        std::cout << "dosePin-------------- ";
        for(short a=0; a < totalDosers; a++) {std::cout << dosePin[a] << ", ";}
        std::cout << "" << std::endl;
        
        std::cout << "doseAmount----------- ";
        for(short a=0; a < totalDosers; a++) {std::cout << doseAmount[a] << ", ";}
        std::cout << "" << std::endl;
        
        std::cout << "doseReservoirRemain-- ";
        for(short a=0; a < totalDosers; a++) {std::cout << doseReservoirRemain[a] << ", ";}
        std::cout << "" << std::endl;
        
        std::cout << "doseReservoirSize---- ";
        for(short a=0; a < totalDosers; a++) {std::cout << doseReservoirSize[a] << ", ";}
        std::cout << "" << std::endl;
        
        std::cout << "dosePumpRate--------- ";
        for(short a=0; a < totalDosers; a++) {std::cout << dosePumpRate[a] << ", ";}
        std::cout << "" << std::endl;
        
        std::cout << "doseStirrer---------- ";
        for(short a=0; a < totalDosers; a++) {std::cout << doseStirrer[a] << ", ";}
        std::cout << "" << std::endl;
                
        std::cout << "stirrerRuntime------- ";
        for(short a=0; a < totalDosers; a++) {std::cout << stirrerRuntime[a] << ", ";}
        std::cout << "" << std::endl;
        
        std::cout << "settlingRuntime------ ";
        for(short a=0; a < totalDosers; a++) {std::cout << settlingRuntime[a] << ", ";}
        std::cout << "" << std::endl;
        
        std::cout << "stirrerSpeed--------- ";
        for(short a=0; a < totalDosers; a++) {std::cout << stirrerSpeed[a] << ", ";}
        std::cout << "" << std::endl;
        
        std::cout << "doseCalibrateAmount-- ";
        for(short a=0; a < totalDosers; a++) {std::cout << doseCalibrateAmount[a] << ", ";}
        std::cout << "" << std::endl;
        
        std::cout << "doseRunTime---------- ";
        for(short a=0; a < totalDosers; a++) {std::cout << doseRunTime[a] << ", ";}
        std::cout << "" << std::endl;
        
        std::cout << "doseInterval--------- ";
        for(short a=0; a < totalDosers; a++) {std::cout << doseInterval[a] << ", ";}
        std::cout << "" << std::endl;
        
        std::cout << "doseDisableRes------- ";
        for(short a=0; a < totalDosers; a++) {std::cout << doseDisableRes[a] << ", ";}
        std::cout << "" << std::endl;
        
        std::cout << "dosePumpSpeed-------- ";
        for(short a=0; a < totalDosers; a++) {std::cout << dosePumpSpeed[a] << ", ";}
        std::cout << "" << std::endl;
        
        std::cout << "doseResEmail--------- ";
        for(short a=0; a < totalDosers; a++) {std::cout << doseResEmail[a] << ", ";}
        std::cout << "" << std::endl;
        
        std::cout << "doseResAlert--------- ";
        for(short a=0; a < totalDosers; a++) {std::cout << doseResAlert[a] << ", ";}
        std::cout << "" << std::endl;
        
        std::cout << "doseName------------- ";
        for(short a=0; a < totalDosers; a++) {std::cout << doseName[a] << ", ";}
        std::cout << "" << std::endl;
        
        std::cout << "doseResAlertML------- ";
        for(short a=0; a < totalDosers; a++) {std::cout << doseResAlertML[a] << ", ";}
        std::cout << "" << std::endl;

        std::cout << "totalDosers------------ " << totalDosers << std::endl;
        std::cout << "----------------------------------------" << std::endl;
        
        std::cout << "stirrerPinID------------ ";
        for(short a=0; a < totalStirrers; a++) {std::cout << stirrerPinID[a] << ", ";}
        std::cout << "" << std::endl;
        
        std::cout << "stirrerDriverID--------- ";
        for(short a=0; a < totalStirrers; a++) {std::cout << stirrerDriverID[a] << ", ";}
        std::cout << "" << std::endl;
        
        std::cout << "stirrerPin-------------- ";
        for(short a=0; a < totalStirrers; a++) {std::cout << stirrerPin[a] << ", ";}
        std::cout << "" << std::endl;
        
        std::cout << "stirrerID--------------- ";
        for(short a=0; a < totalStirrers; a++) {std::cout << stirrerID[a] << ", ";}
        std::cout << "" << std::endl;
        
        std::cout << "totalStirrers------------ " << totalStirrers << std::endl;
        std::cout << "----------------------------------------" << std::endl;
    }
}

void dosing::ruleStartStirrer(short pinID, short interval, float doseML) {  // this is called from custom rules
    short arrayLoc = 0;
    for(short a=0; a < totalDosers; a++) {  // search through all dosing pumps to find match
        if (dosePinID[a] == pinID) {arrayLoc = a; break;}  // get array location
    }

    short source = 4;  // custom rule
    if (interval != doseInterval[arrayLoc]) {tempDoseInterval = interval;}  // if interval was added in rule capture in temporary variable to use set interval
    else {source = 3;}  // if no interval was added change source to schedule so we can use default minimum interval between doses
    startStirrer(pinID,source,doseML);  // start dosing process
}

void dosing::startStirrer(short pinID, short source, float doseML) {  // this starts the dosing process
    // SOURCE    
    //    1 - manual dose
    //    3 - schedule
    //    4 - custom rule


    short arrayLoc = 0;
    for(short a=0; a < totalDosers; a++) {  // search through all dosing pumps to find match
        if (dosePinID[a] == pinID) {arrayLoc = a; break;}  // get array location
    }
    // std::cout << "startStirrer------------------------------" << stirrerPinID[arrayLoc] << std::endl;
    // std::cout << "doseML      ------------------------------" << doseML << std::endl;
    
    doseAmount[arrayLoc] = doseML;  // get dose amount to be used

    if (((doseDisableRes[arrayLoc] == 0) && (doseReservoirRemain[arrayLoc] >= doseML)) || (doseDisableRes[arrayLoc] == 1))
    {
        if (doseML != 0)
        {
            if ((source == 1) || (currentMillisTime >= delayBetweenDosesMillis[arrayLoc]))  // if switching delay has passed or manual dose
            {
                doseReservoirRemain[arrayLoc] = doseReservoirRemain[arrayLoc] - doseAmount[arrayLoc];  // update reservior
                calculateDoses(dosePinID[arrayLoc]);

                if (doseStirrer[arrayLoc] == 1)  // if stirrer is enabled
                {
                    if (stirrerDriverID[arrayLoc] == 2) {digitalWrite(stirrerPin[arrayLoc],1);}  // driver is Raspberry Pi
                    else {pca9685.adjustPinLevel(stirrerPinID[arrayLoc], stirrerSpeed[arrayLoc]);}  // other pca9685 driver
                    // std::cout << "stirrerRuntime------------------------------" << stirrerRuntime[arrayLoc] << std::endl;

                    prevMillisDosing[arrayLoc] = currentMillisTime;
                    doseStepRunning[arrayLoc] = 1;  // stirrer running
                }
                else {startDosing(pinID);}  // if no stirrer

                if (source == 4) {delayBetweenDosesMillis[arrayLoc] = ((tempDoseInterval * 60) * 1000) + currentMillisTime;}  // if dose was started from custom rule reset minimum interval between doses
                else if (source == 3) {delayBetweenDosesMillis[arrayLoc] = ((doseInterval[arrayLoc] * 60) * 1000) + currentMillisTime;}  // if not manual dose reset minimum interval between doses
            }
        }
    }
    
    if ((doseReservoirRemain[arrayLoc] <= doseResAlertML[arrayLoc]) && (doseResEmail[arrayLoc] == 1)) {  // send email if reservoir is low
       	emails.sendEmail("Dosing pump reservoir for " + doseName[arrayLoc] + " is getting low.", "Message from Robo-Tank Aquarium Controller");
    }
}

void dosing::stopStirrer(short pinID) {  // used to turn off stirrer before dosing
    short arrayLoc = 0;
    for(short a=0; a < totalDosers; a++) {  // search through all dosing pumps to find match
        if (dosePinID[a] == pinID) {arrayLoc = a; break;}  // get array location
    }
    // std::cout << "stopStirrer------------------------------" << stirrerPinID[arrayLoc] << std::endl;

    if (stirrerDriverID[arrayLoc] == 2) {digitalWrite(stirrerPin[arrayLoc],0);}  // driver is Raspberry Pi
    else {pca9685.adjustPinLevel(stirrerPinID[arrayLoc], 0);}  // other pca9685 driver
    
    prevMillisDosing[arrayLoc] = currentMillisTime;
    doseStepRunning[arrayLoc] = 2;  // stirrer settling
}

void dosing::startDosing(short pinID) {  // used to start dosing pump
    short arrayLoc = 0;
    for(short a=0; a < totalDosers; a++) {  // search through all dosing pumps to find match
        if (dosePinID[a] == pinID) {arrayLoc = a; break;}  // get array location
    }
    // std::cout << "startDosing------------------------------" << arrayLoc << std::endl;
    // std::cout << "doseAmount[arrayLoc]---------------------" << doseAmount[arrayLoc] << std::endl;

    if (doseCalibrateRunning[arrayLoc] == 0) {  // update reservoir remaining if not calibrating
        // std::cout << "Save Dosing------------------------------" << arrayLoc << std::endl;

        connectMysql();
        std::string query = "UPDATE dosingPumps SET reservoirRemain = " + to_string(doseReservoirRemain[arrayLoc]) + " WHERE dosePinID = '" + to_string(dosePinID[arrayLoc]) + "'";  // update database that sensor is unplugged
        mysql_query(conn, query.c_str());  // run query
        mysql_disconnect();
    }
    
    if (doseDriverID[arrayLoc] == 2) {digitalWrite(dosePin[arrayLoc],1);}  // driver is Raspberry Pi
    else {pca9685.adjustPinLevel(pinID, dosePumpSpeed[arrayLoc]);}  // other pca9685 driver

    prevMillisDosing[arrayLoc] = currentMillisTime;
    doseStepRunning[arrayLoc] = 3;  // pump running
}

void dosing::stopDosing(short pinID) {  // used to stop dosing pump
    short arrayLoc = 0;
    for(short a=0; a < totalDosers; a++) {  // search through all dosing pumps to find match
        if (dosePinID[a] == pinID) {arrayLoc = a; break;}  // get array location
    }
    // std::cout << "stopDosing------------------------------" << arrayLoc << std::endl;
    
    if (doseDriverID[arrayLoc] == 2) {digitalWrite(dosePin[arrayLoc],0);}  // driver is Raspberry Pi
    else {pca9685.adjustPinLevel(pinID, 0);}  // other pca9685 driver

    doseStepRunning[arrayLoc] = 0;  // dosing finsihed
}

void dosing::monitorDosingPumps() {  // monitor the dosing pumps in main loop
    for (short a=0; a < totalDosers; a++)  // monitor each dosing pump
    {
        // std::cout << a << " - " << (currentMillisTime - prevMillisDosing[0]) << std::endl;
        // std::cout << a << " - " << stirrerRuntime[0] * 1000 << std::endl;
        // std::cout << "doseStepRunning[a] - " << doseStepRunning[0] << std::endl;
        if ((doseStepRunning[a] == 1) && (currentMillisTime - prevMillisDosing[a] > (stirrerRuntime[a] * 1000))) {stopStirrer(dosePinID[a]);}  // stop stirrer
        else if ((doseStepRunning[a] == 2) && (currentMillisTime - prevMillisDosing[a] > (settlingRuntime[a] * 1000))) {startDosing(dosePinID[a]);}  // start pump
        else if ((doseStepRunning[a] == 3) && (currentMillisTime - prevMillisDosing[a] > doseRunTime[a])) {stopDosing(dosePinID[a]);}  // stop pump
    }
}

void dosing::manualDose(short pinID, std::string doseML) {  // manual dose
    short arrayLoc = 0;
    for(short a=0; a < totalDosers; a++) {  // search through all dosing pumps to find match
        if (dosePinID[a] == pinID) {arrayLoc = a; break;}  // get array location
    }
    // std::cout << "manualDose = " << pinID << std::endl;
    
    systemSettings.logActivity(1,3,dosePinID[arrayLoc],1,"");  // log action
    startStirrer(pinID, 1, std::stof(doseML));
    printDebug(pinID);
}

void dosing::manualDoseCalibrate(short pinID) {  // manual dose when calibrating
    short arrayLoc = 0;
    for(short a=0; a < totalDosers; a++) {  // search through all dosing pumps to find match
        if (dosePinID[a] == pinID) {arrayLoc = a; break;}  // get array location
    }
    // std::cout << "manualDoseCalibrate = " << arrayLoc << std::endl;

    doseCalibrateRunning[arrayLoc] = 1;  // mark pump as being calibrated
    doseRunTime[arrayLoc] = dosePumpRate[arrayLoc] * doseCalibrateAmount[arrayLoc];  // update runtime with calibration amount
    // startDosing(pinID);
    startStirrer(pinID, 1, doseCalibrateAmount[arrayLoc]);
    printDebug(pinID);
}

void dosing::refillReservoir(short pinID) {  // refill reservoir
    printDebug(pinID);
    short arrayLoc = 0;
    for(short a=0; a < totalDosers; a++) {  // search through all dosing pumps to find match
        if (dosePinID[a] == pinID) {arrayLoc = a; break;}  // get array location
    }
    // std::cout << "refillReservoir = " << arrayLoc << std::endl;

    doseReservoirRemain[arrayLoc] = doseReservoirSize[arrayLoc];  // refill
    calculateDoses(pinID);

    connectMysql();
    std::string query = "UPDATE dosingPumps SET reservoirRemain = '" + to_string(doseReservoirSize[arrayLoc]) + "' WHERE dosePinID = '" + to_string(pinID) + "'";
    mysql_query(conn, query.c_str()); // run query
    mysql_disconnect(); 
    printDebug(pinID);
}

void dosing::saveDisableRes(short pinID, short v) {  // save disable reservoir setting
    printDebug(pinID);
    short arrayLoc = 0;
    for(short a=0; a < totalDosers; a++) {  // search through all dosing pumps to find match
        if (dosePinID[a] == pinID) {arrayLoc = a; break;}  // get array location
    }
    // std::cout << "savePumpRate = " << arrayLoc << std::endl;

    doseDisableRes[arrayLoc] = v;  // update

    connectMysql();
    std::string query = "UPDATE dosingPumps SET disableRes = '" + to_string(v) + "' WHERE dosePinID = '" + to_string(pinID) + "'";
    mysql_query(conn, query.c_str()); // run query
    mysql_disconnect(); 
    printDebug(pinID);
}

void dosing::saveResAlertML(short pinID, short v) {  // save reservoir alert level
    printDebug(pinID);
    short arrayLoc = 0;
    for(short a=0; a < totalDosers; a++) {  // search through all dosing pumps to find match
        if (dosePinID[a] == pinID) {arrayLoc = a; break;}  // get array location
    }
    // std::cout << "saveResAlertML = " << arrayLoc << std::endl;

    doseResAlertML[arrayLoc] = v;  // update

    connectMysql();
    std::string query = "UPDATE dosingPumps SET resAlertML = '" + to_string(v) + "' WHERE dosePinID = '" + to_string(pinID) + "'";
    mysql_query(conn, query.c_str()); // run query
    mysql_disconnect(); 
    printDebug(pinID);
}

void dosing::moveDoseSpeedSlider(short pinID, short v) {  // adjust dosing pump speed while moving slider
    // std::cout << "mode  - " << mode << std::endl;
    // std::cout << "pin   - " << pin << std::endl;
    // std::cout << "value - " << value << std::endl;
    // std::cout << "moveDoseSpeedSlider  - " << endl;
	short arrayLoc = 0;
	for (short a=0; a < totalDosers; a++)  // find channel number based on pin number
	{
		if (dosePinID[a] == pinID) {break;}
		else {arrayLoc++;}
	}
		
	dosePumpSpeed[arrayLoc] = (4095 * (v * .01));  // convert percentage to pwm value 0-4095

    // std::cout << "value - " << value << std::endl;
    if (doseDriverID[arrayLoc] == 2) {digitalWrite(dosePin[arrayLoc],1);}  // driver is Raspberry Pi
    else {pca9685.adjustPinLevel(pinID, dosePumpSpeed[arrayLoc]);}  // other pca9685 driver
}

void dosing::stopDoseSpeedSlider(short pinID, short v) {  // save dosing pump speed after moving slider
    // std::cout << "stopDoseSpeedSlider  - " << endl;
	short arrayLoc = 0;
	for (short a=0; a < totalDosers; a++)  // find channel number based on pin number
	{
		if (dosePinID[a] == pinID) {break;}
		else {arrayLoc++;}
	}

    if (doseDriverID[arrayLoc] == 2) {digitalWrite(dosePin[arrayLoc],0);}  // driver is Raspberry Pi
    else {pca9685.adjustPinLevel(pinID, 0);}  // other pca9685 driver
    
	dosePumpSpeed[arrayLoc] = (4095 * (v * .01));  // convert percentage to pwm value 0-4095

    connectMysql();
    std::string query = "UPDATE dosingPumps SET pumpSpeed = '" + to_string(dosePumpSpeed[arrayLoc]) + "' WHERE dosePinID = '" + to_string(pinID) + "'";
    mysql_query(conn, query.c_str());  // run query
    mysql_disconnect();
}

void dosing::moveStirrerSpeedSlider(short pinID, short v) {  // adjust stirrer speed while moving slider
    // std::cout << "mode  - " << mode << std::endl;
    // std::cout << "pin   - " << pin << std::endl;
    // std::cout << "value - " << value << std::endl;
    // std::cout << "moveStirrerSpeedSlider  - " << endl;
	short arrayLoc = 0;
	for (short a=0; a < totalDosers; a++)  // find channel number based on pin number
	{
		if (stirrerPinID[a] == pinID) {break;}
		else {arrayLoc++;}
	}

    stirrerSpeed[arrayLoc] = (4095 * (v * .01));  // convert percentage to pwm value 0-4095

    // std::cout << "value - " << value << std::endl;
    if (stirrerDriverID[arrayLoc] == 2) {digitalWrite(stirrerPin[arrayLoc],1);}  // driver is Raspberry Pi
    else {pca9685.adjustPinLevel(pinID, stirrerSpeed[arrayLoc]);}  // other pca9685 driver
}

void dosing::stopStirrerSpeedSlider(short pinID, short v) {  // save stirrer speed after moving slider
    // std::cout << "stopStirrerSpeedSlider  - " << endl;
	short arrayLoc = 0;
	for (short a=0; a < totalDosers; a++)  // find channel number based on pin number
	{
		if (stirrerPinID[a] == pinID) {break;}
		else {arrayLoc++;}
	}

    if (stirrerDriverID[arrayLoc] == 2) {digitalWrite(stirrerPin[arrayLoc],0);}  // driver is Raspberry Pi
    else {pca9685.adjustPinLevel(pinID, 0);}  // other pca9685 driver
    
	stirrerSpeed[arrayLoc] = (4095 * (v * .01));  // convert percentage to pwm value 0-4095

    connectMysql();
    std::string query = "UPDATE dosingPumps SET stirrerSpeed = '" + to_string(stirrerSpeed[arrayLoc]) + "' WHERE doseStirrerID = '" + to_string(stirrerID[arrayLoc]) + "'";
    mysql_query(conn, query.c_str());  // run query
    mysql_disconnect();
}

void dosing::saveReservoirSize(short pinID, short v) {  // save reservoir size
    printDebug(pinID);

    short arrayLoc = 0;
    for(short a=0; a < totalDosers; a++) {  // search through all dosing pumps to find match
        if (dosePinID[a] == pinID) {arrayLoc = a; break;}  // get array location
    }
    // std::cout << "saveReservoirSize = " << arrayLoc << std::endl;

    doseReservoirSize[arrayLoc] = v;  // update
    calculateDoses(pinID);

    connectMysql();
    std::string query = "UPDATE dosingPumps SET reservoirSize = '" + to_string(v) + "' WHERE dosePinID = '" + to_string(pinID) + "'";
    mysql_query(conn, query.c_str()); // run query
    mysql_disconnect(); 
    printDebug(pinID);
}

void dosing::savePumpRate(short pinID, short v) {  // save pump rate
    printDebug(pinID);
    short arrayLoc = 0;
    for(short a=0; a < totalDosers; a++) {  // search through all dosing pumps to find match
        if (dosePinID[a] == pinID) {arrayLoc = a; break;}  // get array location
    }
    // std::cout << "savePumpRate = " << arrayLoc << std::endl;

    dosePumpRate[arrayLoc] = v;  // update
    calculateDoses(pinID);

    connectMysql();
    std::string query = "UPDATE dosingPumps SET dosePumpRate = '" + to_string(v) + "' WHERE dosePinID = '" + to_string(pinID) + "'";
    mysql_query(conn, query.c_str()); // run query
    mysql_disconnect(); 
    printDebug(pinID);
}

void dosing::saveStirrer(short pinID, short v) {  // save stirrer status
    printDebug(pinID);
    short arrayLoc = 0;
    for(short a=0; a < totalDosers; a++) {  // search through all dosing pumps to find match
        if (dosePinID[a] == pinID) {arrayLoc = a; break;}  // get array location
    }
    // std::cout << "saveStirrer = " << arrayLoc << std::endl;

    doseStirrer[arrayLoc] = v;  // update

    connectMysql();
    std::string query = "UPDATE dosingPumps SET doseStirrer = '" + to_string(v) + "' WHERE dosePinID = '" + to_string(pinID) + "'";
    mysql_query(conn, query.c_str()); // run query
    mysql_disconnect(); 

    if (doseStirrer[arrayLoc] == 0) 
    {
        connectMysql();
        std::string query = "UPDATE dosingPumps SET doseStirrerID = 0 WHERE dosePinID = '" + to_string(pinID) + "'";
        mysql_query(conn, query.c_str()); // run query
        mysql_disconnect();
    }

    printDebug(pinID);
}

void dosing::saveCalibrationAmount(short pinID, short v) {  // save calibration amount
    printDebug(pinID);
    short arrayLoc = 0;
    for(short a=0; a < totalDosers; a++) {  // search through all dosing pumps to find match
        if (dosePinID[a] == pinID) {arrayLoc = a; break;}  // get array location
    }
    // std::cout << "saveCalibrationAmount = " << arrayLoc << std::endl;

    doseCalibrateAmount[arrayLoc] = v;  // update

    connectMysql();
    std::string query = "UPDATE dosingPumps SET calibrateAmount = '" + to_string(v) + "' WHERE dosePinID = '" + to_string(pinID) + "'";
    mysql_query(conn, query.c_str()); // run query
    mysql_disconnect(); 
    printDebug(pinID);
}

void dosing::saveDoseInterval(short pinID, short v) {  // save minimum interval between doses
    printDebug(pinID);
    short arrayLoc = 0;
    for(short a=0; a < totalDosers; a++) {  // search through all dosing pumps to find match
        if (dosePinID[a] == pinID) {arrayLoc = a; break;}  // get array location
    }
    // std::cout << "saveDoseInterval = " << arrayLoc << std::endl;

    doseInterval[arrayLoc] = v;  // update
    delayBetweenDosesMillis[arrayLoc] = ((doseInterval[arrayLoc] * 60) * 1000) + currentMillisTime;  // if not manual dose reset minimum interval between doses

    connectMysql();
    std::string query = "UPDATE dosingPumps SET doseInterval = '" + to_string(v) + "' WHERE dosePinID = '" + to_string(pinID) + "'";
    mysql_query(conn, query.c_str()); // run query
    mysql_disconnect(); 
    printDebug(pinID);
}

void dosing::doseCalibrationDone(short pinID) {  // update system that calibration is finished
    printDebug(pinID);
    short arrayLoc = 0;
    for(short a=0; a < totalDosers; a++) {  // search through all dosing pumps to find match
        if (dosePinID[a] == pinID) {arrayLoc = a; break;}  // get array location
    }
    // std::cout << "doseCalibrationDone = " << arrayLoc << std::endl;

    doseCalibrateRunning[arrayLoc] = 0;  // calibration finsihed
    calculateDoses(pinID);
    printDebug(pinID);
}

void dosing::saveResEmail(short pinID, short v) {  // save reservoir email alert status
    printDebug(pinID);
    short arrayLoc = 0;
    for(short a=0; a < totalDosers; a++) {  // search through all dosing pumps to find match
        if (dosePinID[a] == pinID) {arrayLoc = a; break;}  // get array location
    }
    // std::cout << "saveStirrer = " << arrayLoc << std::endl;

    doseResEmail[arrayLoc] = v;  // update

    connectMysql();
    std::string query = "UPDATE dosingPumps SET resEmail = '" + to_string(v) + "' WHERE dosePinID = '" + to_string(pinID) + "'";
    mysql_query(conn, query.c_str()); // run query
    mysql_disconnect(); 
}

void dosing::saveResAlert(short pinID, short v) {  // save reservoir email alert status
    printDebug(pinID);
    short arrayLoc = 0;
    for(short a=0; a < totalDosers; a++) {  // search through all dosing pumps to find match
        if (dosePinID[a] == pinID) {arrayLoc = a; break;}  // get array location
    }
    // std::cout << "saveStirrer = " << arrayLoc << std::endl;

    doseResAlert[arrayLoc] = v;  // update

    connectMysql();
    std::string query = "UPDATE dosingPumps SET resAlert = '" + to_string(v) + "' WHERE dosePinID = '" + to_string(pinID) + "'";
    mysql_query(conn, query.c_str()); // run query
    mysql_disconnect(); 
}

void dosing::saveStirrerRuntime(short pinID, short v) {  // save stirrer runtime
    printDebug(pinID);
    short arrayLoc = 0;
    for(short a=0; a < totalDosers; a++) {  // search through all dosing pumps to find match
        if (dosePinID[a] == pinID) {arrayLoc = a; break;}  // get array location
    }
    // std::cout << "savePumpRate = " << arrayLoc << std::endl;

    stirrerRuntime[arrayLoc] = v;  // update

    connectMysql();
    std::string query = "UPDATE dosingPumps SET stirrerRuntime = '" + to_string(v) + "' WHERE dosePinID = '" + to_string(pinID) + "'";
    mysql_query(conn, query.c_str()); // run query
    mysql_disconnect(); 
    printDebug(pinID);
}

void dosing::saveSettlingRuntime(short pinID, short v) {  // save settling runtime
    printDebug(pinID);
    short arrayLoc = 0;
    for(short a=0; a < totalDosers; a++) {  // search through all dosing pumps to find match
        if (dosePinID[a] == pinID) {arrayLoc = a; break;}  // get array location
    }
    // std::cout << "savePumpRate = " << arrayLoc << std::endl;

    settlingRuntime[arrayLoc] = v;  // update

    connectMysql();
    std::string query = "UPDATE dosingPumps SET settlingRuntime = '" + to_string(v) + "' WHERE dosePinID = '" + to_string(pinID) + "'";
    mysql_query(conn, query.c_str()); // run query
    mysql_disconnect(); 
    printDebug(pinID);
}

void dosing::saveStirrerPort(short stirID, short doserPinID) {  // save stirrer port attached to dosing pump
    printDebug(doserPinID);
    short arrayLoc = 0;
    for(short a=0; a < totalDosers; a++) {  // search through all dosing pumps to find match
        if (dosePinID[a] == doserPinID) {arrayLoc = a; break;}  // get array location
    }
    // std::cout << "savePumpRate = " << arrayLoc << std::endl;

    // settlingRuntime[arrayLoc] = v;  // update

    connectMysql();
    std::string query = "UPDATE dosingPumps SET doseStirrerID = '" + to_string(stirID) + "' WHERE dosePinID = '" + to_string(doserPinID) + "'";
    mysql_query(conn, query.c_str()); // run query
    mysql_disconnect(); 
    printDebug(doserPinID);
}

dosing dosing;
