Version 0.9

This commit is contained in:
2020-06-01 19:20:52 +02:00
parent 1b38f5b129
commit da0728d671
5 changed files with 762 additions and 56 deletions

140
src/Scroller.cpp Normal file
View File

@@ -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()
{
}

44
src/Scroller.h Normal file
View File

@@ -0,0 +1,44 @@
#ifndef SCROLLER_H
#define SCROLLER_H
#include <Adafruit_GFX.h>
#include <Adafruit_NeoMatrix.h>
#include <Adafruit_NeoPixel.h>
#include <Arduino.h>
#include <ESP32Ticker.h>
#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

209
src/TimeZone.cpp Normal file
View File

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

206
src/TimeZone.h Normal file
View File

@@ -0,0 +1,206 @@
#ifndef TIMEZONE_H
#define TIMEZONE_H
#include "Arduino.h"
#include <time.h>
#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

View File

@@ -1,4 +1,3 @@
// change next line to use with another board/shield
#include <WiFi.h>
#include <SPI.h>
#include <Adafruit_GFX.h>
@@ -9,19 +8,22 @@
#include <HTTPClient.h>
#include <ArduinoJson.h>
#include <DateTime.h>
#include <IotWebConf.h>
#include <NTP.h>
#include <SPIFFS.h>
#include <time.h>
#include <DateTime.h>
#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 <WiFiUdp.h>
@@ -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; i<ntimers ; i++)
{
t = &Timers[i];
@@ -269,8 +323,8 @@ void calculate_timers()
if (t->d !=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();
}