#include "credentials.h"
#include "IOTTimer.h"
#include "neopixel.h"
#include "par_sensor.h"
#include <Adafruit_MQTT.h>
#include "Adafruit_MQTT/Adafruit_MQTT.h"
#include "Adafruit_MQTT/Adafruit_MQTT_SPARK.h"
SYSTEM_MODE(AUTOMATIC);
SYSTEM_THREAD(ENABLED);
// A0 For future O2 sensor
#define PH_PIN A2 // pin for pH meter
#define EC_PIN A1 // pin for EC
#define WaterLevel A3
#define FlowMeter A4
#define PIXEL_PIN A5
#define WATER_LEVEL_HOT_PIN D10
#define Relay_8_Pin D9 // Not currently used
#define PhUpPumpPin D8
#define PhDownPumpPin D7
#define nutrientPump1 D6
#define nutrientPump2 D5
#define TopDrain D4
#define BottomDrain D3
#define FreshWaterPump D2
// D0 and D1 are for I2C
#define PAR_ADDR 0x39
#define PIXEL_COUNT 63
#define PIXEL_TYPE WS2812B
#define SERIESRESISTOR 560
#define Offset 40.349605 // deviation compensate for pH meter
#define samplingInterval 20
#define printInterval 800
#define ArrayLenth 40 // times of collection
const float K_pHCalc = -2.994219;
const float HIGH_PH_LIMIT = 5.5f;
const float LOW_PH_LIMIT = 4.0f;
const float LOW_WATER_LIMIT = 1925.0f; // This is about 1.5 inches of water
const float EMPTY_MIX_TANK = 1880.0f;
const float HIGH_WATER_LIMIT = 2600.0f;
const float HIGH_WATER_INCHES = 8.5f;
const float ML_PER_COUNT = 2.25;
static float waterLevelArr[60];
static float pHValue, voltage, EC;
static float mixTankLevel;
float waterFlowVolume;
float waterLevelAverage = 0;
float waterLevelInches;
const double MLR_B_0 = -4.8853; // This should be -9.8853 to be accurate in sunlight, however inside that is too much correction
const double MLR_B_1 = 0.0046;
const double MLR_B_2 = 0.0136;
const double MLR_B_3 = 0.0243;
const double MLR_B_4 = 0.0459;
const double MLR_B_5 = -0.0471;
const double MLR_B_6 = 0.0195;
const double MLR_B_7 = 0.0178;
const double MLR_B_8 = -0.0026;
static double mlrProduct;
static unsigned long samplingTime, printTime, last, lastTime;
const int EC_PUMP_RUNTIME = 3000;
const int PH_PUMP_RUNTIME = 3000;
const int DRAIN_RUNTIME = 5000;
const int FRESH_PUMP_RUNTIME = 15000;
int pHArrayIndex = 0;
int blue;
int green;
int red;
int channelIndex = 0;
int waterFlowCount;
int waterArrIndex = 0;
int all10Channels[10];
int pHArray[ArrayLenth]; // Store the average value of the pH sensor feedback
IOTTimer connectTimer;
IOTTimer flowTimer;
IOTTimer waterLevelTimer;
IOTTimer nutrientPumpTimer;
IOTTimer delayTimer;
Adafruit_AS7341 as7341;
Adafruit_NeoPixel strip(PIXEL_COUNT, PIXEL_PIN, PIXEL_TYPE);
// MQTT Constructors
TCPClient TheClient;
Adafruit_MQTT_SPARK mqtt(&TheClient, AIO_SERVER, AIO_SERVERPORT, AIO_USERNAME, AIO_KEY);
// Publish
Adafruit_MQTT_Publish mqttPubHydropnicsPARStatus = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/hydro.parstatus");
Adafruit_MQTT_Publish mqttPubHydropnicsWaterLevelStatus = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/hydro.waterlevelstatus");
Adafruit_MQTT_Publish mqttPubHydropnicsWaterLevelPercentStatus = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/hydro.waterlevelpercent");
Adafruit_MQTT_Publish mqttPubHydropnicsFlowStatus = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/hydro.flowstatus");
Adafruit_MQTT_Publish mqttPubHydropnicsPHStatus = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/hydro.phstatus");
Adafruit_MQTT_Publish mqttPubHydropnicsECStatus = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/hydro.ecstatus");
Adafruit_MQTT_Publish mqttPubCh0 = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/hydro.ch0");
Adafruit_MQTT_Publish mqttPubCh1 = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/hydro.ch1");
Adafruit_MQTT_Publish mqttPubCh2 = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/hydro.ch2");
Adafruit_MQTT_Publish mqttPubCh3 = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/hydro.ch3");
Adafruit_MQTT_Publish mqttPubCh4 = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/hydro.ch4");
Adafruit_MQTT_Publish mqttPubCh5 = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/hydro.ch5");
Adafruit_MQTT_Publish mqttPubCh6 = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/hydro.ch6");
Adafruit_MQTT_Publish mqttPubCh7 = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/hydro.ch7");
Adafruit_MQTT_Publish mqttPubCh8 = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/hydro.ch8");
Adafruit_MQTT_Publish mqttPubCh9 = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/hydro.ch9");
Adafruit_MQTT_Subscribe mqttSubNutrientPumps = Adafruit_MQTT_Subscribe(&mqtt, AIO_USERNAME "/feeds/nutrient-pumps");
Adafruit_MQTT_Subscribe mqttSubPhUpPump = Adafruit_MQTT_Subscribe(&mqtt, AIO_USERNAME "/feeds/phup");
Adafruit_MQTT_Subscribe mqttSubPhDownPump = Adafruit_MQTT_Subscribe(&mqtt, AIO_USERNAME "/feeds/phdown");
Adafruit_MQTT_Subscribe mqttSubFreshWaterRefillPump = Adafruit_MQTT_Subscribe(&mqtt, AIO_USERNAME "/feeds/freshwaterrefill");
void setup()
{
Serial.begin(115200);
waitFor(Serial.isConnected, 1000);
delay(500);
Serial.printf("***\n* Booting up the autonomous hydroponics unit! *\n***\n"); // Test the Serial monitor
pinMode(PhUpPumpPin, OUTPUT);
digitalWrite(PhUpPumpPin, HIGH);
pinMode(PhDownPumpPin, OUTPUT);
digitalWrite(PhDownPumpPin, HIGH);
pinMode(nutrientPump1, OUTPUT);
digitalWrite(nutrientPump1, HIGH);
pinMode(nutrientPump2, OUTPUT);
digitalWrite(nutrientPump2, HIGH);
pinMode(TopDrain, OUTPUT);
digitalWrite(TopDrain, HIGH);
pinMode(BottomDrain, OUTPUT);
digitalWrite(BottomDrain, HIGH);
pinMode(FreshWaterPump, OUTPUT);
digitalWrite(FreshWaterPump, HIGH);
pinMode(WATER_LEVEL_HOT_PIN, OUTPUT);
pinMode(WaterLevel, INPUT_PULLDOWN);
pinMode(FlowMeter, INPUT);
strip.begin();
strip.clear();
strip.setBrightness(45);
strip.show();
as7341.begin();
delay(150);
as7341.setATIME(100);
as7341.setASTEP(999);
as7341.setGain(AS7341_GAIN_1X);
delay(150);
as7341.startReading();
delay(150);
readPAR();
// Setup MQTT subscription for onoff feed.
mqtt.subscribe(&mqttSubNutrientPumps);
mqtt.subscribe(&mqttSubPhUpPump);
mqtt.subscribe(&mqttSubPhDownPump);
mqtt.subscribe(&mqttSubFreshWaterRefillPump);
}
void loop()
{
listenForSubs();
// delay(3000);
turnGreen();
listenForSubs();
readWaterFlow();
listenForSubs();
readMixTankLevel();
listenForSubs();
pHLevelControl();
// ECLevelControl();
readPAR();
listenForSubs();
readPh();
publishReadings();
listenForSubs();
openTopDrain();
listenForSubs();
openBottomDrain();
}
double avergearray(int *arr, int number)
{
int i;
int max, min;
double avg;
long amount = 0;
if (number <= 0)
{
Serial.println("Error number for the array to avraging!/n");
return 0;
}
if (number < 5)
{ // less than 5, calculated directly statistics
for (i = 0; i < number; i++)
{
amount += arr[i];
}
avg = amount / number;
return avg;
}
else
{
if (arr[0] < arr[1])
{
min = arr[0];
max = arr[1];
}
else
{
min = arr[1];
max = arr[0];
}
for (i = 2; i < number; i++)
{
if (arr[i] < min)
{
amount += min; // arr<min
min = arr[i];
}
else
{
if (arr[i] > max)
{
amount += max; // arr>max
max = arr[i];
}
else
{
amount += arr[i]; // min<=arr<=max
}
} // if
} // for
avg = (double)amount / (number - 2);
} // if
return avg;
}
void readPh()
{
printTime = millis();
if (millis() - samplingTime > samplingInterval)
{
pHArray[pHArrayIndex++] = analogRead(PH_PIN);
if (pHArrayIndex == ArrayLenth)
pHArrayIndex = 0;
voltage = avergearray(pHArray, ArrayLenth) * 3.3 / 4096;
pHValue = K_pHCalc * voltage + Offset;
samplingTime = millis();
}
Serial.print("pH value: ");
Serial.println(pHValue, 2);
}
void PhUpPumpPinOn()
{
digitalWrite(PhUpPumpPin, LOW);
Serial.printf("Rising PH Value\n");
delay(PH_PUMP_RUNTIME);
digitalWrite(PhUpPumpPin, HIGH);
Serial.printf("PH Up Off\n");
}
void PhDownPumpPinOn()
{
digitalWrite(PhDownPumpPin, LOW);
Serial.printf("Lowering PH Value\n");
delay(PH_PUMP_RUNTIME);
digitalWrite(PhDownPumpPin, HIGH);
Serial.printf("PH Down off\n");
}
void nutrientPump1On()
{
digitalWrite(nutrientPump1, LOW);
Serial.printf("Adding Nutrient 1\n");
delay(EC_PUMP_RUNTIME);
digitalWrite(nutrientPump1, HIGH);
Serial.printf("Done adding Nutrient 1\n");
}
void nutientPump2On()
{
digitalWrite(nutrientPump2, LOW);
Serial.printf("Adding Nutrient 2\n");
delay(EC_PUMP_RUNTIME);
digitalWrite(nutrientPump2, HIGH);
Serial.printf("Done adding Nutrient 2\n");
}
void openTopDrain()
{
digitalWrite(TopDrain, LOW);
Serial.printf("Draining Top Loop\n");
delay(DRAIN_RUNTIME);
digitalWrite(TopDrain, HIGH);
Serial.printf("Done Draining Top Loop\n");
}
void openBottomDrain()
{
digitalWrite(BottomDrain, LOW);
Serial.printf("Draining Bottom Loop\n");
delay(DRAIN_RUNTIME);
digitalWrite(BottomDrain, HIGH);
Serial.printf("Done Draining Bottom Loop\n");
}
void freshWaterPumpOn()
{
digitalWrite(FreshWaterPump, LOW);
Serial.printf("Running Fresh Water Fill\n");
delay(FRESH_PUMP_RUNTIME);
digitalWrite(FreshWaterPump, HIGH);
Serial.printf("Fresh Water Fill off\n");
}
void Relay_8_PinOn()
{
digitalWrite(Relay_8_Pin, LOW);
Serial.printf("Relay 8 on\n");
delay(FRESH_PUMP_RUNTIME);
digitalWrite(Relay_8_Pin, HIGH);
Serial.printf("Relay 8 off\n");
}
void pHLevelControl()
{
readPh();
pHValue=5.0; //temp to disable pumps
if (pHValue > 5.5)
{
PhDownPumpPinOn();
}
else if (pHValue < 4.5)
{
PhUpPumpPinOn();
}
}
void runNutientPump()
{
nutrientPumpTimer.startTimer(600000);
nutrientPump1On();
while (!nutrientPumpTimer.isTimerReady())
{
delayTimer.startTimer(10000);
Serial.println("Waiting for the nutrient to mix before running the next pump");
while (!delayTimer.isTimerReady())
;
}
nutientPump2On();
}
void ECLevelControl()
{
readMixTankLevel();
if (EC < 1.2)
{
nutrientPumpTimer.startTimer(600000);
nutrientPump1On();
while (!nutrientPumpTimer.isTimerReady())
{
delayTimer.startTimer(10000);
Serial.println("Waiting for the nutrient to mix before running the next pump");
while (!delayTimer.isTimerReady())
;
}
nutientPump2On();
}
else if (EC > 2.0 && mixTankLevel < LOW_WATER_LIMIT)
{
freshWaterPumpOn();
}
}
float readMixTankLevel()
{
digitalWrite(WATER_LEVEL_HOT_PIN, HIGH);
delay(100);
mixTankLevel = (float)analogRead(WaterLevel);
Serial.printf("*The ANALOG value is %f\n", mixTankLevel);
digitalWrite(WATER_LEVEL_HOT_PIN, LOW);
waterLevelInches = map(mixTankLevel, EMPTY_MIX_TANK, HIGH_WATER_LIMIT, 0.0f, 8.1f);
Serial.printlnf("The tank is at %f inches", waterLevelInches);
Serial.printlnf("The average water Level is %f\n", averageWaterLevel(waterLevelInches));
return waterLevelInches;
}
float averageWaterLevel(float waterReading)
{
float waterLevelSum = 0;
waterLevelAverage = 0;
waterLevelArr[waterArrIndex] = waterReading;
waterArrIndex++;
if (waterArrIndex == 60)
{
for (int i = 0; i < waterArrIndex - 1; i++)
{
waterLevelSum += waterLevelArr[i];
}
waterLevelAverage = waterLevelSum / (waterArrIndex + 1);
waterArrIndex = 0;
publishWaterLevelReadings();
if (waterLevelAverage >= 4.0 && waterLevelAverage <= 5.5)
{
freshWaterPumpOn();
}
return waterLevelAverage;
}
else
{
return 7.777;
}
}
float readWaterFlow()
{
Serial.println("Start to read flow rate for 30 seconds");
waterFlowCount = 0;
flowTimer.startTimer(30000);
while (!flowTimer.isTimerReady())
{
if (digitalRead(FlowMeter) == LOW)
{
waterFlowCount++;
while (digitalRead(FlowMeter) == LOW)
;
}
}
waterFlowVolume = ((waterFlowCount * ML_PER_COUNT) * 2.0) / 1000.0;
Serial.printf("Current water flow is %f Liters per minute\n", waterFlowVolume);
return waterFlowVolume;
}
void turnGreen()
{
Serial.println("Correcting NP color");
int glowColor = 0x00FFAA;
if (mlrProduct < 5.0)
{
glowColor = 0x0000FF;
}
else if (mlrProduct > 5.0 && mlrProduct <= 40.0)
{
glowColor = 0x0AFF0A;
}
else if (mlrProduct > 40.0)
{
glowColor = 0xFF0000;
}
strip.clear();
for (int i = 0; i < 35; i++)
{
strip.setPixelColor(i, glowColor);
strip.show();
}
}
double readPAR() {
uint16_t readings[12];
static unsigned int lastPAR = 0;
delay(150);
if (!as7341.readAllChannels(readings))
{
Serial.println("Error reading all channels!");
}
mlrProduct = MLR_B_0 + (MLR_B_1 * (double)readings[0]) + (MLR_B_2 * (double)readings[1]) + (MLR_B_3 * (double)readings[2]) + (MLR_B_4 * (double)readings[3]) + (MLR_B_5 * (double)readings[6]) + (MLR_B_6 * (double)readings[7]) + (MLR_B_7 * (double)readings[8]) + (MLR_B_8 * (double)readings[9]);
if (mlrProduct < 0.0)
{
mlrProduct = 0.0;
}
Serial.printf("PAR = %f\n", mlrProduct);
if((millis()-lastPAR)>300000) {
lastPAR = millis();
if (mqtt.Update()) {
mqttPubCh0.publish(readings[0]);
mqttPubCh1.publish(readings[1]);
mqttPubCh2.publish(readings[2]);
mqttPubCh3.publish(readings[3]);
mqttPubCh4.publish(readings[4]);
mqttPubCh5.publish(readings[5]);
mqttPubCh6.publish(readings[6]);
mqttPubCh7.publish(readings[7]);
mqttPubCh8.publish(readings[8]);
mqttPubCh9.publish(readings[9]);
}
}
return mlrProduct;
}
void publishReadings()
{
MQTT_connect();
if ((millis() - last) > 120000)
{
Serial.printf("Pinging MQTT \n");
if (!mqtt.ping())
{
Serial.printf("Disconnecting \n");
mqtt.disconnect();
}
last = millis();
}
if ((millis() - lastTime > 30000))
{
if (mqtt.Update())
{
mqttPubHydropnicsPARStatus.publish(mlrProduct);
mqttPubHydropnicsFlowStatus.publish(readWaterFlow());
mqttPubHydropnicsPHStatus.publish(pHValue);
mqttPubHydropnicsECStatus.publish(EC);
Serial.println("Sent the readings to the dashboard.");
}
lastTime = millis();
}
}
void publishWaterLevelReadings()
{
MQTT_connect();
if ((millis() - last) > 120000)
{
Serial.printf("Pinging MQTT \n");
if (!mqtt.ping())
{
Serial.printf("Disconnecting \n");
mqtt.disconnect();
}
last = millis();
}
if ((millis() - lastTime > 30000))
{
if (mqtt.Update())
{
mqttPubHydropnicsWaterLevelStatus.publish(waterLevelAverage);
Serial.println("Sent the water level readings to the dashboard.");
mqttPubHydropnicsWaterLevelPercentStatus.publish((waterLevelAverage / HIGH_WATER_INCHES) * 100.0);
Serial.println("Sent the water level percent readings to the dashboard.");
}
lastTime = millis();
}
}
// Function to ensure connection to the adafruit dashboard **Credit to Brian Rashap**
void MQTT_connect()
{
int8_t ret;
// Stop if already connected.
if (mqtt.connected())
{
return;
}
Serial.print("Connecting to MQTT... ");
while ((ret = mqtt.connect()) != 0)
{ // connect will return 0 for connected
Serial.printf("%s\n", (char *)mqtt.connectErrorString(ret));
Serial.printf("Retrying MQTT connection in 5 seconds..\n");
mqtt.disconnect();
connectTimer.startTimer(5000);
while (!connectTimer.isTimerReady())
;
}
Serial.printf("MQTT Connected!\n");
}
void listenForSubs()
{
MQTT_connect();
// this is our 'wait for incoming subscription packets' busy subloop
Adafruit_MQTT_Subscribe *subscription;
while ((subscription = mqtt.readSubscription(1000)))
{
if (subscription == &mqttSubNutrientPumps)
{
int dashNutrientButton = atoi((char *)mqttSubNutrientPumps.lastread);
if (dashNutrientButton == 1)
{
runNutientPump();
}
}
if (subscription == &mqttSubPhUpPump)
{
int dashPhUpButton = atoi((char *)mqttSubPhUpPump.lastread);
if (dashPhUpButton == 1)
{
PhUpPumpPinOn();
}
}
if (subscription == &mqttSubPhDownPump)
{
int dashPhDownButton = atoi((char *)mqttSubPhDownPump.lastread);
if (dashPhDownButton == 1)
{
PhDownPumpPinOn();
}
}
if (subscription == &mqttSubFreshWaterRefillPump)
{
int dashSubFreshWaterRefillButton = atoi((char *)mqttSubPhDownPump.lastread);
if (dashSubFreshWaterRefillButton == 1)
{
freshWaterPumpOn();
}
}
}
}
Comments