DougalPlummer
Published © GPL3+

Controller for Chemical Dosing Pump

A portable WiFi / smart phone based pump system for dosing drip irrigation systems with chemicals.

BeginnerWork in progress1 hour6,187
Controller for Chemical Dosing Pump

Things used in this project

Hardware components

ESP8266 WeMos D1 R2 Uno Compatible
This is a really cool board even if it is 3.3V
×1
Single Channel FET trigger board
These are the generic isolated FET or "trigger" board
×1
Buzzer
Buzzer
If you want it to make noise when done
×1
Fuse and Holder 10A
Only if you don't like to smoke and burn
×1
Switches Automotive 10A
If you want to be able to control manually as in the sprayer example
×2
30A Relay Modual
This is in case you want to do it old school or your motor draws heaps of power
×1
ESP8266 ESP-12E
Espressif ESP8266 ESP-12E
×1
Generic ESP8266 IC
Espressif Generic ESP8266 IC
×1

Software apps and online services

Arduino IDE
Arduino IDE
This was used for both the UNO and WeMos versions, Need to specify the JSON file to compile for D1. Lots of videos on web and also a tutorial at the WeMos site on doing this.
Google Chrome Web Browser
This was great for working out HTML header problems

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)

Story

Read more

Schematics

Circuits for Proof of concept and prototype

Mud map of circuits. All basic stuff

Code

PeroxideInjector.ino

C/C++
Proof of Concept First attempt
As simple as possible, really rude/rough hack.
Hardware:- Uno with relay board
#define MAX_TIME 32000
#define MIN_TIME 1 
#define BUFF_MAX 32

const byte RELAY1 = 7 ;
const byte RELAY2 = 6 ;
const byte RELAY3 = 5 ;
const byte RELAY4 = 4 ;

#include <avr/wdt.h>
#include <EEPROM.h>

int lOnTime ;
int lOffTime ;
int lOnCounter ;
int lOffCounter ;
int lCyclesCounter ;
bool bState ;
int iCurPos ;

int LoadLongFromEEPROM(int address,long minval,long maxval, long defaultval){
long tmp ;  
  EEPROM.get(0 + (address * sizeof(long)) , tmp );
  if (( tmp < minval ) || ( tmp > maxval )) {
    tmp = defaultval ;
    EEPROM.put(0 + (address * sizeof(long)) , tmp );
  }
  return(tmp);  
}


void setup() {
   
  pinMode(RELAY1,OUTPUT);
  pinMode(RELAY2,OUTPUT);
  pinMode(RELAY3,OUTPUT);
  pinMode(RELAY4,OUTPUT);
  digitalWrite(RELAY1,LOW); 
  digitalWrite(RELAY2,LOW); 
  digitalWrite(RELAY3,LOW); 
  digitalWrite(RELAY4,LOW); 

}

void loop() {

  digitalWrite(RELAY1,HIGH); 
  digitalWrite(RELAY2,HIGH); 
  digitalWrite(RELAY3,HIGH); 
  digitalWrite(RELAY4,HIGH); 
  delay(2000);
  digitalWrite(RELAY1,LOW); 
  delay(2000);
  digitalWrite(RELAY2,LOW); 
  delay(4000);
  digitalWrite(RELAY3,LOW); 
  delay(7000);
  digitalWrite(RELAY4,LOW); 
  delay(15000);
}

h2o2Injector.ino Ver 2.0

C/C++
This a WeMos D1 R2 version with an WiFi access point / web interface.
You control the pump via the web browser on a mobile phone or tablet.
#include <ESP8266WiFi.h>
#include <WiFiClient.h> 
#include <ESP8266WebServer.h>
#include <TimeLib.h>
//#include <Wire.h>
//#include <SPI.h>
#include <EEPROM.h>

const byte MOTOR1 = D7 ;   // PWM
const byte BEEPER = D8 ;   // pezo
const byte SCOPE_PIN = D5 ;   // for cycle timer measure


/* Set these to your desired credentials. */
const char *ssid = "Injector";
const char *password = "password";
byte rtc_sec ;
byte rtc_min ;
byte rtc_hour ;
int iOut1 = 0 ;
int iOut2 = 0 ;
long lPrime ;
long lOnTime ;
long lOffTime ;
long lOnCounter ;
long lOffCounter ;
int lCyclesCounter ;
int lCycles ;
float dblMLPerSecond ; 
float dblQty ; 
float dblCurrentQty ; 
bool bState ;
bool bOnOff = false ;
bool bPrime = false ;
int iCurPos ;
long lTimePrev ;
long lTimePrev2 ;
int iFinish = 30 ;
ESP8266WebServer server(80);

/* Just a little test message.  Go to http://192.168.4.1 in a web browser
 * connected to this access point to see it.
 */
