CanGrow/include/CanGrow_Core.h

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;
}
}