Spinneret Web Server with SNTP RTC Sync and DHCP IP Renewal
That's a lot of acronyms. The WebServer_5100.spin (Spinneret) library has been updated to include Beau's SNTP library. On start up, the SNTP server is queried. The time returned is used to update the RTC. The marked time is also used to renew the DHCP lease in 12 hour increments.
WebServer_5100.spin contains four utilities. Two are old demos that allow clients to set and get PIN IO parameters. The other two are newer and related to the RTC and SNTP.
Let's say you want to light the Spinneret user LED.
Get the RTC time
Sync RTC with an SNTP server and return the time
Don't invoke the SNTP time multiple times quickly. Maybe once every 15 seconds - if that. Otherwise, you might get your IP blocked for a bit.
There are many SNTP server to choose from. Pick one that's close to you and update the DAT sections.
http://tf.nist.gov/tf-cgi/servers.cgi
Don't forget to set your time zone in the CON block
Let me know if you find any problems.
Please, always check the Google code for the latest source code.
WebServer_5100.spin contains four utilities. Two are old demos that allow clients to set and get PIN IO parameters. The other two are newer and related to the RTC and SNTP.
Let's say you want to light the Spinneret user LED.
http://192.168.1.110/pinstate.xml?led=23&value=1
Value = 1 -> Turn On
Value = 0 -> Turn Off
Value = -1 -> Query State
Get the RTC time
http://192.168.1.110/time.xml
<root>
<time>02/17/2013 16:59:25</time>
</root>
Sync RTC with an SNTP server and return the time
Don't invoke the SNTP time multiple times quickly. Maybe once every 15 seconds - if that. Otherwise, you might get your IP blocked for a bit.
http://192.168.1.110/sntptime.xml
<root>
<time>02/17/2013 17:01:38</time>
</root>
There are many SNTP server to choose from. Pick one that's close to you and update the DAT sections.
http://tf.nist.gov/tf-cgi/servers.cgi
DAT
[B]sntpIp byte 64, 147, 116, 229[/B] '<- This SNTP server is on the west coast
version byte "1.1", $0
Don't forget to set your time zone in the CON block
{{ USA Standard Time Zone Abbreviations }}
#-10, HST,AtST,_PST,MST,CST,EST,AlST
{{ USA Daylight Time Zone Abbreviations }}
#-9, HDT,AtDT,PDT,MDT,CDT,EDT,AlDT
[B]Zone = MST[/B] '<- Insert your timezone
'Let me know if you find any problems.
Please, always check the Google code for the latest source code.
Comments
Did you implement the latest bug fix in the SNTP driver? I think I have solved the issue here...
http://forums.parallax.com/showthread.php/145697-SNTP-Simple-Network-Time-Protocol-BUG-fix-Update
v2.01 02-02-2013 - Logic order error with previous bug fix
On a Windows PC there is a setting for Arizona.
It's not critical. I was hoping to get the time via SNTP and not write software to switch between PST and MST.
Starting in 2007, it is observed from the second Sunday in March to the first Sunday in November
Read more: Daylight Saving Time — Infoplease.com http://www.infoplease.com/spot/daylight1.html#ixzz2LITYVtc6
Am I correct in this assumption? I haven't found anything that says definitively one way or another. But to me, it makes sense that a time server would not adjust for this automatically... it just spits out the actual number of seconds since whatever date the protocol works with.
I forgot to post this earlier. I realize my humantime method isn't quite as sophisticated as what was Beau wrote, but I wanted to get the daylight savings time feature finished. Since I use my own humantime method instead of the original one in the object, I'll just post it here to show the concept.
It works great, and is quite easy to expand on. It's really nothing more than checking a set of ntp values that are encountered during daylight savings time periods. Finally, I've made a statement instead of asking more questions! heh.
OBJ {{ ****************************************************************** * SNTP Simple Network Time Protocol v1.2 * * Author: Beau Schwabe * * * * Recognition: Benjamin Yaroch, A.G.Schmidt * * * * Copyright (c) 2011 Parallax * * See end of file for terms of use. * ****************************************************************** Revision History: 04-07-2011 - File created 09-08-2011 - Minor code update to correct days in Month rendering - and replace bytefill with bytemove for the 'ref-id' string 12-07-2012 -Rewrote HumanTime method to address leap year issue. Also, fix incorrect byte addressing in several methods. 03-31-2013 -Implemented daylightsavings feature. }} Var long seconds,minutes,hours,day,month,year,dow,daycnt PUB CreateUDPtimeheader(BufferAddress,IPAddr) '--------------------------------------------------------------------- ' UDP IP Address - 4 Bytes '--------------------------------------------------------------------- BYTEMOVE(BufferAddress,IPAddr,4) 'Destination value count '--------------------------------------------------------------------- ' UDP Header - 4 Bytes '--------------------------------------------------------------------- byte[BufferAddress][4] := 0 byte[BufferAddress][5] := 123 '<- Port Address byte[BufferAddress][6] := 0 byte[BufferAddress][7] := 48 '<- Header + Packet '--------------------------------------------------------------------- ' UDP Packet - 44 Bytes '--------------------------------------------------------------------- byte[BufferAddress][8] := _100_011 'leap,version, and mode byte[BufferAddress][9] := 0 'stratum byte[BufferAddress][10] := 0 'Poll byte[BufferAddress][11] := 010100 'precision byte[BufferAddress][12] := 0 'rootdelay byte[BufferAddress][13] := 0 'rootdelay byte[BufferAddress][14] := 0 'rootdispersion byte[BufferAddress][15] := 0 'rootdispersion bytemove(BufferAddress+16,string("LOCL"),4) 'ref-id ; four-character ASCII string bytefill(BufferAddress+20,0,32) '(ref, originate, receive, transmit) time { byte 8= leap = ; alarm condition (clock not synchronized) byte 8= version = 1 or 0 ; Version 3 or 4 byte 8= Mode = 1 ; Client byte 9= stratum = 000000 ; unspecified byte 10= Poll = 000000 ; = 2^n seconds (maximum interval between successive messages) byte 11= precision = 010100 ; -20 (8-bit signed integer) byte 12,13= rootdelay = 0 ; 32 bit value byte 14,15= rootdispersion = 0 ; 32 bit value byte 16 to 19= ref id = "LOCL" ; four-character ASCII string byte 20 to 27= ref time = 0 ; 64 bit value (8 bytes, 2 longs each) byte 28 to 35= originate time = 0 ; 64 bit value byte 36 to 43= receive time = 0 ; 64 bit value byte 44 to 51= transmit time = 0 ; 64 bit value } { PUB GetMode(BufferAddress) result := byte[BufferAddress][8] & 000111 '0 - reserved '1 - symmetric active '2 - symmetric passive '3 - client '4 - server '5 - broadcast '6 - reserved for NTP control message '7 - reserved for private use PUB GetVersion(BufferAddress) result := (byte[BufferAddress][8] & 111000)>>3 '3 - Version 3 (IPv4 only) '4 - Version 4 (IPv4, IPv6 and OSI) PUB GetLI(BufferAddress) result := (byte[BufferAddress][8] & 000000)>>6 '0 - No warning '1 - last minute has 61 seconds '2 - last minute has 59 seconds '3 - alarm condition (clock not synchronized) PUB GetStratum(BufferAddress) result := byte[BufferAddress][9] '0 - unspecified or unavailable '1 - primary reference (e.g., radio clock) '2-15 - secondary reference (via NTP or SNTP) '16-255 - reserved PUB GetPoll(BufferAddress) result := byte[BufferAddress][10] 'This is an eight-bit signed integer indicating the 'maximum interval between successive messages, in seconds 'to the nearest power of two. The values that can appear 'in this field presently range from 4 (16 s) to 14 (16384 s); 'however, most applications use only the sub-range 6 (64 s) 'to 10 (1024 s). PUB GetPrecision(BufferAddress) result := byte[BufferAddress][11] 'This is an eight-bit signed integer indicating the 'precision of the local clock, in seconds to the nearest 'power of two. The values that normally appear in this 'field range from -6 for mains-frequency clocks to -20 for 'microsecond clocks found in some workstations. PUB GetRootDelay(BufferAddress)|Temp1 Temp1 := byte[BufferAddress][12]<<24+byte[BufferAddress][13]<<16 result := Temp1 'This is a 32-bit signed fixed-point number indicating the 'total roundtrip delay to the primary reference source, in 'seconds with fraction point between bits 15 and 16. Note 'that this variable can take on both positive and negative 'values, depending on the relative time and frequency offsets. 'The values that normally appear in this field range from 'negative values of a few milliseconds to positive values of 'several hundred milliseconds. PUB GetRootDispersion(BufferAddress)|Temp1 Temp1 := byte[BufferAddress][14]<<24+byte[BufferAddress][15]<<16 result := Temp1 'This is a 32-bit unsigned fixed-point number indicating the 'nominal error relative to the primary reference source, in 'seconds with fraction point between bits 15 and 16. The values 'that normally appear in this field range from 0 to several 'hundred milliseconds. PUB{ Calling example: PST.str(GetReferenceIdentifier(@Buffer,string("----")) dashes get replaced with 4-Character Buffer contents } GetReferenceIdentifier(BufferAddress,FillAddress) bytemove(FillAddress,BufferAddress+16,4) result := FillAddress { Reference Identifier return codes Code External Reference Source ----------------------------------------------------------- LOCL uncalibrated local clock used as a primary reference for a subnet without external means of synchronization PPS atomic clock or other pulse-per-second source individually calibrated to national standards ACTS NIST dialup modem service USNO USNO modem service PTB PTB (Germany) modem service TDF Allouis (France) Radio 164 kHz DCF Mainflingen (Germany) Radio 77.5 kHz MSF Rugby (UK) Radio 60 kHz WWV Ft. Collins (US) Radio 2.5, 5, 10, 15, 20 MHz WWVB Boulder (US) Radio 60 kHz WWVH Kaui Hawaii (US) Radio 2.5, 5, 10, 15 MHz CHU Ottawa (Canada) Radio 3330, 7335, 14670 kHz LORC LORAN-C radionavigation system OMEG OMEGA radionavigation system GPS Global Positioning Service GOES Geostationary Orbit Environment Satellite } PUB GetReferenceTimestamp(Offset,BufferAddress,Long1,Long2)|Temp1 Temp1 := byte[BufferAddress][20]<<24+byte[BufferAddress][21]<<16 Temp1 += byte[BufferAddress][22]<<8 +byte[BufferAddress][23] long[Long1]:=Temp1 Temp1 := byte[BufferAddress][24]<<24+byte[BufferAddress][25]<<16 Temp1 += byte[BufferAddress][26]<<8 +byte[BufferAddress][27] long[Long2]:=Temp1 'This is the time at which the local clock was 'last set or corrected, in 64-bit timestamp format. HumanTime(Offset,Long2) PUB GetOriginateTimestamp(Offset,BufferAddress,Long1,Long2)|Temp1 Temp1 := byte[BufferAddress][28]<<24+byte[BufferAddress][29]<<16 Temp1 += byte[BufferAddress][30]<<8 +byte[BufferAddress][31] long[Long1]:=Temp1 Temp1 := byte[BufferAddress][32]<<24+byte[BufferAddress][33]<<16 Temp1 += byte[BufferAddress][34]<<8 +byte[BufferAddress][35] long[Long2]:=Temp1 'This is the time at which the request departed the 'client for the server, in 64-bit timestamp format. HumanTime(Offset,Long2) PUB GetReceiveTimestamp(Offset,BufferAddress,Long1,Long2)|Temp1 Temp1 := byte[BufferAddress][36]<<24+byte[BufferAddress][37]<<16 Temp1 += byte[BufferAddress][38]<<8 +byte[BufferAddress][39] long[Long1]:=Temp1 Temp1 := byte[BufferAddress][40]<<24+byte[BufferAddress][41]<<16 Temp1 += byte[BufferAddress][42]<<8 +byte[BufferAddress][43] long[Long2]:=Temp1 'This is the time at which the request arrived at 'the server, in 64-bit timestamp format. HumanTime(Offset,Long2) } PUB GetTransmitTimestamp(Offset,DLS,BufferAddress, Long1, Long2) |Temp1 'Zone, @Tempbuff, @longHIGH, @longLOW Temp1 := byte[BufferAddress][44]<<24+byte[BufferAddress][45]<<16 Temp1 += byte[BufferAddress][46]<<8 +byte[BufferAddress][47] long[Long1]:=Temp1 Temp1 := byte[BufferAddress][48]<<24+byte[BufferAddress][49]<<16 Temp1 += byte[BufferAddress][50]<<8 +byte[BufferAddress][51] long[Long2]:=Temp1 'This is the time at which the reply departed the 'server for the client, in 64-bit timestamp format. HumanTime(Offset,DLS,Long2) 'Let's get some humantime! PUB HumanTime(Offset,DLS,TimeStamp)|Totaldays,DSA 'By starting with a smaller number of seconds for this loop stuff, we do the loops less times. 'Less loop time= faster completion time. DSA:=0 If DLS 'Adjust for daylight savings, if active and used in your time zone. DSA:=DST(long[Timestamp]) seconds:=long[TimeStamp]+DSA seconds:=seconds-3_534_364_800 'Adjust the NTP value for 2012: 3_534_364_800 'is the value for 2012, Jan 01, 0:00:00 hours. seconds:=seconds+(Offset*3600) year:=2012 'This is our starting year. month:=0 day:=0 daycnt:=Totaldays:=(seconds/86400) seconds:=seconds-(Totaldays*86400) repeat if (leapyear(year)) repeat month from 1 to 12 repeat day from 1 to byte[@DiMLY][month] Totaldays-- If Totaldays<0 quit if Totaldays<0 quit if NOT (leapyear(year)) repeat month from 1 to 12 repeat day from 1 to byte[@DiM][month] Totaldays-- If Totaldays<0 quit if Totaldays<0 quit if Totaldays<0 quit year++ '========================================== 'Once the loop above completes, we'll be left with less than a days worth of seconds. 'So, let's see how many hours, minutes and seconds_low we have left. hours:=0 minutes:=0 repeat while (seconds=>60) seconds-=60 minutes++ if minutes == 60 hours++ minutes:=0 '========================================= 'What's the day of the week? (Assuming Sunday== 1st day of week) dow:=1 'This is the start day because Jan 01, 2012 was a Sunday.) repeat while daycnt>0 daycnt-- dow++ If dow==8 dow:=1 '========================================= 'Once the loop above completes, we have our time and date data ready to use. long[TimeStamp][1] := month<<24+day<<16+year long[TimeStamp][2] := dow<<24+hours<<16+minutes<<8+seconds pri leapyear(check) Case check 2012: return 1 'Enter more leap years to extend the available range of this calculator. 2016: return 1 2020: return 1 2024: return 1 2028: return 1 2032: return 1 2036: return 1 2040: return 1 pri DST(second)|adjustment Case second 3571869600..3592432800: adjustment:= 3600 '2013 3603319200..3623882400: adjustment:= 3600 '2014 3634941600..3655504800: adjustment:= 3600 'etc 3666823200..3687386400: adjustment:= 3600 'etc 3698272800..3718836000: adjustment:= 3600 3729722400..3750285600: adjustment:= 3600 3761172000..3781735200: adjustment:= 3600 3792621600..3813184800: adjustment:= 3600 other: adjustment:= 0 return adjustment { DST = second Sunday in March to the first Sunday in November For 2013- In March, we move Forward 1 hour on Mar 10 at 2 AM and then back one hour on Nov 3 at 2 AM. NTP time values 2013- Mar 10 to Nov 3 3571869600 - 3592432800 2014- Mar 9 to Nov 2 3603319200 - 3623882400 2015- Mar 10 to Nov 3 3634941600 - 3655504800 2016 13 6 3666823200 - 3687386400 2017 12 5 3698272800 - 3718836000 2018 11 4 3729722400 - 3750285600 2019 10 3 3761172000 - 3781735200 2020 8 1 3792621600 - 3813184800 UTC Time = NTP time - 2208988800 } DAT DiMLY byte 0,31 ,29 ,31 ,30 ,31 ,30 ,31 ,31 ,30 ,31 ,30 ,31 DiM byte 0,31 ,28 ,31 ,30 ,31 ,30 ,31 ,31 ,30 ,31 ,30 ,31 'Days in Month 'jan feb mar apr may jun jul aug sep oct nov dec 'First byte in each array is just a placeholder.
And, as a FREE BONUS (insert cheesy commercial music here) here is a decoding method... it doesn't consider daylightsavings, but otherwise it works hand in hand with the above human time method. Adapting it to decode to the correct ntp value with daylight savings wouldn't be too difficult. Maybe I'll do that soon.
pub Decode_Time|temp,Totaldays,y,mo,d,h,m,s {{ Here, we decode the current date and time to a value of seconds. This value is the number of elapsed seconds since Jan 01, 2012 at 0:00:00 }} s:=Second 'This value comes from the spinneret RTC m:=Minute 'This value comes from the spinneret RTC h:=Hour 'This value comes from the spinneret RTC d:=Day 'This value comes from the spinneret RTC mo:=Month 'This value comes from the spinneret RTC y:=Year 'This value comes from the spinneret RTC (-2000) temp+=s 'Start with the current # of seconds temp+=(m*60) 'Add the amount of seconds from the current minute temp+=(h*3600) 'Add the amount of seconds from the current hour temp+=(d*86400) 'Add the amount of seconds from the current day 'This gives us a total # of seconds to the beginning 'of the previous month. mo-- 'Let's decrement our current month by 1. repeat 'Let's start repeating until we're told to quit. if (Leapyear(y)) 'Is the year we're on a leap year? repeat mo 'Let's repeat mo times. If mo=5 (May), we'll repeat 5 times. repeat byte[@DiMLY][mo] 'let's repeat DiMLY[mo] times. If it's Jan, we'll repeat 31 times. Totaldays++ 'Let's increment Totaldays each time we repeat. mo:=12 'Once we're done repeating, let's make mo=12 'Total days now equals the number of whole days since the first of the year. if NOT(Leapyear(y)) 'Same as above, but not a leap year (# of days in Feb is different.) repeat mo repeat byte[@DiM][mo] Totaldays++ mo:=12 y-- 'Let's decrement our current year. if y=<12 'Is the year = or < 12? If so, we're done looping. quit temp+=(Totaldays*86400) 'Let's multiply our Totaldays by 86400 to get total seconds equivalent. 'Then we'll add it to our temp seconds to get total seconds. return temp 'Let's return the total # of seconds since Jan 01, 2012 at 0:00:00 pub Leapyear(check) {{ Here, we check to see if a given year is a leap year. }} Case check 12: return 1 'Enter more leap years to extend the available range of this calculator. 16: return 1 20: return 1 24: return 1 28: return 1 32: return 1 36: return 1 40: return 1