Skip to content

UdpNtpClient rewritten in a clearer, more pedantic fashion. #2008

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jun 1, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions libraries/Ethernet/examples/udpntpclient_pedantic/RFC1305.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#ifndef RFC1305_H
#define RFC1305_H
/*
* see https://www.eecis.udel.edu/~mills/database/rfc/rfc1305/rfc1305c.pdf
* https://tools.ietf.org/html/rfc1305
*/

#pragma pack(1)
struct sRFC1305 {
// NOTE all fields are BIG-ENDIAN so must be swapped on little endian machines
uint8_t MODE:3;
uint8_t VN:3;
uint8_t LI:2;
uint8_t stratum;
uint8_t poll;
uint8_t precision;
int16_t rootdelay_main;
uint16_t rootdelay_fraction;
int16_t rootdispersion_main;
uint16_t rootdispersion_fraction;
uint8_t identifier[4];
// 64 bit timestamps contain 32 bit whole part + 32 bit fractional part
uint32_t referencetimestamp_main;
uint32_t referencetimestamp_fraction;
uint32_t origintimestamp_main;
uint32_t origintimestamp_fraction;
uint32_t receivetimestamp_main;
uint32_t receivetimestamp_fraction;
uint32_t transmittimestamp_main;
uint32_t transmittimestamp_fraction;
};
#pragma pack(0)

#define LI_NOWARNING 0
#define LI_61_SEC 1
#define LI_59_SEC 2
#define LI_ALARM 3

#define VERN 4

#define MODE_SYMMETRIC_ACTIVE 1
#define MODE_SYMMETRIC_PASSIVE 2
#define MODE_CLIENT 3
#define MODE_SERVER 4
#define MODE_BROADCAST 5

#define ENDIAN_SWAP_32(l) ((l>>24) |((l>>16)<<8)&0xff00 | ((l>>8)<<16)&0xff0000 | (l << 24))
#define ENDIAN_SWAP_16(l) ((l>>8) | (l << 8))
#endif

Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
/*

Udp NTP Client

Get the time from a Network Time Protocol (NTP) time server
Demonstrates use of UDP sendPacket and ReceivePacket
For more on NTP time servers and the messages needed to communicate with them,
see http://en.wikipedia.org/wiki/Network_Time_Protocol

created 4 Sep 2010
by Michael Margolis
modified 9 Apr 2012
by Tom Igoe

This code is in the public domain.

Modified by David Henry to show where all the 'magic numbers' come from.
You need to read the RFC-1305 spec to understand https://tools.ietf.org/html/rfc1305
[email protected]

*/

#include <SPI.h>
#include <Ethernet.h>
#include <EthernetUdp.h>
#include "RFC1305.h"

// Enter a MAC address for your controller below.
// Newer Ethernet shields have a MAC address printed on a sticker on the shield
byte mac[] = {
0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED
};

unsigned int localPort = 8888; // local port to listen for UDP packets

char timeServer[] = "time.nist.gov"; // time.nist.gov NTP server

#define NTP_PACKET_SIZE sizeof(struct sRFC1305)

struct sRFC1305 packetBuffer; //buffer to hold incoming and outgoing packets

// A UDP instance to let us send and receive packets over UDP
EthernetUDP Udp;

void setup()
{
// Open serial communications and wait for port to open:
Serial.begin(9600);
while (!Serial) {
; // wait for serial port to connect. Needed for Leonardo only
}
//Serial.println(NTP_PACKET_SIZE); // just for debugging
//Serial.println(ENDIAN_SWAP_32(0x11223344),HEX);
//Serial.println(ENDIAN_SWAP_16(0xAABB),HEX);
// start Ethernet and UDP
if (Ethernet.begin(mac) == 0) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ethernet? I feel adding an example which uses WiFi would be more relevant to the scope of this project.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The original client was in the Ethernet library and then copied to the Wifi library. Just like I updated the Ethernet version I could just go ahead and apply the same to the Wifi version.

Serial.println("Failed to configure Ethernet using DHCP");
// no point in carrying on, so do nothing forevermore:
for (;;)
;
}
Udp.begin(localPort);
}

