hydroponic-controller/src/main.cpp
2025-06-29 19:25:17 +02:00

624 lines
No EOL
16 KiB
C++

#include <Arduino.h>
#include <Wire.h>
// ESP32-S3 Devkit M-1 Left USB-C: Program, Right USB-C Serial
bool valueError=false;
unsigned long last_check=0;
#include "wifi_functions.h"
bool debug=false; //print Serial information
bool mqtt=true;
bool eccalibrationoutput=false; //serial output for ec calibration
/* Write to file with:
sudo stty -F /dev/ttyUSB0 115200
cat /dev/ttyUSB0 | tee received.txt
falls nicht geht, vorher einmal kurz per screen verbinden
*/
bool valuesStabilized=false; //gets set true when values are stable (avaeraging arrays filled)
#include "helpfunctions.h"
#ifdef EC_CALIBRATION_POLYNOM
#include "ADS1X15.h"
#endif
// ######## Temperature
#ifdef ONE_WIRE_BUS_PIN
#include "temperature.h"
#endif
// ######## Water Level
#ifdef RES_AREA
#include "waterlevel.h"
#endif
// ######## EC
#ifdef EC_CALIBRATION_POLYNOM
ADS1115 ADS(0x48);
bool adsenabled=true;
#include "ec.h"
#endif
// PUMP
#if defined(VALVECOUNT) || defined(RELAISCOUNT)
#include "pump.h"
#endif
// ######## Flow Rate
#ifdef FLOW_PIN
#include "flow.h"
#endif
// ######## Soilmoisture
//#include "soilmoisture.h"
#ifdef PIN_NEOPIXEL
#include <Adafruit_NeoPixel.h>
Adafruit_NeoPixel pixels(1, PIN_NEOPIXEL, NEO_GRB + NEO_KHZ800);
#endif
void setup() {
#ifdef PIN_BUTTON
pinMode(PIN_BUTTON,INPUT_PULLUP);
#endif
#ifdef PIN_LED
pinMode(PIN_LED,OUTPUT);
digitalWrite(PIN_LED,LOW);
#endif
#if defined(VALVECOUNT) || defined(RELAISCOUNT)
pump_setup();
#endif
#if defined(NEOPIXEL_POWER)
// If this board has a power control pin, we must set it to output and high
// in order to enable the NeoPixels. We put this in an #if defined so it can
// be reused for other boards without compilation errors
pinMode(NEOPIXEL_POWER, OUTPUT);
digitalWrite(NEOPIXEL_POWER, HIGH);
#endif
#ifdef PIN_NEOPIXEL
pixels.begin();
pixels.setBrightness(100); // not so bright
pixels.clear();
//Flash colors for debug
pixels.setPixelColor(0, pixels.Color(255, 0,0));
pixels.show();
delay(250);
pixels.setPixelColor(0, pixels.Color(0, 255,0));
pixels.show();
delay(250);
pixels.setPixelColor(0, pixels.Color(0, 0,255));
pixels.show();
delay(250);
pixels.clear();
pixels.show();
#endif
Serial.begin(115200);
if (mqtt) {
WiFi.begin(ssid, pass);
client.begin(mqtt_host, net);
client.onMessage(messageReceived);
connect();
}
#if defined(VALVECOUNT) || defined(RELAISCOUNT)
pump_setup();
srOutputOff();
#endif
#ifdef PIN_SDA
Wire.begin(PIN_SDA,PIN_SCL);
Serial.print("I2C Clock Speed=");
Serial.println(Wire.getClock());
i2cscan();
#endif
#ifdef RES_AREA
Serial.println("Setup Waterlevel");
waterlevel_setup();
#endif
//init ADS1115
#ifdef EC_CALIBRATION_POLYNOM
if (!ADS.begin()) {
Serial.println("Error:"); delay(2000); Serial.println("ADS1115 Init Error!");
if (mqtt){
publishInfo("error/general","ADS1115 Init Error");
}
adsenabled=false;
}
if (adsenabled){
ADS.setGain(0);
}
Serial.println("Setup EC");
ec_setup();
#endif
#ifdef ONE_WIRE_BUS_PIN
Serial.println("Setup Temperature");
temperature_setup();
#endif
#ifdef FLOW_PIN
Serial.println("Setup Flow");
flow_setup();
#endif
/*
Serial.println("Setup Soilmoisture");
sm_setup();
*/
Serial.println("Finished Setup");
delay(200);
#ifdef EC_CALIBRATION_POLYNOM
//Test adc to ec function output
if (eccalibrationoutput) {
Serial.println();
Serial.print("adc"); Serial.print(","); Serial.print("ec"); Serial.println();
for (int i=728;i<14000;i+=100) {
//float _ec=ec_getECfromADC(i);
float _ec=ec_getECfromADC(i, ec_calibration_polynom, sizeof(ec_calibration_polynom), ec_calibration_linearize_below_adc, ec_calibration_linear_lowADC, ec_calibration_linear_lowEC);
Serial.print(i); Serial.print(","); Serial.print(_ec); Serial.println();
}
Serial.println("Waiting 10 seconds because eccalibrationoutput is enabled");
delay(10000);
}
#endif
//Serial.println("time,tempReservoir,ECadcCalib,ECadc,ECadcAdjusted,EC,EC25");
//Serial.println("time,tempReservoir,ECadcCalib,ECadc,ECadcAdjusted");
}
void loop() {
unsigned long loopmillis=millis();
enableTiming=true; //reactivate
#ifdef EC_CALIBRATION_POLYNOM
ec_loop(loopmillis);
#endif
#ifdef ONE_WIRE_BUS_PIN
temperature_loop(loopmillis);
#endif
#if defined(EC_CALIBRATION_POLYNOM) && defined(RES_AREA)
if (!ec_measurementRunning()){ //skip tof read when ec measurement running, because vlxx sensor reading takes quite long per cycle
waterlevel_loop(loopmillis);
}
#elif defined(RES_AREA)
waterlevel_loop(loopmillis);
#endif
#ifdef FLOW_PIN
flow_loop(loopmillis);
#endif
//sm_loop(loopmillis);
#if defined(VALVECOUNT) || defined(RELAISCOUNT)
pump_loop(loopmillis);
#endif
static bool getReading=false;
if (!eccalibrationoutput) { //Is in normal operation mode
#ifdef PIN_BUTTON
if (!digitalRead(PIN_BUTTON)) { //button pressed
valueError=false;
Serial.println("Reset ValueError flag by user");
#ifdef PIN_LED
digitalWrite(PIN_LED,valueError); //set led before delay to blink if error persists
#endif
#ifdef PIN_NEOPIXEL
pixels.setPixelColor(0, pixels.Color(255*valueError, 0,0));
pixels.show();
#endif
delay(100);
}
#endif
static bool last_valueError=true;
if (!valuesStabilized) { //if values are not okay since boot
#ifdef PIN_LED
digitalWrite(PIN_LED,(loopmillis/250)%2==0); //blink led
#endif
#ifdef PIN_NEOPIXEL
pixels.setPixelColor(0, pixels.Color(((loopmillis/250)%2==0)*255,((loopmillis/250)%2==0)*255,0));
pixels.show();
#endif
}else{ //LED shows valueError flag status when values were okay once
if (last_valueError!=valueError) { //update led if valueerror flag changed
last_valueError=valueError;
#ifdef PIN_LED
digitalWrite(PIN_LED,valueError);
#endif
#ifdef PIN_NEOPIXEL
pixels.setPixelColor(0, pixels.Color((loopmillis/250)%2==0,(loopmillis/250)%2==0,0));
pixels.show();
#endif
}
}
}
#ifdef EC_CALIBRATION_POLYNOM
if (eccalibrationoutput && !digitalRead(PIN_BUTTON) && !getReading) { //Calibration UI . Button Pressed
if (!isValueArrayOK(ec_calib_array,EC_CALIB_ARRAY_SIZE,EC_ADC_UNAVAILABLE)) { //Calibration Array filled?
for (uint8_t blink=0;blink<5;blink++) { //Error Blink
#ifdef PIN_LED
digitalWrite(PIN_LED,HIGH);
delay(100);
digitalWrite(PIN_LED,LOW);
delay(100);
#endif
#ifdef PIN_NEOPIXEL
pixels.setPixelColor(0, pixels.Color(0,0,255));
pixels.show();
delay(100);
pixels.setPixelColor(0, pixels.Color(0,0,0));
pixels.show();
delay(100);
#endif
}
}else{ //Calibration Array is ok, initiate ec reading
getReading=true;
force_ec_measurement=true;
ec_flag_measurement_available=false;
#ifdef PIN_LED
digitalWrite(PIN_LED,HIGH);
#endif
#ifdef PIN_NEOPIXEL
pixels.setPixelColor(0, pixels.Color(0,255,0));
pixels.show();
#endif
}
}
if (eccalibrationoutput && ec_flag_measurement_available && getReading) { //Calibration UI
ec_flag_measurement_available=false;
getReading=false;
#ifdef PIN_LED
digitalWrite(PIN_LED,LOW);
#endif
#ifdef PIN_NEOPIXEL
pixels.setPixelColor(0, pixels.Color(0,0,0));
pixels.show();
#endif
Serial.print(CLIENT_ID); Serial.print(",");
Serial.print(loopmillis); Serial.print(",");
Serial.print(tempCmean_reservoir); Serial.print(",");
Serial.print(ec_calib_adc); Serial.print(",");
Serial.print(ec_adc); Serial.print(",");
Serial.print(ec_adc_adjusted);
Serial.println();
}
#endif
if (loopmillis>last_check+2000) { //check values
last_check=loopmillis;
bool _noErrorsDuringLoop=true;
#ifdef ONE_WIRE_BUS_PIN
bool tempdevdisconnected=false;
#ifdef THERMOMETER_ADDR_RESERVOIR
tempdevdisconnected|= (tempCmean_reservoir==DEVICE_DISCONNECTED_C);
#endif
#ifdef THERMOMETER_ADDR_RESERVOIR
tempdevdisconnected|= (tempCmean_case==DEVICE_DISCONNECTED_C);
#endif
if (tempdevdisconnected) {
if (!valueError && valuesStabilized) { //error just appeared
#ifdef THERMOMETER_ADDR_RESERVOIR
if (tempCmean_reservoir==DEVICE_DISCONNECTED_C) {
Serial.println("valueError tempCmean_reservoir");
if (mqtt){
publishInfo("error/temperature","valueError tempCmean_reservoir");
}
}
#endif
#ifdef THERMOMETER_ADDR_CASE
if (tempCmean_case==DEVICE_DISCONNECTED_C) {
Serial.println("valueError tempCmean_case");
if (mqtt){
publishInfo("error/temperature","valueError tempCmean_case");
}
}
#endif
}
valueError=true;
_noErrorsDuringLoop=false;
}
#endif
/*
if (sm_mean1==SM_DISCONNECTED || sm_mean2==SM_DISCONNECTED) {
valueError=true;
}*/
#ifdef EC_CALIBRATION_POLYNOM
if (ec==EC_UNAVAILABLE){
if (!valueError && valuesStabilized) { //error just appeared
if (ec==EC_UNAVAILABLE){
Serial.println("valueError ec");
if (mqtt){
publishInfo("error/ec","valueError ec");
}
}
}
valueError=true;
_noErrorsDuringLoop=false;
}
#endif
#ifdef RES_AREA
if (distance_unsuccessful_count>20) {
if (!valueError && valuesStabilized) { //error just appeared
Serial.println("valueError distance");
if (mqtt){
publishInfo("error/waterlevel","valueError distance");
}
}
valueError=true;
_noErrorsDuringLoop=false;
}
#endif
if (_noErrorsDuringLoop && !valuesStabilized) {
valuesStabilized=true; //gets only set to true once
valueError=false; //clear error flag once after boot
Serial.println("Values Stable, clear error flag");
}
if (debug) {
Serial.println("_______________________");
Serial.print(millis()/1000.0,2); Serial.println(":");
#ifdef THERMOMETER_ADDR_RESERVOIR
Serial.print("temperature reservoir = ");
Serial.print(tempCmean_reservoir);
Serial.println();
#endif
#ifdef THERMOMETER_ADDR_CASE
Serial.print("temperature case = ");
Serial.print(tempCmean_case);
Serial.println();
#endif
/*
Serial.print("sm_mean 1,2,3 = ");
Serial.print(sm_mean1); Serial.print(",");
Serial.print(sm_mean2); Serial.print(",");
Serial.print(sm_mean3);
Serial.println();
*/
/*
Serial.print("sm_mean 1,2,3 = ");
Serial.print(getMean(sm_mean1array,SM_SIZE)); Serial.print(",");
Serial.print(getMean(sm_mean2array,SM_SIZE)); Serial.print(",");
Serial.print(getMean(sm_mean3array,SM_SIZE));
Serial.println();
Serial.print("sm_max 1,2,3 = ");
Serial.print(getMax(sm_mean1array,SM_SIZE)); Serial.print(",");
Serial.print(getMax(sm_mean2array,SM_SIZE)); Serial.print(",");
Serial.print(getMax(sm_mean3array,SM_SIZE));
Serial.println();
Serial.print("sm_min 1,2,3 = ");
Serial.print(getMin(sm_mean1array,SM_SIZE)); Serial.print(",");
Serial.print(getMin(sm_mean2array,SM_SIZE)); Serial.print(",");
Serial.print(getMin(sm_mean3array,SM_SIZE));
Serial.println();
//Serial.print(getMax(sm_mean3array,SM_SIZE)); Serial.println();
*/
#ifdef FLOW_PIN
Serial.print("Flow = "); Serial.print(flow);
Serial.println();
#endif
#ifdef EC_CALIBRATION_POLYNOM
Serial.print("EC ec_calib_adc,ec_adc,ec_adc_adjusted = ");
Serial.print(ec_calib_adc); Serial.print(",");
Serial.print(ec_adc); Serial.print(",");
Serial.print(ec_adc_adjusted);
Serial.println();
Serial.print("EC ec,ec25 = ");
Serial.print(ec); Serial.print(",");
Serial.print(ec25);
Serial.println();
#endif
#ifdef RES_AREA
Serial.print("distance,Waterlevel,Volume = ");
Serial.print(distance); Serial.print(",");
Serial.print(waterlevel); Serial.print(",");
Serial.print(watervolume); Serial.println();
Serial.println();
#endif
}
if (mqtt && mqtt_loop(loopmillis)) {
if (sendallnext_flag) {
sendallnext_flag=false;
enableTiming=false;
}
#ifdef THERMOMETER_ADDR_RESERVOIR
if (tempCmean_reservoir!=DEVICE_DISCONNECTED_C) {
publishValueTimed("temperature/reservoir",tempCmean_reservoir,2,timing_temperature_reservoir,loopmillis);
}
#endif
#ifdef THERMOMETER_ADDR_CASE
if (tempCmean_case!=DEVICE_DISCONNECTED_C) {
publishValueTimed("case/temperature",tempCmean_case,2,timing_temperature_case,loopmillis);
}
#endif
/*
if (sm_mean1!=SM_DISCONNECTED) {
publishValueTimed("soilmoisture/sm1",sm_mean1,3,timing_soilmoisture_sm1,loopmillis);
}
if (sm_mean2!=SM_DISCONNECTED) {
publishValueTimed("soilmoisture/sm2",sm_mean2,3,timing_soilmoisture_sm2,loopmillis);
}
if (sm_mean3!=SM_DISCONNECTED) {
publishValueTimed("soilmoisture/sm3",sm_mean3,3,timing_soilmoisture_sm3,loopmillis);
}
*/
#ifdef FLOW_PIN
static float last_flow=0;
if (valuesStabilized){
if (flow==0.0 && last_flow!=flow) {
publishValueTimedOverride("flow",flow,2,timing_flow,loopmillis); //publish without waiting if flow is 0
}else{
publishValueTimed("flow",flow,2,timing_flow,loopmillis);
}
last_flow=flow;
}
#endif
#ifdef RES_AREA
if (waterlevel!=WATERLEVEL_UNAVAILABLE) {
bool _published=publishValueTimed("waterlevel/height",waterlevel,2,timing_waterlevel,loopmillis);
if (_published) { //use height for timing. send calculated volume with it
publishValue("waterlevel/volume",watervolume,2);
}
}
#endif
#ifdef EC_CALIBRATION_POLYNOM
if (ec_flag_measurement_available){
ec_flag_measurement_available=false;
if (ec_calib_adc!=0) {
publishValue("ec/eccalibadc",ec_calib_adc,0);
}
//Probe A
if (ec_adc!=0) {
publishValue("ec/adc",ec_adc,0);
}
if (ec_adc_adjusted!=0) {
publishValue("ec/adcadjusted",ec_adc_adjusted,0);
}
if (ec!=EC_UNAVAILABLE){
publishValue("ec/ec",ec,0);
publishValue("ec/sc",ec25,0);
}
}
#endif
#ifdef VALVECOUNT
String valvestatestring="";
static String last_valvestatestring="";
for (uint8_t i=0;i<VALVECOUNT;i++){
if (valveOffTime[i]!=0) { //valve on
valvestatestring+="1";
}else{
valvestatestring+="0";
}
}
if (!last_valvestatestring.equals(valvestatestring)) { //has changed
publishInfo("valve/state",valvestatestring); //Example: valve 2 active: 010000000
}
last_valvestatestring=valvestatestring;
#endif
#ifdef RELAISCOUNT
String relaisstatestring="";
static String last_relaisstatestring="";
for (uint8_t i=0;i<RELAISCOUNT;i++){
if (relaisOffTime[i]!=0) { //valve on
relaisstatestring+="1";
}else{
relaisstatestring+="0";
}
}
if (!last_relaisstatestring.equals(relaisstatestring)) { //has changed
publishInfo("relais/state",relaisstatestring); //Example: valve 2 active: 010000000
}
last_relaisstatestring=relaisstatestring;
#endif
/*
if (ec_adc!=0) {
publishValueTimed("ec/adc",ec_adc,0,timing_ec_adc,loopmillis);
}
if (ec_calib_adc!=0) {
publishValueTimed("ec/eccalibadc",ec_calib_adc,0,timing_ec_calibadc,loopmillis);
}
if (ec_adc_adjusted!=0) {
publishValueTimed("ec/adcadjusted",ec_adc_adjusted,0,timing_ec_adcadjusted,loopmillis);
}
if (ec!=EC_UNAVAILABLE){
publishValueTimed("ec/ec",ec,0,timing_ec_ec,loopmillis);
publishValueTimed("ec/sc",ec25,0,timing_ec_sc,loopmillis);
}*/
}
}
}