DougalPlummer
Published © GPL3+

Chemical Injector Part 2

A new season and new features are required.

BeginnerWork in progress2 hours1,149
Chemical Injector Part 2

Things used in this project

Hardware components

ESP8266 ESP-12E
Espressif ESP8266 ESP-12E
×1
WeMos D1 R2
My precious....
×1
FET Trigger Board
Any port in a storm - as long as it has enough amps
×1
MBR360G –  3A Schottky Barrier Rectifier
MBR360G – 3A Schottky Barrier Rectifier
Like this but more Amps
×1
OLED 1.3 or 0.96 inch
×1
Buzzer
Buzzer
×1
Enclosure (IP65)
×1

Software apps and online services

Arduino IDE
Arduino IDE
Visual C++ V4 Embedded

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)
Hand Drill

Story

Read more

Schematics

Mud map of circuit

Code

Injector

C/C++
#include <ESP8266WiFi.h>
//#include <WiFiClient.h> 
#include <ESP8266WebServer.h>
#include <ESP8266mDNS.h>
#include <ESP8266HTTPUpdateServer.h>
#include <ESP8266httpUpdate.h>
//#include <DNSServer.h>
#include <TimeLib.h>
//#include <Wire.h>
//#include <SPI.h>
#include <EEPROM.h>
#include "SSD1306.h"
//#include "SH1106.h"
#include "SH1106Wire.h"
#include "ds3231.h"

void handleRoot();   // ide sometimes compiles and sometimes does not ????
void handleSetup();
void handleNotFound();
void i2cScan();

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

#define BUFF_MAX 32
#define TIMER_MAX 3600000
#define TIMER_MIN 250
#define CYCLE_COUNT_MAX 1000000
#define PWM_INC_MIN 0
#define PWM_INC_MAX 50
#define PUMPED_QTY_MAX 20000.0
#define PUMP_ML_PER_SEC_MIN 50.0
#define PUMP_ML_PER_SEC_MAX 10000.0

#define MAX_PUMPS 2

//SSD1306 display(0x3c, 5, 4);   // GPIO 5 = D1, GPIO 4 = D2   - onboard display 0.96" 
SH1106Wire display(0x3c, 4, 5);   // arse about ??? GPIO 5 = D1, GPIO 4 = D2  -- external ones 1.3"

const int NTP_PACKET_SIZE = 48;       // NTP time stamp is in the first 48 bytes of the message
byte packetBuffer[NTP_PACKET_SIZE];   // buffer to hold incoming and outgoing packets

typedef struct __attribute__((__packed__)) {            // eeprom stuff
  long lNodeAddress ;
  char TimeServer[24] ;
  char nssid[32] ;
  char npassword[16] ;
  char cssid[32] ;
  char cpassword[16] ;
  char NodeName[16] ;
  float fTimeZone ;  
  IPAddress MyIP  ;                 // main
  IPAddress MyIPC  ;                
  unsigned int localPort ;          // local port to listen for NTP UDP packets
  unsigned int localPortCtrl ;      // local port to listen for Control UDP packets
  unsigned int RemotePortCtrl ;     // local port to listen for Control UDP packets
  unsigned int uiThorPort ;
  unsigned int uiModbusPort ;       // modbus port
  bool bNWG ; 
}network_stuff_t ;                        // network control

typedef struct __attribute__((__packed__)) {            // volitile stuff for RTC if present
  bool bHasRTC ;
  byte rtc_sec ;
  byte rtc_min ;
  byte rtc_hour ;
  float rtc_temp ;
  uint8_t rtc_status ;
  bool bDoTimeUpdate ;  
  struct ts tc;
  tmElements_t tm;
} rtc_stuff_t ;                   

typedef struct __attribute__((__packed__)) {            // eeprom stuff
  long lPrime ;                // 0  modbus offset
  long lOnTime ;               // 2
  long lOffTime ;              // 4
  long lWebOnTime ;            // 6
  long lOnCounter ;            // 8
  long lOffCounter ;           // 10
  long lCyclesCounter ;        // 12
  long lCycles ;               // 14
  float dblMLPerSecond ;       // 16 
  float dblQty ;               // 18 
  float dblCurrentQty ;        // 20
  long lOnOff ;                // 22
  long lTimePrev ;             // 24  
  long lTimePrev2 ;            // 26
  long PWM_inc ;               // 28 
  time_t AutoOff_t ;           // 30  yep its a long
  int  iFinish ;               // 31
  int  iCurPos ;               // 32
  int  iMatchBit ;             // 33
  int  iMatchSlot ;            // 34
  int  iOut ;                  // 35
  bool bState ;                // 36 done in one
  bool bPrime ;                // 36
} pump_stuff_t ;                   

typedef struct __attribute__((__packed__)) {            // eeprom stuff
  uint8_t net_id ; 
  uint8_t fc ;   
  uint16_t address ;   
  uint16_t wordcount ; 
  uint16_t remotePort ;
  IPAddress remoteIP ;
} modbus_stuff_t ;                   


rtc_stuff_t      rtc ;             // all volitile stuff
network_stuff_t  nwc ;             // saved in eeprom  Network Control
pump_stuff_t     pcs ;             // saved in eprom   Pump Control System
modbus_stuff_t   MB ;

/* Set these to your desired credentials. */
const char *ssid = "Injector";
const char *password = "password";
const char *host = "injector";
char Toleo[10] = {"Ver 3.1A\0"}  ;

long lScanCtr = 0 ;
long lScanLast = 0 ;


char buff[BUFF_MAX]; 
long lDisplayOptions = 1 ;
String LastPacket ;
time_t PacketTime ;
//long lWebOnTime ;
//IPAddress MyIP(192,168,2,110) ;

ESP8266WebServer server(80);
//DNSServer dnsServer;
ESP8266WebServer OTAWebServer(81);
ESP8266HTTPUpdateServer OTAWebUpdater;

WiFiUDP ctrludp;
WiFiUDP ntpudp;
WiFiUDP modbusudp;

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

void LoadParamsFromEEPROM(bool bLoad){
long eeAddress ;  
  if ( bLoad ) {
    eeAddress = 0 ;
    EEPROM.get(eeAddress,lDisplayOptions);
    eeAddress = PROG_BASE ;
    EEPROM.get(eeAddress,pcs);
    eeAddress += sizeof(pcs) ;
    EEPROM.get(eeAddress,nwc);
    eeAddress += sizeof(nwc) ;  
      
  }else{
    eeAddress = 0 ;
    EEPROM.put(eeAddress,lDisplayOptions);
    eeAddress = PROG_BASE ;
    EEPROM.put(eeAddress,pcs);
    eeAddress += sizeof(pcs) ;
    EEPROM.put(eeAddress,nwc);
    eeAddress += sizeof(nwc) ;        
    EEPROM.commit();  // save changes in one go ???
  }
}

