Peristaltic Doser - Doser set amount over 24hr period

I have not created a particle variable, would that be particle.variable("channelStr", channelStr);

Also, the error I’m getting is that I have onTime, doseRate and totalDose have not been declared. I presumed that was done here…or is that just creating the struct?

typedef struct {
    int onTime;
    int offTime;
    float totalDose;
    float doseRate; // mL/min
    int connectedPin;

Your declaration of the Particle variable is fine; the first parameter doesn’t have to be the same as the variable name, it can be anything you want, it’s the name you use when you want to get the variable from your web page or with the CLI.

Those variable names, onTime, offTime, etc. are part of the struct so you cannot use them by themselves. The easiest place to create the string would be the same place where I have the printlnf statement in the setupChannel function. You need to use the same syntax that I have there, for example c->doseRate. The syntax for sprintf should be the same as for printf (except that you pass in channelStr as the first argument).

What you’re going to get is a variable that will point to the last channel that you set up, if you do it this way. Each time you call the Particle function, the value of channelStr will be changed to what you sent in that call. If you want to be able to see the values of all channels, you would have to create one variable for each channel.

Take a look at what I did…I thought that by creating the particle variables I was declaring the variable getRelay, but I guess not.

Am I on the right track here?

typedef struct {
    int onTime;
    int offTime;
    float totalDose;
    float doseRate; // mL/min
    int connectedPin;
} Channel;

 void updateChannel(Channel c);
 float now;
 
char getRelay[75]="";

Channel channels[4];


void setup() {
    channels[0].connectedPin = D0;
    channels[1].connectedPin = D1;
    channels[2].connectedPin = D2;
    channels[3].connectedPin = D3;
    pinMode(D0,OUTPUT);
    pinMode(D1,OUTPUT);
    pinMode(D2,OUTPUT);
    pinMode(D3,OUTPUT);
    Serial.begin(9600);
    Time.zone(-4);
    delay(3000);
    Particle.function("setupChannel", setupChannel);
    Particle.variable("getRelay1", getRelay4);
    Particle.variable("getRelay2", getRelay4);
    Particle.variable("getRelay3", getRelay4);
    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;
    }
    
    
}


int setupChannel(String cmd) { // cmd syntax: "channel#,startTime,doseRate, dose" (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->doseRate = atof(strtok_r(NULL, ",", &rmdr));
    c->totalDose = atof(rmdr);
    float dosePerActuation = c->totalDose/12;
    float durationPerActuation = (dosePerActuation/c->doseRate) * 60; // assuming doseRate is in volume/minute
    c->offTime = c->onTime + durationPerActuation;
    
    Serial.printlnf("channel#: %d, onTime: %d, doseRate: %f, totalDose: %f", index, c->onTime, c->doseRate, c->totalDose);
    sprintf("getRelay1, %d, onTime: %d, doseRate: %f, totalDose: %f", index, c->onTime, c->doseRate, c->totalDose);
    sprintf("getRelay2, %d, onTime: %d, doseRate: %f, totalDose: %f", index, c->onTime, c->doseRate, c->totalDose);
    sprintf("getRelay3, %d, onTime: %d, doseRate: %f, totalDose: %f", index, c->onTime, c->doseRate, c->totalDose);
    sprintf("getRelay4, %d, onTime: %d, doseRate: %f, totalDose: %f", index, c->onTime, c->doseRate, c->totalDose);

    return 1;
}

Well, there’s still a bunch of stuff wrong there. You still need to declare each of the variables that you point to in the Particle.variable declarations. Also, in your declarations, you have getRelay4 in all four Particle.variables, you should have 4 different variables. You don’t need getRelay[75] at all since you don’t use that one. Instead, you need to declare each variable,

char getRelay1[75];
char getRelay2[75];
char getRelay3[75];
char getRelay4[75];

In the setupChannel function, each of your 4 sprintf statements will run each time you call that function, so each will contain the same values (the values of the last one you sent, which is not what you want). You need to use a switch clause or some if-else statements to only execute the correct sprintf statement. You can do that based on the value of index. So. if index==0 then execute the getRelay1 statement, etc.

