aboutsummaryrefslogblamecommitdiff
path: root/hydroponics_broker.ino
blob: a2e0b4393a26211c139d3e3ad8a30e50ba0d3fe3 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12











                                    
                                                                    


                                  
                             



















































                                                  
                                                                                                                                


                                                                                                                             
                                                                                






























































                                                                                                                                                       
                                                           


                                                                          
                                    

















































                                                                                                                

                                                                        






























































































                                                                                                                                                                                     






















                                                                  
#include <OneWire.h>
#include <DallasTemperature.h>
#include <WiFi.h>
#include <PubSubClient.h>
#include <WebServer.h>
#include <Update.h>
#include "secrets.h"

#define WIFI_CHECK_INTERVAL    30000
#define SENSOR_SAMPLE_INTERVAL 5000
#define MQTT_PUSH_INTERVAL     60000

#define SAMPLE_HISTORY_N       20     // 60*5s => 5m running average
#define PIN_ONEWIRE            4
#define PIN_TDS                34
#define VREF                   3.3
float ec_calibration = 0.98f;

#define CTEMP temperatures[sensors_current_i]
#define CTDS  tds[sensors_current_i]
#define CEC   ec[sensors_current_i]

const char *ssid = WIFI_SSID;
const char *password = WIFI_PASSWORD;

const char *mqtt_broker = MQTT_BROKER;
const char *topic_temp = "emqx/nft_system_temp";
const char *topic_tds = "emqx/nft_system_tds";
const char *topic_ec = "emqx/nft_system_ec";
const char *mqtt_username = MQTT_USERNAME;
const char *mqtt_password = MQTT_PASSWORD;
const int mqtt_port = 1883;

WiFiClient espClient;
PubSubClient client(espClient);

OneWire oneWire(PIN_ONEWIRE);
DallasTemperature sensors(&oneWire);

int sensors_current_i = 0;
bool sensors_initialized = false;

float temperatures[SAMPLE_HISTORY_N];
float temperatures_total = 0.0f;
float temperatures_average = 0.0f;
bool temperature_mqtt_last_publish_status = false;

float tds[SAMPLE_HISTORY_N];
float tds_total = 0.0f;
float tds_average = 0.0f;
bool tds_mqtt_last_publish_status = false;

float ec[SAMPLE_HISTORY_N];
float ec_total = 0.0f;
float ec_average = 0.0f;
bool ec_mqtt_last_publish_status = false;

unsigned long lastWifiCheck = 0;
unsigned long lastSensorSample = 0;
unsigned long lastMqttPublish = 0;

WebServer server(80);
const char *serverIndex PROGMEM =
  "<!DOCTYPE html>"
  "<html>"
    "<head><style type='text/css'>"
    "div {display:flex;gap:1em;}"
    "</style></head>" 
    "<body>"
      "<div><strong>Sensors</strong><span>Initialized: <em>%d</em></span><span>Last sample: <em>%.1fs</em> ago</span></div><hr>"
      "<div><strong>Temperature</strong><span><em>%.1fC</em> ~ <em>%.1fC</em></span><span>MQTT: <em>%d</em></span></div><hr>"
      "<div><strong>TDS</strong><span><em>%.0f</em> ~ <em>%.0f</em></span><span>MQTT: <em>%d</em></span></div><hr>"
      "<div><strong>EC</strong><span><em>%.1f</em> ~ <em>%.1f</em></span><span>MQTT: <em>%d</em></span></div><hr>"
      "<div><strong>MQTT</strong><span>Last <em>%.1fs</em> ago</span></div><hr>"
      "<form method='POST' action='/update' enctype='multipart/form-data'><input type='file' name='update'><input type='submit' value='Update'></form>"
    "</body>"
  "</html>";

void setup_wifi() {
  delay(3);
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);

  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  randomSeed(micros());

  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
}

void reconnect() {
  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");
    String client_id = "esp32-client-";
    client_id += String(WiFi.macAddress());
    if (client.connect(client_id.c_str(), mqtt_username, mqtt_password)) {
      Serial.println("connected");
    } else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      delay(5000);
    }
  }
}

void initialize_sensors() {
  sensors.begin();
  pinMode(PIN_TDS, INPUT);
  for (int i = 0; i < SAMPLE_HISTORY_N; i++) {
    temperatures[i] = 0.0f;
  }
}

void setup() {
    Serial.begin(115200);
    Serial.println("init");
    initialize_sensors();
    setup_wifi();
    client.setServer(mqtt_broker, mqtt_port);

    server.on("/", HTTP_GET, []() {
      unsigned long now = millis();
      server.sendHeader("Connection", "close");
      char buffer[strlen(serverIndex)+64];
      sprintf(
        buffer, serverIndex,
        sensors_initialized, (now - lastSensorSample)/1000,
        CTEMP, temperatures_average, temperature_mqtt_last_publish_status,
        CTDS, tds_average, tds_mqtt_last_publish_status,
        CEC, ec_average, ec_mqtt_last_publish_status,
        (now - lastMqttPublish)/1000
      );
      server.send(200, "text/html", buffer);
    });
    server.on(
      "/update", HTTP_POST,
      []() {
        server.sendHeader("Connection", "close");
        server.send(200, "text/plain", (Update.hasError()) ? "FAIL" : "OK");
        ESP.restart();
      },
      []() {
        HTTPUpload &upload = server.upload();
        if (upload.status == UPLOAD_FILE_START) {
          Serial.setDebugOutput(true);
          Serial.printf("Update: %s\n", upload.filename.c_str());
          if (!Update.begin()) {  //start with max available size
            Update.printError(Serial);
          }
        } else if (upload.status == UPLOAD_FILE_WRITE) {
          if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) {
            Update.printError(Serial);
          }
        } else if (upload.status == UPLOAD_FILE_END) {
          if (Update.end(true)) {  //true to set the size to the current progress
            Serial.printf("Update Success: %u\nRebooting...\n", upload.totalSize);
          } else {
            Update.printError(Serial);
          }
          Serial.setDebugOutput(false);
        } else {
          Serial.printf(F("Update Failed Unexpectedly (likely broken connection): status=%d\n"), upload.status);
        }
      }
    );
    server.begin();
}

