Peristaltic Doser - Doser set amount over 24hr period

We’re sending it as a dose now, and the end time is calculated in the Photon code, so

“channel#,startTime,doseRate, dose” (startTime is sent as hour3600 + minute60 + second)

channel# is 0 to 3

int setupChannel(String cmd) { // cmd syntax: "channel#,startTime,doseRate, dose" (startTime is sent as hour*3600 + minute*60 + second)
    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);
    
    updateParticleVariables(index, c);
    return 1;
}

Thank you @bko and @Ric for chiming in here. Here is the final working code. If any adjustments need to be made to accommodate the javascript let me know.

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

One thing to be aware of. The way the code is currently operating, once you set up a channel, that relay will trigger every 2 hours indefinitely. So, the totalDose, isn’t really a total, it’s the amount that will be dosed in one 24 hour period. If this is not what you want, then we need to add something in the code to stop the dosing after the 12th dose.

Nope that’s what I want! I do want to be able to change that 12 via the web page though. It’s not urgent, but it’s on the list. Consistency is the key to the doser.

Not having much luck with the web page though, hopefully someone can chime in to help out.

One big suggestion: Carefully test what happens when your wifi goes down!

I am doing something similar. Aquarium related dosing pumps.
Unfortunately, when my wifi went down, or when Photon disconnected,
pretty much everything hung. Not good if your doser pump is on at the time.
I have a lot of workarounds now, thanks to helpful community, so about 90% reliable,
but still not perfect.

im open to suggestions. Thankfully my wifi never goes out.

@bko @Moors7 @Ric
There are a few things in trying to do at the moment and I haven’t had much luck. I tried again today with a bunch of different things and had no luck. Finding references for javascript and particle and/or arduino are much harder to find.

Here’s my end goal…I’m actually willing to pay at this point, as I really want to finish this project and get it posted.
If you see the webpage its pretty clear what my goals are there as well.

  1. Send a calibration function (calibrate)
  2. Send a manual on/off button function. (manual)
  3. Send start time (setupChannel)
  4. Set dosage for 24hr period. (setupChannel)
  5. Set dose rate (setupChannel)

