@NeoX
I use a Nano, Ethernet, LCD display, and GPS for an NTP server on my isolated network to provide the time for [suspect] cameras.
#define VER 12.2
bool debug = false; // must be true to have debug statements go to serial
/*
NTP Time Server:
Modified by Gregg Ferry
using softwareserial instead of Serial1
using GY-GPS6MV2
This code is in the public domain.
ver 12
time server gets time from Arduino clock instead of from gps
*/
#define enableEthernet false
#define enableGPS true
#define enableDisplay true
#define minimizeSerialTraffic true
#if enableEthernet
// ethernet
#include <SPI.h> // needed for Arduino versions later than 0018
#include <Ethernet.h>
#include <EthernetUdp.h> // UDP library from: bjoern@cs.stanford.edu 12/30/2008
// Time Server MAC address
byte mac[] = {0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF };
// NTP Server public IP Address
IPAddress ip(192, 168, 2, 130);
// Time Server Port
#define NTP_PORT 123
unsigned int localPort = 123;
static const int NTP_PACKET_SIZE = 48;
// buffers for receiving and sending data
byte packetBuffer[NTP_PACKET_SIZE];
// An Ethernet UDP instance
EthernetUDP Udp;
int packetSize;
// NTP since 1900/01/01
//#include <PGMWrap.h>
//const uint8_t daysInMonth [] PROGMEM = { 31,28,31,30,31,30,31,31,30,31,30,31 }; //const or compiler complains
const uint8_t daysInMonth [] = { 31,28,31,30,31,30,31,31,30,31,30,31 }; //const or compiler complains
const unsigned long seventyYears = 2208988800UL; // to convert unix time to epoch
#endif
#if enableGPS
// GPS
#include <TinyGPS++.h>
//GPS instance
TinyGPSPlus tinyGPS;
#include <NeoSWSerial.h>
//#include <SoftwareSerial.h>
#define rxPin 5
#define txPin 4
NeoSWSerial GPSSerial(rxPin, txPin); // CONNECT BT RX PIN TO ARDUINO txPin | CONNECT BT TX PIN TO ARDUINO rxPin
char incharBefore=255;
char inchar=255;
#endif
#if enableDisplay
// Display
#include "SSD1306Ascii.h"
#include "SSD1306AsciiAvrI2c.h"
#define I2C_ADDRESS 0x3C
SSD1306AsciiAvrI2c oled;
#endif
bool TimeHasBeenSet = false;
bool firstPass = true;
//Time
#include <Timezone.h>
//#include "Time.h"
TimeChangeRule myDST = {"PDT", Second, Sun, Mar, 2, -420}; // Daylight time = UTC - 7 hours
TimeChangeRule mySTD = {"PST", First, Sun, Nov, 2, -480}; // Standard time = UTC - 8 hours
Timezone myTZ(myDST, mySTD);
TimeChangeRule *tcr; // pointer to the time change rule, use to get TZ abbrev
time_t local;
String timeString = "hh:mm:ss";
String dateString = "yyyy MMM dd";
const String localDaysOfWeek[] = {"Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday" };
const String localMonthsOfYear[] = {"JAN","FEB","MAR","APR","MAY","JUN","JUL","AUG","SEP","OCT","NOV","DEC"};
uint8_t localDayOfWeek;
uint8_t previousHour = 0;
uint8_t previousMinute = 0;
uint8_t previousSecond = 0;
unsigned long timestamp1970;
//-----------------------------------------------------------------------------------printTimes
void printTimes(){
Serial.print(F("GPS "));
Serial.print(tinyGPS.date.year());Serial.print(F("/"));
Serial.print(tinyGPS.date.month());Serial.print(F("/"));
Serial.print(tinyGPS.date.day());Serial.print(F(" "));
Serial.print(tinyGPS.time.hour());Serial.print(F(":"));
Serial.print(tinyGPS.time.minute());Serial.print(F(":"));
Serial.println(tinyGPS.time.second());
Serial.print(F("UTC "));
Serial.print(year());Serial.print(F("/"));
Serial.print(month());Serial.print(F("/"));
Serial.print(day());Serial.print(F(" "));
Serial.print(hour());Serial.print(F(":"));
Serial.print(minute());Serial.print(F(":"));
Serial.println(second());
Serial.print(F("local "));
Serial.print(year(local));Serial.print(F("/"));
Serial.print(month(local));Serial.print(F("/"));
Serial.print(day(local));Serial.print(F(" "));
Serial.print(hour(local));Serial.print(F(":"));
Serial.print(minute(local));Serial.print(F(":"));
Serial.println(second(local));
Serial.print(F(" "));
Serial.print(dateString);Serial.print(F(" "));
Serial.println(timeString);
}
//-----------------------------------------------------------------------------------setTimeAndDateStrings
void setTimeAndDateStrings(){
local = myTZ.toLocal(now(), &tcr); // two days ago -172800
char buf[32];
sprintf(buf, "%.2d:%.2d:%.2d",hour(local), minute(local), second(local));
timeString = String(buf);
// char m[4]; // temporary storage for month string (DateStrings.cpp uses shared buffer)
// strcpy(m, monthShortStr(month(local)));
// sprintf(buf, "%.2d %s %d",day(local), m, year(local));
// dateString = String(buf);
sprintf(buf, "%.2d ",day(local));
dateString = String(buf);
dateString += localMonthsOfYear[month(local)-1];
sprintf(buf," %d",year(local));
dateString += String(buf);
localDayOfWeek = weekday(local)-1;
}
#if enableGPS
//-----------------------------------------------------------------------------------getgps
static bool getgps()
{ // and update arduino time
if (GPSSerial.available()==0) return false;
// while (GPSSerial.available()) {
incharBefore = inchar;
inchar = GPSSerial.read();
tinyGPS.encode(inchar);
if (!TimeHasBeenSet) Serial.print(inchar);
// }
if ((inchar != 10) || (incharBefore != 13)) return false; // make sure to full NMEA statement was received
inchar = 0; incharBefore = 0; // NMEA statement received, reset terminators
if(tinyGPS.time.isValid()){
/*
* setTime(
tinyGPS.time.hour(),
tinyGPS.time.minute(),
tinyGPS.time.second(),
tinyGPS.date.day(),
tinyGPS.date.month(),
tinyGPS.date.year()
);
*/
int hh=tinyGPS.time.hour();
int mm=tinyGPS.time.minute();
int ss=tinyGPS.time.second();
int DD=tinyGPS.date.day();
int MM=tinyGPS.date.month();
int YY=tinyGPS.date.year();
setTime(hh,mm,ss,DD,MM,YY);
local = myTZ.toLocal(now(), &tcr); //get the local time
setTimeAndDateStrings();
if (!TimeHasBeenSet) printTimes();
TimeHasBeenSet = true;
return true;
}
else {
return false;
}
}
#endif
#if enableDisplay
//-----------------------------------------------------------------------------------displayGPStimeSetTheTime
void displayGPStimeSetTheTime(bool TimeSetByGPS){
oled.setCursor(0,0); // Start at top-left corner
if (TimeSetByGPS) oled.print(F(" ")); else oled.print(F("!"));
}
//-----------------------------------------------------------------------------------displayAwaitingGPS
void displayAwaitingGPS() {
oled.clear();
oled.setCursor(0,0); // Start at top-left corner
oled.println(F("NTP Server"));
oled.println(F(" Awating"));
oled.println(F(" GPS Lock "));
Serial.println(F(" Awating GPS Lock "));
}
//-----------------------------------------------------------------------------------displayNewDay
void displayNewDay(){
previousHour = hour(local);
previousMinute= minute(local);
previousSecond = second(local);
oled.clear();
oled.setCursor(12,0); // center time string
oled.print(timeString);
uint8_t s = (10-localDaysOfWeek[localDayOfWeek].length())*6;
oled.setCursor(s,3);
oled.print(localDaysOfWeek[localDayOfWeek]);
oled.setCursor(0,6);
oled.print(dateString);
}
//-----------------------------------------------------------------------------------displayTime
void displayTime(){
// digital clock display of the time
if ((second(local)==0 && minute(local)==0 && hour(local)==0) || firstPass){ // new day
firstPass = false;
displayNewDay();
}
else {
if (hour(local) != previousHour){
previousHour = hour(local);
oled.setCursor(12,0);
oled.print(timeString);
}
else {
if (minute(local) != previousMinute){
previousMinute= minute(local);
oled.setCursor(48,0);
oled.print(timeString.substring(3));
}
else {
if (second(local) != previousSecond){
previousSecond = second(local);
oled.setCursor(84,0);
oled.print(timeString.substring(6));
}
}
}
}
}
// send to serial monitor
#else
void displayGPStimeSetTheTime(bool TimeSetByGPS){
}
void displayAwaitingGPS() {
Serial.println(F(" Awating GPS Lock "));
}
void displayTime(){
#if !minimizeSerialTraffic
Serial.println();
printTimes();
#endif
}
#endif
//-----------------------------------------------------------------------------------setup
#if enableEthernet
void setupEthernet(){
// start the Ethernet and UDP:
Ethernet.begin(mac,ip);
Udp.begin(localPort);
Serial.print(Ethernet.localIP()); Serial.print(F(" "));Serial.println(NTP_PORT);
}
#endif
#if enableGPS
void setupGPS(){
GPSSerial.begin(9600); // start GPS module UART
// Disable everything but $GPRMC
// Note the following sentences are for UBLOX NEO6MV2 GPS
GPSSerial.write("$PUBX,40,GLL,0,0,0,0,0,0*5C\r\n");
GPSSerial.write("$PUBX,40,VTG,0,0,0,0,0,0*5E\r\n");
GPSSerial.write("$PUBX,40,GSV,0,0,0,0,0,0*59\r\n");
GPSSerial.write("$PUBX,40,GGA,0,0,0,0,0,0*5A\r\n");
GPSSerial.write("$PUBX,40,GSA,0,0,0,0,0,0*4E\r\n");
}
#endif
#if enableDisplay
void setupDisplay(){
oled.begin(&Adafruit128x64, I2C_ADDRESS);
oled.setFont(System5x7);
oled.set2X();
}
#endif
void setup() {
Serial.begin(115200);
Serial.print("NTP Server with display version ");Serial.println(VER);
#if enableDisplay
setupDisplay();
#endif
displayAwaitingGPS();
#if enableEthernet
setupEthernet();
#endif
#if enableGPS
setupGPS();
#endif
}
//-----------------------------------------------------------------------------------loop
void loop() {
/*
while (Serial.available()){
char inChar = Serial.read();
if (inChar == 'd' || inChar == 'D') {
debug = !debug;
if (debug) Serial.println(F("debug is ON"));else Serial.println(F("debug is off"));
}
}
*/
#if enableGPS
if (getgps()){
if(TimeHasBeenSet) displayTime();
}
#endif
#if enableEthernet
// if there's data available, read a packet
packetSize = Udp.parsePacket();
if(packetSize) processNTP();
#endif
}
#if enableEthernet
//-----------------------------------------------------------------------------------numberOfSecondsSince1900Epoch
unsigned long int numberOfSecondsSince1900Epoch(uint16_t y, uint8_t m, uint8_t d, uint8_t h, uint8_t mm, uint8_t s) {
if (y >= 1970)
y -= 1970;
uint16_t daysLocal = d;
for (uint8_t i = 1; i < m; ++i) daysLocal += daysInMonth[i - 1];
// for (uint8_t i = 1; i < m; ++i)
// daysLocal += pgm_read_byte(daysInMonth + i - 1);
if (m > 2 && y % 4 == 0)
++daysLocal;
daysLocal += 365 * y + (y + 3) / 4 - 1;
return daysLocal*24L*3600L + h*3600L + mm*60L + s + seventyYears;
}
//-----------------------------------------------------------------------------------processNTP
void processNTP() {
Udp.read(packetBuffer,NTP_PACKET_SIZE);
IPAddress Remote = Udp.remoteIP();
int PortNum = Udp.remotePort();
/*
if (debug) {
Serial.println();
Serial.print("Received UDP packet size ");
Serial.println(packetSize);
Serial.print("From ");
for (int i =0; i < 4; i++)
{
Serial.print(Remote[i], DEC);
if (i < 3)
{
Serial.print(".");
}
}
Serial.print(", port ");
Serial.print(PortNum);
byte LIVNMODE = packetBuffer[0];
Serial.print(" LI, Vers, Mode :");
Serial.print(packetBuffer[0],HEX);
byte STRATUM = packetBuffer[1];
Serial.print(" Stratum :");
Serial.print(packetBuffer[1],HEX);
byte POLLING = packetBuffer[2];
Serial.print(" Polling :");
Serial.print(packetBuffer[2],HEX);
byte PRECISION = packetBuffer[3];
Serial.print(" Precision :");
Serial.println(packetBuffer[3],HEX);
for (int z = 0; z < NTP_PACKET_SIZE; z++) {
Serial.print(packetBuffer[z],HEX);
if (((z+1) % 4) == 0) {
Serial.println();
}
}
Serial.println();
} // end of debug statements
*/
packetBuffer[0] = 0b00100100; // LI, Version, Mode
packetBuffer[1] = 1 ; // stratum
packetBuffer[2] = 6 ; // polling minimum
packetBuffer[3] = 0xFA; // precision
packetBuffer[7] = 0; // root delay
packetBuffer[8] = 0;
packetBuffer[9] = 8;
packetBuffer[10] = 0;
packetBuffer[11] = 0; // root dispersion
packetBuffer[12] = 0;
packetBuffer[13] = 0xC;
packetBuffer[14] = 0;
/*
uint16_t y=year();
uint8_t m=month();
uint8_t d=day();
uint8_t h=hour();
uint8_t mm=minute();
uint8_t s=second();
if (y >= 1970) y -= 1970;
uint16_t daysLocal = d;
for (uint8_t i = 1; i < m; ++i) daysLocal += daysInMonth[i - 1];
if (m > 2 && y % 4 == 0) ++daysLocal;
daysLocal += 365 * y + (y + 3) / 4 - 1;
timestamp1970 = daysLocal*24L*3600L + h*3600L + mm*60L + s + seventyYears;
*/
// uint16_t utcYY=year();
// uint8_t utcMM=month();
// uint8_t utcDD=day();
// uint8_t utchh=hour();
// uint8_t utcmm=minute();
// uint8_t utcss=second();
// timestamp1970 = numberOfSecondsSince1900Epoch(utcYY,utcMM,utcDD,utchh,utcmm,utcss);
// timestamp1970 = numberOfSecondsSince1900Epoch(2022,4,30,10,10,10);
timestamp1970 = numberOfSecondsSince1900Epoch(year(),month(),day(),hour(),minute(),second());
/*
*
timestamp1970 = numberOfSecondsSince1900Epoch(
tinyGPS.date.year(),
tinyGPS.date.month(),
tinyGPS.date.day(),
tinyGPS.time.hour(),
tinyGPS.time.minute(),
tinyGPS.time.second()
);
*/
unsigned long tempval = timestamp1970;
packetBuffer[12] = 71; //"G";
packetBuffer[13] = 80; //"P";
packetBuffer[14] = 83; //"S";
packetBuffer[15] = 0; //"0";
// reference timestamp1970
packetBuffer[16] = (tempval >> 24) & 0XFF;
tempval = timestamp1970;
packetBuffer[17] = (tempval >> 16) & 0xFF;
tempval = timestamp1970;
packetBuffer[18] = (tempval >> 8) & 0xFF;
tempval = timestamp1970;
packetBuffer[19] = (tempval) & 0xFF;
packetBuffer[20] = 0;
packetBuffer[21] = 0;
packetBuffer[22] = 0;
packetBuffer[23] = 0;
//copy originate timestamp from incoming UDP transmit timestamp
packetBuffer[24] = packetBuffer[40];
packetBuffer[25] = packetBuffer[41];
packetBuffer[26] = packetBuffer[42];
packetBuffer[27] = packetBuffer[43];
packetBuffer[28] = packetBuffer[44];
packetBuffer[29] = packetBuffer[45];
packetBuffer[30] = packetBuffer[46];
packetBuffer[31] = packetBuffer[47];
//receive timestamp
packetBuffer[32] = (tempval >> 24) & 0XFF;
tempval = timestamp1970;
packetBuffer[33] = (tempval >> 16) & 0xFF;
tempval = timestamp1970;
packetBuffer[34] = (tempval >> 8) & 0xFF;
tempval = timestamp1970;
packetBuffer[35] = (tempval) & 0xFF;
packetBuffer[36] = 0;
packetBuffer[37] = 0;
packetBuffer[38] = 0;
packetBuffer[39] = 0;
//transmitt timestamp
packetBuffer[40] = (tempval >> 24) & 0XFF;
tempval = timestamp1970;
packetBuffer[41] = (tempval >> 16) & 0xFF;
tempval = timestamp1970;
packetBuffer[42] = (tempval >> 8) & 0xFF;
tempval = timestamp1970;
packetBuffer[43] = (tempval) & 0xFF;
packetBuffer[44] = 0;
packetBuffer[45] = 0;
packetBuffer[46] = 0;
packetBuffer[47] = 0;
// Reply to the IP address and port that sent the NTP request
Udp.beginPacket(Remote, PortNum);
Udp.write(packetBuffer,NTP_PACKET_SIZE);
Udp.endPacket();
}
#else
void processNTP() {}
#endif