Browse code

minor restructering

gandolf authored on 07/05/2022 18:42:58
Showing 2 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,1049 @@
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 2018 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) 2018\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 5000  //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 43200 //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 bUseStaticIP;
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(bUseStaticIP)
219
+  {
220
+    Ethernet.begin(mac, ipAddr);
221
+  } else {
222
+    if ( Ethernet.begin(mac) == 0 )
223
+    {
224
+      /* DHCP not responding so use APIPA address */
225
+      parseIpAddress(ipAddr, DEFAULT_APIPA_IP_ADDRESS);
226
+      Ethernet.begin(mac, ipAddr);
227
+      Serial.println(F("DHCP failed - using APIPA "));
228
+    }
229
+  }
230
+  Serial.print(F("IP address: ")); Serial.println(Ethernet.localIP());
231
+  /*
232
+   Start up NTP client service.
233
+  */
234
+  Udp.begin(NTP_PORT);
235
+  /*
236
+    Synchronize the system clock to network time.
237
+  */
238
+  synchronizeSystemClock();
239
+  /*
240
+   Start up the HTTP server.
241
+  */
242
+  Serial.println(F("Starting http server..."));
243
+  httpServer.begin();
244
+  /*
245
+    Open serial communications to the MightyOhm device.
246
+  */  
247
+  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
+  /*
255
+   Initialize MightyOhm data string to empty.
256
+  */
257
+  mightOhmData[0] = 0;
258
+  return;
259
+}
260
+
261
+/*** MAIN LOOP ***/
262
+
263
+void loop() {
264
+  long currentTime;
265
+
266
+  currentTime = millis();
267
+  
268
+  /*
269
+   Check for user keyboard 'c' pressed.  This character switches
270
+   to command mode.
271
+  */   
272
+  if ( Serial.available() ) {
273
+    // get incoming byte
274
+    if(Serial.read() == 'c') {
275
+      commandMode();
276
+    }
277
+  }
278
+  
279
+  /*
280
+    Poll serial input buffer from MightyOhm for new data and 
281
+    process received bytes to form a complete data string.
282
+  */
283
+  while ( MightyOhmTxOut.available() ) {
284
+    processRxByte( MightyOhmTxOut.read() );
285
+  }
286
+  
287
+  /*
288
+    In verbose mode, send the MightyOhm data string to the
289
+    serial port at regular intervals.
290
+  */
291
+  if (bVerbose) {
292
+    if (abs(millis() - lastSerialUpdateTime) > SERIAL_UPDATE_INTERVAL) {
293
+      lastSerialUpdateTime = millis();
294
+      Serial.println( mightOhmData );
295
+    }
296
+  }
297
+  
298
+  /*
299
+   Periodically synchronize local system clock to time
300
+   provided by NTP time server.
301
+  */
302
+  if ( now() > nextClockSynchTime ) {
303
+    synchronizeSystemClock();
304
+  }
305
+  
306
+  /*
307
+   Listen for and and process requests from HTTP clients.
308
+  */  
309
+  listenForEthernetClients();
310
+
311
+  #ifdef DEBUG
312
+    Serial.print("lp time: "); Serial.println(millis() - currentTime);
313
+  #endif
314
+}
315
+
316
+/*
317
+ Synchronize the local system clock to
318
+ network time provided by NTP time server.
319
+*/
320
+void synchronizeSystemClock()
321
+{
322
+  byte count;
323
+  
324
+  Serial.println(F("Synchronizing with network time server..."));
325
+    
326
+  for(count = 0; count < 3; count++)  // Attempt to synchronize 3 times
327
+  {
328
+    if(syncToNetworkTime() == 1)
329
+    {
330
+      //  Synchronization successful
331
+      break;
332
+    }
333
+    delay(1000);
334
+  } /* end for */ 
335
+  if(count == 3) {
336
+    Serial.println(F("synch failed"));
337
+  }
338
+  /* 
339
+   Set the time for the next network NTP
340
+   time synchronization to occur.
341
+  */
342
+  nextClockSynchTime = now() + NET_SYNCH_INTERVAL;
343
+  return;
344
+}
345
+
346
+/*
347
+  Handle HTTP GET requests from an HTTP client.
348
+*/  
349
+void listenForEthernetClients()
350
+{
351
+  // listen for incoming clients
352
+  EthernetClient client = httpServer.available();
353
+  if (client) {
354
+    char sBuf[REQUEST_STRING_BUFFER_LENGTH];
355
+    byte i;
356
+    char c, c_prev;
357
+    boolean processedCommand;
358
+    boolean firstLineFound;
359
+
360
+    Serial.println(F("\nclient request"));
361
+
362
+    i = 0;
363
+    c_prev = 0;
364
+    sBuf[0] = 0;
365
+    processedCommand = false;
366
+    firstLineFound = false;
367
+  
368
+    /*
369
+     * The beginning and end of an HTTP client request is always signaled
370
+     * by a blank line, that is, by two consecutive line feed and carriage 
371
+     * return characters "\r\n\r\n".  The following lines of code 
372
+     * look for this condition, as well as the url extension (following
373
+     * "GET").
374
+     */
375
+    
376
+    while (client.connected())  {
377
+      if (client.available()) {
378
+        c = client.read();
379
+
380
+        if (bVerbose) {
381
+          Serial.print(c);
382
+        }
383
+              
384
+        if (c == '\r') {
385
+          continue; // discard character
386
+        }  
387
+        else if (c == '\n') {
388
+          if (firstLineFound && c_prev == '\n') {
389
+             break;
390
+          }
391
+        } 
392
+        
393
+        if (!processedCommand) {
394
+          
395
+          if (c != '\n') {
396
+            if(i > REQUEST_STRING_BUFFER_LENGTH - 2) {
397
+              i = 0;
398
+              sBuf[0] = 0;
399
+            }
400
+            sBuf[i++] = c;
401
+            sBuf[i] = 0;
402
+          }
403
+
404
+          if (!firstLineFound && strstr(sBuf, "GET /") != NULL) {
405
+            firstLineFound = true;
406
+            strcpy(sBuf, "/");
407
+            i = 1;
408
+          }
409
+
410
+          if (firstLineFound && (c == '\n' || i > REQUEST_STRING_BUFFER_LENGTH - 2)) {
411
+            processedCommand = true;
412
+          }
413
+        }
414
+        c_prev = c;
415
+      } // end single character processing
416
+    } // end character processing loop
417
+
418
+    /*
419
+     Send a standard HTTP response header to the
420
+     client's GET request.
421
+    */
422
+    transmitHttpHeader(client);
423
+    
424
+    char * pStr = strtok(sBuf, " ");
425
+    if (pStr != NULL) {
426
+      if (strcmp(pStr, "/rdata") == 0)
427
+        transmitRawData(client);
428
+      else if (strcmp(pStr, "/") == 0)
429
+        transmitWebPage(client);
430
+      else if(strcmp(pStr, "/reset") == 0) {
431
+        client.print(F("ok"));
432
+        delay(10);
433
+        // close the connection and reboot:
434
+        client.stop();
435
+        software_Reset();
436
+      }
437
+      else 
438
+        transmitErrorPage(client);
439
+    }
440
+
441
+    //Serial.println(mightOhmData);
442
+    // give the web browser time to receive the data
443
+    delay(10);
444
+    // close the connection:
445
+    client.stop();
446
+  }
447
+}
448
+
449
+/*
450
+ Send standard http response header back to
451
+ requesting client,
452
+*/
453
+void transmitHttpHeader(EthernetClient client) {
454
+  client.print(F("HTTP/1.1 200 OK\r\n"          \
455
+                 "Content-Type: text/html\r\n"  \
456
+                 "Connnection: close\r\n"       \
457
+                 "Refresh: 5\r\n"               \
458
+                 "\r\n"                         \
459
+                 ));
460
+}
461
+
462
+/*
463
+ Send to the client the MightyOhm Geiger counter's
464
+ current readings, embedded in an HTML document.
465
+*/
466
+void transmitWebPage(EthernetClient client) {
467
+  char strBuffer[MIGHTYOHM_DATA_STRING_LENGTH];
468
+
469
+  strcpy(strBuffer, mightOhmData);
470
+  /*
471
+   * Send the actual HTML page the user will see in their web
472
+   * browser.
473
+  */
474
+  client.print(F("<!DOCTYPE HTML>" \
475
+                 "<html><head><title>Radiation Monitor</title>"  \
476
+                 "<style>pre {font: 16px arial, sans-serif;}" \
477
+                 "p {font: 16px arial, sans-serif;}"
478
+                 "h2 {font: 24px arial, sans-serif;}</style>" \
479
+                 "</head><body><h2>Radiation Monitor</h2>" \
480
+                 "<p><a href=\"http://intravisions.com/radmon/\">" \
481
+                 "<i>intravisions.com/radmon</i></a></p>" \
482
+                 "<hr>"));
483
+  /* Data Items */             
484
+  client.print(F("<pre>UTC &#9;"));
485
+  client.print(strtok(strBuffer, ","));
486
+  client.print(F("<br>"));
487
+  client.print(strtok(NULL, ", "));
488
+  client.print(F(" &#9;"));
489
+  client.print(strtok(NULL, ", "));
490
+  client.print(F("<br>"));
491
+  client.print(strtok(NULL, ", "));
492
+  client.print(F(" &#9;"));
493
+  client.print(strtok(NULL, ", "));
494
+  client.print(F("<br>"));
495
+  client.print(strtok(NULL, ", "));
496
+  client.print(F(" &#9;"));
497
+  client.print(strtok(NULL, ", "));
498
+  client.print(F("<br>"));
499
+  client.print(F("Mode &#9;"));
500
+  client.print(strtok(NULL, ", "));
501
+  client.print(F("<br></pre></body></html>"));
502
+}  
503
+
504
+/*
505
+ Send to the client the MightyOhm Geiger counter's
506
+ current readings, embedded in a JSON compatible string.
507
+*/
508
+void transmitRawData(EthernetClient client) {
509
+  char strBuffer[MIGHTYOHM_DATA_STRING_LENGTH];
510
+
511
+  strcpy(strBuffer, mightOhmData);
512
+  /*
513
+   * Format and transmit a JSON compatible data string.
514
+   */
515
+  client.print(F("$,UTC="));
516
+  client.print(strtok(strBuffer, " "));
517
+  client.print(F(" "));
518
+  client.print(strtok(NULL, ", "));
519
+  client.print(F(","));
520
+  client.print(strtok(NULL, ", "));
521
+  client.print(F("="));
522
+  client.print(strtok(NULL, ", "));
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(F("Mode="));
533
+  client.print(strtok(NULL, ", "));
534
+  client.print(F(",#\n"));
535
+}
536
+
537
+/*
538
+ * Send an error message web page back to the requesting
539
+ * client when the client provides an invalid url extension.
540
+ */
541
+void transmitErrorPage(EthernetClient client) {
542
+  client.print(F("<!DOCTYPE HTML>" \
543
+                 "<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>" \
548
+                 "</body></html>"
549
+                 ));
550
+}
551
+
552
+/*
553
+ Process bytes received from the MightyOhm Geiger counter,
554
+ one at a time, to create a well formed string.
555
+*/
556
+void processRxByte( char RxByte )
557
+{
558
+  static char readBuffer[MIGHTYOHM_DATA_STRING_LENGTH];
559
+  static byte cIndex = 0;
560
+  
561
+  /*
562
+     Discard carriage return characters.
563
+  */
564
+  if (RxByte == '\r')
565
+  {
566
+    return;
567
+  }
568
+  /*
569
+   A new line character indicates the line of data from
570
+   the MightyOhm is complete and can be written to the
571
+   MightyOhm data buffer.
572
+  */
573
+  else if (RxByte == '\n')
574
+  {
575
+    /*
576
+     First copy the timestamp to the MightyOhm data buffer. The "CPS"
577
+     characters are not preserved in the temporary read buffer, so
578
+     restore them to the MightyOhm data buffer, as well.
579
+    */
580
+    sprintf( mightOhmData, "%d:%02d:%02d %d/%d/%d, %s",       \
581
+          hour(), minute(), second(), month(), day(), year(),  \
582
+          MIGHTYOHM_DATA_STRING_HEADER );
583
+    /*
584
+     Now copy the rest of the data in the temporary read buffer to the
585
+     MightyOhm data buffer.
586
+    */ 
587
+    strcat(mightOhmData, readBuffer);
588
+    /*
589
+     Flush the temporary read buffer.
590
+    */
591
+    cIndex = 0;
592
+    readBuffer[0] = 0;
593
+    return;
594
+  }
595
+  /* 
596
+   A new line of data will always have "CPS" as the first
597
+   three characters.  Therefore, when these characters occur in
598
+   sequence, the read buffer should begin collecting characters.
599
+   This is a kluge to deal with an inherent problem in the Software
600
+   Serial library implementation that results in characters dropped
601
+   from the software serial stream buffer.
602
+  */
603
+  if( strstr(readBuffer, MIGHTYOHM_DATA_STRING_HEADER) > 0 ) 
604
+  {
605
+    cIndex = 0;
606
+  }
607
+  /*
608
+   Read characters into a temporary buffer until
609
+   the line of data is complete or the buffer is full.
610
+  */
611
+  if(cIndex < MIGHTYOHM_DATA_STRING_LENGTH)
612
+  {
613
+    readBuffer[cIndex] = RxByte;
614
+    cIndex += 1;
615
+    readBuffer[cIndex] = 0;
616
+  }
617
+  return;
618
+} 
619
+
620
+/* 
621
+  Send a UDP request packet to an NTP time server and listen for a reply.
622
+  When the reply arrives, parse the received UPD packet and compute unix
623
+  epoch time.  Then set the local system clock to the epoch time.
624
+*/
625
+int syncToNetworkTime()
626
+{
627
+  /*
628
+   Send a request to the NTP time server.
629
+  */
630
+  byte packetBuffer[ NTP_PACKET_SIZE]; //buffer to hold outgoing and incoming packets 
631
+  /*
632
+   Send an NTP packet to the time server and allow for network lag
633
+   before checking if a reply is available.
634
+  */
635
+  sendNTPpacket(packetBuffer);
636
+  delay(2000);  // allow 2000 milli-seconds for network lag
637
+
638
+  /*
639
+   Wait for response from NTP time server.
640
+  */
641
+  if ( Udp.parsePacket() )
642
+  {  
643
+    /*
644
+     A UDP packet has arrived, so read the data from it.
645
+    */
646
+    Udp.read( packetBuffer, NTP_PACKET_SIZE );
647
+    /*
648
+     The timestamp starts at byte 40 of the received packet and is four bytes,
649
+     or two words, long. First, esxtract the two words.
650
+    */
651
+    unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);
652
+    unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);
653
+    /*  
654
+     Combine the four bytes (two words) into a long integer
655
+     this is NTP time (seconds since Jan 1 1900).
656
+    */
657
+    unsigned long secsSince1900 = highWord << 16 | lowWord;
658
+    /*  
659
+     Now convert NTP time into UTC time.  Note that
660
+     Unix time starts on Jan 1 1970. In seconds,
661
+     that's 2208988800.  Therfore,
662
+     
663
+         epoch = secsSince1900 - 2208988800UL
664
+         
665
+     Set the local system clock with this value.
666
+    */
667
+    setTime(secsSince1900 - 2208988800UL);
668
+    return 1;
669
+  }
670
+  else
671
+  {
672
+    return 0;
673
+  } /* end if */
674
+}
675
+
676
+/*
677
+  Send an NTP request to the NTP time server.
678
+*/
679
+void sendNTPpacket( byte* packetBuffer )
680
+{
681
+  /*
682
+   Set all bytes in the buffer to 0.
683
+  */
684
+  memset( packetBuffer, 0, NTP_PACKET_SIZE );
685
+  /*
686
+   Initialize values needed to form NTP request.
687
+  */
688
+  packetBuffer[0] = 0b11100011;  // LI, Version, Mode
689
+  packetBuffer[1] = 0;           // Stratum, or type of clock
690
+  packetBuffer[2] = 6;           // Polling Interval
691
+  packetBuffer[3] = 0xEC;        // Peer Clock Precision
692
+  /*
693
+   Set the remaining 8 bytes to zero for Root Delay & Root Dispersion.
694
+  */
695
+  packetBuffer[12]  = 49; 
696
+  packetBuffer[13]  = 0x4E;
697
+  packetBuffer[14]  = 49;
698
+  packetBuffer[15]  = 52;
699
+  /*
700
+   All NTP fields have been given values, so now
701
+   send a packet requesting a timestamp.
702
+  */ 		
703
+  Udp.beginPacket( ntpIpAddr, 123 ); //NTP requests are to port 123
704
+  Udp.write( packetBuffer, NTP_PACKET_SIZE );
705
+  Udp.endPacket();
706
+  return;
707
+}
708
+
709
+/***  COMMAND LINE INTERFACE  ***/
710
+
711
+/*
712
+ Print a command menu to the USB port.  Then wait for a
713
+ response from the user.  When the response has been
714
+ received, execute the command.
715
+*/
716
+void commandMode()
717
+{
718
+  char sCmdBuf[2];
719
+  
720
+  getCurrentIP();  //used for display of settings
721
+  
722
+  while(true)
723
+  {
724
+    /*
725
+     Print the menu.
726
+    */
727
+    Serial.print( F("\n"                         \
728
+                  "1 - view settings\r\n"        \
729
+                  "2 - set IP address\r\n"       \
730
+                  "3 - set NTP server\r\n"       \
731
+                  "4 - toggle verbose\r\n"       \
732
+                  "5 - exit without saving\r\n"  \
733
+                  "6 - save & restart\r\n"       \
734
+                  ">"));
735
+    /*
736
+     Get the command from the user.
737
+    */
738
+    getSerialLine(sCmdBuf, 2);
739
+    Serial.print(F("\n\n\r"));
740
+    /* 
741
+     Execute the command.
742
+    */
743
+    switch (sCmdBuf[0])
744
+    {
745
+      case '1':
746
+        displaySettings();
747
+        break;
748
+      case '2':
749
+        setIP();
750
+        break;
751
+      case '3':
752
+        setNTPIP();
753
+        break;
754
+      case '4':
755
+        toggleVerbose();
756
+        break;
757
+      case '5':
758
+        readSettingsFromEEPROM();
759
+        return;
760
+      case '6':
761
+        writeSettingsToEEPROM();
762
+        /*
763
+         A software reboot is necessary to force the 
764
+         Arduino to request an IP address from a DHCP
765
+         server or to initialize the Ethernet interface
766
+         with a static IP address.
767
+        */
768
+        software_Reset();
769
+        return;
770
+      default:
771
+        Serial.println(F("invalid command"));
772
+    } /* end switch */
773
+  } /* end while */
774
+  return;
775
+}
776
+
777
+/*
778
+ Displays the current system settings.  Displays
779
+ RadMon software version, local IP address, NTP server
780
+ address, and verbose mode setting.
781
+*/
782
+void displaySettings()
783
+{
784
+  char sBuf[16];
785
+  
786
+  // Display RadMon version
787
+  Serial.print(F("Firmware "));
788
+  Serial.print(F(RADMON_VERSION));
789
+  Serial.println();
790
+
791
+  // Display local IP address
792
+  sprintf(sBuf, "%d.%d.%d.%d", ipAddr[0], ipAddr[1], ipAddr[2], ipAddr[3]);
793
+  if (bUseStaticIP)
794
+  {
795
+    Serial.print(F("Static IP: "));
796
+  }
797
+  else
798
+  {
799
+    Serial.print(F("DHCP IP: "));
800
+  }
801
+  Serial.println(sBuf);
802
+  
803
+  // Display NTP server IP address
804
+  sprintf(sBuf, "%d.%d.%d.%d", ntpIpAddr[0], ntpIpAddr[1], ntpIpAddr[2], ntpIpAddr[3]);
805
+  Serial.print(F("NTP server: ")); 
806
+  Serial.println(sBuf);
807
+
808
+  // Display verbose mode setting
809
+  printVerboseMode();
810
+  return;
811
+}
812
+
813
+/*
814
+ Sets the local IP address. If the user sends a carriage
815
+ return as the first character, then switch to acquiring
816
+ IP address via DHCP server.
817
+*/
818
+void setIP()
819
+{
820
+  char sBuf[16];
821
+
822
+  Serial.print(F("enter IP (<CR> for DHCP): "));
823
+  getSerialLine(sBuf, 16);
824
+  
825
+  if(strlen(sBuf) == 0)
826
+  {
827
+    bUseStaticIP = false;
828
+    strcpy(sBuf, "0.0.0.0");
829
+    parseIpAddress(ipAddr, sBuf);
830
+  }
831
+  else
832
+  {
833
+    bUseStaticIP = true;
834
+    parseIpAddress(ipAddr, sBuf);
835
+  }
836
+  Serial.println();
837
+  return;
838
+}
839
+
840
+/*
841
+ Sets the NTP server IP address.  If the user sends a
842
+ carriage return as the first character, then use the
843
+ default IP address for the NTP server.
844
+*/
845
+void setNTPIP()
846
+{
847
+  char sBuf[16];
848
+  
849
+  Serial.print(F("enter IP (<CR> for default): "));
850
+  getSerialLine(sBuf, 16);
851
+  
852
+  if (strlen(sBuf) == 0)
853
+  {
854
+    strcpy(sBuf, DEFAULT_NTP_SERVER_IP_ADDR);
855
+    parseIpAddress(ntpIpAddr, sBuf);
856
+  }
857
+  else
858
+  {
859
+    parseIpAddress(ntpIpAddr, sBuf);
860
+  }
861
+  Serial.println();
862
+  return;
863
+}
864
+
865
+/*
866
+ Turns verbose mode ON or OFF.
867
+*/
868
+void toggleVerbose()
869
+{
870
+  bVerbose = !bVerbose;
871
+  printVerboseMode();
872
+  return;
873
+}
874
+
875
+/***  GENERAL HELPER FUNCTIONS  ***/
876
+
877
+/*
878
+ Print current verbose mode.
879
+*/
880
+void printVerboseMode()
881
+{
882
+  Serial.print(F("Verbose: "));
883
+  if (bVerbose)
884
+  {
885
+    Serial.println(F("ON"));
886
+  }
887
+  else
888
+  {
889
+    Serial.println(F("OFF"));
890
+  }
891
+  return;
892
+}
893
+
894
+/*
895
+ Get the current IP address from the Ethernet interface
896
+*/
897
+void getCurrentIP()
898
+{
899
+  ipAddr[0] = Ethernet.localIP()[0];
900
+  ipAddr[1] = Ethernet.localIP()[1];
901
+  ipAddr[2] = Ethernet.localIP()[2];
902
+  ipAddr[3] = Ethernet.localIP()[3];
903
+  return;
904
+}
905
+
906
+/*
907
+ Gets a line of data from the user via USB port.
908
+*/
909
+char* getSerialLine(char* sBuffer, int bufferLength)
910
+{
911
+  byte index;
912
+  char cRx;
913
+
914
+  /* 
915
+   Discard extranious characters that may still be in the
916
+   USB serial stream read buffer.  Most often these characters
917
+   will be unprocessed carriage return or line feed characters.
918
+  */
919
+  delay(10);
920
+  while (Serial.available())
921
+  {
922
+    cRx = Serial.read();
923
+  }
924
+
925
+  /*
926
+   Read and process characters from the user as they arrive in
927
+   the USB serial read buffer.
928
+  */
929
+  index = 0;
930
+  while(true)
931
+  {
932
+    /*
933
+     Wait until the user starts pressing keys and bytes
934
+     arrive in the serial read buffer.
935
+    */
936
+    if (Serial.available())
937
+    {
938
+      cRx = Serial.read();
939
+      if (cRx == '\r' || cRx == '\n')
940
+      {
941
+        /*
942
+         The user has finished typing the command and
943
+         has pressed the Enter key. So, discard the
944
+         carriage return and newline characters and then
945
+         return control to the calling function.
946
+        */
947
+        break;
948
+      }
949
+      else if (cRx == 8 || cRx == 127)
950
+      {
951
+        if (index > 0)
952
+        {
953
+          /*
954
+           The user has hit the delete-backspace key,
955
+           so send out a backspace, followed by a space,
956
+           followed by another backspace character.
957
+           This allows for in-line ediiting.
958
+          */
959
+          Serial.write(8);
960
+          Serial.write(32);
961
+          Serial.write(8); 
962
+          index -= 1;
963
+        }
964
+      }
965
+      else if ( index < (bufferLength - 1) )
966
+      {
967
+        /*
968
+         The received character is valid, so write it
969
+         to the buffer. Once the buffer becomes full
970
+         do not write any more characters to it.  When
971
+         the user pressses the enter key, the string
972
+         will be null terminated and control will pass
973
+         back to the calling function.
974
+        */
975
+        Serial.write(cRx); // echo character to terminal
976
+        sBuffer[index] = cRx;
977
+        index += 1;
978
+      } /* end if */
979
+    } /* end if */
980
+  } /* end while */
981
+  sBuffer[index] = 0; // terminate the string
982
+  return sBuffer;
983
+}
984
+
985
+/*
986
+ Writes system configuration settings to non-volitile
987
+ EEPROM.  The items written are the local IP address,
988
+ the NTP server IP address, the state of verbose mode,
989
+ and local IP mode (static or DHCP).
990
+*/
991
+void writeSettingsToEEPROM()
992
+{
993
+  byte ix;
994
+  for (ix = 0; ix < 4; ix++)
995
+  {
996
+    EEPROM.write(ix, ipAddr[ix]);
997
+    EEPROM.write(ix + 4, ntpIpAddr[ix]);
998
+  }
999
+  EEPROM.write(8, bVerbose);
1000
+  EEPROM.write(9, bUseStaticIP);
1001
+  return;
1002
+}
1003
+
1004
+/*
1005
+ Reads system configuration settings from non-volitile
1006
+ EEPROM.  The items read are the local IP address,
1007
+ the NTP server IP address, the state of verbose mode,
1008
+ and local IP mode (static or DHCP).
1009
+*/
1010
+void readSettingsFromEEPROM()
1011
+{
1012
+  byte ix;
1013
+  for (ix = 0; ix < 4; ix++)
1014
+  {
1015
+    ipAddr[ix] = EEPROM.read(ix);
1016
+    ntpIpAddr[ix] = EEPROM.read(ix + 4);
1017
+  }
1018
+  bVerbose = EEPROM.read(8);
1019
+  bUseStaticIP = EEPROM.read(9);
1020
+  return;
1021
+}
1022
+
1023
+/*
1024
+ Parses an IP address given in "nnn.nnn.nnn.nnn" string
1025
+ format into four bytes and stores them in an array. Note
1026
+ that this function destroys the contents of the sIP
1027
+ character array.  Therefore this array cannot be
1028
+ reinitialized after calling this function.
1029
+*/
1030
+void parseIpAddress(byte* byBuf, char* sIP)
1031
+{
1032
+  byBuf[0] = atoi(strtok(sIP, "."));
1033
+  byBuf[1] = atoi(strtok(NULL, "."));
1034
+  byBuf[2] = atoi(strtok(NULL, "."));
1035
+  byBuf[3] = atoi(strtok(NULL, "."));
1036
+  return;
1037
+}
1038
+
1039
+/*
1040
+ Restarts the Uno and runs this program from beginning.  This
1041
+ function gets called after a change made from the user
1042
+ interface when the user selects "save and restart".
1043
+*/
1044
+void software_Reset() 
1045
+{
1046
+  asm volatile ("  jmp 0");
1047
+  return; 
1048
+}  
1049
+
0 1050
new file mode 100644
1 1051
Binary files /dev/null and b/docs/DIY Radmon Project Description.pdf differ