humi.ino 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  1. // Device ID (grafana label)
  2. #define DEV_ID "humi"
  3. // ESP32 with Wifi
  4. #define DEVICE "ESP32"
  5. #include <WiFiMulti.h>
  6. WiFiMulti wifiMulti;
  7. #define WIFI_SSID "cazicthule2"
  8. #define WIFI_PASSWORD "kalamazoomi"
  9. // InfluxDB for grafana
  10. #include <InfluxDbClient.h>
  11. #define INFLUXDB_URL "http://192.168.1.6:8086"
  12. #define INFLUXDB_DB_NAME "ifdb"
  13. InfluxDBClient client(INFLUXDB_URL, INFLUXDB_DB_NAME);
  14. Point sensor(DEV_ID);
  15. // BME280 - TPH Temperature Pressure Humidity
  16. #include <Wire.h>
  17. #include <Adafruit_Sensor.h>
  18. #include <Adafruit_BME280.h>
  19. Adafruit_BME280 bme;
  20. const int HUMID_PIN_A = 16; // Mosfet A for ultrasonic himidifier
  21. const int HUMID_PIN_B = 23; // Mosfet B (unused for now)
  22. const int PID_PIN_KP = 33; //orange
  23. const int PID_PIN_KI = 32; //brown
  24. const int PID_PIN_KD = 34; //green\
  25. int potKp;
  26. int potKi;
  27. int potKd;
  28. const float Kp_Range = 2;
  29. const float Ki_Range = 0.2;
  30. const float Kd_Range = 2;
  31. // TODO add buttons to control these targets
  32. int VPD_mode = 1; // VPD -> 1; RH -> 0
  33. float VPD_target = 0.9; // Vapor Pressure Deficit target
  34. float RH_target = 65; // Relative Humidity target
  35. float setpoint;
  36. float temp_meas=18;
  37. float humid_meas=50;
  38. // used to revert outlandish measurements
  39. float humid_sane=humid_meas;
  40. float temp_sane=temp_meas;
  41. float Psat(float T){
  42. // Calculate the Saturation Vapor Pressure using Arden Buck equation
  43. // Psat = 0.61121 exp( (18.678 - T/234.5) * (T/(257.14+T)) )
  44. return 0.61121 * pow(2.7182818284, (18.678 - T/234.5) * (T/(257.14+T)));
  45. }
  46. float VPD(float T, float RH){
  47. // Calculate the Vapor Pressure Deficit in kPa
  48. return Psat(T) * (1-RH/100.);
  49. }
  50. float RH(float VPDtarget, float T){
  51. // Compute the required %RH to hit VPD_target
  52. return 100. * ( 1 - VPDtarget/Psat(T) );
  53. }
  54. double readPot(int pin){
  55. double pot_val;
  56. double mag = 0.5;
  57. pot_val = (double)analogRead(pin);
  58. if(pot_val<1000) {
  59. mag=0.0;
  60. }
  61. else{
  62. if (pot_val > 3000){
  63. mag=1.0;
  64. }
  65. else{
  66. mag = (pot_val-1000.)/(3000.-1000.);
  67. }
  68. }
  69. return(mag);
  70. }
  71. // PID Controller
  72. #include <PID_v1.h>
  73. //Declare PID Variables
  74. double PID_Setpoint, PID_Input, PID_Output;
  75. unsigned long PID_now;
  76. double PID_Kp=0.05, PID_Ki=5, PID_Kd=0;
  77. //Specify the links and initial tuning parameters
  78. PID myPID(&PID_Input, &PID_Output, &PID_Setpoint, PID_Kp, PID_Ki, PID_Kd, P_ON_M, DIRECT);
  79. // PID timer for interval between turning device on/off 10000=10seconds
  80. int PID_WindowSize = 10000;
  81. int PID_RESTART(){
  82. float out=PID_Output;
  83. myPID.SetMode(AUTOMATIC);
  84. myPID.SetOutputLimits(0.0, 1.0); // Forces minimum up to 0.0
  85. myPID.SetOutputLimits(-1.0, 0.0); // Forces maximum down to 0.0
  86. myPID.SetOutputLimits(1, PID_WindowSize-1); // reset the output range
  87. if (0<out && out<PID_WindowSize){
  88. PID_Output=out;
  89. return(1);
  90. }
  91. else{
  92. PID_Output=0.25*PID_WindowSize;
  93. return(0);
  94. }
  95. }
  96. void setup() {
  97. pinMode(HUMID_PIN_A, OUTPUT);
  98. pinMode(HUMID_PIN_B, OUTPUT);
  99. pinMode(PID_PIN_KP, INPUT);
  100. pinMode(PID_PIN_KI, INPUT);
  101. pinMode(PID_PIN_KD, INPUT);
  102. Serial.begin(115200);
  103. // BME setup
  104. if (!bme.begin(0x76)) {
  105. Serial.println("Could not find a valid BME280 sensor, check wiring!");
  106. while (1);
  107. }
  108. // PID setup
  109. //initialize the variables we're linked to (Input and Setpoint)
  110. PID_Setpoint = RH(VPD_target,bme.readTemperature());
  111. //tell the PID to range between 0 and the full window size
  112. myPID.SetOutputLimits(1, PID_WindowSize-1);
  113. //turn the PID on
  114. myPID.SetMode(AUTOMATIC);
  115. PID_Output=0.25*(PID_WindowSize);
  116. // WiFi setup
  117. Serial.println("Connecting to WiFi");
  118. WiFi.mode(WIFI_STA);
  119. wifiMulti.addAP(WIFI_SSID, WIFI_PASSWORD);
  120. while (wifiMulti.run() != WL_CONNECTED) {
  121. Serial.print(".");
  122. delay(500);
  123. }
  124. Serial.println();
  125. // Check server connection
  126. if (client.validateConnection()) {
  127. Serial.print("Connected to InfluxDB: ");
  128. Serial.println(client.getServerUrl());
  129. } else {
  130. Serial.print("InfluxDB connection failed: ");
  131. Serial.println(client.getLastErrorMessage());
  132. }
  133. }
  134. void loop() {
  135. // If measured RH is too low
  136. PID_now = millis();
  137. // This loop runs every 10seconds (windowsize)
  138. //loop for device on time (deltat < windowsize) 10000 = 10seconds between power cycles
  139. while ( millis() - PID_now < PID_WindowSize ){
  140. // Measure Temperature and Humidity (TODO do some error checking)
  141. temp_meas = bme.readTemperature(); // Celcius
  142. humid_meas = bme.readHumidity(); // Relative Humidity (%)
  143. // Calculate the setpoint RH from the target VPD and current temperature
  144. setpoint=(RH(VPD_target,temp_meas));
  145. if (humid_meas<0.9*setpoint){
  146. //reset PID (only in automatic mode)
  147. //myPID.SetOutputLimits(0.0, 1.0); // Forces minimum up to 0.0
  148. //myPID.SetOutputLimits(-1.0, 0.0); // Forces maximum down to 0.0
  149. //myPID.SetOutputLimits(1, PID_WindowSize-1);
  150. // turn humi full on
  151. PID_Output=PID_Output+PID_WindowSize*0.01;
  152. myPID.SetMode(MANUAL);
  153. digitalWrite(HUMID_PIN_A, HIGH);
  154. // wait windowsize time to try again
  155. delay(PID_WindowSize);
  156. }
  157. else if (humid_meas>1.1*setpoint) {
  158. //reset PID (only in automatic mode)
  159. //myPID.SetOutputLimits(0.0, 1.0); // Forces minimum up to 0.0
  160. //myPID.SetOutputLimits(-1.0, 0.0); // Forces maximum down to 0.0
  161. //myPID.SetOutputLimits(1, PID_WindowSize-1);
  162. // turn humi full off
  163. PID_Output=abs(PID_Output-PID_WindowSize*0.01);
  164. myPID.SetMode(MANUAL);
  165. digitalWrite(HUMID_PIN_A, LOW);
  166. // wait windowsize time to try again
  167. delay(PID_WindowSize);
  168. }
  169. else{
  170. // PID control
  171. myPID.SetMode(AUTOMATIC);
  172. //PID input: the current RH value
  173. PID_Input = humid_meas;
  174. //PID Setpoint: calculate the required RH to hit target VPD
  175. PID_Setpoint = setpoint;
  176. myPID.Compute();
  177. //Serial.println(PID_WindowSize);
  178. //Serial.println(PID_Output); // duty cycle is PID_Output divided by PID_WindowSize
  179. //Serial.println(PID_Setpoint);
  180. //Serial.println(PID_Input);
  181. // trigger off for cycle
  182. if ( millis() - PID_now > PID_Output ){
  183. digitalWrite(HUMID_PIN_A, LOW);
  184. }
  185. //trigger on for cycle
  186. else{
  187. digitalWrite(HUMID_PIN_A, HIGH);
  188. }
  189. delay(200);
  190. }
  191. }
  192. //read PID from pots
  193. PID_Kp= readPot(PID_PIN_KP)*Kp_Range+1.0;
  194. PID_Ki= readPot(PID_PIN_KI)*Ki_Range;
  195. PID_Kd= 2.0;
  196. //print PID
  197. Serial.print(" KP:");
  198. Serial.print(PID_Kp);
  199. Serial.print(" KI:");
  200. Serial.print(PID_Ki);
  201. Serial.print(" KD:");
  202. Serial.println(PID_Kd);
  203. // update PID
  204. myPID.SetTunings(PID_Kp, PID_Ki, PID_Kd);
  205. // Write values and triggers into influxdb
  206. sensor.clearFields();
  207. sensor.addField("VPD",VPD(temp_meas,humid_meas));
  208. sensor.addField("VPDtarget",VPD_target);
  209. sensor.addField("Temperature",(temp_meas*9/5.) + 32.);
  210. sensor.addField("Humidity",humid_meas);
  211. sensor.addField("Humidity_target",RH(VPD_target,temp_meas));
  212. sensor.addField("Duty",PID_Output/(float)PID_WindowSize);
  213. sensor.addField("PID_Output",PID_Output);
  214. sensor.addField("PID_Kp",PID_Kp);
  215. sensor.addField("PID_Ki",PID_Ki);
  216. sensor.addField("PID_Kd",PID_Kd);
  217. // Send fields to InfluxDB
  218. Serial.print("Writing: ");
  219. Serial.println(client.pointToLineProtocol(sensor));
  220. // If no Wifi signal, try to reconnect it
  221. if (wifiMulti.run() != WL_CONNECTED) {
  222. Serial.println("Wifi connection lost");
  223. }
  224. // Write point
  225. if (!client.writePoint(sensor)) {
  226. Serial.print("InfluxDB write failed: ");
  227. Serial.println(client.getLastErrorMessage());
  228. }
  229. // modify window size to tune mister to 30% load
  230. // need to collect the "average load" over a long period
  231. // if mean(load) < 0.25 and variance is low ; PID_WindowSize increases by 10%
  232. // if mean(load) > 0.60 and variance is low ; PID_WindowSize decreases by 10%
  233. }