void BackInTheBoxMemory(){
  sprintf( nwc.TimeServer , "au.pool.ntp.org\0" ) ;
  sprintf(nwc.NodeName,"Control_%08X\0",ESP.getChipId());  
  nwc.localPort = 2390 ;                                // local port to listen for NTP UDP packets
  nwc.localPortCtrl = 8666 ;                            // local port to listen for Control UDP packets
  nwc.RemotePortCtrl = 8664 ;                           // local port to listen for Control UDP packets
  nwc.uiThorPort = 8056 ;
  sprintf( nwc.nssid,"lodge\0" ) ;
  sprintf( nwc.npassword,"\0" ) ;
  sprintf(nwc.cssid,"Control_%08X\0",ESP.getChipId());  
//  sprintf( nwc.cssid,"Injector\0" ) ;
  sprintf( nwc.cpassword,"\0" ) ;
  nwc.MyIP = IPAddress(192,168,1,111) ;                                  // main
  nwc.MyIPC = IPAddress(192, 168, (5 +(ESP.getChipId() & 0x7f )) , 1) ;  // configuration              
  nwc.uiThorPort = 8056 ;
  nwc.fTimeZone = 10.0 ;
  nwc.lNodeAddress = 0 ;
  nwc.uiModbusPort = 502 ;
  
  pcs.iMatchBit = 0 ;
  pcs.iMatchSlot = 9 ;
  pcs.lOnTime = 4000 ;                     // On time in ms
  pcs.lOffTime = 26000 ;                   // Off time in ms
  pcs.lCycles  = 32000 ;                   // maximum no of cycles        
  pcs.dblMLPerSecond = 100.0 ;             // pump rate
  pcs.dblQty = 0 ;                         // Total Qty Required Litres
  pcs.lPrime = 15000 ;                     // On time in ms
  pcs.lOnOff = 0 ;                         // start and run
  pcs.PWM_inc = 0 ;                        // pwm increment 0 is no PWM
  pcs.iMatchBit = 31 ;
  pcs.iMatchSlot = 9 ;
  pcs.iFinish = 0 ;

  sprintf(nwc.npassword,"******\0");
  sprintf(nwc.nssid,"**********\0");
  
}

//###########################################  SETUP  ##########################################################
void setup() {
int j ;
  
  Serial.begin(115200);
  Serial.setDebugOutput(true);  
  Serial.print("\nChip ID ");
  Serial.println(ESP.getChipId(), HEX);
  rtc.bDoTimeUpdate = false ;
  rtc.bHasRTC = false ;
  nwc.bNWG = false ;
  EEPROM.begin(2000);  
  LoadParamsFromEEPROM(true);
  
  display.init();
  if (( lDisplayOptions & 0x01 ) != 0 ) {  // if bit one on then flip the display
    display.flipScreenVertically();
  }

  /* show start screen */
  display.clear();
  display.setTextAlignment(TEXT_ALIGN_CENTER);  
  display.setFont(ArialMT_Plain_16);
  display.drawString(63, 0, "Injection");
  display.drawString(63, 16, "Controler");
  display.setFont(ArialMT_Plain_10);
  display.drawString(63, 34, "Copyright (c) 2019");
  display.drawString(63, 44, "Dougal Plummer");
  display.drawString(63, 54, String(Toleo));
  display.display();
  if (( nwc.uiThorPort == 0 ) || ( nwc.uiThorPort == 0xffff ) || (( pcs.lOnTime == 0 )  && ( pcs.lOffTime == 0 )) || (( pcs.lOnTime == 0xffffffff )  && ( pcs.lOffTime == 0xffffffff ))) {
    BackInTheBoxMemory();         // load defaults if blank memory detected but dont save user can still restore from eeprom
    Serial.println("Loading memory defaults...");
  }
//  BackInTheBoxMemory();         // load defaults if blank memory detected but dont save user can still restore from eeprom

  delay(2000);
  WiFi.disconnect();
  Serial.println("Configuring soft access point...");
//  WiFi.mode(WIFI_AP_STA);  // we are having our cake and eating it eee har
  if ( nwc.cssid[0] == 0 || nwc.cssid[1] == 0 ){   // pick a default setup ssid if none
    sprintf(nwc.cssid,"Configure_%08X\0",ESP.getChipId());
  }
  WiFi.softAPConfig(nwc.MyIPC,nwc.MyIPC,IPAddress (255, 255, 255 , 0));  
  sprintf(nwc.cpassword,"\0");
  Serial.println("Starting access point...");
  Serial.print("SSID: ");
  Serial.println(nwc.cssid);
  Serial.print("Password: >");
  Serial.print(nwc.cpassword);
  Serial.println("<");
  if ( nwc.cpassword[0] == 0 ){
    WiFi.softAP((char*)nwc.cssid);                   // no passowrd
  }else{
    WiFi.softAP((char*)nwc.cssid,(char*) nwc.cpassword);
  }

  nwc.MyIPC = WiFi.softAPIP();  // get back the address to verify what happened
  Serial.print("Soft AP IP address: ");
  snprintf(buff, BUFF_MAX, ">> IP %03u.%03u.%03u.%03u <<", nwc.MyIPC[0],nwc.MyIPC[1],nwc.MyIPC[2],nwc.MyIPC[3]);      
  Serial.println(buff);
  
  Serial.println("client connecting to access point...");
  Serial.print("SSID: ");
  Serial.println(nwc.nssid);
  Serial.print("Password: >");
  Serial.print(nwc.npassword);
  Serial.println("<");
  if ( nwc.npassword[0] == 0 ){
    WiFi.begin((char*)nwc.nssid);                         // connect to unencrypted access point      
  }else{
    WiFi.begin((char*)nwc.nssid, (char*)nwc.npassword);   // connect to access point with encryption
  }
  while (( WiFi.status() != WL_CONNECTED ) && ( j < 45 )) {
   j = j + 1 ;
   delay(500);
   Serial.print("+");
   display.clear();
   display.setTextAlignment(TEXT_ALIGN_LEFT);
   display.drawString(0, 0, "Chip ID " + String(ESP.getChipId(), HEX) );
   display.drawString(0, 9, String("SSID:") );
   display.drawString(0, 18, String("Password:") );
   display.setTextAlignment(TEXT_ALIGN_RIGHT);
   display.drawString(128 , 0, String(WiFi.RSSI()));
   display.drawString(128, 9, String(nwc.nssid) );
   display.drawString(128, 18, String(nwc.npassword) );
   display.drawString(j*3, 27 , String(">") );
   display.drawString(0, 36 , String(1.0*j/2) + String(" (s)" ));   
//   snprintf(buff, BUFF_MAX, ">> IP %03u.%03u.%03u.%03u <<", nwc.MyIPC[0],nwc.MyIPC[1],nwc.MyIPC[2],nwc.MyIPC[3]);      
   display.drawString(63 , 54 ,  String(buff) );
   display.display();  
  } 
  if ( j >= 45 ) {
   Serial.println("");
   Serial.println("Connection to " + String() + " Failed");
  }else{
    nwc.MyIP = WiFi.localIP() ;
    Serial.println("");
    Serial.print("Connected to " + String(nwc.nssid) + " IP ");
     snprintf(buff, BUFF_MAX, ">> IP %03u.%03u.%03u.%03u <<", nwc.MyIP[0],nwc.MyIP[1],nwc.MyIP[2],nwc.MyIP[3]);      
    Serial.print(buff) ;
    nwc.bNWG = true ;
  }

  if ( nwc.NodeName == 0 ){
    sprintf(nwc.NodeName,"Control_%08X\0",ESP.getChipId());  
  }
/*  if (MDNS.begin(nwc.NodeName)) {
    MDNS.addService("http", "tcp", 80);
    Serial.println("MDNS responder started");
    Serial.print("You can now connect to http://");
    Serial.print(nwc.NodeName);
    Serial.println(".local");
  }
*/  
  server.on("/", handleRoot);
  server.on("/setup", handleSetup);
  server.on("/scan", i2cScan);
  server.on("/stime", handleRoot);  
  server.on("/EEPROM",DisplayEEPROM);
  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);
