CanGrow/include/CanGrow_Output.h

540 lines
18 KiB
C

/*
*
* include/CanGrow_Output.h - Output header file
*
*
*
*/
#include "Output/Output_Common.h"
#include "Output/Output_I2C_01_MCP4725.h"
#include "Output/Output_I2C_02_GP8403.h"
/* OutputI2C index struct */
struct OutputI2C_Index {
const char name[16];
const byte port[OUTPUT_TYPE_I2C_MAX_PORTS];
const byte max;
};
OutputI2C_Index OutputI2Cindex[] {
/* OutputI2C 00 is unset */
{ "unset", {
{},
}},
/* OutputI2C 01 */
{
OUTPUT_I2C_01_NAME,
{
OUTPUT_TYPE_I2C_PORT_BYTE
},
sizeof(Output_I2C_01_MCP4725_Addr),
},
/* 02 - DFRobot Gravity (GP8403) */
{
OUTPUT_I2C_02_NAME,
{
OUTPUT_TYPE_I2C_PORT_BYTE,
OUTPUT_TYPE_I2C_PORT_BYTE
},
sizeof(Output_I2C_02_GP8403_Addr),
}
};
/* Dont forget to increase the index counter after you added a new output module */
const byte OutputI2Cindex_length = 2;
byte Output_I2C_Addr_Init_Update(const byte OutputI2CindexId, const byte AddrId, const byte PortId, const byte Mode, const bool Invert = false, const byte Value = 0) {
const static char LogLoc[] PROGMEM = "[Output:I2C:Addr_Init_Update]";
/* Multi purpose function.
*
* Modes:
* 0 - return the i2c address as byte
* 1 - init the output i2c module, returns true (1) if succeeded
* 2 - update i2 module data
*/
//byte Dummy_Addr[] = { 0x42, 0x69 };
/* invert Value if set, save it to Value_tmp */
byte Value_tmp = Value;
if(Invert == true)
/* Value comes from outputState[] which is only type byte (max 255) */
Value_tmp = 255 - Value;
switch(OutputI2CindexId) {
/* I2C Output module 01 */
case 1:
switch(Mode) {
case OUPUT_I2C_AIU_MODE_ADDR:
return Output_I2C_01_MCP4725_Addr[AddrId];
break;
case OUPUT_I2C_AIU_MODE_INIT:
return Output_I2C_01_MCP4725_Init(AddrId, PortId);
break;
case OUPUT_I2C_AIU_MODE_UPDATE:
Output_I2C_01_MCP4725_Update(AddrId, PortId, Value_tmp);
break;
}
break;
/* I2C Output module 02 */
case 2:
switch(Mode) {
case OUPUT_I2C_AIU_MODE_ADDR:
return Output_I2C_02_GP8403_Addr[AddrId];
break;
case OUPUT_I2C_AIU_MODE_INIT:
return Output_I2C_02_GP8403_Init(AddrId, PortId);
break;
case OUPUT_I2C_AIU_MODE_UPDATE:
Output_I2C_02_GP8403_Update(AddrId, PortId, Value_tmp);
break;
}
break;
/* 02 - dummy*/
//case 2:
//switch(Mode) {
//case 0:
//return Dummy_Addr[AddrId];
//break;
//case 1:
//return true;
//break;
//case 2:
//true;
//break;
//}
//break;
/* unknown i2c output module id */
default:
Log.error(F("%s OutputI2Cindex ID %d not found" CR), LogLoc, OutputI2CindexId);
break;
}
return 0;
}
byte Output_GPIO_Init_Update(const byte GPIOindexId, const bool PWM, const bool Invert, const byte Mode, const byte Value = 0) {
const static char LogLoc[] PROGMEM = "[Output:GPIO:Init_Update]";
bool Value_bool = Value;
switch(Mode) {
case OUTPUT_GPIO_IU_MODE_INIT:
pinMode(GPIOindex[GPIOindexId].gpio, OUTPUT);
if(Invert == true) {
digitalWrite(GPIOindex[GPIOindexId].gpio, 1 - Value_bool);
} else {
digitalWrite(GPIOindex[GPIOindexId].gpio, Value_bool);
}
return true;
break;
case OUTPUT_GPIO_IU_MODE_UPDATE:
if((PWM == false) || (GPIOindex[GPIOindexId].note == NO_PWM)) {
if(Invert == true) {
digitalWrite(GPIOindex[GPIOindexId].gpio, 1 - Value_bool);
} else {
digitalWrite(GPIOindex[GPIOindexId].gpio, Value);
}
} else {
/* output inverted? */
if(Invert == true) {
/* when output is set to 0 (off), use digitalWrite LOW to prevent spikes */
if(GPIOindex[GPIOindexId].gpio < 255) {
analogWrite(GPIOindex[GPIOindexId].gpio, 255 - Value);
} else {
digitalWrite(GPIOindex[GPIOindexId].gpio, HIGH);
#ifdef DEBUG
Log.verbose(F("%s digitalWrite HIGH" CR), LogLoc);
#endif
}
/* not inverted */
} else {
/* when output is set to 0 (off), use digitalWrite LOW to prevent spikes */
if(GPIOindex[GPIOindexId].gpio < 255) {
analogWrite(GPIOindex[GPIOindexId].gpio, Value);
} else {
digitalWrite(GPIOindex[GPIOindexId].gpio, LOW);
#ifdef DEBUG
Log.verbose(F("%s digitalWrite LOW" CR), LogLoc);
#endif
}
}
}
break;
}
return 0;
}
bool Output_Webcall_Init_Update(const byte OutputId, const bool Value = false) {
const static char LogLoc[] PROGMEM = "[Output:Webcall:Init_Update]";
/* here we invert the value if set. First we hand it to a tmp var, Value_tmp */
bool Value_tmp = Value;
/* check if output has inverted flagged */
if(config.system.output.invert[OutputId] == true)
Value_tmp = 1 - Value;
String url;
url += F("http://");
url += config.system.output.webcall_host[OutputId];
url += F("/");
WiFiClient client;
HTTPClient http;
///* set timeout to three seconds */
//#ifdef ESP8266
///* on ESP8266 http.setTimeout accepts miliseconds */
//http.setTimeout(3000);
//#endif
//#ifdef ESP32
///* on ESP32 http.setTimeout() accepts only seconds - https://github.com/espressif/arduino-esp32/issues/3732 */
//http.setTimeout(3);
//#endif
switch(Value_tmp) {
/* turn on */
case true:
url += config.system.output.webcall_path_on[OutputId];
break;
/* turn off */
case false:
url += config.system.output.webcall_path_off[OutputId];
break;
}
/* build request */
http.begin(client, url);
/* fire request and check result, */
int httpResponseCode = http.GET();
/* if 200 , OK */
if(httpResponseCode > 0) {
#ifdef DEBUG2
Log.verbose(F("%s GET %s (%d)" CR), LogLoc, url.c_str(), httpResponseCode);
#endif
return true;
} else {
#ifdef DEBUG2
Log.verbose(F("%s FAILED GET %s (%d)" CR), LogLoc, url.c_str(), httpResponseCode);
#endif
return false;
}
return 0;
}
bool Output_Check_PWM(const byte OutputId) {
/* when we verify output is GPIO PWM OR */
if(((config.system.output.type[OutputId] == OUTPUT_TYPE_GPIO) && (config.system.output.gpio_pwm[OutputId] == true) && (GPIOindex[config.system.output.type[OutputId]].note != NO_PWM)) ||
/* Output is type I2C */
(config.system.output.type[OutputId] == OUTPUT_TYPE_I2C)
) {
/* return true */
return true;
} else {
return false;
}
}
/*
* Output_Device
*
* add, remove, (modify) config.grow.light[] objects
*/
void Output_Device_Grow_AddRemove(const byte OutputId, const byte mode) {
/* switch on device type of the output
*
* Modes:
* 0 - add - receive OutputId
* 1 - remove - receive LightId
* 2 - modify - receive LightId
* */
switch(config.system.output.device[OutputId]) {
case OUTPUT_DEVICE_LIGHT:
//byte LightId;
switch(mode) {
/* add */
case 0:
config.grow.light.configured[OutputId] = true;
config.grow.light.output[OutputId] = OutputId;
config.grow.light.sunriseHourVeg[OutputId] = 6;
config.grow.light.sunriseMinuteVeg[OutputId] = 0;
config.grow.light.sunsetHourVeg[OutputId] = 23;
config.grow.light.sunsetMinuteVeg[OutputId] = 59;
config.grow.light.sunriseHourBloom[OutputId] = 6;
config.grow.light.sunriseMinuteBloom[OutputId] = 0;
config.grow.light.sunsetHourBloom[OutputId] = 18;
config.grow.light.sunsetMinuteBloom[OutputId] = 0;
config.grow.light.power[OutputId] = 0;
if(Output_Check_PWM(OutputId)) {
config.grow.light.fade[OutputId] = true;
} else {
config.grow.light.fade[OutputId] = false;
}
config.grow.light.fadeDuration[OutputId] = 30;
//SaveConfig()
break;
/* remove */
case 1:
/* get LightId for this Output */
//for(byte i = 0; i < Max_Outputs; i++) {
//if((config.grow.light.configured[i] == true) && (config.grow.light.output[i] == OutputId)) {
//LightId = i;
//break;
//}
//}
config.grow.light.configured[OutputId] = 0;
config.grow.light.output[OutputId] = 0;
config.grow.light.sunriseHourVeg[OutputId] = 0;
config.grow.light.sunriseMinuteVeg[OutputId] = 0;
config.grow.light.sunsetHourVeg[OutputId] = 0;
config.grow.light.sunsetMinuteVeg[OutputId] = 0;
config.grow.light.sunriseHourBloom[OutputId] = 0;
config.grow.light.sunriseMinuteBloom[OutputId] = 0;
config.grow.light.sunsetHourBloom[OutputId] = 0;
config.grow.light.sunsetMinuteBloom[OutputId] = 0;
config.grow.light.power[OutputId] = 0;
config.grow.light.fade[OutputId] = 0;
config.grow.light.fadeDuration[OutputId] = 0;
//SaveConfig();
break;
//case 2:
//true;
//break;
}
break;
case OUTPUT_DEVICE_FAN:
case OUTPUT_DEVICE_HUMIDIFIER:
case OUTPUT_DEVICE_DEHUMIDIFIER:
case OUTPUT_DEVICE_HEATING:
switch(mode) {
case 0:
config.grow.air.configured[OutputId] = true;
config.grow.air.output[OutputId] = OutputId;
config.grow.air.power[OutputId] = 0;
config.grow.air.controlSensor[OutputId] = 255; // 255 means unconfigured, because SensorId begins at 0
config.grow.air.controlRead[OutputId] = 255; // same here
config.grow.air.controlMode[OutputId] = 0;
config.grow.air.min[OutputId] = 0;
config.grow.air.max[OutputId] = 0;
break;
case 1:
config.grow.air.configured[OutputId] = false;
config.grow.air.output[OutputId] = 0;
config.grow.air.power[OutputId] = 0;
config.grow.air.controlSensor[OutputId] = 0;
config.grow.air.controlRead[OutputId] = 0;
config.grow.air.controlMode[OutputId] = 0;
config.grow.air.min[OutputId] = 0;
config.grow.air.max[OutputId] = 0;
break;
}
break;
case OUTPUT_DEVICE_PUMP:
switch(mode) {
case 0:
config.grow.water.configured[OutputId] = true;
config.grow.water.output[OutputId] = OutputId;
config.grow.water.onTime[OutputId] = 0;
config.grow.water.controlSensor[OutputId] = 255; // 255 means unconfigured, because SensorId begins at 0
config.grow.water.controlRead[OutputId] = 255; // same here
config.grow.water.controlMode[OutputId] = 0;
config.grow.water.min[OutputId] = 0;
config.grow.water.max[OutputId] = 0;
config.grow.water.interval[OutputId] = 0;
config.grow.water.intervalUnit[OutputId] = 0;
break;
case 1:
config.grow.water.configured[OutputId] = false;
config.grow.water.output[OutputId] = 0;
config.grow.water.onTime[OutputId] = 0;
config.grow.water.controlSensor[OutputId] = 0;
config.grow.water.controlRead[OutputId] = 0;
config.grow.water.controlMode[OutputId] = 0;
config.grow.water.min[OutputId] = 0;
config.grow.water.max[OutputId] = 0;
config.grow.water.interval[OutputId] = 0;
config.grow.water.intervalUnit[OutputId] = 0;
break;
}
break;
//case OUTPUT_DEVICE_HUMIDIFIER:
//break;
//case OUTPUT_DEVICE_DEHUMIDIFIER:
//break;
//case OUTPUT_DEVICE_HEATING:
//break;
}
}
/*
* Output main functions
*
*/
void Output_Init() {
/* initialize all configured outputs */
const static char LogLoc[] PROGMEM = "[Output:Init]";
#ifdef DEBUG
Log.verbose(F("%s == configured outputs ==" CR), LogLoc);
#endif
/* interate through all available Output IDs */
for(byte i = 0; i < Max_Outputs; i++) {
/* if configured */
if(config.system.output.type[i] > 0) {
/* get the configured output type */
switch(config.system.output.type[i]) {
case OUTPUT_TYPE_GPIO:
#ifdef DEBUG
Log.verbose(F("%s Output ID %d: %s - Device %s (%d), GPIO %d (%d), Enabled %T, PWM %T, Invert %T" CR),
LogLoc,
i, config.system.output.name[i],
Output_Device_descr[config.system.output.device[i]], config.system.output.device[i],
GPIOindex[config.system.output.gpio[i]].gpio, config.system.output.gpio[i],
config.system.output.enabled[i], config.system.output.gpio_pwm[i], config.system.output.invert[i]);
#endif
/* TODO implement gpio init */
outputStatus[i] = Output_GPIO_Init_Update(config.system.output.gpio[i], config.system.output.gpio_pwm[i], config.system.output.invert[i], OUTPUT_GPIO_IU_MODE_INIT);
break;
case OUTPUT_TYPE_I2C:
#ifdef DEBUG
Log.verbose(F("%s Output ID %d: %s - Device %s (%d), I2C type %s (%d), I2C addr 0x%x (%d), Module port %d, Enabled %T, PWM %T, Invert %T" CR),
LogLoc,
i, config.system.output.name[i],
Output_Device_descr[config.system.output.device[i]], config.system.output.device[i],
OutputI2Cindex[config.system.output.i2c_type[i]].name, config.system.output.i2c_type[i],
config.system.output.enabled[i], config.system.output.gpio_pwm[i], config.system.output.invert[i]);
#endif
outputStatus[i] = Output_I2C_Addr_Init_Update(config.system.output.i2c_type[i], config.system.output.i2c_addr[i], config.system.output.i2c_port[i], OUPUT_I2C_AIU_MODE_INIT, config.system.output.invert[i]);
break;
case OUTPUT_TYPE_WEB:
#ifdef DEBUG
Log.verbose(F("%s Output ID %d: %s - Device %s (%d), Webcall host %s, Webcall Path ON '%s', Webcall Path OFF '%s', Enabled %T, PWM %T, Invert %T" CR),
LogLoc,
i, config.system.output.name[i],
Output_Device_descr[config.system.output.device[i]], config.system.output.device[i],
config.system.output.webcall_host[i], config.system.output.webcall_path_on[i], config.system.output.webcall_path_off[i],
config.system.output.enabled[i], config.system.output.gpio_pwm[i], config.system.output.invert[i]);
#endif
/* TODO implement webcall init */
outputStatus[i] = Output_Webcall_Init_Update(i);
break;
default:
Log.error(F("%s (%d) Output type %d not found" CR), LogLoc, i, config.system.output.type[i]);
break;
}
}
}
}
void Output_Update() {
const static char LogLoc[] PROGMEM = "[Output:Update]";
#ifdef DEBUG2
unsigned long mStart = millis();
unsigned long mStop;
Log.verbose(F("%s Start %u" CR), LogLoc, mStart);
#endif
/* interate through all available Output IDs */
for(byte i = 0; i < Max_Outputs; i++) {
/* if configured and enabled */
if((config.system.output.type[i] > 0) && (config.system.output.enabled[i] == true)) {
/* get the configured output type */
switch(config.system.output.type[i]) {
/*******
* GPIO
* *****/
case OUTPUT_TYPE_GPIO:
/* update GPIO output */
Output_GPIO_Init_Update(config.system.output.gpio[i], config.system.output.gpio_pwm[i], config.system.output.invert[i], OUTPUT_GPIO_IU_MODE_UPDATE, outputState[i]);
break;
/*******
* I2C
* *****/
case OUTPUT_TYPE_I2C:
/* perform I2C output update only, when outputStatus is OK (true) */
if(outputStatus[i] == true)
Output_I2C_Addr_Init_Update(config.system.output.i2c_type[i], config.system.output.i2c_addr[i], config.system.output.i2c_port[i], OUPUT_I2C_AIU_MODE_UPDATE, config.system.output.invert[i], outputState[i]);
break;
/*******
* WEB
* ****/
case OUTPUT_TYPE_WEB:
/* check how often webcall output failed. if limit exceeded, do not update anymore */
if((outputStatus[i] == false) && (outputWebcallFailed[i] > 5)) {
/* if webcall fail counter has reached limit of 255, reset to 0
* so we retry a call. */
if(outputWebcallFailed[i] >= 255) {
outputWebcallFailed[i] = 0;
} else {
/* increment webcall failed counter. */
outputWebcallFailed[i]++;
}
} else {
/* update webcall output */
outputStatus[i] = Output_Webcall_Init_Update(i, outputState[i]);
if(outputStatus[i] == false) {
/* increment webcall failed counter */
outputWebcallFailed[i]++;
} else {
/* otherwise set to 0 */
outputWebcallFailed[i] = 0;
}
}
true;
break;
default:
Log.error(F("%s (%d) Output type %d not found" CR), LogLoc, i, config.system.output.type[i]);
break;
}
}
}
#ifdef DEBUG2
mStop = millis();
Log.verbose(F("%s Stop %u (%u)" CR), LogLoc, mStop, mStop - mStart);
#endif
}