Hardware components | ||||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
Software apps and online services | ||||||
| ||||||
| ||||||
Hand tools and fabrication machines | ||||||
| ||||||
|
Following on from the previous seasons effort our portable unit gets a make over.
This year we had a new challenge, To dose an insanely expensive systemic pesticide ("SAMURAI") to rid our orchard of highly resistant and notoriously difficult species to kill. This meant new pumping and control software. We also wanted to interface this injector via the farm network with the others running on the irrigation control dot net platform.
Our farm is nominally controlled via a system called "Project Thor", It's a dot net based system running on ARM CPU with windows CE4 (yes it's old I wrote it over 10 years ago) This system broadcasts fertilization information to the injector program via UDP port, the injector process receives this and processes the data to activate the injector pumps to allow the correct amount of chemicals to be added to the water.
So our job here is simple, add a new mode to the system where it will respond as if it was an injector on the main system. That way we can remote inject specific extras into an irrigation lateral without these chemicals being added to all the water. We also needed to fix up many issues with the original ESP8266 program, especially the timing accuracy ones. Maybe even attach a OLED display as I found myself impatiently checking quantities pumped and remaining far to often via the cell phone. The original drop down list for turning the pump on and off SUX! So me thinks it will be replaced with some (ON/OFF) buttons on the form instead.
The original injector programs are written in Visual C++ Embedded, they use a windows timer in CE to produce the pulses via a callback. The code although primitive is surprisingly accurate and reliable. It allows multiple requests to be processed via the same program and it is the fasted process to startup in the system. All it does is process a text string sent in a UDP packet ( slot, bit, time ). It turns on the output and posts a timer request for the time embedding the slot and bit data into a parameter so it can be retrieved when the WM_TIMER message comes back. It then turns off the output as per the data in the timer tick message.
So in our project we will need to pick a slot and bit we want to match (in the setup page) then we listen for the request and turn on the pump as requested if it is in the magic mode, simple yes !
So the code will consist of my trusty usual complement of web pages for setup, i2csan, OTA for updates. And utilize UDP for SCADA communication.
You will notice I've finally worked out how to breakup the project on tabs in the IDE. So it's much easier to work on and understand. Actually this project was OK it was another scary long one that made me work this out but makes it all simpler anyhow.
I did have some issues with what data I thought was being stored in the EEProm so I wrote a page to display this. This also helped me work out what blank/uninitialized memory actually looks like. Turned out my issue was with calling String() with an IP address as an argument as well as other issues of a similar ilk which were just laziness/dumbness on my behalf.
One thing I have finally nailed down is the network startup. I can now have my cake and eat it, soft AP and client connection with 2 IP addresses. Now combining this with blank memory detection and a config startup pin and you get much closer to a proper "factory" self initialization system.
The OLED display is very nice addition, no more guessing. I think this be the best of both worlds, setup via mobile phone, control via intranet and output on the OLED display. All the while maintaining IP65 or better on the box.
#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);
}
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() ;
}
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();
}
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));
}
}
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:
}
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() ;
}
Comments