//  dnsServer.setTTL(300);
//  dnsServer.setErrorReplyCode(DNSReplyCode::ServerFailure);
//  dnsServer.start(53,"injector.local",nwc.myIP);

  rtc.tc.mon = 0 ;
  rtc.tc.wday = 0 ;
  DS3231_init(DS3231_INTCN); // look for a rtc
  DS3231_get(&rtc.tc);
  rtc.rtc_status = DS3231_get_sreg();
  if (((rtc.tc.mon < 1 )|| (rtc.tc.mon > 12 ))&& (rtc.tc.wday>8)){  // no rtc to load off
    Serial.println(F("NO RTC ?"));
  }else{
    setTime((int)rtc.tc.hour,(int)rtc.tc.min,(int)rtc.tc.sec,(int)rtc.tc.mday,(int)rtc.tc.mon,(int)rtc.tc.year ) ; // set the internal RTC
    rtc.bHasRTC = true ;
    Serial.println(F("Has RTC ?"));
    rtc.rtc_temp = DS3231_get_treg(); 
  }
  rtc.rtc_min = minute();
  rtc.rtc_sec = second();

  OTAWebUpdater.setup(&OTAWebServer);
  OTAWebServer.begin(); 
  if ( nwc.bNWG ) {
    if (( nwc.uiThorPort > 0 ) && (nwc.uiThorPort < 30000)){
      ctrludp.begin(nwc.uiThorPort);                 // recieve on the control port  
      Serial.print(F("T"));
    }
    if (( nwc.localPort > 0 ) && (nwc.localPort < 30000)){
      ntpudp.begin(nwc.localPort);                   // this is the recieve on NTP port
      Serial.print(F("N"));
    }
    if (( nwc.uiModbusPort > 0 ) && (nwc.uiModbusPort < 30000)){
      modbusudp.begin(nwc.uiModbusPort);
      Serial.print(F("M"));
    }
    Serial.println(F(" UDP Listerers started...."));
  }
}
// ############################################# LOOP ##########################################################
void loop() {
long lTime ;  
long lRet ;
long lTmpOnCount ;

  server.handleClient();
  OTAWebServer.handleClient();

  lTime = millis() ;                                   // Get the lo res timer
  pcs.lOffCounter = constrain(pcs.lOffCounter,0, pcs.lOffTime);    // anti LA LA land code
  if (pcs.lOnOff != 2 ) {
    if (pcs.lPrime > pcs.lOnTime ){
      pcs.lOnCounter = constrain(pcs.lOnCounter,0, pcs.lPrime);    
    }else{
      pcs.lOnCounter = constrain(pcs.lOnCounter,0, pcs.lOnTime);      
    }
  }
  pcs.lCyclesCounter = constrain(pcs.lCyclesCounter,0, CYCLE_COUNT_MAX ); 

  if ( pcs.lCycles > 0 ) {                                       // if we have cycle or quantity limmits check them
    if (( pcs.lOnOff != 0 ) && ( pcs.lCyclesCounter >= pcs.lCycles )) {  // if out then shut off
      pcs.bState = false ;
      pcs.iFinish = 1 ;
      pcs.lOnOff = 0 ;
    }
  }
  if ( pcs.dblQty > 0 ){                                         // if we have cycle or quantity limmits check them
    if (( pcs.dblCurrentQty >= pcs.dblQty ) && ( pcs.lOnOff != 0 )) {    // if out then shut off
      pcs.bState = false ;
      pcs.iFinish = 1 ;
      pcs.lOnOff = 0 ;
    }
  }
  if (( pcs.lTimePrev2  ) < lTime ){  // look every 2 ms
    if ((pcs.bState)|| (pcs.bPrime )){           // soft starter
      if ( pcs.iOut < 1023 ){  // speed up
        pcs.iOut = pcs.iOut + pcs.PWM_inc ;
        if ( pcs.iOut > 1023 ){
          pcs.iOut = 1023 ;
        }
      }
    }else{
      if ( pcs.iOut > 0  ){   // slow down 
        pcs.iOut = pcs.iOut - pcs.PWM_inc ;
        if ( pcs.iOut < 0 ){
          pcs.iOut = 0 ;
        }
      }
    }
    pcs.lTimePrev2 = lTime + 1 ;
  }

  display.clear();
  display.setTextAlignment(TEXT_ALIGN_LEFT);
  snprintf(buff, BUFF_MAX, "%d/%02d/%02d %02d:%02d:%02d", year(), month(), day() , hour(), minute(), second());
  display.drawString(0 , 0, String(buff) );
  display.setTextAlignment(TEXT_ALIGN_RIGHT);
  display.drawString(127 , 0, String(WiFi.RSSI()));
  display.setTextAlignment(TEXT_ALIGN_CENTER);
  snprintf(buff, BUFF_MAX, ">> IP %03u.%03u.%03u.%03u <<", nwc.MyIPC[0],nwc.MyIPC[1],nwc.MyIPC[2],nwc.MyIPC[3]);    
  if (( nwc.bNWG ) && (( rtc.rtc_sec & 0x02 ) == 0  )){
    snprintf(buff, BUFF_MAX, "IP %03u.%03u.%03u.%03u", nwc.MyIP[0],nwc.MyIP[1],nwc.MyIP[2],nwc.MyIP[3]);          
  }
  display.drawString(63 , 54 ,  String(buff) );

  display.setTextAlignment(TEXT_ALIGN_RIGHT);
  display.drawString(127 , 9, String(pcs.lCyclesCounter));
  display.drawString(127 , 18, String(pcs.lOnCounter));
  display.drawString(127 , 27 , String(pcs.lOffCounter));
  display.drawString(127 , 36, String(pcs.dblCurrentQty,2));
  display.drawString(80 , 18, String(pcs.lOnTime));
  display.drawString(80 , 27 , String(pcs.lOffTime));
  display.drawString(80 , 36, String(pcs.dblQty,2));
  
  display.setTextAlignment(TEXT_ALIGN_LEFT);
  snprintf(buff, BUFF_MAX, "%02d:%02d:%02d", hour(PacketTime), minute(PacketTime), second(PacketTime));
  display.drawString(0 , 45, String(buff) + " >"+ LastPacket + "<");


  display.setTextAlignment(TEXT_ALIGN_LEFT);
  switch (pcs.lOnOff){
    case 1: // ON free run 
      display.drawString(0 , 9, String("Pmp -ON-"));
    break;  
    case 2: // auto
      display.drawString(0 , 9, String("Pmp AUTO"));
    break;  
    default: // off
      display.drawString(0 , 9, String("Pmp OFF"));
    break;
  }

  display.setTextAlignment(TEXT_ALIGN_RIGHT);
  display.drawString(80 , 9, String("Cyls"));
  display.setTextAlignment(TEXT_ALIGN_LEFT);
  display.drawString(0 , 18, String("TOn"));
  display.drawString(0 , 27, String("TOff"));
  display.drawString(0 , 36, String("Qty"));  
  display.display();
    
  if (pcs.lTimePrev > ( lTime + 100000 )){ // has wrapped around so back to zero
    pcs.lTimePrev = lTime ; // skip a bit 
    Serial.println("Wrap around");
  }
  
  if (( pcs.lTimePrev + 100 ) < lTime ){  // look every 1/10 of a second ish
    if (( pcs.bState) || ( pcs.bPrime )) {  // true is on - assume called once per second
      pcs.lOnCounter+= (lTime - pcs.lTimePrev)  ;
      if (pcs.bPrime){
        if ( pcs.lOnCounter >= pcs.lPrime ){
          Serial.println("prime complete");
          pcs.lOnCounter = 0 ;
          pcs.bPrime = false ;
          pcs.bState = false ;
        }      
      }else{
        if ( pcs.lOnOff == 2 ){
          lTmpOnCount = pcs.lWebOnTime ;
        }else{
          lTmpOnCount = pcs.lOnTime ;
        }
        if ( pcs.lOnCounter >= lTmpOnCount ){
          pcs.lOnCounter = 0 ;
          pcs.lCyclesCounter++ ;
          pcs.bState = !pcs.bState ;
        }
      }
      pcs.dblCurrentQty += ( pcs.dblMLPerSecond * (float(lTime - pcs.lTimePrev)/1000))/1000 ;    //work out how much
    }else{
      switch ( pcs.lOnOff ){
        case 1:
          pcs.lOffCounter += (lTime - pcs.lTimePrev) ;
          digitalWrite(BUILTIN_LED,!digitalRead(BUILTIN_LED));
          if ( pcs.lOffCounter >= pcs.lOffTime ) {
            pcs.lOffCounter = 0 ;      
            pcs.bState = !pcs.bState ;
          }
        break;
        default:  // 0 and 2 
          pcs.lOffCounter = 0 ;              
        break;  
      }
    }
    pcs.lTimePrev += 100 ; 
  }
  if ( pcs.PWM_inc > 0 ){
    analogWrite(MOTOR1,pcs.iOut) ; // soft start the motor
  }else{
    if (pcs.bState) {
      digitalWrite(MOTOR1,true) ; // using a relay clunck clunk
    }else{
      digitalWrite(MOTOR1,false) ;     
    }
  }
  
  lScanCtr++ ;
  if (rtc.rtc_sec != second() )  {
    lScanLast = lScanCtr ;
    lScanCtr = 0 ;

    digitalWrite(BUILTIN_LED,!digitalRead(BUILTIN_LED));
    if (pcs.iFinish < 31 ){
      pcs.iFinish++ ;
    }
    if ((pcs.iFinish < 30 )&& (pcs.iFinish >0 )){
      digitalWrite(BEEPER,!digitalRead(BEEPER));
    }else{
      digitalWrite(BEEPER,0);      
    }
    rtc.rtc_sec = second();
  }
  if (rtc.rtc_hour != hour()){  // hourly stuff
    if ( nwc.bNWG ) { // ie we have a network
      sendNTPpacket(nwc.TimeServer); // send an NTP packet to a time server  once and hour
    }else{
      if ( rtc.bHasRTC ){
        DS3231_get(&rtc.tc);
        setTime((int)rtc.tc.hour,(int)rtc.tc.min,(int)rtc.tc.sec,(int)rtc.tc.mday,(int)rtc.tc.mon,(int)rtc.tc.year ) ; // set the internal RTC
      }
    }
    rtc.rtc_hour = hour();
  }
  if ( rtc.rtc_min != minute()){
    if (( year() < 2019 )|| (rtc.bDoTimeUpdate)) {  // not the correct time try to fix every minute
      if ( nwc.bNWG ) { // ie we have a network
        sendNTPpacket(nwc.TimeServer); // send an NTP packet to a time server  
        rtc.bDoTimeUpdate = false ;
      }
    }
    if ( rtc.bHasRTC ){
      rtc.rtc_temp = DS3231_get_treg(); 
    }
    rtc.rtc_min = minute() ;
  }
  
  digitalWrite(SCOPE_PIN,!digitalRead(SCOPE_PIN));  // my scope says we are doing this loop at an unreasonable speed except when we do web stuff

  lRet = ctrludp.parsePacket() ;
  if ( lRet != 0 ) {
    processCtrlUDPpacket(lRet);
  }
  
  lRet = ntpudp.parsePacket() ;
  if ( lRet != 0 ) {
    processNTPpacket();
  }
  
  lRet = modbusudp.parsePacket() ;
  if ( lRet != 0 ) {
    ProcessModbusPacket();
  }
}