Here are the functions that I send:
Calibration: particle call doser calibrate 0,on
setupChannel: particle call doser setupChannel 0,60000,1.26,120 (channel#,startTime,doseRate, dose" (startTime is sent as hour3600 + minute60 + second)
Manual: ON -> particle call doser manual 1,on
OFF-> particle call doser manual 1,off

If you can be any help I’d greatly appreciate it. If you know of someone else who might be willing to give me a hand please let me know. I’m not trying to take advantage of anyone’s time, so I apologize if it seems that way. Sometimes having one part of the code written helps me to make headway.

Thank you again,

Dustin

For the manual on/off function javascript, I started with this in the html:

</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>

And for the javascript

var deviceID    = "51ff6a06506754575XXXXXX";
var accessToken = "3aaacdf9121d404c1a60d5f5f853XXXXXX";
var setFunc = "manual";


      function manual(relay,newValue) {
        var paramStr = "relay" + manual.toString();
        if (newValue==1) { 
           paramStr = paramStr + ",HIGH";
        } else {
          paramStr = paramStr + ",LOW";
        }
    var requestURL = "https://api.particle.io/v1/devices/" +deviceID + "/" + setFunc + "/";
        $.post( requestURL, { params: newValue, access_token: accessToken }); 

When I run that html file and click on, the console returns no errors, but the network function returns

https://api.particle.io/v1/devices/51ff6a06506754575XXXXXX/manual/

which also says this:

{
"error": "invalid_request",
"error_description": "The access token was not found"
}

However, when I use the getFunc for the current time setting, I don't get the access token error.

This might help. Check the source for the code, and replace the function names/arguments in the buttons. See if that not only works, but if you also understand what’s going on.
This is using the (now old) javascript library:
http://jordymoors.nl/particle/demos/function_button.html

Thank you, I’ll check it out. I appreciate it.

I'm definitely missing something here..I feel like I'm close, but I can't figure out what I'm doing wrong. Does it matter that I'm using particle call in my code vs spark call?

This is the response I get in the console:

Device action successfull: Object { id: "51ff6a065067545754xxxxxx", last_app: "", connected: true, return_value: -1 }

<html>
  <head>
      <title>Spark function buttons Example</title>
      <script src="http://cdn.jsdelivr.net/sparkjs/1.0.0/spark.min.js"></script>
  </head>

  <body>
    <div id="login">
        <input type="email" class="form-control" placeholder="Emai" id="email"> </br>
        <input type="password" class="form-control" placeholder="Password" id="password">
        </br>
        <button type="button" onclick="login()">Login</button>
    </div>
    <button type="button" onclick="functionPost('manual', 'on')">function 1</button>
    <button type="button" onclick="functionPost('functionName', 'functionArgument')">function 2</button>
    
    
    
    <script>
      //you can use your accesstoken, or your email/passsword to log in. Uncomment accordingly below.

      userAccessToken = "<readacted for security reasons>";
      var deviceID = "<readacted>";
      
      
      

      // callback to be executed by each core
      var callback = function(err, data) {
        if (err) {
          console.log('An error occurred:', err);
        } else {
          console.log('Device action successfull:', data);
        }
      };
      
    
      function functionPost(manual, on){
        // The function needs to be defined  in the firmware uploaded to the
        // the Spark core/Particle photon and registered to the Particle cloud, same thing we do
        // with variables. You pass along the name of the function and the params.
        spark.callFunction(deviceID, manual, on, callback);  
      }
      
      function variableGet(variableName){
        spark.getVariable(devicesID, variableName, callback);
      }
      
      spark.on('login', function(response) {           
        console.log(response);
      });
      
      function login(){
        emailInput = document.getElementById('email');
        passwordInput = document.getElementById('password');
        
        console.log(emailInput.value + ", " + passwordInput.value);
        spark.login({ username: emailInput.value, password: passwordInput.value });
        
        emailInput.value = '';
        passwordInput.value = '';
      }
     
      spark.login({ accessToken: userAccessToken });
      
    </script>
  </body>
</html>

Any guidance on the sample?

The sample doesn’t do anything other than calling the function you specified. Looking at the device code, it seems it responds a -1 if the argument doesn’t match one of the two programmed ones. With that in mind, try to double check the argument?

I spent several hours on it yesterday and couldn’t get it to respond “1.” What I posted in the previous statement was what I filled in. Is it obvious where I went wrong?

The previous statement was

You expect others to plough through previous posts to guess what you actually did? Why not just repost that very command? No more typing than the sentence: "What I posted ..."

1 Like

I’m with @ScruffR on this one. If you’ve indeed spent several hours trying different things, then surely you must have tried more than the single example above? If so, I really don’t see why I should have to guess what you’ve done, thereby doing double work.
Like I mentioned before, it’s almost certainly an issue with the argument. I suggest you plough through some of the previous posts to figure out how it’s supposed to be constructed, and give that another shot. You can use this site to quickly test them without having to recode them every time.

1 Like

My apologies, I don’t expect anything, I’m just frustrated because I don’t what I’m doing, and I can’t seem to get an explanation as to what does what. I understand what the functionName (manual), and the functionArgument(1,on). What I don’t understand is how the html is passed to the javascript…or how they work together.

This is what I put into html…1 is the relay number. Is the doser number and the function (on) the whole argument? Or is just “on” the argument?

<button type="button" onclick="functionPost('manual', '1', 'on')">function 1</button>

And then in the javascript I did the following:

function functionPost(manual, 1, on){
spark.callFunction(deviceID, manual, 1, on, callback);

Aha, that is not what's in the code you posted above, hence our query as to what you've tried, rather than have us guess.

I hoped the example to be fairly self-explanatory, since there's not really anything special going on. At all. It's 'just' a function call in Javascript, something for which I recommend you to check out this. It's pretty much the same as you would do on the device.

int manual(String cmd){ 
  String argumentVariable = cmd;
}

manual('pizza');

Is the exact same thing. manual is your function, and cmd is your function argument. That argument is then being used inside the function as a variable. argumentVariable would in this case be 'pizza', since that's what you passed it.

The exact same thing happens with the javascript. You make a function that expects two arguments. In the HTML you call that function and give it those two arguments. Those arguments are then used as variables to call the Particle.function(), which in turn expect's an argument itself.

The changes you made to the Javascript were unnecessary, and will not work, since the library only expects a certain amount of arguments. Not by accident, it expects

spark.callFunction(device_id, function_name, function_argument, callback_function);

Which is the reason why I named those variables that, so it'd be self-explanatory. Simply put, don't touch that entire function, unless you know what you're doing. The only thing that needs to be changed is the HTML part in the button. Quite literally, replace the words with their contents. For functionName insert the function name, and for the functionArgument insert the function argument. if your argument is I, Like, To, Insert, Commas, then that, as a whole, is your function argument. Don't go around creating new things inside the function, since that won't work.
If you've tried the page I've linked to, and you found a working argument, then that's what you need to insert there, nothing more, nothing less.

Answer me this, what is the exact function argument needed to toggle the 3rd relay on? Don't guess, try it with the page above, and report back if you've figured out what it's supposed to be. That saves us from guessing as well.

Though I might know the answer already (I think), I'm purposely keeping this a bit 'vague'. If you indeed intend on making this into kits of sorts, or making a guide for others to use, I want you to understand what's going on. There's absolutely no use in handing you the code, with you then not knowing what to say if people have questions regarding the kits you've provided them with. To make this work as intended, you need to how and why it works. The only way to learn that is to try, and read. For Javascript, W3schools has always been a good resource for information.

With all the missing basics here, you might need some primers in CS
I always enjoy watching these videos (despite knowing the basics quite well, but to keep in mind what others might be missing) from Harvard Open University CS50
https://www.youtube.com/watch?v=z-OxzIC6pic&list=PLvJoKWRPIu8G6Si7LlvmBPA5rOJ9BA29R&index=1

If you think you know these bits, just skip on to the next one, till you find your level of understanding and then watch on

Thank you both. I appreciate you taking the time. I’m getting on a plane here shortly, provided the internet is good enough I’ll take a look on the plane. You don’t know if there happens to be a particle simulator do you? I’ve used the arduino one in the past and it works quite well. Just figured something like that would be easier to monkey with on a plane.