void handleRoot() {
  boolean currentLineIsBlank = true;
  char buff[40] ;
  long  i = 0 ;
  Serial.println("web Request");
  for (uint8_t j=0; j<server.args(); j++){
    i = String(server.argName(j)).indexOf("command");
    if (i != -1){  // have a request to set the time zone
      switch (String(server.arg(j)).toInt()){
        case 1:  // load values
          LoadParamsFromEEPROM(true);
          Serial.println("Load from EEPROM");
        break;
        case 2: // Save values
          LoadParamsFromEEPROM(false);
          Serial.println("Save to EEPROM");
        break;
        case 3: // prime command
          bPrime = true ;
          Serial.println("Prime Pumps");
        break;
        case 4: // Clear current qty
          dblCurrentQty = 0 ;
          Serial.println("Clear current qty");
        break;
        case 5: // Clear current qty
          lCyclesCounter = 0 ;
          Serial.println("Clear current cycles");
        break;
      }  
    }
    i = String(server.argName(j)).indexOf("prime");
    if (i != -1){  // have a request to set the time zone
      lPrime = String(server.arg(j)).toInt() ;
      if (lPrime > 32000 ){
        lPrime = 32000 ;
      }
      if ( lPrime < 500 ){
        lPrime = 500 ;
      }
    }        

    i = String(server.argName(j)).indexOf("timon");
    if (i != -1){  // have a request to set the time zone
      lOnTime = String(server.arg(j)).toInt() ;
      if ( lOnTime > 100000 ){
        lOnTime = 100000 ;
      }
      if ( lOnTime < 1000 ){
        lOnTime = 1000 ;
      }
    }        
    i = String(server.argName(j)).indexOf("timof");
    if (i != -1){  // have a request to set the time zone
      lOffTime = String(server.arg(j)).toInt() ;
      if ( lOffTime > 100000 ){
        lOffTime = 100000 ;
      }
      if ( lOffTime < 1000 ){
        lOffTime = 1000 ;
      }
    }        
    i = String(server.argName(j)).indexOf("cycnt");
    if (i != -1){  // have a request to set the time zone
      lCycles = String(server.arg(j)).toInt() ;
      if ( lCycles > 32000 ){
        lCycles = 32000 ;
      }
      if ( lCycles < 0 ){
        lCycles = 0 ;
      }
    }        
    i = String(server.argName(j)).indexOf("pumpea");
    if (i != -1){  // have a request to set the time zone
      if ( String(server.arg(j)).toInt()== 1){ //on
        bOnOff = true ; 
        bState = true ;       
      }else{
        bOnOff = false ;
        bState = false ;                
      }
    }        

    i = String(server.argName(j)).indexOf("pmlps");    // pump ml per second
    if (i != -1){  // have a request to set the latitude
      dblMLPerSecond = String(server.arg(j)).toFloat() ;
      if ( dblMLPerSecond < 50){
        dblMLPerSecond = 50 ;
      }
      if ( dblMLPerSecond > 10000 ){
        dblMLPerSecond = 10000 ;        
      }
    }        
    i = String(server.argName(j)).indexOf("quant");    // maximum qty to be pumped
    if (i != -1){  // have a request to set the latitude
      dblQty = String(server.arg(j)).toFloat() ;
      if ( dblQty < 1){
        dblQty = 1 ;
      }
      if ( dblQty > 1000 ){
        dblQty = 1000 ;        
      }
    }        
    
  }
  
//  server.send(200, "text/html", "<h1>YO DUDE - You are connected</h1>");
  server.sendHeader("HTTP/1.1 200 OK","Content-Type: text/html",true);
  server.sendContent("<!DOCTYPE HTML>");
  server.sendContent("<head><title>Team Trouble - Chemical Injector</title>");
  server.sendContent("<meta name=viewport content='width=320, auto inital-scale=1'>");

  server.sendContent("</head><body><html><center><h2>Chemical Injector Mk2.0</h2>");

  server.sendContent("<a href='/'>Refresh</a><br><br>") ;         
  server.sendContent("<a href='/?command=2'>Save Parameters to EEPROM</a><br>") ;         

  server.sendContent("<table border=1 title='Pump Control'>");
  server.sendContent("<tr><th> Parameter</th><th>Value</th><th>.</th></tr>");

  server.sendContent("<form method=get action=/><tr><td>Pump Control</td><td align=center><select name='pumpea'>") ;
  if (bOnOff){
    server.sendContent("<option value='0' >OFF<option value='1' SELECTED>ON"); 
  }else{
    server.sendContent("<option value='0' SELECTED>OFF<option value='1'>ON"); 
  }
  server.sendContent("</select></td><td><input type='submit' value='SET'></td></tr></form>");

  server.sendContent("<form method=get action=" + server.uri() + "><tr><td>Time On</td><td align=center>") ; 
  server.sendContent("<input type='text' name='timon' value='" + String(lOnTime) + "' size=6></td><td><input type='submit' value='SET'></td></tr></form>");

  server.sendContent("<form method=get action=" + server.uri() + "><tr><td>Time Off</td><td align=center>") ; 
  server.sendContent("<input type='text' name='timof' value='" + String(lOffTime) + "' size=6></td><td><input type='submit' value='SET'></td></tr></form>");

  server.sendContent("<form method=get action=" + server.uri() + "><tr><td><a href='/?command=3' title='Click to Prime Pump'>Prime Time (ms)</a></td><td align=center>") ; 
  server.sendContent("<input type='text' name='prime' value='" + String(lPrime) + "' size=6></td><td><input type='submit' value='SET'></td></tr></form>");

  server.sendContent("<form method=get action=" + server.uri() + "><tr><td>Max Cycles</td><td align=center>") ; 
  server.sendContent("<input type='text' name='cycnt' value='" + String(lCycles) + "' size=6></td><td><input type='submit' value='SET'></td></tr></form>");

  server.sendContent("<form method=get action=" + server.uri() + "><tr><td>ml Per Second</td><td align=center>") ; 
  server.sendContent("<input type='text' name='pmlps' value='" + String(dblMLPerSecond,2) + "' size=6></td><td><input type='submit' value='SET'></td></tr></form>");

  server.sendContent("<form method=get action=" + server.uri() + "><tr><td>Max Quanity (l)</td><td align=center>") ; 
  server.sendContent("<input type='text' name='quant' value='" + String(dblQty,2) + "' size=6></td><td><input type='submit' value='SET'></td></tr></form>");

  server.sendContent("<tr><td><a href='/?command=4' title='click to clear'>Current Qty</a></td><td align=center>" + String(dblCurrentQty,3) + "</td><td>(l)</td></tr>");
//  server.sendContent("<tr><td>Current On Counter</td><td align=center>" + String(lOnCounter) + "</td><td>(ms)</td></tr>");
//  server.sendContent("<tr><td>Current Off Counter</td><td align=center>" + String(lOffCounter) + "</td><td>(ms)</td></tr>");
  server.sendContent("<tr><td><a href='/?command=5' title='click to clear'>Current Cycles</td><td align=center>" + String(lCyclesCounter) + "</a></td><td>.</td></tr>");
  server.sendContent("</table>");
  server.sendContent("<a href='/?command=1'>Load Parameters from EEPROM</a><br>") ;         
  server.sendContent("</body></html>");
  
}

