Browse code

fixed bug in NTP clock synchronization

Gandolf authored on 12/02/2019 02:42:25
Showing 1 changed files
... ...
@@ -2,14 +2,14 @@
2 2
  Background Radiation Monitor - Web Server
3 3
  
4 4
  A simple web server that makes available to clients over the Internet
5
- readings from a MightyOhm Geiger counter. The MightyOhm is connected to
6
- an Arduino Uno with attached Ethernet shield.  This software module
5
+ readings from a MightyOhm Geiger counter. The MightyOhm is connected
6
+ to an Arduino Uno with attached Ethernet shield.  This software module
7 7
  runs on the Arduino Uno an embedded HTTP server by which Internet
8 8
  applications can query the MightyOhm for Geiger counter readings.
9 9
  Also, this software runs a Network Time Protocol (NTP) client, that
10 10
  periodically synchronizes the local system clock to network time.
11
- Included is a simple command line interface that may be used to change the
12
- network interface IP address, NTP server address, or configure a
11
+ Included is a simple command line interface that may be used to change
12
+ the network interface IP address, NTP server address, or configure a
13 13
  verbose output mode.
14 14
  
15 15
  Copyright 2018 Jeff Owrey
... ...
@@ -75,6 +75,13 @@
75 75
          NTP address set to "pool.ntp.org" per ntp.org request to use
76 76
          (in order to facilitate load balancing) the fully qualified
77 77
          domain name instead of individual server IP addresses.
78
+   * v18 released 01 Nov 2019 by J L Owrey
79
+       - fixed a bug in NTP time synchronization whereby the network time
80
+         synchronization would only occur during boot up.  Thereafter NTP 
81
+         time synchronization would fail to happen, resulting in a large 
82
+         amount of clock drift.
83
+
84
+12345678901234567890123456789012345678901234567890123456789012345678901234567890         
78 85
 */
79 86
 
80 87
 /***  PREPROCESSOR DEFINES  ***/
... ...
@@ -85,8 +92,8 @@
85 92
  Define the header and version number displayed at startup
86 93
  and also by the 'view settings' command.
87 94
 */
88
-#define STARTUP_HEADER "\n\rRadmon v1.7 (c) 2019\n"
89
-#define RADMON_VERSION "v1.7"
95
+#define STARTUP_HEADER "\n\rRadmon v1.8 (c) 2019"
96
+#define RADMON_VERSION "v1.8"
90 97
 /*
91 98
  The following define sets the MAC address of the device.  This
92 99
  address is a permanent attribute of the device's Ethernet interface,
... ...
@@ -95,7 +102,7 @@
95 102
  specific instance of the Ethernet shield.  This MAC address should
96 103
  be shown on a label affixed to the device housing.
97 104
 */
98
-#define ETHERNET_MAC_ADDRESS 0xNN, 0xNN, 0xNN, 0xNN, 0xNN, 0xNN
105
+#define ETHERNET_MAC_ADDRESS 0x90, 0xA2, 0xDA, 0x0D, 0x84, 0xF6
99 106
 /*
100 107
  The following defines an APIPA default address in the event that
101 108
  DHCP mode is ON and a DHCP address cannot be obtained.
... ...
@@ -106,7 +113,7 @@
106 113
  out over the device's USB port.  This heartbeat consists of a serial
107 114
  data string containing the current radiation reading and GM time.
108 115
 */
109
-#define SERIAL_UPDATE_INTERVAL 5000  //milli-seconds
116
+#define SERIAL_UPDATE_INTERVAL 1000  //milli-seconds
110 117
 /*
111 118
  The following define sets the port number the HTTP service will use to
112 119
  listen for requests from Internet clients.  Normally HTTP requests use
... ...
@@ -116,18 +123,19 @@
116 123
 /*
117 124
  The following defines are for configuring a local NTP client
118 125
  for synchronizing the local system clock to network time.
119
- Note that the default setting is the IP address of the following
120
- time server:
121
-              time-c-b.nist.gov
126
+ Note that the ntp server address should be sent to the local
127
+ server pool of the country where the radmon will be used.  See
128
+ the web site 'ntp.org' for details. Users in the USA should set
129
+ the ntp server to 'us.pool.ntp.org'.
122 130
 */
