diff options
author | Qrius <[email protected]> | 2024-09-26 07:53:32 +0200 |
---|---|---|
committer | Qrius <[email protected]> | 2024-09-26 07:53:32 +0200 |
commit | d9506755c6029a2a1f32bd5969f64d42dff980e6 (patch) | |
tree | 1c79d6527a7a0dc182d25a7a3b7c45b6c552ed6f /hydroponics_broker.ino | |
download | hydroponics_broker-d9506755c6029a2a1f32bd5969f64d42dff980e6.tar.gz hydroponics_broker-d9506755c6029a2a1f32bd5969f64d42dff980e6.zip |
Initial commit
Diffstat (limited to 'hydroponics_broker.ino')
-rw-r--r-- | hydroponics_broker.ino | 288 |
1 files changed, 288 insertions, 0 deletions
diff --git a/hydroponics_broker.ino b/hydroponics_broker.ino new file mode 100644 index 0000000..a22bd42 --- /dev/null +++ b/hydroponics_broker.ino @@ -0,0 +1,288 @@ +#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 60 // 60*5s => 5m running average +#define PIN_ONEWIRE 4 +#define PIN_TDS 34 +#define VREF 3.3 +float ec_calibration = 1.0f; + +#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>%dms</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>%dms</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, + 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 + ); + 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; + + // 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; +} |