The sprintf statements are not correct either – the one you showed in your previous post was correct. You just need to replace channelStr, with getRelay1, getRelay2, etc.

Something like this maybe....and where does that belong?

if(index==0(getRelay1)); 
if)index==1(getRelay2));

I'm still not sure about this, can you give me an example?

This is where I"m at...

typedef struct {
    int onTime;
    int offTime;
    float totalDose;
    float doseRate; // mL/min
    int connectedPin;
} Channel;

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

Channel channels[4];


void setup() {
    channels[0].connectedPin = D0;
    channels[1].connectedPin = D1;
    channels[2].connectedPin = D2;
    channels[3].connectedPin = D3;
    pinMode(D0,OUTPUT);
    pinMode(D1,OUTPUT);
    pinMode(D2,OUTPUT);
    pinMode(D3,OUTPUT);
    Serial.begin(9600);
    Time.zone(-4);
    delay(3000);
    Particle.function("setupChannel", setupChannel);
    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;
    }
    
    
}


int setupChannel(String cmd) { // cmd syntax: "channel#,startTime,doseRate, dose" (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->doseRate = atof(strtok_r(NULL, ",", &rmdr));
    c->totalDose = atof(rmdr);
    float dosePerActuation = c->totalDose/12;
    float durationPerActuation = (dosePerActuation/c->doseRate) * 60; // assuming doseRate is in volume/minute
    c->offTime = c->onTime + durationPerActuation;
    
    Serial.printlnf("channel#: %d, onTime: %d, doseRate: %f, totalDose: %f", index, c->onTime, c->doseRate, c->totalDose);
    sprintf("getRelay1, channel#: %d, onTime: %d, doseRate: %f, totalDose: %f", index, onTime, doseRate, totalDose);
    sprintf("getRelay2, channel#: %d, onTime: %d, doseRate: %f, totalDose: %f", index, onTime, doseRate, totalDose);
    sprintf("getRelay3, channel#: %d, onTime: %d, doseRate: %f, totalDose: %f", index, onTime, doseRate, totalDose);
    sprintf("getRelay4, channel#: %d, onTime: %d, doseRate: %f, totalDose: %f", index, onTime, doseRate, totalDose);

    return 1;
}

That’s close. Your variable declarations are correct. In setupChannel, you need to call sprintf inside the if statements, and all the variable names need to be preceded with “c->”.

if (index == 0) {
    sprintf(getRelay1, "channel#: %d, onTime: %d, doseRate: %.1f, totalDose: %.1f", index, c->onTime, c->doseRate, c->totalDose);
}else if (index == 1) {
     sprintf(getRelay2, "channel#: %d, onTime: %d, doseRate: %.1f, totalDose: %.1f", index, c->onTime, c->doseRate, c->totalDose);
}else if (index == 2) {
     sprintf(getRelay3, "channel#: %d, onTime: %d, doseRate: %.1f, totalDose: %.1f", index, c->onTime, c->doseRate, c->totalDose);
}else if (index == 3 {
     sprintf(getRelay4, "channel#: %d, onTime: %d, doseRate: %.1f, totalDose: %.1f", index, c->onTime, c->doseRate, c->totalDose);
}

Actually, there’s a better way to call the sprintf statements that doesn’t repeat basically the same code 4 times. I don’t have time to post it now. If someone doesn’t beat me to it, I’ll post it later.

No rush…seriously, you’ve been such a huge help!!! The sprints worked out just as I had hoped. Hopefully it wasn’t too painful for you.

Next part of this will be to figure out a way to manually run each channel for a 60 seconds to obtain a dose rate for each pump.

I’m back. Instead of repeating the same sprintf line 4 times, we can create a pointer (char str* in the code below), and assign it to one of the variables in a switch statement.

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, "channel#: %d, onTime: %d, doseRate: %.1f, totalDose: %.1f", index, c->onTime, c->doseRate, c->totalDose);
    return 1;