123 131
 #define DEFAULT_NTP_SERVER_ADDR "pool.ntp.org"
124 132
 #define NTP_PORT 8888
125
-#define NTP_PACKET_SIZE 48 // NTP time stamp is in the first 48 bytes of the message
133
+#define NTP_PACKET_SIZE 48 // NTP time in the first 48 bytes of the message
126 134
 /*
127 135
  The following defines how often the system clock gets synchronized
128 136
  to network time.
129 137
 */
130
-#define NET_SYNCH_INTERVAL 21600 //number in seconds - 4 times a day
138
+#define NTP_SYNCH_INTERVAL 43200 // number in seconds - 2 times a day
131 139
 /*
132 140
  Number of retries if first time server request fails.
133 141
 */
... ...
@@ -190,7 +198,7 @@ SoftwareSerial MightyOhmTxOut(5, 6);
190 198
 */
191 199
 char mightOhmData[MIGHTYOHM_DATA_STRING_LENGTH + 1];
192 200
 unsigned long nextSerialUpdateTime = 0;
193
-time_t nextClockSynchTime = -1;
201
+unsigned long nextClockSynchTime = 0;
194 202
 /*
195 203
  Create global variables to store the verbose mode state (ON or OFF)
196 204
  and the IP address mode state (static or DHCP).
... ...
@@ -240,14 +248,11 @@ void setup()
240 248
     }
241 249
   }
242 250
   Serial.print(F("IP address: ")); Serial.println(Ethernet.localIP());
243
-  /*
244
-   Start up NTP client service.
245
-  */
246
-  Udp.begin(NTP_PORT);
247 251
   /*
248 252
     Synchronize the system clock to network time.
249 253
   */
250 254
   synchronizeSystemClock();
255
+  nextClockSynchTime = now() + NTP_SYNCH_INTERVAL;
251 256
   /*
252 257
    Start up the HTTP server.
253 258
   */
... ...
@@ -310,7 +315,7 @@ void loop() {
310 315
      Set the time for the next network NTP
311 316
      time synchronization to occur.
312 317
     */
313
-    nextClockSynchTime = now() + NET_SYNCH_INTERVAL;
318
+    nextClockSynchTime = now() + NTP_SYNCH_INTERVAL;
314 319
   }
315 320
   
316 321
   /*
... ...
@@ -323,36 +328,6 @@ void loop() {
323 328
   #endif
324 329
 }
325 330
 
326
-/*
327
- Synchronize the local system clock to
328
- network time provided by NTP time server.
329
-*/
330
-void synchronizeSystemClock()
331
-{
332
-  byte count;
333
-  
334
-  Serial.println(F("Synchronizing with network time server..."));
335
-
336
-  count = 0;
337
-  while (1)  // Attempt to synchronize 3 times
338
-  {
339
-    if (syncToNetworkTime() == 1) {
340
-      //  Synchronization successful
341
-      break;
342
-    }
343
-    if (count == TIME_SERVER_REQUEST_RETRIES) {
344
-      Serial.print(F("synch failed: "));
345
-      break;
346
-    }
347
-    count++;
348
-    delay(2000);
349
-  }
350
-  if (count > 0) {
351
-    Serial.print(count);Serial.println(F(" retries"));
352
-  }
353
-  return;
354
-}
355
-
356 331
 /*
357 332
   Handle HTTP GET requests from an HTTP client.
358 333
 */  
... ...
@@ -376,12 +351,12 @@ void listenForEthernetClients()
376 351
     firstLineFound = false;
377 352
   