void FloatToModbusWords(float src_value , uint16_t * dest_lo , uint16_t * dest_hi ) {
  uint16_t tempdata[2] ;
  float *tf ;
  tf = (float * )&tempdata[0]  ;
  *tf = src_value ;
  *dest_lo = tempdata[1] ;
  *dest_hi = tempdata[0] ;
}
float FloatFromModbusWords( uint16_t dest_lo , uint16_t dest_hi ) {
  uint16_t tempdata[2] ;
  float *tf ;
  tf = (float * )&tempdata[0]  ;
  tempdata[1] = dest_lo ;
  tempdata[0] = dest_hi  ;
  return (*tf) ;
}
int NumberOK (float target) {
  int tmp = 0 ;
  tmp = isnan(target);
  if ( tmp != 1 ) {
    tmp = isinf(target);
  }
  return (tmp);
}

int LoadIntFromEEPROM(int address,int minval,int maxval, int defaultval){
int dummy1 = 0 ; // belt and braces ... dont know which way the stack works
int tmp ;  
int dummy2 = 0 ; // yep write this one as well ... maybe
int i ;
byte  *ba ;  

  ba =(byte *)&tmp ; // set the byte array to point at the long
  for ( i = 0 ; i < 4 ; i++ ){
    ba[i] = EEPROM.read((address*4)+i);  // read the 4 bytes
  }
  if (( tmp < minval ) || ( tmp > maxval )) {
    tmp = defaultval ;
    for ( i = 0 ; i < 4 ; i++ ){
      EEPROM.write((address*4)+i , ba[i] );
      EEPROM.commit();  // save changes in one go ???    
    }  
  }
  return(tmp);  
}
int LoadLongFromEEPROM(int address,long minval,long maxval, long defaultval){
long tmp ;
int i ;
byte  *ba ;  

  ba =(byte *)&tmp ; // set the byte array to point at the long
  for ( i = 0 ; i < 4 ; i++ ){
    ba[i] = EEPROM.read((address*4)+i);  // read the 4 bytes
  }
  if (( tmp < minval ) || ( tmp > maxval )) {
    tmp = defaultval ;
    for ( i = 0 ; i < 4 ; i++ ){
      EEPROM.write((address*4)+i , ba[i] );
      EEPROM.commit();  // save changes in one go ???    
    }  
  }
  return(tmp);  
}
float LoadFloatFromEEPROM(int address,float minval,float maxval, float defaultval){
float tmp ;  
int i ;
byte  *ba ;  

  ba =(byte *)&tmp ; // set the byte array to point at the long
  for ( i = 0 ; i < 4 ; i++ ){
    ba[i] = EEPROM.read((address*4)+i);  // read the 4 bytes
  }
  if (( tmp < minval ) || ( tmp > maxval )) {
    tmp = defaultval ;
    for ( i = 0 ; i < 4 ; i++ ){
      EEPROM.write((address*4)+i , ba[i] );
      EEPROM.commit();  // save changes in one go ???    
    }  
  }
  return(tmp);  
}
void SaveFloatToEEPROM(int address,float val){
float tmp ;  
int i ;
byte  *ba ;  
  tmp = val ;
  ba =(byte *)&tmp ; // set the byte array to point at the long
  for ( i = 0 ; i < 4 ; i++ ){
    EEPROM.write((address*4)+i , ba[i] );
  }  
}
void SaveLongToEEPROM(int address,long val){
long tmp ;  
int i ;
byte  *ba ;  
  tmp = val ;
  ba =(byte *)&tmp ; // set the byte array to point at the long
  for ( i = 0 ; i < 4 ; i++ ){
    EEPROM.write((address*4)+i , ba[i] );
  }  
}
void SaveIntToEEPROM(int address,int val){
int dummy1 = 0 ;
int tmp ;  
int dummy2 = 0 ;
int i ;
byte  *ba ;  
  tmp = val ;
  ba =(byte *)&tmp ; // set the byte array to point at the long
  for ( i = 0 ; i < 4 ; i++ ){
    EEPROM.write((address*4)+i , ba[i] );
  }  
}

