529 lines
16 KiB
C++
529 lines
16 KiB
C++
/*
|
|
***************************************************************************
|
|
** Program : ESP_ticker (lichtkrant)
|
|
*/
|
|
const char* FWversion = "v1.7.3 (04-05-2023)";
|
|
/*
|
|
** Copyright (c) 2021 .. 2023 Willem Aandewiel
|
|
**
|
|
** TERMS OF USE: MIT License. See bottom of file.
|
|
***************************************************************************
|
|
|
|
Arduino-IDE settings for ESP-12E:
|
|
|
|
- Board: "Generic ESP8266 Module" (ALLWAYS!!!!!)
|
|
- Buildin Led: "2"
|
|
- Upload Speed: "115200"
|
|
- CPU Frequency: "80 MHz" (or if you need the speed: 160MHz)
|
|
- Flash size: "4MB (FS:2M OTA~1019KB)"
|
|
- Flash mode: "DIO" / "DOUT"
|
|
- Flash Frequency: "40MHz"
|
|
- Reset Method: "nodemcu" or something else
|
|
- Debug port: "Disabled"
|
|
- Debug Level: "None"
|
|
- IwIP Variant: "v2 Lower Memory"
|
|
- VTables: "Flash"
|
|
- Exceptions: "Legacy (new can return nullptr)"
|
|
- Erase Flash: "Only Sketch"
|
|
- Espressif FW: "nonos-sdk 2.2.1+100 (190703)"
|
|
- SSL Support: "All SSL ciphers (most compatible)"
|
|
- Port: "ESPticker at <-- IP address -->"
|
|
|
|
Arduino ESP8266 core v2.7.+
|
|
*/
|
|
|
|
|
|
// Use the Parola library to scroll text on the display
|
|
// IP address for the ESP8266 is displayed on the scrolling display
|
|
// after startup initialisation and connected to the WiFi network.
|
|
//
|
|
// Connections for ESP8266 hardware SPI are:
|
|
// Vcc 3v3 LED matrices seem to work at 3.3V
|
|
// GND GND GND
|
|
// DIN D7 HSPID or HMOSI
|
|
// CS or LD D8 HSPICS or HCS
|
|
// CLK D5 CLK or HCLK
|
|
//
|
|
// MD_MAX72XX library can be found at https://github.com/MajicDesigns/MD_MAX72XX
|
|
//
|
|
|
|
#define USE_UPDATE_SERVER
|
|
|
|
#define _HOSTNAME "ESPticker"
|
|
#include "ESP_ticker.h"
|
|
|
|
|
|
//---------------------------------------------------------------------
|
|
int16_t calculateIntensity()
|
|
{
|
|
int a0In = 0;
|
|
for (int l=0; l<2; l++)
|
|
{
|
|
//--- read analog A0
|
|
a0In+= analogRead(A0);
|
|
delay(200);
|
|
}
|
|
a0In = a0In / 2; //-- smooth things up a bit
|
|
|
|
DebugTf("analogRead[%d], ", a0In);
|
|
//---test if (a0In < settingLDRlowOffset) a0In = settingLDRlowOffset;
|
|
Debugf(" LDRlowOffset[%d] LDRhighOffset[%d] ", settingLDRlowOffset, settingLDRhighOffset);
|
|
valueLDR = (valueLDR + a0In) / 2;
|
|
if (valueLDR < settingLDRlowOffset) valueLDR = settingLDRlowOffset;
|
|
if (valueLDR > settingLDRhighOffset) valueLDR = settingLDRhighOffset;
|
|
Debugf(" ==> valueLDR[%d]\r\n", valueLDR);
|
|
|
|
//--- map LDR to offset..1024 -> 0..settingMax
|
|
int intensity = map(valueLDR, settingLDRlowOffset, settingLDRhighOffset, 0, settingMaxIntensity);
|
|
//DebugTf("map(%d, %d, %d, 0, %d) => [%d]\r\n", valueLDR, settingLDRlowOffset, settingLDRhighOffset
|
|
// , 0 , settingMaxIntensity);
|
|
|
|
return intensity;
|
|
|
|
} // calculateIntensity()
|
|
|
|
|
|
//---------------------------------------------------------------------
|
|
char *updateTime()
|
|
{
|
|
time(&now);
|
|
snprintf(timeMsg, 20, "%02d : %02d", localtime(&now)->tm_hour, localtime(&now)->tm_min);
|
|
return timeMsg;
|
|
|
|
} // updateTime()
|
|
|
|
|
|
//---------------------------------------------------------------------
|
|
bool getTheLocalTime(struct tm *info, uint32_t ms)
|
|
{
|
|
//-- getLocalTime() is not implemented in the ArduinoIDE
|
|
//-- so this is a 'work around' function
|
|
uint32_t start = millis();
|
|
time_t now;
|
|
while((millis()-start) <= ms)
|
|
{
|
|
time(&now);
|
|
localtime_r(&now, info);
|
|
if(info->tm_year > (2016 - 1900))
|
|
{
|
|
return true;
|
|
}
|
|
delay(10);
|
|
}
|
|
return false;
|
|
|
|
} // getTheLocalTime()
|
|
|
|
|
|
//---------------------------------------------------------------------
|
|
void splitNewsNoWords(const char *noNo)
|
|
{
|
|
DebugTln(noNo);
|
|
int8_t wc = splitString(String(noNo), ' ', noWords, MAX_NO_NO_WORDS);
|
|
for(int8_t i=0; i<wc; i++)
|
|
{
|
|
noWords[i].trim();
|
|
if (noWords[i].length() > 1)
|
|
{
|
|
noWords[i].toLowerCase();
|
|
DebugTf("NoNoWord[%d] [%s]\r\n", i, noWords[i].c_str());
|
|
}
|
|
}
|
|
|
|
} // splitNewsNoWords()
|
|
|
|
//---------------------------------------------------------------------
|
|
bool hasNoNoWord(const char *cIn)
|
|
{
|
|
for(int8_t i=0; i<MAX_NO_NO_WORDS; i++)
|
|
{
|
|
String sIn = String(cIn);
|
|
sIn.toLowerCase();
|
|
int idx = sIn.indexOf(noWords[i]);
|
|
if ((idx > -1) && (noWords[i].length() > 1)) // yes! it's in there somewhere
|
|
{
|
|
DebugTf("found [%s]\r\n", noWords[i].c_str());
|
|
return true;
|
|
}
|
|
}
|
|
//DebugTln("no NoNo words found!");
|
|
return false;
|
|
|
|
} // hasNoNoWord()
|
|
|
|
|
|
//---------------------------------------------------------------------
|
|
void nextNieuwsBericht()
|
|
{
|
|
bool breakOut = false;
|
|
newsMsgID++;
|
|
if (newsMsgID >= settingNewsMaxMsg) newsMsgID = 0;
|
|
while (!readFileById("NWS", newsMsgID))
|
|
{
|
|
DebugTln("File not found!");
|
|
newsMsgID++;
|
|
if (newsMsgID > settingNewsMaxMsg)
|
|
{
|
|
newsMsgID = 0;
|
|
breakOut = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!breakOut)
|
|
{
|
|
snprintf(actMessage, NEWS_SIZE, "** %s **", fileMessage);
|
|
//DebugTf("newsMsgID[%d] %s\r\n", newsMsgID, actMessage);
|
|
utf8Ascii(actMessage);
|
|
P.displayScroll(actMessage, PA_LEFT, PA_SCROLL_LEFT, (MAX_SPEED - settingTextSpeed));
|
|
}
|
|
|
|
} // nextNieuwsBericht()
|
|
|
|
|
|
//---------------------------------------------------------------------
|
|
void nextLocalBericht()
|
|
{
|
|
bool nothingThere = false;
|
|
|
|
localMsgID++;
|
|
if (localMsgID > settingLocalMaxMsg)
|
|
{
|
|
localMsgID = 0;
|
|
nothingThere = true;
|
|
}
|
|
while (!readFileById("LCL", localMsgID))
|
|
{
|
|
DebugTf("File [/newsFiles/LCL-%03d] not found!\r\n", localMsgID);
|
|
localMsgID++;
|
|
if (localMsgID > settingLocalMaxMsg)
|
|
{
|
|
DebugTln("Back to LCL-000, exit while-loop");
|
|
localMsgID = 0;
|
|
continue;
|
|
}
|
|
}
|
|
if (nothingThere && (localMsgID == 0))
|
|
{
|
|
nothingThere = true;
|
|
getRevisionData();
|
|
}
|
|
else nothingThere = false;
|
|
|
|
snprintf(actMessage, LOCAL_SIZE, "** %s **", fileMessage);
|
|
//DebugTf("localMsgID[%d] %s\r\n", localMsgID, actMessage);
|
|
utf8Ascii(actMessage);
|
|
P.displayScroll(actMessage, PA_LEFT, PA_SCROLL_LEFT, (MAX_SPEED - settingTextSpeed));
|
|
|
|
if ((millis() - revisionTimer) > 900000)
|
|
{
|
|
revisionTimer = millis();
|
|
getRevisionData();
|
|
}
|
|
|
|
} // nextLocalBericht()
|
|
|
|
|
|
//=====================================================================
|
|
void setup()
|
|
{
|
|
Serial.begin(115200);
|
|
while(!Serial) { /* wait a bit */ }
|
|
|
|
lastReset = ESP.getResetReason();
|
|
|
|
DebugTln("\r\n[MD_Parola WiFi Message Display]\r\n");
|
|
DebugTf("Booting....[%s]\r\n\r\n", String(FWversion).c_str());
|
|
|
|
P.begin();
|
|
P.displayClear();
|
|
P.displaySuspend(false);
|
|
P.setIntensity(2);
|
|
P.displayScroll(actMessage, PA_LEFT, PA_NO_EFFECT, 20);
|
|
P.setTextEffect(PA_SCROLL_LEFT, PA_NO_EFFECT);
|
|
do
|
|
{
|
|
yield();
|
|
} while( !P.displayAnimate() );
|
|
|
|
actMessage[0] = '\0';
|
|
|
|
//================ LittleFS ===========================================
|
|
if (LittleFS.begin())
|
|
{
|
|
DebugTln(F("LittleFS Mount succesfull\r"));
|
|
LittleFSmounted = true;
|
|
|
|
readSettings(true);
|
|
splitNewsNoWords(settingNewsNoWords);
|
|
|
|
if (settingNewsInterval == 0)
|
|
{
|
|
removeNewsData();
|
|
}
|
|
else
|
|
{
|
|
if (!LittleFS.exists("/newsFiles/LCL-000"))
|
|
{
|
|
char LCL000[100];
|
|
sprintf(LCL000, "ESP_ticker %s by Willem Aandewiel", String(FWversion).c_str());
|
|
writeFileById("LCL", 0, LCL000);
|
|
}
|
|
if (!LittleFS.exists("/newsFiles/LCL-001"))
|
|
{
|
|
char LCL001[100];
|
|
sprintf(LCL001, "ESP_ticker %s by Willem Aandewiel", String(FWversion).c_str());
|
|
writeFileById("LCL", 1, LCL001);
|
|
}
|
|
writeFileById("NWS", 1, "(c) 2021 Willem Aandewiel");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DebugTln(F("LittleFS Mount failed\r")); // Serious problem with LittleFS
|
|
LittleFSmounted = false;
|
|
}
|
|
//==========================================================//
|
|
// writeLastStatus(); // only for firsttime initialization //
|
|
//==========================================================//
|
|
readLastStatus(); // place it in actTimestamp
|
|
|
|
// attempt to connect to Wifi network:
|
|
int t = 0;
|
|
while ((WiFi.status() != WL_CONNECTED) && (t < 25))
|
|
{
|
|
delay(500);
|
|
Serial.print(".");
|
|
t++;
|
|
}
|
|
if ( WiFi.status() != WL_CONNECTED) {
|
|
DebugTln("Attempting to connect to WiFi network\r");
|
|
sprintf(actMessage, "Connect to AP '%s' and configure WiFi on 192.168.4.1 ", _HOSTNAME);
|
|
P.setTextEffect(PA_SCROLL_LEFT, PA_NO_EFFECT);
|
|
do { yield(); } while( !P.displayAnimate() );
|
|
//P.print(" 192.168.4.1");
|
|
}
|
|
// Connect to and initialise WiFi network
|
|
digitalWrite(LED_BUILTIN, HIGH);
|
|
startWiFi(_HOSTNAME, 240); // timeout 4 minuten
|
|
digitalWrite(LED_BUILTIN, LOW);
|
|
|
|
startMDNS(settingHostname);
|
|
|
|
DebugTln("Get time from NTP");
|
|
timeSync.setup();
|
|
timeSync.sync(300);
|
|
time(&now);
|
|
if (localtime(&now)->tm_year > 120)
|
|
{
|
|
timeSynced = true;
|
|
Serial.println("Time synchronized with NTP Service");
|
|
}
|
|
else
|
|
{
|
|
timeSynced = false;
|
|
Serial.println("Could not synchronize time with NTP Service");
|
|
}
|
|
|
|
time(&now);
|
|
Serial.println("-------------------------------------------------------------------------------");
|
|
if (!getTheLocalTime(&timeinfo, 10000))
|
|
{
|
|
Debugln("Time : Failed to obtain time!");
|
|
}
|
|
else
|
|
{
|
|
Debugf( "Time : %04d-%02d-%02d %02d:%02d:%02d\r\n", localtime(&now)->tm_year+1900
|
|
, localtime(&now)->tm_mon+1
|
|
, localtime(&now)->tm_mday
|
|
, localtime(&now)->tm_hour
|
|
, localtime(&now)->tm_min
|
|
, localtime(&now)->tm_sec);
|
|
}
|
|
|
|
nrReboots++;
|
|
writeLastStatus();
|
|
//writeToLog("=========REBOOT==========================");
|
|
|
|
snprintf(cMsg, sizeof(cMsg), "Last reset reason: [%s]", ESP.getResetReason().c_str());
|
|
DebugTln(cMsg);
|
|
//writeToLog(cMsg);
|
|
|
|
Serial.print("\nGebruik 'telnet ");
|
|
Serial.print (WiFi.localIP());
|
|
Serial.println("' voor verdere debugging\r\n");
|
|
|
|
//================ Start HTTP Server ================================
|
|
setupFSexplorer();
|
|
httpServer.serveStatic("/FSexplorer.png", LittleFS, "/FSexplorer.png");
|
|
httpServer.on("/", sendIndexPage);
|
|
httpServer.on("/index", sendIndexPage);
|
|
httpServer.on("/index.html",sendIndexPage);
|
|
httpServer.serveStatic("/index.css", LittleFS, "/index.css");
|
|
httpServer.serveStatic("/index.js", LittleFS, "/index.js");
|
|
// all other api calls are catched in FSexplorer onNotFounD!
|
|
httpServer.on("/api", HTTP_GET, processAPI);
|
|
|
|
|
|
httpServer.begin();
|
|
DebugTln("\nServer started\r");
|
|
|
|
// Set up first message as the IP address
|
|
sprintf(actMessage, "%03d.%03d.%d.%d", WiFi.localIP()[0], WiFi.localIP()[1], WiFi.localIP()[2], WiFi.localIP()[3]);
|
|
DebugTf("\nAssigned IP[%s]\r\n", actMessage);
|
|
P.displayScroll(actMessage, PA_LEFT, PA_NO_EFFECT, (MAX_SPEED - settingTextSpeed));
|
|
P.setTextEffect(PA_SCROLL_LEFT, PA_NO_EFFECT);
|
|
|
|
valueIntensity = calculateIntensity(); // read analog input pin 0
|
|
|
|
P.setIntensity(valueIntensity);
|
|
newsMsgID = 0;
|
|
do { yield(); } while( !P.displayAnimate() );
|
|
|
|
P.setFont(ExtASCII);
|
|
|
|
inFX = 0;
|
|
outFX= 0;
|
|
|
|
for (int i=0; i<=settingNewsMaxMsg; i++)
|
|
{
|
|
writeFileById("NWS", i, "");
|
|
//DebugTf("readFileById(NWS, %d)\r\n", i);
|
|
//readFileById("NWS", i);
|
|
}
|
|
|
|
} // setup()
|
|
|
|
|
|
//=====================================================================
|
|
void loop()
|
|
{
|
|
httpServer.handleClient();
|
|
MDNS.update();
|
|
yield();
|
|
|
|
if ((millis() > weerTimer) && (strlen(settingWeerLiveAUTH) > 5))
|
|
{
|
|
weerTimer = millis() + (settingWeerLiveInterval * (300 * 1000)); // Interval in Minutes!
|
|
if (settingWeerLiveInterval > 0) getWeerLiveData();
|
|
}
|
|
|
|
if ((millis() > newsapiTimer) && (strlen(settingNewsAUTH) > 5))
|
|
{
|
|
newsapiTimer = millis() + (settingNewsInterval * (300 * 1000)); // Interval in Minutes!
|
|
if (settingNewsInterval > 0)
|
|
{
|
|
if (!getNewsapiData()) //-- first try ...
|
|
{
|
|
delay(100);
|
|
if (!getNewsapiData()) //-- second try ...
|
|
{
|
|
//-- try again in two(2) minutes ...
|
|
newsapiTimer = millis() + (2 * (60 * 1000)); // Interval in Minutes!
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (P.displayAnimate()) // done with animation, ready for next message
|
|
{
|
|
yield();
|
|
msgType++;
|
|
DebugTf("msgType[%d]\r\n", msgType);
|
|
time(&now);
|
|
if (localtime(&now)->tm_year > 120) timeSynced = true;
|
|
|
|
|
|
switch(msgType)
|
|
{
|
|
case 1: if (!(millis() > timeTimer)) { DebugTln("Not yet time to display weekday"); return; }
|
|
if (!timeSynced) { DebugTf("Time not (yet) synced!!\n"); return; }
|
|
inFX = random(0, ARRAY_SIZE(effect));
|
|
outFX = random(0, ARRAY_SIZE(effect));
|
|
snprintf(actMessage, LOCAL_SIZE, weekDayName[localtime(&now)->tm_wday+1]);
|
|
snprintf(onTickerMessage, 120, "%s", actMessage);
|
|
DebugT(" ["); Debug(onTickerMessage); Debugln("]");
|
|
P.displayClear();
|
|
P.displayText(actMessage, PA_CENTER, (MAX_SPEED - settingTextSpeed), 1000, effect[inFX], effect[outFX]);
|
|
DebugTf("Animate IN[%d], OUT[%d] %s\r\n", inFX, outFX, actMessage);
|
|
break;
|
|
case 2: if (!(millis() > timeTimer)) { DebugTln("Not yet time to display the time"); return; }
|
|
if (!timeSynced) { DebugTf("Time not (yet) synced!!\n"); return; }
|
|
timeTimer = millis() + 60000;
|
|
inFX = random(0, ARRAY_SIZE(effect));
|
|
outFX = random(0, ARRAY_SIZE(effect));
|
|
sprintf(actMessage, "%s", updateTime());
|
|
snprintf(onTickerMessage, 120, "%s", actMessage);
|
|
DebugT(" ["); Debug(onTickerMessage); Debugln("]");
|
|
P.displayText(actMessage, PA_CENTER, (MAX_SPEED - settingTextSpeed), 2000, effect[inFX], effect[outFX]);
|
|
DebugTf("Animate IN[%d], OUT[%d] %s\r\n", inFX, outFX, actMessage);
|
|
break;
|
|
case 3: nextLocalBericht();
|
|
P.setTextEffect(PA_SCROLL_LEFT, PA_NO_EFFECT);
|
|
break;
|
|
case 6: nextLocalBericht();
|
|
P.setTextEffect(PA_SCROLL_LEFT, PA_NO_EFFECT);
|
|
break;
|
|
case 4:
|
|
case 5:
|
|
case 7:
|
|
case 8: if (settingNewsInterval > 0)
|
|
nextNieuwsBericht();
|
|
else nextLocalBericht();
|
|
P.setTextEffect(PA_SCROLL_LEFT, PA_NO_EFFECT);
|
|
break;
|
|
case 9: if (settingWeerLiveInterval > 0)
|
|
{
|
|
snprintf(actMessage, LOCAL_SIZE, "** %s **", tempMessage);
|
|
Debugf("\t[%s]\r\n", actMessage);
|
|
utf8Ascii(actMessage);
|
|
}
|
|
else nextLocalBericht();
|
|
P.setTextEffect(PA_SCROLL_LEFT, PA_NO_EFFECT);
|
|
break;
|
|
case 10: if (settingNewsInterval > 0)
|
|
nextNieuwsBericht();
|
|
else nextLocalBericht();
|
|
break;
|
|
default: msgType = 0;
|
|
return;
|
|
|
|
} // switch()
|
|
|
|
//DebugTln(actMessage);
|
|
valueIntensity = calculateIntensity(); // read analog input pin 0
|
|
DebugTf("Intensity set to [%d]\r\n", valueIntensity);
|
|
P.setIntensity(valueIntensity);
|
|
// Tell Parola we have a new animation
|
|
P.displayReset();
|
|
DebugTln("End of displayAnimate()..");
|
|
|
|
} // dislayAnimate()
|
|
|
|
|
|
} // loop()
|
|
|
|
|
|
/***************************************************************************
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
* copy of this software and associated documentation files (the
|
|
* "Software"), to deal in the Software without restriction, including
|
|
* without limitation the rights to use, copy, modify, merge, publish,
|
|
* distribute, sublicense, and/or sell copies of the Software, and to permit
|
|
* persons to whom the Software is furnished to do so, subject to the
|
|
* following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included
|
|
* in all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT
|
|
* OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
|
|
* THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
*
|
|
****************************************************************************
|
|
*/
|