unsigned long processCtrlUDPpacket(long lSize){
int i , j ;  
int myslot , mybit , mytime  ;
byte packetBuffer[16] ;                                //  buffer to hold incomming packets  

  memset(packetBuffer, 0, sizeof(packetBuffer));
  ctrludp.read(packetBuffer, sizeof(packetBuffer));    // read the packet into the buffer
  Serial.print(F("Process Ctrl Packet "));
  Serial.println(String((char *)packetBuffer));

  sscanf((char *) packetBuffer , "%d %d %d" , &myslot , &mybit , &mytime );
  if (( myslot == pcs.iMatchSlot ) && ( mybit == pcs.iMatchBit ) && ( mytime > 0  ) && ( mytime < 32000  )){   // is our address or a braodcast
    LastPacket = String((char *)packetBuffer) ;
    PacketTime = now() ;
    pcs.lWebOnTime  = mytime * 100 ; // go from tenths of a second to ms
    if ( pcs.lOnOff != 0 ){  
      pcs.bState = true ;             // enable the on counter
      pcs.lOnCounter = 0 ;            // set it to zero
      pcs.lCyclesCounter++ ;
      pcs.lTimePrev = millis() ;      // zero out the residual
    }
  }

  while (ctrludp.available()){  // clean out the rest of the packet and dump overboard
    ctrludp.read(packetBuffer, sizeof(packetBuffer));  
  }
  return(0);
}

void SerialOutParams(){
String message ;
  message = "Web Request URI: ";
  message += server.uri();
  message += "\nMethod: ";
  message += (server.method() == HTTP_GET)?"GET":"POST";
  message += "\nArguments: ";
  message += server.args();
  message += "\n";
  Serial.println(message);
  message = "";
  for (uint8_t i=0; i<server.args(); i++){
    message += " NAME:"+server.argName(i) + "\n VALUE:" + server.arg(i) + "\n";
  }
  Serial.println(message);
}

I2CSCAN page

C/C++
void i2cScan() {
  uint8_t i, address, error;
  uint8_t first = 0x03 ; 
  uint8_t last = 0x77 ;
  char buff[10];

//  SerialOutParams();
  SendHTTPHeader();

  server.sendContent(F("<center><b>I2C Bus Scan</b><br><table border=1 title='I2C Bus Scan'><tr><th>.</th>"));
 // table header
  for (i = 0; i < 16; i++) {
    server.sendContent("<th>"+String(i,HEX)+"</th>");
  }
  server.sendContent(F("</tr>"));
  
  // table body
  // addresses 0x00 through 0x77
  for (address = 0; address <= 119; address++) {
    if (address % 16 == 0) {
      server.sendContent(F("<tr>"));
      server.sendContent("<td><b>"+String((address & 0xF0),HEX)+"</b></td>");
    }
    if (address >= first && address <= last) {
      Wire.beginTransmission(address);
      error = Wire.endTransmission();
      if (error == 0) {
        // device found
        server.sendContent("<td bgcolor='lime'>"+String((address),HEX)+"</td>");
      } else if (error == 4) {
        // other error
        server.sendContent(F("<td bgcolor='red'>XX</td>"));
      } else {
        // error = 2: received NACK on transmit of address
        // error = 3: received NACK on transmit of data
        server.sendContent(F("<td align=center>--</td>"));
      }
    } else {
      server.sendContent(F("<td align=center>.</td>"));
    }
    if (address % 16 == 15) {
      server.sendContent(F("</tr>"));
    }
  }
  server.sendContent(F("</tr></table>"));
  SendHTTPPageFooter() ;
}