void LoadParamsFromEEPROM(bool bLoad){
  if ( bLoad ) {
    lOnTime = LoadLongFromEEPROM(0,500,100000,4000);           // On time in ms
    lOffTime = LoadLongFromEEPROM(1,500,100000, 26000);        //   Off time in ms
    lCycles  = LoadLongFromEEPROM(2,0,100000,32000);           // maximum no of cycles
        
    dblMLPerSecond = LoadFloatFromEEPROM(3,10,1000.0,100.0);   // pump rate
    dblQty = LoadFloatFromEEPROM(4,5,1000,10);                 // Total Qty Required Literds
    lPrime = LoadLongFromEEPROM(5,500,32000, 15000);            //   Off time in ms
        
  }else{
    SaveLongToEEPROM(0,lOnTime );
    SaveLongToEEPROM(1 , lOffTime );
    SaveLongToEEPROM(2, lCycles );
    SaveFloatToEEPROM(3 , dblMLPerSecond );
    SaveFloatToEEPROM(4 , dblQty );
    SaveLongToEEPROM(5 , lPrime );
    EEPROM.commit();  // save changes in one go ???
  }
}


void setup() {
  delay(1000);
  Serial.begin(115200);
  Serial.setDebugOutput(true);  
  Serial.println("\n");
  Serial.print("Configuring access point...");
  /* You can remove the password parameter if you want the AP to be open. */
  WiFi.softAP(ssid, password);

  IPAddress myIP = WiFi.softAPIP();
  Serial.print("AP IP address: ");
  Serial.println(myIP);
  server.on("/", handleRoot);
  server.begin();
  Serial.println("HTTP server started");

  pinMode(BUILTIN_LED,OUTPUT);
  pinMode(MOTOR1,OUTPUT);
  pinMode(BEEPER,OUTPUT);
  pinMode(SCOPE_PIN,OUTPUT);
  digitalWrite(MOTOR1,LOW);   // Off I think
  digitalWrite(BEEPER,LOW); 
  
  EEPROM.begin(512);
  LoadParamsFromEEPROM(true);
}

void loop() {
long lTime ;  
  server.handleClient();

  lTime = millis() ;
  lOffCounter = constrain(lOffCounter,0, lOffTime);
  lOnCounter = constrain(lOnCounter,0, lOnTime);
  lCyclesCounter = constrain(lCyclesCounter,0, lCycles);

  if ((dblCurrentQty >= dblQty )|| (!bOnOff ) || (lCyclesCounter >= lCycles)){
    bState = false ;
    if ( bOnOff ){
      iFinish = 1 ;
    }
    bOnOff = false ;
  }
  if (( lTimePrev2  ) < lTime ){  // look every 2 ms
    if ((bState)|| (bPrime )){           // soft starter
      if ( iOut1 < 1023 ){  // speed up
        iOut1 ++ ;
      }
    }else{
      if ( iOut1 > 0  ){   // slow down 
        iOut1 -- ;
      }
    }
    lTimePrev2 = lTime ;
  }
  if (lTimePrev > ( lTime + 100000 )){ // has wrapped around so back to zero
    lTimePrev = lTime ; // skip a bit 
    Serial.println("Wrap around");
  }
  
  if (( lTimePrev + 100 ) < lTime ){  // look every 1/10 of a second ish
    if (( bState) || ( bPrime )) {  // true is on - assume called once per second
      lOnCounter+= (lTime - lTimePrev)  ;
      if (bPrime){
        if ( lOnCounter >= lPrime ){
          Serial.println("prime complete");
          lOnCounter = 0 ;
          bPrime = false ;
          bState = false ;
        }      
      }else{
        if ( lOnCounter >= lOnTime ){
          lOnCounter = 0 ;
          lCyclesCounter++ ;
          bState = !bState ;
        }
      }
      dblCurrentQty += ( dblMLPerSecond * (float(lTime - lTimePrev)/1000))/1000 ; //work out how much
    }else{
      if (bOnOff){
        lOffCounter += (lTime - lTimePrev) ;
        digitalWrite(BUILTIN_LED,!digitalRead(BUILTIN_LED));
        if ( lOffCounter >= lOffTime ) {
          lOffCounter = 0 ;      
          bState = !bState ;
        }
      }else{
          lOffCounter = 0 ;              
      }
    }
    lTimePrev += 100 ; 
  }

  analogWrite(MOTOR1,iOut1) ; // soft start the motor
//  analogWrite(MOTOR2,iOut2) ; // soft start the motor
  
  if (rtc_sec != second() )  {
    digitalWrite(BUILTIN_LED,!digitalRead(BUILTIN_LED));
    if (iFinish < 31 ){
      iFinish++ ;
    }
    if ((iFinish < 30 )&& (iFinish >0 )){
      digitalWrite(BEEPER,!digitalRead(BEEPER));
    }else{
      digitalWrite(BEEPER,0);      
    }
    
    rtc_sec = second();
//    if ( iOut1 > 0 )
//      Serial.println(iOut1);
  }
  digitalWrite(SCOPE_PIN,!digitalRead(SCOPE_PIN));  // my scope says we are doing this loop at an unreasonable speed except when we do web stuff
  
}

h2o2Injector.ino Ver 2.1

