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
+