void DisplayEEPROM() {
  uint8_t i ;
  uint16_t ii ;
  uint32_t iiii ;
  int j , k ;
  int r  = 0  ;
  int b = 0 ;
  int d = 1 ;
  int  address;
  char buff[10];

  for (uint8_t j=0; j<server.args(); j++){
    k = String(server.argName(j)).indexOf("RADIX");
    if (k != -1){  // have a request to set the time zone
      r = String(server.arg(j)).toInt() ;
    }
    k = String(server.argName(j)).indexOf("BITS");
    if (k != -1){  // have a request to set the time zone
      b = String(server.arg(j)).toInt() ;
      switch(b){
        case 32:
          d = 4 ;
        break;
        case 16:
          d = 2 ;
        break;
        default:
          d = 1 ;
        break;
      }
    }
  }  
//  SerialOutParams();
  SendHTTPHeader();
  server.sendContent("<br><form method=get action=" + server.uri() + ">");
  server.sendContent("Radix: <select name=RADIX>");
  switch(r){
    case 2:
      server.sendContent(F("<option value='2' SELECTED>Binary")); 
      server.sendContent(F("<option value='8'>Octal")); 
      server.sendContent(F("<option value='10'>Decimal")); 
      server.sendContent(F("<option value='16'>Hexadecimal")); 
    break;
    case 8:
      server.sendContent(F("<option value='2'>Binary")); 
      server.sendContent(F("<option value='8' SELECTED>Octal")); 
      server.sendContent(F("<option value='10'>Decimal")); 
      server.sendContent(F("<option value='16'>Hexadecimal")); 
    break;
    case 10:
      server.sendContent(F("<option value='2'>Binary")); 
      server.sendContent(F("<option value='8'>Octal")); 
      server.sendContent(F("<option value='10' SELECTED>Decimal")); 
      server.sendContent(F("<option value='16'>Hexadecimal")); 
    break;
    default:
      server.sendContent(F("<option value='2'>Binary")); 
      server.sendContent(F("<option value='8'>Octal")); 
      server.sendContent(F("<option value='10'>Decimal")); 
      server.sendContent(F("<option value='16' SELECTED>Hexadecimal")); 
    break;
  }
  server.sendContent("</select>");
  server.sendContent("Bits: <select name=BITS>");
  switch(b){
    case 32:
      server.sendContent(F("<option value='8'>8 Bit - Byte")); 
      server.sendContent(F("<option value='16'>16 Bit - Word")); 
      server.sendContent(F("<option value='32' SELECTED>32 Bit - DWord")); 
    break;
    case 16:
      server.sendContent(F("<option value='8'>8 Bit - Byte")); 
      server.sendContent(F("<option value='16' SELECTED>16 Bit - Word")); 
      server.sendContent(F("<option value='32'>32 Bit - DWord")); 
    break;
    default:
      server.sendContent(F("<option value='8' SELECTED>8 Bit - Byte")); 
      server.sendContent(F("<option value='16'>16 Bit - Word")); 
      server.sendContent(F("<option value='32'>32 Bit - DWord")); 
    break;
  }
  server.sendContent("</select>");
  server.sendContent(F("<input type='submit' value='SET'></form><br><table border=1 title='EEPROM Contents'><tr><th>.</th>"));
 // table header
  for (i = 0; i < 32; i+=d) {
    server.sendContent("<th>"+String(i,HEX)+"</th>");
  }
  server.sendContent(F("</tr>"));
  for (address = 0; address < 1984  ; address+=d ) {
    if (address % 32 == 0) {
      server.sendContent(F("<tr>"));
      server.sendContent("<td align=center><b>"+String((address & 0xFFE0),HEX)+"</b></td>");
    }
    switch(b){
      case 16:
        EEPROM.get(address,ii);
        switch(r){
          case 8:
            server.sendContent("<td>"+String(ii,OCT)+"</td>");
          break;
          case 10:
            server.sendContent("<td>"+String(ii,DEC)+"</td>");
          break;
          case 2:
            server.sendContent("<td>"+String(ii,BIN)+"</td>");
          break;
          default:
            server.sendContent("<td>"+String(ii,HEX)+"</td>");
          break;
        }  
      break;
      case 32:
        EEPROM.get(address,iiii);
        switch(r){
          case 8:
            server.sendContent("<td>"+String(iiii,OCT)+"</td>");
          break;
          case 10:
            server.sendContent("<td>"+String(iiii,DEC)+"</td>");
          break;
          case 2:
            server.sendContent("<td>"+String(iiii,BIN)+"</td>");
          break;
          default:
            server.sendContent("<td>"+String(iiii,HEX)+"</td>");
          break;
        }  
      break;
      default: // byte 8
        EEPROM.get(address,i);
        switch(r){
          case 8:
            server.sendContent("<td>"+String(i,OCT)+"</td>");
          break;
          case 10:
            server.sendContent("<td>"+String(i,DEC)+"</td>");
          break;
          case 2:
            server.sendContent("<td>"+String(i,BIN)+"</td>");
          break;
          default:
            server.sendContent("<td>"+String(i,HEX)+"</td>");
          break;
        }  
      break;
    }
    if (address % 32 == 31) {
      server.sendContent(F("</tr>"));
    }
  }
  server.sendContent(F("</tr></table>"));
  SendHTTPPageFooter() ;
}

MAIN_WEB_PAGE.ino

C/C++
void SendHTTPHeader(){ 
  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 " + String(Toleo) + "</h2>");
  server.sendContent("<a href='/'>Refresh</a><br><br>") ;         
  server.sendContent("<a href='/?command=2'>Save Parameters to EEPROM</a><br>") ;         
}

void SendHTTPPageFooter(){
  server.sendContent(F("<br><a href='/?command=1'>Load Parameters from EEPROM</a><br><br><br><a href='/?command=667'>Reset Memory to Factory Default</a><br><a href='/?command=665'>Sync UTP Time</a><br><a href='/stime'>Manual Time Set</a><br><a href='/scan'>I2C Scan</a><br><a href='/?command=9'>Reboot</a><br>"));   
  server.sendContent(F("<a href='/setup'>Node/Network Setup</a><br>"));  
  snprintf(buff, BUFF_MAX, "%u.%u.%u.%u", nwc.MyIP[0],nwc.MyIP[1],nwc.MyIP[2],nwc.MyIP[3]);
  server.sendContent("<br><a href='http://" + String(buff) + ":81/update'>OTA Firmware Update</a><br>");
  server.sendContent(F("</body></html>\r\n"));
}

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] ;
  String MyColor ;
  String MyText ;
  long  i = 0 ;
  bool bDefault = true ;