C/C++
This version can drive a relay or FET board.
#include <ESP8266WiFi.h>
#include <WiFiClient.h> 
#include <ESP8266WebServer.h>
#include <ESP8266mDNS.h>
//#include <DNSServer.h>
#include <TimeLib.h>
//#include <Wire.h>
//#include <SPI.h>
#include <EEPROM.h>

const byte MOTOR1 = D7 ;   // PWM
const byte BEEPER = D8 ;   // pezo
const byte SCOPE_PIN = D5 ;   // for cycle timer measure


/* Set these to your desired credentials. */
const char *ssid = "Injector";
const char *password = "password";
const char *host = "injector";

byte rtc_sec ;
byte rtc_min ;
byte rtc_hour ;
int iOut1 = 0 ;
int iOut2 = 0 ;
long lPrime ;
long lOnTime ;
long lOffTime ;
long lOnCounter ;
long lOffCounter ;
int lCyclesCounter ;
int lCycles ;
float dblMLPerSecond ; 
float dblQty ; 
float dblCurrentQty ; 
bool bState ;
bool bOnOff = false ;
bool bPrime = false ;
int iCurPos ;
long lTimePrev ;
long lTimePrev2 ;
int iFinish = 30 ;
long PWM_inc = 5 ;

ESP8266WebServer server(80);
//DNSServer dnsServer;

/* Just a little test message.  Go to http://192.168.4.1 in a web browser
 * connected to this access point to see it.
 */