You can replace everything in the setupChannel method after the printlnf line with he above code.

If you want to check the dose rate of each pump, you should write another little app where you just do a digitalWrite HIGH, followed by a 60,000 ms delay, then digitalWrite LOW. It would only be 4 lines or so that you should put in setup(). No need for any code in loop().

Alright, so I figured it out, and then some. I’m pretty sure there is a simpler way to do this though.

Let me know your thoughts.

Thank you as always!

typedef struct {
    int onTime;
    int offTime;
    float totalDose;
    float doseRate; // mL/min
    int connectedPin;
} Channel;

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

Channel channels[4];


void setup() {
    channels[0].connectedPin = D0;
    channels[1].connectedPin = D1;
    channels[2].connectedPin = D2;
    channels[3].connectedPin = D3;
    pinMode(D0,OUTPUT);
    pinMode(D1,OUTPUT);
    pinMode(D2,OUTPUT);
    pinMode(D3,OUTPUT);
    Serial.begin(9600);
    Time.zone(-4);
    delay(3000);
    Particle.function("setupChannel", setupChannel);
    Particle.variable("getRelay1", getRelay1);
    Particle.variable("getRelay2", getRelay2);
    Particle.variable("getRelay3", getRelay3);
    Particle.variable("getRelay4", getRelay4);
    Particle.function("manual1", manual1);
    Particle.function("manual2", manual2);
    Particle.function("manual3", manual3);
    Particle.function("manual4", manual4);
    Particle.function("calibrate1", calibrate1);
    Particle.function("calibrate2", calibrate2);
    Particle.function("calibrate3", calibrate3);
    Particle.function("calibrate4", calibrate4);
   }


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


int setupChannel(String cmd) { // cmd syntax: "channel#,startTime,doseRate, dose" (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->doseRate = atof(strtok_r(NULL, ",", &rmdr));
    c->totalDose = atof(rmdr);
    float dosePerActuation = c->totalDose/12;
    float durationPerActuation = (dosePerActuation/c->doseRate) * 60; // assuming doseRate is in volume/minute
    c->offTime = c->onTime + durationPerActuation;
  
    Serial.printlnf("channel#: %d, onTime: %d, doseRate: %f, totalDose: %f", index, c->onTime, c->doseRate, c->totalDose);
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, "channel#: %d, onTime: %d, doseRate: %.1f, totalDose: %.1f", index, c->onTime, c->doseRate, c->totalDose);
    return 1;
}

int manual1(String cmd){
       if (cmd=="on") {
        digitalWrite(D0,HIGH);
        return 1;
    }
    else if (cmd=="off") {
        digitalWrite(D0,LOW);
        return 0;
    }
    else {
        return -1;
    }
}
int manual2(String cmd){
       if (cmd=="on") {
        digitalWrite(D1,HIGH);
        return 1;
    }
    else if (cmd=="off") {
        digitalWrite(D1,LOW);
        return 0;
    }
    else {
        return -1;
    }
}
int manual3(String cmd){
       if (cmd=="on") {
        digitalWrite(D2,HIGH);
        return 1;
    }
    else if (cmd=="off") {
        digitalWrite(D2,LOW);
        return 0;
    }
    else {
        return -1;
    }
}
int manual4(String cmd){
       if (cmd=="on") {
        digitalWrite(D3,HIGH);
        return 1;
    }
    else if (cmd=="off") {
        digitalWrite(D3,LOW);
        return 0;
    }
    else {
        return -1;
    }
}
int calibrate1(String cmd){
       if (cmd=="on") {
        digitalWrite(D0,HIGH);
        delay(60000);
        digitalWrite(D0,LOW);
        return 1;
    }
    else if (cmd=="off") {
        digitalWrite(D0,LOW);
        return 0;
    }
    else {
        return -1;
    }
}
int calibrate2(String cmd){
       if (cmd=="on") {
        digitalWrite(D1,HIGH);
        delay(60000);
        digitalWrite(D1,LOW);
        return 1;
    }
    else if (cmd=="off") {
        digitalWrite(D1,LOW);
        return 0;
    }
    else {
        return -1;
    }
}
int calibrate3(String cmd){
       if (cmd=="on") {
        digitalWrite(D2,HIGH);
        delay(60000);
        digitalWrite(D2,LOW);
        return 1;
    }
    else if (cmd=="off") {
        digitalWrite(D2,LOW);
        return 0;
    }
    else {
        return -1;
    }
}
int calibrate4(String cmd){
       if (cmd=="on") {
        digitalWrite(D3,HIGH);
        delay(60000);
        digitalWrite(D3,LOW);
    }
    else if (cmd=="off") {
        digitalWrite(D3,LOW);
        return 0;
    }
    else {
        return -1;
    }
}

