373 lines
12 KiB
C++
373 lines
12 KiB
C++
#include "options.h"
|
|
#include "Arduino.h"
|
|
#include "timekeeper.h"
|
|
#include "config.h"
|
|
#include "network.h"
|
|
#include "display.h"
|
|
#include "player.h"
|
|
#include "netserver.h"
|
|
#include "rtcsupport.h"
|
|
#include "../displays/tools/l10n.h"
|
|
#include "../pluginsManager/pluginsManager.h"
|
|
#ifdef USE_NEXTION
|
|
#include "../displays/nextion.h"
|
|
#endif
|
|
#if DSP_MODEL==DSP_DUMMY
|
|
#define DUMMYDISPLAY
|
|
#endif
|
|
|
|
#if RTCSUPPORTED
|
|
//#define TIME_SYNC_INTERVAL 24*60*60*1000
|
|
#define TIME_SYNC_INTERVAL config.store.timeSyncIntervalRTC*60*60*1000
|
|
#else
|
|
#define TIME_SYNC_INTERVAL config.store.timeSyncInterval*60*1000
|
|
#endif
|
|
#define WEATHER_SYNC_INTERVAL config.store.weatherSyncInterval*60*1000
|
|
|
|
#define SYNC_STACK_SIZE 1024 * 4
|
|
#define SYNC_TASK_CORE 0
|
|
#define SYNC_TASK_PRIORITY 3
|
|
#define WEATHER_STRING_L 254
|
|
|
|
#ifdef HEAP_DBG
|
|
void printHeapFragmentationInfo(const char* title){
|
|
size_t freeHeap = heap_caps_get_free_size(MALLOC_CAP_DEFAULT);
|
|
size_t largestBlock = heap_caps_get_largest_free_block(MALLOC_CAP_DEFAULT);
|
|
float fragmentation = 100.0 * (1.0 - ((float)largestBlock / (float)freeHeap));
|
|
Serial.printf("\n****** %s ******\n", title);
|
|
Serial.printf("* Free heap: %u bytes\n", freeHeap);
|
|
Serial.printf("* Largest free block: %u bytes\n", largestBlock);
|
|
Serial.printf("* Fragmentation: %.2f%%\n", fragmentation);
|
|
Serial.printf("*************************************\n\n");
|
|
}
|
|
#define HEAP_INFO() printHeapFragmentationInfo(__PRETTY_FUNCTION__)
|
|
#else
|
|
#define HEAP_INFO()
|
|
#endif
|
|
|
|
TimeKeeper timekeeper;
|
|
|
|
void _syncTask(void *pvParameters) {
|
|
if (timekeeper.forceWeather && timekeeper.forceTimeSync) {
|
|
timekeeper.timeTask();
|
|
timekeeper.weatherTask();
|
|
}
|
|
else if (timekeeper.forceWeather) {
|
|
timekeeper.weatherTask();
|
|
}
|
|
else if (timekeeper.forceTimeSync) {
|
|
timekeeper.timeTask();
|
|
}
|
|
timekeeper.busy = false;
|
|
vTaskDelete(NULL);
|
|
}
|
|
|
|
TimeKeeper::TimeKeeper(){
|
|
busy = false;
|
|
forceWeather = true;
|
|
forceTimeSync = true;
|
|
_returnPlayerTime = _doAfterTime = 0;
|
|
weatherBuf=NULL;
|
|
#if (DSP_MODEL!=DSP_DUMMY || defined(USE_NEXTION)) && !defined(HIDE_WEATHER)
|
|
weatherBuf = (char *) malloc(sizeof(char) * WEATHER_STRING_L);
|
|
memset(weatherBuf, 0, WEATHER_STRING_L);
|
|
#endif
|
|
}
|
|
|
|
bool TimeKeeper::loop0(){ // core0 (display)
|
|
uint32_t currentTime = millis();
|
|
static uint32_t _last1s = 0;
|
|
static uint32_t _last2s = 0;
|
|
static uint32_t _last5s = 0;
|
|
if (currentTime - _last1s >= 1000) { // 1sec
|
|
_last1s = currentTime;
|
|
//#ifndef DUMMYDISPLAY
|
|
#if !defined(DUMMYDISPLAY) || defined(USE_NEXTION)
|
|
#ifndef UPCLOCK_CORE1
|
|
_upClock();
|
|
#endif
|
|
#endif
|
|
}
|
|
if (currentTime - _last2s >= 2000) { // 2sec
|
|
_last2s = currentTime;
|
|
_upRSSI();
|
|
}
|
|
if (currentTime - _last5s >= 5000) { // 5sec
|
|
_last5s = currentTime;
|
|
//HEAP_INFO();
|
|
}
|
|
|
|
return true; // just in case
|
|
}
|
|
|
|
bool TimeKeeper::loop1(){ // core1 (player)
|
|
uint32_t currentTime = millis();
|
|
static uint32_t _last1s = 0;
|
|
static uint32_t _last2s = 0;
|
|
if (currentTime - _last1s >= 1000) { // 1sec
|
|
pm.on_ticker();
|
|
_last1s = currentTime;
|
|
//#ifndef DUMMYDISPLAY
|
|
#if !defined(DUMMYDISPLAY) || defined(USE_NEXTION)
|
|
#ifdef UPCLOCK_CORE1
|
|
_upClock();
|
|
#endif
|
|
#endif
|
|
_upScreensaver();
|
|
_upSDPos();
|
|
_returnPlayer();
|
|
_doAfterWait();
|
|
}
|
|
if (currentTime - _last2s >= 2000) { // 2sec
|
|
_last2s = currentTime;
|
|
}
|
|
|
|
//#ifdef DUMMYDISPLAY
|
|
#if defined(DUMMYDISPLAY) && !defined(USE_NEXTION)
|
|
return true;
|
|
#endif
|
|
// Sync weather & time
|
|
static uint32_t lastWeatherTime = 0;
|
|
if (currentTime - lastWeatherTime >= WEATHER_SYNC_INTERVAL) {
|
|
lastWeatherTime = currentTime;
|
|
forceWeather = true;
|
|
}
|
|
static uint32_t lastTimeTime = 0;
|
|
if (currentTime - lastTimeTime >= TIME_SYNC_INTERVAL) {
|
|
lastTimeTime = currentTime;
|
|
forceTimeSync = true;
|
|
}
|
|
if (!busy && (forceWeather || forceTimeSync) && network.status == CONNECTED) {
|
|
busy = true;
|
|
//config.setTimeConf();
|
|
xTaskCreatePinnedToCore(
|
|
_syncTask,
|
|
"syncTask",
|
|
SYNC_STACK_SIZE,
|
|
NULL, // Params
|
|
SYNC_TASK_PRIORITY,
|
|
NULL, // Descriptor
|
|
SYNC_TASK_CORE
|
|
);
|
|
}
|
|
|
|
return true; // just in case
|
|
}
|
|
|
|
void TimeKeeper::waitAndReturnPlayer(uint8_t time_s){
|
|
_returnPlayerTime = millis()+time_s*1000;
|
|
}
|
|
void TimeKeeper::_returnPlayer(){
|
|
if(_returnPlayerTime>0 && millis()>=_returnPlayerTime){
|
|
_returnPlayerTime = 0;
|
|
display.putRequest(NEWMODE, PLAYER);
|
|
}
|
|
}
|
|
|
|
void TimeKeeper::waitAndDo(uint8_t time_s, void (*callback)()){
|
|
_doAfterTime = millis()+time_s*1000;
|
|
_aftercallback = callback;
|
|
}
|
|
void TimeKeeper::_doAfterWait(){
|
|
if(_doAfterTime>0 && millis()>=_doAfterTime){
|
|
_doAfterTime = 0;
|
|
_aftercallback();
|
|
}
|
|
}
|
|
|
|
void TimeKeeper::_upClock(){
|
|
#if RTCSUPPORTED
|
|
if(config.isRTCFound()) rtc.getTime(&network.timeinfo);
|
|
#else
|
|
if(network.timeinfo.tm_year>100 || network.status == SDREADY) {
|
|
network.timeinfo.tm_sec++;
|
|
mktime(&network.timeinfo);
|
|
}
|
|
#endif
|
|
if(display.ready()) display.putRequest(CLOCK);
|
|
}
|
|
|
|
void TimeKeeper::_upScreensaver(){
|
|
#ifndef DSP_LCD
|
|
if(!display.ready()) return;
|
|
if(config.store.screensaverEnabled && display.mode()==PLAYER && !player.isRunning()){
|
|
config.screensaverTicks++;
|
|
if(config.screensaverTicks > config.store.screensaverTimeout+SCREENSAVERSTARTUPDELAY){
|
|
if(config.store.screensaverBlank){
|
|
display.putRequest(NEWMODE, SCREENBLANK);
|
|
}else{
|
|
display.putRequest(NEWMODE, SCREENSAVER);
|
|
}
|
|
config.screensaverTicks=SCREENSAVERSTARTUPDELAY;
|
|
}
|
|
}
|
|
if(config.store.screensaverPlayingEnabled && display.mode()==PLAYER && player.isRunning()){
|
|
config.screensaverPlayingTicks++;
|
|
if(config.screensaverPlayingTicks > config.store.screensaverPlayingTimeout*60+SCREENSAVERSTARTUPDELAY){
|
|
if(config.store.screensaverPlayingBlank){
|
|
display.putRequest(NEWMODE, SCREENBLANK);
|
|
}else{
|
|
display.putRequest(NEWMODE, SCREENSAVER);
|
|
}
|
|
config.screensaverPlayingTicks=SCREENSAVERSTARTUPDELAY;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void TimeKeeper::_upRSSI(){
|
|
if(network.status == CONNECTED){
|
|
netserver.setRSSI(WiFi.RSSI());
|
|
netserver.requestOnChange(NRSSI, 0);
|
|
if(display.ready()) display.putRequest(DSPRSSI, netserver.getRSSI());
|
|
}
|
|
#ifdef USE_SD
|
|
if(display.mode()!=SDCHANGE) player.sendCommand({PR_CHECKSD, 0});
|
|
#endif
|
|
player.sendCommand({PR_VUTONUS, 0});
|
|
}
|
|
|
|
void TimeKeeper::_upSDPos(){
|
|
if(player.isRunning() && config.getMode()==PM_SDCARD) netserver.requestOnChange(SDPOS, 0);
|
|
}
|
|
|
|
void TimeKeeper::timeTask(){
|
|
static uint8_t tsFailCnt = 0;
|
|
config.waitConnection();
|
|
if(getLocalTime(&network.timeinfo)){
|
|
tsFailCnt = 0;
|
|
forceTimeSync = false;
|
|
mktime(&network.timeinfo);
|
|
display.putRequest(CLOCK);
|
|
network.requestTimeSync(true);
|
|
#if RTCSUPPORTED
|
|
if (config.isRTCFound()) rtc.setTime(&network.timeinfo);
|
|
#endif
|
|
}else{
|
|
if(tsFailCnt<4){
|
|
forceTimeSync = true;
|
|
tsFailCnt++;
|
|
}else{
|
|
forceTimeSync = false;
|
|
tsFailCnt=0;
|
|
}
|
|
}
|
|
}
|
|
void TimeKeeper::weatherTask(){
|
|
forceWeather = false;
|
|
if(!weatherBuf || strlen(config.store.weatherkey)==0 || !config.store.showweather) return;
|
|
_getWeather();
|
|
}
|
|
|
|
bool _getWeather() {
|
|
#if (DSP_MODEL!=DSP_DUMMY || defined(USE_NEXTION)) && !defined(HIDE_WEATHER)
|
|
static AsyncClient * weatherClient = NULL;
|
|
static const char* host = "api.openweathermap.org";
|
|
if(weatherClient) return false;
|
|
weatherClient = new AsyncClient();
|
|
if(!weatherClient) return false;
|
|
|
|
weatherClient->onError([](void * arg, AsyncClient * client, int error){
|
|
Serial.println("##WEATHER###: connection error");
|
|
weatherClient = NULL;
|
|
delete client;
|
|
}, NULL);
|
|
|
|
weatherClient->onConnect([](void * arg, AsyncClient * client){
|
|
weatherClient->onError(NULL, NULL);
|
|
weatherClient->onDisconnect([](void * arg, AsyncClient * c){ weatherClient = NULL; delete c; }, NULL);
|
|
|
|
char httpget[250] = {0};
|
|
sprintf(httpget, "GET /data/2.5/weather?lat=%s&lon=%s&units=%s&lang=%s&appid=%s HTTP/1.1\r\nHost: %s\r\nConnection: close\r\n\r\n", config.store.weatherlat, config.store.weatherlon, LANG::weatherUnits, LANG::weatherLang, config.store.weatherkey, host);
|
|
client->write(httpget);
|
|
|
|
client->onData([](void * arg, AsyncClient * c, void * data, size_t len){
|
|
uint8_t * d = (uint8_t*)data;
|
|
const char *bodyStart = strstr((const char*)d, "\r\n\r\n");
|
|
if (bodyStart != NULL) {
|
|
bodyStart += 4;
|
|
size_t bodyLen = len - (bodyStart - (const char*)d);
|
|
char line[bodyLen+1];
|
|
memcpy(line, bodyStart, bodyLen);
|
|
line[bodyLen] = '\0';
|
|
/* parse it */
|
|
char *cursor;
|
|
char desc[120], icon[5];
|
|
float tempf, tempfl, wind_speed;
|
|
int hum, press, wind_deg;
|
|
bool result = true;
|
|
|
|
cursor = strstr(line, "\"description\":\"");
|
|
if (cursor) { sscanf(cursor, "\"description\":\"%119[^\"]", desc); }else{ Serial.println("##WEATHER###: description not found !"); result=false; }
|
|
cursor = strstr(line, "\"icon\":\"");
|
|
if (cursor) { sscanf(cursor, "\"icon\":\"%4[^\"]", icon); }else{ Serial.println("##WEATHER###: icon not found !"); result=false; }
|
|
cursor = strstr(line, "\"temp\":");
|
|
if (cursor) { sscanf(cursor, "\"temp\":%f", &tempf); }else{ Serial.println("##WEATHER###: temp not found !"); result=false; }
|
|
cursor = strstr(line, "\"pressure\":");
|
|
if (cursor) { sscanf(cursor, "\"pressure\":%d", &press); }else{ Serial.println("##WEATHER###: pressure not found !"); result=false; }
|
|
cursor = strstr(line, "\"humidity\":");
|
|
if (cursor) { sscanf(cursor, "\"humidity\":%d", &hum); }else{ Serial.println("##WEATHER###: humidity not found !"); result=false; }
|
|
cursor = strstr(line, "\"feels_like\":");
|
|
if (cursor) { sscanf(cursor, "\"feels_like\":%f", &tempfl); }else{ Serial.println("##WEATHER###: feels_like not found !"); result=false; }
|
|
cursor = strstr(line, "\"grnd_level\":");
|
|
if (cursor) { sscanf(cursor, "\"grnd_level\":%d", &press); }
|
|
cursor = strstr(line, "\"speed\":");
|
|
if (cursor) { sscanf(cursor, "\"speed\":%f", &wind_speed); }else{ Serial.println("##WEATHER###: wind speed not found !"); result=false; }
|
|
cursor = strstr(line, "\"deg\":");
|
|
if (cursor) { sscanf(cursor, "\"deg\":%d", &wind_deg); }else{ Serial.println("##WEATHER###: wind deg not found !"); result=false; }
|
|
press = press / 1.333;
|
|
|
|
if(!result) return;
|
|
|
|
#ifdef USE_NEXTION
|
|
nextion.putcmdf("press_txt.txt=\"%dmm\"", press);
|
|
nextion.putcmdf("hum_txt.txt=\"%d%%\"", hum);
|
|
char cmd[30];
|
|
snprintf(cmd, sizeof(cmd)-1,"temp_txt.txt=\"%.1f\"", tempf);
|
|
nextion.putcmd(cmd);
|
|
int iconofset;
|
|
if(strstr(icon,"01")!=NULL) iconofset = 0;
|
|
else if(strstr(icon,"02")!=NULL) iconofset = 1;
|
|
else if(strstr(icon,"03")!=NULL) iconofset = 2;
|
|
else if(strstr(icon,"04")!=NULL) iconofset = 3;
|
|
else if(strstr(icon,"09")!=NULL) iconofset = 4;
|
|
else if(strstr(icon,"10")!=NULL) iconofset = 5;
|
|
else if(strstr(icon,"11")!=NULL) iconofset = 6;
|
|
else if(strstr(icon,"13")!=NULL) iconofset = 7;
|
|
else if(strstr(icon,"50")!=NULL) iconofset = 8;
|
|
else iconofset = 9;
|
|
nextion.putcmd("cond_img.pic", 50+iconofset);
|
|
nextion.weatherVisible(1);
|
|
#endif
|
|
|
|
Serial.printf("##WEATHER###: description: %s, temp:%.1f C, pressure:%dmmHg, humidity:%d%%, wind: %d\n", desc, tempf, press, hum, (int)(wind_deg/22.5));
|
|
#ifdef WEATHER_FMT_SHORT
|
|
sprintf(timekeeper.weatherBuf, weatherFmt, tempf, press, hum);
|
|
#else
|
|
#if EXT_WEATHER
|
|
sprintf(timekeeper.weatherBuf, LANG::weatherFmt, desc, tempf, tempfl, press, hum, wind_speed, LANG::wind[(int)(wind_deg/22.5)]);
|
|
#else
|
|
sprintf(timekeeper.weatherBuf, LANG::weatherFmt, desc, tempf, press, hum);
|
|
#endif
|
|
#endif
|
|
display.putRequest(NEWWEATHER);
|
|
} else {
|
|
Serial.println("##WEATHER###: weather not found !");
|
|
}
|
|
}, NULL); // <-- client->onData
|
|
}, NULL); // <-- weatherClient->onConnect
|
|
config.waitConnection();
|
|
if(!weatherClient->connect(host, 80)){
|
|
Serial.println("##WEATHER###: connection failed");
|
|
AsyncClient * client = weatherClient;
|
|
weatherClient = NULL;
|
|
delete client;
|
|
}
|
|
|
|
return true;
|
|
#endif // if (DSP_MODEL!=DSP_DUMMY || defined(USE_NEXTION)) && !defined(HIDE_WEATHER)
|
|
return false;
|
|
}
|
|
|
|
//******************
|