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);
Browse code

version 1.7 upgrade

Gandolf authored on 10/29/2019 22:20:38
Showing 1 changed files
... ...
@@ -72,7 +72,7 @@
72 72
    * v17 released 29 Oct 2019 by J L Owrey
73 73
        - modified NTP server address user setting to allow fully
74 74
          qualified domain names as well as IP addresses.  Default
75
-         NTP address set to "time.nist.gov" per NIST request to use
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 78
 */
Browse code

version 1.7 upgrade

Gandolf authored on 10/29/2019 22:18:24
Showing 1 changed files
... ...
@@ -68,7 +68,13 @@
68 68
        - simplified serial data output
69 69
    * v16 released 16 Sep 2017 by J L Owrey
70 70
        - added capability of rebooting via network http request,
71
-         i.e.,    "http://{device_IP_address}/reset"
71
+         i.e., "http://{device IP address}/reset"
72
+   * v17 released 29 Oct 2019 by J L Owrey
73
+       - modified NTP server address user setting to allow fully
74
+         qualified domain names as well as IP addresses.  Default
75
+         NTP address set to "time.nist.gov" per NIST request to use
76
+         (in order to facilitate load balancing) the fully qualified
77
+         domain name instead of individual server IP addresses.
72 78
 */
73 79
 
74 80
 /***  PREPROCESSOR DEFINES  ***/
... ...
@@ -79,8 +85,8 @@
79 85
  Define the header and version number displayed at startup
80 86
  and also by the 'view settings' command.
81 87
 */
82
-#define STARTUP_HEADER "\n\rRadmon v1.6 (c) 2018\n"
83
-#define RADMON_VERSION "v1.6"
88
+#define STARTUP_HEADER "\n\rRadmon v1.7 (c) 2019\n"
89
+#define RADMON_VERSION "v1.7"
84 90
 /*
85 91
  The following define sets the MAC address of the device.  This
86 92
  address is a permanent attribute of the device's Ethernet interface,
... ...
@@ -89,7 +95,7 @@
89 95
  specific instance of the Ethernet shield.  This MAC address should
90 96
  be shown on a label affixed to the device housing.
91 97
 */
92
-#define ETHERNET_MAC_ADDRESS 0x90, 0xA2, 0xDA, 0x0D, 0x84, 0xF6
98
+#define ETHERNET_MAC_ADDRESS 0xNN, 0xNN, 0xNN, 0xNN, 0xNN, 0xNN
93 99
 /*
94 100
  The following defines an APIPA default address in the event that
95 101
  DHCP mode is ON and a DHCP address cannot be obtained.
... ...
@@ -114,14 +120,18 @@
114 120
  time server:
115 121
               time-c-b.nist.gov
116 122
 */
117
-#define DEFAULT_NTP_SERVER_IP_ADDR "132.163.96.3"
123
+#define DEFAULT_NTP_SERVER_ADDR "pool.ntp.org"
118 124
 #define NTP_PORT 8888
119 125
 #define NTP_PACKET_SIZE 48 // NTP time stamp is in the first 48 bytes of the message
120 126
 /*
121 127
  The following defines how often the system clock gets synchronized
122 128
  to network time.
123 129
 */
124
-#define NET_SYNCH_INTERVAL 43200 //number in seconds
130
+#define NET_SYNCH_INTERVAL 21600 //number in seconds - 4 times a day
131
+/*
132
+ Number of retries if first time server request fails.
133
+*/
134
+#define TIME_SERVER_REQUEST_RETRIES 3
125 135
 /*
126 136
  The following defines the size of the buffer space required for the
127 137
  serial data string from the Mighty Ohm Geiger counter.  The serial
... ...
@@ -179,8 +189,8 @@ SoftwareSerial MightyOhmTxOut(5, 6);
179 189
  time, and next synchronization time.
180 190
 */
181 191
 char mightOhmData[MIGHTYOHM_DATA_STRING_LENGTH + 1];
182
-unsigned long lastSerialUpdateTime;
183
-time_t nextClockSynchTime;
192
+unsigned long nextSerialUpdateTime = 0;
193
+time_t nextClockSynchTime = -1;
184 194
 /*
185 195
  Create global variables to store the verbose mode state (ON or OFF)
186 196
  and the IP address mode state (static or DHCP).
... ...
@@ -189,10 +199,10 @@ boolean bVerbose;
189 199
 boolean bUseStaticIP;
190 200
 /*
191 201
  Create and initialize global arrays to hold the current IP address
192
- and the NTP server IP address.
202
+ and the NTP server address.
193 203
 */
194 204
 byte ipAddr[4];
195
-byte ntpIpAddr[4];
205
+char timeServer[32];
196 206
 
197 207
 /*** SYSTEM STARTUP  ***/
198 208
 
... ...
@@ -215,11 +225,13 @@ void setup()
215 225
    Start up the Ethernet interface using either a static or
216 226
    DHCP supplied address (depending on stored system configuration).
217 227
   */
