kh monitor + variable dosing

Requests for new functions or software apps
User avatar
Posts: 5390
Joined: Fri Jul 20, 2012 9:42 am
PostPosted: Fri Dec 14, 2018 7:23 pm
So with all the new devices that are out to do KH monitoring, we have two choices so far that we can interface with...

Alkatronic - https://www.coralvue.com/alkatronic-alk ... controller
All in one device, 4 pumps, doses. Extensive software, lots of failsafes built in. Active development, good customer support. WiFi support, wall mountable, 2 bnc outputs (one can go to their doser), can control a bluetooth outlet and therefore 3rd party doser or co2 regulator - $899 - $999

KH Guardian Pro - https://www.coralvue.com/kh-guardian-pro or
KH Monitor - https://www.coralvue.com/kh-guardian-alkalinity-monitor
Simpler device. 2 or 3 pumps depending if you want dosing. Requires ethernet connection. Stand-alone just tests and provides logs. Pro will dose and auto-correct, etc. Dosing is always optional. Active developer and support. $499 - $599

These devices come with a BNC output that can be connected to a pH port and when calibrated can tell the RA what our dKH values are. So I'm trying to think about what failsafes and what enhancements to my existing dosing routine. From everything we've seen so far, the results have been extremely consistent and controlling our dosers based on this really helps bring out the stability.

My code already has a function to dose the appropriate amount of sodium bicarb if I input my current PPM into one of the Custom Variables. So my target is set to 150, if I input 145 it will dose enough alk to bring me to 150. Today it only does that with a few checks (adjustment is only 1-9ppm) and dependencies (another outlet is on).

What I'm thinking of doing is leaving my current dosing schedule alone and every 4 hours read the dkh and run that adjustment function. There's already a threshold there to avoid correcting too much, and I can alert if the threshold is approaching that. This way I can review and increase my static dosing.

What other checks would you do? Do you think I'm nuts? Should I just rely on the vendors code and hand-over the reigns to these other guys?
User avatar
Posts: 5390
Joined: Fri Jul 20, 2012 9:42 am
PostPosted: Fri Dec 14, 2018 7:35 pm
Oh and the nice thing about doing the corrections ourself is I can adjust calcium and magnesium to match and do a round of correction for them too based on the dkh reading.
User avatar
Posts: 5390
Joined: Fri Jul 20, 2012 9:42 am
PostPosted: Sat Dec 15, 2018 11:29 pm
A sneak preview :)

alk.png
alk.png (3.14 KiB) Viewed 360 times


After some more research I decided to go with the KH Guardian Monitor. While the forums seem quiet about this product, there's a pretty direct source of support from the inventor for the KH Guardian monitor. What I also found out was that the monitor only model may be discontinued so I jumped on it now while I could and the price while high, isn't as damaging as the alkatronic. This one does seem much less user friendly than the alkatronic, but I really want to do most of my code on the RA. Like I said, it's already there, I just need to add some tracking for the adjustments and to schedule the adjustments. I really look forward to adjusting my other supplements along with the changes in alk to hopefully really keep things stable.

Now I have to think about where I'm gonna put it...
User avatar
Posts: 5390
Joined: Fri Jul 20, 2012 9:42 am
PostPosted: Sat Dec 22, 2018 3:05 pm
Ok, the stars aligned, my KH guardian came yesterday, the ph expansion module arrived today, I was able to get everything setup flawlessly. It was all almost too good to be true. The KH Guardian is not the most user friendly, but man it works nicely.

Here's what my test results look like so far.
Code: Select all
  12/22 15:04:41 W.116 %0 KH :8.10
  12/22 15:53:43 W.118 %0 KH :7.90
  12/22 16:12:56 W.113 %0 KH :8.00
  12/22 16:31:20 W.113 %1 KH :8.30


I'm going to run it for a while and see how it behaves before I start adjusting any code. I am planning to recalibrate the pH probe soon. I didn't receive calibration fluid and so used what I had on-hand, so just want to double check. Also, you can reverse calibrate the unit to another test kit. So with fresh Hanna reagent on hand, I will do some comparison tests tonight and possibly adjust the result. Based on the iDip tests I do, I'm quite lower, so it will be good to have a 3rd party in the results.

The pH input is working perfectly. The calibration went smoothly and the difference between the Guardian and the result on screen is quite close and consistent. It's not bouncing around at all, so it gets a steady signal and when my result was 8.10 the value was 8.09 on the RA, whereas its 8.30 on the Guardian, my RA is reading 8.26 (and 8->7.96, 7.9 -> 7.86). So basically the RA reads just a touch under.