378 353
     /*
379
-     * The beginning and end of an HTTP client request is always signaled
380
-     * by a blank line, that is, by two consecutive line feed and carriage 
381
-     * return characters "\r\n\r\n".  The following lines of code 
382
-     * look for this condition, as well as the url extension (following
383
-     * "GET").
384
-     */
354
+     The beginning and end of an HTTP client request is always signaled
355
+     by a blank line, that is, by two consecutive line feed and carriage 
356
+     return characters "\r\n\r\n".  The following lines of code 
357
+     look for this condition, as well as the url extension (following
358
+     "GET").
359
+    */
385 360
     
386 361
     while (client.connected())  {
387 362
       if (client.available()) {
... ...
@@ -417,7 +392,9 @@ void listenForEthernetClients()
417 392
             i = 1;
418 393
           }
419 394
 
420
-          if (firstLineFound && (c == '\n' || i > REQUEST_STRING_BUFFER_LENGTH - 2)) {
395
+          if (firstLineFound && (c == '\n' || i >
396
+              REQUEST_STRING_BUFFER_LENGTH - 2))
397
+          {
421 398
             processedCommand = true;
422 399
           }
423 400
         }
... ...
@@ -447,10 +424,13 @@ void listenForEthernetClients()
447 424
       else 
448 425
         transmitErrorPage(client);
449 426
     }
450
-
451
-    //Serial.println(mightOhmData);
427
+    client.println();
428
+    #ifdef DEBUG
429
+      Serial.println(mightOhmData);  //debug
430
+    #endif
431
+    
452 432
     // give the web browser time to receive the data
453
-    delay(10);
433
+    delay(20);
454 434
     // close the connection:
455 435
     client.stop();
456 436
   }
