diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..89cc49c --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.pio +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..080e70d --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,10 @@ +{ + // See http://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": [ + "platformio.platformio-ide" + ], + "unwantedRecommendations": [ + "ms-vscode.cpptools-extension-pack" + ] +} diff --git a/BREAD-Local-Software.code-workspace b/BREAD-Local-Software.code-workspace new file mode 100644 index 0000000..139ddfe --- /dev/null +++ b/BREAD-Local-Software.code-workspace @@ -0,0 +1,10 @@ +{ + "folders": [ + { + "path": "." + } + ], + "settings": { + "liveServer.settings.multiRootWorkspaceName": "BREAD-Local-Software" + } +} \ No newline at end of file diff --git a/DCMT_R1/DCMT_R1.ino b/DCMT_R1/DCMT_R1.ino index fd519e5..35ce172 100644 --- a/DCMT_R1/DCMT_R1.ino +++ b/DCMT_R1/DCMT_R1.ino @@ -207,6 +207,7 @@ void loop() { } else if(DCMT.turbPump1 || DCMT.turbPump2){ // run as turbidity pump measureTurbidity(); + calculateTurbidity(); }else{ // run as continuous motor/pump actuateMotors(); } @@ -292,15 +293,20 @@ void measureTurbidity() } } +//Maybe needs to change to: Output Voltage (V)= -0.0008× (Turbidity of Water (NTU)) + 3.9994 +//Needs testing and calibration with known NTU values // need to change once biomass is calibrated void calculateTurbidity(){ - float x; - if(DCMT.turbVoltage[1] >= 2.5){ - x = DCMT.turbVoltage[1]; - }else{ - x = 2.5; + for(int i = 0; i < 2; i++){ + float x; + if(DCMT.turbVoltage[i] >= 2.5){ + x = DCMT.turbVoltage[i]; + }else{ + x = 2.5; + } + DCMT.currentTurbidity[i] = (5742.3 - 1120.4*x) * x - 4352.9 - TURBIDITY_OFFSET; + //DCMT.currentTurbidity[i] = (x-3.9994)/(-0.0008); //Potential equation replacement } - DCMT.currentTurbidity[1] = (5742.3 - 1120.4*x) * x - 4352.9 - TURBIDITY_OFFSET; } void setPIDTunings() @@ -415,12 +421,12 @@ void requestEvent() { if(isnan(DCMT.turbVoltage[0]) || !(DCMT.turbPump1)) turb1.number = 0; else - turb1.number = DCMT.turbVoltage[0]; + turb1.number = DCMT.currentTurbidity[0]; if(isnan(DCMT.turbVoltage[1]) || !(DCMT.turbPump2)) turb2.number = 0; else - turb2.number = DCMT.turbVoltage[1]; + turb2.number = DCMT.currentTurbidity[1]; for (int i = 0; i < 4; i++) Wire.write(turb1.bytes[i]); for (int i = 0; i < 4; i++) Wire.write(turb2.bytes[i]); diff --git a/RLHT_R2/RLHT_R2.ino b/RLHT_R2/RLHT_R2.ino index c45c821..c0dda6d 100644 --- a/RLHT_R2/RLHT_R2.ino +++ b/RLHT_R2/RLHT_R2.ino @@ -20,6 +20,10 @@ long lastThermoRead = 0; long lastSerialPrint = 0; +//Edit these offsets to calibrate thermocouples as needed +double thermo1_offset = -4.0; +double thermo2_offset = -4.0; + bool E_STOP = false; CRGB led; @@ -224,8 +228,8 @@ void measureThermocouples() { if(millis() - lastThermoRead >= THERMO_UPDATE_TIME_MS) { - RLHT.thermo1 = CH1.readCelsius(); - RLHT.thermo2 = CH2.readCelsius(); + RLHT.thermo1 = CH1.readCelsius() + thermo1_offset; + RLHT.thermo2 = CH2.readCelsius() + thermo2_offset; lastThermoRead = millis(); } } @@ -356,5 +360,16 @@ void setParametersRLHT(char *in_data) RLHT.Kd_2 = (double)float3.number; } break; + case 'O': + for(int i=0; i<4; i++) float1.bytes[i] = in_data[i+2]; + if(in_data[1] == 1) + { + thermo1_offset = (double)float1.number; + } + if(in_data[1] == 2) + { + thermo2_offset = (double)float1.number; + } + break; } -} \ No newline at end of file +} diff --git a/Website Code on SD Card/css/style.css b/Website Code on SD Card/css/style.css new file mode 100644 index 0000000..c830531 --- /dev/null +++ b/Website Code on SD Card/css/style.css @@ -0,0 +1,72 @@ +/* Set height of body and the document to 100% to enable "full page tabs" */ +body, html { + /* height: 100%; */ + margin: 0; + font-family: Arial; + } + + /* Style tab links */ + .tablink { + background-color: #555; + color: white; + float: left; + border: none; + outline: none; + cursor: pointer; + padding: 14px 16px; + font-size: 17px; + width: 25%; + } + .tabnav { + width: 100%; + background-color: #777; + display: flex; + } + + .tablink:hover { + background-color: #777; + } + + /* Style the tab content (and add height:100% for full page content) */ + .tabcontent { + color: black; + display: grid; + padding: 100px 20px; + height: 100%; + } + + /* Ensure grid containers have explicit sizes */ + .grid-container { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 10px; + width: 100%; + max-width: 1200px; + margin: 0 auto; +} + +/* Set heights to prevent overlap */ +.graph-container, .gauge-container { + height: 350px; + position: relative; +} + +/* Prevent graph overlap */ +.graph-container { + z-index: 1; +} + +/* Ensure gauges are on top */ +.gauge-container { + z-index: 2; +} + +/* Tab content visibility */ +.tabcontent { + display: none; + padding: 20px; +} + +.tabcontent.active { + display: block; +} \ No newline at end of file diff --git a/Website Code on SD Card/favicon.ico b/Website Code on SD Card/favicon.ico new file mode 100644 index 0000000..45bd893 Binary files /dev/null and b/Website Code on SD Card/favicon.ico differ diff --git a/Website Code on SD Card/index.html b/Website Code on SD Card/index.html index 7bef873..d7cc3ae 100644 --- a/Website Code on SD Card/index.html +++ b/Website Code on SD Card/index.html @@ -1,2019 +1,2036 @@ - - - - - - -

BREAD Configuration and Controls

-
- - -
- - -
-
- - -
-
- - -
- -
-

Connection to ESP32:

-
-
-
- - -
+ + + + -
-

Pyrolysis Reactor

- -
-
-
- -
- -
- -
-
-
- - -
- - -
- -
- -
-

Thermocouple #:

-

Heater #:

+

BREAD Configuration and Controls

+
+ + +
+ +
-
-
- -
- -
- -
-
-
- - -
- - -
- -
- -
-

Thermocouple #:

-

Heater #:

+
+ +
-
-
- -
- -
- -
-
-
- - -
- - -
- -
- -
-

Thermocouple #:

-

Heater #:

+
+ +
-
-
- -
- -
- -
-
-
- - -
- - -
- -
- -
-

Thermocouple #:

-

Heater #:

+ +
+

Connection to ESP32:

+
-
-
- -
- -
- -
-
-
- - +
+ + +
+ +
+ + +
+
+
+ + + +
+ +
- - +
+ + +
+ + +
+ +
+ +
+

Thermocouple #:

+

Heater #:

+
+
+
+ +
+ +
+ +

- -
- - -

Thermocouple #:

-

Heater #:

-
-
-
- -
- -
- -
-
-
- - + + + +
+ + +
+ +
+ +
+

Thermocouple #:

+

Heater #:

+
+
+
+ +
+ +
+ +

- - +
+ + +
+ + +
+ +
+ +
+

Thermocouple #:

+

Heater #:

+
+
+
+ +
+ +
+ +

- -
- - -

Thermocouple #:

-

Heater #:

-
-
-
- -
- -
- -
-
-
- - + + + +
+ + +
+ +
+ +
+

Thermocouple #:

+

Heater #:

+
+
+
+ +
+ +
+ +

- - +
+ + +
+ + +
+ +
+ +
+

Thermocouple #:

+

Heater #:

+
+
+
+ +
+ +
+ +

- -
- - -

Thermocouple #:

-

Heater #:

-
-
-
- -
- -
- -
-
-
- - -
- - -
- -
- -
-

Thermocouple #:

-

Heater #:

-
-
-
- -
- -
- -
-
-
- - + + + +
+ + +
+ +
+ +
+

Thermocouple #:

+

Heater #:

+
+
+
+ +
+ +
+ +

- - +
+ + +
+ + +
+ +
+ +
+

Thermocouple #:

+

Heater #:

+
+
+
+ +
+ +
+ +

- -
- - -

Thermocouple #:

-

Heater #:

-
-
- -
- -
- -
-
-
- - + + + +
+ + +
+ +
+ +
+

Thermocouple #:

+

Heater #:

+
+
+
+ +
+ +
+ +

- - +
+ + +
+ + +
+ +
+ +
+

Thermocouple #:

+

Heater #:

+
+
+ +
+ +
+ +

- -
- - -

Thermocouple #:

-

Heater #:

+
+ + +
+ + +
+ +
+ +
+

Thermocouple #:

+

Heater #:

+
-
- -
- - - + +
+ + + -



-
+



+
-
-

Bioreactor

-
-
-
-
-
- - - +
+ +
+
+
+
+
+ + + +
-
- -
-
-
-
- - - -
-
-
- - -
- - + +
+
+
+ + + + +
- - +
+ + +
+ + +
+ + +
+ +
+
+
+
+
+ + + +

- - +
+ + +
+ + +
+ + +
+ +
+
+
+
+ + + +
-
-
+
+
+
+
+
+ + + +
+
+
- - - +
+ + +
+
-
- - -
- - -
- - -
- +
+ + +
+
+
+
+ + + +
-
-
- - - -
-
-
-
-
-
-
- - - -
-
-
-
-
- - -
- -
-
-
- - -
- -
-
-
- - - -
-
-
-
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
-
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
- -

Post Processing

-
-
-
+
+
-
- +
+
-
-
- - -
- - -
- - -
- -
-
-
-
+
- - +
+
-
- - -
- - -
- - -
- +
+ +
+
-
-
-
- - - -
-
-
- -
-

Chemical Deconstruction Reactor

-
-
-
+
- - +
+
-
- - -
- - -
- - - +
+ +
+
-
-
+
+
- - +
+
-
- - -
- - -
- - - -
-
-
-
-
+
- - +
+
-
-
-
+
- - +
+
-
-
-
+
- - +
+
-
-
-
+
- - +
+
+ +

Post Processing

+
+
+
+
+ +
+ +
+
+
+ + +
+ + +
+ + +
+ +
+
+
+
+
+ + + +
+
+
+ + + + +
+
+
+ + +
+ + +
+ + +
+ +
+
+
+
+ + + +
+
-
-
- - - + +
+ +
+
+
+
+ + + +
+
+
+ + +
+ + +
+ + + +
+
+
+
+
+ + + +
+
+
+ + +
+ + +
+ + + +
+
+
+
+
+ + + +
+
+
+
+
+ + + +
+
+
+
+
+ + + +
+
+
+
+
+ + + +
+
+
+
+
+ + + +
-
- - + + + function clearChemreactor() { + sendPOST('delete-chemreactor', 'delete-chemreactor') + for(let x = 0; x < 2; x++) { + chem_thermo_traces[x].x = [] + chem_thermo_traces[x].y = [] + } + Plotly.newPlot('chem-thermo-graph', chem_thermo_traces, chem_thermo_layout, {scrollZoom: true}); + } - + + + \ No newline at end of file diff --git a/Website Code on SD Card/plotly.js b/Website Code on SD Card/js/plotly.js similarity index 100% rename from Website Code on SD Card/plotly.js rename to Website Code on SD Card/js/plotly.js diff --git a/Website Code on SD Card/js/tabbing.js b/Website Code on SD Card/js/tabbing.js new file mode 100644 index 0000000..3bd9126 --- /dev/null +++ b/Website Code on SD Card/js/tabbing.js @@ -0,0 +1,20 @@ +function openPage(pageName, elmnt, color) { + // Hide all elements with class="tabcontent" by default */ + var i, tabcontent, tablinks; + tabcontent = document.getElementsByClassName("tabcontent"); + for (i = 0; i < tabcontent.length; i++) { + tabcontent[i].style.display = "none"; + } + + // Remove the background color of all tablinks/buttons + tablinks = document.getElementsByClassName("tablink"); + for (i = 0; i < tablinks.length; i++) { + tablinks[i].style.backgroundColor = ""; + } + + // Show the specific tab content + document.getElementById(pageName).style.display = "grid"; + + // Add the specific color to the button used to open the tab content + elmnt.style.backgroundColor = color; +} \ No newline at end of file diff --git a/platformio.ini b/platformio.ini new file mode 100644 index 0000000..0f4b0ed --- /dev/null +++ b/platformio.ini @@ -0,0 +1,26 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[env] +board = esp32doit-devkit-v1 +monitor_speed = 115200 + +[env:sparkfun_esp32s2_thing_plus_c] +platform = espressif32 +board = sparkfun_esp32s2_thing_plus_c +framework = arduino +; upload_speed = 921600 + + +lib_deps = + esphome/AsyncTCP-esphome@^2.1.4 + bblanchon/ArduinoJson@^7.2.0 + fbiego/ESP32Time@^2.0.6 + esphome/ESPAsyncWebServer-esphome@^3.2.2 \ No newline at end of file diff --git a/Arduino Code/BREAD-DARPA-Controls/BREAD-DARPA-Controls.ino b/src/BREAD-DARPA-Controls.ino similarity index 86% rename from Arduino Code/BREAD-DARPA-Controls/BREAD-DARPA-Controls.ino rename to src/BREAD-DARPA-Controls.ino index 5f94204..80a4fd1 100644 --- a/Arduino Code/BREAD-DARPA-Controls/BREAD-DARPA-Controls.ino +++ b/src/BREAD-DARPA-Controls.ino @@ -131,6 +131,14 @@ int bio_post_heaters[2][4] = { // bioreactor post processing heaters {address float bio_post_heater_pid[2][4]; // {setpoint, Kp, Ki, Kd} float bio_thermo_val[4] = {0,0,0,0}; +// Automatic Heater PID Vars +uint8_t autoCheck = 0; +uint8_t checkOsc = 0; +uint8_t peak = 0; +hw_timer_t *timer = NULL; +float bio_post_heater_auto[5] = {0,0,0,0,0}; // {maxTemp, prevTemp, prevTime, Peak1, prevDeriv} + + int bio_ph[2][2] = { // bioreactor pH layout {PHDO address, DCMT address} {98, 20}, {100, 21} @@ -404,6 +412,14 @@ Chem Decon Commands: }else if(postName.charAt(0) == 'P') { // bioreactor post processing uint8_t index = postName.substring(2).toInt() - 1; switch(postName.charAt(1)) { + //new case for autotune 3-6-2024 + case 'a': + autoCheck = 1; + //Send initial Kp value + bio_post_heater_pid[index][1] = postValue.toFloat(); + timer = timerBegin(0, 80, true); + RLHTCommandPID(bio_post_heaters[index][0], bio_post_heaters[index][1], bio_post_heater_pid[index][1], 0, 0); + break; case 's': //setpoint bio_post_heater_pid[index][0] = postValue.toFloat(); RLHTCommandSetpoint(bio_post_heaters[index][0], bio_post_heaters[index][1], bio_post_heater_pid[index][0], bio_post_heaters[index][2], bio_post_heaters[index][3]); @@ -548,8 +564,8 @@ Chem Decon Commands: client->send("hello!", NULL, millis(), 10000); }); server.addHandler(&events); - - server.serveStatic("/", SD, "/"); + server.serveStatic("/js", SD, "/js").setCacheControl("max-age=31536000"); + server.serveStatic("/", SD, "/").setCacheControl("max-age=31536000"); server.begin(); lastPOST = millis(); @@ -598,6 +614,22 @@ void loop() { } for(int i=0;i<2;i++){ float temp; + //if thermocouple 1? + if(bio_post_heaters[i][2] == 1) { + RLHTRequestThermo(bio_post_heaters[i][0], &(bio_thermo_val[i+2]), &(temp)); + + } + if(bio_post_heaters[i][2] == 2) { + RLHTRequestThermo(bio_post_heaters[i][0], &(temp), &(bio_thermo_val[i+2])); + //(int address, byte heater, float Ku,float setpoint, float temp, float time) + + RLHTCommandPIDAuto(bio_post_heaters[i][0], bio_post_heaters[i][1],bio_post_heater_pid[i][1],bio_post_heater_pid[i][0],bio_thermo_val[4],timerReadSeconds(timer)); + //if not oscillating... + if (checkOsc == 0) { + timerRestart(timer); + } + //RLHTCommandSetpoint(bio_post_heaters[index][0], bio_post_heaters[index][1], bio_post_heater_pid[index][0], bio_post_heaters[index][2], bio_post_heaters[index][3]); + } if(bio_post_heaters[i][2] == 1) RLHTRequestThermo(bio_post_heaters[i][0], &(bio_thermo_val[i+2]), &(temp)); if(bio_post_heaters[i][2] == 2) @@ -714,6 +746,77 @@ void RLHTCommandSetpoint(int address, byte heater, float heatSetpoint, byte ther Serial.println(enableReverse); } + + /* + * Automatically tunes heater gains (Ki, Kd, Kp) + * + * int address - address of heater slice + * byte heater - + * float Ku - ultimate gain + * float setpoint + */ +void RLHTCommandPIDAuto(int address, byte heater, float Ku,float setpoint, float temp, double time) +{ + + +//we could just increase the gain super slowly + if (autoCheck == 1) + { + //((current T - previous T) / (current time - previous timee)) + double driv = (temp - bio_post_heater_auto[1])/(time - bio_post_heater_auto[2]); + //if T is less than 2% of Setpoint and the derivative is decreasing and it isn't oscillating... + if ((temp < setpoint - (setpoint * 0.02)) && driv - bio_post_heater_auto[4] < 0 && checkOsc == 0) + { + //double Ku and send new gains + Ku = Ku * 2; + RLHTCommandPID(address, heater, Ku, 0, 0); + + } + else + { + //check for oscillations + checkOsc = 1; + //If MaxTemp is less than current temp, set new peak 1 max and continue + if(bio_post_heater_auto[0] < temp) + { + bio_post_heater_auto[0] = temp; + } + //If previous T is smaller than current, set new maximum T + else if(bio_post_heater_auto[1] < temp){ + //increasing to peak 2 + bio_post_heater_auto[0] = temp; + } + //If max observed T is larger than new and we have not found the first peak, set it as the first peak + if(bio_post_heater_auto[0] > temp && peak == 0){ + bio_post_heater_auto[3] = bio_post_heater_auto[0]; + peak = 1; + timerRestart(timer); + } + //If max observed T is larger than new and we have found the first peak... + else if (bio_post_heater_auto[0] > temp && peak == 1){ + //Find the delta between the secondpeak-firstpeak + float delp = bio_post_heater_auto[0]-bio_post_heater_auto[3]; + //If the delta is greater than 0, change Ku + if(delp > 0){ + Ku = (Ku / 1.5); //or Ku - (Ku/2) + } + else{ + float time = timerReadSeconds(timer); + RLHTCommandPID(address, heater, 0.6*Ku, (1.2*Ku)/time, 0.075*Ku*time); + checkOsc = 0; + autoCheck = 0; + timerStop(timer); + } + } + } + //saving old time, temp, and dericative + bio_post_heater_auto[1] = temp; + bio_post_heater_auto[2] = time; + bio_post_heater_auto[4] = driv; + } +} + + void RLHTCommandPID(int address, byte heater, float Kp_set, float Ki_set, float Kd_set) { FLOATUNION_t Kp; @@ -746,6 +849,25 @@ void RLHTCommandPID(int address, byte heater, float Kp_set, float Ki_set, float Serial.println(Kd.number); } +void RLHTCommandOffset(int address, byte heater, float offsetSetpoint) +{ + FLOATUNION_t offset; + offset.number = offsetSetpoint; + + Wire.beginTransmission(address); + Wire.write('O'); + Wire.write(heater); + for(int i=0; i<4; i++){ + Wire.write(offset.bytes[i]); // sends one byte + } + Wire.endTransmission(); // stop transmitting + + Serial.print(address); + Serial.print('O'); + Serial.print(heater); + Serial.print(offset.number); +} + void DCMTRequestTurbidity(int address, float* turbidity1, float* turbidity2) { bool data_received = false; @@ -820,6 +942,7 @@ void DCMTCommandPH(int address, float pHSetpoint_set, float currentPH_set) void DCMTCommandPHPID(int address, float Kp_set, float Ki_set, float Kd_set) { + //autotune code here FLOATUNION_t Kp, Ki, Kd; Kp.number = Kp_set; Ki.number = Ki_set; @@ -888,4 +1011,4 @@ float PHDORequest(int address) return atof(in_data); }else return 0; -} \ No newline at end of file +}