ImageImageImage

Posts: 12391
Joined: Fri Mar 18, 2011 6:47 pm
PostPosted: Sat Dec 22, 2018 4:37 pm
That is very cool!!!
Let us know how it compares to another test.
Roberto.
User avatar
Posts: 5390
Joined: Fri Jul 20, 2012 9:42 am
PostPosted: Sat Dec 22, 2018 5:22 pm
Nice, it's even in my signature already :)
User avatar
Posts: 5390
Joined: Fri Jul 20, 2012 9:42 am
PostPosted: Mon Dec 24, 2018 8:18 am
Fresh batteries and reagents for the Hannas with the tests below. The iDip batteries and reagents were replaced in October.

Alk
Hanna - 135, 136, 137
Idip - 120, 120, 123
KH Guardian - 143, 148, 143

Just thought I’d share these as well. Nice performance from the iDip :)
Phosphate
Hanna .37, .38, .34
Idip .39, .38, .34

So results are higher than expected, but it's completely normal. First I used questionable 4/7 ph calibration fluids, waiting for new ones to come in. Second, you mix the reagent yourself so some room for error and lastly it's a different methodology, so to be expected.

The alk appears to have increased slightly as well, however it's to be expected as I've been trying to increase from what was as low as 88pm via my iDip so I have my dosers a bit higher as I've been trying to stop what's been a dropping value.

I'd like to get it up enough that the iDip is reading at least 125 / 7dKH and then we'll see how things look, but so far everything has been consistent.

Here's the latest dump from the Guardian:
12/22 14:16:52 W.118 %0 AK. 0.00 KH :8.10
12/22 15:04:41 W.116 %0 KH :8.10
12/22 15:53:43 W.118 %0 KH :7.90
12/22 16:12:56 W.113 %0 KH :8.00
12/22 16:31:20 W.113 %1 KH :8.30
12/22 20:50:48 W.116 %0 KH :8.00
12/23 01:10:16 W.115 %0 KH :8.10
12/23 05:22:33 W.113 %0 KH :8.40
12/23 09:42:15 W.114 %0 KH :8.40
12/23 14:02:22 W.120 %0 KH :8.40
12/23 18:14:35 W.121 %0 KH :8.50
12/23 21:08:40 W.121 %0 KH :8.50
12/23 21:48:13 W.121 %0 KH :8.40
12/24 02:08:15 W.121 %0 KH :8.40
12/24 06:28:29 W.121 %0 KH :8.40

The earlier results that were lower could also be the lines flushing and everything getting settled in. Obviously still in observation phase :)
User avatar
Posts: 5390
Joined: Fri Jul 20, 2012 9:42 am
PostPosted: Mon Dec 24, 2018 4:22 pm
Ok so I started preparing some code finally. The first goal is to have a more reliable way to track how long each dosing pump is used in order to set some thresholds and better log. Currently I have a log in volume through the CustomVariables. I could just move that to a memory field, but it changes at least once per hour now and I'd rather more accurately store the seconds run which could end up using more than a byte. So I'm trying something new and logging the data to a file on the SD card. When I start up, I initialize the timer that's been running and each day I reset the timer and the file. The saving of the log happens when the dosing pump switches off. The timer starts counting when the pump goes on.

Code: Select all
#define numDPumps 3
byte pump[numDPumps]={ DPump1, DPump2, DPump3}; // Pump 3 is just for logging/calibration routine
byte varReport[numDPumps]={ Var_DPump1, Var_DPump2, Var_DPump3};

void LogDosingPumps() {
  static time_t pumpTimer[numDPumps];
  static boolean pumpStatus[numDPumps], initLog;
  static char* pumpLogfiles[] = { "dp1.txt", "dp2.txt", "dp3.txt" };
  float rate;
  File logfile;
 
  if (!initLog) {
    initLog=true;
    for (int i=0;i< numDPumps;i++) {
      logfile=SD.open(pumpLogfiles[i]);
      if (logfile) {
        while (logfile.available()) {
          pumpTimer[i] = read32(logfile);
        }
        logfile.close();
      } else {
        Serial.println("error opening logfile");
      }
    }
  }

  for (int i=0;i< numDPumps;i++) {
    if (ReefAngel.Relay.Status(pump[i])) {
      if (!pumpStatus[i]) {
        pumpTimer[i]=now()-pumpTimer[i]; // Pump was off, timer is now a time
        pumpStatus[i]=true;
      }
    } else {
      if (pumpStatus[i]) {
        pumpTimer[i]=now()-pumpTimer[i]; // Pump was on, timer is now a timer
        pumpStatus[i]=false;
   
        rate=(float)InternalMemory.read_int(memCalVol[i])/InternalMemory.read_int(memCalTime[i]);
        ReefAngel.CustomVar[varReport[i]]=pumpTimer[i]*rate;

        // Open file and save current pumpTimer[i] // seconds elapsed today
        logfile=SD.open(pumpLogfiles[i], FILE_WRITE);
        if (logfile) {
          while (logfile.available()) {
            logfile.print(pumpTimer[i]);
          }
          logfile.close();
        } else {
          Serial.println("error opening logfile");
        }
      }
    }

    if (now()%SECS_PER_DAY==SECS_PER_DAY-1) {
      pumpTimer[i]=0; // Clear timer at end of day
      ReefAngel.CustomVar[varReport[i]]=0; // Clear portal variable
     
      // Clear files for pumpTimer
      for (int i=0;i< numDPumps;i++) {
        logfile=SD.open(pumpLogfiles[i], FILE_WRITE);
        if (logfile) {
          while (logfile.available()) {
            logfile.print((time_t)0);
          }
          logfile.close();
        } else {
         Serial.println("error opening logfile");
        }
      }
    }
  } 
}
User avatar
Posts: 5390
Joined: Fri Jul 20, 2012 9:42 am
PostPosted: Tue Dec 25, 2018 12:18 pm
Ok, file operations were not working well for me. :) I've reverted to EEPROM :) No worries. Maybe I'll revisit if someone has some good experience with reading and writing files.

Code: Select all
#define numDPumps 3
byte pump[numDPumps]={ DPump1, DPump2, DPump3}; // Pump 3 is just for logging/calibration routine
byte varReport[numDPumps]={ Var_DPump1, Var_DPump2, Var_DPump3};

void LogDosingPumps() {
  static time_t pumpTimer[numDPumps];
  static boolean pumpStatus[numDPumps], initLog;
  byte memDPLogs[numDPumps]={ Mem_I_DP1Log, Mem_I_DP2Log, Mem_I_DP3Log};
  float rate;

  if (!initLog) {
    initLog=true;
    for (int i=0;i< numDPumps;i++) {
      pumpTimer[i] = InternalMemory.read_int(memDPLogs[i]);
      Serial.print("Init Pump ");
      Serial.print(i);
      Serial.print(":");
      Serial.println(pumpTimer[i]);
      rate=(float)InternalMemory.read_int(memCalVol[i])/InternalMemory.read_int(memCalTime[i]);
      ReefAngel.CustomVar[varReport[i]]=pumpTimer[i]*rate;
    }
  }

  for (int i=0;i< numDPumps;i++) {

    if (ReefAngel.Relay.Status(pump[i])) {
      if (!pumpStatus[i]) {
        pumpTimer[i]=now()-pumpTimer[i]; // Pump was off, timer is now a time
        pumpStatus[i]=true;
      }
    } else {
      if (pumpStatus[i]) {
        pumpTimer[i]=now()-pumpTimer[i]; // Pump was on, timer is now a timer
        pumpStatus[i]=false;
        rate=(float)InternalMemory.read_int(memCalVol[i])/InternalMemory.read_int(memCalTime[i]);
        ReefAngel.CustomVar[varReport[i]]=pumpTimer[i]*rate;
        InternalMemory.write_int(memDPLogs[i],pumpTimer[i]);
        Serial.print("Writing Pump ");
        Serial.print(i);
        Serial.print(":");
        Serial.println(pumpTimer[i]);
      }
    }

    if (now()%SECS_PER_DAY==SECS_PER_DAY-1) {
      pumpTimer[i]=0; // Clear timer at end of day
      ReefAngel.CustomVar[varReport[i]]=0; // Clear portal variable
      InternalMemory.write_int(memDPLogs[i],pumpTimer[i]);
    }
  } 
}

Posts: 12391
Joined: Fri Mar 18, 2011 6:47 pm
PostPosted: Tue Dec 25, 2018 10:14 pm
May I ask why SD file didn't work?
Roberto.
Next

Return to Requests

Who is online

Users browsing this forum: No registered users and 1 guest

cron