... ...
@@ -478,17 +458,17 @@ void transmitWebPage(EthernetClient client) {
478 458
 
479 459
   strcpy(strBuffer, mightOhmData);
480 460
   /*
481
-   * Send the actual HTML page the user will see in their web
482
-   * browser.
461
+   Send the actual HTML page the user will see in their web
462
+   browser.
483 463
   */
484
-  client.print(F("<!DOCTYPE HTML>" \
464
+  client.print(F("<!DOCTYPE HTML>"                               \
485 465
                  "<html><head><title>Radiation Monitor</title>"  \
486
-                 "<style>pre {font: 16px arial, sans-serif;}" \
487
-                 "p {font: 16px arial, sans-serif;}"
488
-                 "h2 {font: 24px arial, sans-serif;}</style>" \
489
-                 "</head><body><h2>Radiation Monitor</h2>" \
490
-                 "<p><a href=\"http://intravisions.com/\">" \
491
-                 "<i>IntraVisions.com</i></a></p>" \
466
+                 "<style>pre {font: 16px arial, sans-serif;}"    \
467
+                 "p {font: 16px arial, sans-serif;}"             \
468
+                 "h2 {font: 24px arial, sans-serif;}</style>"    \
469
+                 "</head><body><h2>Radiation Monitor</h2>"       \
470
+                 "<p><a href=\"http://intravisions.com/\">"      \
471
+                 "<i>IntraVisions.com</i></a></p>"               \
492 472
                  "<hr>"));
493 473
   /* Data Items */             
494 474
   client.print(F("<pre>UTC &#9;"));
... ...
@@ -520,8 +500,8 @@ void transmitRawData(EthernetClient client) {
520 500
 
521 501
   strcpy(strBuffer, mightOhmData);
522 502
   /*
523
-   * Format and transmit a JSON compatible data string.
524
-   */
503
+   Format and transmit a JSON compatible data string.
504
+  */
525 505
   client.print(F("$,UTC="));
526 506
   client.print(strtok(strBuffer, " "));
527 507
   client.print(F(" "));
... ...
@@ -545,13 +525,13 @@ void transmitRawData(EthernetClient client) {
545 525
 }
546 526
 
547 527
 /*
548
- * Send an error message web page back to the requesting
549
- * client when the client provides an invalid url extension.
550
- */
528
+ Send an error message web page back to the requesting
529
+ client when the client provides an invalid url extension.
530
+*/
551 531
 void transmitErrorPage(EthernetClient client) {
552
-  client.print(F("<!DOCTYPE HTML>" \
532
+  client.print(F("<!DOCTYPE HTML>"                                      \
553 533
                  "<html><head><title>Radiation Monitor</title></head>"  \
554
-                 "<body><h2>404 Not Found</h2>"  \
534
+                 "<body><h2>404 Not Found</h2>"                         \
555 535
                  "</body></html>"
556 536
                  ));
557 537
 }
... ...
@@ -584,7 +564,7 @@ void processRxByte( char RxByte )
584 564
      characters are not preserved in the temporary read buffer, so
585 565
      restore them to the MightyOhm data buffer, as well.
586 566
     */
587
-    sprintf( mightOhmData, "%d:%02d:%02d %d/%d/%d, %s",       \
567
+    sprintf( mightOhmData, "%d:%02d:%02d %d/%d/%d, %s",        \
588 568
           hour(), minute(), second(), month(), day(), year(),  \
589 569
           MIGHTYOHM_DATA_STRING_HEADER );
590 570
     /*
... ...
@@ -624,6 +604,48 @@ void processRxByte( char RxByte )
624 604
   return;
625 605
 } 
626 606
 
607
+/*
608
+ Synchronize the local system clock to
609
+ network time provided by NTP time server.
610
+*/
611
+void synchronizeSystemClock()
612
+{
613
+  byte count;
614
+
615
+  Serial.print(F("Synchronizing with NTP server: "));
616
+  Serial.print(timeServer);Serial.println(F("..."));
617
+
618
+  /*
619
+   * NOTICE!!!    NOTICE!!!   NOTICE!!!
620
+   * Due to a bug in the Ethernet library, it is necessary to reinitialize 
621
+   * the ethernet UDP library everytime after an  after an EthernetClient 
622
+   * class object has been instantiated.  Also, the Udp stop() function 
623
+   * must be called at the end of each session.
624
+   */
625
+  Udp.begin(NTP_PORT);  // see above comment
626
+
627
+  count = 1;
628
+  while (true)  // Attempt to synchronize 3 times
629
+  {
630
+    if (syncToNetworkTime() == 1) {
631
+      //  Synchronization successful
632
+      break;
633
+    }
634
+    if (count == TIME_SERVER_REQUEST_RETRIES) {
635
+      Serial.print(F("synch failed: "));
636
+      break;
637
+    }
638
+    count++;
639
+    delay(2000);
640
+  }
641
+  if (count > 1) {
642
+    Serial.print(count);Serial.println(F(" retries"));
643
+  }
644
+  
645
+  Udp.stop(); // see above comment
646
+  return;
647
+}
648
+
627 649
 /* 
628 650
   Send a UDP request packet to an NTP time server and listen for a reply.
629 651
   When the reply arrives, parse the received UPD packet and compute unix
... ...
@@ -632,9 +654,10 @@ void processRxByte( char RxByte )
632 654
 int syncToNetworkTime()
633 655
 {
634 656
   /*
635
-   Send a request to the NTP time server.
657
+   Send a request to the NTP time server.  Define a buffer to hold outgoing
658
+   and incoming packets.
636 659
   */
637
-  byte packetBuffer[ NTP_PACKET_SIZE]; //buffer to hold outgoing and incoming packets 
660
+  byte packetBuffer[ NTP_PACKET_SIZE]; // buffer to hold packets
638 661
   /*
639 662
    Send an NTP packet to the time server and allow for network lag
640 663
    before checking if a reply is available.
... ...
@@ -652,8 +675,8 @@ int syncToNetworkTime()
652 675
     */
653 676
     Udp.read( packetBuffer, NTP_PACKET_SIZE );
654 677
     /*
655
-     The timestamp starts at byte 40 of the received packet and is four bytes,
656
-     or two words, long. First, esxtract the two words.
678
+     The timestamp starts at byte 40 of the received packet and is four
679
+     bytes, or two words, long. First, esxtract the two words.
657 680
     */
658 681
     unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);
659 682
     unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);
... ...
@@ -681,7 +704,7 @@ int syncToNetworkTime()
681 704
 }
682 705
 
683 706
 /*
684
-  Send an NTP request to the NTP time server.
707
+ Send an NTP request to the NTP time server.
685 708
 */
686 709
 void sendNTPpacket( char * serverAddress, byte * packetBuffer )
687 710
 {
... ...
@@ -706,8 +729,8 @@ void sendNTPpacket( char * serverAddress, byte * packetBuffer )
706 729
   /*
707 730
    All NTP fields have been given values, so now
708 731
    send a packet requesting a timestamp.
709
-  */ 		
710
-  Udp.beginPacket( serverAddress, 123 ); //NTP requests are to port 123
732
+  */
733
+  Udp.beginPacket( serverAddress, 123 ); // NTP requests are to port 123
711 734
   Udp.write( packetBuffer, NTP_PACKET_SIZE );
712 735
   Udp.endPacket();
713 736
   return;
... ...
@@ -724,31 +747,27 @@ void commandMode()
724 747
 {
725 748
   char sCmdBuf[2];
726 749
   
727
-  getCurrentIP();  //used for display of settings
750
+  getCurrentIP();  // used for display of settings
751
+
752
+  Serial.println();
753
+  displayMenu(); // display the menu
728 754
   
729 755
   while(true)
730 756
   {
731
-    /*
732
-     Print the menu.
733
-    */
734
-    Serial.print( F("\n"                         \
735
-                  "1 - view settings\r\n"        \
736
-                  "2 - set IP address\r\n"       \
737
-                  "3 - set NTP server\r\n"       \
738
-                  "4 - toggle verbose\r\n"       \
739
-                  "5 - exit without saving\r\n"  \
740
-                  "6 - save & restart\r\n"       \
741
-                  ">"));
742 757
     /*
743 758
      Get the command from the user.
744 759
     */
760
+    Serial.print(F(">"));
745 761
     getSerialLine(sCmdBuf, 2);
746
-    Serial.print(F("\n\n\r"));
762
+    Serial.print(F("\n\r"));
747 763
     /* 
748 764
      Execute the command.
749 765
     */
750 766
     switch (sCmdBuf[0])
751 767
     {
768
+      case '0':
769
+        displayMenu();
770
+        break;
752 771
       case '1':
753 772
         displaySettings();
754 773
         break;
... ...
@@ -782,6 +801,25 @@ void commandMode()
782 801
   return;
783 802
 }
784 803
 
804
+/*
805
+ Displays the menu.
806
+*/
807
+void displayMenu()
808
+{
809
+  /*
810
+   Print the menu.
811
+  */
812
+  Serial.print( F("Available commands (type a number):\r\n" \
813
+                  "  0 - display this menu\r\n"    \
814
+                  "  1 - view settings\r\n"        \
815
+                  "  2 - set IP address\r\n"       \
816
+                  "  3 - set NTP server\r\n"       \
817
+                  "  4 - toggle verbose\r\n"       \
818
+                  "  5 - exit without saving\r\n"  \
819
+                  "  6 - save & restart\r\n"       \
820
+              ));  
821
+}
822
+
785 823
 /*
786 824
  Displays the current system settings.  Displays
787 825
  RadMon software version, local IP address, NTP server
... ...
@@ -853,9 +891,9 @@ void setNTPServer()
853 891
 {
854 892
   char sBuf[32];
855 893
   
856
-  Serial.print(F("enter IP (<CR> for default): "));
894
+  Serial.print(F("enter NTP server (<CR> for default): "));
857 895
   getSerialLine(sBuf, 32);
858
-  
896
+
859 897
   if (strlen(sBuf) == 0)
860 898
   {
861 899
     strcpy(timeServer, DEFAULT_NTP_SERVER_ADDR);