void handleNotFound(){
  String message = "Seriously - No way DUDE\n\n";
  message += "URI: ";
  message += server.uri();
  message += "\nMethod: ";
  message += (server.method() == HTTP_GET)?"GET":"POST";
  message += "\nArguments: ";
  message += server.args();
  message += "\n";
  for (uint8_t i=0; i<server.args(); i++){
    message += " NAME:"+server.argName(i) + "\n VALUE:" + server.arg(i) + "\n";
  }
  server.send(404, "text/plain", message);
  Serial.print(message);
}

 
void handleRoot() {
  boolean currentLineIsBlank = true;
  char buff[40] ;
  long  i = 0 ;

  String message = "Web Request URI: ";
  message += server.uri();
  message += "\nMethod: ";
  message += (server.method() == HTTP_GET)?"GET":"POST";
  message += "\nArguments: ";
  message += server.args();
  message += "\n";
  for (uint8_t i=0; i<server.args(); i++){
    message += " NAME:"+server.argName(i) + "\n VALUE:" + server.arg(i) + "\n";
  }
  Serial.println(message);
  
  for (uint8_t j=0; j<server.args(); j++){
    i = String(server.argName(j)).indexOf("command");
    if (i != -1){  // have a request to set the time zone
      switch (String(server.arg(j)).toInt()){
        case 1:  // load values
          LoadParamsFromEEPROM(true);
          Serial.println("Load from EEPROM");
        break;
        case 2: // Save values
          LoadParamsFromEEPROM(false);
          Serial.println("Save to EEPROM");
        break;
        case 3: // prime command
          bPrime = true ;
          Serial.println("Prime Pumps");
        break;
        case 4: // Clear current qty
          dblCurrentQty = 0 ;
          Serial.println("Clear current qty");
        break;
        case 5: // Clear current qty
          lCyclesCounter = 0 ;
          Serial.println("Clear current cycles");
        break;
      }  
    }
    i = String(server.argName(j)).indexOf("prime");
    if (i != -1){  // have a request to set the time zone
      lPrime = String(server.arg(j)).toInt() ;
      if (lPrime > 32000 ){
        lPrime = 32000 ;
      }
      if ( lPrime < 500 ){
        lPrime = 500 ;
      }
    }        

    i = String(server.argName(j)).indexOf("timon");
    if (i != -1){  // have a request to set the time zone
      lOnTime = String(server.arg(j)).toInt() ;
      if ( lOnTime > 100000 ){
        lOnTime = 100000 ;
      }
      if ( lOnTime < 1000 ){
        lOnTime = 1000 ;
      }
    }        
    i = String(server.argName(j)).indexOf("timof");
    if (i != -1){  // have a request to set the time zone
      lOffTime = String(server.arg(j)).toInt() ;
      if ( lOffTime > 100000 ){
        lOffTime = 100000 ;
      }
      if ( lOffTime < 1000 ){
        lOffTime = 1000 ;
      }
    }        
    i = String(server.argName(j)).indexOf("cycnt");
    if (i != -1){  // have a request to set the time zone
      lCycles = String(server.arg(j)).toInt() ;
      if ( lCycles > 32000 ){
        lCycles = 32000 ;
      }
      if ( lCycles < 0 ){
        lCycles = 0 ;
      }
    }        
    i = String(server.argName(j)).indexOf("pwminc");
    if (i != -1){  // have a request to set the time zone
      PWM_inc = String(server.arg(j)).toInt() ;
      if ( PWM_inc > 50 ){
        PWM_inc = 50 ;
      }
      if ( PWM_inc < 0 ){
        PWM_inc = 0 ;
      }
    }        
    i = String(server.argName(j)).indexOf("pumpea");
    if (i != -1){  // have a request to set the time zone
      if ( String(server.arg(j)).toInt()== 1){ //on
        bOnOff = true ; 
        bState = true ;       
      }else{
        bOnOff = false ;
        bState = false ;                
      }
    }        

    i = String(server.argName(j)).indexOf("pmlps");    // pump ml per second
    if (i != -1){  // have a request to set the latitude
      dblMLPerSecond = String(server.arg(j)).toFloat() ;
      if ( dblMLPerSecond < 50){
        dblMLPerSecond = 50 ;
      }
      if ( dblMLPerSecond > 10000 ){
        dblMLPerSecond = 10000 ;        
      }
    }        
    i = String(server.argName(j)).indexOf("quant");    // maximum qty to be pumped
    if (i != -1){  // have a request to set the latitude
      dblQty = String(server.arg(j)).toFloat() ;
      if ( dblQty < 0){
        dblQty = 0 ;
      }
      if ( dblQty > 100000 ){
        dblQty = 100000 ;        
      }
    }        
    
  }
  
  server.sendHeader("Server","ESP8266-on-steroids",false);
  server.sendHeader("X-Powered-by","Dougal-1.0",false);
  server.setContentLength(CONTENT_LENGTH_UNKNOWN);
  server.send(200, "text/html", "");
  server.sendContent("<!DOCTYPE HTML>");
  server.sendContent("<head><title>Team Trouble - Chemical Injector</title>");
  server.sendContent("<meta name=viewport content='width=320, auto inital-scale=1'>");

  server.sendContent("</head><body><html><center><h2>Chemical Injector Mk2.1</h2>");

  server.sendContent("<a href='/'>Refresh</a><br><br>") ;         
  server.sendContent("<a href='/?command=2'>Save Parameters to EEPROM</a><br>") ;         

  server.sendContent("<table border=1 title='Pump Control'>");
  server.sendContent("<tr><th> Parameter</th><th>Value</th><th>.</th></tr>");

  server.sendContent("<form method=get action=/><tr><td>Pump Control</td><td align=center><select name='pumpea'>") ;
  if (bOnOff){
    server.sendContent("<option value='0' >OFF<option value='1' SELECTED>ON"); 
  }else{
    server.sendContent("<option value='0' SELECTED>OFF<option value='1'>ON"); 
  }
  server.sendContent("</select></td><td><input type='submit' value='SET'></td></tr></form>");

  server.sendContent("<form method=get action=" + server.uri() + "><tr><td>Time On (ms)</td><td align=center>") ; 
  server.sendContent("<input type='text' name='timon' value='" + String(lOnTime) + "' size=6></td><td><input type='submit' value='SET'></td></tr></form>");

  server.sendContent("<form method=get action=" + server.uri() + "><tr><td>Time Off (ms)</td><td align=center>") ; 
  server.sendContent("<input type='text' name='timof' value='" + String(lOffTime) + "' size=6></td><td><input type='submit' value='SET'></td></tr></form>");

  server.sendContent("<form method=get action=" + server.uri() + "><tr><td><a href='/?command=3' title='Click to Prime Pump'>Prime Time (ms)</a></td><td align=center>") ; 
  server.sendContent("<input type='text' name='prime' value='" + String(lPrime) + "' size=6></td><td><input type='submit' value='SET'></td></tr></form>");

  server.sendContent("<form method=get action=" + server.uri() + "><tr><td>Max Cycles</td><td align=center>") ; 
  server.sendContent("<input type='text' name='cycnt' value='" + String(lCycles) + "' size=6></td><td><input type='submit' value='SET'></td></tr></form>");

  server.sendContent("<form method=get action=" + server.uri() + "><tr><td>ml Per Second</td><td align=center>") ; 
  server.sendContent("<input type='text' name='pmlps' value='" + String(dblMLPerSecond,2) + "' size=6></td><td><input type='submit' value='SET'></td></tr></form>");

  server.sendContent("<form method=get action=" + server.uri() + "><tr><td>Max Quanity (l)</td><td align=center>") ; 
  server.sendContent("<input type='text' name='quant' value='" + String(dblQty,2) + "' size=6></td><td><input type='submit' value='SET'></td></tr></form>");

  server.sendContent("<form method=get action=" + server.uri() + "><tr><td>PWM inc (0 Disable)</td><td align=center>") ; 
  server.sendContent("<input type='text' name='pwminc' value='" + String(PWM_inc) + "' size=6></td><td><input type='submit' value='SET'></td></tr></form>");
  
  server.sendContent("<tr><td><a href='/?command=4' title='click to clear'>Current Qty</a></td><td align=center>" + String(dblCurrentQty,3) + "</td><td>(l)</td></tr>");
//  server.sendContent("<tr><td>Current On Counter</td><td align=center>" + String(lOnCounter) + "</td><td>(ms)</td></tr>");
//  server.sendContent("<tr><td>Current Off Counter</td><td align=center>" + String(lOffCounter) + "</td><td>(ms)</td></tr>");
  server.sendContent("<tr><td><a href='/?command=5' title='click to clear'>Current Cycles</td><td align=center>" + String(lCyclesCounter) + "</a></td><td>.</td></tr>");
  server.sendContent("</table>");
  server.sendContent("<a href='/?command=1'>Load Parameters from EEPROM</a><br>") ;         
  server.sendContent("</body></html>\r\n");
  
}

