Peristaltic Doser - Doser set amount over 24hr period

Via the particle relay board. And this is my code thanks to @Moors7 @Ric @ScruffR @bko

 STARTUP(System.enableFeature(FEATURE_RETAINED_MEMORY));
 
typedef struct {
    int onTime;
    int offTime;
    float totalDose;
    float doseRate; // mL/min
    int connectedPin;
    int calibrationEnd = 90000; // a vaule greater than "now" can ever be
    int numberOfDoses; //number of doses in 24hr period
} Channel;

void updateChannel(Channel c);
 float now;
    
     retained char getRelay1[75];
     retained char getRelay2[75];
     retained char getRelay3[75];
     retained char getRelay4[75];

     retained Channel channels[4];


void setup() {
    channels[0].connectedPin = D3;
    channels[1].connectedPin = D4;
    channels[2].connectedPin = D5;
    channels[3].connectedPin = D6;
    pinMode(D3,OUTPUT);
    pinMode(D4,OUTPUT);
    pinMode(D5,OUTPUT);
    pinMode(D6,OUTPUT);
    Serial.begin(9600);
    Time.zone(-4);
    delay(3000);
    Particle.function("setDoseRate", setDoseRate);
    Particle.function("setupChannel", setupChannel);
    Particle.function("calibrate",calibrate);
    Particle.function("manual",manual);
    Particle.variable("getRelay1", getRelay1);
    Particle.variable("getRelay2", getRelay2);
    Particle.variable("getRelay3", getRelay3);
    Particle.variable("getRelay4", getRelay4);
   }


void loop() {
    now = Time.hour() * 3600 + Time.minute() * 60 + Time.second();
    Serial.println(now);
    for (int i=0;i<4;i++) {
        updateChannel(i);
    }
    
    
    delay(5000);
}



void updateChannel(int index) {
    Channel *c = &channels[index];
    if (c->onTime <= now && c->totalDose > 0) { 
        digitalWrite(c->connectedPin, HIGH);
        Serial.printlnf("Fired Relay %d", index);
        c->onTime += 7200;
    }

    if (c->offTime <= now && c->totalDose > 0) {
        digitalWrite(c->connectedPin, LOW);
        Serial.printlnf("Turned off Relay %d", index);
        c->offTime += 7200;
    }
    
        if (c->calibrationEnd <= now) {
        digitalWrite(c->connectedPin, LOW);
        c->calibrationEnd = 90000;
    }
} 
int setDoseRate(String cmd) {
    char *rmdr;
    char *stringArgs = (char*)cmd.c_str();
    int index = atoi(strtok_r(stringArgs, ",", &rmdr));
    Channel* c = &channels[index];
    c->doseRate = atof(rmdr);
    return 1;
}


int setupChannel(String cmd) { // cmd syntax: "channel#,startTime,doseRate, numberOfDoses, totalDose" (startTime is sent as hour*3600 + minute*60 + second)
    Serial.println(cmd);
    char *rmdr;
    char *stringArgs = (char*)cmd.c_str();
    int index = atoi(strtok_r(stringArgs, ",", &rmdr));
    Channel* c = &channels[index];
    c->onTime = atof(strtok_r(NULL, ",", &rmdr));
    c->numberOfDoses = atoi(strtok_r(NULL, ",", &rmdr));
    c->totalDose = atof(rmdr);
    float dosePerActuation = c->totalDose/c->numberOfDoses;
    float durationPerActuation = (dosePerActuation/c->doseRate) * 60; // assuming doseRate is in volume/minute
    c->offTime = c->onTime + durationPerActuation;
  
    Serial.printlnf("channel#: %d, onTime: %d, totalDose: %f, numberOfDoses: %d", index, c->onTime, c->totalDose, c->numberOfDoses);
char *str;
    switch (index) {
        case 0:
            str = getRelay1;
            break;
        case 1:
            str = getRelay2;
            break;
        case 2:
            str = getRelay3;
            break;
        case 3:
            str = getRelay4;
            break;
    }
    sprintf(str, "onTime: %d, totalDose: %.1f, numberOfDoses: %.1d", c->onTime, c->totalDose, c->numberOfDoses);
    return 1;
}

    int calibrate(String cmd) {
    char *rmdr;
    char *stringArgs = (char*)cmd.c_str();
    int index = atoi(strtok_r(stringArgs, ",", &rmdr));
    Channel* c = &channels[index];
    
    if (strcmp(rmdr, "on") == 0) {
        digitalWrite(c->connectedPin,HIGH);
        c->calibrationEnd =  Time.hour() * 3600 + Time.minute() * 60 + Time.second() + 60;
        return 1;
    }
    else if (strcmp(rmdr, "off") == 0) {
        digitalWrite(c->connectedPin,LOW);
        return 0;
    }
    else {
        return -1;
    }
}
    int manual(String cmd) {
    char *rmdr;
    char *stringArgs = (char*)cmd.c_str();
    int index = atoi(strtok_r(stringArgs, ",", &rmdr));
    Channel* c = &channels[index];
    
    if (strcmp(rmdr, "on") == 0) {
        digitalWrite(c->connectedPin,HIGH);
        return 1;
    }
    else if (strcmp(rmdr, "off") == 0) {
        digitalWrite(c->connectedPin,LOW);
        return 0;
    }
    else {
        return -1;
    }
}
1 Like