218
-  if(bUseStaticIP)
228
+  if (bUseStaticIP)
219 229
   {
220 230
     Ethernet.begin(mac, ipAddr);
221
-  } else {
222
-    if ( Ethernet.begin(mac) == 0 )
231
+  }
232
+  else
233
+  {
234
+    if (Ethernet.begin(mac) == 0)
223 235
     {
224 236
       /* DHCP not responding so use APIPA address */
225 237
       parseIpAddress(ipAddr, DEFAULT_APIPA_IP_ADDRESS);
... ...
@@ -245,12 +257,6 @@ void setup()
245 257
     Open serial communications to the MightyOhm device.
246 258
   */  
247 259
   MightyOhmTxOut.begin(9600);
248
-   /*
249
-    Initialize initial time for sending out the hearbeat string. Normally
250
-    the system clock will be at approx 3200 msec at this point. So allow
251
-    some additional time for data to accumulate in MightyOhm data buffer.
252
-  */
253
-  lastSerialUpdateTime = -1;
254 260
   /*
255 261
    Initialize MightyOhm data string to empty.
256 262
   */
... ...
@@ -261,17 +267,13 @@ void setup()
261 267
 /*** MAIN LOOP ***/
262 268
 
263 269
 void loop() {
264
-  long currentTime;
265
-
266
-  currentTime = millis();
267
-  
268 270
   /*
269 271
    Check for user keyboard 'c' pressed.  This character switches
270 272
    to command mode.
271 273
   */   
272
-  if ( Serial.available() ) {
274
+  if (Serial.available()) {
273 275
     // get incoming byte
274
-    if(Serial.read() == 'c') {
276
+    if (Serial.read() == 'c') {
275 277
       commandMode();
276 278
     }
277 279
   }
... ...
@@ -280,8 +282,8 @@ void loop() {
280 282
     Poll serial input buffer from MightyOhm for new data and 
281 283
     process received bytes to form a complete data string.
282 284
   */
283
-  while ( MightyOhmTxOut.available() ) {
284
-    processRxByte( MightyOhmTxOut.read() );
285
+  while (MightyOhmTxOut.available()) {
286
+    processRxByte(MightyOhmTxOut.read());
285 287
   }
286 288
   
287 289
   /*
... ...
@@ -289,9 +291,12 @@ void loop() {
289 291
     serial port at regular intervals.
290 292
   */
291 293
   if (bVerbose) {
292
-    if (abs(millis() - lastSerialUpdateTime) > SERIAL_UPDATE_INTERVAL) {
293
-      lastSerialUpdateTime = millis();
294
-      Serial.println( mightOhmData );
294
+    if (millis() > nextSerialUpdateTime) {
295
+      Serial.println(mightOhmData);
296
+      /* 
297
+       Set the time for the next serial update to occur.
298
+      */
299
+      nextSerialUpdateTime = millis() + SERIAL_UPDATE_INTERVAL;
295 300
     }
296 301
   }
297 302
   
... ...
@@ -299,8 +304,13 @@ void loop() {
299 304
    Periodically synchronize local system clock to time
300 305
    provided by NTP time server.
301 306
   */
302
-  if ( now() > nextClockSynchTime ) {
307
+  if (now() > nextClockSynchTime) {
303 308
     synchronizeSystemClock();
309
+    /* 
310
+     Set the time for the next network NTP
311
+     time synchronization to occur.
312
+    */
313
+    nextClockSynchTime = now() + NET_SYNCH_INTERVAL;
304 314
   }
305 315
   
306 316
   /*
... ...
@@ -322,24 +332,24 @@ void synchronizeSystemClock()
322 332
   byte count;
323 333
   
324 334
   Serial.println(F("Synchronizing with network time server..."));
325
-    
326
-  for(count = 0; count < 3; count++)  // Attempt to synchronize 3 times
335
+
336
+  count = 0;
337
+  while (1)  // Attempt to synchronize 3 times
327 338
   {
328
-    if(syncToNetworkTime() == 1)
329
-    {
339
+    if (syncToNetworkTime() == 1) {
330 340
       //  Synchronization successful
331 341
       break;
332 342
     }
333
-    delay(1000);
334
-  } /* end for */ 
335
-  if(count == 3) {
336
-    Serial.println(F("synch failed"));
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"));
337 352
   }
338
-  /* 
339
-   Set the time for the next network NTP
340
-   time synchronization to occur.
341
-  */
342
-  nextClockSynchTime = now() + NET_SYNCH_INTERVAL;
343 353
   return;
344 354
 }
345 355
 
... ...
@@ -477,8 +487,8 @@ void transmitWebPage(EthernetClient client) {
477 487
                  "p {font: 16px arial, sans-serif;}"
478 488
                  "h2 {font: 24px arial, sans-serif;}</style>" \
479 489
                  "</head><body><h2>Radiation Monitor</h2>" \
480
-                 "<p><a href=\"http://intravisions.com/radmon/\">" \
481
-                 "<i>intravisions.com/radmon</i></a></p>" \
490
+                 "<p><a href=\"http://intravisions.com/\">" \
491
+                 "<i>IntraVisions.com</i></a></p>" \
482 492
                  "<hr>"));
483 493
   /* Data Items */             
484 494
   client.print(F("<pre>UTC &#9;"));
... ...
@@ -541,10 +551,7 @@ void transmitRawData(EthernetClient client) {
541 551
 void transmitErrorPage(EthernetClient client) {
542 552
   client.print(F("<!DOCTYPE HTML>" \
543 553
                  "<html><head><title>Radiation Monitor</title></head>"  \
544
-                 "<body><h2>Invalid Url</h2>"  \
545
-                 "<p>You have requested a service at an unknown " \
546
-                 "url.</p><p>If you think you made this request in error, " \
547
-                 "please disconnect and try your request again.</p>" \
554
+                 "<body><h2>404 Not Found</h2>"  \
548 555
                  "</body></html>"
549 556
                  ));
550 557
 }
... ...
@@ -632,12 +639,12 @@ int syncToNetworkTime()
632 639
    Send an NTP packet to the time server and allow for network lag
633 640
    before checking if a reply is available.
634 641
   */
635
-  sendNTPpacket(packetBuffer);
636
-  delay(2000);  // allow 2000 milli-seconds for network lag
637
-
642
+  sendNTPpacket(timeServer, packetBuffer);
638 643
   /*
639 644
    Wait for response from NTP time server.
640 645
   */
646
+  delay(1000);  // allow 1000 milli-seconds for network lag
647
+
641 648
   if ( Udp.parsePacket() )
642 649
   {  
643 650
     /*
... ...
@@ -676,7 +683,7 @@ int syncToNetworkTime()
676 683
 /*
677 684
   Send an NTP request to the NTP time server.
678 685
 */
679
-void sendNTPpacket( byte* packetBuffer )
686
+void sendNTPpacket( char * serverAddress, byte * packetBuffer )
680 687
 {
681 688
   /*
682 689
    Set all bytes in the buffer to 0.
... ...
@@ -700,7 +707,7 @@ void sendNTPpacket( byte* packetBuffer )
700 707
    All NTP fields have been given values, so now
701 708
    send a packet requesting a timestamp.
702 709
   */ 		
703
-  Udp.beginPacket( ntpIpAddr, 123 ); //NTP requests are to port 123
710
+  Udp.beginPacket( serverAddress, 123 ); //NTP requests are to port 123
704 711
   Udp.write( packetBuffer, NTP_PACKET_SIZE );
705 712
   Udp.endPacket();
706 713
   return;
... ...
@@ -749,7 +756,7 @@ void commandMode()
749 756
         setIP();
750 757
         break;
751 758
       case '3':
752
-        setNTPIP();
759
+        setNTPServer();
753 760
         break;
754 761
       case '4':
755 762
         toggleVerbose();
... ...
@@ -765,6 +772,7 @@ void commandMode()
765 772
          server or to initialize the Ethernet interface
766 773
          with a static IP address.
767 774
         */
775
+        delay(100);
768 776
         software_Reset();
769 777
         return;
770 778
       default:
... ...
@@ -801,9 +809,8 @@ void displaySettings()
801 809
   Serial.println(sBuf);
802 810
   
803 811
   // Display NTP server IP address
804
-  sprintf(sBuf, "%d.%d.%d.%d", ntpIpAddr[0], ntpIpAddr[1], ntpIpAddr[2], ntpIpAddr[3]);
805 812
   Serial.print(F("NTP server: ")); 
806
-  Serial.println(sBuf);
813
+  Serial.println(timeServer);
807 814
 
808 815
   // Display verbose mode setting
809 816
   printVerboseMode();
... ...
@@ -842,21 +849,20 @@ void setIP()
842 849
  carriage return as the first character, then use the
843 850
  default IP address for the NTP server.
844 851
 */
845
-void setNTPIP()
852
+void setNTPServer()
846 853
 {
847
-  char sBuf[16];
854
+  char sBuf[32];
848 855
   
849 856
   Serial.print(F("enter IP (<CR> for default): "));
850
-  getSerialLine(sBuf, 16);
857
+  getSerialLine(sBuf, 32);
851 858
   
852 859
   if (strlen(sBuf) == 0)
853 860
   {
854
-    strcpy(sBuf, DEFAULT_NTP_SERVER_IP_ADDR);
855
-    parseIpAddress(ntpIpAddr, sBuf);
861
+    strcpy(timeServer, DEFAULT_NTP_SERVER_ADDR);
856 862
   }
857 863
   else
858 864
   {
859
-    parseIpAddress(ntpIpAddr, sBuf);
865
+    strcpy(timeServer, sBuf);
860 866
   }
861 867
   Serial.println();
862 868
   return;
... ...
@@ -991,13 +997,20 @@ char* getSerialLine(char* sBuffer, int bufferLength)
991 997
 void writeSettingsToEEPROM()
992 998
 {
993 999
   byte ix;
1000
+  char c;
994 1001
   for (ix = 0; ix < 4; ix++)
995 1002
   {
996 1003
     EEPROM.write(ix, ipAddr[ix]);
997
-    EEPROM.write(ix + 4, ntpIpAddr[ix]);
998 1004
   }
999
-  EEPROM.write(8, bVerbose);
1000
-  EEPROM.write(9, bUseStaticIP);
1005
+  EEPROM.write(4, bVerbose);
1006
+  EEPROM.write(5, bUseStaticIP);
1007
+  ix = 0;
1008
+  while(1) {
1009
+    c = timeServer[ix];
1010
+    EEPROM.write(6 + ix, c);
1011
+    if (c == 0 || ix > 31) break;
1012
+    ix++;
1013
+  }
1001 1014
   return;
1002 1015
 }
1003 1016
 
... ...
@@ -1010,13 +1023,20 @@ void writeSettingsToEEPROM()
1010 1023
 void readSettingsFromEEPROM()
1011 1024
 {
1012 1025
   byte ix;
1026
+  char c;
1013 1027
   for (ix = 0; ix < 4; ix++)
1014 1028
   {
1015 1029
     ipAddr[ix] = EEPROM.read(ix);
1016
-    ntpIpAddr[ix] = EEPROM.read(ix + 4);
1017 1030
   }
1018
-  bVerbose = EEPROM.read(8);
1019
-  bUseStaticIP = EEPROM.read(9);
1031
+  bVerbose = EEPROM.read(4);
1032
+  bUseStaticIP = EEPROM.read(5);
1033
+  ix = 0;
1034
+  while(1) {
1035
+    c = EEPROM.read(6 + ix);
1036
+    timeServer[ix] = c;
1037
+    if (c == 0 || ix > 31) break;
1038
+    ix++;
1039
+  }
1020 1040
   return;
1021 1041
 }
1022 1042
 
... ...
@@ -1046,4 +1066,3 @@ void software_Reset()
1046 1066
   asm volatile ("  jmp 0");
1047 1067
   return; 
1048 1068
 }  
1049
-
Browse code

sync 2018-07-27

fractalxaos authored on 07/27/2018 19:27:41
Showing 1 changed files
... ...
@@ -68,7 +68,7 @@
68 68
        - simplified serial data output
69 69
    * v16 released 16 Sep 2017 by J L Owrey
70 70
        - added capability of rebooting via network http request,
71
-         i.e., "http://{device IP address}/reset"
71
+         i.e.,    "http://{device_IP_address}/reset"
72 72
 */
73 73
 
74 74
 /***  PREPROCESSOR DEFINES  ***/
Browse code

revisions 2018-07-24

fractalxaos authored on 07/24/2018 19:19:53
Showing 1 changed files
... ...
@@ -121,7 +121,7 @@
121 121
  The following defines how often the system clock gets synchronized
122 122
  to network time.
123 123
 */
124
-#define NET_SYNCH_INTERVAL 21600 //number in seconds
124
+#define NET_SYNCH_INTERVAL 43200 //number in seconds
125 125
 /*
126 126
  The following defines the size of the buffer space required for the
127 127
  serial data string from the Mighty Ohm Geiger counter.  The serial
... ...
@@ -191,8 +191,6 @@ boolean bUseStaticIP;
191 191
  Create and initialize global arrays to hold the current IP address
192 192
  and the NTP server IP address.
193 193
 */
194
-//byte ipAddr[4] = {};
195
-//byte ntpIpAddr[4] = {};
196 194
 byte ipAddr[4];
197 195
 byte ntpIpAddr[4];
198 196
 
... ...
@@ -440,7 +438,7 @@ void listenForEthernetClients()
440 438
         transmitErrorPage(client);
441 439
     }
442 440
 
443
-    Serial.println(mightOhmData);
441
+    //Serial.println(mightOhmData);
444 442
     // give the web browser time to receive the data
445 443
     delay(10);
446 444
     // close the connection:
... ...
@@ -480,7 +478,7 @@ void transmitWebPage(EthernetClient client) {
480 478
                  "h2 {font: 24px arial, sans-serif;}</style>" \
481 479
                  "</head><body><h2>Radiation Monitor</h2>" \
482 480
                  "<p><a href=\"http://intravisions.com/radmon/\">" \
483
-                 "<i>IntraVisions.com/radmon</i></a></p>" \
481
+                 "<i>intravisions.com/radmon</i></a></p>" \
484 482
                  "<hr>"));
485 483
   /* Data Items */             
486 484
   client.print(F("<pre>UTC &#9;"));
Browse code

minor revision 20180225

fractalxaos authored on 02/26/2018 01:30:49
Showing 1 changed files
... ...
@@ -453,10 +453,10 @@ void listenForEthernetClients()
453 453
  requesting client,
454 454
 */
455 455
 void transmitHttpHeader(EthernetClient client) {
456
-  client.print(F("HTTP/1.1 200 OK\r\n"        \
456
+  client.print(F("HTTP/1.1 200 OK\r\n"          \
457 457
                  "Content-Type: text/html\r\n"  \
458 458
                  "Connnection: close\r\n"       \
459
-                 "Refresh: 10\r\n"              \
459
+                 "Refresh: 5\r\n"               \
460 460
                  "\r\n"                         \
461 461
                  ));
462 462
 }
Browse code

minor revision 20180225

fractalxaos authored on 02/26/2018 00:19:05
Showing 1 changed files
... ...
@@ -12,7 +12,7 @@
12 12
  network interface IP address, NTP server address, or configure a
13 13
  verbose output mode.
14 14
  
15
- Copyright 2017 Jeff Owrey
15
+ Copyright 2018 Jeff Owrey
16 16
     This program is free software: you can redistribute it and/or modify
17 17
     it under the terms of the GNU General Public License as published by
18 18
     the Free Software Foundation, either version 3 of the License, or
... ...
@@ -79,7 +79,7 @@
79 79
  Define the header and version number displayed at startup
80 80
  and also by the 'view settings' command.
81 81
 */
82
-#define STARTUP_HEADER "\n\rRadmon v1.6 (c) 2017\n"
82
+#define STARTUP_HEADER "\n\rRadmon v1.6 (c) 2018\n"
83 83
 #define RADMON_VERSION "v1.6"
84 84
 /*
85 85
  The following define sets the MAC address of the device.  This
... ...
@@ -100,7 +100,7 @@
100 100
  out over the device's USB port.  This heartbeat consists of a serial
101 101
  data string containing the current radiation reading and GM time.
102 102
 */
103
-#define SERIAL_UPDATE_INTERVAL 10000  //milli-seconds
103
+#define SERIAL_UPDATE_INTERVAL 5000  //milli-seconds
104 104
 /*
105 105
  The following define sets the port number the HTTP service will use to
106 106
  listen for requests from Internet clients.  Normally HTTP requests use
... ...
@@ -186,13 +186,15 @@ time_t nextClockSynchTime;
186 186
  and the IP address mode state (static or DHCP).
187 187
 */
188 188
 boolean bVerbose;
189
-boolean bUseDHCP;
189
+boolean bUseStaticIP;
190 190
 /*
191 191
  Create and initialize global arrays to hold the current IP address
192 192
  and the NTP server IP address.
193 193
 */
194
-byte ipAddr[4] = {};
195
-byte ntpIpAddr[4] = {};
194
+//byte ipAddr[4] = {};
195
+//byte ntpIpAddr[4] = {};
196
+byte ipAddr[4];
197
+byte ntpIpAddr[4];
196 198
 
197 199
 /*** SYSTEM STARTUP  ***/
198 200
 
... ...
@@ -215,8 +217,10 @@ void setup()
215 217
    Start up the Ethernet interface using either a static or
216 218
    DHCP supplied address (depending on stored system configuration).
217 219
   */
218
-  if(bUseDHCP)
220
+  if(bUseStaticIP)
219 221
   {
222
+    Ethernet.begin(mac, ipAddr);
223
+  } else {
220 224
     if ( Ethernet.begin(mac) == 0 )
221 225
     {
222 226
       /* DHCP not responding so use APIPA address */
... ...
@@ -224,14 +228,6 @@ void setup()
224 228
       Ethernet.begin(mac, ipAddr);
225 229
       Serial.println(F("DHCP failed - using APIPA "));
226 230
     }
227
-    else
228
-    {
229
-      Serial.print(F("DHCP "));
230
-    }
231
-  }
232
-  else
233
-  {
234
-    Ethernet.begin(mac, ipAddr);
235 231
   }
236 232
   Serial.print(F("IP address: ")); Serial.println(Ethernet.localIP());
237 233
   /*
... ...
@@ -796,13 +792,13 @@ void displaySettings()
796 792
 
797 793
   // Display local IP address
798 794
   sprintf(sBuf, "%d.%d.%d.%d", ipAddr[0], ipAddr[1], ipAddr[2], ipAddr[3]);
799
-  if (bUseDHCP)
795
+  if (bUseStaticIP)
800 796
   {
801
-    Serial.print(F("DHCP IP: "));
797
+    Serial.print(F("Static IP: "));
802 798
   }
803 799
   else
804 800
   {
805
-    Serial.print(F("Static IP: "));
801
+    Serial.print(F("DHCP IP: "));
806 802
   }
807 803
   Serial.println(sBuf);
808 804
   
... ...
@@ -830,12 +826,13 @@ void setIP()
830 826
   
831 827
   if(strlen(sBuf) == 0)
832 828
   {
833
-    bUseDHCP = true;
834
-    parseIpAddress(ipAddr, "0.0.0.0");
829
+    bUseStaticIP = false;
830
+    strcpy(sBuf, "0.0.0.0");
831
+    parseIpAddress(ipAddr, sBuf);
835 832
   }
836 833
   else
837 834
   {
838
-    bUseDHCP = false;
835
+    bUseStaticIP = true;
839 836
     parseIpAddress(ipAddr, sBuf);
840 837
   }
841 838
   Serial.println();
... ...
@@ -851,12 +848,13 @@ void setNTPIP()
851 848
 {
852 849
   char sBuf[16];
853 850
   
854
-  Serial.print(F("enter IP: "));
851
+  Serial.print(F("enter IP (<CR> for default): "));
855 852
   getSerialLine(sBuf, 16);
856 853
   
857 854
   if (strlen(sBuf) == 0)
858 855
   {
859
-    parseIpAddress(ntpIpAddr, DEFAULT_NTP_SERVER_IP_ADDR);
856
+    strcpy(sBuf, DEFAULT_NTP_SERVER_IP_ADDR);
857
+    parseIpAddress(ntpIpAddr, sBuf);
860 858
   }
861 859
   else
862 860
   {
... ...
@@ -1001,7 +999,7 @@ void writeSettingsToEEPROM()
1001 999
     EEPROM.write(ix + 4, ntpIpAddr[ix]);
1002 1000
   }
1003 1001
   EEPROM.write(8, bVerbose);
1004
-  EEPROM.write(9, bUseDHCP);
1002
+  EEPROM.write(9, bUseStaticIP);
1005 1003
   return;
1006 1004
 }
1007 1005
 
... ...
@@ -1020,7 +1018,7 @@ void readSettingsFromEEPROM()
1020 1018
     ntpIpAddr[ix] = EEPROM.read(ix + 4);
1021 1019
   }
1022 1020
   bVerbose = EEPROM.read(8);
1023
-  bUseDHCP = EEPROM.read(9);
1021
+  bUseStaticIP = EEPROM.read(9);
1024 1022
   return;
1025 1023
 }
1026 1024
 
Browse code

2017-11-27 update

fractalxaos authored on 11/28/2017 01:38:25
Showing 1 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,1053 @@
1
+/*
2
+ Background Radiation Monitor - Web Server
3
+ 
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
7
+ runs on the Arduino Uno an embedded HTTP server by which Internet
8
+ applications can query the MightyOhm for Geiger counter readings.
9
+ Also, this software runs a Network Time Protocol (NTP) client, that
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
13
+ verbose output mode.
14
+ 
15
+ Copyright 2017 Jeff Owrey
16
+    This program is free software: you can redistribute it and/or modify
17
+    it under the terms of the GNU General Public License as published by
18
+    the Free Software Foundation, either version 3 of the License, or
19
+    (at your option) any later version.
20
+
21
+    This program is distributed in the hope that it will be useful,
22
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
23
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
24
+    GNU General Public License for more details.
25
+
26
+    You should have received a copy of the GNU General Public License
27
+    along with this program.  If not, see http://www.gnu.org/license.
28
+ 
29
+ Circuit:
30
+ * Main components: Arduino Uno, Ethernet shield, Mighty Ohm Geiger counter
31
+ * Ethernet shield attached to pins 10, 11, 12, 13
32
+ * In order to allow the MightyOhm to operate on the Uno's 5 volt power
33
+   supply, and thus make the MightyOhm's serial output compatible with the
34
+   Uno, the following has to be done (see MightyOhm schematic):
35
+     1. Change R6 to 1K Ohm.
36
+     2. Change R11 to 330 Ohm.
37
+     3. Connect +5v from the Uno to MightyOhm J6 pin 1.
38
+     4. Connect GND from the Uno to MightyOhm J6 pin 3.
39
+     5. Connect D5 from the Uno to MightyOhm J7 pin 5.
40
+   
41
+ Misc Notes:
42
+   As of this release the Uno's SRAM gets entirely maxed out by
43
+   this program.  Any modifications to this program that requires
44
+   additional memory seriously entails the risk that the modifications
45
+   will cause the program to become un-stable.
46
+   
47
+ Revision History:  
48
+   * v10 released 25 Feb 2014 by J L Owrey
49
+   * v11 released 24 Jun 2014 by J L Owrey
50
+       - optimization of processRxByte function to conserve SRAM
51
+       - removal of non-used function code
52
+       - defaults to APIPA IP address in the event a DHCP address
53
+         cannot be obtained
54
+   * v12 released 20 Dec 2014 by J L Owrey
55
+       - removed Timestamp global variable to make more dynamic
56
+         memory available for local variables
57
+       - optimized clock network synch algorithm
58
+       - optimized serial update algorithm
59
+   * v13 released 22 Jul 2015 by J L Owrey
60
+       - add use of "F" function to store constant strings in
61
+         program flash memory in order to save SRAM space
62
+   * v14 released 19 Aug 2015 by J L Owrey
63
+       - add ability to respond to web a client request with either
64
+         a JSON compatible string or a standard HTML document
65
+   * v15 released 20 Feb 2016 by J L Owrey
66
+       - improved http request handling
67
+       - simplified raw data request format
68
+       - simplified serial data output
69
+   * v16 released 16 Sep 2017 by J L Owrey
70
+       - added capability of rebooting via network http request,
71
+         i.e., "http://{device IP address}/reset"
72
+*/
73
+
74
+/***  PREPROCESSOR DEFINES  ***/
75
+
76
+//#define DEBUG
77
+
78
+/*
79
+ Define the header and version number displayed at startup
80
+ and also by the 'view settings' command.
81
+*/
82
+#define STARTUP_HEADER "\n\rRadmon v1.6 (c) 2017\n"
83
+#define RADMON_VERSION "v1.6"
84
+/*
85
+ The following define sets the MAC address of the device.  This
86
+ address is a permanent attribute of the device's Ethernet interface,
87
+ and never, ever, should be changed.  This address was provided
88
+ by the Arduino Ethernet shield manufacturer for use with this
89
+ specific instance of the Ethernet shield.  This MAC address should
90
+ be shown on a label affixed to the device housing.
91
+*/
92
+#define ETHERNET_MAC_ADDRESS 0x90, 0xA2, 0xDA, 0x0D, 0x84, 0xF6
93
+/*
94
+ The following defines an APIPA default address in the event that
95
+ DHCP mode is ON and a DHCP address cannot be obtained.
96
+*/
97
+#define DEFAULT_APIPA_IP_ADDRESS "169.254.100.10"
98
+/*
99
+ The following define sets the period of a 'heartbeat' string sent
100
+ out over the device's USB port.  This heartbeat consists of a serial
101
+ data string containing the current radiation reading and GM time.
102
+*/
103
+#define SERIAL_UPDATE_INTERVAL 10000  //milli-seconds
104
+/*
105
+ The following define sets the port number the HTTP service will use to
106
+ listen for requests from Internet clients.  Normally HTTP requests use
107
+ port 80.
108
+*/
109
+#define HTTP_SERVER_PORT 80
110
+/*
111
+ The following defines are for configuring a local NTP client
112
+ for synchronizing the local system clock to network time.
113
+ Note that the default setting is the IP address of the following
114
+ time server:
115
+              time-c-b.nist.gov
116
+*/
117
+#define DEFAULT_NTP_SERVER_IP_ADDR "132.163.96.3"
118
+#define NTP_PORT 8888
119
+#define NTP_PACKET_SIZE 48 // NTP time stamp is in the first 48 bytes of the message
120
+/*
121
+ The following defines how often the system clock gets synchronized
122
+ to network time.
123
+*/
124
+#define NET_SYNCH_INTERVAL 21600 //number in seconds
125
+/*
126
+ The following defines the size of the buffer space required for the
127
+ serial data string from the Mighty Ohm Geiger counter.  The serial
128
+ data string is defined as the text from newline character to newline
129
+ character.
130
+*/
131
+#define MIGHTYOHM_DATA_STRING_LENGTH 65
132
+/*
133
+ The beginning of the MightyOhm data string always begins with the
134
+ same three characters.  These three characters determine the 
135
+ beginning of a new line of data from the MightyOhm.
136
+*/
137
+#define MIGHTYOHM_DATA_STRING_HEADER "CPS"
138
+/*
139
+ Set the depth of the string buffer that receives the http
140
+ request header from the client.  Must be large enough to
141
+ capture 'GET /rdata '.
142
+*/
143
+#define REQUEST_STRING_BUFFER_LENGTH 24
144
+
145
+/***  LIBRARY MODULES USED  ***/
146
+
147
+#include <Time.h>
148
+#include <SPI.h>         
149
+#include <Ethernet.h>
150
+#include <EthernetUdp.h>
151
+#include <SoftwareSerial.h>
152
+#include <EEPROM.h>;
153
+
154
+/***  GLOBAL DECLARATIONS ***/
155
+
156
+/*
157
+ Create and initialize a mac address object for the Ethernet interface. 
158
+*/
159
+byte mac[] = { ETHERNET_MAC_ADDRESS };
160
+/*
161
+ Create and initialize an HTTP server object.  The object is initialized
162
+ to the TCP port the HTTP server will use to listen for clients.
163
+*/
164
+EthernetServer httpServer(HTTP_SERVER_PORT);
165
+/*
166
+ Create a UDP client object for sending packets to
167
+ and receiveing packets from an NTP time server.
168
+*/
169
+EthernetUDP Udp;
170
+/*
171
+ Create a software serial port for receiving serial data from
172
+ the MightyOhm. Note that the Uno pin 5 receives serial data
173
+ FROM the MightyOhm.  The Uno's pin 6 is not used, as there is
174
+ no need to send serial data to the MightyOhm. 
175
+*/
176
+SoftwareSerial MightyOhmTxOut(5, 6);
177
+/*
178
+ Create global variables to store the MightOhm data, next heartbeat
179
+ time, and next synchronization time.
180
+*/
181
+char mightOhmData[MIGHTYOHM_DATA_STRING_LENGTH + 1];
182
+unsigned long lastSerialUpdateTime;
183
+time_t nextClockSynchTime;
184
+/*
185
+ Create global variables to store the verbose mode state (ON or OFF)
186
+ and the IP address mode state (static or DHCP).
187
+*/
188
+boolean bVerbose;
189
+boolean bUseDHCP;
190
+/*
191
+ Create and initialize global arrays to hold the current IP address
192
+ and the NTP server IP address.
193
+*/
194
+byte ipAddr[4] = {};
195
+byte ntpIpAddr[4] = {};
196
+
197
+/*** SYSTEM STARTUP  ***/
198
+
199
+void setup()
200
+{
201
+  /*
202
+   Open serial communications to and from the Uno's USB port.
203
+  */
204
+  Serial.begin(9600);
205
+  /* 
206
+   Print to the USB port a header showing Radmon
207
+   version of this program and the copyright notice.
208
+  */
209
+  Serial.println(F(STARTUP_HEADER));
210
+  /*
211
+    Get the system configuration from EEPROM.
212
+  */
213
+  readSettingsFromEEPROM();
214
+  /*
215
+   Start up the Ethernet interface using either a static or
216
+   DHCP supplied address (depending on stored system configuration).
217
+  */
218
+  if(bUseDHCP)
219
+  {
220
+    if ( Ethernet.begin(mac) == 0 )
221
+    {
222
+      /* DHCP not responding so use APIPA address */
223
+      parseIpAddress(ipAddr, DEFAULT_APIPA_IP_ADDRESS);
224
+      Ethernet.begin(mac, ipAddr);
225
+      Serial.println(F("DHCP failed - using APIPA "));
226
+    }
227
+    else
228
+    {
229
+      Serial.print(F("DHCP "));
230
+    }
231
+  }
232
+  else
233
+  {
234
+    Ethernet.begin(mac, ipAddr);
235
+  }
236
+  Serial.print(F("IP address: ")); Serial.println(Ethernet.localIP());
237
+  /*
238
+   Start up NTP client service.
239
+  */
240
+  Udp.begin(NTP_PORT);
241
+  /*
242
+    Synchronize the system clock to network time.
243
+  */
244
+  synchronizeSystemClock();
245
+  /*
246
+   Start up the HTTP server.
247
+  */
248
+  Serial.println(F("Starting http server..."));
249
+  httpServer.begin();
250
+  /*
251
+    Open serial communications to the MightyOhm device.
252
+  */  
253
+  MightyOhmTxOut.begin(9600);
254
+   /*
255
+    Initialize initial time for sending out the hearbeat string. Normally
256
+    the system clock will be at approx 3200 msec at this point. So allow
257
+    some additional time for data to accumulate in MightyOhm data buffer.
258
+  */
259
+  lastSerialUpdateTime = -1;
260
+  /*
261
+   Initialize MightyOhm data string to empty.
262
+  */
263
+  mightOhmData[0] = 0;
264
+  return;
265
+}
266
+
267
+/*** MAIN LOOP ***/
268
+
269
+void loop() {
270
+  long currentTime;
271
+
272
+  currentTime = millis();
273
+  
274
+  /*
275
+   Check for user keyboard 'c' pressed.  This character switches
276
+   to command mode.
277
+  */   
278
+  if ( Serial.available() ) {
279
+    // get incoming byte
280
+    if(Serial.read() == 'c') {
281
+      commandMode();
282
+    }
283
+  }
284
+  
285
+  /*
286
+    Poll serial input buffer from MightyOhm for new data and 
287
+    process received bytes to form a complete data string.
288
+  */
289
+  while ( MightyOhmTxOut.available() ) {
290
+    processRxByte( MightyOhmTxOut.read() );
291
+  }
292
+  
293
+  /*
294
+    In verbose mode, send the MightyOhm data string to the
295
+    serial port at regular intervals.
296
+  */
297
+  if (bVerbose) {
298
+    if (abs(millis() - lastSerialUpdateTime) > SERIAL_UPDATE_INTERVAL) {
299
+      lastSerialUpdateTime = millis();
300
+      Serial.println( mightOhmData );
301
+    }
302
+  }
303
+  
304
+  /*
305
+   Periodically synchronize local system clock to time
306
+   provided by NTP time server.
307
+  */
308
+  if ( now() > nextClockSynchTime ) {
309
+    synchronizeSystemClock();
310
+  }
311
+  
312
+  /*
313
+   Listen for and and process requests from HTTP clients.
314
+  */  
315
+  listenForEthernetClients();
316
+
317
+  #ifdef DEBUG
318
+    Serial.print("lp time: "); Serial.println(millis() - currentTime);
319
+  #endif
320
+}
321
+
322
+/*
323
+ Synchronize the local system clock to
324
+ network time provided by NTP time server.
325
+*/
326
+void synchronizeSystemClock()
327
+{
328
+  byte count;
329
+  
330
+  Serial.println(F("Synchronizing with network time server..."));
331
+    
332
+  for(count = 0; count < 3; count++)  // Attempt to synchronize 3 times
333
+  {
334
+    if(syncToNetworkTime() == 1)
335
+    {
336
+      //  Synchronization successful
337
+      break;
338
+    }
339
+    delay(1000);
340
+  } /* end for */ 
341
+  if(count == 3) {
342
+    Serial.println(F("synch failed"));
343
+  }
344
+  /* 
345
+   Set the time for the next network NTP
346
+   time synchronization to occur.
347
+  */
348
+  nextClockSynchTime = now() + NET_SYNCH_INTERVAL;
349
+  return;
350
+}
351
+
352
+/*
353
+  Handle HTTP GET requests from an HTTP client.
354
+*/  
355
+void listenForEthernetClients()
356
+{
357
+  // listen for incoming clients
358
+  EthernetClient client = httpServer.available();
359
+  if (client) {
360
+    char sBuf[REQUEST_STRING_BUFFER_LENGTH];
361
+    byte i;
362
+    char c, c_prev;
363
+    boolean processedCommand;
364
+    boolean firstLineFound;
365
+
366
+    Serial.println(F("\nclient request"));
367
+
368
+    i = 0;
369
+    c_prev = 0;
370
+    sBuf[0] = 0;
371
+    processedCommand = false;
372
+    firstLineFound = false;
373
+  
374
+    /*
375
+     * The beginning and end of an HTTP client request is always signaled
376
+     * by a blank line, that is, by two consecutive line feed and carriage 
377
+     * return characters "\r\n\r\n".  The following lines of code 
378
+     * look for this condition, as well as the url extension (following
379
+     * "GET").
380
+     */
381
+    
382
+    while (client.connected())  {
383
+      if (client.available()) {
384
+        c = client.read();
385
+
386
+        if (bVerbose) {
387
+          Serial.print(c);
388
+        }
389
+              
390
+        if (c == '\r') {
391
+          continue; // discard character
392
+        }  
393
+        else if (c == '\n') {
394
+          if (firstLineFound && c_prev == '\n') {
395
+             break;
396
+          }
397
+        } 
398
+        
399
+        if (!processedCommand) {
400
+          
401
+          if (c != '\n') {
402
+            if(i > REQUEST_STRING_BUFFER_LENGTH - 2) {
403
+              i = 0;
404
+              sBuf[0] = 0;
405
+            }
406
+            sBuf[i++] = c;
407
+            sBuf[i] = 0;
408
+          }
409
+
410
+          if (!firstLineFound && strstr(sBuf, "GET /") != NULL) {
411
+            firstLineFound = true;
412
+            strcpy(sBuf, "/");
413
+            i = 1;
414
+          }
415
+
416
+          if (firstLineFound && (c == '\n' || i > REQUEST_STRING_BUFFER_LENGTH - 2)) {
417
+            processedCommand = true;
418
+          }
419
+        }
420
+        c_prev = c;
421
+      } // end single character processing
422
+    } // end character processing loop
423
+
424
+    /*
425
+     Send a standard HTTP response header to the
426
+     client's GET request.
427
+    */
428
+    transmitHttpHeader(client);
429
+    
430
+    char * pStr = strtok(sBuf, " ");
431
+    if (pStr != NULL) {
432
+      if (strcmp(pStr, "/rdata") == 0)
433
+        transmitRawData(client);
434
+      else if (strcmp(pStr, "/") == 0)
435
+        transmitWebPage(client);
436
+      else if(strcmp(pStr, "/reset") == 0) {
437
+        client.print(F("ok"));
438
+        delay(10);
439
+        // close the connection and reboot:
440
+        client.stop();
441
+        software_Reset();
442
+      }
443
+      else 
444
+        transmitErrorPage(client);
445
+    }
446
+
447
+    Serial.println(mightOhmData);
448
+    // give the web browser time to receive the data
449
+    delay(10);
450
+    // close the connection:
451
+    client.stop();
452
+  }
453
+}
454
+
455
+/*
456
+ Send standard http response header back to
457
+ requesting client,
458
+*/
459
+void transmitHttpHeader(EthernetClient client) {
460
+  client.print(F("HTTP/1.1 200 OK\r\n"        \
461
+                 "Content-Type: text/html\r\n"  \
462
+                 "Connnection: close\r\n"       \
463
+                 "Refresh: 10\r\n"              \
464
+                 "\r\n"                         \
465
+                 ));
466
+}
467
+
468
+/*
469
+ Send to the client the MightyOhm Geiger counter's
470
+ current readings, embedded in an HTML document.
471
+*/
472
+void transmitWebPage(EthernetClient client) {
473
+  char strBuffer[MIGHTYOHM_DATA_STRING_LENGTH];
474
+
475
+  strcpy(strBuffer, mightOhmData);
476
+  /*
477
+   * Send the actual HTML page the user will see in their web
478
+   * browser.
479
+  */
480
+  client.print(F("<!DOCTYPE HTML>" \
481
+                 "<html><head><title>Radiation Monitor</title>"  \
482
+                 "<style>pre {font: 16px arial, sans-serif;}" \
483
+                 "p {font: 16px arial, sans-serif;}"
484
+                 "h2 {font: 24px arial, sans-serif;}</style>" \
485
+                 "</head><body><h2>Radiation Monitor</h2>" \
486
+                 "<p><a href=\"http://intravisions.com/radmon/\">" \
487
+                 "<i>IntraVisions.com/radmon</i></a></p>" \
488
+                 "<hr>"));
489
+  /* Data Items */             
490
+  client.print(F("<pre>UTC &#9;"));
491
+  client.print(strtok(strBuffer, ","));
492
+  client.print(F("<br>"));
493
+  client.print(strtok(NULL, ", "));
494
+  client.print(F(" &#9;"));
495
+  client.print(strtok(NULL, ", "));
496
+  client.print(F("<br>"));
497
+  client.print(strtok(NULL, ", "));
498
+  client.print(F(" &#9;"));
499
+  client.print(strtok(NULL, ", "));
500
+  client.print(F("<br>"));
501
+  client.print(strtok(NULL, ", "));
502
+  client.print(F(" &#9;"));
503
+  client.print(strtok(NULL, ", "));
504
+  client.print(F("<br>"));
505
+  client.print(F("Mode &#9;"));
506
+  client.print(strtok(NULL, ", "));
507
+  client.print(F("<br></pre></body></html>"));
508
+}  
509
+
510
+/*
511
+ Send to the client the MightyOhm Geiger counter's
512
+ current readings, embedded in a JSON compatible string.
513
+*/
514
+void transmitRawData(EthernetClient client) {
515
+  char strBuffer[MIGHTYOHM_DATA_STRING_LENGTH];
516
+
517
+  strcpy(strBuffer, mightOhmData);
518
+  /*
519
+   * Format and transmit a JSON compatible data string.
520
+   */
521
+  client.print(F("$,UTC="));
522
+  client.print(strtok(strBuffer, " "));
523
+  client.print(F(" "));
524
+  client.print(strtok(NULL, ", "));
525
+  client.print(F(","));
526
+  client.print(strtok(NULL, ", "));
527
+  client.print(F("="));
528
+  client.print(strtok(NULL, ", "));
529
+  client.print(F(","));
530
+  client.print(strtok(NULL, ", "));
531
+  client.print(F("="));
532
+  client.print(strtok(NULL, ", "));
533
+  client.print(F(","));
534
+  client.print(strtok(NULL, ", "));
535
+  client.print(F("="));
536
+  client.print(strtok(NULL, ", "));
537
+  client.print(F(","));
538
+  client.print(F("Mode="));
539
+  client.print(strtok(NULL, ", "));
540
+  client.print(F(",#\n"));
541
+}
542
+
543
+/*
544
+ * Send an error message web page back to the requesting
545
+ * client when the client provides an invalid url extension.
546
+ */
547
+void transmitErrorPage(EthernetClient client) {
548
+  client.print(F("<!DOCTYPE HTML>" \
549
+                 "<html><head><title>Radiation Monitor</title></head>"  \
550
+                 "<body><h2>Invalid Url</h2>"  \
551
+                 "<p>You have requested a service at an unknown " \
552
+                 "url.</p><p>If you think you made this request in error, " \
553
+                 "please disconnect and try your request again.</p>" \
554
+                 "</body></html>"
555
+                 ));
556
+}
557
+
558
+/*
559
+ Process bytes received from the MightyOhm Geiger counter,
560
+ one at a time, to create a well formed string.
561
+*/
562
+void processRxByte( char RxByte )
563
+{
564
+  static char readBuffer[MIGHTYOHM_DATA_STRING_LENGTH];
565
+  static byte cIndex = 0;
566
+  
567
+  /*
568
+     Discard carriage return characters.
569
+  */
570
+  if (RxByte == '\r')
571
+  {
572
+    return;
573
+  }
574
+  /*
575
+   A new line character indicates the line of data from
576
+   the MightyOhm is complete and can be written to the
577
+   MightyOhm data buffer.
578
+  */
579
+  else if (RxByte == '\n')
580
+  {
581
+    /*
582
+     First copy the timestamp to the MightyOhm data buffer. The "CPS"
583
+     characters are not preserved in the temporary read buffer, so
584
+     restore them to the MightyOhm data buffer, as well.
585
+    */
586
+    sprintf( mightOhmData, "%d:%02d:%02d %d/%d/%d, %s",       \
587
+          hour(), minute(), second(), month(), day(), year(),  \
588
+          MIGHTYOHM_DATA_STRING_HEADER );
589
+    /*
590
+     Now copy the rest of the data in the temporary read buffer to the
591
+     MightyOhm data buffer.
592
+    */ 
593
+    strcat(mightOhmData, readBuffer);
594
+    /*
595
+     Flush the temporary read buffer.
596
+    */
597
+    cIndex = 0;
598
+    readBuffer[0] = 0;
599
+    return;
600
+  }
601
+  /* 
602
+   A new line of data will always have "CPS" as the first
603
+   three characters.  Therefore, when these characters occur in
604
+   sequence, the read buffer should begin collecting characters.
605
+   This is a kluge to deal with an inherent problem in the Software
606
+   Serial library implementation that results in characters dropped
607
+   from the software serial stream buffer.
608
+  */
609
+  if( strstr(readBuffer, MIGHTYOHM_DATA_STRING_HEADER) > 0 ) 
610
+  {
611
+    cIndex = 0;
612
+  }
613
+  /*
614
+   Read characters into a temporary buffer until
615
+   the line of data is complete or the buffer is full.
616
+  */
617
+  if(cIndex < MIGHTYOHM_DATA_STRING_LENGTH)
618
+  {
619
+    readBuffer[cIndex] = RxByte;
620
+    cIndex += 1;
621
+    readBuffer[cIndex] = 0;
622
+  }
623
+  return;
624
+} 
625
+
626
+/* 
627
+  Send a UDP request packet to an NTP time server and listen for a reply.
628
+  When the reply arrives, parse the received UPD packet and compute unix
629
+  epoch time.  Then set the local system clock to the epoch time.
630
+*/
631
+int syncToNetworkTime()
632
+{
633
+  /*
634
+   Send a request to the NTP time server.
635
+  */
636
+  byte packetBuffer[ NTP_PACKET_SIZE]; //buffer to hold outgoing and incoming packets 
637
+  /*
638
+   Send an NTP packet to the time server and allow for network lag
639
+   before checking if a reply is available.
640
+  */
641
+  sendNTPpacket(packetBuffer);
642
+  delay(2000);  // allow 2000 milli-seconds for network lag
643
+
644
+  /*
645
+   Wait for response from NTP time server.
646
+  */
647
+  if ( Udp.parsePacket() )
648
+  {  
649
+    /*
650
+     A UDP packet has arrived, so read the data from it.
651
+    */
652
+    Udp.read( packetBuffer, NTP_PACKET_SIZE );
653
+    /*
654
+     The timestamp starts at byte 40 of the received packet and is four bytes,
655
+     or two words, long. First, esxtract the two words.
656
+    */
657
+    unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);
658
+    unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);
659
+    /*  
660
+     Combine the four bytes (two words) into a long integer
661
+     this is NTP time (seconds since Jan 1 1900).
662
+    */
663
+    unsigned long secsSince1900 = highWord << 16 | lowWord;
664
+    /*  
665
+     Now convert NTP time into UTC time.  Note that
666
+     Unix time starts on Jan 1 1970. In seconds,
667
+     that's 2208988800.  Therfore,
668
+     
669
+         epoch = secsSince1900 - 2208988800UL
670
+         
671
+     Set the local system clock with this value.
672
+    */
673
+    setTime(secsSince1900 - 2208988800UL);
674
+    return 1;
675
+  }
676
+  else
677
+  {
678
+    return 0;
679
+  } /* end if */
680
+}
681
+
682
+/*
683
+  Send an NTP request to the NTP time server.
684
+*/
685
+void sendNTPpacket( byte* packetBuffer )
686
+{
687
+  /*
688
+   Set all bytes in the buffer to 0.
689
+  */
690
+  memset( packetBuffer, 0, NTP_PACKET_SIZE );
691
+  /*
692
+   Initialize values needed to form NTP request.
693
+  */
694
+  packetBuffer[0] = 0b11100011;  // LI, Version, Mode
695
+  packetBuffer[1] = 0;           // Stratum, or type of clock
696
+  packetBuffer[2] = 6;           // Polling Interval
697
+  packetBuffer[3] = 0xEC;        // Peer Clock Precision
698
+  /*
699
+   Set the remaining 8 bytes to zero for Root Delay & Root Dispersion.
700
+  */
701
+  packetBuffer[12]  = 49; 
702
+  packetBuffer[13]  = 0x4E;
703
+  packetBuffer[14]  = 49;
704
+  packetBuffer[15]  = 52;
705
+  /*
706
+   All NTP fields have been given values, so now
707
+   send a packet requesting a timestamp.
708
+  */ 		
709
+  Udp.beginPacket( ntpIpAddr, 123 ); //NTP requests are to port 123
710
+  Udp.write( packetBuffer, NTP_PACKET_SIZE );
711
+  Udp.endPacket();
712
+  return;
713
+}
714
+
715
+/***  COMMAND LINE INTERFACE  ***/
716
+
717
+/*
718
+ Print a command menu to the USB port.  Then wait for a
719
+ response from the user.  When the response has been
720
+ received, execute the command.
721
+*/
722
+void commandMode()
723
+{
724
+  char sCmdBuf[2];
725
+  
726
+  getCurrentIP();  //used for display of settings
727
+  
728
+  while(true)
729
+  {
730
+    /*
731
+     Print the menu.
732
+    */
733
+    Serial.print( F("\n"                         \
734
+                  "1 - view settings\r\n"        \
735
+                  "2 - set IP address\r\n"       \
736
+                  "3 - set NTP server\r\n"       \
737
+                  "4 - toggle verbose\r\n"       \
738
+                  "5 - exit without saving\r\n"  \
739
+                  "6 - save & restart\r\n"       \
740
+                  ">"));
741
+    /*
742
+     Get the command from the user.
743
+    */
744
+    getSerialLine(sCmdBuf, 2);
745
+    Serial.print(F("\n\n\r"));
746
+    /* 
747
+     Execute the command.
748
+    */
749
+    switch (sCmdBuf[0])
750
+    {
751
+      case '1':
752
+        displaySettings();
753
+        break;
754
+      case '2':
755
+        setIP();
756
+        break;
757
+      case '3':
758
+        setNTPIP();
759
+        break;
760
+      case '4':
761
+        toggleVerbose();
762
+        break;
763
+      case '5':
764
+        readSettingsFromEEPROM();
765
+        return;
766
+      case '6':
767
+        writeSettingsToEEPROM();
768
+        /*
769
+         A software reboot is necessary to force the 
770
+         Arduino to request an IP address from a DHCP
771
+         server or to initialize the Ethernet interface
772
+         with a static IP address.
773
+        */
774
+        software_Reset();
775
+        return;
776
+      default:
777
+        Serial.println(F("invalid command"));
778
+    } /* end switch */
779
+  } /* end while */
780
+  return;
781
+}
782
+
783
+/*
784
+ Displays the current system settings.  Displays
785
+ RadMon software version, local IP address, NTP server
786
+ address, and verbose mode setting.
787
+*/
788
+void displaySettings()
789
+{
790
+  char sBuf[16];
791
+  
792
+  // Display RadMon version
793
+  Serial.print(F("Firmware "));
794
+  Serial.print(F(RADMON_VERSION));
795
+  Serial.println();
796
+
797
+  // Display local IP address
798
+  sprintf(sBuf, "%d.%d.%d.%d", ipAddr[0], ipAddr[1], ipAddr[2], ipAddr[3]);
799
+  if (bUseDHCP)
800
+  {
801
+    Serial.print(F("DHCP IP: "));
802
+  }
803
+  else
804
+  {
805
+    Serial.print(F("Static IP: "));
806
+  }
807
+  Serial.println(sBuf);
808
+  
809
+  // Display NTP server IP address
810
+  sprintf(sBuf, "%d.%d.%d.%d", ntpIpAddr[0], ntpIpAddr[1], ntpIpAddr[2], ntpIpAddr[3]);
811
+  Serial.print(F("NTP server: ")); 
812
+  Serial.println(sBuf);
813
+
814
+  // Display verbose mode setting
815
+  printVerboseMode();
816
+  return;
817
+}
818
+
819
+/*
820
+ Sets the local IP address. If the user sends a carriage
821
+ return as the first character, then switch to acquiring
822
+ IP address via DHCP server.
823
+*/
824
+void setIP()
825
+{
826
+  char sBuf[16];
827
+
828
+  Serial.print(F("enter IP (<CR> for DHCP): "));
829
+  getSerialLine(sBuf, 16);
830
+  
831
+  if(strlen(sBuf) == 0)
832
+  {
833
+    bUseDHCP = true;
834
+    parseIpAddress(ipAddr, "0.0.0.0");
835
+  }
836
+  else
837
+  {
838
+    bUseDHCP = false;
839
+    parseIpAddress(ipAddr, sBuf);
840
+  }
841
+  Serial.println();
842
+  return;
843
+}
844
+
845
+/*
846
+ Sets the NTP server IP address.  If the user sends a
847
+ carriage return as the first character, then use the
848
+ default IP address for the NTP server.
849
+*/
850
+void setNTPIP()
851
+{
852
+  char sBuf[16];
853
+  
854
+  Serial.print(F("enter IP: "));
855
+  getSerialLine(sBuf, 16);
856
+  
857
+  if (strlen(sBuf) == 0)
858
+  {
859
+    parseIpAddress(ntpIpAddr, DEFAULT_NTP_SERVER_IP_ADDR);
860
+  }
861
+  else
862
+  {
863
+    parseIpAddress(ntpIpAddr, sBuf);
864
+  }
865
+  Serial.println();
866
+  return;
867
+}
868
+
869
+/*
870
+ Turns verbose mode ON or OFF.
871
+*/
872
+void toggleVerbose()
873
+{
874
+  bVerbose = !bVerbose;
875
+  printVerboseMode();
876
+  return;
877
+}
878
+
879
+/***  GENERAL HELPER FUNCTIONS  ***/
880
+
881
+/*
882
+ Print current verbose mode.
883
+*/
884
+void printVerboseMode()
885
+{
886
+  Serial.print(F("Verbose: "));
887
+  if (bVerbose)
888
+  {
889
+    Serial.println(F("ON"));
890
+  }
891
+  else
892
+  {
893
+    Serial.println(F("OFF"));
894
+  }
895
+  return;
896
+}
897
+
898
+/*
899
+ Get the current IP address from the Ethernet interface
900
+*/
901
+void getCurrentIP()
902
+{
903
+  ipAddr[0] = Ethernet.localIP()[0];
904
+  ipAddr[1] = Ethernet.localIP()[1];
905
+  ipAddr[2] = Ethernet.localIP()[2];
906
+  ipAddr[3] = Ethernet.localIP()[3];
907
+  return;
908
+}
909
+
910
+/*
911
+ Gets a line of data from the user via USB port.
912
+*/
913
+char* getSerialLine(char* sBuffer, int bufferLength)
914
+{
915
+  byte index;
916
+  char cRx;
917
+
918
+  /* 
919
+   Discard extranious characters that may still be in the
920
+   USB serial stream read buffer.  Most often these characters
921
+   will be unprocessed carriage return or line feed characters.
922
+  */
923
+  delay(10);
924
+  while (Serial.available())
925
+  {
926
+    cRx = Serial.read();
927
+  }
928
+
929
+  /*
930
+   Read and process characters from the user as they arrive in
931
+   the USB serial read buffer.
932
+  */
933
+  index = 0;
934
+  while(true)
935
+  {
936
+    /*
937
+     Wait until the user starts pressing keys and bytes
938
+     arrive in the serial read buffer.
939
+    */
940
+    if (Serial.available())
941
+    {
942
+      cRx = Serial.read();
943
+      if (cRx == '\r' || cRx == '\n')
944
+      {
945
+        /*
946
+         The user has finished typing the command and
947
+         has pressed the Enter key. So, discard the
948
+         carriage return and newline characters and then
949
+         return control to the calling function.
950
+        */
951
+        break;
952
+      }
953
+      else if (cRx == 8 || cRx == 127)
954
+      {
955
+        if (index > 0)
956
+        {
957
+          /*
958
+           The user has hit the delete-backspace key,
959
+           so send out a backspace, followed by a space,
960
+           followed by another backspace character.
961
+           This allows for in-line ediiting.
962
+          */
963
+          Serial.write(8);
964
+          Serial.write(32);
965
+          Serial.write(8); 
966
+          index -= 1;
967
+        }
968
+      }
969
+      else if ( index < (bufferLength - 1) )
970
+      {
971
+        /*
972
+         The received character is valid, so write it
973
+         to the buffer. Once the buffer becomes full
974
+         do not write any more characters to it.  When
975
+         the user pressses the enter key, the string
976
+         will be null terminated and control will pass
977
+         back to the calling function.
978
+        */
979
+        Serial.write(cRx); // echo character to terminal
980
+        sBuffer[index] = cRx;
981
+        index += 1;
982
+      } /* end if */
983
+    } /* end if */
984
+  } /* end while */
985
+  sBuffer[index] = 0; // terminate the string
986
+  return sBuffer;
987
+}
988
+
989
+/*
990
+ Writes system configuration settings to non-volitile
991
+ EEPROM.  The items written are the local IP address,
992
+ the NTP server IP address, the state of verbose mode,
993
+ and local IP mode (static or DHCP).
994
+*/
995
+void writeSettingsToEEPROM()
996
+{
997
+  byte ix;
998
+  for (ix = 0; ix < 4; ix++)
999
+  {
1000
+    EEPROM.write(ix, ipAddr[ix]);
1001
+    EEPROM.write(ix + 4, ntpIpAddr[ix]);
1002
+  }
1003
+  EEPROM.write(8, bVerbose);
1004
+  EEPROM.write(9, bUseDHCP);
1005
+  return;
1006
+}
1007
+
1008
+/*
1009
+ Reads system configuration settings from non-volitile
1010
+ EEPROM.  The items read are the local IP address,
1011
+ the NTP server IP address, the state of verbose mode,
1012
+ and local IP mode (static or DHCP).
1013
+*/
1014
+void readSettingsFromEEPROM()
1015
+{
1016
+  byte ix;
1017
+  for (ix = 0; ix < 4; ix++)
1018
+  {
1019
+    ipAddr[ix] = EEPROM.read(ix);
1020
+    ntpIpAddr[ix] = EEPROM.read(ix + 4);
1021
+  }
1022
+  bVerbose = EEPROM.read(8);
1023
+  bUseDHCP = EEPROM.read(9);
1024
+  return;
1025
+}
1026
+
1027
+/*
1028
+ Parses an IP address given in "nnn.nnn.nnn.nnn" string
1029
+ format into four bytes and stores them in an array. Note
1030
+ that this function destroys the contents of the sIP
1031
+ character array.  Therefore this array cannot be
1032
+ reinitialized after calling this function.
1033
+*/
1034
+void parseIpAddress(byte* byBuf, char* sIP)
1035
+{
1036
+  byBuf[0] = atoi(strtok(sIP, "."));
1037
+  byBuf[1] = atoi(strtok(NULL, "."));
1038
+  byBuf[2] = atoi(strtok(NULL, "."));
1039
+  byBuf[3] = atoi(strtok(NULL, "."));
1040
+  return;
1041
+}
1042
+
1043
+/*
1044
+ Restarts the Uno and runs this program from beginning.  This
1045
+ function gets called after a change made from the user
1046
+ interface when the user selects "save and restart".
1047
+*/
1048
+void software_Reset() 
1049
+{
1050
+  asm volatile ("  jmp 0");
1051
+  return; 
1052
+}  
1053
+
Browse code

2017-11-27 update

fractalxaos authored on 11/28/2017 01:35:42
Showing 1 changed files
1 1
deleted file mode 100644
... ...
@@ -1,995 +0,0 @@
1
-/*
2
- Background Radiation Monitor - Web Server
3
- 
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
7
- runs on the Arduino Uno an embedded HTTP server by which Internet
8
- applications can query the MightyOhm for Geiger counter readings.
9
- Also, this software runs a Network Time Protocol (NTP) client, that
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
13
- verbose output mode.
14
- 
15
- Copyright 2014 Jeff Owrey
16
-    This program is free software: you can redistribute it and/or modify
17
-    it under the terms of the GNU General Public License as published by
18
-    the Free Software Foundation, either version 3 of the License, or
19
-    (at your option) any later version.
20
-
21
-    This program is distributed in the hope that it will be useful,
22
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
23
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
24
-    GNU General Public License for more details.
25
-
26
-    You should have received a copy of the GNU General Public License
27
-    along with this program.  If not, see http://www.gnu.org/license.
28
- 
29
- Circuit:
30
- * Main components: Arduino Uno, Ethernet shield, Mighty Ohm Geiger counter
31
- * Ethernet shield attached to pins 10, 11, 12, 13
32
- * In order to allow the MightyOhm to operate on the Uno's 5 volt power
33
-   supply, and thus make the MightyOhm's serial output compatible with the
34
-   Uno, the following has to be done (see MightyOhm schematic):
35
-     1. Change R6 to 1K Ohm.
36
-     2. Change R11 to 330 Ohm.
37
-     3. Connect +5v from the Uno to MightyOhm J6 pin 1.
38
-     4. Connect GND from the Uno to MightyOhm J6 pin 3.
39
-     5. Connect D5 from the Uno to MightyOhm J7 pin 5.
40
-   
41
- Misc Notes:
42
-   As of this release the Uno's SRAM gets entirely maxed out by
43
-   this program.  Any modifications to this program that requires
44
-   additional memory seriously entails the risk that the modifications
45
-   will cause the program to become un-stable.
46
-   
47
- Revision History:  
48
-   * v10 released 25 Feb 2014 by J L Owrey
49
-   * v11 released 24 Jun 2014 by J L Owrey
50
-       - optimization of processRxByte function to conserve SRAM
51
-       - removal of non-used function code
52
-       - defaults to APIPA IP address in the event a DHCP address
53
-         cannot be obtained
54
-   * v12 released 20 Dec 2014 by J L Owrey
55
-       - removed Timestamp global variable to make more dynamic
56
-         memory available for local variables
57
-       - optimized clock network synch algorithm
58
-       - optimized serial update algorithm
59
-   * v13 released 22 Jul 2015 by J L Owrey
60
-       - add use of "F" function to store constant strings in
61
-         program flash memory in order to save SRAM space
62
-   * v14 released 19 Aug 2015 by J L Owrey
63
-       - add ability to respond to web a client request with either
64
-         a JSON compatible string or a standard HTML document
65
-*/
66
-
67
-/***  PREPROCESSOR DEFINES  ***/
68
-
69
-/*
70
- Define the header and version number displayed at startup
71
- and also by the 'view settings' command.
72
-*/
73
-#define STARTUP_HEADER "\n\rRadmon v1.4 (c) 2015"
74
-#define RADMON_VERSION "v1.4"
75
-/*
76
- The following define sets the MAC address of the device.  This
77
- address is a permanent attribute of the device's Ethernet interface,
78
- and never, ever, should be changed.  This address was provided
79
- by the Arduino Ethernet shield manufacturer for use with this
80
- specific instance of the Ethernet shield.  This MAC address should
81
- be shown on a label affixed to the device housing.
82
-*/
83
-#define ETHERNET_MAC_ADDRESS 0x90, 0xA2, 0xDA, 0x0D, 0x84, 0xF6
84
-/*
85
- The following defines an APIPA default address in the event that
86
- DHCP mode is ON and a DHCP address cannot be obtained.
87
-*/
88
-#define DEFAULT_APIPA_IP_ADDRESS "169.254.100.10"
89
-/*
90
- The following defines set the period of a 'heartbeat' string sent
91
- out over the device's USB port.  This heartbeat consists of a serial
92
- data string containing the current radiation reading and GM time.
93
-*/
94
-#define SERIAL_UPDATE_INTERVAL 30000  //milli-seconds
95
-#define VERBOSE_SERIAL_UPDATE_INTERVAL 10000  //milli-seconds
96
-/*
97
- The following define sets the port number the HTTP service will use to
98
- listen for requests from Internet clients.  Normally HTTP requests use
99
- port 80.
100
-*/
101
-#define HTTP_SERVER_PORT 80
102
-/*
103
- The following defines are for configuring a local NTP client
104
- for synchronizing the local system clock to network time.
105
- Note that the default setting is the IP address of the following
106
- time server:
107
-              time-a.timefreq.bldrdoc.gov
108
-*/
109
-#define DEFAULT_NTP_SERVER_IP_ADDR "132.163.4.101"
110
-#define NTP_PORT 8888
111
-#define NTP_PACKET_SIZE 48 // NTP time stamp is in the first 48 bytes of the message
112
-/*
113
- The following defines how often the system clock gets synchronized
114
- to network time.
115
-*/
116
-#define NET_SYNCH_INTERVAL 43200 //number in seconds
117
-/*
118
- The following defines the size of the buffer space required for the
119
- serial data string from the Mighty Ohm Geiger counter.  The serial
120
- data string is defined as the text from newline character to newline
121
- character.
122
-*/
123
-#define MIGHTYOHM_DATA_STRING_LENGTH 65
124
-/*
125
- The beginning of the MightyOhm data string always begins with the
126
- same three characters.  These three characters determine the 
127
- beginning of a new line of data from the MightyOhm.
128
-*/
129
-#define MIGHTYOHM_DATA_STRING_HEADER "CPS"
130
-
131
-/***  LIBRARY MODULES USED  ***/
132
-
133
-#include <Time.h>
134
-#include <SPI.h>         
135
-#include <Ethernet.h>
136
-#include <EthernetUdp.h>
137
-#include <SoftwareSerial.h>
138
-#include <EEPROM.h>;
139
-
140
-/***  GLOBAL DECLARATIONS ***/
141
-
142
-/*
143
- Create and initialize a mac address object for the Ethernet interface. 
144
-*/
145
-byte mac[] = { ETHERNET_MAC_ADDRESS };
146
-/*
147
- Create and initialize an HTTP server object.  The object is initialized
148
- to the TCP port the HTTP server will use to listen for clients.
149
-*/
150
-EthernetServer httpServer(HTTP_SERVER_PORT);
151
-/*
152
- Create a UDP client object for sending packets to
153
- and receiveing packets from an NTP time server.
154
-*/
155
-EthernetUDP Udp;
156
-/*
157
- Create a software serial port for receiving serial data from
158
- the MightyOhm. Note that the Uno pin 5 receives serial data
159
- FROM the MightyOhm.  The Uno's pin 6 is not used, as there is
160
- no need to send serial data to the MightyOhm. 
161
-*/
162
-SoftwareSerial MightyOhmTxOut(5, 6);
163
-/*
164
- Create global variables to store the MightOhm data, next heartbeat
165
- time, and next synchronization time.
166
-*/
167
-char MightyOhmData[MIGHTYOHM_DATA_STRING_LENGTH + 1];
168
-unsigned long SerialUpdateTime;
169
-time_t NextClockSynchTime;
170
-/*
171
- Create global variables to store the verbose mode state (ON or OFF)
172
- and the IP address mode state (static or DHCP).
173
-*/
174
-boolean bVerbose;
175
-boolean bUseDHCP;
176
-/*
177
- Create and initialize global arrays to hold the current IP address
178
- and the NTP server IP address.
179
-*/
180
-byte ipAddr[4] = {};
181
-byte ntpip[4] = {};
182
-
183
-/*** SYSTEM STARTUP  ***/
184
-
185
-void setup()
186
-{
187
-  /*
188
-   Open serial communications to and from the Uno's USB port.
189
-  */
190
-  Serial.begin(9600);
191
-  /* 
192
-   Print to the USB port a header showing Radmon
193
-   version of this program and the copyright notice.
194
-  */
195
-  Serial.println(F(STARTUP_HEADER));
196
-  /*
197
-    Get the system configuration from EEPROM.
198
-  */
199
-  readSettingsFromEEPROM();
200
-  /*
201
-   Start up the Ethernet interface using either a static or
202
-   DHCP supplied address (depending on stored system configuration).
203
-  */
204
-  Serial.println(F("Configuring network interface..."));
205
-  if(bUseDHCP)
206
-  {
207
-    if ( Ethernet.begin(mac) == 0 )
208
-    {
209
-      /* DHCP not responding so use APIPA address */
210
-      parseIpAddress(ipAddr, DEFAULT_APIPA_IP_ADDRESS);
211
-      Ethernet.begin(mac, ipAddr);
212
-      Serial.println(F("DHCP failed - using APIPA"));
213
-    }
214
-    else
215
-    {
216
-      Serial.print(F("DHCP supplied IP: "));
217
-      Serial.println(Ethernet.localIP());
218
-    }
219
-  }
220
-  else
221
-  {
222
-    Ethernet.begin(mac, ipAddr);
223
-  }
224
-  /*
225
-   Start up NTP client service.
226
-  */
227
-  Udp.begin(NTP_PORT);
228
-  /*
229
-    Synchronize the system clock to network time.
230
-  */
231
-  synchronizeSystemClock();
232
-  /*
233
-   Start up the HTTP server.
234
-  */
235
-  Serial.println(F("Starting HTTP server..."));
236
-  httpServer.begin();
237
-  /*
238
-    Open serial communications to the MightyOhm device.
239
-  */  
240
-  MightyOhmTxOut.begin(9600);
241
-   /*
242
-    Initialize initial time for sending out the hearbeat string. Normally
243
-    the system clock will be at approx 3200 msec at this point. So allow
244
-    some additional time for data to accumulate in MightyOhm data buffer.
245
-  */
246
-  SerialUpdateTime = millis() + 2000;
247
-  /*
248
-   Initialize MightyOhm data string to empty.
249
-  */
250
-  MightyOhmData[0] = 0;
251
-  return;
252
-}
253
-
254
-/*** MAIN LOOP ***/
255
-
256
-void loop()
257
-{
258
-  /*
259
-   Check for user keyboard 'c' pressed.  This character switches
260
-   to command mode.
261
-  */   
262
-  if ( Serial.available() )
263
-  {
264
-    // get incoming byte
265
-    if(Serial.read() == 'c')
266
-    {
267
-      commandMode();
268
-    }
269
-  }  
270
-  /*
271
-    Poll serial input buffer from MightyOhm for new data and 
272
-    process received bytes to form a complete data string.
273
-  */
274
-  while ( MightyOhmTxOut.available() )
275
-  {
276
-    processRxByte( MightyOhmTxOut.read() );
277
-  }
278
-  /*
279
-    Every so often send a 'heartbeat' string to the USB port.
280
-    The heartbeat consists of UTC time stamp and current data
281
-    from the MightyOhm Geiger counter.
282
-  */
283
-  if ( millis() > SerialUpdateTime )
284
-  {
285
-    Serial.println( MightyOhmData );
286
-    /* Set the time for the next heartbeat pulse to go out. */
287
-    SerialUpdateTime = millis() +  \
288
-                       (bVerbose ? VERBOSE_SERIAL_UPDATE_INTERVAL :  \ 
289
-                                    SERIAL_UPDATE_INTERVAL);
290
-  }
291
-  /*
292
-   Listen for and and process requests from HTTP clients.
293
-  */  
294
-  listenForEthernetClients();
295
-  /*
296
-   Periodically synchronize local system clock to time
297
-   provided by NTP time server.
298
-  */
299
-  if ( now() > NextClockSynchTime )
300
-  {
301
-    synchronizeSystemClock();
302
-  }
303
-  return;
304
-}
305
-
306
-/*
307
- Synchronize the local system clock to
308
- network time provided by NTP time server.
309
-*/
310
-void synchronizeSystemClock()
311
-{
312
-  byte count;
313
-  
314
-  Serial.println(F("Synchronizing with network time server..."));
315
-    
316
-  for(count = 0; count < 3; count++)  // Attempt to synchronize 3 times
317
-  {
318
-    if(syncToNetworkTime() == 1)
319
-    {
320
-      //  Synchronization successful
321
-      break;
322
-    }
323
-    delay(1000);
324
-  } /* end for */ 
325
-  if(count == 3) {
326
-    Serial.println(F("synch failed"));
327
-  }
328
-  /* 
329
-   Set the time for the next network NTP
330
-   time synchronization to occur.
331
-  */
332
-  NextClockSynchTime = now() + NET_SYNCH_INTERVAL;
333
-  return;
334
-}
335
-
336
-/*
337
-  Handle HTTP GET requests from an HTTP client.
338
-*/  
339
-void listenForEthernetClients()
340
-{
341
-  char sBuffer[14];
342
-  byte i;
343
-  byte iMode;
344
-  char c, d;
345
-  
346
-  // listen for incoming clients
347
-  EthernetClient client = httpServer.available();
348
-  if (client) {
349
-    if (bVerbose) Serial.print("\n");
350
-    Serial.println(F("client request"));
351
-    i = 0;
352
-    sBuffer[0] = 0;
353
-    iMode = 0;
354
-    d = 0;
355
-  
356
-    while(client.connected() && client.available()) {
357
-      c = client.read();
358
-      /*
359
-       * The end of an HTTP client request is always signaled by a
360
-       * blank line, that is, by two consecutive line feed and carriage 
361
-       * return characters "\r\n\r\n".  The following two lines of code 
362
-       * look for this condition.
363
-       */
364
-      if(c == '\n' && d == '\n') break; //end of HTTP client request
365
-      if (c != '\r') d = c; // ignore carriage return characters
366
-
367
-      if (bVerbose) {
368
-        Serial.write(c);
369
-      }
370
-      // prevent buffer overruns
371
-      if (i < 13) {
372
-        sBuffer[i++] = c;
373
-        sBuffer[i] = 0;
374
-      }
375
-      // request for JSON string response
376
-      if(strcmp(sBuffer, "GET /rdata ") == 0) {
377
-        iMode = 1;
378
-      }
379
-      // request for standard HTML document
380
-      if(strcmp(sBuffer, "GET / ") == 0) {
381
-        iMode = 2;
382
-      }
383
-    }
384
-    /*
385
-     Send a standard HTTP response header to the
386
-     client's GET request.
387
-    */
388
-    client.println(F("HTTP/1.1 200 OK\n"        \
389
-                   "Content-Type: text/html\n"  \
390
-                   "Connnection: close\n"       \
391
-                   "\n"                         \
392
-                   ));
393
-                   
394
-    switch (iMode) {
395
-      case 0:
396
-        // Respond to an invalid URL received from the client
397
-        client.print(F("<!DOCTYPE html>\n" \
398
-                       "<html><head><title>DIY Radmon</title></head>"  \
399
-                       "<body><h2>Invalid URL</h2>" \
400
-                       "<p>You have reached a server at an unknown URL.</p>" \
401
-                       "<p>If you think you made this call in error,<br>" \
402
-                       "please hangup and try your call again.</p>" \
403
-                       "</body></html>"));
404
-        break;
405
-      case 1:
406
-        transmitJson(client);
407
-        break;
408
-      case 2:
409
-        transmitWebPage(client);
410
-        break;
411
-    }
412
-    // give the web browser time to receive the data
413
-    delay(5);
414
-    // close the connection:
415
-    client.stop();
416
-    if (bVerbose) {
417
-      Serial.print(F("client disconnected\r\n\n"));
418
-    }
419
-  }
420
-  return;
421
-}
422
-
423
-/*
424
- Send to the client the MightyOhm Geiger counter's
425
- current readings, embedded in an HTML document.
426
-*/
427
-void transmitWebPage(EthernetClient client)
428
-{
429
-  char strBuffer[MIGHTYOHM_DATA_STRING_LENGTH];
430
-
431
-  strcpy(strBuffer, MightyOhmData);
432
-  /*
433
-   * Send the actual HTML page the user will see in their web
434
-   * browser.
435
-  */
436
-  client.print(F("<!DOCTYPE html>\n" \
437
-                 "<html><head><title>DIY Radmon</title>"  \
438
-                 "<style>pre{font-size:140%;}</style>" \
439
-                 "</head><body><h2>DIY Radiation Monitor</h2>" \
440
-                 "<p><a href=\"http://intravisions.com/radmon/\">" \
441
-                 "<i>intravisions.com</i></a></p><hr>" \
442
-                 "<p><pre>UTC&#9;"));
443
-  /* Data Items */
444
-  client.print(strtok(strBuffer, " "));
445
-  client.print(F(" "));
446
-  client.print(strtok(NULL, ", "));
447
-  client.print(F("<br>"));
448
-  client.print(strtok(NULL, ", "));
449
-  client.print(F("&#9;"));
450
-  client.print(strtok(NULL, ", "));
451
-  client.print(F("<br>"));
452
-  client.print(strtok(NULL, ", "));
453
-  client.print(F("&#9;"));
454
-  client.print(strtok(NULL, ", "));
455
-  client.print(F("<br>"));
456
-  client.print(strtok(NULL, ", "));
457
-  client.print(F("&#9;"));
458
-  client.print(strtok(NULL, ", "));
459
-  client.print(F("<br>"));
460
-  client.print(F("Mode&#9;"));
461
-  client.print(strtok(NULL, ", "));
462
-  client.print(F("<br></pre></p></body></html>"));
463
-  return;
464
-}  
465
-
466
-/*
467
- Send to the client the MightyOhm Geiger counter's
468
- current readings, embedded in a JSON compatible string.
469
-*/
470
-void transmitJson(EthernetClient client) {
471
-  char strBuffer[MIGHTYOHM_DATA_STRING_LENGTH];
472
-
473
-  strcpy(strBuffer, MightyOhmData);
474
-  /*
475
-   * Format and transmit a JSON compatible data string.
476
-   */
477
-  client.print(F("$,UTC="));
478
-  client.print(strtok(strBuffer, " "));
479
-  client.print(F(" "));
480
-  client.print(strtok(NULL, ", "));
481
-  client.print(F(","));
482
-  client.print(strtok(NULL, ", "));
483
-  client.print(F("="));
484
-  client.print(strtok(NULL, ", "));
485
-  client.print(F(","));
486
-  client.print(strtok(NULL, ", "));
487
-  client.print(F("="));
488
-  client.print(strtok(NULL, ", "));
489
-  client.print(F(","));
490
-  client.print(strtok(NULL, ", "));
491
-  client.print(F("="));
492
-  client.print(strtok(NULL, ", "));
493
-  client.print(F(","));
494
-  client.print(F("Mode="));
495
-  client.print(strtok(NULL, ", "));
496
-  client.print(F(",#"));
497
-  return;
498
-}
499
-
500
-/*
501
-   Process bytes received from the MightyOhm Geiger counter,
502
-   one at a time, to create a well formed string.
503
-*/
504
-void processRxByte( char RxByte )
505
-{
506
-  static char readBuffer[MIGHTYOHM_DATA_STRING_LENGTH];
507
-  static byte cIndex = 0;
508
-  
509
-  /*
510
-     Discard carriage return characters.
511
-  */
512
-  if (RxByte == '\r')
513
-  {
514
-    return;
515
-  }
516
-  /*
517
-   A new line character indicates the line of data from
518
-   the MightyOhm is complete and can be written to the
519
-   MightyOhm data buffer.
520
-  */
521
-  else if (RxByte == '\n')
522
-  {
523
-    /*
524
-     First copy the timestamp to the MightyOhm data buffer. The "CPS"
525
-     characters are not preserved in the temporary read buffer, so
526
-     restore them to the MightyOhm data buffer, as well.
527
-    */
528
-    sprintf( MightyOhmData, "%d:%02d:%02d %d/%d/%d, %s",       \
529
-          hour(), minute(), second(), month(), day(), year(),  \
530
-          MIGHTYOHM_DATA_STRING_HEADER );
531
-    /*
532
-     Now copy the rest of the data in the temporary read buffer to the
533
-     MightyOhm data buffer.
534
-    */ 
535
-    strcat(MightyOhmData, readBuffer);
536
-    /*
537
-     Flush the temporary read buffer.
538
-    */
539
-    cIndex = 0;
540
-    readBuffer[0] = 0;
541
-    return;
542
-  }
543
-  /* 
544
-   A new line of data will always have "CPS" as the first
545
-   three characters.  Therefore, when these characters occur in
546
-   sequence, the read buffer should begin collecting characters.
547
-   This is a kluge to deal with an inherent problem in the Software
548
-   Serial library implementation that results in characters dropped
549
-   from the software serial stream buffer.
550
-  */
551
-  if( strstr(readBuffer, MIGHTYOHM_DATA_STRING_HEADER) > 0 ) 
552
-  {
553
-    cIndex = 0;
554
-  }
555
-  /*
556
-   Read characters into a temporary buffer until
557
-   the line of data is complete or the buffer is full.
558
-  */
559
-  if(cIndex < MIGHTYOHM_DATA_STRING_LENGTH)
560
-  {
561
-    readBuffer[cIndex] = RxByte;
562
-    cIndex += 1;
563
-    readBuffer[cIndex] = 0;
564
-  }
565
-  return;
566
-} 
567
-
568
-/* 
569
-  Send a UDP request packet to an NTP time server and listen for a reply.
570
-  When the reply arrives, parse the received UPD packet and compute unix
571
-  epoch time.  Then set the local system clock to the epoch time.
572
-*/
573
-int syncToNetworkTime()
574
-{
575
-  /*
576
-   Send a request to the NTP time server.
577
-  */
578
-  byte packetBuffer[ NTP_PACKET_SIZE]; //buffer to hold outgoing and incoming packets 
579
-  /*
580
-   Send an NTP packet to the time server and allow for network lag
581
-   before checking if a reply is available.
582
-  */
583
-  sendNTPpacket(packetBuffer);
584
-  delay(2000);  // allow 2000 milli-seconds for network lag
585
-
586
-  /*
587
-   Wait for response from NTP time server.
588
-  */
589
-  if ( Udp.parsePacket() )
590
-  {  
591
-    /*
592
-     A UDP packet has arrived, so read the data from it.
593
-    */
594
-    Udp.read( packetBuffer, NTP_PACKET_SIZE );
595
-    /*
596
-     The timestamp starts at byte 40 of the received packet and is four bytes,
597
-     or two words, long. First, esxtract the two words.
598
-    */
599
-    unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);
600
-    unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);
601
-    /*  
602
-     Combine the four bytes (two words) into a long integer
603
-     this is NTP time (seconds since Jan 1 1900).
604
-    */
605
-    unsigned long secsSince1900 = highWord << 16 | lowWord;
606
-    /*  
607
-     Now convert NTP time into UTC time.  Note that
608
-     Unix time starts on Jan 1 1970. In seconds,
609
-     that's 2208988800.  Therfore,
610
-     
611
-         epoch = secsSince1900 - 2208988800UL
612
-         
613
-     Set the local system clock with this value.
614
-    */
615
-    setTime(secsSince1900 - 2208988800UL);
616
-    return 1;
617
-  }
618
-  else
619
-  {
620
-    return 0;
621
-  } /* end if */
622
-}
623
-
624
-/*
625
-  Send an NTP request to the NTP time server.
626
-*/
627
-void sendNTPpacket( byte* packetBuffer )
628
-{
629
-  /*
630
-   Set all bytes in the buffer to 0.
631
-  */
632
-  memset( packetBuffer, 0, NTP_PACKET_SIZE );
633
-  /*
634
-   Initialize values needed to form NTP request.
635
-  */
636
-  packetBuffer[0] = 0b11100011;  // LI, Version, Mode
637
-  packetBuffer[1] = 0;           // Stratum, or type of clock
638
-  packetBuffer[2] = 6;           // Polling Interval
639
-  packetBuffer[3] = 0xEC;        // Peer Clock Precision
640
-  /*
641
-   Set the remaining 8 bytes to zero for Root Delay & Root Dispersion.
642
-  */
643
-  packetBuffer[12]  = 49; 
644
-  packetBuffer[13]  = 0x4E;
645
-  packetBuffer[14]  = 49;
646
-  packetBuffer[15]  = 52;
647
-  /*
648
-   All NTP fields have been given values, so now
649
-   send a packet requesting a timestamp.
650
-  */ 		
651
-  Udp.beginPacket( ntpip, 123 ); //NTP requests are to port 123
652
-  Udp.write( packetBuffer, NTP_PACKET_SIZE );
653
-  Udp.endPacket();
654
-  return;
655
-}
656
-
657
-/***  COMMAND LINE INTERFACE  ***/
658
-
659
-/*
660
- Print a command menu to the USB port.  Then wait for a
661
- response from the user.  When the response has been
662
- received, execute the command.
663
-*/
664
-void commandMode()
665
-{
666
-  char sCmdBuf[2];
667
-  
668
-  getCurrentIP();  //used for display of settings
669
-  
670
-  while(true)
671
-  {
672
-    /*
673
-     Print the menu.
674
-    */
675
-    Serial.print( F("\n"                         \
676
-                  "1 - view settings\r\n"        \
677
-                  "2 - set IP address\r\n"       \
678
-                  "3 - set NTP server\r\n"       \
679
-                  "4 - toggle verbose\r\n"       \
680
-                  "5 - exit without saving\r\n"  \
681
-                  "6 - save & restart\r\n"       \
682
-                  ">"));
683
-    /*
684
-     Get the command from the user.
685
-    */
686
-    getSerialLine(sCmdBuf, 2);
687
-    Serial.print(F("\n\n\r"));
688
-    /* 
689
-     Execute the command.
690
-    */
691
-    switch (sCmdBuf[0])
692
-    {
693
-      case '1':
694
-        displaySettings();
695
-        break;
696
-      case '2':
697
-        setIP();
698
-        break;
699
-      case '3':
700
-        setNTPIP();
701
-        break;
702
-      case '4':
703
-        toggleVerbose();
704
-        break;
705
-      case '5':
706
-        readSettingsFromEEPROM();
707
-        return;
708
-      case '6':
709
-        writeSettingsToEEPROM();
710
-        /*
711
-         A software reboot is necessary to force the 
712
-         Arduino to request an IP address from a DHCP
713
-         server or to initialize the Ethernet interface
714
-         with a static IP address.
715
-        */
716
-        software_Reset();
717
-        return;
718
-      default:
719
-        Serial.println(F("invalid command"));
720
-    } /* end switch */
721
-  } /* end while */
722
-  return;
723
-}
724
-
725
-/*
726
- Displays the current system settings.  Displays
727
- RadMon software version, local IP address, NTP server
728
- address, and verbose mode setting.
729
-*/
730
-void displaySettings()
731
-{
732
-  char sBuf[16];
733
-  
734
-  // Display RadMon version
735
-  Serial.print(F("Firmware "));
736
-  Serial.print(F(RADMON_VERSION));
737
-  Serial.println();
738
-
739
-  // Display local IP address
740
-  sprintf(sBuf, "%d.%d.%d.%d", ipAddr[0], ipAddr[1], ipAddr[2], ipAddr[3]);
741
-  if (bUseDHCP)
742
-  {
743
-    Serial.print(F("DHCP IP: "));
744
-  }
745
-  else
746
-  {
747
-    Serial.print(F("Static IP: "));
748
-  }
749
-  Serial.println(sBuf);
750
-  
751
-  // Display NTP server IP address
752
-  sprintf(sBuf, "%d.%d.%d.%d", ntpip[0], ntpip[1], ntpip[2], ntpip[3]);
753
-  Serial.print(F("NTP server: ")); 
754
-  Serial.println(sBuf);
755
-
756
-  // Display verbose mode setting
757
-  printVerboseMode();
758
-  return;
759
-}
760
-
761
-/*
762
- Sets the local IP address. If the user sends a carriage
763
- return as the first character, then switch to acquiring
764
- IP address via DHCP server.
765
-*/
766
-void setIP()
767
-{
768
-  char sBuf[16];
769
-
770
-  Serial.print(F("enter IP (<CR> for DHCP): "));
771
-  getSerialLine(sBuf, 16);
772
-  
773
-  if(strlen(sBuf) == 0)
774
-  {
775
-    bUseDHCP = true;
776
-    parseIpAddress(ipAddr, "0.0.0.0");
777
-  }
778
-  else
779
-  {
780
-    bUseDHCP = false;
781
-    parseIpAddress(ipAddr, sBuf);
782
-  }
783
-  Serial.println();
784
-  return;
785
-}
786
-
787
-/*
788
- Sets the NTP server IP address.  If the user sends a
789
- carriage return as the first character, then use the
790
- default IP address for the NTP server.
791
-*/
792
-void setNTPIP()
793
-{
794
-  char sBuf[16];
795
-  
796
-  Serial.print(F("enter IP: "));
797
-  getSerialLine(sBuf, 16);
798
-  
799
-  if (strlen(sBuf) == 0)
800
-  {
801
-    parseIpAddress(ntpip, DEFAULT_NTP_SERVER_IP_ADDR);
802
-  }
803
-  else
804
-  {
805
-    parseIpAddress(ntpip, sBuf);
806
-  }
807
-  Serial.println();
808
-  return;
809
-}
810
-
811
-/*
812
- Turns verbose mode ON or OFF.
813
-*/
814
-void toggleVerbose()
815
-{
816
-  bVerbose = !bVerbose;
817
-  printVerboseMode();
818
-  return;
819
-}
820
-
821
-/***  GENERAL HELPER FUNCTIONS  ***/
822
-
823
-/*
824
- Print current verbose mode.
825
-*/
826
-void printVerboseMode()
827
-{
828
-  Serial.print(F("Verbose: "));
829
-  if (bVerbose)
830
-  {
831
-    Serial.println(F("ON"));
832
-  }
833
-  else
834
-  {
835
-    Serial.println(F("OFF"));
836
-  }
837
-  return;
838
-}
839
-
840
-/*
841
- Get the current IP address from the Ethernet interface
842
-*/
843
-void getCurrentIP()
844
-{
845
-  ipAddr[0] = Ethernet.localIP()[0];
846
-  ipAddr[1] = Ethernet.localIP()[1];
847
-  ipAddr[2] = Ethernet.localIP()[2];
848
-  ipAddr[3] = Ethernet.localIP()[3];
849
-  return;
850
-}
851
-
852
-/*
853
- Gets a line of data from the user via USB port.
854
-*/
855
-char* getSerialLine(char* sBuffer, int bufferLength)
856
-{
857
-  byte index;
858
-  char cRx;
859
-
860
-  /* 
861
-   Discard extranious characters that may still be in the
862
-   USB serial stream read buffer.  Most often these characters
863
-   will be unprocessed carriage return or line feed characters.
864
-  */
865
-  delay(10);
866
-  while (Serial.available())
867
-  {
868
-    cRx = Serial.read();
869
-  }
870
-
871
-  /*
872
-   Read and process characters from the user as they arrive in
873
-   the USB serial read buffer.
874
-  */
875
-  index = 0;
876
-  while(true)
877
-  {
878
-    /*
879
-     Wait until the user starts pressing keys and bytes
880
-     arrive in the serial read buffer.
881
-    */
882
-    if (Serial.available())
883
-    {
884
-      cRx = Serial.read();
885
-      if (cRx == '\r' || cRx == '\n')
886
-      {
887
-        /*
888
-         The user has finished typing the command and
889
-         has pressed the Enter key. So, discard the
890
-         carriage return and newline characters and then
891
-         return control to the calling function.
892
-        */
893
-        break;
894
-      }
895
-      else if (cRx == 8 || cRx == 127)
896
-      {
897
-        if (index > 0)
898
-        {
899
-          /*
900
-           The user has hit the delete-backspace key,
901
-           so send out a backspace, followed by a space,
902
-           followed by another backspace character.
903
-           This allows for in-line ediiting.
904
-          */
905
-          Serial.write(8);
906
-          Serial.write(32);
907
-          Serial.write(8); 
908
-          index -= 1;
909
-        }
910
-      }
911
-      else if ( index < (bufferLength - 1) )
912
-      {
913
-        /*
914
-         The received character is valid, so write it
915
-         to the buffer. Once the buffer becomes full
916
-         do not write any more characters to it.  When
917
-         the user pressses the enter key, the string
918
-         will be null terminated and control will pass
919
-         back to the calling function.
920
-        */
921
-        Serial.write(cRx); // echo character to terminal
922
-        sBuffer[index] = cRx;
923
-        index += 1;
924
-      } /* end if */
925
-    } /* end if */
926
-  } /* end while */
927
-  sBuffer[index] = 0; // terminate the string
928
-  return sBuffer;
929
-}
930
-
931
-/*
932
- Writes system configuration settings to non-volitile
933
- EEPROM.  The items written are the local IP address,
934
- the NTP server IP address, the state of verbose mode,
935
- and local IP mode (static or DHCP).
936
-*/
937
-void writeSettingsToEEPROM()
938
-{
939
-  byte ix;
940
-  for (ix = 0; ix < 4; ix++)
941
-  {
942
-    EEPROM.write(ix, ipAddr[ix]);
943
-    EEPROM.write(ix + 4, ntpip[ix]);
944
-  }
945
-  EEPROM.write(8, bVerbose);
946
-  EEPROM.write(9, bUseDHCP);
947
-  return;
948
-}
949
-
950
-/*
951
- Reads system configuration settings from non-volitile
952
- EEPROM.  The items read are the local IP address,
953
- the NTP server IP address, the state of verbose mode,
954
- and local IP mode (static or DHCP).
955
-*/
956
-void readSettingsFromEEPROM()
957
-{
958
-  byte ix;
959
-  for (ix = 0; ix < 4; ix++)
960
-  {
961
-    ipAddr[ix] = EEPROM.read(ix);
962
-    ntpip[ix] = EEPROM.read(ix + 4);
963
-  }
964
-  bVerbose = EEPROM.read(8);
965
-  bUseDHCP = EEPROM.read(9);
966
-  return;
967
-}
968
-
969
-/*
970
- Parses an IP address given in "nnn.nnn.nnn.nnn" string
971
- format into four bytes and stores them in an array. Note
972
- that this function destroys the contents of the sIP
973
- character array.  Therefore this array cannot be
974
- reinitialized after calling this function.
975
-*/
976
-void parseIpAddress(byte* byBuf, char* sIP)
977
-{
978
-  byBuf[0] = atoi(strtok(sIP, "."));
979
-  byBuf[1] = atoi(strtok(NULL, "."));
980
-  byBuf[2] = atoi(strtok(NULL, "."));
981
-  byBuf[3] = atoi(strtok(NULL, "."));
982
-  return;
983
-}
984
-
985
-/*
986
- Restarts the Uno and runs this program from beginning.  This
987
- function gets called after a change made from the user
988
- interface when the user selects "save and restart".
989
-*/
990
-void software_Reset() 
991
-{
992
-  asm volatile ("  jmp 0");
993
-  return; 
994
-}  
995
-
Browse code

revisions 20151229

Jeff Owrey authored on 12/30/2015 04:41:17
Showing 1 changed files
... ...
@@ -373,7 +373,7 @@ void listenForEthernetClients()
373 373
         sBuffer[i] = 0;
374 374
       }
375 375
       // request for JSON string response
376
-      if(strcmp(sBuffer, "GET /jsdata ") == 0) {
376
+      if(strcmp(sBuffer, "GET /rdata ") == 0) {
377 377
         iMode = 1;
378 378
       }
379 379
       // request for standard HTML document
... ...
@@ -388,20 +388,19 @@ void listenForEthernetClients()
388 388
     client.println(F("HTTP/1.1 200 OK\n"        \
389 389
                    "Content-Type: text/html\n"  \
390 390
                    "Connnection: close\n"       \
391
-               //  "Refresh: 10\n"              \
392 391
                    "\n"                         \
393 392
                    ));
394 393
                    
395 394
     switch (iMode) {
396 395
       case 0:
397 396
         // Respond to an invalid URL received from the client
398
-        client.print(F("<!DOCTYPE HTML>\n" \
399
-                       "<HTML><HEAD><TITLE>Radmon</TITLE></HEAD>"  \
400
-                       "<BODY><H2>Invalid URL</H2>"  \
401
-                       "<P> You have reached a server at an unknown " \
402
-                       "URL.  If you think you made this request in error " \
403
-                       "please disconnect and try your request again.</P>" \
404
-                       "</BODY></HTML>"));
397
+        client.print(F("<!DOCTYPE html>\n" \
398
+                       "<html><head><title>DIY Radmon</title></head>"  \
399
+                       "<body><h2>Invalid URL</h2>" \
400
+                       "<p>You have reached a server at an unknown URL.</p>" \
401
+                       "<p>If you think you made this call in error,<br>" \
402
+                       "please hangup and try your call again.</p>" \
403
+                       "</body></html>"));
405 404
         break;
406 405
       case 1:
407 406
         transmitJson(client);
... ...
@@ -434,33 +433,33 @@ void transmitWebPage(EthernetClient client)
434 433
    * Send the actual HTML page the user will see in their web
435 434
    * browser.
436 435
   */
437
-  client.print(F("<!DOCTYPE HTML>\n" \
438
-                 "<HTML><HEAD><TITLE>Radmon</TITLE>"  \
439
-                 "</HEAD><BODY><H2>Radiation Monitor</H2>" \
440
-                 "<P><A HREF=\"http://intravisions.com/radmon/\">" \
441
-                 "<I>IntraVisions.com/radmon</I></A></P>" \
442
-                 "<HR><FONT SIZE=\"+1\">"));
443
-  /* Data Items */             
444
-  client.print(F("<PRE>UTC&#9;"));
436
+  client.print(F("<!DOCTYPE html>\n" \
437
+                 "<html><head><title>DIY Radmon</title>"  \
438
+                 "<style>pre{font-size:140%;}</style>" \
439
+                 "</head><body><h2>DIY Radiation Monitor</h2>" \
440
+                 "<p><a href=\"http://intravisions.com/radmon/\">" \
441
+                 "<i>intravisions.com</i></a></p><hr>" \
442
+                 "<p><pre>UTC&#9;"));
443
+  /* Data Items */
445 444
   client.print(strtok(strBuffer, " "));
446 445
   client.print(F(" "));
447 446
   client.print(strtok(NULL, ", "));
448
-  client.print(F("<BR>"));
447
+  client.print(F("<br>"));
449 448
   client.print(strtok(NULL, ", "));
450 449
   client.print(F("&#9;"));
451 450
   client.print(strtok(NULL, ", "));
452
-  client.print(F("<BR>"));
451
+  client.print(F("<br>"));
453 452
   client.print(strtok(NULL, ", "));
454 453
   client.print(F("&#9;"));
455 454
   client.print(strtok(NULL, ", "));
456
-  client.print(F("<BR>"));
455
+  client.print(F("<br>"));
457 456
   client.print(strtok(NULL, ", "));
458 457
   client.print(F("&#9;"));
459 458
   client.print(strtok(NULL, ", "));
460
-  client.print(F("<BR>"));
459
+  client.print(F("<br>"));
461 460
   client.print(F("Mode&#9;"));
462 461
   client.print(strtok(NULL, ", "));
463
-  client.print(F("<BR></PRE></FONT></BODY></HTML>"));
462
+  client.print(F("<br></pre></p></body></html>"));
464 463
   return;
465 464
 }  
466 465
 
... ...
@@ -475,7 +474,7 @@ void transmitJson(EthernetClient client) {
475 474
   /*
476 475
    * Format and transmit a JSON compatible data string.
477 476
    */
478
-  client.print(F("[{\"radmon\":\"$,UTC="));
477
+  client.print(F("$,UTC="));
479 478
   client.print(strtok(strBuffer, " "));
480 479
   client.print(F(" "));
481 480
   client.print(strtok(NULL, ", "));
... ...
@@ -494,7 +493,7 @@ void transmitJson(EthernetClient client) {
494 493
   client.print(F(","));
495 494
   client.print(F("Mode="));
496 495
   client.print(strtok(NULL, ", "));
497
-  client.print(F(",#,\"}]"));
496
+  client.print(F(",#"));
498 497
   return;
499 498
 }
500 499
 
Browse code

first release

Jeff Owrey authored on 09/25/2015 03:44:17
Showing 1 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,996 @@
1
+/*
2
+ Background Radiation Monitor - Web Server
3
+ 
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
7
+ runs on the Arduino Uno an embedded HTTP server by which Internet
8
+ applications can query the MightyOhm for Geiger counter readings.
9
+ Also, this software runs a Network Time Protocol (NTP) client, that
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
13
+ verbose output mode.
14
+ 
15
+ Copyright 2014 Jeff Owrey
16
+    This program is free software: you can redistribute it and/or modify
17
+    it under the terms of the GNU General Public License as published by
18
+    the Free Software Foundation, either version 3 of the License, or
19
+    (at your option) any later version.
20
+
21
+    This program is distributed in the hope that it will be useful,
22
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
23
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
24
+    GNU General Public License for more details.
25
+
26
+    You should have received a copy of the GNU General Public License
27
+    along with this program.  If not, see http://www.gnu.org/license.
28
+ 
29
+ Circuit:
30
+ * Main components: Arduino Uno, Ethernet shield, Mighty Ohm Geiger counter
31
+ * Ethernet shield attached to pins 10, 11, 12, 13
32
+ * In order to allow the MightyOhm to operate on the Uno's 5 volt power
33
+   supply, and thus make the MightyOhm's serial output compatible with the
34
+   Uno, the following has to be done (see MightyOhm schematic):
35
+     1. Change R6 to 1K Ohm.
36
+     2. Change R11 to 330 Ohm.
37
+     3. Connect +5v from the Uno to MightyOhm J6 pin 1.
38
+     4. Connect GND from the Uno to MightyOhm J6 pin 3.
39
+     5. Connect D5 from the Uno to MightyOhm J7 pin 5.
40
+   
41
+ Misc Notes:
42
+   As of this release the Uno's SRAM gets entirely maxed out by
43
+   this program.  Any modifications to this program that requires
44
+   additional memory seriously entails the risk that the modifications
45
+   will cause the program to become un-stable.
46
+   
47
+ Revision History:  
48
+   * v10 released 25 Feb 2014 by J L Owrey
49
+   * v11 released 24 Jun 2014 by J L Owrey
50
+       - optimization of processRxByte function to conserve SRAM
51
+       - removal of non-used function code
52
+       - defaults to APIPA IP address in the event a DHCP address
53
+         cannot be obtained
54
+   * v12 released 20 Dec 2014 by J L Owrey
55
+       - removed Timestamp global variable to make more dynamic
56
+         memory available for local variables
57
+       - optimized clock network synch algorithm
58
+       - optimized serial update algorithm
59
+   * v13 released 22 Jul 2015 by J L Owrey
60
+       - add use of "F" function to store constant strings in
61
+         program flash memory in order to save SRAM space
62
+   * v14 released 19 Aug 2015 by J L Owrey
63
+       - add ability to respond to web a client request with either
64
+         a JSON compatible string or a standard HTML document
65
+*/
66
+
67
+/***  PREPROCESSOR DEFINES  ***/
68
+
69
+/*
70
+ Define the header and version number displayed at startup
71
+ and also by the 'view settings' command.
72
+*/
73
+#define STARTUP_HEADER "\n\rRadmon v1.4 (c) 2015"
74
+#define RADMON_VERSION "v1.4"
75
+/*
76
+ The following define sets the MAC address of the device.  This
77
+ address is a permanent attribute of the device's Ethernet interface,
78
+ and never, ever, should be changed.  This address was provided
79
+ by the Arduino Ethernet shield manufacturer for use with this
80
+ specific instance of the Ethernet shield.  This MAC address should
81
+ be shown on a label affixed to the device housing.
82
+*/
83
+#define ETHERNET_MAC_ADDRESS 0x90, 0xA2, 0xDA, 0x0D, 0x84, 0xF6
84
+/*
85
+ The following defines an APIPA default address in the event that
86
+ DHCP mode is ON and a DHCP address cannot be obtained.
87
+*/
88
+#define DEFAULT_APIPA_IP_ADDRESS "169.254.100.10"
89
+/*
90
+ The following defines set the period of a 'heartbeat' string sent
91
+ out over the device's USB port.  This heartbeat consists of a serial
92
+ data string containing the current radiation reading and GM time.
93
+*/
94
+#define SERIAL_UPDATE_INTERVAL 30000  //milli-seconds
95
+#define VERBOSE_SERIAL_UPDATE_INTERVAL 10000  //milli-seconds
96
+/*
97
+ The following define sets the port number the HTTP service will use to
98
+ listen for requests from Internet clients.  Normally HTTP requests use
99
+ port 80.
100
+*/
101
+#define HTTP_SERVER_PORT 80
102
+/*
103
+ The following defines are for configuring a local NTP client
104
+ for synchronizing the local system clock to network time.
105
+ Note that the default setting is the IP address of the following
106
+ time server:
107
+              time-a.timefreq.bldrdoc.gov
108
+*/
109
+#define DEFAULT_NTP_SERVER_IP_ADDR "132.163.4.101"
110
+#define NTP_PORT 8888
111
+#define NTP_PACKET_SIZE 48 // NTP time stamp is in the first 48 bytes of the message
112
+/*
113
+ The following defines how often the system clock gets synchronized
114
+ to network time.
115
+*/
116
+#define NET_SYNCH_INTERVAL 43200 //number in seconds
117
+/*
118
+ The following defines the size of the buffer space required for the
119
+ serial data string from the Mighty Ohm Geiger counter.  The serial
120
+ data string is defined as the text from newline character to newline
121
+ character.
122
+*/
123
+#define MIGHTYOHM_DATA_STRING_LENGTH 65
124
+/*
125
+ The beginning of the MightyOhm data string always begins with the
126
+ same three characters.  These three characters determine the 
127
+ beginning of a new line of data from the MightyOhm.
128
+*/
129
+#define MIGHTYOHM_DATA_STRING_HEADER "CPS"
130
+
131
+/***  LIBRARY MODULES USED  ***/
132
+
133
+#include <Time.h>
134
+#include <SPI.h>         
135
+#include <Ethernet.h>
136
+#include <EthernetUdp.h>
137
+#include <SoftwareSerial.h>
138
+#include <EEPROM.h>;
139
+
140
+/***  GLOBAL DECLARATIONS ***/
141
+
142
+/*
143
+ Create and initialize a mac address object for the Ethernet interface. 
144
+*/
145
+byte mac[] = { ETHERNET_MAC_ADDRESS };
146
+/*
147
+ Create and initialize an HTTP server object.  The object is initialized
148
+ to the TCP port the HTTP server will use to listen for clients.
149
+*/
150
+EthernetServer httpServer(HTTP_SERVER_PORT);
151
+/*
152
+ Create a UDP client object for sending packets to
153
+ and receiveing packets from an NTP time server.
154
+*/
155
+EthernetUDP Udp;
156
+/*
157
+ Create a software serial port for receiving serial data from
158
+ the MightyOhm. Note that the Uno pin 5 receives serial data
159
+ FROM the MightyOhm.  The Uno's pin 6 is not used, as there is
160
+ no need to send serial data to the MightyOhm. 
161
+*/
162
+SoftwareSerial MightyOhmTxOut(5, 6);
163
+/*
164
+ Create global variables to store the MightOhm data, next heartbeat
165
+ time, and next synchronization time.
166
+*/
167
+char MightyOhmData[MIGHTYOHM_DATA_STRING_LENGTH + 1];
168
+unsigned long SerialUpdateTime;
169
+time_t NextClockSynchTime;
170
+/*
171
+ Create global variables to store the verbose mode state (ON or OFF)
172
+ and the IP address mode state (static or DHCP).
173
+*/
174
+boolean bVerbose;
175
+boolean bUseDHCP;
176
+/*
177
+ Create and initialize global arrays to hold the current IP address
178
+ and the NTP server IP address.
179
+*/
180
+byte ipAddr[4] = {};
181
+byte ntpip[4] = {};
182
+
183
+/*** SYSTEM STARTUP  ***/
184
+
185
+void setup()
186
+{
187
+  /*
188
+   Open serial communications to and from the Uno's USB port.
189
+  */
190
+  Serial.begin(9600);
191
+  /* 
192
+   Print to the USB port a header showing Radmon
193
+   version of this program and the copyright notice.
194
+  */
195
+  Serial.println(F(STARTUP_HEADER));
196
+  /*
197
+    Get the system configuration from EEPROM.
198
+  */
199
+  readSettingsFromEEPROM();
200
+  /*
201
+   Start up the Ethernet interface using either a static or
202
+   DHCP supplied address (depending on stored system configuration).
203
+  */
204
+  Serial.println(F("Configuring network interface..."));
205
+  if(bUseDHCP)
206
+  {
207
+    if ( Ethernet.begin(mac) == 0 )
208
+    {
209
+      /* DHCP not responding so use APIPA address */
210
+      parseIpAddress(ipAddr, DEFAULT_APIPA_IP_ADDRESS);
211
+      Ethernet.begin(mac, ipAddr);
212
+      Serial.println(F("DHCP failed - using APIPA"));
213
+    }
214
+    else
215
+    {
216
+      Serial.print(F("DHCP supplied IP: "));
217
+      Serial.println(Ethernet.localIP());
218
+    }
219
+  }
220
+  else
221
+  {
222
+    Ethernet.begin(mac, ipAddr);
223
+  }
224
+  /*
225
+   Start up NTP client service.
226
+  */
227
+  Udp.begin(NTP_PORT);
228
+  /*
229
+    Synchronize the system clock to network time.
230
+  */
231
+  synchronizeSystemClock();
232
+  /*
233
+   Start up the HTTP server.
234
+  */
235
+  Serial.println(F("Starting HTTP server..."));
236
+  httpServer.begin();
237
+  /*
238
+    Open serial communications to the MightyOhm device.
239
+  */  
240
+  MightyOhmTxOut.begin(9600);
241
+   /*
242
+    Initialize initial time for sending out the hearbeat string. Normally
243
+    the system clock will be at approx 3200 msec at this point. So allow
244
+    some additional time for data to accumulate in MightyOhm data buffer.
245
+  */
246
+  SerialUpdateTime = millis() + 2000;
247
+  /*
248
+   Initialize MightyOhm data string to empty.
249
+  */
250
+  MightyOhmData[0] = 0;
251
+  return;
252
+}
253
+
254
+/*** MAIN LOOP ***/
255
+
256
+void loop()
257
+{
258
+  /*
259
+   Check for user keyboard 'c' pressed.  This character switches
260
+   to command mode.
261
+  */   
262
+  if ( Serial.available() )
263
+  {
264
+    // get incoming byte
265
+    if(Serial.read() == 'c')
266
+    {
267
+      commandMode();
268
+    }
269
+  }  
270
+  /*
271
+    Poll serial input buffer from MightyOhm for new data and 
272
+    process received bytes to form a complete data string.
273
+  */
274
+  while ( MightyOhmTxOut.available() )
275
+  {
276
+    processRxByte( MightyOhmTxOut.read() );
277
+  }
278
+  /*
279
+    Every so often send a 'heartbeat' string to the USB port.
280
+    The heartbeat consists of UTC time stamp and current data
281
+    from the MightyOhm Geiger counter.
282
+  */
283
+  if ( millis() > SerialUpdateTime )
284
+  {
285
+    Serial.println( MightyOhmData );
286
+    /* Set the time for the next heartbeat pulse to go out. */
287
+    SerialUpdateTime = millis() +  \
288
+                       (bVerbose ? VERBOSE_SERIAL_UPDATE_INTERVAL :  \ 
289
+                                    SERIAL_UPDATE_INTERVAL);
290
+  }
291
+  /*
292
+   Listen for and and process requests from HTTP clients.
293
+  */  
294
+  listenForEthernetClients();
295
+  /*
296
+   Periodically synchronize local system clock to time
297
+   provided by NTP time server.
298
+  */
299
+  if ( now() > NextClockSynchTime )
300
+  {
301
+    synchronizeSystemClock();
302
+  }
303
+  return;
304
+}
305
+
306
+/*
307
+ Synchronize the local system clock to
308
+ network time provided by NTP time server.
309
+*/
310
+void synchronizeSystemClock()
311
+{
312
+  byte count;
313
+  
314
+  Serial.println(F("Synchronizing with network time server..."));
315
+    
316
+  for(count = 0; count < 3; count++)  // Attempt to synchronize 3 times
317
+  {
318
+    if(syncToNetworkTime() == 1)
319
+    {
320
+      //  Synchronization successful
321
+      break;
322
+    }
323
+    delay(1000);
324
+  } /* end for */ 
325
+  if(count == 3) {
326
+    Serial.println(F("synch failed"));
327
+  }
328
+  /* 
329
+   Set the time for the next network NTP
330
+   time synchronization to occur.
331
+  */
332
+  NextClockSynchTime = now() + NET_SYNCH_INTERVAL;
333
+  return;
334
+}
335
+
336
+/*
337
+  Handle HTTP GET requests from an HTTP client.
338
+*/  
339
+void listenForEthernetClients()
340
+{
341
+  char sBuffer[14];
342
+  byte i;
343
+  byte iMode;
344
+  char c, d;
345
+  
346
+  // listen for incoming clients
347
+  EthernetClient client = httpServer.available();
348
+  if (client) {
349
+    if (bVerbose) Serial.print("\n");
350
+    Serial.println(F("client request"));
351
+    i = 0;
352
+    sBuffer[0] = 0;
353
+    iMode = 0;
354
+    d = 0;
355
+  
356
+    while(client.connected() && client.available()) {
357
+      c = client.read();
358
+      /*
359
+       * The end of an HTTP client request is always signaled by a
360
+       * blank line, that is, by two consecutive line feed and carriage 
361
+       * return characters "\r\n\r\n".  The following two lines of code 
362
+       * look for this condition.
363
+       */
364
+      if(c == '\n' && d == '\n') break; //end of HTTP client request
365
+      if (c != '\r') d = c; // ignore carriage return characters
366
+
367
+      if (bVerbose) {
368
+        Serial.write(c);
369
+      }
370
+      // prevent buffer overruns
371
+      if (i < 13) {
372
+        sBuffer[i++] = c;
373
+        sBuffer[i] = 0;
374
+      }
375
+      // request for JSON string response
376
+      if(strcmp(sBuffer, "GET /jsdata ") == 0) {
377
+        iMode = 1;
378
+      }
379
+      // request for standard HTML document
380
+      if(strcmp(sBuffer, "GET / ") == 0) {
381
+        iMode = 2;
382
+      }
383
+    }
384
+    /*
385
+     Send a standard HTTP response header to the
386
+     client's GET request.
387
+    */
388
+    client.println(F("HTTP/1.1 200 OK\n"        \
389
+                   "Content-Type: text/html\n"  \
390
+                   "Connnection: close\n"       \
391
+               //  "Refresh: 10\n"              \
392
+                   "\n"                         \
393
+                   ));
394
+                   
395
+    switch (iMode) {
396
+      case 0:
397
+        // Respond to an invalid URL received from the client
398
+        client.print(F("<!DOCTYPE HTML>\n" \
399
+                       "<HTML><HEAD><TITLE>Radmon</TITLE></HEAD>"  \
400
+                       "<BODY><H2>Invalid URL</H2>"  \
401
+                       "<P> You have reached a server at an unknown " \
402
+                       "URL.  If you think you made this request in error " \
403
+                       "please disconnect and try your request again.</P>" \
404
+                       "</BODY></HTML>"));
405
+        break;
406
+      case 1:
407
+        transmitJson(client);
408
+        break;
409
+      case 2:
410
+        transmitWebPage(client);
411
+        break;
412
+    }
413
+    // give the web browser time to receive the data
414
+    delay(5);
415
+    // close the connection:
416
+    client.stop();
417
+    if (bVerbose) {
418
+      Serial.print(F("client disconnected\r\n\n"));
419
+    }
420
+  }
421
+  return;
422
+}
423
+
424
+/*
425
+ Send to the client the MightyOhm Geiger counter's
426
+ current readings, embedded in an HTML document.
427
+*/
428
+void transmitWebPage(EthernetClient client)
429
+{
430
+  char strBuffer[MIGHTYOHM_DATA_STRING_LENGTH];
431
+
432
+  strcpy(strBuffer, MightyOhmData);
433
+  /*
434
+   * Send the actual HTML page the user will see in their web
435
+   * browser.
436
+  */
437
+  client.print(F("<!DOCTYPE HTML>\n" \
438
+                 "<HTML><HEAD><TITLE>Radmon</TITLE>"  \
439
+                 "</HEAD><BODY><H2>Radiation Monitor</H2>" \
440
+                 "<P><A HREF=\"http://intravisions.com/radmon/\">" \
441
+                 "<I>IntraVisions.com/radmon</I></A></P>" \
442
+                 "<HR><FONT SIZE=\"+1\">"));
443
+  /* Data Items */             
444
+  client.print(F("<PRE>UTC&#9;"));
445
+  client.print(strtok(strBuffer, " "));
446
+  client.print(F(" "));
447
+  client.print(strtok(NULL, ", "));
448
+  client.print(F("<BR>"));
449
+  client.print(strtok(NULL, ", "));
450
+  client.print(F("&#9;"));
451
+  client.print(strtok(NULL, ", "));
452
+  client.print(F("<BR>"));
453
+  client.print(strtok(NULL, ", "));
454
+  client.print(F("&#9;"));
455
+  client.print(strtok(NULL, ", "));
456
+  client.print(F("<BR>"));
457
+  client.print(strtok(NULL, ", "));
458
+  client.print(F("&#9;"));
459
+  client.print(strtok(NULL, ", "));
460
+  client.print(F("<BR>"));
461
+  client.print(F("Mode&#9;"));
462
+  client.print(strtok(NULL, ", "));
463
+  client.print(F("<BR></PRE></FONT></BODY></HTML>"));
464
+  return;
465
+}  
466
+
467
+/*
468
+ Send to the client the MightyOhm Geiger counter's
469
+ current readings, embedded in a JSON compatible string.
470
+*/
471
+void transmitJson(EthernetClient client) {
472
+  char strBuffer[MIGHTYOHM_DATA_STRING_LENGTH];
473
+
474
+  strcpy(strBuffer, MightyOhmData);
475
+  /*
476
+   * Format and transmit a JSON compatible data string.
477
+   */
478
+  client.print(F("[{\"radmon\":\"$,UTC="));
479
+  client.print(strtok(strBuffer, " "));
480
+  client.print(F(" "));
481
+  client.print(strtok(NULL, ", "));
482
+  client.print(F(","));
483
+  client.print(strtok(NULL, ", "));
484
+  client.print(F("="));
485
+  client.print(strtok(NULL, ", "));
486
+  client.print(F(","));
487
+  client.print(strtok(NULL, ", "));
488
+  client.print(F("="));
489
+  client.print(strtok(NULL, ", "));
490
+  client.print(F(","));
491
+  client.print(strtok(NULL, ", "));
492
+  client.print(F("="));
493
+  client.print(strtok(NULL, ", "));
494
+  client.print(F(","));
495
+  client.print(F("Mode="));
496
+  client.print(strtok(NULL, ", "));
497
+  client.print(F(",#,\"}]"));
498
+  return;
499
+}
500
+
501
+/*
502
+   Process bytes received from the MightyOhm Geiger counter,
503
+   one at a time, to create a well formed string.
504
+*/
505
+void processRxByte( char RxByte )
506
+{
507
+  static char readBuffer[MIGHTYOHM_DATA_STRING_LENGTH];
508
+  static byte cIndex = 0;
509
+  
510
+  /*
511
+     Discard carriage return characters.
512
+  */
513
+  if (RxByte == '\r')
514
+  {
515
+    return;
516
+  }
517
+  /*
518
+   A new line character indicates the line of data from
519
+   the MightyOhm is complete and can be written to the
520
+   MightyOhm data buffer.
521
+  */
522
+  else if (RxByte == '\n')
523
+  {
524
+    /*
525
+     First copy the timestamp to the MightyOhm data buffer. The "CPS"
526
+     characters are not preserved in the temporary read buffer, so
527
+     restore them to the MightyOhm data buffer, as well.
528
+    */
529
+    sprintf( MightyOhmData, "%d:%02d:%02d %d/%d/%d, %s",       \
530
+          hour(), minute(), second(), month(), day(), year(),  \
531
+          MIGHTYOHM_DATA_STRING_HEADER );
532
+    /*
533
+     Now copy the rest of the data in the temporary read buffer to the
534
+     MightyOhm data buffer.
535
+    */ 
536
+    strcat(MightyOhmData, readBuffer);
537
+    /*
538
+     Flush the temporary read buffer.
539
+    */
540
+    cIndex = 0;
541
+    readBuffer[0] = 0;
542
+    return;
543
+  }
544
+  /* 
545
+   A new line of data will always have "CPS" as the first
546
+   three characters.  Therefore, when these characters occur in
547
+   sequence, the read buffer should begin collecting characters.
548
+   This is a kluge to deal with an inherent problem in the Software
549
+   Serial library implementation that results in characters dropped
550
+   from the software serial stream buffer.
551
+  */
552
+  if( strstr(readBuffer, MIGHTYOHM_DATA_STRING_HEADER) > 0 ) 
553
+  {
554
+    cIndex = 0;
555
+  }
556
+  /*
557
+   Read characters into a temporary buffer until
558
+   the line of data is complete or the buffer is full.
559
+  */
560
+  if(cIndex < MIGHTYOHM_DATA_STRING_LENGTH)
561
+  {
562
+    readBuffer[cIndex] = RxByte;
563
+    cIndex += 1;
564
+    readBuffer[cIndex] = 0;
565
+  }
566
+  return;
567
+} 
568
+
569
+/* 
570
+  Send a UDP request packet to an NTP time server and listen for a reply.
571
+  When the reply arrives, parse the received UPD packet and compute unix
572
+  epoch time.  Then set the local system clock to the epoch time.
573
+*/
574
+int syncToNetworkTime()
575
+{
576
+  /*
577
+   Send a request to the NTP time server.
578
+  */
579
+  byte packetBuffer[ NTP_PACKET_SIZE]; //buffer to hold outgoing and incoming packets 
580
+  /*
581
+   Send an NTP packet to the time server and allow for network lag
582
+   before checking if a reply is available.
583
+  */
584
+  sendNTPpacket(packetBuffer);
585
+  delay(2000);  // allow 2000 milli-seconds for network lag
586
+
587
+  /*
588
+   Wait for response from NTP time server.
589
+  */
590
+  if ( Udp.parsePacket() )
591
+  {  
592
+    /*
593
+     A UDP packet has arrived, so read the data from it.
594
+    */
595
+    Udp.read( packetBuffer, NTP_PACKET_SIZE );
596
+    /*
597
+     The timestamp starts at byte 40 of the received packet and is four bytes,
598
+     or two words, long. First, esxtract the two words.
599
+    */
600
+    unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);
601
+    unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);
602
+    /*  
603
+     Combine the four bytes (two words) into a long integer
604
+     this is NTP time (seconds since Jan 1 1900).
605
+    */
606
+    unsigned long secsSince1900 = highWord << 16 | lowWord;
607
+    /*  
608
+     Now convert NTP time into UTC time.  Note that
609
+     Unix time starts on Jan 1 1970. In seconds,
610
+     that's 2208988800.  Therfore,
611
+     
612
+         epoch = secsSince1900 - 2208988800UL
613
+         
614
+     Set the local system clock with this value.
615
+    */
616
+    setTime(secsSince1900 - 2208988800UL);
617
+    return 1;
618
+  }
619
+  else
620
+  {
621
+    return 0;
622
+  } /* end if */
623
+}
624
+
625
+/*
626
+  Send an NTP request to the NTP time server.
627
+*/
628
+void sendNTPpacket( byte* packetBuffer )
629
+{
630
+  /*
631
+   Set all bytes in the buffer to 0.
632
+  */
633
+  memset( packetBuffer, 0, NTP_PACKET_SIZE );
634
+  /*
635
+   Initialize values needed to form NTP request.
636
+  */
637
+  packetBuffer[0] = 0b11100011;  // LI, Version, Mode
638
+  packetBuffer[1] = 0;           // Stratum, or type of clock
639
+  packetBuffer[2] = 6;           // Polling Interval
640
+  packetBuffer[3] = 0xEC;        // Peer Clock Precision
641
+  /*
642
+   Set the remaining 8 bytes to zero for Root Delay & Root Dispersion.
643
+  */
644
+  packetBuffer[12]  = 49; 
645
+  packetBuffer[13]  = 0x4E;
646
+  packetBuffer[14]  = 49;
647
+  packetBuffer[15]  = 52;
648
+  /*
649
+   All NTP fields have been given values, so now
650
+   send a packet requesting a timestamp.
651
+  */ 		
652
+  Udp.beginPacket( ntpip, 123 ); //NTP requests are to port 123
653
+  Udp.write( packetBuffer, NTP_PACKET_SIZE );
654
+  Udp.endPacket();
655
+  return;
656
+}
657
+
658
+/***  COMMAND LINE INTERFACE  ***/
659
+
660
+/*
661
+ Print a command menu to the USB port.  Then wait for a
662
+ response from the user.  When the response has been
663
+ received, execute the command.
664
+*/
665
+void commandMode()
666
+{
667
+  char sCmdBuf[2];
668
+  
669
+  getCurrentIP();  //used for display of settings
670
+  
671
+  while(true)
672
+  {
673
+    /*
674
+     Print the menu.
675
+    */
676
+    Serial.print( F("\n"                         \
677
+                  "1 - view settings\r\n"        \
678
+                  "2 - set IP address\r\n"       \
679
+                  "3 - set NTP server\r\n"       \
680
+                  "4 - toggle verbose\r\n"       \
681
+                  "5 - exit without saving\r\n"  \
682
+                  "6 - save & restart\r\n"       \
683
+                  ">"));
684
+    /*
685
+     Get the command from the user.
686
+    */
687
+    getSerialLine(sCmdBuf, 2);
688
+    Serial.print(F("\n\n\r"));
689
+    /* 
690
+     Execute the command.
691
+    */
692
+    switch (sCmdBuf[0])
693
+    {
694
+      case '1':
695
+        displaySettings();
696
+        break;
697
+      case '2':
698
+        setIP();
699
+        break;
700
+      case '3':
701
+        setNTPIP();
702
+        break;
703
+      case '4':
704
+        toggleVerbose();
705
+        break;
706
+      case '5':
707
+        readSettingsFromEEPROM();
708
+        return;
709
+      case '6':
710
+        writeSettingsToEEPROM();
711
+        /*
712
+         A software reboot is necessary to force the 
713
+         Arduino to request an IP address from a DHCP
714
+         server or to initialize the Ethernet interface
715
+         with a static IP address.
716
+        */
717
+        software_Reset();
718
+        return;
719
+      default:
720
+        Serial.println(F("invalid command"));
721
+    } /* end switch */
722
+  } /* end while */
723
+  return;
724
+}
725
+
726
+/*
727
+ Displays the current system settings.  Displays
728
+ RadMon software version, local IP address, NTP server
729
+ address, and verbose mode setting.
730
+*/
731
+void displaySettings()
732
+{
733
+  char sBuf[16];
734
+  
735
+  // Display RadMon version
736
+  Serial.print(F("Firmware "));
737
+  Serial.print(F(RADMON_VERSION));
738
+  Serial.println();
739
+
740
+  // Display local IP address
741
+  sprintf(sBuf, "%d.%d.%d.%d", ipAddr[0], ipAddr[1], ipAddr[2], ipAddr[3]);
742
+  if (bUseDHCP)
743
+  {
744
+    Serial.print(F("DHCP IP: "));
745
+  }
746
+  else
747
+  {
748
+    Serial.print(F("Static IP: "));
749
+  }
750
+  Serial.println(sBuf);
751
+  
752
+  // Display NTP server IP address
753
+  sprintf(sBuf, "%d.%d.%d.%d", ntpip[0], ntpip[1], ntpip[2], ntpip[3]);
754
+  Serial.print(F("NTP server: ")); 
755
+  Serial.println(sBuf);
756
+
757
+  // Display verbose mode setting
758
+  printVerboseMode();
759
+  return;
760
+}
761
+
762
+/*
763
+ Sets the local IP address. If the user sends a carriage
764
+ return as the first character, then switch to acquiring
765
+ IP address via DHCP server.
766
+*/
767
+void setIP()
768
+{
769
+  char sBuf[16];
770
+
771
+  Serial.print(F("enter IP (<CR> for DHCP): "));
772
+  getSerialLine(sBuf, 16);
773
+  
774
+  if(strlen(sBuf) == 0)
775
+  {
776
+    bUseDHCP = true;
777
+    parseIpAddress(ipAddr, "0.0.0.0");
778
+  }
779
+  else
780
+  {
781
+    bUseDHCP = false;
782
+    parseIpAddress(ipAddr, sBuf);
783
+  }
784
+  Serial.println();
785
+  return;
786
+}
787
+
788
+/*
789
+ Sets the NTP server IP address.  If the user sends a
790
+ carriage return as the first character, then use the
791
+ default IP address for the NTP server.
792
+*/
793
+void setNTPIP()
794
+{
795
+  char sBuf[16];
796
+  
797
+  Serial.print(F("enter IP: "));
798
+  getSerialLine(sBuf, 16);
799
+  
800
+  if (strlen(sBuf) == 0)
801
+  {
802
+    parseIpAddress(ntpip, DEFAULT_NTP_SERVER_IP_ADDR);
803
+  }
804
+  else
805
+  {
806
+    parseIpAddress(ntpip, sBuf);
807
+  }
808
+  Serial.println();
809
+  return;
810
+}
811
+
812
+/*
813
+ Turns verbose mode ON or OFF.
814
+*/
815
+void toggleVerbose()
816
+{
817
+  bVerbose = !bVerbose;
818
+  printVerboseMode();
819
+  return;
820
+}
821
+
822
+/***  GENERAL HELPER FUNCTIONS  ***/
823
+
824
+/*
825
+ Print current verbose mode.
826
+*/
827
+void printVerboseMode()
828
+{
829
+  Serial.print(F("Verbose: "));
830
+  if (bVerbose)
831
+  {
832
+    Serial.println(F("ON"));
833
+  }
834
+  else
835
+  {
836
+    Serial.println(F("OFF"));
837
+  }
838
+  return;
839
+}
840
+
841
+/*
842
+ Get the current IP address from the Ethernet interface
843
+*/
844
+void getCurrentIP()
845
+{
846
+  ipAddr[0] = Ethernet.localIP()[0];
847
+  ipAddr[1] = Ethernet.localIP()[1];
848
+  ipAddr[2] = Ethernet.localIP()[2];
849
+  ipAddr[3] = Ethernet.localIP()[3];
850
+  return;
851
+}
852
+
853
+/*
854
+ Gets a line of data from the user via USB port.
855
+*/
856
+char* getSerialLine(char* sBuffer, int bufferLength)
857
+{
858
+  byte index;
859
+  char cRx;
860
+
861
+  /* 
862
+   Discard extranious characters that may still be in the
863
+   USB serial stream read buffer.  Most often these characters
864
+   will be unprocessed carriage return or line feed characters.
865
+  */
866
+  delay(10);
867
+  while (Serial.available())
868
+  {
869
+    cRx = Serial.read();
870
+  }
871
+
872
+  /*
873
+   Read and process characters from the user as they arrive in
874
+   the USB serial read buffer.
875
+  */
876
+  index = 0;
877
+  while(true)
878
+  {
879
+    /*
880
+     Wait until the user starts pressing keys and bytes
881
+     arrive in the serial read buffer.
882
+    */
883
+    if (Serial.available())
884
+    {
885
+      cRx = Serial.read();
886
+      if (cRx == '\r' || cRx == '\n')
887
+      {
888
+        /*
889
+         The user has finished typing the command and
890
+         has pressed the Enter key. So, discard the
891
+         carriage return and newline characters and then
892
+         return control to the calling function.
893
+        */
894
+        break;
895
+      }
896
+      else if (cRx == 8 || cRx == 127)
897
+      {
898
+        if (index > 0)
899
+        {
900
+          /*
901
+           The user has hit the delete-backspace key,
902
+           so send out a backspace, followed by a space,
903
+           followed by another backspace character.
904
+           This allows for in-line ediiting.
905
+          */
906
+          Serial.write(8);
907
+          Serial.write(32);
908
+          Serial.write(8); 
909
+          index -= 1;
910
+        }
911
+      }
912
+      else if ( index < (bufferLength - 1) )
913
+      {
914
+        /*
915
+         The received character is valid, so write it
916
+         to the buffer. Once the buffer becomes full
917
+         do not write any more characters to it.  When
918
+         the user pressses the enter key, the string
919
+         will be null terminated and control will pass
920
+         back to the calling function.
921
+        */
922
+        Serial.write(cRx); // echo character to terminal
923
+        sBuffer[index] = cRx;
924
+        index += 1;
925
+      } /* end if */
926
+    } /* end if */
927
+  } /* end while */
928
+  sBuffer[index] = 0; // terminate the string
929
+  return sBuffer;
930
+}
931
+
932
+/*
933
+ Writes system configuration settings to non-volitile
934
+ EEPROM.  The items written are the local IP address,
935
+ the NTP server IP address, the state of verbose mode,
936
+ and local IP mode (static or DHCP).
937
+*/
938
+void writeSettingsToEEPROM()
939
+{
940
+  byte ix;
941
+  for (ix = 0; ix < 4; ix++)
942
+  {
943
+    EEPROM.write(ix, ipAddr[ix]);
944
+    EEPROM.write(ix + 4, ntpip[ix]);
945
+  }
946
+  EEPROM.write(8, bVerbose);
947
+  EEPROM.write(9, bUseDHCP);
948
+  return;
949
+}
950
+
951
+/*
952
+ Reads system configuration settings from non-volitile
953
+ EEPROM.  The items read are the local IP address,
954
+ the NTP server IP address, the state of verbose mode,
955
+ and local IP mode (static or DHCP).
956
+*/
957
+void readSettingsFromEEPROM()
958
+{
959
+  byte ix;
960
+  for (ix = 0; ix < 4; ix++)
961
+  {
962
+    ipAddr[ix] = EEPROM.read(ix);
963
+    ntpip[ix] = EEPROM.read(ix + 4);
964
+  }
965
+  bVerbose = EEPROM.read(8);
966
+  bUseDHCP = EEPROM.read(9);
967
+  return;
968
+}
969
+
970
+/*
971
+ Parses an IP address given in "nnn.nnn.nnn.nnn" string
972
+ format into four bytes and stores them in an array. Note
973
+ that this function destroys the contents of the sIP
974
+ character array.  Therefore this array cannot be
975
+ reinitialized after calling this function.
976
+*/
977
+void parseIpAddress(byte* byBuf, char* sIP)
978
+{
979
+  byBuf[0] = atoi(strtok(sIP, "."));
980
+  byBuf[1] = atoi(strtok(NULL, "."));
981
+  byBuf[2] = atoi(strtok(NULL, "."));
982
+  byBuf[3] = atoi(strtok(NULL, "."));
983
+  return;
984
+}
985
+
986
+/*
987
+ Restarts the Uno and runs this program from beginning.  This
988
+ function gets called after a change made from the user
989
+ interface when the user selects "save and restart".
990
+*/
991
+void software_Reset() 
992
+{
993
+  asm volatile ("  jmp 0");
994
+  return; 
995
+}  
996
+