//  SerialOutParams();
  
  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
          pcs.bPrime = true ;
          pcs.bState = true ;          
          Serial.println("Prime Pumps");
        break;
        case 4: // Clear current qty
          pcs.dblCurrentQty = 0 ;
          Serial.println("Clear current qty");
        break;
        case 5: // Clear current qty
          pcs.lCyclesCounter = 0 ;
          Serial.println("Clear current cycles");
        break;
        case 8: //  Cold Reboot
          ESP.reset() ;
        break;
        case 9: //  Warm Reboot
          ESP.restart() ;
        break;        
        case 665:
          sendNTPpacket(nwc.TimeServer); // send an NTP packet to a time server  once and hour  
        break;        
        case 666:
          pcs.lOnOff = 1 ; 
          pcs.bState = true ;          
        break;
        case 667:
          pcs.lOnOff = 2 ; 
          pcs.bState = false ;          
        break;
        case 999:
          pcs.lOnOff = 0 ; 
          pcs.bState = false ;          
        break;
        case 42:
          ESP.reset() ;
        break;
      }  
    }
    i = String(server.argName(j)).indexOf("disop");
    if (i != -1){  // 
      lDisplayOptions = String(server.arg(j)).toInt() ;
      constrain(lDisplayOptions,0,255);
    }  
    
    i = String(server.argName(j)).indexOf("prime");
    if (i != -1){  // have a request to set the time zone
      pcs.lPrime = String(server.arg(j)).toInt() ;
      constrain(pcs.lPrime,500,32000);
    }        

    i = String(server.argName(j)).indexOf("timon");
    if (i != -1){  // have a request to set the time zone
      pcs.lOnTime = String(server.arg(j)).toInt() ;
      constrain(pcs.lOnTime,TIMER_MIN,TIMER_MAX);
    }        
    i = String(server.argName(j)).indexOf("timof");
    if (i != -1){  // have a request to set the time zone
      pcs.lOffTime = String(server.arg(j)).toInt() ;
      constrain(pcs.lOffTime,TIMER_MIN,TIMER_MAX);
    }        
    i = String(server.argName(j)).indexOf("cycnt");
    if (i != -1){  // have a request to set the time zone
      pcs.lCycles = String(server.arg(j)).toInt() ;
      constrain(pcs.lCycles,0,CYCLE_COUNT_MAX);
    }
            
    i = String(server.argName(j)).indexOf("mybit");
    if (i != -1){  // have a request to set the time zone
      pcs.iMatchBit = String(server.arg(j)).toInt() ;
      constrain(pcs.iMatchBit,0,63);
    }        
    i = String(server.argName(j)).indexOf("myslt");
    if (i != -1){  // have a request to set the time zone
      pcs.iMatchSlot = String(server.arg(j)).toInt() ;
      constrain(pcs.iMatchSlot,0,31);
    }        
    
    i = String(server.argName(j)).indexOf("pwminc");
    if (i != -1){  // have a request to set the time zone
      pcs.PWM_inc = String(server.arg(j)).toInt() ;
      constrain(pcs.PWM_inc,PWM_INC_MIN,PWM_INC_MAX);
    }        
    
    i = String(server.argName(j)).indexOf("pumpea");
    if (i != -1){  // have a request to set the time zone
      pcs.lOnOff =  String(server.arg(j)).toInt() ;
      if ( pcs.lOnOff == 1 ) {
          pcs.bState = true ;
      }else{
          pcs.bState = false ;
      }
    }        

    i = String(server.argName(j)).indexOf("pmlps");    // pump ml per second
    if (i != -1){  // have a request to set the latitude
      pcs.dblMLPerSecond = String(server.arg(j)).toFloat() ;
      constrain(pcs.dblMLPerSecond,PUMP_ML_PER_SEC_MIN,PUMP_ML_PER_SEC_MIN);
    }        
    i = String(server.argName(j)).indexOf("quant");    // maximum qty to be pumped
    if (i != -1){  // have a request to set the latitude
      pcs.dblQty = String(server.arg(j)).toFloat() ;
      constrain(pcs.dblQty,0,PUMPED_QTY_MAX);
    }        
    i = String(server.argName(j)).indexOf("stime");
    if (i != -1){  // 
      rtc.tm.Year = (String(server.arg(j)).substring(0,4).toInt()-1970) ;
      rtc.tm.Month =(String(server.arg(j)).substring(5,7).toInt()) ;
      rtc.tm.Day = (String(server.arg(j)).substring(8,10).toInt()) ;
      rtc.tm.Hour =(String(server.arg(j)).substring(11,13).toInt()) ;
      rtc.tm.Minute = (String(server.arg(j)).substring(14,16).toInt()) ;
      rtc.tm.Second = 0 ;
      setTime(makeTime(rtc.tm));    
      if ( rtc.bHasRTC ){
        rtc.tc.sec = second();     
        rtc.tc.min = minute();     
        rtc.tc.hour = hour();   
        rtc.tc.wday = dayOfWeek(makeTime(rtc.tm));            
        rtc.tc.mday = day();  
        rtc.tc.mon = month();   
        rtc.tc.year = year();       
        DS3231_set(rtc.tc);                       // set the RTC as well
        rtc.rtc_status = DS3231_get_sreg();       // get the status
        DS3231_set_sreg(rtc.rtc_status & 0x7f ) ; // clear the clock fail bit when you set the time
      }
    }        
    
  }

  SendHTTPHeader();   //  ################### START OF THE RESPONSE  ######
  if (String(server.uri()).indexOf("stime")>0) {  // ################   SETUP TIME    #######################################
    bDefault = false ;
    snprintf(buff, BUFF_MAX, "%04d/%02d/%02d %02d:%02d", year(), month(), day() , hour(), minute());
    server.sendContent("<br><br><form method=get action=" + server.uri() + "><br>Set Current Time: <input type='text' name='stime' value='"+ String(buff) + "' size=12>");
    server.sendContent(F("<input type='submit' value='SET'><br><br></form>"));
  }
  if (bDefault) {     // #####################################   Default Control ##############################################  
    snprintf(buff, BUFF_MAX, "%d/%02d/%02d %02d:%02d:%02d", year(), month(), day() , hour(), minute(), second());
    if (nwc.fTimeZone > 0 ) {
      server.sendContent("<b>"+ String(buff) + " UTC +" + String(nwc.fTimeZone,1) ) ;   
    }else{
      server.sendContent("<b>"+ String(buff) + " UTC " + String(nwc.fTimeZone,1) ) ;       
    }
    if ( year() < 2000 ) {
      server.sendContent(F("  --- CLOCK NOT SET ---")) ;
    }
    server.sendContent(F("</b><br>")) ;  
    if ( pcs.AutoOff_t > now() )  {
      snprintf(buff, BUFF_MAX, "%d/%02d/%02d %02d:%02d:%02d", year(pcs.AutoOff_t), month(pcs.AutoOff_t), day(pcs.AutoOff_t) , hour(pcs.AutoOff_t), minute(pcs.AutoOff_t), second(pcs.AutoOff_t));
      server.sendContent(F("<b><font color=red>Automation OFFLINE Untill ")) ;  
      server.sendContent(String(buff)) ; 
      server.sendContent(F("</font></b><br>")) ; 
    }else{
      if ( year() > 2000 ) {
        server.sendContent(F("<b><font color=green>Automation ONLINE</font></b><br>")) ;  
      }else{
        server.sendContent(F("<b><font color=green>Automation OFFLINE Invalid time</font></b><br>")) ;        
      }
    }
  
    server.sendContent("<table border=1 title='Pump Control'>");
    server.sendContent("<tr><th> Parameter</th><th>Value</th><th>.</th></tr>");
  
    switch(pcs.lOnOff){
      case 1:
        MyText = String("ON") ;
        MyColor = " BGCOLOR='green' " ;
      break;
      case 2:
        MyText = String("AUTO") ;
        MyColor = " BGCOLOR='yellow' " ;
      break;
      default:
        MyText = String("OFF") ;
        MyColor = " BGCOLOR='red' " ;
      break;
    }
  
    server.sendContent("<tr><td "+ MyColor + ">Pump Control " + MyText + "</td><td align=right><form method=get action=/><input type='hidden' name='pumpea' value='1'><input type='submit' value='ON'></form>") ;
    server.sendContent("<form method=get action=/><input type='hidden' name='pumpea' value='2'><input type='submit' value='AUTO'></form>") ;
    server.sendContent("</td><td><form method=get action=/><input type='hidden' name='pumpea' value='0'><input type='submit' value='OFF'></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(pcs.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(pcs.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(pcs.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(pcs.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(pcs.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(pcs.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(pcs.PWM_inc) + "' size=6></td><td><input type='submit' value='SET'></td></tr></form>");
  
    server.sendContent("<form method=get action=" + server.uri() + "<tr><td>Display Options</td><td align=center>") ; 
    server.sendContent("<input type='text' name='disop' value='" + String(lDisplayOptions) + "' size=6 maxlength=3></td><td><input type='submit' value='SET'></td></tr></form>");
  
    if( rtc.bHasRTC ){
      rtc.rtc_status = DS3231_get_sreg();
      if (( rtc.rtc_status & 0x80 ) != 0 ){
        server.sendContent(F("<tr><td>RTC Battery</td><td align=center bgcolor='red'>DEPLETED</td><td></td></tr>")) ;            
      }else{
        server.sendContent(F("<tr><td>RTC Battery</td><td align=center bgcolor='green'>-- OK --</td><td></td></tr>")) ;                    
      }
      server.sendContent("<tr><td>RTC Temperature</td><td align=center>"+String(rtc.rtc_temp,1)+"</td><td>(C)</td></tr>") ;                    
    }
    
    server.sendContent("<tr><td><a href='/?command=4' title='click to clear'>Current Qty</a></td><td align=center>" + String(pcs.dblCurrentQty,3) + "</td><td>(l)</td></tr>");
  //  server.sendContent("<tr><td>Current On Counter</td><td align=center>" + String(pcs.lOnCounter) + "</td><td>(ms)</td></tr>");
  //  server.sendContent("<tr><td>Current Off Counter</td><td align=center>" + String(pcs.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(pcs.lCyclesCounter) + "</a></td><td>.</td></tr>");
    server.sendContent("</table>");
  }
  SendHTTPPageFooter();
}

MODBUS_UDP.ino

