// ESP32 with NTP sync
// https://wokwi.com/projects/426995175166837761
// from https://forum.arduino.cc/t/best-way-to-fire-events-from-static-time-schedule-8-different-times-day/1367667/12?u=davex
/*
# ============================================
# code is placed under the MIT license
# Copyright (c) 2022 J-M-L
# For the Arduino Forum : https://forum.arduino.cc/u/j-m-l
#
# 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.
# ===============================================
*/
#include <WiFi.h>
#include <esp_sntp.h> // https://github.com/espressif/esp-idf/blob/v5.2/components/lwip/include/apps/esp_sntp.h (doc https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/system/system_time.html)
const int bellRelayPin = 2; // Relay GPIO pin
const int smoothPin = 18; // smooth mode
#define convertToAlarm(hour,minute) ((hour)*60u+(minute))
const uint16_t alarms[] = {
convertToAlarm(10, 0),
convertToAlarm(12, 0),
convertToAlarm(14, 50),
};
const byte numAlarms = sizeof alarms / sizeof * alarms;
byte nextAlarmIndex = 0;
const char* ssid = "Wokwi-GUEST";
const char* wifiPassword = "";
const char* ntpServer = "pool.ntp.org"; // use "pool.ntp.org" to be generic
bool ntpSyncCompleted = false;
bool wifiConnected = false;
time_t lastNtpSync;
void ntpSyncCallback(struct timeval *tv) {
Serial.println("NTP synchronized");
lastNtpSync = time(NULL);
Serial.print("NTP sync completed on ");
struct tm *pTime = localtime(&lastNtpSync);
Serial.println(pTime, "%Y-%m-%d %H:%M:%S"); // https://github.com/espressif/arduino-esp32/blob/ccacb7e3d1dd0e58b309c83e5ebc2302ce97c7b2/cores/esp32/Print.h#L98
ntpSyncCompleted = true;
}
void WiFiStationConnection(WiFiEvent_t event, WiFiEventInfo_t info) {
Serial.println("...Network connection established");
}
void WiFiStationGotIP(WiFiEvent_t event, WiFiEventInfo_t info) {
Serial.print("Obtained an IP address = ");
Serial.println(WiFi.localIP());
wifiConnected = true;
}
void WiFiStationDisconnected(WiFiEvent_t event, WiFiEventInfo_t info) {
wifiConnected = false;
Serial.print("Disconnected from network. (reason: ");
Serial.println(info.wifi_sta_disconnected.reason);
Serial.println(")\nAttempting to reconnect.");
WiFi.begin(ssid, wifiPassword);
}
// Function that displays the current time every second (can force display by passing true)
//
bool checkTime(bool forceDisplay = false) {
static unsigned long lastDisplay = -2000;
if (ntpSyncCompleted) {
time_t timestamp = time( NULL );
struct tm *pTime = localtime(×tamp );
if (forceDisplay || (millis() - lastDisplay >= 1000)) {
Serial.println(pTime, "%Y-%m-%d %H:%M:%S"); // https://github.com/espressif/arduino-esp32/blob/ccacb7e3d1dd0e58b309c83e5ebc2302ce97c7b2/cores/esp32/Print.h#L98
lastDisplay = millis();
}
uint16_t now = convertToAlarm(pTime->tm_hour, pTime->tm_min);
if (now >= alarms[nextAlarmIndex]) {
nextAlarmIndex = (nextAlarmIndex + 1) % numAlarms;
Serial.println("***** Bell ringing *****");
tone(bellRelayPin, 500, 3000); // buzz for 3 seconds
}
}
return ntpSyncCompleted;
}
void report_sntp() {
static uint32_t lastMs;
uint32_t now = millis();
if (now - lastMs > 1000) {
lastMs = now;
// https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/system/system_time.html#_CPPv420sntp_get_sync_statusv
int syncStatus = sntp_get_sync_status();
static int lastSyncStatus = -1;
if (syncStatus != lastSyncStatus) {
Serial.print("sntp_sync_status=");
Serial.println(syncStatus);
lastSyncStatus = syncStatus;
switch (syncStatus) {
case SNTP_SYNC_STATUS_RESET:
Serial.println("SNTP_SYNC_STATUS_RESET");
break;
case SNTP_SYNC_STATUS_COMPLETED:
Serial.println("SNTP_SYNC_STATUS_COMPLETED");
break;
case SNTP_SYNC_STATUS_IN_PROGRESS:
Serial.println("SNTP_SYNC_STATUS_IN_PROGRESS");
break;
default:
Serial.print("SNTP_STATUS not known for ");
Serial.println(syncStatus);
}
}
// https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/system/system_time.html#_CPPv418sntp_get_sync_modev
int syncMode = sntp_get_sync_mode();
static int lastSyncMode = -1;
if (syncMode != lastSyncMode) {
Serial.print("sntp_sync_mode=");
switch (syncMode) {
case SNTP_SYNC_MODE_IMMED:
Serial.println("SNTP_SYNC_MODE_IMMED");
break;
case SNTP_SYNC_MODE_SMOOTH:
Serial.println("SNTP_SYNC_MODE_SMOOTH");
break;
default:
Serial.print("SNTP_SYNC_MODE not known for ");
Serial.println(syncMode);
}
lastSyncMode = syncMode;
}
// sntp update interval
//
static int32_t last_sync_interval = -1;
int32_t sync_interval = sntp_get_sync_interval() ;
if (sync_interval != last_sync_interval) {
last_sync_interval = sync_interval;
Serial.print("sntp sync_interval:");
Serial.println(sync_interval);
}
// adjtime
// https://www.man7.org/linux/man-pages/man3/adjtime.3.html
// for adjtime
static timeval delta {0, 0}, olddelta {0, 0};
adjtime(0, &olddelta);
if (olddelta.tv_sec + olddelta.tv_usec) {
Serial.print(" adjtime:");
Serial.print(olddelta.tv_sec);
Serial.print(" ");
Serial.print(olddelta.tv_usec);
Serial.print(" ");
}
Serial.print('.');
}
}
void doSettings() {
static uint32_t lastIO = -100;
uint32_t now = millis();
if (now - lastIO < 100) return;
lastIO = now;
static int lastSmoothMode = -1;
int smoothMode = digitalRead(smoothPin);
if (smoothMode != lastSmoothMode) {
lastSmoothMode = smoothMode;
sntp_set_sync_mode(smoothMode == LOW ? SNTP_SYNC_MODE_SMOOTH : SNTP_SYNC_MODE_IMMED );
}
}
void setup() {
pinMode(bellRelayPin, OUTPUT);
Serial.begin(115200);
Serial.println("NTP based Bell Management");
Serial.println("Connecting to WiFi network");
// Callbacks to check if the network is operational
WiFi.onEvent(WiFiStationConnection, WiFiEvent_t::ARDUINO_EVENT_WIFI_STA_CONNECTED);
WiFi.onEvent(WiFiStationGotIP, WiFiEvent_t::ARDUINO_EVENT_WIFI_STA_GOT_IP);
WiFi.onEvent(WiFiStationDisconnected, WiFiEvent_t::ARDUINO_EVENT_WIFI_STA_DISCONNECTED);
// Configure the time retrieval from NTP, setting the timezone to France
// and define winter / summer time
// Last Sunday of March at 2:00 AM: GMT+2
// Last Sunday of October at 3:00 AM: GMT+1
// configTzTime("CET-1CEST-2,M3.5.0/02:00:00,M10.5.0/03:00:00", ntpServer);
configTzTime("EST5EDT", ntpServer);
// Callback to be notified when time sync with the NTP server is successful
sntp_set_time_sync_notification_cb(ntpSyncCallback);
// Shorten the sync interval
Serial.print("SNTP sync interval:");
Serial.println(sntp_get_sync_interval());
sntp_set_sync_interval(30);
Serial.print("SNTP sync interval:");
Serial.println(sntp_get_sync_interval());
// Set for smooth
// https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/system/system_time.html#_CPPv418sntp_set_sync_mode16sntp_sync_mode_t
sntp_set_sync_mode(SNTP_SYNC_MODE_SMOOTH);
WiFi.begin(ssid, wifiPassword);
pinMode(smoothPin, INPUT_PULLUP);
}
void loop() {
checkTime();
doSettings();
report_sntp();
// If your code requires WiFi, you can check
if (wifiConnected) {
// •••
}
// Code that does not require WiFi goes here
delay(10);
}