diff --git a/Documentation/Grow Controller EL System Diagram.vsdx b/Documentation/Grow Controller EL System Diagram.vsdx index e30fd96..e769929 100644 Binary files a/Documentation/Grow Controller EL System Diagram.vsdx and b/Documentation/Grow Controller EL System Diagram.vsdx differ diff --git a/Firmware/CustomHandlers.cpp b/Firmware/CustomHandlers.cpp index e7f26a8..ebfb551 100644 --- a/Firmware/CustomHandlers.cpp +++ b/Firmware/CustomHandlers.cpp @@ -179,6 +179,7 @@ void HandleAPConfigPage() { String url = wifiConfig.getURL(); String ntp = wifiConfig.getNTPServerName(); + // Handle submissions from the client if (server.hasArg("ssid") && server.hasArg("pass")) { String _ssid = server.hasArg("_ssid") ? server.arg("_ssid") : ""; String _pass = server.hasArg("_pass") ? server.arg("_pass") : ""; @@ -207,11 +208,17 @@ void HandleAPConfigPage() { } } + // Send [updated] Configuration page to client + // String modeText = (WiFiStateGet() == WFC_JoinedAP) ? "Station mode
" : "Access Point mode
"; if (server.hasArg("ssid") && !server.hasArg("pass")) { ssid = server.arg("ssid"); pass = ""; Serial.println("ssid: " + ssid + ", pass: " + pass); + } else { + Serial.printf("name : %s\n", name.c_str()); + Serial.printf(" ssid: %s\n", ssid.c_str()); + Serial.printf(" pass: %s\n", pass.c_str()); } if (server.hasArg("url")) url = server.arg("url"); @@ -220,60 +227,55 @@ void HandleAPConfigPage() { server.send(200, "text/html", "\n" "\n" - "Grow Controller - Config\n" - "\n" + "Grow Controller - Config\n" + "\n" + "\n" + "\n" + "\n" "

Grow Controller - Configuration

" "
\n" "
\n" " \n" " \n" " \n" - " \n" + " \n" " \n" " \n" " \n" - " \n" + " \n" " \n" " \n" " \n" - " \n" + " \n" " \n" " \n" " \n" - " \n" + " \n" + " \n" + " \n" + " \n" + " \n" " \n" -// " \n" -// " \n" -// " \n" -// " \n" " \n" " \n" " \n" " \n" - " \n" + " \n" " \n" - " \n" - " \n" - " \n" - " \n" - " \n" + " \n" + " \n" + " \n" + " \n" + " \n" "
Node Name
SSID **\n" - "  **\n" + "
Password **\n" - "  **\n" + "
Update URL
NTP Server
NTP Server
 \n" - " \n" - " ** Changes to these will trigger a module restart.\n" + " \n" + " ** Changes to these will trigger a module restart.\n" "

 Check for SW update (module will restart if updated)
 Check for SW update (module will restart if updated)
\n" "
\n" "
\n" - "NAV:  " - " Home | " - " Config | " - " Scan | " - " RSSI | " - " Current | " - " Factory Reset | " - " About\n" - "
\n" + "\n" "\n" "\n" ); diff --git a/Firmware/PlantModel.h b/Firmware/PlantModel.h index 1c2c54e..34367b0 100644 --- a/Firmware/PlantModel.h +++ b/Firmware/PlantModel.h @@ -1,9 +1,12 @@ // // // +#ifndef PLANTMODEL_H +#define PLANTMODEL_H #include "ProjGlobals.h" void HandleGetState(); void simulation(); +#endif // PLANTMODEL_H diff --git a/Firmware/Resources/Open.png b/Firmware/Resources/Open.png index 72bc390..feefe3a 100644 Binary files a/Firmware/Resources/Open.png and b/Firmware/Resources/Open.png differ diff --git a/Firmware/Resources/curr.js b/Firmware/Resources/curr.js index 458b553..5bdde88 100644 --- a/Firmware/Resources/curr.js +++ b/Firmware/Resources/curr.js @@ -28,12 +28,12 @@ getIt = function (aUrl, callback) { function RefreshStatus() { getIt(url, - function (data) { - obj = JSON.parse(data); - UpdateGraph(obj.raw, obj.sense); // raw and averaged - document.getElementById('uptime').innerHTML = 'Uptime: ' + obj.uptime; - document.getElementById('currText').innerHTML = 'raw: ' + obj.raw + ', avg: ' + obj.sense; - } + function (data) { + obj = JSON.parse(data); + UpdateGraph(obj.raw, obj.sense); // raw and averaged + document.getElementById('uptime').innerHTML = 'Uptime: ' + obj.uptime; + document.getElementById('currText').innerHTML = 'raw: ' + obj.raw + ', avg: ' + obj.sense; + } ); } diff --git a/Firmware/Resources/index.htm b/Firmware/Resources/index.htm index 168a91e..4a737c0 100644 --- a/Firmware/Resources/index.htm +++ b/Firmware/Resources/index.htm @@ -39,7 +39,7 @@
--.- °F
-- %
-
+
O
C
Motor
@@ -57,7 +57,7 @@
8 s
10 s
-
+
diff --git a/Firmware/Resources/index.js b/Firmware/Resources/index.js index ec3d477..ee8c703 100644 --- a/Firmware/Resources/index.js +++ b/Firmware/Resources/index.js @@ -1,9 +1,13 @@ // // Main Page period status update // +// @todo Draw the 'drum' as an arc instead of trying to rotate a picture of an arc. +// which will work better to reflect the opening. +// +// // var mySite = 'http://192.168.1.23'; // from myip.js var url = mySite + '/state'; -setInterval(RefreshStatus, 2500); +setInterval(RefreshStatus, 1000); // @todo speed this up to 500 msec or even 250 getIt = function (aUrl, callback) { var xhr = new XMLHttpRequest(); @@ -19,28 +23,24 @@ getIt = function (aUrl, callback) { function ConvertTempToString(tempC, showF) { var tempF = tempC * 9 / 5 + 32; - if (showF) - return tempF.toFixed(1) + "°F"; + if (showF == 0) + return tempF.toFixed(0) + "°F"; else - return tempC.toFixed(1) + "°C"; + return tempC.toFixed(0) + "°C"; +} + +function ConvertSecToMMSS(sec) { + mm = parseInt(sec/60).toString().padStart(4,'0'); + ss = (sec%60).toString().padStart(2,'0'); + return mm + ":" + ss; } function RefreshStatus() { getIt(url, function(data) { obj = JSON.parse(data); - if (obj.state === 1) - state = "On"; - else - state = "Off"; document.getElementById('version').innerHTML = obj.version; document.getElementById('name').innerHTML = obj.name; - //document.getElementById('state').innerHTML = 'Output: ' + state; - //document.getElementById('raw').innerHTML = 'raw: ' + obj.raw; - //document.getElementById('sense').innerHTML = 'Sense: ' + obj.sense; - //document.getElementById('toggle').innerHTML = 'Toggle: ' + obj.toggle; - //document.getElementById('reset').innerHTML = 'Reset: ' + obj.reset; - //document.getElementById('countdown').innerHTML = 'Countdown: ' + obj.countdown; document.getElementById('uptime').innerHTML = 'Uptime: ' + obj.uptime; document.getElementById('wifimode').innerHTML = 'WiFi Mode: ' + obj.wifimode; // IP Address @@ -67,7 +67,7 @@ function RefreshStatus() { } if (typeof obj.SoilHeater != "undefined") { - document.getElementById('SoilHeater').innerHTML = obj.SoilHeater; + document.getElementById('SoilHeater').innerHTML = (obj.SoilHeater) ? "Heat On" : "Heat Off"; } else { document.getElementById('SoilHeater').innerHTML = "--"; } @@ -83,24 +83,34 @@ function RefreshStatus() { document.getElementById('SoilMoisture_X').innerHTML = "--"; } - if (obj.DrumDoorIcon == 1) { - document.getElementById('DrumDoorIcon').style.display === "none"; + if (obj.DrumState == 1) { // closed, hide the "open" image + document.getElementById('DrumState').style.display === "none"; } else { - document.getElementById('DrumDoorIcon').style.display === "block"; + document.getElementById('DrumState').style.display === "block"; } - document.getElementById('DrumOpenSensor').innerHTML = obj.DrumOpenSensor; - document.getElementById('DrumClosedSensor').innerHTML = obj.DrumClosedSensor; - document.getElementById('DrumMotorStatus').innerHTML = obj.DrumMotorStatus; + document.getElementById('DrumOpenSensor').innerHTML = (obj.DrumOpenSensor) ? "O" : "!O"; + document.getElementById('DrumClosedSensor').innerHTML = (obj.DrumClosedSensor) ? "C" : "!C"; + document.getElementById('DrumMotorStatus').innerHTML = (obj.DrumMotorStatus == 0) ? "stopped" : (obj.DrumMotorStatus == 1) ? "CW" : "CCW"; document.getElementById('DrumMotorOn_sec').innerHTML = obj.DrumMotorOn_sec + "s"; - document.getElementById('DrumMotor_V').innerHTML = parseFloat(obj.DrumMotor_V).toFixed(1) + "V"; - document.getElementById('DrumMotor_I').innerHTML = parseFloat(obj.DrumMotor_I).toFixed(1) + "A"; + document.getElementById('DrumMotor_V').innerHTML = parseFloat(obj.DrumMotor_V).toFixed(1) + " V"; + document.getElementById('DrumMotor_I').innerHTML = parseFloat(obj.DrumMotor_I).toFixed(1) + " A"; document.getElementById('WaterInTank').innerHTML = obj.WaterInTank; - document.getElementById('WaterPumpStatus').innerHTML = obj.WaterPumpStatus; - document.getElementById('WaterMotor_V').innerHTML = parseFloat(obj.WaterMotor_V).toFixed(1) + "V"; - document.getElementById('WaterCyclePeriod_sec').innerHTML = obj.WaterCyclePeriod_sec + "s"; - document.getElementById('WaterCycleOn_sec').innerHTML = obj.WaterCycleOn_sec + "s"; - document.getElementById('WaterCycle_sec').innerHTML = obj.WaterCycle_sec + "s"; + document.getElementById('WaterPumpStatus').innerHTML = (obj.WaterPumpStatus) ? "On" : "Off"; + document.getElementById('WaterMotor_V').innerHTML = parseFloat(obj.WaterMotor_V).toFixed(1) + " V"; + document.getElementById('WaterCyclePeriod_sec').innerHTML = ConvertSecToMMSS(obj.WaterCyclePeriod_sec); + document.getElementById('WaterCycleOn_sec').innerHTML = ConvertSecToMMSS(obj.WaterCycleOn_sec); + document.getElementById('WaterCycle_sec').innerHTML = ConvertSecToMMSS(obj.WaterCycle_sec); + + // The full-scale pump timer box ranges from 22 to 234px + // The WaterCycleOnBox.width is proportional to the WaterCyclePeriod_sec and the (234 - 22)px width + var boxW = (234 - 22); + // The WaterCyclePointer_sec.width (24) is positioned from 22 to 234 - 1/2 width. + var onW = (obj.WaterCycleOn_sec/obj.WaterCyclePeriod_sec) * boxW; + var ptrX = 22 + (obj.WaterCycle_sec/obj.WaterCyclePeriod_sec) * boxW - 12; + console.log("onW: " + onW + ", ptrX: " + ptrX); + document.getElementById('WaterCycleOnBox').width = onW; + document.getElementById('WaterCyclePointer_sec').left = ptrX; } ); } diff --git a/Firmware/Resources/plantmodel.css b/Firmware/Resources/plantmodel.css index 79ce1bc..c94938b 100644 --- a/Firmware/Resources/plantmodel.css +++ b/Firmware/Resources/plantmodel.css @@ -53,7 +53,7 @@ div.ChamberHumi { } div.SoilMoisture_X { position: absolute; - left: 130px; + left: 135px; top: 135px; color: white; } @@ -64,12 +64,12 @@ div.SoilHumiIcon { } div.SoilThermoIcon { position: absolute; - left: 170px; + left: 175px; top: 100px; } div.SoilTemp_C { position: absolute; - left: 165px; + left: 170px; top: 135px; color: white; } @@ -90,7 +90,7 @@ div.SoilHeaterOffIcon { top: 181px; } -div.DrumDoorIcon { +div.DrumState { position: absolute; transform: rotate(180deg); left: 14px; @@ -109,7 +109,7 @@ div.DrumClosedSensor { div.DrumMotorLabel { position: absolute; left: 95px; - top: 270px; + top: 260px; width: 50px; text-align: center; color: white; @@ -117,7 +117,7 @@ div.DrumMotorLabel { div.DrumMotorStatus { position: absolute; left: 95px; - top: 290px; + top: 280px; width: 50px; text-align: center; color: white; @@ -231,19 +231,19 @@ div.WaterCycleOnBox { height: 20px; background-color: #BDD7EE; z-index: -35; - //border-color: red; + border-color: red; border-style: solid; border-width: 1px; } -div.WaterCycleStatusIcon { +div.WaterCyclePointer_sec { position: absolute; left: 80px; top: 469px; - width: 25px; + width: 24px; text-align: center; z-index: -25; border-color: green; - //border-style: solid; + border-style: solid; border-width: 1px; } \ No newline at end of file diff --git a/Firmware/Web_Resources.h b/Firmware/Web_Resources.h index fbc3d4f..2ec7677 100644 --- a/Firmware/Web_Resources.h +++ b/Firmware/Web_Resources.h @@ -1,8 +1,8 @@ // // Command: MakeByteArray -ESP -DIR -ROOT *.* -o=..\Web_Resources.h // version: 1.0 -// From cwd: D:/Projects/GrowController/gcfw/Resources -// Generated: Mon Oct 25 17:32:22 2021 +// From cwd: C:/Projects/GrowController/Firmware/Resources +// Generated: Tue Oct 26 13:26:40 2021 // NOTE: Run script in command shell, not PowerShell // // PROGMEM is not recognized by Win32 tools... @@ -1208,7 +1208,7 @@ const char index_htm[] PROGMEM = { "
--.- °F
\n" "
-- %
\n" " \n" - "
\n" + "
\n" "
O
\n" "
C
\n" "
Motor
\n" @@ -1226,7 +1226,7 @@ const char index_htm[] PROGMEM = { "
8 s
\n" "
10 s
\n" "
\n" - "
\n" + "
\n" " \n" " \n" " \n" @@ -1343,7 +1343,7 @@ const char index_js[] PROGMEM = { " } \n" "\n" " if (typeof obj.SoilHeater != \"undefined\") {\n" - " document.getElementById('SoilHeater').innerHTML = obj.SoilHeater;\n" + " document.getElementById('SoilHeater').innerHTML = (obj.SoilHeater) ? \"Heat On\" : \"Heat Off\";\n" " } else {\n" " document.getElementById('SoilHeater').innerHTML = \"--\";\n" " }\n" @@ -1359,14 +1359,14 @@ const char index_js[] PROGMEM = { " document.getElementById('SoilMoisture_X').innerHTML = \"--\";\n" " }\n" " \n" - " if (obj.DrumDoorIcon == 1) {\n" - " document.getElementById('DrumDoorIcon').style.display === \"none\";\n" + " if (obj.DrumState == 1) { // closed, hide the \"open\" image\n" + " document.getElementById('DrumState').style.display === \"none\";\n" " } else {\n" - " document.getElementById('DrumDoorIcon').style.display === \"block\";\n" + " document.getElementById('DrumState').style.display === \"block\";\n" " }\n" - " document.getElementById('DrumOpenSensor').innerHTML = obj.DrumOpenSensor;\n" - " document.getElementById('DrumClosedSensor').innerHTML = obj.DrumClosedSensor;\n" - " document.getElementById('DrumMotorStatus').innerHTML = obj.DrumMotorStatus;\n" + " document.getElementById('DrumOpenSensor').innerHTML = (obj.DrumOpenSensor) ? \"O\" : \"!O\";\n" + " document.getElementById('DrumClosedSensor').innerHTML = (obj.DrumClosedSensor) ? \"C\" : \"!C\";\n" + " document.getElementById('DrumMotorStatus').innerHTML = (obj.DrumMotorStatus == 0) ? \"stopped\" : (obj.DrumMotorStatus == 1) ? \"CW\" : \"CCW\";\n" " document.getElementById('DrumMotorOn_sec').innerHTML = obj.DrumMotorOn_sec + \"s\";\n" " document.getElementById('DrumMotor_V').innerHTML = parseFloat(obj.DrumMotor_V).toFixed(1) + \"V\";\n" " document.getElementById('DrumMotor_I').innerHTML = parseFloat(obj.DrumMotor_I).toFixed(1) + \"A\";\n" @@ -1380,7 +1380,7 @@ const char index_js[] PROGMEM = { " }\n" " );\n" "}\n" -}; // index_js, 4528 bytes +}; // index_js, 4702 bytes // File: myip.js // @@ -2475,7 +2475,7 @@ const char plantmodel_css[] PROGMEM = { " top: 181px;\n" "}\n" "\n" - "div.DrumDoorIcon {\n" + "div.DrumState {\n" " position: absolute;\n" " transform: rotate(180deg);\n" " left: 14px;\n" @@ -2494,7 +2494,7 @@ const char plantmodel_css[] PROGMEM = { "div.DrumMotorLabel {\n" " position: absolute;\n" " left: 95px;\n" - " top: 270px;\n" + " top: 260px;\n" " width: 50px;\n" " text-align: center;\n" " color: white;\n" @@ -2502,7 +2502,7 @@ const char plantmodel_css[] PROGMEM = { "div.DrumMotorStatus {\n" " position: absolute;\n" " left: 95px;\n" - " top: 290px;\n" + " top: 280px;\n" " width: 50px;\n" " text-align: center;\n" " color: white;\n" @@ -2620,7 +2620,7 @@ const char plantmodel_css[] PROGMEM = { " border-style: solid;\n" " border-width: 1px;\n" "}\n" - "div.WaterCycleStatusIcon {\n" + "div.WaterCyclePointer_sec {\n" " position: absolute;\n" " left: 80px;\n" " top: 469px;\n" @@ -4634,7 +4634,7 @@ const DirEntry Directory[] PROGMEM = { { "/green1x1.png" , green1x1_png , "image/png", 119 }, { "/icon.png" , icon_png , "image/png", 8721 }, { "/index.htm" , index_htm , "text/html", 5346 }, - { "/index.js" , index_js , "text/javascript", 4528 }, + { "/index.js" , index_js , "text/javascript", 4702 }, { "/myip.js" , myip_js , "text/javascript", 167 }, { "/nav.js" , nav_js , "text/javascript", 684 }, { "/navigation.htm" , navigation_htm , "text/html", 557 }, diff --git a/Firmware/WiFiStateHandler.cpp b/Firmware/WiFiStateHandler.cpp index c385dc7..fd470ea 100644 --- a/Firmware/WiFiStateHandler.cpp +++ b/Firmware/WiFiStateHandler.cpp @@ -5,6 +5,10 @@ #include "ProjGlobals.h" #include "WiFiStateHandler.h" +#include ///< Required for Captive Portal behavior. +const byte DNS_PORT = 53; ///< Capture DNS requests on port 53 +//DNSServer dnsServer; ///< Used for the captive portal AP mode + WiFiConnectState wifiConState; ///< Track the current WiFi state @@ -73,7 +77,7 @@ WiFiConnectState WiFiStateHandler() { WiFi.softAP(AP_NAME.c_str()); myIP = WiFi.softAPIP(); Serial.printf("IP Address: %s\n", myIP.toString().c_str()); - // @todo dnsServer.start(DNS_PORT, "*", apIP); + //dnsServer.start(DNS_PORT, "growcontroller.info", myIP); timeStateChange = millis(); //StartWebServer(); wifiConState = WFC_HostingAP; @@ -93,7 +97,7 @@ WiFiConnectState WiFiStateHandler() { // @todo SetLED(LED_WIFI, 0); // @todo } // @todo server.handleClient(); - // @todo dnsServer.processNextRequest(); +// dnsServer.processNextRequest(); break; default: case WFC_Idle: