PCB lives now in its own git repo https://git.la10cy.net/DeltaLima/CanGrow-12V-PCB
407 lines
16 KiB
C
407 lines
16 KiB
C
/*
|
|
*
|
|
* include/CanGrow_Control.h - control stuff for light,air,water header file
|
|
*
|
|
*
|
|
*
|
|
*/
|
|
|
|
|
|
/*
|
|
*
|
|
* Light stuff
|
|
*
|
|
*/
|
|
|
|
/* Light fade */
|
|
byte Light_Power(byte id, unsigned int sunriseSec, unsigned int sunsetSec, unsigned int nowSec, bool shifted) {
|
|
const static char LogLoc[] PROGMEM = "[Control:Light_Power]";
|
|
if(config.grow.light.fade[id] == true) {
|
|
unsigned int fadeDurationSec = config.grow.light.fadeDuration[id] * 60;
|
|
byte power_tmp;
|
|
//byte power_tmp; // = (durationSec - ((sunriseSec + durationSec) - nowSec) * config.grow.light.power[id] / durationSec);
|
|
|
|
/* rising sun */
|
|
if(nowSec <= sunriseSec + fadeDurationSec) {
|
|
/* calculate fade power value */
|
|
//power_tmp = ( ( (nowSec - sunriseSec) / (fadeDurationSec / 255) ) * config.grow.light.power[id] ) / 255;
|
|
power_tmp = (fadeDurationSec - ((sunriseSec + fadeDurationSec) - nowSec)) * config.grow.light.power[id] / fadeDurationSec;
|
|
/* setting sun */
|
|
} else if((nowSec >= sunsetSec - fadeDurationSec) && (nowSec <= sunsetSec)) {
|
|
/* calculate fade power value */
|
|
//power_tmp = ( ( (sunsetSec - nowSec) / (fadeDurationSec / 255) ) * config.grow.light.power[id] ) / 255;
|
|
power_tmp = (sunsetSec - nowSec) * config.grow.light.power[id] / fadeDurationSec;
|
|
} else {
|
|
/* otherwise just turn the light on with configured value */
|
|
power_tmp = config.grow.light.power[id];
|
|
}
|
|
|
|
//if(shifted == false) {
|
|
|
|
//} else {
|
|
|
|
//}
|
|
|
|
#ifdef DEBUG
|
|
Log.verbose(F("%s Light %d - power_tmp %d" CR), LogLoc, id, power_tmp);
|
|
#endif
|
|
return power_tmp;
|
|
} else {
|
|
return config.grow.light.power[id];
|
|
}
|
|
|
|
|
|
//return 0;
|
|
}
|
|
/* Function to set light based on time */
|
|
void Control_Light() {
|
|
const static char LogLoc[] PROGMEM = "[Control:Light]";
|
|
//Log.verbose(F("%s start %s %s" CR), LogLoc, Str_DateNow(), Str_TimeNow());
|
|
/* iterate through all configured lights */
|
|
for(byte i = 0; i < Max_Outputs; i++) {
|
|
if(config.grow.light.configured[i] == true) {
|
|
unsigned int nowSec = (hour() * 60 * 60) + (minute() * 60) + second();
|
|
unsigned int sunriseSec;
|
|
unsigned int sunsetSec;
|
|
|
|
/* check if veg or bloom */
|
|
if((config.grow.start < 1) || (now() - config.grow.start <= config.grow.daysVeg * 24 * 60 * 60)) {
|
|
sunriseSec = (config.grow.light.sunriseHourVeg[i] * 60 * 60) + (config.grow.light.sunriseMinuteVeg[i] * 60);
|
|
sunsetSec = (config.grow.light.sunsetHourVeg[i] * 60 * 60) + (config.grow.light.sunsetMinuteVeg[i] * 60);
|
|
#ifdef DEBUG
|
|
Log.verbose(F("%s Veg" CR), LogLoc);
|
|
#endif
|
|
/* now > than veg = bloom */
|
|
} else if(now() - config.grow.start > config.grow.daysVeg * 24 * 60 * 60) {
|
|
sunriseSec = (config.grow.light.sunriseHourBloom[i] * 60 * 60) + (config.grow.light.sunriseMinuteBloom[i] * 60);
|
|
sunsetSec = (config.grow.light.sunsetHourBloom[i] * 60 * 60) + (config.grow.light.sunsetMinuteBloom[i] * 60);
|
|
#ifdef DEBUG
|
|
Log.verbose(F("%s Bloom" CR), LogLoc);
|
|
#endif
|
|
/* now > than veg+bloom = harvest*/
|
|
} //else if(now() - config.grow.start > (config.grow.daysVeg + config.grow.daysBloom) * 24 * 60 * 60)) {
|
|
//}
|
|
|
|
|
|
|
|
|
|
/*
|
|
* Sunrise / Day
|
|
*/
|
|
|
|
/* when now is greater than sunrise AND sunsetTime is greater than sunrise */
|
|
if((nowSec >= sunriseSec) && (nowSec < sunsetSec) && (sunsetSec > sunriseSec)) {
|
|
//outputState[i] = config.grow.light.power[i];
|
|
outputState[i] = Light_Power(i, sunriseSec, sunsetSec, nowSec, false);
|
|
//Log.verbose(F("%s Light %d - nowSec %d - sunriseSec %d - sunsetSec %d - %s %s Day" CR), LogLoc, i, nowSec, sunriseSec, sunsetSec, Str_DateNow(), Str_TimeNow());
|
|
|
|
|
|
/* when now is greater than sunrise OR */
|
|
} else if(((nowSec >= sunriseSec) && (sunsetSec < sunriseSec)) ||
|
|
/* when now is smaller than sunset AND sunset is
|
|
* smaller than sunrise - this is a shifted daytime */
|
|
((nowSec <= sunsetSec) && (sunsetSec < sunriseSec))) {
|
|
|
|
//outputState[i] = config.grow.light.power[i];
|
|
outputState[i] = Light_Power(i, sunriseSec, sunsetSec, nowSec, true);
|
|
//Log.verbose(F("%s Light %d - nowSec %d - sunriseSec %d - sunsetSec %d - %s %s Day (shifted)" CR), LogLoc, i, nowSec, sunriseSec, sunsetSec, Str_DateNow(), Str_TimeNow());
|
|
|
|
|
|
} else {
|
|
/* otherwise its night, turn off the light */
|
|
outputState[i] = 0;
|
|
//Log.verbose(F("%s Light %d - nowSec %d - sunriseSec %d - sunsetSec %d - %s %s Night" CR), LogLoc, i, nowSec, sunriseSec, sunsetSec, Str_DateNow(), Str_TimeNow());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
*
|
|
* Air stuff
|
|
*
|
|
*/
|
|
|
|
|
|
/*
|
|
* Output Device
|
|
*/
|
|
|
|
/* Air Mode definitions */
|
|
// 0 is unconfigured
|
|
const byte CONTROL_AIR_MODE__TOTAL = 3;
|
|
|
|
const byte CONTROL_AIR_MODE_ONOFF = 1;
|
|
const byte CONTROL_AIR_MODE_LINEAR = 2;
|
|
const byte CONTROL_AIR_MODE_STEPS = 3;
|
|
|
|
const char CONTROL_AIR_MODE_ONOFF_descr[] PROGMEM = {"On/Off"};
|
|
const char CONTROL_AIR_MODE_LINEAR_descr[] PROGMEM = {"Linear"};
|
|
const char CONTROL_AIR_MODE_STEPS_descr[] PROGMEM = {"Steps"};
|
|
|
|
const char * Control_Air_Mode_descr[] = {
|
|
NULL, // 0 - no description because 0 means unconfigured
|
|
CONTROL_AIR_MODE_ONOFF_descr,
|
|
CONTROL_AIR_MODE_LINEAR_descr,
|
|
CONTROL_AIR_MODE_STEPS_descr,
|
|
};
|
|
|
|
|
|
/* Air control modes themselfs */
|
|
|
|
byte Control_Air_Mode_OnOff(byte id) {
|
|
/* turns the output on or off, depending if the is within min and max */
|
|
|
|
/* if only min is set (max = 0), turn on when above it */
|
|
if((config.grow.air.min[id] > 0) && (config.grow.air.max[id] == 0)) {
|
|
/* check if Sensor reading is above min value, then turn on */
|
|
if(Sensor_getCalibratedValue(config.grow.air.controlSensor[id], config.grow.air.controlRead[id]) >= config.grow.air.min[id]) {
|
|
return config.grow.air.power[id];
|
|
} else {
|
|
return 0;
|
|
}
|
|
|
|
/* if only max is set (min = 0), turn off when above */
|
|
} else if((config.grow.air.min[id] == 0) && (config.grow.air.max[id] > 0)) {
|
|
/* check if Sensor reading is under max value, then turn on */
|
|
if(Sensor_getCalibratedValue(config.grow.air.controlSensor[id], config.grow.air.controlRead[id]) <= config.grow.air.max[id]) {
|
|
return config.grow.air.power[id];
|
|
} else {
|
|
return 0;
|
|
}
|
|
/* when min and max are set (> 0) turn output on when within the given values */
|
|
} else if((config.grow.air.min[id] > 0) && (config.grow.air.max[id] > 0)) {
|
|
if((Sensor_getCalibratedValue(config.grow.air.controlSensor[id], config.grow.air.controlRead[id]) >= config.grow.air.min[id]) && (Sensor_getCalibratedValue(config.grow.air.controlSensor[id], config.grow.air.controlRead[id]) <= config.grow.air.max[id])) {
|
|
return config.grow.air.power[id];
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
byte Control_Air_Mode_Linear(byte id) {
|
|
/* if min and max are set */
|
|
if((config.grow.air.min[id] > 0) && (config.grow.air.max[id] > 0)) {
|
|
/* return power value calculated with map() and contrain()
|
|
* multiply by 100 to "convert" the float to int. With constrain we prevent returning negative or out of range values */
|
|
return map(constrain(Sensor_getCalibratedValue(config.grow.air.controlSensor[id], config.grow.air.controlRead[id]) * 100, config.grow.air.min[id] * 100, config.grow.air.max[id] * 100),
|
|
config.grow.air.min[id] * 100,
|
|
config.grow.air.max[id] * 100,
|
|
0,
|
|
config.grow.air.power[id]);
|
|
} else {
|
|
return Control_Air_Mode_OnOff(id);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
byte Control_Air_Mode_Steps(byte id) {
|
|
/* if min and max are set */
|
|
if((config.grow.air.min[id] > 0) && (config.grow.air.max[id] > 0)) {
|
|
/* return power value calculated with map() and contrain()
|
|
* multiply by 100 to "convert" the float to int. With constrain we prevent returning negative or out of range values */
|
|
byte power_tmp = map(constrain(Sensor_getCalibratedValue(config.grow.air.controlSensor[id], config.grow.air.controlRead[id]) * 100, config.grow.air.min[id] * 100, config.grow.air.max[id] * 100),
|
|
config.grow.air.min[id] * 100,
|
|
config.grow.air.max[id] * 100,
|
|
0,
|
|
config.grow.air.power[id]);
|
|
if(power_tmp == 0) {
|
|
return 0;
|
|
} else if(power_tmp < 64) {
|
|
return 64;
|
|
} else if(power_tmp < 128) {
|
|
return 128;
|
|
} else if(power_tmp < 192) {
|
|
return 192;
|
|
} else if(power_tmp < 255) {
|
|
return 192;
|
|
} else {
|
|
return 255;
|
|
}
|
|
} else {
|
|
return Control_Air_Mode_OnOff(id);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Function to set air devices */
|
|
void Control_Air() {
|
|
const static char LogLoc[] PROGMEM = "[Control:Air]";
|
|
|
|
/* iterate through all configured air devices */
|
|
for(byte i = 0; i < Max_Outputs; i++) {
|
|
if(config.grow.air.configured[i] == true) {
|
|
/* check if a control Sensor reading is set. As SensorIndex starts by 0, 255 is "unset" */
|
|
if((config.grow.air.controlSensor[i] < 255) && (config.grow.air.controlRead[i] < 255)) {
|
|
/* switch for control modes */
|
|
switch(config.grow.air.controlMode[i]) {
|
|
|
|
case CONTROL_AIR_MODE_ONOFF:
|
|
outputState[i] = Control_Air_Mode_OnOff(i);
|
|
break;
|
|
|
|
case CONTROL_AIR_MODE_LINEAR:
|
|
outputState[i] = Control_Air_Mode_Linear(i);
|
|
break;
|
|
|
|
case CONTROL_AIR_MODE_STEPS:
|
|
outputState[i] = Control_Air_Mode_Steps(i);
|
|
break;
|
|
|
|
}
|
|
|
|
/* if there is no control sensor reading selected, just set power */
|
|
} else {
|
|
outputState[i] = config.grow.air.power[i];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
*
|
|
* Water stuff
|
|
*
|
|
*/
|
|
|
|
|
|
/*
|
|
* Output Device
|
|
*/
|
|
|
|
/* Water Mode definitions */
|
|
// 0 is unconfigured
|
|
const byte CONTROL_WATER_MODE__TOTAL = 3;
|
|
|
|
const byte CONTROL_WATER_MODE_TIMEINTERVAL = 1;
|
|
const byte CONTROL_WATER_MODE_SENSOR_MIN_THRESHOLD = 2;
|
|
const byte CONTROL_WATER_MODE_SENSMIN_TIMEINT_COMBINED = 3;
|
|
|
|
const char CONTROL_WATER_MODE_TIMEINTERVAL_descr[] PROGMEM = {"Timeinterval"};
|
|
const char CONTROL_WATER_MODE_SENSOR_MIN_THRESHOLD_descr[] PROGMEM = {"Sensor min threshold"};
|
|
const char CONTROL_WATER_MODE_SENSMIN_TIMEINT_COMBINED_descr[] PROGMEM = {"Sensor min + Timeinterval"};
|
|
|
|
const char * Control_Water_Mode_descr[] = {
|
|
NULL, // 0 - no description because 0 means unconfigured
|
|
CONTROL_WATER_MODE_TIMEINTERVAL_descr,
|
|
CONTROL_WATER_MODE_SENSOR_MIN_THRESHOLD_descr,
|
|
CONTROL_WATER_MODE_SENSMIN_TIMEINT_COMBINED_descr,
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/* Function to set water devices */
|
|
void Control_Water() {
|
|
const static char LogLoc[] PROGMEM = "[Control:Water]";
|
|
|
|
/* iterate through all configured water devices which have a control mode set */
|
|
for(byte i = 0; i < Max_Outputs; i++) {
|
|
if((config.grow.water.configured[i] == true) && (config.grow.water.controlMode[i] > 0)) {
|
|
|
|
/* which mode was set in config.grow.water.controlMode */
|
|
switch(config.grow.water.controlMode[i]) {
|
|
|
|
case CONTROL_WATER_MODE_TIMEINTERVAL:
|
|
// when diff of time now and time pumpLastOn is greater then water.interval, do some watering (Or manual watering)
|
|
if( (now() - controlWaterLast[i]) >= (config.grow.water.interval[i] * Timescale(config.grow.water.intervalUnit[i])) ) {
|
|
/* check if output is already on. If not so, we begin a watering cycle and remember the timestamp of it. */
|
|
if(outputState[i] == 0) {
|
|
controlWaterLastStarted[i] = now();
|
|
}
|
|
|
|
/* when diff of now and controlWaterLastStarted is smaller than onTime, turn output on */
|
|
if((now() - controlWaterLastStarted[i]) < config.grow.water.onTime[i]) {
|
|
/* at the moment i think PWM is not necessary here, so we set the output to 255 */
|
|
outputState[i] = 255;
|
|
/* when onTime is exceeded, turn output off */
|
|
} else {
|
|
outputState[i] = 0;
|
|
|
|
/* remember when we finished watering */
|
|
controlWaterLast[i] = now();
|
|
|
|
/* Todo, write controlWaterLast to LittleFS. */
|
|
}
|
|
} else {
|
|
/* turn output off when interval is not exceeded */
|
|
outputState[i] = 0;
|
|
}
|
|
break;
|
|
|
|
case CONTROL_WATER_MODE_SENSOR_MIN_THRESHOLD:
|
|
/* when sensor reading config.grow.water.controlSensor is lower then config.grow.water.min , do some watering */
|
|
if( (Sensor_getCalibratedValue(config.grow.water.controlSensor[i], config.grow.water.controlRead[i]) < config.grow.water.min[i]) ||
|
|
/* or when the sensor value is larger than min but onTime has not exceeded yet */
|
|
((Sensor_getCalibratedValue(config.grow.water.controlSensor[i], config.grow.water.controlRead[i]) >= config.grow.water.min[i]) && ( (now() - controlWaterLastStarted[i]) < config.grow.water.onTime[i]))
|
|
) {
|
|
/* check if output is already on. If not so, we begin a watering cycle and remember the timestamp of it. */
|
|
if(outputState[i] == 0) {
|
|
controlWaterLastStarted[i] = now();
|
|
}
|
|
|
|
/* when diff of now and controlWaterLastStarted is smaller than onTime, turn output on */
|
|
if((now() - controlWaterLastStarted[i]) < config.grow.water.onTime[i]) {
|
|
/* at the moment i think PWM is not necessary here, so we set the output to 255 */
|
|
outputState[i] = 255;
|
|
/* when onTime is exceeded, turn output off */
|
|
} else {
|
|
outputState[i] = 0;
|
|
}
|
|
/* turn output off when water conditions are not met */
|
|
} else {
|
|
outputState[i] = 0;
|
|
}
|
|
break;
|
|
|
|
case CONTROL_WATER_MODE_SENSMIN_TIMEINT_COMBINED:
|
|
// when diff of time now and time pumpLastOn is greater then water.interval AND sensor read value is below min
|
|
if( ( (now() - controlWaterLast[i]) >= (config.grow.water.interval[i] * Timescale(config.grow.water.intervalUnit[i])) ) &&
|
|
( (Sensor_getCalibratedValue(config.grow.water.controlSensor[i], config.grow.water.controlRead[i]) < config.grow.water.min[i]) ||
|
|
/* or when the sensor value is larger than min but onTime has not exceeded yet */
|
|
((Sensor_getCalibratedValue(config.grow.water.controlSensor[i], config.grow.water.controlRead[i]) >= config.grow.water.min[i]) && ( (now() - controlWaterLastStarted[i]) < config.grow.water.onTime[i])) )
|
|
) {
|
|
/* check if output is already on. If not so, we begin a watering cycle and remember the timestamp of it. */
|
|
if(outputState[i] == 0) {
|
|
controlWaterLastStarted[i] = now();
|
|
}
|
|
|
|
/* when diff of now and controlWaterLastStarted is smaller than onTime, turn output on */
|
|
if((now() - controlWaterLastStarted[i]) < config.grow.water.onTime[i]) {
|
|
/* at the moment i think PWM is not necessary here, so we set the output to 255 */
|
|
outputState[i] = 255;
|
|
/* when onTime is exceeded, turn output off */
|
|
} else {
|
|
outputState[i] = 0;
|
|
|
|
/* remember when we finished watering */
|
|
controlWaterLast[i] = now();
|
|
|
|
/* Todo, write controlWaterLast to LittleFS. */
|
|
}
|
|
} else {
|
|
/* turn output off when interval is not exceeded */
|
|
outputState[i] = 0;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
/* when no mode is selected, turn output off */
|
|
outputState[i] = 0;
|
|
break;
|
|
|
|
}
|
|
}
|
|
/* if neither configured or mode set, force output being off */
|
|
}
|
|
}
|