void loop()
{
sendNTPpacket(timeServer); // send an NTP packet to a time server

// wait to see if a reply is available
delay(1000);
if ( Udp.parsePacket() ) {
// We've received a packet, read the data from it
Udp.read((byte *)&packetBuffer, NTP_PACKET_SIZE); // read the packet into the buffer
#if 0 // just for debugging
Serial.println(ENDIAN_SWAP_16(packetBuffer.rootdelay_main),HEX);
Serial.println(ENDIAN_SWAP_16(packetBuffer.rootdelay_fraction),HEX);
Serial.println(ENDIAN_SWAP_16(packetBuffer.rootdispersion_main),HEX);
Serial.println(ENDIAN_SWAP_16(packetBuffer.rootdispersion_fraction),HEX);
Serial.println(ENDIAN_SWAP_32(packetBuffer.referencetimestamp_main),HEX);
Serial.println(ENDIAN_SWAP_32(packetBuffer.referencetimestamp_fraction),HEX);
Serial.println(ENDIAN_SWAP_32(packetBuffer.origintimestamp_main),HEX);
Serial.println(ENDIAN_SWAP_32(packetBuffer.origintimestamp_fraction),HEX);
Serial.println(ENDIAN_SWAP_32(packetBuffer.receivetimestamp_main),HEX);
Serial.println(ENDIAN_SWAP_32(packetBuffer.receivetimestamp_fraction),HEX);
Serial.println(ENDIAN_SWAP_32(packetBuffer.transmittimestamp_main),HEX);
Serial.println(ENDIAN_SWAP_32(packetBuffer.transmittimestamp_fraction),HEX);
#endif
Serial.print("Delay ");
Serial.print(ENDIAN_SWAP_16(packetBuffer.rootdelay_main));Serial.print(".");Serial.println(ENDIAN_SWAP_16(packetBuffer.rootdelay_fraction));
Serial.print("Seconds since Jan 1 1900 = " );
unsigned long secsSince1900 = ENDIAN_SWAP_32(packetBuffer.transmittimestamp_main);
Serial.print(secsSince1900);Serial.print(".");Serial.println(ENDIAN_SWAP_32(packetBuffer.transmittimestamp_fraction));

// now convert NTP time into everyday time:
Serial.print("Unix time = ");
// Unix time starts on Jan 1 1970. In seconds, that's 2208988800:
const unsigned long seventyYears = 2208988800UL;
// subtract seventy years:
unsigned long epoch = secsSince1900 - seventyYears;
// print Unix time:
Serial.println(epoch);

#define SECS_PER_MINUTE 60
#define SECS_PER_HOUR 3600
#define SECS_PER_DAY 86400L

// print the hour, minute and second:
Serial.print("The UTC time is "); // UTC is the time at Greenwich Meridian (GMT)
Serial.print((epoch % SECS_PER_DAY) / SECS_PER_HOUR);
Serial.print(':');
if ( ((epoch % SECS_PER_HOUR) / SECS_PER_MINUTE) < 10 ) {
// In the first 10 minutes of each hour, we'll want a leading '0'
Serial.print('0');
}
Serial.print((epoch % SECS_PER_HOUR) / SECS_PER_MINUTE);
Serial.print(':');
if ( (epoch % SECS_PER_MINUTE) < 10 ) {
// In the first 10 seconds of each minute, we'll want a leading '0'
Serial.print('0');
}
Serial.println(epoch % SECS_PER_MINUTE); // print the second
}
// wait ten seconds before asking for the time again
delay(10000);
}

// send an NTP request to the time server at the given address
unsigned long sendNTPpacket(char* address)
{
// set all bytes in the buffer to 0
memset((char *)&packetBuffer, 0, NTP_PACKET_SIZE);
// Initialize values needed to form NTP request
// (see URL above for details on the packets)
packetBuffer.LI = LI_ALARM;
packetBuffer.VN = VERN;
packetBuffer.MODE = MODE_CLIENT;
packetBuffer.stratum = 0;
packetBuffer.poll = 6;
packetBuffer.precision = -20; // ? copied from original UdnNtpClient code
packetBuffer.identifier[0] = '1'; // I've no idea where this ID comes from
packetBuffer.identifier[1] = 'N';
packetBuffer.identifier[2] = '1';
packetBuffer.identifier[3] = '4';
// Serial.println(*(uint8_t *)&packetBuffer,HEX);
// all NTP fields have been given values, now
// you can send a packet requesting a timestamp:
Udp.beginPacket(address, 123); //NTP requests are to port 123
Udp.write((byte *)&packetBuffer, NTP_PACKET_SIZE);
Udp.endPacket();
}










4 changes: 4 additions & 0 deletions libraries/Ethernet/examples/udpntpclient_pedantic/readme.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
This is essentially the same as the original, classic, example. However instead of treating the NTP message as a mysterious array of bytes it is referred to by its offical name and structure.
The concept of Big-Endian and Little_Endian data is introduced, which may be new to many readers.
Actually there is a good historical reason for the dominance of BigEndian in internet protocols. In the early days of networking Sun computers were the major force and they had BigEndian processors. Later on Intel entered the field with LittleEndian processors and the rest is history. I wonder how many CPU cycles are wasted every second converting between the two?
Treating byte arrays as structures encourages good programming style as 'magic numbers' are replaced by meaningful names.