PCB lives now in its own git repo https://git.la10cy.net/DeltaLima/CanGrow-12V-PCB
642 lines
15 KiB
C
642 lines
15 KiB
C
/*
|
|
*
|
|
* include/CanGrow_Core.h - core stuff header file
|
|
*
|
|
*
|
|
*
|
|
*/
|
|
|
|
/*
|
|
* NTP Stuff
|
|
*/
|
|
|
|
WiFiUDP ntpUDP;
|
|
NTPClient timeClient(ntpUDP);
|
|
|
|
/*
|
|
* RTC Stuff
|
|
*/
|
|
|
|
/* I would more like not to define four individual globals for each RTC type
|
|
* but Adafruit lib seems to work only this way - and i am too lazyscared to use
|
|
* some other lib or do it myself - so i hope this will not eat up my ram */
|
|
RTC_DS1307 rtc_ds1307;
|
|
RTC_DS3231 rtc_ds3231;
|
|
RTC_PCF8523 rtc_pcf8523;
|
|
RTC_PCF8563 rtc_pcf8563;
|
|
|
|
|
|
/*
|
|
* Timer stuff
|
|
*/
|
|
NusabotSimpleTimer timer;
|
|
|
|
|
|
/*
|
|
* Logging stuff
|
|
*
|
|
* Example Log call
|
|
* const static char LogLoc[] PROGMEM= "[Some:Stuff:Happening]"
|
|
* Log.notice(F("%s This is %d" CR), LogLoc, i);
|
|
*
|
|
* LogLoc stands for "LogLocation"
|
|
*/
|
|
|
|
/* Logging prefix */
|
|
void LogPrefix(Print* _logOutput, int logLevel) {
|
|
//_logOutput->print(":: TEST");
|
|
switch (logLevel)
|
|
{
|
|
default:
|
|
// silent
|
|
case 0:_logOutput->print("--" ); break;
|
|
// fatal
|
|
case 1:_logOutput->print("!!!! " ); break;
|
|
// error
|
|
case 2:_logOutput->print("!! " ); break;
|
|
// warning
|
|
case 3:_logOutput->print("!: "); break;
|
|
// info / notice
|
|
case 4:_logOutput->print(":: " ); break;
|
|
// trace
|
|
case 5:_logOutput->print("T: " ); break;
|
|
// verbose / debug
|
|
case 6:_logOutput->print("DB "); break;
|
|
}
|
|
}
|
|
|
|
/* System core stuff , like restart , give free Id of xy, .. */
|
|
void Restart() {
|
|
const static char LogLoc[] PROGMEM = "[Core:Restart]";
|
|
Log.notice(F("%s got triggered, restarting in 2 seconds" CR), LogLoc);
|
|
|
|
// blink fast with the built in LED in an infinite loop
|
|
byte i = 0;
|
|
while(i <= 16) {
|
|
if(i % 2) {
|
|
digitalWrite(PinWIPE, 1 - PinWIPE_default);
|
|
|
|
} else {
|
|
digitalWrite(PinWIPE, PinWIPE_default);
|
|
|
|
}
|
|
i++;
|
|
delay(125);
|
|
}
|
|
ESP.restart();
|
|
|
|
}
|
|
|
|
|
|
// IP2Char helper function to convert ip arrarys to char arrays
|
|
char* IP2Char(IPAddress ipaddr){
|
|
// https://forum.arduino.cc/t/trouble-returning-char-array-string/473246/6
|
|
static char buffer[18];
|
|
sprintf(buffer, "%d.%d.%d.%d", ipaddr[0], ipaddr[1], ipaddr[2], ipaddr[3] );
|
|
return buffer;
|
|
}
|
|
|
|
byte Give_Free_OutputId() {
|
|
const static char LogLoc[] PROGMEM = "[Core:Give_Free_OutputId]";
|
|
byte outputId_free;
|
|
for(byte i=0; i < Max_Outputs; i++) {
|
|
if(config.system.output.type[i] > 0) {
|
|
// here i define that 255 stands for "no more free outputs"
|
|
outputId_free = 255;
|
|
} else {
|
|
outputId_free = i;
|
|
break;
|
|
}
|
|
}
|
|
#ifdef DEBUG
|
|
Log.verbose(F("%s next free output id: %d" CR), LogLoc, outputId_free);
|
|
#endif
|
|
return outputId_free;
|
|
}
|
|
|
|
byte Give_Free_SensorId() {
|
|
const static char LogLoc[] PROGMEM = "[Core:Give_Free_SensorId]";
|
|
|
|
byte sensorId_free;
|
|
for(byte i=0; i < Max_Sensors; i++) {
|
|
if(config.system.sensor.type[i] > 0) {
|
|
// here i define that 255 stands for "no more free outputs"
|
|
sensorId_free = 255;
|
|
} else {
|
|
sensorId_free = i;
|
|
break;
|
|
}
|
|
}
|
|
#ifdef DEBUG
|
|
Log.verbose(F("%s next free sensor id: %d" CR), LogLoc, sensorId_free);
|
|
#endif
|
|
return sensorId_free;
|
|
}
|
|
|
|
|
|
|
|
// checks if GPIO is already in use by output or sensor
|
|
bool Check_GPIOindex_Used(byte gpio) {
|
|
const static char LogLoc[] PROGMEM = "[Core:Check_GPIOindex_Used]";
|
|
|
|
bool used;
|
|
|
|
//Log.verbose(F("%s check GPIO: %d" CR), LogLoc, gpio);
|
|
|
|
// go through each outputid
|
|
for(byte i=0; i < Max_Outputs; i++) {
|
|
|
|
// check if output type is gpio
|
|
if(config.system.output.type[i] == OUTPUT_TYPE_GPIO) {
|
|
#ifdef DEBUG
|
|
Log.verbose(F("%s OutputId: %d is GPIO (type %d)" CR), LogLoc, i, config.system.output.type[i]);
|
|
#endif
|
|
// check if gpio id is already in use
|
|
if(config.system.output.gpio[i] == gpio) {
|
|
#ifdef DEBUG
|
|
Log.verbose(F("%s output.gpio[%d](%d) == GPIO %d" CR), LogLoc, i, config.system.output.gpio[i], gpio);
|
|
#endif
|
|
used = true;
|
|
break;
|
|
} else {
|
|
used = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(used == false) {
|
|
for(byte i=0; i < Max_Sensors; i++) {
|
|
|
|
// check if sensor type uses gpio
|
|
if((SensorIndex[config.system.sensor.type[i]].type == SENSOR_TYPE_INTADC) || (SensorIndex[config.system.sensor.type[i]].type == SENSOR_TYPE_ONEWIRE) || (SensorIndex[config.system.sensor.type[i]].type == SENSOR_TYPE_TWOWIRE)) {
|
|
#ifdef DEBUG
|
|
Log.verbose(F("%s SensorId: %d is using GPIO (type %d)" CR), LogLoc, i, config.system.sensor.type[i]);
|
|
#endif
|
|
// check if gpio id is already in use
|
|
for(byte j = 0; j < Max_Sensors_GPIO; j++) {
|
|
if(config.system.sensor.gpio[i][j] == gpio) {
|
|
#ifdef DEBUG
|
|
Log.verbose(F("%s sensor.gpio[%d][%d](%d) == GPIO %d" CR), LogLoc, i, j, config.system.sensor.gpio[i][j], gpio);
|
|
#endif
|
|
used = true;
|
|
break;
|
|
} else {
|
|
used = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#ifdef DEBUG
|
|
Log.verbose(F("%s GPIO: %d, used: %d" CR), LogLoc, gpio, used);
|
|
#endif
|
|
return used;
|
|
}
|
|
|
|
|
|
/*
|
|
*
|
|
* Time related stuff
|
|
*
|
|
*/
|
|
|
|
/*
|
|
* NTP stuff
|
|
*/
|
|
|
|
void NTP_OffsetUpdate() {
|
|
const static char LogLoc[] PROGMEM = "[Core:NTP_OffsetUpdate]";
|
|
#ifdef DEBUG
|
|
Log.verbose(F("%s updating time with offset %dh" CR), LogLoc, config.system.ntpOffset);
|
|
#endif
|
|
timeClient.setTimeOffset(config.system.ntpOffset * 60 * 60);
|
|
if( (config.system.ntp == true) && (timeSrcStatus < 1) ) {
|
|
timeClient.update();
|
|
setTime(timeClient.getEpochTime());
|
|
}
|
|
#ifdef DEBUG
|
|
else {
|
|
Log.verbose(F("%s update requirements not met, timeSrcStatus %d > 0" CR), LogLoc, timeSrcStatus);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
bool NTP_Init() {
|
|
const static char LogLoc[] PROGMEM = "[Core:NTP_Init]";
|
|
bool result;
|
|
timeClient.begin();
|
|
NTP_OffsetUpdate();
|
|
// when NTP update failes (e.g. no connection to internet)
|
|
Log.notice(F("%s updating " ), LogLoc);
|
|
|
|
byte i = 0;
|
|
while( (! timeClient.isTimeSet()) && ( i < 5 )) {
|
|
timeClient.update();
|
|
delay(100);
|
|
Serial.print(".");
|
|
i++;
|
|
}
|
|
Serial.println();
|
|
|
|
if( ! timeClient.isTimeSet()) {
|
|
Log.error(F("%s FAILED" CR), LogLoc);
|
|
//Serial.println("!! [Core:NTP_Init] update failed");
|
|
result = false;
|
|
} else {
|
|
|
|
Log.notice(F("%s Success! Time: %s (%u), Offset: %d h" CR), LogLoc, timeClient.getFormattedTime(), timeClient.getEpochTime(), config.system.ntpOffset);
|
|
|
|
result = true;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
time_t NTP_getEpochTime() {
|
|
/* convert epoch from ntp (UL) to time_t */
|
|
const static char LogLoc[] PROGMEM = "[Core:NTP_getEpochTime]";
|
|
unsigned long epochTime = timeClient.getEpochTime();
|
|
Log.verbose(F("%s epochTime: %u" CR), LogLoc, epochTime);
|
|
return epochTime;
|
|
}
|
|
|
|
/*
|
|
* RTC stuff
|
|
*/
|
|
|
|
void RTC_Init() {
|
|
const static char LogLoc[] PROGMEM = "[Core:RTC_Init]";
|
|
|
|
switch(config.system.rtc) {
|
|
case RTCs_DS1307:
|
|
if (! rtc_ds1307.begin()) {
|
|
Log.warning(F("%s Couldn't find RTC DS1307" CR), LogLoc);
|
|
rtcError = true;
|
|
} else {
|
|
Log.notice(F("%s RTC DS1307 found" CR), LogLoc);
|
|
if (rtc_ds1307.isrunning()) {
|
|
Log.warning(F("%s RTC DS1307 is not running, let's set the time!" CR), LogLoc);
|
|
rtcError = true;
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
case RTCs_DS3231:
|
|
if (! rtc_ds3231.begin()) {
|
|
Log.warning(F("%s Couldn't find RTC DS3231" CR), LogLoc);
|
|
rtcError = true;
|
|
} else {
|
|
Log.notice(F("%s RTC DS3231 found" CR), LogLoc);
|
|
if (rtc_ds3231.lostPower()) {
|
|
Log.warning(F("%s RTC DS3231 lost power, let's set the time!" CR), LogLoc);
|
|
rtcError = true;
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
case RTCs_PCF8563:
|
|
if (! rtc_pcf8563.begin()) {
|
|
Log.warning(F("%s Couldn't find RTC PCF8563" CR), LogLoc);
|
|
rtcError = true;
|
|
|
|
} else {
|
|
Log.notice(F("%s RTC PCF8563 found" CR), LogLoc);
|
|
if (rtc_pcf8563.lostPower()) {
|
|
Log.warning(F("%s RTC PCF8563 lost power, let's set the time!" CR), LogLoc);
|
|
rtcError = true;
|
|
}
|
|
}
|
|
rtc_pcf8563.start();
|
|
break;
|
|
|
|
case RTCs_PCF8523:
|
|
if (! rtc_pcf8523.begin()) {
|
|
Log.warning(F("%s Couldn't find RTC PCF8523" CR), LogLoc);
|
|
rtcError = true;
|
|
|
|
} else {
|
|
Log.notice(F("%s RTC PCF8523 found" CR), LogLoc);
|
|
if ( ! rtc_pcf8523.initialized() || rtc_pcf8523.lostPower()) {
|
|
Log.warning(F("%s RTC PCF8523 lost power, let's set the time!" CR), LogLoc);
|
|
rtcError = true;
|
|
}
|
|
}
|
|
rtc_pcf8523.start();
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
time_t RTC_getEpochTime() {
|
|
/* convert epoch from RTC (UL) to time_t */
|
|
const static char LogLoc[] PROGMEM = "[Core:RTC_getEpochTime]";
|
|
unsigned long epochTime; // = timeClient.getEpochTime();
|
|
DateTime TimeNow;
|
|
switch(config.system.rtc) {
|
|
case RTCs_DS1307:
|
|
TimeNow = rtc_ds1307.now();
|
|
break;
|
|
|
|
case RTCs_DS3231:
|
|
TimeNow = rtc_ds3231.now();
|
|
break;
|
|
|
|
case RTCs_PCF8523:
|
|
TimeNow = rtc_pcf8523.now();
|
|
break;
|
|
|
|
case RTCs_PCF8563:
|
|
TimeNow = rtc_pcf8563.now();
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
epochTime = TimeNow.unixtime();
|
|
Log.verbose(F("%s epochTime: %u" CR), LogLoc, epochTime);
|
|
return epochTime;
|
|
}
|
|
|
|
void RTC_SaveTime() {
|
|
const static char LogLoc[] PROGMEM = "[Core:RTC_SaveTime]";
|
|
unsigned int TimeNow = now();
|
|
bool saved = true;
|
|
|
|
switch(config.system.rtc) {
|
|
case RTCs_DS1307:
|
|
rtc_ds1307.adjust(DateTime(TimeNow));
|
|
break;
|
|
|
|
case RTCs_DS3231:
|
|
rtc_ds3231.adjust(DateTime(TimeNow));
|
|
break;
|
|
|
|
case RTCs_PCF8523:
|
|
rtc_pcf8523.adjust(DateTime(TimeNow));
|
|
break;
|
|
|
|
case RTCs_PCF8563:
|
|
rtc_pcf8563.adjust(DateTime(TimeNow));
|
|
break;
|
|
|
|
default:
|
|
/* only when not in case, we consider not saved */
|
|
saved = false;
|
|
break;
|
|
}
|
|
#ifdef DEBUG
|
|
if(saved == true)
|
|
Log.verbose(F("%s Time (%u) saved to %S" CR), LogLoc, TimeNow, RTCs_descr[config.system.rtc]);
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* Main Time stuff
|
|
*
|
|
*/
|
|
|
|
String Str_TimeNow() {
|
|
/* simple helper function to return a String with HH:MM:SS */
|
|
String str_time;
|
|
if(hour() < 10)
|
|
str_time += F("0");
|
|
str_time += hour();
|
|
str_time += F(":");
|
|
if(minute() < 10)
|
|
str_time += F("0");
|
|
str_time += minute();
|
|
str_time += F(":");
|
|
if(second() < 10)
|
|
str_time += F("0");
|
|
str_time += second();
|
|
return str_time;
|
|
}
|
|
|
|
String Str_DateNow() {
|
|
/* simple helper function to return a String with HH:MM:SS */
|
|
String str_date;
|
|
if(day() < 10)
|
|
str_date += F("0");
|
|
str_date += day();
|
|
str_date += F(".");
|
|
if(month() < 10)
|
|
str_date += F("0");
|
|
str_date += month();
|
|
str_date += F(".");
|
|
str_date += year();
|
|
return str_date;
|
|
}
|
|
|
|
String Str_Epoch2Date(unsigned long epochTime) {
|
|
String dateStr;
|
|
byte Day = day(epochTime);
|
|
byte Month = month(epochTime);
|
|
unsigned int Year = year(epochTime);
|
|
|
|
dateStr = Year;
|
|
dateStr += "-";
|
|
|
|
if(Month < 10) {
|
|
dateStr += "0";
|
|
dateStr += Month;
|
|
} else {
|
|
dateStr += Month;
|
|
}
|
|
|
|
dateStr += "-";
|
|
|
|
if(Day < 10) {
|
|
dateStr += "0";
|
|
dateStr += Day;
|
|
} else {
|
|
dateStr += Day;
|
|
}
|
|
|
|
return dateStr;
|
|
}
|
|
|
|
|
|
/* Those two functions should be in LittleFS file, but because dependency and lazyness */
|
|
void Time2FS_Save() {
|
|
const static char LogLoc[] PROGMEM = "[Core:Time2FS_Save]";
|
|
unsigned long TimeNow;
|
|
#ifdef ESP8266
|
|
File file = LittleFS.open(TIME2FS, "w");
|
|
#endif
|
|
|
|
#ifdef ESP32
|
|
fs::FS &fs = LittleFS;
|
|
File file = fs.open(TIME2FS, FILE_WRITE);
|
|
#endif
|
|
|
|
if (!file) {
|
|
Log.error(F("%s FAILED to open file for writing: %s" CR), LogLoc, TIME2FS);
|
|
return;
|
|
}
|
|
TimeNow = now();
|
|
if (!file.print(TimeNow)) {
|
|
Log.error(F("%s writing time FAILED" CR), LogLoc);
|
|
}
|
|
#ifdef DEBUG
|
|
else {
|
|
Log.verbose(F("%s time (%u) written: %s" CR), LogLoc, TimeNow, TIME2FS);
|
|
}
|
|
#endif
|
|
//delay(2000); // Make sure the CREATE and LASTWRITE times are different
|
|
file.close();
|
|
|
|
}
|
|
|
|
void Time2FS_Read() {
|
|
const static char LogLoc[] PROGMEM = "[Core:Time2FS_Read]";
|
|
String TimeRead;
|
|
#ifdef ESP8266
|
|
File file = LittleFS.open(TIME2FS, "r");
|
|
#endif
|
|
|
|
#ifdef ESP32
|
|
fs::FS &fs = LittleFS;
|
|
File file = fs.open(TIME2FS);
|
|
#endif
|
|
|
|
if (!file) {
|
|
Log.error(F("%s FAILED to open time file: %s" CR), LogLoc, TIME2FS);
|
|
return;
|
|
}
|
|
|
|
//Log.notice(F("%s file content: %s" CR), LogLoc, TIME2FS);
|
|
//Log.notice(F("%s ----------" CR), LogLoc);
|
|
//while (file.available()) { Serial.write(file.read()); }
|
|
//Log.notice(F("%s ----------" CR), LogLoc);
|
|
|
|
while (file.available()) { TimeRead = file.readString(); }
|
|
#ifdef DEBUG
|
|
Log.verbose(F("%s applying time (%u) to system" CR), LogLoc, TimeRead.toInt());
|
|
#endif
|
|
setTime(TimeRead.toInt());
|
|
file.close();
|
|
}
|
|
|
|
|
|
/* Time_Init - Main function for time initialization */
|
|
void Time_Init() {
|
|
const static char LogLoc[] PROGMEM = "[Core:Time_Init]";
|
|
|
|
/* first check if RTC is configured and init if */
|
|
if(config.system.rtc > 0)
|
|
RTC_Init();
|
|
|
|
/* check if ntp is enabled */
|
|
if(config.system.ntp == true) {
|
|
Log.notice(F("%s Using NTP" CR), LogLoc);
|
|
/* initialize NTP and check */
|
|
if(NTP_Init()) {
|
|
#ifdef DEBUG
|
|
Log.verbose(F("%s set NTP as TimeLib SyncProvider" CR), LogLoc);
|
|
#endif
|
|
setSyncProvider(NTP_getEpochTime);
|
|
|
|
/* when having a RTC, update it now with new not time */
|
|
if(config.system.rtc > 0) {
|
|
RTC_SaveTime();
|
|
}
|
|
|
|
if(config.system.time2fs == true) {
|
|
true;
|
|
//writeFile(TIME2FS, now());
|
|
Time2FS_Save();
|
|
}
|
|
|
|
} else if((config.system.rtc > 0) && (rtcError == false)) {
|
|
#ifdef DEBUG
|
|
Log.verbose(F("%s set RTC as TimeLib SyncProvider" CR), LogLoc);
|
|
#endif
|
|
setSyncProvider(RTC_getEpochTime);
|
|
//setTime(RTC_getEpochTime());
|
|
timeSrcStatus = 1;
|
|
} else {
|
|
Log.warning(F("%s no TimeLib SyncProvider available. Reading last Timestamp from flash memory" CR), LogLoc);
|
|
Time2FS_Read();
|
|
timeSrcStatus = 2;
|
|
}
|
|
}
|
|
/* how often TimeLib should sync with source
|
|
* 10 minutes is ok i guess
|
|
*/
|
|
setSyncInterval(600);
|
|
|
|
Log.notice(F("%s Time initialization done. Fallback status %d, %s %s (%u)" CR), LogLoc, timeSrcStatus, Str_DateNow(), Str_TimeNow(), now());
|
|
|
|
|
|
}
|
|
|
|
|
|
/*
|
|
* semi random string generator
|
|
* https://arduino.stackexchange.com/a/86659
|
|
*/
|
|
const byte RANDOMSTRING_MAX = 16;
|
|
const char * RandomString(){
|
|
/* Change to allowable characters */
|
|
const char possible[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!?_-=()%&.,;:";
|
|
static char str[RANDOMSTRING_MAX + 1];
|
|
for(byte p = 0, i = 0; i < RANDOMSTRING_MAX; i++){
|
|
byte r = random(0, strlen(possible));
|
|
str[p++] = possible[r];
|
|
}
|
|
str[RANDOMSTRING_MAX] = '\0';
|
|
return str;
|
|
}
|
|
|
|
|
|
/*
|
|
* Timescale()
|
|
* returns timescale unit (seconds, minutes, hours,...) in seconds
|
|
*/
|
|
|
|
unsigned long Timescale(byte unit) {
|
|
switch(unit) {
|
|
case TIMESCALE_SECOND:
|
|
return 1;
|
|
break;
|
|
|
|
case TIMESCALE_MINUTE:
|
|
return 60;
|
|
break;
|
|
|
|
case TIMESCALE_HOUR:
|
|
//return 60*60;
|
|
return 3600;
|
|
break;
|
|
|
|
case TIMESCALE_DAY:
|
|
//return 60*60*24;
|
|
return 86400;
|
|
break;
|
|
|
|
case TIMESCALE_WEEK:
|
|
//return 60*60*24*7;
|
|
return 604800;
|
|
break;
|
|
|
|
case TIMESCALE_MONTH:
|
|
//return 60*60*24*7*4;
|
|
return 2419200;
|
|
break;
|
|
|
|
case TIMESCALE_YEAR:
|
|
//return 60*60*24*7*4*52;
|
|
return 125798400;
|
|
break;
|
|
|
|
default:
|
|
return 0;
|
|
break;
|
|
}
|
|
}
|