void FloatToModbusWords(float src_value , uint16_t * dest_lo , uint16_t * dest_hi ) {
  uint16_t tempdata[2] ;
  float *tf ;
  tf = (float * )&tempdata[0]  ;
  *tf = src_value ;
  *dest_lo = tempdata[1] ;
  *dest_hi = tempdata[0] ;
}
float FloatFromModbusWords( uint16_t dest_lo , uint16_t dest_hi ) {
  uint16_t tempdata[2] ;
  float *tf ;
  tf = (float * )&tempdata[0]  ;
  tempdata[1] = dest_lo ;
  tempdata[0] = dest_hi  ;
  return (*tf) ;
}
int NumberOK (float target) {
  int tmp = 0 ;
  tmp = isnan(target);
  if ( tmp != 1 ) {
    tmp = isinf(target);
  }
  return (tmp);
}

int LoadIntFromEEPROM(int address,int minval,int maxval, int defaultval){
int dummy1 = 0 ; // belt and braces ... dont know which way the stack works
int tmp ;  
int dummy2 = 0 ; // yep write this one as well ... maybe
int i ;
byte  *ba ;  

  ba =(byte *)&tmp ; // set the byte array to point at the long
  for ( i = 0 ; i < 4 ; i++ ){
    ba[i] = EEPROM.read((address*4)+i);  // read the 4 bytes
  }
  if (( tmp < minval ) || ( tmp > maxval )) {
    tmp = defaultval ;
    for ( i = 0 ; i < 4 ; i++ ){
      EEPROM.write((address*4)+i , ba[i] );
      EEPROM.commit();  // save changes in one go ???    
    }  
  }
  return(tmp);  
}
int LoadLongFromEEPROM(int address,long minval,long maxval, long defaultval){
long tmp ;
int i ;
byte  *ba ;  

  ba =(byte *)&tmp ; // set the byte array to point at the long
  for ( i = 0 ; i < 4 ; i++ ){
    ba[i] = EEPROM.read((address*4)+i);  // read the 4 bytes
  }
  if (( tmp < minval ) || ( tmp > maxval )) {
    tmp = defaultval ;
    for ( i = 0 ; i < 4 ; i++ ){
      EEPROM.write((address*4)+i , ba[i] );
      EEPROM.commit();  // save changes in one go ???    
    }  
  }
  return(tmp);  
}
float LoadFloatFromEEPROM(int address,float minval,float maxval, float defaultval){
float tmp ;  
int i ;
byte  *ba ;  

  ba =(byte *)&tmp ; // set the byte array to point at the long
  for ( i = 0 ; i < 4 ; i++ ){
    ba[i] = EEPROM.read((address*4)+i);  // read the 4 bytes
  }
  if (( tmp < minval ) || ( tmp > maxval )) {
    tmp = defaultval ;
    for ( i = 0 ; i < 4 ; i++ ){
      EEPROM.write((address*4)+i , ba[i] );
      EEPROM.commit();  // save changes in one go ???    
    }  
  }
  return(tmp);  
}
void SaveFloatToEEPROM(int address,float val){
float tmp ;  
int i ;
byte  *ba ;  
  tmp = val ;
  ba =(byte *)&tmp ; // set the byte array to point at the long
  for ( i = 0 ; i < 4 ; i++ ){
    EEPROM.write((address*4)+i , ba[i] );
  }  
}
void SaveLongToEEPROM(int address,long val){
long tmp ;  
int i ;
byte  *ba ;  
  tmp = val ;
  ba =(byte *)&tmp ; // set the byte array to point at the long
  for ( i = 0 ; i < 4 ; i++ ){
    EEPROM.write((address*4)+i , ba[i] );
  }  
}
void SaveIntToEEPROM(int address,int val){
int dummy1 = 0 ;
int tmp ;  
int dummy2 = 0 ;
int i ;
byte  *ba ;  
  tmp = val ;
  ba =(byte *)&tmp ; // set the byte array to point at the long
  for ( i = 0 ; i < 4 ; i++ ){
    EEPROM.write((address*4)+i , ba[i] );
  }  
}

void LoadParamsFromEEPROM(bool bLoad){
long lTmp ;  
  if ( bLoad ) {
    lOnTime = LoadLongFromEEPROM(0,500,100000,4000);           // On time in ms
    lOffTime = LoadLongFromEEPROM(1,500,100000, 26000);        //   Off time in ms
    lCycles  = LoadLongFromEEPROM(2,0,100000,32000);           // maximum no of cycles
        
    dblMLPerSecond = LoadFloatFromEEPROM(3,10,1000.0,100.0);   // pump rate
    dblQty = LoadFloatFromEEPROM(4,0,100000,0);                 // Total Qty Required Literds
    lPrime = LoadLongFromEEPROM(5,500,32000, 1000);            //   Off time in ms
    lTmp = LoadLongFromEEPROM(6,1,10, 0);                   //   start and run
    if (lTmp != 0){
      bOnOff = true ;
    }    
    PWM_inc = LoadLongFromEEPROM(7,0,50, 0);                   //   pwm increment 0 is no PWM
  }else{
    SaveLongToEEPROM(0,lOnTime );
    SaveLongToEEPROM(1 , lOffTime );
    SaveLongToEEPROM(2, lCycles );
    SaveFloatToEEPROM(3 , dblMLPerSecond );
    SaveFloatToEEPROM(4 , dblQty );
    SaveLongToEEPROM(5 , lPrime );
    if ( bOnOff ){
      SaveLongToEEPROM(6 , 1 );
    }else{
      SaveLongToEEPROM(6 , 0 );      
    }
    SaveLongToEEPROM(7, PWM_inc );
    EEPROM.commit();  // save changes in one go ???
  }
}


