PCB lives now in its own git repo https://git.la10cy.net/DeltaLima/CanGrow-12V-PCB
540 lines
18 KiB
C
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
|
|
}
|