aboutsummaryrefslogtreecommitdiff
path: root/hydroponics_broker.ino
diff options
context:
space:
mode:
authorQrius <[email protected]>2024-09-26 07:53:32 +0200
committerQrius <[email protected]>2024-09-26 07:53:32 +0200
commitd9506755c6029a2a1f32bd5969f64d42dff980e6 (patch)
tree1c79d6527a7a0dc182d25a7a3b7c45b6c552ed6f /hydroponics_broker.ino
downloadhydroponics_broker-d9506755c6029a2a1f32bd5969f64d42dff980e6.tar.gz
hydroponics_broker-d9506755c6029a2a1f32bd5969f64d42dff980e6.zip
Initial commit
Diffstat (limited to 'hydroponics_broker.ino')
-rw-r--r--hydroponics_broker.ino288
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;
+}