void setup() {
  delay(1000);
  Serial.begin(115200);
  Serial.setDebugOutput(true);  
  Serial.print("\nChip ID ");
  Serial.println(ESP.getChipId(), HEX);
  Serial.println("Configuring access point...");
  /* You can remove the password parameter if you want the AP to be open. */
  WiFi.softAP(ssid, password);

  if (MDNS.begin(host)) {
    MDNS.addService("http", "tcp", 80);
    Serial.println("MDNS responder started");
    Serial.print("You can now connect to http://");
    Serial.print(host);
    Serial.println(".local");
  }

  IPAddress myIP = WiFi.softAPIP();
  Serial.print("AP IP address: ");
  Serial.println(myIP);
  server.on("/", handleRoot);
  server.onNotFound(handleNotFound);  
  server.begin();
  Serial.println("HTTP server started");

  pinMode(BUILTIN_LED,OUTPUT);
  pinMode(MOTOR1,OUTPUT);
  pinMode(BEEPER,OUTPUT);
  pinMode(SCOPE_PIN,OUTPUT);
  digitalWrite(MOTOR1,LOW);   // Off I think
  digitalWrite(BEEPER,LOW); 
  
  EEPROM.begin(512);
  LoadParamsFromEEPROM(true);
//  dnsServer.setTTL(300);
//  dnsServer.setErrorReplyCode(DNSReplyCode::ServerFailure);
//  dnsServer.start(53,"injector.local",myIP);
}

void loop() {
long lTime ;  
  server.handleClient();

  lTime = millis() ;
  lOffCounter = constrain(lOffCounter,0, lOffTime);
  if (lPrime > lOnTime ){
    lOnCounter = constrain(lOnCounter,0, lPrime);    
  }else{
    lOnCounter = constrain(lOnCounter,0, lOnTime);
  }
  lCyclesCounter = constrain(lCyclesCounter,0, 100000);

  if (( lCycles > 0 ) || ( dblQty > 0 )){
    if ((dblCurrentQty >= dblQty )|| (!bOnOff ) || (lCyclesCounter >= lCycles)){
      bState = false ;
      if ( bOnOff ){
        iFinish = 1 ;
      }
      bOnOff = false ;
    }
  }
  if (( lTimePrev2  ) < lTime ){  // look every 2 ms
    if ((bState)|| (bPrime )){           // soft starter
      if ( iOut1 < 1023 ){  // speed up
        iOut1 =iOut1 + PWM_inc ;
        if ( iOut1 > 1023 ){
          iOut1 = 1023 ;
        }
      }
    }else{
      if ( iOut1 > 0  ){   // slow down 
        iOut1 = iOut1 - PWM_inc ;
        if (iOut1 < 0 ){
          iOut1 = 0 ;
        }
      }
    }
    lTimePrev2 = lTime ;
  }
  if (lTimePrev > ( lTime + 100000 )){ // has wrapped around so back to zero
    lTimePrev = lTime ; // skip a bit 
    Serial.println("Wrap around");
  }
  
  if (( lTimePrev + 100 ) < lTime ){  // look every 1/10 of a second ish
    if (( bState) || ( bPrime )) {  // true is on - assume called once per second
      lOnCounter+= (lTime - lTimePrev)  ;
      if (bPrime){
        if ( lOnCounter >= lPrime ){
          Serial.println("prime complete");
          lOnCounter = 0 ;
          bPrime = false ;
          bState = false ;
        }      
      }else{
        if ( lOnCounter >= lOnTime ){
          lOnCounter = 0 ;
          lCyclesCounter++ ;
          bState = !bState ;
        }
      }
      dblCurrentQty += ( dblMLPerSecond * (float(lTime - lTimePrev)/1000))/1000 ; //work out how much
    }else{
      if (bOnOff){
        lOffCounter += (lTime - lTimePrev) ;
        digitalWrite(BUILTIN_LED,!digitalRead(BUILTIN_LED));
        if ( lOffCounter >= lOffTime ) {
          lOffCounter = 0 ;      
          bState = !bState ;
        }
      }else{
          lOffCounter = 0 ;              
      }
    }
    lTimePrev += 100 ; 
  }
  if ( PWM_inc > 0 ){
    analogWrite(MOTOR1,iOut1) ; // soft start the motor
  }else{
    if (bState) {
      digitalWrite(MOTOR1,true) ; // using a realy clunck clunk
    }else{
      digitalWrite(MOTOR1,false) ;     
    }
  }
//  analogWrite(MOTOR2,iOut2) ; // soft start the motor
  
  if (rtc_sec != second() )  {
    digitalWrite(BUILTIN_LED,!digitalRead(BUILTIN_LED));
    if (iFinish < 31 ){
      iFinish++ ;
    }
    if ((iFinish < 30 )&& (iFinish >0 )){
      digitalWrite(BEEPER,!digitalRead(BEEPER));
    }else{
      digitalWrite(BEEPER,0);      
    }
    
    rtc_sec = second();
//    if ( iOut1 > 0 )
//      Serial.println(iOut1);
  }
  digitalWrite(SCOPE_PIN,!digitalRead(SCOPE_PIN));  // my scope says we are doing this loop at an unreasonable speed except when we do web stuff
//  dnsServer.processNextRequest();
}

Credits

DougalPlummer

DougalPlummer

14 projects • 90 followers
I write code for a living, on everything from PLC's to web and accounting systems. I'm also very talented at farming prickles :)

Comments