Shop OBEX P1 Docs P2 Docs Learn Events
Spinneret Web Server with SNTP RTC Sync and DHCP IP Renewal — Parallax Forums

Spinneret Web Server with SNTP RTC Sync and DHCP IP Renewal

Mike GMike G Posts: 2,702
edited 2013-04-10 19:34 in Accessories
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.
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

  • Beau SchwabeBeau Schwabe Posts: 6,566
    edited 2013-02-17 16:37
    Mike,

    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
  • Mike GMike G Posts: 2,702
    edited 2013-02-17 16:53
    I believe I grabbed the latest code; v2.01. I just double checked the source.
    v2.01   02-02-2013              - Logic order error with previous bug fix 
    
  • Beau SchwabeBeau Schwabe Posts: 6,566
    edited 2013-02-17 16:57
    Then you have the correct version ... just checking :-)
  • Mike GMike G Posts: 2,702
    edited 2013-02-17 16:59
    Beau, Do you know how to handle Arizona time with SNTP. We do not have day light savings time here in AZ. I kinda' lightly looked around for an Arizona setting but did not see one, I missed it, or I'm just too lazy to figure it out myself.
  • Beau SchwabeBeau Schwabe Posts: 6,566
    edited 2013-02-17 19:32
    I briefly looked over the SNTP protocol to make sure that a byte was not hiding in the packet that would indicate the use of daylight savings or not and didn't see that this information is conveyed. I also looked for specific SNTP servers that were specific to Arizona. The University of Arizona looked like it used to have an SNTP server at one time but the information was vague. So for Arizona why not use GMT-7 ? How do you normally specify the time zone for your PC?
  • Mike GMike G Posts: 2,702
    edited 2013-02-18 08:06
    So for Arizona why not use GMT-7 ? How do you normally specify the time zone for your PC?
    I'll have to do more research. In the past I just accept that half the year the time is off by an hour. It seems that the server update the time they send back during daylight saving time.

    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.
  • Beau SchwabeBeau Schwabe Posts: 6,566
    edited 2013-02-18 08:29
    The best thing to do might be to find an SNTP server that strictly run on mountain time.... be it from Arizona or somewhere else. Frankly I'm surprise that Daylight savings is not indicated in the SNTP packet. They include a Leap Year "warning" byte, but not any provision indicating that Daylight savings in in effect. If that were the case, you could simple poll the byte and make the adjustment automatically.
  • RforbesRforbes Posts: 281
    edited 2013-02-18 15:38
    Seems that if we know the SNTP server is adjusting for daylight savings time, we can "de-adjust" for it in code. Couldn't remember when DST is in effect but found it.

    Starting in 2007, it is observed from the second Sunday in March to the first Sunday in November
  • RforbesRforbes Posts: 281
    edited 2013-02-26 17:53
    Since this post really peaked my curiosity, I have read a little more on it but haven't found a definitive answer. It "seems" that ntp servers do NOT adjust for daylight savings time.... which makes sense, because a good portion of the world doesn't implement it. So because of that, it's up to the end software developer to implement dst functionality... which would solve Mike G's issue of not needing/wanting dst because it doesn't apply to him.

    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.
  • Beau SchwabeBeau Schwabe Posts: 6,566
    edited 2013-02-26 20:08
    "It "seems" that ntp servers do NOT adjust for daylight savings time" - I think you are probably correct. If you want DST, then you will have to implement it yourself in code.
  • RforbesRforbes Posts: 281
    edited 2013-04-10 19:34
    Mike/Beau,

    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
    
    
Sign In or Register to comment.