void updateSensorValues() {
    sensors.requestTemperatures();

    sensors_current_i++;
    if (sensors_current_i >= SAMPLE_HISTORY_N) {
      sensors_current_i = 0;
      sensors_initialized = true;
    }
    
    // Temperature 1
    temperatures_total -= CTEMP;
    CTEMP = sensors.getTempCByIndex(0);
    temperatures_total += CTEMP;
    //temperatures_average = temperatures_total / SAMPLE_HISTORY_N;
    temperatures_average = getMedianNum(temperatures, SAMPLE_HISTORY_N);

    // TDS 1/EC 1
    ec_total -= CEC;
    tds_total -= CTDS;
    float rawEc = analogRead(PIN_TDS) * VREF / 4096.0; // read the analog value more stable by the median filtering algorithm, and convert to voltage value
    float temperatureCoefficient = 1.0 + 0.02 * (temperatures_average - 25.0); // temperature compensation formula: fFinalResult(25^C) = fFinalResult(current)/(1.0+0.02*(fTP-25.0));
    CEC = (rawEc / temperatureCoefficient) * ec_calibration; // temperature and calibration compensation
    ec_total += CEC;
    ec_average = ec_total / SAMPLE_HISTORY_N;
    CTDS = (133.42 * pow(CEC, 3) - 255.86 * CEC * CEC + 857.39 * CEC) * 0.5; //convert voltage value to tds value
    tds_total += CTDS;
    tds_average = tds_total / SAMPLE_HISTORY_N;
    
    Serial.print(F("TDS:")); Serial.println(CTDS);
    Serial.print(F("EC:")); Serial.println(CEC, 2);

    Serial.print(F("   "));
    Serial.print(CTEMP);
    Serial.print(F(" C"));
    Serial.print(F("~"));
    Serial.print(temperatures_average);
    Serial.println(F(" C"));

}

void loop() {
    unsigned long now = millis();

    if ((WiFi.status() != WL_CONNECTED) && (now - lastWifiCheck > WIFI_CHECK_INTERVAL)) {
      Serial.println("Reconnecting to WiFi...");
      WiFi.disconnect();
      WiFi.reconnect();
      lastWifiCheck = now;
    }
    server.handleClient();
    if (!client.connected()) {
      reconnect();
    }
    client.loop();
    
    now = millis();

    if (now - lastSensorSample > SENSOR_SAMPLE_INTERVAL) {
      lastSensorSample = now;
      updateSensorValues();
    }
    
    if (now - lastMqttPublish > MQTT_PUSH_INTERVAL) {
        lastMqttPublish = now;

        if (sensors_initialized) {
          // Publish temperature
          char result[8]; // Buffer big enough for 7-character float
          dtostrf(temperatures_average, 6, 1, result); // Leave room for too large numbers!
          temperature_mqtt_last_publish_status = client.publish(topic_temp, result);
          Serial.println("Published temperature update");
          // Publish tds
          result[8]; // Buffer big enough for 7-character float
          dtostrf(tds_average, 6, 0, result); // Leave room for too large numbers!
          tds_mqtt_last_publish_status = client.publish(topic_tds, result);
          Serial.println("Published tds update");
          // Publish ec
          result[8]; // Buffer big enough for 7-character float
          dtostrf(ec_average, 6, 1, result); // Leave room for too large numbers!
          ec_mqtt_last_publish_status = client.publish(topic_ec, result);
          Serial.println("Published ec update");
        }
    } 
}


// median filtering algorithm
// https://randomnerdtutorials.com/arduino-tds-water-quality-sensor/
int getMedianNum(int bArray[], int iFilterLen){
  int bTab[iFilterLen];
  for (byte i = 0; i<iFilterLen; i++)
  bTab[i] = bArray[i];
  int i, j, bTemp;
  for (j = 0; j < iFilterLen - 1; j++) {
    for (i = 0; i < iFilterLen - j - 1; i++) {
      if (bTab[i] > bTab[i + 1]) {
        bTemp = bTab[i];
        bTab[i] = bTab[i + 1];
        bTab[i + 1] = bTemp;
      }
    }
  }
  if ((iFilterLen & 1) > 0){
    bTemp = bTab[(iFilterLen - 1) / 2];
  }
  else {
    bTemp = (bTab[iFilterLen / 2] + bTab[iFilterLen / 2 - 1]) / 2;
  }
  return bTemp;
}

float fgetMedianNum(float bArray[], int iFilterLen){
  float bTab[iFilterLen];
  for (byte i = 0; i<iFilterLen; i++)
  bTab[i] = bArray[i];
  int i, j, bTemp;
  for (j = 0; j < iFilterLen - 1; j++) {
    for (i = 0; i < iFilterLen - j - 1; i++) {
      if (bTab[i] > bTab[i + 1]) {
        bTemp = bTab[i];
        bTab[i] = bTab[i + 1];
        bTab[i + 1] = bTemp;
      }
    }
  }
  if ((iFilterLen & 1) > 0){
    bTemp = bTab[(iFilterLen - 1) / 2];
  }
  else {
    bTemp = (bTab[iFilterLen / 2] + bTab[iFilterLen / 2 - 1]) / 2;
  }
  return bTemp;
}