diff --git a/src/Scroller.cpp b/src/Scroller.cpp new file mode 100644 index 0000000..e6a2453 --- /dev/null +++ b/src/Scroller.cpp @@ -0,0 +1,140 @@ +#include "Scroller.h" + +Scroller::Scroller(int max_loops) +{ + _max_loops = max_loops; +} + +void Scroller::SetMatrix(Adafruit_NeoMatrix *matrix) +{ + this->_matrix = matrix; +} + +int Scroller::GetNumberOfLines() +{ + return this->_nlines; +} + +int Scroller::AddLine(String line) +{ + this->_Lines[this->_nlines] = line; + return _nlines++; +} + +int Scroller::AddLines(String* lines,int nlines) +{ + for (int i=0; i < nlines; i++) { + _Lines[i] = lines[i]; + } + _nlines = nlines; + + return _nlines; +} + +bool Scroller::ReplaceLine(int number,String line) +{ + _Lines[number] = line; + return true; +} + +void Scroller::DeleteLines() +{ + _nlines = 0; +} + +int Scroller::Scroll(int tick) +{ + int mx = 32; + String line; + + line = _Lines[_lp]; + + if (line.length() * 6 > mx) + { + if (_x < line.length() * 6 - mx) + { + _matrix->fillScreen(0); + _matrix->setCursor(-_x, _y); + _matrix->setTextColor(_colors[1]); + _matrix->print(line); + _matrix->show(); + if (_x++ == 0) { + return 3; + } + else { + return 1; + } + } + _lp++; + _x = 0; + return 10; + } + else + { + _matrix->fillScreen(0); + _matrix->setCursor(_x, _y); + _matrix->setTextColor(_colors[1]); + _matrix->print(line); + _matrix->show(); + _lp++; + return 30; + } +} + +bool Scroller::TurnOn() +{ + _off = false; + _lp = 0; + _x = 0; + _y = 0; + _nscrolled = 0; + _delayTicks = 0; + + return true; +} + +void Scroller::TurnOff() +{ + _matrix->fillScreen(0); + _matrix->print(""); + _matrix->show(); + _off = true; +} + +void Scroller::SetColors(uint16_t *colors) +{ + _colors = colors; +} + +void Scroller::loop(int tick) +{ + if (_off) return; + + int delta = tick - _tick; + + if (_lp == _nlines) { + _lp =0; + _nscrolled++; + _delayTicks = 50; + } + + if (TimesScrolled() == _max_loops) { + TurnOff(); + return; + } + + if (_delayTicks - delta <= 0) _delayTicks = Scroll(tick); + else _delayTicks -= delta; + + if (_delayTicks < 0) _delayTicks = 0; +} + +int Scroller::TimesScrolled() +{ + return _nscrolled; +} + +Scroller::~Scroller() +{ +} + diff --git a/src/Scroller.h b/src/Scroller.h new file mode 100644 index 0000000..b89211a --- /dev/null +++ b/src/Scroller.h @@ -0,0 +1,44 @@ +#ifndef SCROLLER_H +#define SCROLLER_H +#include +#include +#include +#include +#include + +#define SCROLLER_TICK_TIME 100 //ms + +class Scroller +{ +private: + /* data */ + const static int MAX_LINES = 10; + String _Lines[MAX_LINES]; + int _nlines = 0; + int _lp = 0; + int _tick=0; + int _delayTicks = 0; + int _nscrolled = 0; + int _x = 0, _y = 0; + Adafruit_NeoMatrix *_matrix; + uint16_t *_colors; + int _max_loops; + bool _off = true; + +public: + Scroller(int max_loops); + bool TurnOn(); + void SetColors(uint16_t *colors); + void SetMatrix(Adafruit_NeoMatrix *matrix); + int AddLine(String line); + int AddLines(String* lines,int nlines); + void DeleteLines(); + bool ReplaceLine(int number,String line); + int GetNumberOfLines(); + int Scroll(int tick); + int TimesScrolled(); + void TurnOff(); + void loop(int tick); + ~Scroller(); +}; +#endif \ No newline at end of file diff --git a/src/TimeZone.cpp b/src/TimeZone.cpp new file mode 100644 index 0000000..d3b04ad --- /dev/null +++ b/src/TimeZone.cpp @@ -0,0 +1,209 @@ +#include "TimeZone.h" + +void TimeZone::ruleDST(const char* tzName, int8_t week, int8_t wday, int8_t month, int8_t hour, int tzOffset) { + strcpy(dstStart.tzName, tzName); + dstStart.week = week; + dstStart.wday = wday; + dstStart.month = month; + dstStart.hour = hour; + dstStart.tzOffset = tzOffset; + } + +const char* TimeZone::ruleDST() { + if(dstZone) { + return ctime(&dstTime); + } + else return RULE_DST_MESSAGE; + } + +void TimeZone::ruleSTD(const char* tzName, int8_t week, int8_t wday, int8_t month, int8_t hour, int tzOffset) { + strcpy(dstEnd.tzName, tzName); + dstEnd.week = week; + dstEnd.wday = wday; + dstEnd.month = month; + dstEnd.hour = hour; + dstEnd.tzOffset = tzOffset; + } + +const char* TimeZone::ruleSTD() { + if(dstZone) { + return ctime(&stdTime); + } + else return RULE_STD_MESSAGE; + } + +const char* TimeZone::tzName() { + if (dstZone) { + if (summerTime()) return dstStart.tzName; + else return dstEnd.tzName; + } + return GMT_MESSAGE; // TODO add timeZoneOffset + } + +void TimeZone::timeZone(int8_t tzHours, int8_t tzMinutes) { + this->tzHours = tzHours; + this->tzMinutes = tzMinutes; + timezoneOffset = tzHours * 3600; + if (tzHours < 0) { + timezoneOffset -= tzMinutes * 60; + } + else { + timezoneOffset += tzMinutes * 60; + } + } + +void TimeZone::isDST(bool dstZone) { + this->dstZone = dstZone; + } + +bool TimeZone::isDST() { + return summerTime(); + } + +time_t TimeZone::epoch() { + currentTime(); + return utcCurrent; + } + +void TimeZone::currentTime() { + utcCurrent = utcTime; + if (dstZone) { + if (summerTime()) { + local = utcCurrent + dstOffset + timezoneOffset; + current = gmtime(&local); + } + else { + local = utcCurrent + timezoneOffset; + current = gmtime(&local); + } + if ((current->tm_year + 1900) > yearDST) beginDST(); + } + else { + local = utcCurrent + timezoneOffset; + current = gmtime(&local); + } + } + +int16_t TimeZone::year() { + currentTime(); + return current->tm_year + 1900; + } + +int8_t TimeZone::month() { + currentTime(); + return current->tm_mon + 1; + } + +int8_t TimeZone::day() { + currentTime(); + return current->tm_mday; + } + +int8_t TimeZone::weekDay() { + currentTime(); + return current->tm_wday; + } + +int8_t TimeZone::hours() { + currentTime(); + return current->tm_hour; + } + +int8_t TimeZone::minutes() { + currentTime(); + return current->tm_min; + } + +int8_t TimeZone::seconds() { + currentTime(); + return current->tm_sec; + } + +void TimeZone::begin() { + if (dstZone) { + timezoneOffset = dstEnd.tzOffset * SECS_PER_MINUTES; + dstOffset = (dstStart.tzOffset - dstEnd.tzOffset) * SECS_PER_MINUTES; + currentTime(); + beginDST(); + } + } + +TimeZone& TimeZone::setUTCTime(time_t t) { + utcTime = t; + begin(); + return *this; +} + +TimeZone& TimeZone::setLocalTime(time_t t) { + utcTime = t; + begin(); + if (dstZone) { + if ((t > utcDST) && (t <= utcSTD)) { + t -= dstOffset + timezoneOffset;; + } else { + t -= timezoneOffset; + } + utcTime = t; + currentTime(); + } + return *this; +} + +time_t TimeZone::getLocalTime() { + currentTime(); + return local; +} + +time_t TimeZone::getUTCTime() { + currentTime(); + return utcTime; +} + +char* TimeZone::formattedTime(const char *format) { + currentTime(); + memset(timeString, 0, sizeof(timeString)); + strftime(timeString, sizeof(timeString), format, current); + return timeString; + } + +void TimeZone::beginDST() { + dstTime = calcDateDST(dstStart, current->tm_year + 1900); + utcDST = dstTime - (dstEnd.tzOffset * SECS_PER_MINUTES); + stdTime = calcDateDST(dstEnd, current->tm_year + 1900); + utcSTD = stdTime - (dstStart.tzOffset * SECS_PER_MINUTES); + yearDST = current->tm_year + 1900; + } + +time_t TimeZone::calcDateDST(struct ruleDST rule, int year) { + uint8_t month = rule.month; + uint8_t week = rule.week; + if (week == 0) { + if (month++ > 11) { + month = 0; + year++; + } + week = 1; + } + + struct tm tm; + tm.tm_hour = rule.hour; + tm.tm_min = 0; + tm.tm_sec = 0; + tm.tm_mday = 1; + tm.tm_mon = month; + tm.tm_year = year - 1900; + time_t t = mktime(&tm); + + t += ((rule.wday - tm.tm_wday + 7) % 7 + (week - 1) * 7 ) * SECS_PER_DAY; + if (rule.week == 0) t -= 7 * SECS_PER_DAY; + return t; + } + +bool TimeZone::summerTime() { + if ((utcCurrent > utcDST) && (utcCurrent <= utcSTD)) { + return true; + } + else { + return false; + } + } \ No newline at end of file diff --git a/src/TimeZone.h b/src/TimeZone.h new file mode 100644 index 0000000..aab1ce0 --- /dev/null +++ b/src/TimeZone.h @@ -0,0 +1,206 @@ +#ifndef TIMEZONE_H +#define TIMEZONE_H + +#include "Arduino.h" +#include + +#ifndef NTP_H +#define SEVENTYYEARS 2208988800UL +#define NTP_PACKET_SIZE 48 +#define NTP_DEFAULT_LOCAL_PORT 123 +#define SECS_PER_MINUTES 60 +#define SECS_PER_DAY 86400 + +#define GMT_MESSAGE "GMT +/- offset" +#define RULE_DST_MESSAGE "no DST rule" +#define RULE_STD_MESSAGE "no STD rule" + +enum week_t {Last, First, Second, Third, Fourth}; +enum dow_t {Sun, Mon, Tue, Wed, Thu, Fri, Sat}; +enum month_t {Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec}; +#endif + +class TimeZone { + public: + + struct ruleDST { + char tzName[6]; // five chars max + int8_t week; // First, Second, Third, Fourth, or Last week of the month + int8_t wday; // day of week, 0 = Sun, 2 = Mon, ... 6 = Sat + int8_t month; // 0 = Jan, 1 = Feb, ... 11=Dec + int8_t hour; // 0 - 23 + int tzOffset; // offset from UTC in minutes + }; + + /** + * @brief set the rule for DST (daylight saving time) + * start date of DST + * + * @param tzName name of the time zone + * @param week Last, First, Second, Third, Fourth (0 - 4) + * @param wday Sun, Mon, Tue, Wed, Thu, Fri, Sat (0 - 7) + * @param month Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec (0 -11) + * @param hour the local hour when rule chages + * @param tzOffset sum of summertime and timezone offset + */ + void ruleDST(const char* tzName, int8_t week, int8_t wday, int8_t month, int8_t hour, int tzOffset); + + /** + * @brief get the DST time as a ctime string + * + * @return char* time string + */ + const char* ruleDST(); + + /** + * @brief set the rule for STD (standard day) + * end date of DST + * + * @param tzName name of the time zone + * @param week Last, First, Second, Third, Fourth (0 - 4) + * @param wday Sun, Mon, Tue, Wed, Thu, Fri, Sat (0 - 7) + * @param month Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec (0 -11) + * @param hour the local hour when rule chages + * @param tzOffset timezone offset + */ + void ruleSTD(const char* tzName, int8_t week, int8_t wday, int8_t month, int8_t hour, int tzOffset); + + /** + * @brief get the STD time as a ctime string + * + * @return char* time string + */ + const char* ruleSTD(); + + /** + * @brief get the name of the timezone + * + * @return char* name of the timezone + */ + const char* tzName(); + + /** + * @brief set the timezone manually + * this should used if there is no DST! + * + * @param tzHours + * @param tzMinutes + */ + void timeZone(int8_t tzHours, int8_t tzMinutes = 0); + + /** + * @brief set daylight saving manually! + * use in conjunction with timeZone, when there is no DST! + * + * @param dstZone + */ + void isDST(bool dstZone); + + /** + * @brief returns the DST state + * + * @return int 1 if summertime, 0 no summertime + */ + bool isDST(); + + /** + * @brief get the Unix epoch timestamp + * + * @return time_t timestamp + */ + time_t epoch(); + + /** + * @brief get the year + * + * @return int year + */ + int16_t year(); + + /** + * @brief get the month + * + * @return int month, 1 = january + */ + int8_t month(); + + /** + * @brief get the day of a month + * + * @return int day + */ + int8_t day(); + + /** + * @brief get the day of a week + * + * @return int day of the week, 0 = sunday + */ + int8_t weekDay(); + + /** + * @brief get the hour of the day + * + * @return int + */ + int8_t hours(); + + /** + * @brief get the minutes of the hour + * + * @return int minutes + */ + int8_t minutes(); + + /** + * @brief get the seconds of a minute + * + * @return int seconds + */ + int8_t seconds(); + + /** + * @brief returns a formatted string + * + * @param format for strftime + * @return char* formated time string + */ + + time_t fromUTC(time_t t); + time_t toUTC(time_t t); + time_t toLocalTime(time_t t); + + time_t calcDateDST(struct ruleDST rule, int year); + bool summerTime(time_t t); + bool summerTime(); + + TimeZone& setUTCTime(time_t t); + TimeZone& setLocalTime(time_t t); + time_t getLocalTime(); + time_t getUTCTime(); + void begin(); + char* formattedTime(const char *format); + + private: + time_t utcCurrent = 0; + time_t local = 0; + struct tm *current; + uint32_t interval = 60000; + uint32_t lastUpdate = 0; + uint8_t tzHours = 0; + uint8_t tzMinutes = 0; + int32_t timezoneOffset; + int16_t dstOffset = 0; + bool dstZone = true; + uint32_t utcTime = 0; + time_t utcSTD, utcDST; + time_t dstTime, stdTime; + uint16_t yearDST; + char timeString[64]; + struct ruleDST dstStart, dstEnd; + + void currentTime(); + void beginDST(); +}; + +#endif \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 156aa45..2f17aec 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,4 +1,3 @@ -// change next line to use with another board/shield #include #include #include @@ -9,19 +8,22 @@ #include #include -#include #include #include #include #include +#include +#include "ESP32TimerInterrupt.h" +#include "Scroller.h" #include "calendar.h" +#include "TimeZone.h" #ifndef PSTR #define PSTR // Make Arduino Due happy #endif #define COMMAND_PARAMETER_LENGTH 30 - +#define MAX_TIMES_SCROLLED 5 #define PIN 15 #include @@ -49,7 +51,7 @@ enum Lines_names String Lines[LAST_LINE]; Adafruit_NeoMatrix *matrix; -const uint16_t colors[] = { +uint16_t colors[] = { Adafruit_NeoMatrix::Color(255, 0, 0), Adafruit_NeoMatrix::Color(0, 255, 0), Adafruit_NeoMatrix::Color(0, 0, 255)}; WiFiUDP ntpUDP; @@ -74,10 +76,53 @@ struct timer_def { int when; int h; int m; int d; int mh; int y; }; -int ntimers; +int ntimers,nextTimer = -1; +int nts; + +DateTimeClass nextAlarmDateTime; #define MAX_TIMERS 10 timer_def Timers[MAX_TIMERS]; +Scroller scroller(MAX_TIMES_SCROLLED); + +unsigned long last_millis; +bool showText = false; +int scrolledTicker = 0; +bool updateNtpTime = false; + +ESP32Timer ITimer0(0); +ESP32Timer ITimer1(1); +ESP32Timer ITimer2(2); + +#define TIMER0_INTERVAL_MS 1000 +#define TIMER1_INTERVAL_MS 100 +#define TIMER2_INTERVAL_MS 60000 + +void IRAM_ATTR TimerCheckTime0(void) +{ + unsigned long m = millis(), delta; + + delta = (m - last_millis)/1000; + + DateTime += delta; + + if (nextAlarmDateTime <= DateTime && nextTimer != -1) { + showText = true; + scroller.TurnOn(); + } + + last_millis = m; +} + +void IRAM_ATTR TimerScrollTime1(void) +{ + scrolledTicker++; +} + +void IRAM_ATTR TimerUpdateNTP2(void) +{ + updateNtpTime = true; +} String getValue(String data, char separator, int index) { @@ -212,7 +257,7 @@ void read_config() { s += (char)file.read(); } - Serial.print(s); + Serial.println(s); file.close(); @@ -258,7 +303,16 @@ void read_config() void calculate_timers() { timer_def *t; + int timer = -1; struct tm tm; + DateTimeClass AlarmTime,NextAlarm; + time_t alarm; + TimeZone TZ; + + Serial.println("---CALC TIMERS---"); + TZ.ruleDST("CEST", Last, Sun, Mar, 2, 120); // last sunday in march 2:00, timetone +120min (+1 GMT + 1h summertime offset) + TZ.ruleSTD("CET", Last, Sun, Oct, 3, 60); // last sunday in october 3:00, timezone +60min (+1 GMT) + for (int i=0; id !=0) { tm.tm_mday = t->d; - tm.tm_mon = t->mh; - tm.tm_year = t->y; + tm.tm_mon = t->mh - 1; + tm.tm_year = t->y - 1900; } else { tm.tm_mday = 0; tm.tm_mon = 0; @@ -279,15 +333,67 @@ void calculate_timers() if (t->d == 0) { if (t->when == WORK_DAYS) { + int offset=24*3600; + int wday; + + tm.tm_mday = DateTime.getParts().getMonthDay(); + tm.tm_mon = DateTime.getParts().getMonth(); + tm.tm_year = DateTime.getParts().getYear() - 1900; + wday = DateTime.getParts().getWeekDay(); + + if (wday == 0) offset = 3600*24; + if (wday == 6) offset = 2*3600*24; + + alarm = mktime(&tm); + TZ.setLocalTime(alarm); + + AlarmTime.setTime(TZ.getUTCTime()); + + if (AlarmTime > DateTime && wday == 5 ) offset = 3*3600*24; + + TZ.setLocalTime(alarm + offset); + AlarmTime.setTime(TZ.getUTCTime()); } else if (t->when == WEEKENDS) { + int offset=24*3600; + int wday; - } else if (t->when == TOMOROW ) { + tm.tm_mday = DateTime.getParts().getMonthDay(); + tm.tm_mon = DateTime.getParts().getMonth(); + tm.tm_year = DateTime.getParts().getYear() - 1900; + wday = DateTime.getParts().getWeekDay(); + + if (wday >= 1 && wday <= 5) offset = (7-wday) *3600*24; + alarm = mktime(&tm); + TZ.setLocalTime(alarm); + AlarmTime.setTime(TZ.getUTCTime()); + + if (AlarmTime > DateTime && wday == 5 ) offset = 3*3600*24; + + TZ.setLocalTime(alarm + offset); + AlarmTime.setTime(TZ.getUTCTime()); + + } else if (t->when == ONCE ) { + alarm = mktime(&tm); + TZ.setLocalTime(alarm); + AlarmTime.setTime(TZ.getUTCTime()); + } + + if (AlarmTime > DateTime && (AlarmTime < NextAlarm || timer == -1)) { + timer = i; + NextAlarm = AlarmTime; } } } + + if (timer != -1) { + nextTimer = timer; + nextAlarmDateTime = NextAlarm; + Serial.println(DateTime.toString()); + Serial.println(nextAlarmDateTime.toString()); + } } void setup() @@ -304,6 +410,22 @@ void setup() return; } + if (ITimer0.attachInterruptInterval(TIMER0_INTERVAL_MS * 1000, TimerCheckTime0)) + Serial.println("Starting ITimer0 OK, millis() = " + String(millis())); + else + Serial.println("Can't set ITimer0. Select another freq. or timer"); + + // Interval in microsecs + if (ITimer1.attachInterruptInterval(TIMER1_INTERVAL_MS * 1000, TimerScrollTime1)) + Serial.println("Starting ITimer1 OK, millis() = " + String(millis())); + else + Serial.println("Can't set ITimer1. Select another freq. or timer"); + + if (ITimer2.attachInterruptInterval(TIMER2_INTERVAL_MS * 1000, TimerUpdateNTP2)) + Serial.println("Starting ITimer2 OK, millis() = " + String(millis())); + else + Serial.println("Can't set ITimer2. Select another freq. or timer"); + // -- Set up required URL handlers on the web server. server.on("/", handleRoot); @@ -313,42 +435,11 @@ void setup() setup_matrix(); read_config(); + + scroller.SetColors(colors); + scroller.SetMatrix(matrix); } -void scroll_line(String line) -{ - int x = 0, mx = 32; - int y = 0; - - if (line.length() * 6 > mx) - { - for (x = 0; x < line.length() * 6 - mx; x++) - { - matrix->fillScreen(0); - matrix->setCursor(-x, y); - matrix->setTextColor(colors[1]); - matrix->print(line); - matrix->show(); - if (x == 0) - delay(300); - else - delay(100); - } - delay(1000); - } - else - { - matrix->fillScreen(0); - matrix->setCursor(x, y); - matrix->setTextColor(colors[1]); - matrix->print(line); - matrix->show(); - delay(3000); - } -} - - - void handleRoot() { // -- Let IotWebConf test and handle captive portal requests. @@ -385,32 +476,48 @@ void loop() unsigned long n = 0; // -- doLoop should be called as frequently as possible. - iotWebConf.doLoop(); if (WiFi.status() == WL_CONNECTED) { if (initialized == false) { + Serial.println("---INIT---"); ntp.ruleDST("CEST", Last, Sun, Mar, 2, 120); // last sunday in march 2:00, timetone +120min (+1 GMT + 1h summertime offset) ntp.ruleSTD("CET", Last, Sun, Oct, 3, 60); // last sunday in october 3:00, timezone +60min (+1 GMT) ntp.begin(); + sec = ntp.epoch(); + DateTime.setTime(sec); + initialized = true; } - sec = ntp.epoch(); - DateTime.setTime(sec); - if (n++ % 25 == 0) - Lines[WEATHER_LINE] = get_weather_line(); + if (updateNtpTime) { + Serial.println("---NTP UPDATE---"); + ntp.update(); + sec = ntp.epoch(); + DateTime.setTime(sec); + calculate_timers(); + updateNtpTime = false; + - Lines[TIME_LINE] = get_time_line(); - Lines[DATE_LINE] = get_date_line(); - Lines[NAMES_LINE] = get_names_line(); + if (n++ % 10 == 0) + Lines[WEATHER_LINE] = get_weather_line(); - for (int i = 0; i < LAST_LINE; i++) - { - Serial.println(Lines[i]); - scroll_line(Lines[i]); - iotWebConf.doLoop(); + Lines[TIME_LINE] = get_time_line(); + Lines[DATE_LINE] = get_date_line(); + Lines[NAMES_LINE] = get_names_line(); + + scroller.AddLines(Lines,LAST_LINE); + + } + + if (showText) { + Serial.println("---SHOW TEXT----"); + scroller.loop(scrolledTicker); + + if (scroller.TimesScrolled() > MAX_TIMES_SCROLLED) showText = false; } } + + iotWebConf.doLoop(); } \ No newline at end of file