2020-10-25 20:48:57 +01:00
/*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* * Program : OTGW - firmware . ino
2022-05-30 08:27:46 +02:00
* * Version : v0 .9 .5
2020-10-25 20:48:57 +01:00
* *
2023-01-08 16:14:41 +01:00
* * Copyright ( c ) 2021 - 2023 Robert van den Breemen
2020-10-25 20:48:57 +01:00
* *
* * TERMS OF USE : MIT License . See bottom of file .
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*/
2020-12-10 13:24:40 +01:00
/*
2021-01-31 23:43:52 +01:00
* How to install the OTGW on your nodeMCU :
* Read this : https : //github.com/rvdbreemen/OTGW-firmware/wiki/How-to-compile-OTGW-firmware-yourself
2020-12-10 13:24:40 +01:00
*
2021-01-31 23:43:52 +01:00
* How to upload to your LittleFS ?
* Read this : https : //github.com/rvdbreemen/OTGW-firmware/wiki/Upload-files-to-LittleFS-(filesystem)
*
2020-12-10 13:24:40 +01:00
* How to compile this firmware ?
* - NodeMCU v1 .0
* - Flashsize ( 4 MB - FS : 2 MB - OTA ~ 1019 KB )
* - CPU frequentcy : 160 MHz
* - Normal defaults should work fine .
* First time : Make sure to flash sketch + wifi or flash ALL contents .
*
*/
2020-10-25 20:48:57 +01:00
# include "version.h"
# include "OTGW-firmware.h"
2020-10-25 20:35:34 +01:00
2023-01-25 07:01:46 +01:00
# define SetupDebugTln(...) ({ if (bPICavailable) DebugTln(__VA_ARGS__); })
# define SetupDebugln(...) ({ if (bPICavailable) Debugln(__VA_ARGS__); })
# define SetupDebugTf(...) ({ if (bPICavailable) DebugTf(__VA_ARGS__); })
# define SetupDebugf(...) ({ if (bPICavailable) Debugf(__VA_ARGS__); })
# define SetupDebugT(...) ({ if (bPICavailable) DebugT(__VA_ARGS__); })
# define SetupDebug(...) ({ if (bPICavailable) Debug(__VA_ARGS__); })
# define SetupDebugFlush() ({ if (bPICavailable) DebugFlush(); })
2023-01-22 23:18:23 +01:00
2021-02-08 00:13:52 +01:00
# define ON LOW
# define OFF HIGH
2021-03-14 21:58:35 +01:00
DECLARE_TIMER_SEC ( timerpollsensor , settingGPIOSENSORSinterval , CATCH_UP_MISSED_TICKS ) ;
2023-01-23 20:00:27 +01:00
DECLARE_TIMER_SEC ( timers0counter , settingS0COUNTERinterval , CATCH_UP_MISSED_TICKS ) ;
2021-03-14 21:58:35 +01:00
2020-10-25 20:35:34 +01:00
//=====================================================================
2021-02-14 19:26:37 +01:00
void setup ( ) {
2022-06-15 07:17:06 +02:00
2021-02-08 00:13:52 +01:00
// Serial is initialized by OTGWSerial. It resets the pic and opens serialdevice.
// OTGWSerial.begin();//OTGW Serial device that knows about OTGW PIC
// while (!Serial) {} //Wait for OK
2023-01-24 21:47:56 +01:00
WatchDogEnabled ( 0 ) ; // turn off watchdog
2023-01-22 23:18:23 +01:00
SetupDebugln ( F ( " \r \n [OTGW firmware - Nodoshop version] \r \n " ) ) ;
SetupDebugf ( " Booting....[%s] \r \n \r \n " , _VERSION ) ;
2023-01-24 21:47:56 +01:00
2023-01-25 07:01:46 +01:00
detectPIC ( ) ;
2023-01-22 23:18:23 +01:00
2020-10-25 20:35:34 +01:00
//setup randomseed the right way
randomSeed ( RANDOM_REG32 ) ; //This is 8266 HWRNG used to seed the Random PRNG: Read more: https://config9.com/arduino/getting-a-truly-random-number-in-arduino/
2021-12-04 10:58:34 +01:00
2020-10-25 20:35:34 +01:00
//setup the status LED
2021-02-08 00:13:52 +01:00
setLed ( LED1 , ON ) ;
setLed ( LED2 , ON ) ;
2020-10-25 20:35:34 +01:00
2021-02-14 19:26:37 +01:00
LittleFS . begin ( ) ;
2020-10-25 20:35:34 +01:00
readSettings ( true ) ;
// Connect to and initialise WiFi network
2021-02-08 00:13:52 +01:00
setLed ( LED1 , ON ) ;
2023-01-22 23:18:23 +01:00
SetupDebugln ( F ( " Attempting to connect to WiFi network \r " ) ) ;
//setup NTP before connecting to wifi will enable DHCP to overrule the NTP setting
2023-01-01 22:48:55 +01:00
startNTP ( ) ;
2023-01-22 23:18:23 +01:00
//start with setting wifi hostname
WiFi . hostname ( String ( settingHostname ) ) ;
2021-03-25 21:08:09 +01:00
startWiFi ( CSTR ( settingHostname ) , 240 ) ; // timeout 240 seconds
2021-03-06 10:52:51 +01:00
blinkLED ( LED1 , 3 , 100 ) ;
2021-02-08 00:13:52 +01:00
setLed ( LED1 , OFF ) ;
2021-03-07 10:55:52 +01:00
startTelnet ( ) ; // start the debug port 23
2020-12-31 02:20:47 +01:00
startMDNS ( CSTR ( settingHostname ) ) ;
2021-03-06 19:14:08 +01:00
startLLMNR ( CSTR ( settingHostname ) ) ;
2020-10-25 20:35:34 +01:00
setupFSexplorer ( ) ;
2021-02-19 01:48:31 +01:00
startWebserver ( ) ;
2021-12-19 20:47:20 +01:00
startMQTT ( ) ; // start the MQTT after webserver, always.
2022-01-03 21:19:00 +01:00
initWatchDog ( ) ; // setup the WatchDog
2021-12-04 10:58:34 +01:00
lastReset = ESP . getResetReason ( ) ;
2023-01-22 23:18:23 +01:00
SetupDebugf ( " Last reset reason: [%s] \r \n " , CSTR ( lastReset ) ) ;
2021-12-04 10:58:34 +01:00
rebootCount = updateRebootCount ( ) ;
2021-12-04 12:33:45 +01:00
updateRebootLog ( lastReset ) ;
2023-01-22 23:18:23 +01:00
SetupDebugln ( F ( " Setup finished! \r \n " ) ) ;
2021-02-02 19:24:31 +01:00
// After resetting the OTGW PIC never send anything to Serial for debug
// and switch to telnet port 23 for debug purposed.
2021-01-31 23:43:52 +01:00
// Setup the OTGW PIC
2021-01-18 21:53:53 +01:00
resetOTGW ( ) ; // reset the OTGW pic
2021-02-02 00:56:38 +01:00
startOTGWstream ( ) ; // start port 25238
2023-01-23 20:00:27 +01:00
// initSensors(); // init DS18B20 (after MQ is up! )
2021-03-13 10:35:18 +01:00
initOutputs ( ) ;
2022-01-03 21:19:00 +01:00
2021-09-11 18:04:15 +02:00
WatchDogEnabled ( 1 ) ; // turn on watchdog
2021-03-14 23:05:56 +01:00
sendOTGWbootcmd ( ) ;
2021-02-08 00:13:52 +01:00
//Blink LED2 to signal setup done
2021-03-06 10:36:07 +01:00
setLed ( LED1 , OFF ) ;
2021-03-06 10:52:51 +01:00
blinkLED ( LED2 , 3 , 100 ) ;
2021-02-08 00:13:52 +01:00
setLed ( LED2 , OFF ) ;
2021-10-24 10:53:18 +02:00
sendMQTTuptime ( ) ;
sendMQTTversioninfo ( ) ;
2023-01-23 20:00:27 +01:00
initS0Count ( ) ; // init S0 counter
initSensors ( ) ; // init DS18B20 (after MQ is up!)
2021-03-15 22:18:30 +01:00
}
2020-10-25 20:35:34 +01:00
//=====================================================================
2021-09-15 20:35:22 +02:00
//====[ restartWifi ]===
/*
The restartWifi function takes tries to just reconnect to the wifi . When the wifi is restated , it then waits for maximum of 30 seconds ( timeout ) .
It keeps count of how many times it tried , when it tried to reconnect for 15 times . It goes into failsafe mode , and reboots the ESP .
The watchdog is turned off during this process
*/
void restartWifi ( ) {
static int iTryRestarts = 0 ; //So if we have more than 15 restarts, then it's time to reboot
iTryRestarts + + ; //Count the number of attempts
2021-09-16 23:24:08 +02:00
WiFi . hostname ( settingHostname ) ; //make sure hostname is set
2021-09-15 20:35:22 +02:00
if ( WiFi . begin ( ) ) // if the wifi ssid exist, you can try to connect.
{
//active wait for connections, this can take seconds
DECLARE_TIMER_SEC ( timeoutWifiConnect , 30 , CATCH_UP_MISSED_TICKS ) ;
while ( ( WiFi . status ( ) ! = WL_CONNECTED ) )
{
delay ( 100 ) ;
feedWatchDog ( ) ; //feeding the dog, while waiting activly
if DUE ( timeoutWifiConnect ) break ; //timeout, then break out of this loop
}
}
if ( WiFi . status ( ) = = WL_CONNECTED )
{ //when reconnect, restart some services, just to make sure all works
2021-11-18 21:08:32 +01:00
// Turn off ESP reconnect, to make sure that's not the issue (16/11/2021)
// WiFi.setAutoReconnect(true);
// WiFi.persistent(true);
2021-09-15 20:35:22 +02:00
startTelnet ( ) ;
startOTGWstream ( ) ;
startMQTT ( ) ;
iTryRestarts = 0 ; //reset attempt counter
return ;
}
//if all fails, and retry 15 is hit, then reboot esp
if ( iTryRestarts > = 15 ) doRestart ( " Too many wifi reconnect attempts " ) ;
}
2021-10-24 10:53:18 +02:00
void sendMQTTuptime ( ) {
2023-01-10 20:42:04 +01:00
DebugTf ( PSTR ( " Uptime seconds: %d \r \n " ) , upTimeSeconds ) ;
2021-10-24 10:53:18 +02:00
String sUptime = String ( upTimeSeconds ) ;
sendMQTTData ( F ( " otgw-firmware/uptime " ) , sUptime , false ) ;
}
2021-11-02 23:00:43 +01:00
void sendtimecommand ( ) {
2021-11-03 19:01:02 +01:00
if ( ! settingNTPenable ) return ; // if NTP is disabled, then return
if ( NtpStatus ! = TIME_SYNC ) return ; // only send time command when time is synced
2023-01-22 23:18:23 +01:00
if ( OTGWSerial . firmwareType ( ) ! = FIRMWARE_OTGW ) return ; //only send timecommand when in gateway firmware, not in diagnotic or interface mode
2021-11-03 19:01:02 +01:00
//send time command to OTGW
2021-11-02 23:00:43 +01:00
//send time / weekday
2023-01-03 14:33:00 +01:00
time_t now = time ( nullptr ) ;
TimeZone myTz = timezoneManager . createForZoneName ( CSTR ( settingNTPtimezone ) ) ;
ZonedDateTime myTime = ZonedDateTime : : forUnixSeconds64 ( now , myTz ) ;
2023-01-10 20:42:04 +01:00
//DebugTf(PSTR("%02d:%02d:%02d %02d-%02d-%04d\r\n"), myTime.hour(), myTime.minute(), myTime.second(), myTime.day(), myTime.month(), myTime.year());
2023-01-03 14:33:00 +01:00
2021-11-02 23:00:43 +01:00
char msg [ 15 ] = { 0 } ;
2023-01-22 23:18:23 +01:00
//Send msg id xx: hour:minute/day of week
int day_of_week = ( myTime . dayOfWeek ( ) + 6 ) % 7 + 1 ;
2023-01-03 14:33:00 +01:00
sprintf ( msg , " SC=%d:%02d/%d " , myTime . hour ( ) , myTime . minute ( ) , day_of_week ) ;
2021-11-15 21:32:19 +01:00
addOTWGcmdtoqueue ( msg , strlen ( msg ) , true ) ;
2023-01-24 21:47:56 +01:00
handleOTGWqueue ( ) ; //send command right away
2021-11-02 23:00:43 +01:00
2023-01-22 23:18:23 +01:00
if ( dayChanged ( ) ) {
2021-11-02 23:00:43 +01:00
//Send msg id 21: month, day
2023-01-03 14:33:00 +01:00
sprintf ( msg , " SR=21:%d,%d " , myTime . month ( ) , myTime . day ( ) ) ;
2023-01-24 21:47:56 +01:00
addOTWGcmdtoqueue ( msg , strlen ( msg ) , true ) ;
handleOTGWqueue ( ) ; //send command right away
2021-11-02 23:00:43 +01:00
}
2023-01-22 23:18:23 +01:00
if ( yearChanged ( ) ) {
2021-11-02 23:00:43 +01:00
//Send msg id 22: HB of Year, LB of Year
2023-01-22 23:18:23 +01:00
sprintf ( msg , " SR=22:%d,%d " , ( myTime . year ( ) > > 8 ) & 0xFF , myTime . year ( ) & 0xFF ) ;
2021-11-15 21:32:19 +01:00
addOTWGcmdtoqueue ( msg , strlen ( msg ) , true ) ;
2023-01-24 21:47:56 +01:00
handleOTGWqueue ( ) ; //send command right away
2021-11-02 23:00:43 +01:00
}
}
2020-10-25 20:35:34 +01:00
//===[ blink status led ]===
2021-02-08 00:13:52 +01:00
void setLed ( uint8_t led , uint8_t status ) {
pinMode ( led , OUTPUT ) ;
digitalWrite ( led , status ) ;
}
void blinkLEDms ( uint32_t delay ) {
2020-10-25 20:35:34 +01:00
//blink the statusled, when time passed
2021-02-08 00:13:52 +01:00
DECLARE_TIMER_MS ( timerBlink , delay ) ;
2020-10-25 20:35:34 +01:00
if ( DUE ( timerBlink ) ) {
blinkLEDnow ( ) ;
}
}
2021-03-06 10:54:53 +01:00
void blinkLED ( uint8_t led , int nr , uint32_t waittime_ms ) {
2021-03-06 10:52:51 +01:00
for ( int i = nr ; i > 0 ; i - - ) {
blinkLEDnow ( led ) ;
delayms ( waittime_ms ) ;
blinkLEDnow ( led ) ;
delayms ( waittime_ms ) ;
}
2021-02-08 00:13:52 +01:00
}
void blinkLEDnow ( uint8_t led = LED1 ) {
pinMode ( led , OUTPUT ) ;
2021-12-30 16:03:11 +01:00
if ( settingLEDblink ) {
digitalWrite ( led , ! digitalRead ( led ) ) ;
} else setLed ( led , OFF ) ;
2020-10-25 20:35:34 +01:00
}
//===[ no-blocking delay with running background tasks in ms ]===
void delayms ( unsigned long delay_ms )
{
DECLARE_TIMER_MS ( timerDelayms , delay_ms ) ;
while ( DUE ( timerDelayms ) )
doBackgroundTasks ( ) ;
}
//=====================================================================
//===[ Do task every 1s ]===
void doTaskEvery1s ( ) {
//== do tasks ==
2021-03-18 00:53:30 +01:00
handleOTGWqueue ( ) ; //just check if there are commands to retry
2020-10-25 20:35:34 +01:00
upTimeSeconds + + ;
}
//===[ Do task every 5s ]===
void doTaskEvery5s ( ) {
//== do tasks ==
2021-03-14 21:58:35 +01:00
2020-10-25 20:35:34 +01:00
}
//===[ Do task every 30s ]===
void doTaskEvery30s ( ) {
//== do tasks ==
}
//===[ Do task every 60s ]===
void doTaskEvery60s ( ) {
2020-11-19 16:47:10 +01:00
//== do tasks ==
2022-06-06 23:35:12 +02:00
if ( sPICdeviceid = = " unknown " ) {
//keep trying to figure out which pic is used!
2023-01-22 23:18:23 +01:00
DebugTln ( " PIC is unknown, probe pic using PR=A " ) ;
2022-06-06 23:35:12 +02:00
sPICfwversion = getpicfwversion ( ) ;
sPICfwversion = String ( OTGWSerial . firmwareVersion ( ) ) ;
2023-01-10 20:42:04 +01:00
DebugTf ( PSTR ( " Current firmware version: %s \r \n " ) , CSTR ( sPICfwversion ) ) ;
2022-06-06 23:35:12 +02:00
sPICdeviceid = OTGWSerial . processorToString ( ) ;
2023-01-22 23:18:23 +01:00
DebugTf ( PSTR ( " Current device id: %s \r \n " ) , CSTR ( sPICdeviceid ) ) ;
sPICtype = OTGWSerial . firmwareToString ( ) ;
DebugTf ( PSTR ( " Current firmware type: %s \r \n " ) , CSTR ( sPICtype ) ) ;
2022-06-06 23:35:12 +02:00
}
2020-10-25 20:35:34 +01:00
}
2023-01-22 23:18:23 +01:00
//===[ Do task exactly on the minute ]===
void doTaskMinuteChanged ( ) {
//== do tasks ==
//if no wifi, try reconnecting (once a minute)
if ( WiFi . status ( ) ! = WL_CONNECTED ) restartWifi ( ) ;
DebugTln ( " Minute changed: " ) ;
sendtimecommand ( ) ;
}
2021-03-08 02:15:28 +01:00
//===[ Do task every 5min ]===
void do5minevent ( ) {
2021-10-24 10:53:18 +02:00
sendMQTTuptime ( ) ;
2021-10-23 21:18:41 +02:00
sendMQTTversioninfo ( ) ;
2022-01-05 22:08:28 +01:00
sendMQTTstateinformation ( ) ;
2022-05-28 10:29:55 +02:00
if ( bCheckOTGWPICupdate ) {
bCheckOTGWPICupdate = false ;
checkOTWGpicforupdate ( ) ;
}
}
//===[ Do task every 24 hours ]===
void doTaskEvery24h ( ) {
bCheckOTGWPICupdate = true ;
2021-03-08 02:15:28 +01:00
}
2020-10-25 20:35:34 +01:00
//===[ Do the background tasks ]===
void doBackgroundTasks ( )
{
2020-11-02 08:09:26 +01:00
feedWatchDog ( ) ; // Feed the dog before it bites!
2021-09-09 23:30:05 +02:00
if ( WiFi . status ( ) = = WL_CONNECTED ) {
//while connected handle everything that uses network stuff
handleDebug ( ) ;
handleMQTT ( ) ; // MQTT transmissions
handleOTGW ( ) ; // OTGW handling
httpServer . handleClient ( ) ;
MDNS . update ( ) ;
2021-10-16 20:36:00 +02:00
loopNTP ( ) ;
2021-09-09 23:30:05 +02:00
} //otherwise, just wait until reconnected gracefully
2020-11-20 00:43:22 +01:00
delay ( 1 ) ;
2020-10-25 20:35:34 +01:00
}
void loop ( )
{
2021-12-30 16:03:11 +01:00
DECLARE_TIMER_SEC ( timer1s , 1 , SKIP_MISSED_TICKS ) ;
DECLARE_TIMER_SEC ( timer5s , 5 , SKIP_MISSED_TICKS ) ;
2020-10-25 20:35:34 +01:00
DECLARE_TIMER_SEC ( timer30s , 30 , CATCH_UP_MISSED_TICKS ) ;
DECLARE_TIMER_SEC ( timer60s , 60 , CATCH_UP_MISSED_TICKS ) ;
2021-03-08 02:15:28 +01:00
DECLARE_TIMER_MIN ( timer5min , 5 , CATCH_UP_MISSED_TICKS ) ;
2022-05-28 10:29:55 +02:00
DECLARE_TIMER_MIN ( timer24h , 1440 , CATCH_UP_MISSED_TICKS ) ;
2021-03-14 21:58:35 +01:00
2022-05-28 09:11:39 +02:00
if ( DUE ( timerpollsensor ) ) pollSensors ( ) ; // poll the temperature sensors connected to 2wire gpio pin
2023-01-23 20:00:27 +01:00
if ( DUE ( timers0counter ) ) sendS0Counters ( ) ; // poll the s0 counter connected to gpio pin when due
2022-05-28 09:11:39 +02:00
if ( DUE ( timer5min ) ) do5minevent ( ) ;
if ( DUE ( timer60s ) ) doTaskEvery60s ( ) ;
if ( DUE ( timer30s ) ) doTaskEvery30s ( ) ;
if ( DUE ( timer5s ) ) doTaskEvery5s ( ) ;
if ( DUE ( timer1s ) ) doTaskEvery1s ( ) ;
2022-05-28 10:29:55 +02:00
if ( DUE ( timer24h ) ) doTaskEvery24h ( ) ;
2023-01-22 23:18:23 +01:00
if ( minuteChanged ( ) ) doTaskMinuteChanged ( ) ; //exactly on the minute
2022-05-28 09:11:39 +02:00
evalOutputs ( ) ; // when the bits change, the output gpio bit will follow
2020-10-25 20:35:34 +01:00
doBackgroundTasks ( ) ;
}
2021-01-30 18:35:11 +01:00
/***************************************************************************
*
* 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 .
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*/