CanGrow/include/CanGrow_Control.h

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 */
}
}