Hardware components | ||||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 2 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
Software apps and online services | ||||||
| ||||||
| ||||||
Hand tools and fabrication machines | ||||||
|
Last season our avocados suffered from a lack of water, the problem was caused by blocked dippers. After a few dipper autopsies it was clear that "organic stuff" was growing in the lines. The only way to treat this is by injecting chemicals to kill and dissolve said gunge from the inside of the pipes. Regardless of the chosen chemical you need to inject it at the correct rate during an irrigation.
In this case we elected to inject Hydrogen Peroxide, chosen for its lack of residual byproducts after decomposition. The trouble with this chemical is that it is not very user friendly. If we could pump it directly from the container it would eliminate a considerable source of personal risk while handling the concentrated chemical. There is also a degree of mechanical cleaning action from peroxide decomposition which is better expressed at higher concentrations.
Step 1 - Proof of ConceptHaving built two other dosing pumps for fertigation using "dot net", I wanted to see if my box of Arduino bits could be used to build a portable one. So I constructed a test unit out of a UNO and relay shield. This is as basic as it gets, the only external components are an automotive relay to drive the pump and pre-regulator to keep the Arduino cool while running off the 12V battery. The code was stripped to delays and digital writes, this would later yield interesting observations about the system.
So after connecting up the pump to a drum of water I started to check the calibration of the pump. The pump was supposed to be 5L/min according to the eBay ad but like most items the spec are some what "optimistic". Turned out the pump was about 3L/min which meant that a 2 second burst of the pump yielded about 100ml. At this stage I also checked the plumbing for leaks and the safety switch for operating against a closed head. Now I selected a 30 second cycle as I didn't want Oxygen bubbles forming in the pump or pipes. The flow rate on the target irrigation line was about 1.5 l/sec which should yield a final average concentration of 0.1 1%. Just in case, I programmed the shield relays as 2, 4, 8 and 16 seconds. This meant there was a bit of clicking going on which is a good confidence meter in the field.
Now it was time to try it out in the field. So after hooking everything up, taking the photos and then putting on gloves and goggles (in case a pipe fails) the pump was stated. The result positive, but had issues with interference from the pump or relay crashing the CPU periodically. My wife commented that the pump jumped around when it started and stopped, which made me think that it's really a job for soft start PWM not a on/off relay. Anyhow the field results looked good from the trial and the pump controller so on to the next step.
I didn't want a physical UI on this controller as it's near chemicals and wants to be in an IP65 box, etc. So I thought it should be operated at a distance via WiFi. Been itching to try out a WeMos D1 R2, they are same layout as a UNO but 3.3 Volt. So I designed a HTML user interface and implemented it via the WeMos web server and WiFi access point.
The PWM output form the D1 is connected to the FET board which drives the motor directly. Everything else is done in the software on the WeMos. Actually I think this photo might be "self-fritzing" as you can see where the connections go. Obviously power has to be connected to the D1 but it can accept 9 to 24V DC so a 12V battery is in the butter zone.
HTML Headers Are ImportantYep this one caused me some grief, pages that would work on Android but not iOS browsers - WTF. I have been coding data backed web pages for years but I've never had to make or send a header. Bit of an eye opener, all this stuff you normally take for granted. Must say the google chrome was great, you can click and see the header source after pressing F12. I'd did some simple pages on my office web server and compared the results to my Arduino versions. A bit of study of the included library files (.h and .cpp) to see what they do, how they do it and my issues where sorted.
Yet Another UseMy neighbor and I was chatting about fruit fly control, his new spray rig lacked the pulse control to deliver the right amount of bait to the trees. After seeing what my injector did he asked for one with some modifications. A quick build later and he was a happy camper. So the new features in the software are setting the max quantity and max cycles both to zero. This lets the pump free run at the selected duty cycle. I have also set it to remember the previously saved pump run state instead of the default off at power up. You can also adjust the PWM ramp or disable (0) it so the ESP can drive a relay instead of the FET board.
So stay tuned, I will upgrade this as changes are made during the next 12 months as reality and practicality catch-up with workshop theory.
PeroxideInjector.ino
C/C++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++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
}
#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();
}
Comments