@james211, I should have asked ā€œwhat hardwareā€ do you use! However, I can see that you use the relay board. There are 2 possible failure modes that could keep a relay turned on, thus dispensing uncontrollably:

  1. Your software is in a failed mode, driving the relay ON
  2. The relay fails (fused contacts) in the ON mode

For the first failure mode, you could setup a ā€œwatchdogā€ timer or thread that monitors the relay outputs (by reading the output pins!) and measuring the time they are ON. If they exceed a maximum ON time, the watchdog can take action, possibly by resetting the Photon or setting a flag, or as outlined below.

For the second case, it’s not so easy. You would need to measure the voltage on the output of the relay, indicating that it is ON. Coupled with the watchdog mentioned above, you could take action through a ā€œwhole systemā€ power relay that would disable all pump power, thus preventing further pumping. :smiley:

Well that’s well over my head! Not even sure where I would begin with that…especially given I know virtually no programming. Thank you for the info though. Knowing the logic behind it helps.

In the mean time I realized my relays are running much longer than they should be, so I’ll have to troubleshoot that first. Uggg…this is the part I don’t like. Guess I’ll start the backtracking.

When you say ā€œmuch longerā€ what time frame do you mean? The original code I wrote turned the relays off at the correct time in my testing, so I think the problem is probably due to the way you’re setting the dose rate.

That was going to be my guess as well. I changed it so the dose rate could be changed via the webpage and I’m guessing that messed something up. I added a setDoseRate function and it may not be in sync with your original code.

So I was able to do a trade with a friend for some help with the website programming. Here is what I came up with. I would love some feed back from you guys. I do need to incorporate a watchdog feature, but I’m going to save that for a bit so I can read up on that a bit more.

The other thing I thought about adding to this page is some sort of indicator that shows the status of the photon, online or offline, pulsing a color…I’m not too sure. Any thoughts?

What exactly do you want feedback on? If it works for you, then great :smile:

Get the initial status by doing a GET request, then subscribe to the status SSEs. Shouldn't be too hard :wink:

Thanks @Moors7, I guess I just like to get feedback from people…part of my personality.

I’ll check into the GET request. I presume I don’t need to add any additional code to my photon?

Nope, shouldn't be necessary :smile:

Yup, I was setting the dose rate for a different pump....I'm an idiot!

However, here is a problem that I'm not sure I know how to resolve, the program doesn't run beyond midnight. So for example, yesterday morning I set a program to run every hour, it worked perfect all the way to 11pm. At midnight it didn't dose, at 1am it didn't dose and at 2am it didn't dose (I was awake working and the unit is right next to me).

Yeah, in helping you with the code I was focused on other aspects of the logic, so I overlooked that problem. Surely, you can look at the math and figure that out yourself. Look at how we adjust the onTime and offTime in updateChannel.

1 Like

No worries…thanks for the direction. Given everything is in seconds, I’m guessing that 7200 needs to be 86400.

void updateChannel(int index) {
    Channel *c = &channels[index];
    if (c->onTime <= now && c->totalDose > 0) { 
        digitalWrite(c->connectedPin, HIGH);
        Serial.printlnf("Fired Relay %d", index);
        c->onTime += 7200;
    }

    if (c->offTime <= now && c->totalDose > 0) {
        digitalWrite(c->connectedPin, LOW);
        Serial.printlnf("Turned off Relay %d", index);
        c->offTime += 7200;
    }

You have to consider that your current time will roll over 0 … 86399 … 0 and if you expect that value to become greater than 86399 (e.g 23:30 + 1h = 84600 + 3600 = 88200), you will wait forever.

I just made an edit, I should learn to use a calculator. So should it be 86399 or 86400…based on your statement 86399 seems right.

On a side note, has anyone read anywhere on the forums about using mosfet relay boards instead of the mechanical relay board offered by particle?

One day has 86400 seconds which are numbered 0 .. 86399 :wink:

Ok, so just to verify, is this correct then?

void updateChannel(int index) {
    Channel *c = &channels[index];
    if (c->onTime <= now && c->totalDose > 0) { 
        digitalWrite(c->connectedPin, HIGH);
        Serial.printlnf("Fired Relay %d", index);
        c->onTime += 86399;
    }

    if (c->offTime <= now && c->totalDose > 0) {
        digitalWrite(c->connectedPin, LOW);
        Serial.printlnf("Turned off Relay %d", index);
        c->offTime += 86399;
    }

No, that is not correct. If you want it to fire every hour, you want 3600; the number of seconds in one hour (I don’t know how it would have worked before when you had 7200 in there, if you really had it firing hourly). You need to take care of the wrap around case; at midnight the number of seconds into the day is 0. So how do you do that? Think about it!

Try to write out the maths on paper - especially round about the turn of day - and see what you get.

Just a quick refresher on time conversion

                               1sec
                    1min =    60sec
         1hour =   60min =  3600sec
1day = 24hours = 1440min = 86400sec

e.g.
11:30pm = 23:30 = 23*3600 + 30*60 = 84600sec
84600sec + 3600sec = 88200sec -> 24:30 does not exist! Needs correction!

Let me step back a bit here. Currently part of the code is numberOfDoses, which is a defined by the user. So in theory the onTime and offTime would have to be a calculated number, rather than a defined number. Is that correct?

If thats true, then I need to take a step back as that is way over my head at the moment.

Right now I’m having a hard time wrapping my head around this math. If I want the interval to be 3600 seconds, then in my head I see it as current time - last fire time = 3600 then fire the relay again. But you could end up with a negative number then…which is where I get lost…now I’m the one going in circles! Arrrgggg!