You’re only allowed to have 4 particle functions in an app. So, you should do one manual function, like we did for the setupChannel function – pass the channel number along with on or off, and separate the command string with strtok_r. Same for the calibrate function. Also, you generally want to return from a particle function quickly, so putting in a 1 minute delay in that function is not a good thing. You could make that function similar to the setupChannel function where you setup an on time and an off time. Another, probably easier way, would be to digitalWrite HIGH then start a software Timer (reference here) in the function. The handler for the timer would then do a digitalWrite LOW.

With the photon it's possible to have more than 4. It's depends on the length of the names and some other factors. It's yet to be doc'd since it can be confusing and 4 are guaranteed to work.
That said, you're of course right that you can group those together and use the info from the arguments to figure what it is you want to do.

1 Like

@Moors7 @Ric
So I would put this into setup()

particle.function("calibrate", calibrate);

Then move on to this section??

int setupChannel(String cmd) {
    Serial.println(cmd);
    char *rmdr;
    char *stringArgs = (char*)cmd.c_str();
    int index = atoi(strtok_r(stringArgs, ",", &rmdr));
    Channel* c = &channels[index];

I presume I don’t need this section, as it seems relative to the dosing schedule.

    c->onTime = atof(strtok_r(NULL, ",", &rmdr));
    c->doseRate = atof(strtok_r(NULL, ",", &rmdr));
    c->totalDose = atof(rmdr);
    float dosePerActuation = c->totalDose/12;
    float durationPerActuation = (dosePerActuation/c->doseRate) * 60; // assuming doseRate is in volume/minute
    c->offTime = c->onTime + durationPerActuation;

From here, I’m not sure I understand how to incorporate a digitalWrite function.

int calibrate(String cmd) {
    char *rmdr;
    char *stringArgs = (char*)cmd.c_str();
    int index = atoi(strtok_r(stringArgs, ",", &rmdr));
    Channel* c = &channels[index];

What you have so far gives you the pointer, c, to a channel in your array of channels. To use both an “on” and “off” command, you also need to use the value of rmdr (which stands for remainder). The rest of the code would be similar to what you posted, but we need to replace the delay with some other method to turn off the relay in 60 seconds. I had said that we could use a software timer for that, but that won’t work because the callback function for those timers doesn’t take any arguments, which means we can’t tell that function which pin to turn off. So, we need to add a new variable to the Channel struct called calibrationEnd,

typedef struct {
    int startTime;
    int onTime;
    int offTime;
    float totalDose;
    float doseRate; // mL/min
    int connectedPin;
    int calibrationEnd = 90000; // a vaule greater than "now" can ever be
} Channel;

That new variable will be set to the time 60 seconds after we call the calibrate function. We need to add another time check in the updateChannel method to compare this time to “now”. So, that function should now look like this,

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+1);
        c->onTime += 7200;
        updateParticleVariables(index, c);
    }

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

And this is what the calibrate function should look like,

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

Awesome! Works perfect…your brilliance is once again appreciated.

Next question, I need a manual on/off option. I’ve debated doing this two different ways. One is a physical button, the other on/off web button. Any thoughts on this?