C/C++
unsigned long SendModbusPacketReply(char* address){
byte packetBuffer[ NTP_PACKET_SIZE];     //buffer to hold incoming and outgoing packets  
  uint8_t * data ; 
  uint8_t bc = 0 ;
  int i ;

  data = (uint8_t *) &pcs ;
  memset(packetBuffer, 0, NTP_PACKET_SIZE);    // set all bytes in the buffer to 0
  packetBuffer[0] = MB.net_id ; 
  packetBuffer[1] = MB.fc ;
  switch(MB.fc){
    case 3:
      bc = MB.wordcount * 2 ;
      packetBuffer[2] = bc ; 
      if ( bc > 32 ){
        packetBuffer[2] = 0 ; 
        packetBuffer[1] |= 0x80 ;   
      }else{
        for ( i = 0 ; i < bc ; i++ ){
          packetBuffer[3+i] = data[i] ;
        }
      }
    break;
    case 16:
    break;
    default:
        bc = 0 ;
        packetBuffer[2] = 0 ; 
        packetBuffer[1] |= 0x80 ;   
    break;
  }

  modbusudp.beginPacket(MB.remotePort, MB.remoteIP );          // send back to the remote port
  modbusudp.write(packetBuffer, (bc+3) );
  modbusudp.endPacket();
}

unsigned long ProcessModbusPacket(void){
  MB.remotePort = modbusudp.remotePort();
  MB.remoteIP = modbusudp.remoteIP();
  modbusudp.read(packetBuffer, NTP_PACKET_SIZE);       // the timestamp starts at byte 40 of the received packet and is four bytes, or two words, long. First, esxtract the two words:
  if ( packetBuffer[6] == 6 ){
    MB.net_id = packetBuffer[7] ;       // usefull stuff starts here
    MB.fc = packetBuffer[8] ;   
    switch(MB.fc){
      case 4:
        MB.address = packetBuffer[8]  << 8 | packetBuffer[9] ;   // get the address 
        MB.wordcount = packetBuffer[10] << 8 | packetBuffer[11] ;  // get the word count 
      break;
      case 16:
      break;
      default:
      break;
    }
    
  }
  while (modbusudp.available()){  // clean out the rest of the packet and dump overboard
    modbusudp.read(packetBuffer, sizeof(packetBuffer));  
  }
}

NTP_STUFF.ino

C/C++
unsigned long sendNTPpacket(char* address){
byte packetBuffer[ NTP_PACKET_SIZE];     //buffer to hold incoming and outgoing packets  
//  Serial.println("sending NTP packet...");
                    
  memset(packetBuffer, 0, NTP_PACKET_SIZE);    // set all bytes in the buffer to 0
  // Initialize values needed to form NTP request
  // (see URL above for details on the packets)
  packetBuffer[0] = 0b11100011;   // LI, Version, Mode
  packetBuffer[1] = 0;     // Stratum, or type of clock
  packetBuffer[2] = 6;     // Polling Interval
  packetBuffer[3] = 0xEC;  // Peer Clock Precision
  // 8 bytes of zero for Root Delay & Root Dispersion
  packetBuffer[12]  = 49;
  packetBuffer[13]  = 0x4E;
  packetBuffer[14]  = 49;
  packetBuffer[15]  = 52;

  // all NTP fields have been given values, now
  // you can send a packet requesting a timestamp:
  ntpudp.beginPacket(address, 123); //NTP requests are to port 123
  ntpudp.write(packetBuffer, NTP_PACKET_SIZE);
  ntpudp.endPacket();
}
unsigned long processNTPpacket(void){
    ntpudp.read(packetBuffer, NTP_PACKET_SIZE);                                         // the timestamp starts at byte 40 of the received packet and is four bytes, or two words, long. First, esxtract the two words:
    unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);                  // combine the four bytes (two words) into a long integer
    unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);
    unsigned long secsSince1900 = highWord << 16 | lowWord;                             // this is NTP time (seconds since Jan 1 1900):
    const unsigned long seventyYears = 2208988800UL;                                    // now convert NTP time into everyday time:     Unix time starts on Jan 1 1970. In seconds, that's 2208988800:   
    unsigned long epoch = secsSince1900 - seventyYears + long(nwc.fTimeZone * SECS_PER_HOUR);   // subtract seventy years:
    setTime((time_t)epoch);                                                             // update the clock
    Serial.print(F("Unix time = "));
    Serial.println(epoch);                                                              // print Unix time:
}

SETUP_PAGE.ino