Whether you use a physical button or a web one depends on what you want, so I think that’s a decision you need to make. If you do it as a web button, then you can use code like you showed above, but combine it into one function. You would pass the channel number and on and off just like for the calibrate function, so the parsing would be identical. You could even do it and the calibrate all in one function if you want; in that case you might pass “on”, “off”, “calibrateOn”, and “calibrateOff”.

Ok, got the manual option working, I just replicated the calibration code and removed the time function and changed it to “manual”, works perfect! Also, my photon came today, will everything work fine on that?

So with the webpage, I took the original HTML page that @bko did for me. I expanded on the buttons and setup, but I can’t anything beyond retrieving the current settings to work. I read through a few tutorials, but because this code is written different than all of the examples, I’m having a hard time figuring out what does what in the javascript.

A few questions, starting from the top down:

  1. It would be nice to be able to select how many doses over a 24hr period.
  2. For doseRate, how hard it would be to have javascript divide the total volume of liquid by 60 to get mL/min?

I’m not sure if @bko script converted the time to seconds or not, take a look and let me know. It did work out pretty slick with the drop downs.

    <!DOCTYPE HTML>
    <html>
      <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js" type="text/javascript" charset="utf-8">  </script>
    <body>
    <p> <b>4 Channel Doser</b>
    
      <p>Doses Over 24hr Period: 
         <select id="totalDose"></select>
         </p>
    
      <p>Pump 1: Current setting <span id="relay1">unknown</span>
        <button id="getR1" onclick="getRelay(1)">Refresh</button>
        &nbsp;&nbsp;&nbsp;&nbsp;New setting
        <select id="relay1hours">
        </select> :
        <select id="relay1minutes">
        </select>
        </p>
    
      <p>Pump 2: Current setting <span id="relay2">unknown</span>
        <button id="getR2" onclick="getRelay(2)">Refresh</button>
        &nbsp;&nbsp;&nbsp;&nbsp;New setting
        <select id="relay2hours">
        </select> :
        <select id="relay2minutes">
        </select>
        </p>
    
      <p>Pump 3: Current setting <span id="relay3">unknown</span>
        <button id="getR3" onclick="getRelay(3)">Refresh</button>
        &nbsp;&nbsp;&nbsp;&nbsp;New setting
        <select id="relay3hours">
        </select> :
        <select id="relay3minutes">
        </select>
        </p>
    
      <p>Pump 4: Current setting <span id="relay4">unknown</span>
        <button id="getR4" onclick="getRelay(4)">Refresh</button>
        &nbsp;&nbsp;&nbsp;&nbsp;New setting
        <select id="relay4hours">
        </select> :
        <select id="relay4minutes">
        </select>  
        </p>
        
        <p> <b>Calibration</b>
        <br><br>
        Each pump will run for 60 seconds.  Enter measured amount, divide by 60 and click set.
        </p>
        
        <p><button id="calibrate1"  onclick="calibration(0,1)">Pump 1</button>
        &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Dose Rate
        <input type="text" id="doseRate(1)" value="1.26">  mL/min
        <button id="setR1" onclick="setRelay(1)">Set</button>
        </p>
        
        <p><button id="calibrate2"  onclick="calibration(1,1)">Pump 2</button>
        &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Dose Rate
        <input type="text" id="doseRate(2)" value="1.26">  mL/min
        <button id="setR2" onclick="setRelay(2)">Set</button>
        </p>
        
        <p><button id="calibrate3"  onclick="calibration(2,1)">Pump 3</button>
        &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Dose Rate
        <input type="text" id="doseRate(3)" value="1.26">  mL/min
        <button id="setR3" onclick="setRelay(3)">Set</button>
        </p>
        
        <p><button id="calibrate4"  onclick="calibration(3,1)">Pump 4</button>
        &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Dose Rate
        <input type="text" id="doseRate(4)" value="1.26">  mL/min
        <button id="setR4" onclick="setRelay(4)">Set</button>
        </p>
        
        <p> <b>Manual ON/OFF</b>
        </p>
        <p>Pump 1:&nbsp;&nbsp;&nbsp;
        <button id="pump1onbutton"  onclick="manual(0,1)">ON</button>
        <button id="pump1offbutton"  onclick="manual(0,0)">OFF</button>
        </p>
        
        <p>Pump 2:&nbsp;&nbsp;&nbsp;
        <button id="pump2onbutton"  onclick="manual(1,1)">ON</button>
        <button id="pump2offbutton"  onclick="manual(1,0)">OFF</button>
        </p>
        
        <p>Pump 3:&nbsp;&nbsp;&nbsp;
        <button id="pump3onbutton"  onclick="manual(2,1)">ON</button>
        <button id="pump3offbutton"  onclick="manual(2,0)">OFF</button>
        </p>
        
        <p>Pump 4:&nbsp;&nbsp;&nbsp;
        <button id="pump4onbutton"  onclick="manual(3,1)">ON</button>
        <button id="pump4offbutton"  onclick="manual(3,0)">OFF</button>
    
        <script type="text/javascript">
        
        var select = '';
        for (i=1;i<=24;i++){
            select += '<option val=' + i + '>' + i + '</option>';
        }
        $('#totalDose').html(select);
    
        for(i=0;i<24;i++) {
                  for(relay=1;relay<5;relay++) {
                               var rh = document.getElementById("relay"+relay.toString()+"hours");
                               var opt = document.createElement("OPTION");
                               opt.setAttribute("value", i.toString() );
                               var nd  = document.createTextNode(i.toString());
                               opt.appendChild(nd);
                               rh.appendChild(opt);
                               }
                  }
    
        for(i=0;i<60;i++) {
                  for(relay=1;relay<5;relay++) {
                               var rm = document.getElementById("relay"+relay.toString()+"minutes");
                               var opt = document.createElement("OPTION");
                               opt.setAttribute("value", i.toString() );
                               var nd  = document.createTextNode(i.toString());
                               opt.appendChild(nd);
                               rm.appendChild(opt);
                               }
                  }
    
          var deviceID    = "xxxx";
          var accessToken = "xxxx";
          var setFunc = "setRelay";
          var getFunc = "getRelay";
    
          function getRelay(relay) {
             requestURL = "https://api.particle.io/v1/devices/" + deviceID + "/" + getFunc + relay.toString() + "/?access_token=" + accessToken;
                 $.getJSON(requestURL, function(json) {
                     document.getElementById("relay"+relay.toString() ).innerHTML = json.result;
                     });
          }
    
          function setupChannel(relay) {
            var newValue = document.getElementById("relay"+relay+"hours").value   + ":" +
                           document.getElementById("relay"+relay+"minutes").value + ":" +
                                                                             "00" + "*" +
                           document.getElementById("relay"+relay+"duration").value;
        var requestURL = "https://api.particle.io/v1/devices/" +deviceID + "/" + setFunc + relay + "/";
            $.post( requestURL, { params: newValue, access_token: accessToken });
          }
                var setFunc = "manual()";
    
          function manual() {
            if (newValue==1) { 
               paramStr = paramStr + ",HIGH";
            } else {
              paramStr = paramStr + ",LOW";
            }
        var requestURL = "https://api.particle.io/v1/devices/" +deviceID + "/" + setFunc + "/";
            $.post( requestURL, { params: paramStr, access_token: accessToken });
          }
    
        </script>
        </p>
    </p>
    </body>
    </html>

Hopefully bko or someone else can help you with this. I am not a web programmer, and don’t know java script.

Ha! Me too! I do okay with html, but javascript eludes me.

I appreciate your help. Did you see the private message I sent you?

Hi @Ric and @james211

The javascript I originally wrote builds a command string to send to the Particle device in the setupChannel that looks like “12:34:00*6000” which meant turn-on the relay at that twelve thirty-four and zero seconds for 6000 seconds (I think it was seconds anyway). I tried to keep this pretty generic so other folks could use the same code for other timer-like applications.

What kind of command string are you trying to send now?