C/C++
void handleSetup() {
int i ;
String MyColor ;
byte mac[6];
//  SerialOutParams();
  for (uint8_t j=0; j<server.args(); j++){
    i = String(server.argName(j)).indexOf("ndadd");
    if (i != -1){  // 
      nwc.lNodeAddress = String(server.arg(j)).toInt() ;
      constrain(nwc.lNodeAddress,0,32768);
    }        
    i = String(server.argName(j)).indexOf("tzone");
    if (i != -1){  // 
      nwc.fTimeZone = String(server.arg(j)).toFloat() ;
      constrain(nwc.fTimeZone,-12,12);
      rtc.bDoTimeUpdate = true ; // trigger and update to fix the time
    }        
    i = String(server.argName(j)).indexOf("disop");
    if (i != -1){  // 
      lDisplayOptions = String(server.arg(j)).toInt() ;
      constrain(lDisplayOptions,0,255);
    }  
    i = String(server.argName(j)).indexOf("lpntp");
    if (i != -1){  // 
      nwc.localPort = String(server.arg(j)).toInt() ;
      constrain(nwc.localPort,1,65535);
    }        
    i = String(server.argName(j)).indexOf("lpctr");
    if (i != -1){  // 
      nwc.localPortCtrl = String(server.arg(j)).toInt() ;
      constrain(nwc.localPortCtrl,1,65535);
    }        
    i = String(server.argName(j)).indexOf("rpctr");
    if (i != -1){  // 
      nwc.localPortCtrl = String(server.arg(j)).toInt() ;
      constrain(nwc.localPortCtrl,1,65535);
    }        
    i = String(server.argName(j)).indexOf("dontp");
    if (i != -1){  // have a request to request a time update
      rtc.bDoTimeUpdate = true ;
    }
    i = String(server.argName(j)).indexOf("cname");
    if (i != -1){  // have a request to request a time update
     String(server.arg(j)).toCharArray( nwc.NodeName , sizeof(nwc.NodeName)) ;
    }
    i = String(server.argName(j)).indexOf("atoff");    
    if (i != -1){  // have a request to request a time update
      rtc.tm.Year = (String(server.arg(j)).substring(0,4).toInt()-1970) ;
      rtc.tm.Month =(String(server.arg(j)).substring(5,7).toInt()) ;
      rtc.tm.Day = (String(server.arg(j)).substring(8,10).toInt()) ;
      rtc.tm.Hour =(String(server.arg(j)).substring(11,13).toInt()) ;
      rtc.tm.Minute = (String(server.arg(j)).substring(14,16).toInt()) ;
      rtc.tm.Second = 0 ;
      pcs.AutoOff_t = makeTime(rtc.tm);
    }  
    i = String(server.argName(j)).indexOf("nssid");
    if (i != -1){                                    // SSID
 //    Serial.println("SookyLala 1 ") ;
     String(server.arg(j)).toCharArray( nwc.nssid , sizeof(nwc.nssid)) ;
    }
    i = String(server.argName(j)).indexOf("npass");
    if (i != -1){                                    // Password
     String(server.arg(j)).toCharArray( nwc.npassword , sizeof(nwc.npassword)) ;
    }    
    i = String(server.argName(j)).indexOf("sssid");
    if (i != -1){                                    // SSID
 //    Serial.println("SookyLala 1 ") ;
     String(server.arg(j)).toCharArray( nwc.cssid , sizeof(nwc.nssid)) ;
    }
    i = String(server.argName(j)).indexOf("spass");
    if (i != -1){                                    // Password
     String(server.arg(j)).toCharArray( nwc.cpassword , sizeof(nwc.npassword)) ;
    }    
  }
  
  SendHTTPHeader();

  server.sendContent("<form method=get action=" + server.uri() + "><table border=1 title='Node Settings'>");
  server.sendContent(F("<tr><th width=100>Parameter</th><th width=165>Value</th><th width=55><input type='submit' value='SET'></th></tr>"));

  server.sendContent(F("<tr><td>Controler Name</td><td align=center>")) ; 
  server.sendContent("<input type='text' name='cname' value='"+String(nwc.NodeName)+"' maxlength=15 size=12></td><td></td></tr>");

  snprintf(buff, BUFF_MAX, "%04d/%02d/%02d %02d:%02d", year(pcs.AutoOff_t), month(pcs.AutoOff_t), day(pcs.AutoOff_t) , hour(pcs.AutoOff_t), minute(pcs.AutoOff_t));
  if (pcs.AutoOff_t > now()){
    MyColor =  F("bgcolor=red") ;
  }else{
    MyColor =  "" ;
  }
  server.sendContent("<tr><td "+String(MyColor)+">Auto Off Until</td><td align=center>") ; 
  server.sendContent("<input type='text' name='atoff' value='"+ String(buff) + "' size=12></td><td>(yyyy/mm/dd)</td></tr>");

  server.sendContent(F("<tr><td>Time Zone</td><td align=center>")) ; 
  server.sendContent("<input type='text' name='tzone' value='" + String(nwc.fTimeZone,1) + "' size=12></td><td>(Hours)</td></tr>");

  server.sendContent(F("<tr><td>Display Options</td><td align=center>")) ; 
  server.sendContent("<input type='text' name='disop' value='" + String(lDisplayOptions) + "' size=12></td><td></td></tr>");

  server.sendContent(F("<tr><td>Local UDP Port NTP</td><td align=center>")) ; 
  server.sendContent("<input type='text' name='lpntp' value='" + String(nwc.localPort) + "' size=12></td><td></td></tr>");

  server.sendContent(F("<tr><td>Local UDP Port Control</td><td align=center>")) ; 
  server.sendContent("<input type='text' name='lpctr' value='" + String(nwc.localPortCtrl) + "' size=12></td><td></td></tr>");

  server.sendContent(F("<tr><td>Remote UDP Port Control</td><td align=center>")) ; 
  server.sendContent("<input type='text' name='rpctr' value='" + String(nwc.RemotePortCtrl) + "' size=12></td><td></td></tr>");

  server.sendContent(F("<tr><td>Network SSID</td><td align=center>")) ; 
  server.sendContent("<input type='text' name='nssid' value='" + String(nwc.nssid) + "' maxlength=31 size=12></td><td></td></tr>");

  server.sendContent(F("<tr><td>Network Password</td><td align=center>")) ; 
  server.sendContent("<input type='text' name='npass' value='" + String(nwc.npassword) + "' maxlength=15 size=12></td><td></td></tr>");

  server.sendContent(F("<tr><td>Setup SSID</td><td align=center>")) ; 
  server.sendContent("<input type='text' name='sssid' value='" + String(nwc.cssid) + "' maxlength=31 size=12></td><td></td></tr>");

  server.sendContent(F("<tr><td>Setup Password</td><td align=center>")) ; 
  server.sendContent("<input type='text' name='spass' value='" + String(nwc.cpassword) + "' maxlength=15 size=12></td><td></td></tr>");

  server.sendContent("<tr><td>ESP ID</td><td align=center>0x" + String(ESP.getChipId(), HEX) + "</td><td align=center>"+String(ESP.getChipId())+"</td></tr>" ) ; 
  WiFi.macAddress(mac);      
  snprintf(buff, BUFF_MAX, "%02X:%02X:%02X:%02X:%02X:%02X", mac[5], mac[4], mac[3], mac[2], mac[1], mac[0]);
  server.sendContent("<tr><td>Station MAC Address</td><td align=center>" + String(buff) + "</td><td align=center>.</td></tr>" ) ; 
  WiFi.softAPmacAddress(mac);
  snprintf(buff, BUFF_MAX, "%02X:%02X:%02X:%02X:%02X:%02X", mac[5], mac[4], mac[3], mac[2], mac[1], mac[0]);
  server.sendContent("<tr><td>SoftAP MAC Address</td><td align=center>" + String(buff) + "</td><td align=center>.</td></tr>" ) ; 
  snprintf(buff, BUFF_MAX, "%03u.%03u.%03u.%03u", nwc.MyIP[0],nwc.MyIP[1],nwc.MyIP[2],nwc.MyIP[3]);
  server.sendContent("<tr><td>Network IP Address</td><td align=center>" + String(buff) + "</td><td>.</td></tr>" ) ; 
  snprintf(buff, BUFF_MAX, "%03u.%03u.%03u.%03u", nwc.MyIPC[0],nwc.MyIPC[1],nwc.MyIPC[2],nwc.MyIPC[3]);
  server.sendContent("<tr><td>Soft AP IP Address</td><td align=center>" + String(buff) + "</td><td>.</td></tr>" ) ; 

  server.sendContent("<tr><td>WiFi RSSI</td><td align=center>" + String(WiFi.RSSI()) + "</td><td>(dBm)</td></tr>" ) ; 
  snprintf(buff, BUFF_MAX, "%03u.%03u.%03u.%03u", nwc.MyIP[0],nwc.MyIP[1],nwc.MyIP[2],nwc.MyIP[3]);
  server.sendContent("<tr><td>Node IP Address</td><td align=center>" + String(buff) + "</td><td>.</td></tr>" ) ; 
  server.sendContent("<tr><td>Last Scan Speed</td><td align=center>" + String(lScanLast) + "</td><td>(per second)</td></tr>" ) ;    
  if( rtc.bHasRTC ){
    rtc.rtc_status = DS3231_get_sreg();
    if (( rtc.rtc_status & 0x80 ) != 0 ){
      server.sendContent(F("<tr><td>RTC Battery</td><td align=center bgcolor='red'>DEPLETED</td><td></td></tr>")) ;            
    }else{
      server.sendContent(F("<tr><td>RTC Battery</td><td align=center bgcolor='green'>-- OK --</td><td></td></tr>")) ;                    
    }
    server.sendContent("<tr><td>RTC Temperature</td><td align=center>"+String(rtc.rtc_temp,1)+"</td><td>(C)</td></tr>") ;                    
  }
  server.sendContent("<tr><td>ESP Core Version</td><td align=center>" + String(ESP.getCoreVersion()) + "</td><td>.</td></tr>" ) ;    
  server.sendContent("<tr><td>ESP Full Version</td><td align=center>" + String(ESP.getFullVersion()) + "</td><td>.</td></tr>" ) ;    
  server.sendContent("<tr><td>SDK Version</td><td align=center>" + String(ESP.getSdkVersion()) + "</td><td>.</td></tr>" ) ;    
  server.sendContent("<tr><td>CPU Volts</td><td align=center>" + String(ESP.getVcc()) + "</td><td>(V)</td></tr>" ) ;    
  server.sendContent("<tr><td>CPU Frequecy</td><td align=center>" + String(ESP.getCpuFreqMHz()) + "</td><td>(MHz)</td></tr>" ) ;    
  server.sendContent("<tr><td>Get Rest Reason</td><td align=center>" + String(ESP.getResetReason()) + "</td><td></td></tr>" ) ;    
  server.sendContent("<tr><td>Get Reset Into</td><td align=center>" + String(ESP.getResetInfo()) + "</td><td></td></tr>" ) ;    
  server.sendContent("<tr><td>Get Sketch Size</td><td align=center>" + String(ESP.getSketchSize()) + "</td><td>(Bytes)</td></tr>" ) ;    
  server.sendContent("<tr><td>Free Sketch Space</td><td align=center>" + String(ESP.getFreeSketchSpace()) + "</td><td>(Bytes)</td></tr>" ) ;    
  
  server.sendContent(F("</form></table>"));
  SendHTTPPageFooter() ;

}

Chemical Injector Controller

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